diff --git a/.github/workflows/bgfx-builds.yml b/.github/workflows/bgfx-builds.yml new file mode 100644 index 00000000000..f2855b3262b --- /dev/null +++ b/.github/workflows/bgfx-builds.yml @@ -0,0 +1,383 @@ +name: BGFX Builds + +run-name: bgfx builds from ${{ inputs.ref || github.ref_name }} + +permissions: + contents: write + +on: + push: + branches: + - bobtista/topic/trunk + workflow_dispatch: + inputs: + ref: + description: "Branch, tag, or SHA to build" + required: false + default: "bobtista/topic/trunk" + type: string + publish: + description: "Update the latest-bgfx rolling release" + required: false + default: true + type: boolean + +concurrency: + group: bgfx-builds + cancel-in-progress: true + +env: + FFMPEG_WIN64_ASSET: ffmpeg-n8.1-latest-win64-gpl-shared-8.1 + +jobs: + win64: + name: ${{ matrix.preset }} + strategy: + fail-fast: false + matrix: + include: + - preset: win64 + config: Release + zip: GeneralsZH-win64 + - preset: win64-debug + config: Debug + zip: GeneralsZH-win64-debug + runs-on: windows-2022 + timeout-minutes: 120 + outputs: + sha: ${{ steps.sha.outputs.sha }} + steps: + - name: Checkout Code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ inputs.ref || github.ref_name }} + + - name: Resolve Built Commit + id: sha + shell: pwsh + run: | + "sha=$(git rev-parse HEAD)" >> $env:GITHUB_OUTPUT + + - name: Cache FFmpeg + id: cache-ffmpeg + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: .deps-cache/ffmpeg-x64 + key: ffmpeg-win64-${{ env.FFMPEG_WIN64_ASSET }} + + - name: Download FFmpeg + if: steps.cache-ffmpeg.outputs.cache-hit != 'true' + shell: pwsh + run: | + $asset = "$env:FFMPEG_WIN64_ASSET.zip" + # The rolling alias assets (ffmpeg-nX.Y-latest-*) live in the release TAGGED + # "latest", which is not the same thing as the newest release. Asking for + # releases/latest/download resolves to whatever autobuild-* published most + # recently, and those carry versioned names only, so the pinned asset 404s. + $uri = "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/$asset" + # Every autobuild deletes and re-uploads those alias assets, so a download + # landing in that window 404s on a file that exists minutes either side. + # Retry instead of failing the build over a few seconds of republishing. + $downloaded = $false + foreach ($attempt in 1..5) { + try { + Invoke-WebRequest -Uri $uri -OutFile ffmpeg.zip + $downloaded = $true + break + } catch { + Write-Host "FFmpeg download attempt $attempt/5 failed: $($_.Exception.Message)" + if ($attempt -lt 5) { Start-Sleep -Seconds 30 } + } + } + if (-not $downloaded) { throw "FFmpeg download failed after 5 attempts: $uri" } + Expand-Archive -Path ffmpeg.zip -DestinationPath ffmpeg-tmp + $inner = Get-ChildItem -Directory ffmpeg-tmp | Select-Object -First 1 + New-Item -ItemType Directory -Force -Path .deps-cache | Out-Null + Move-Item -Path $inner.FullName -Destination .deps-cache/ffmpeg-x64 + Remove-Item ffmpeg.zip + + - name: Set Up VC2022 Environment + uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 + with: + arch: x64 + + - name: Set Up sccache + # Single-archive local sccache dir cached as ONE entry per run (not the GHA + # per-object backend, which fragmented into thousands of tiny cache entries and + # hit API rate limits -> silent false misses). append-timestamp + restore-key + # prefix warm-restore each run; max-size bounds it well under the repo cache cap. + uses: hendrikmuhs/ccache-action@d62db5f07c26379fc4b4e0916f098a92573c3b03 # v1.2.23 + with: + variant: sccache + key: bgfx-${{ matrix.preset }} + max-size: 2G + verbose: 1 + + - name: Cache CMake Dependencies + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: build\${{ matrix.preset }}\_deps + key: cmake-deps-${{ matrix.preset }}-${{ hashFiles('CMakePresets.json','cmake/**/*.cmake','**/CMakeLists.txt') }} + restore-keys: | + cmake-deps-${{ matrix.preset }}- + + - name: Configure with CMake + shell: pwsh + run: | + cmake --preset ${{ matrix.preset }} -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache + + - name: Build GeneralsMD + shell: pwsh + run: | + cmake --build build/${{ matrix.preset }} --config ${{ matrix.config }} --target z_generals + sccache --show-stats + + - name: Stage and Zip + shell: pwsh + run: | + $build = "build/${{ matrix.preset }}" + $cfg = "${{ matrix.config }}" + $stage = New-Item -ItemType Directory -Force -Path "stage/${{ matrix.zip }}" + + Copy-Item "$build/GeneralsMD/$cfg/*.exe" $stage + if (Test-Path "$build/GeneralsMD/$cfg/*.pdb") { + Copy-Item "$build/GeneralsMD/$cfg/*.pdb" $stage + } + + $dlls = Get-ChildItem -Path "$build/_deps" -Recurse -Include "SDL3*.dll","OpenAL32*.dll" | + Where-Object { $_.FullName -like "*\$cfg\*" } + if (-not $dlls) { + throw "No SDL3/OpenAL runtime DLLs found under $build/_deps for config $cfg" + } + $dlls | Copy-Item -Destination $stage + Copy-Item ".deps-cache/ffmpeg-x64/bin/*.dll" $stage + + @( + "Command & Conquer Generals Zero Hour (GeneralsMD) bgfx build", + "preset: ${{ matrix.preset }} ($cfg)", + "ref: ${{ inputs.ref || github.ref_name }}", + "commit: $(git rev-parse HEAD)", + "built: $((Get-Date).ToUniversalTime().ToString('o'))" + ) | Set-Content "$stage/BUILD_INFO.txt" + + # Ship the default render settings (sun shadow map on). The exe-only zip + # excludes Data\ via the $slim filter below. + New-Item -ItemType Directory -Force -Path "$stage/Data/INI" | Out-Null + Copy-Item "scripts/build/dist/Bgfx.ini" "$stage/Data/INI/Bgfx.ini" + Copy-Item "LICENSE.md","INSTALLING.md" $stage + + Compress-Archive -Path $stage.FullName -DestinationPath "${{ matrix.zip }}.zip" + + $slimStage = New-Item -ItemType Directory -Force -Path "stage/${{ matrix.zip }}-exe-only" + Get-ChildItem "$stage/*" -Include "*.exe","*.pdb","BUILD_INFO.txt" | + Copy-Item -Destination $slimStage + Copy-Item "LICENSE.md","INSTALLING.md" $slimStage + Compress-Archive -Path $slimStage.FullName -DestinationPath "${{ matrix.zip }}-exe-only.zip" + + - name: Upload Artifact + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: ${{ matrix.zip }} + path: | + ${{ matrix.zip }}.zip + ${{ matrix.zip }}-exe-only.zip + retention-days: 30 + if-no-files-found: error + + macos: + name: macos-generalsmd-sdl3-bgfx + runs-on: macos-15 + timeout-minutes: 120 + env: + GGC_MACOS_RUNTIME_DIR: ${{ github.workspace }}/stage-macos/GeneralsZH-macos-arm64 + steps: + - name: Checkout Code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ inputs.ref || github.ref_name }} + + - name: Install Dependencies + run: | + brew install ninja dylibbundler ffmpeg ccache + command -v pkg-config || brew install pkgconf + + - name: Cache CMake Dependencies + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: build/macos-generalsmd-sdl3-bgfx/_deps + key: cmake-deps-macos-generalsmd-sdl3-bgfx-${{ hashFiles('CMakePresets.json','cmake/**/*.cmake','**/CMakeLists.txt') }} + restore-keys: | + cmake-deps-macos-generalsmd-sdl3-bgfx- + + - name: Set Up ccache + # Single-archive ccache dir, append-timestamp + restore-key prefix so each run + # saves a fresh entry and warm-restores the latest; max-size caps total size. + uses: hendrikmuhs/ccache-action@d62db5f07c26379fc4b4e0916f098a92573c3b03 # v1.2.23 + with: + variant: ccache + key: bgfx-macos + max-size: 2G + verbose: 1 + + - name: Configure with CMake + run: | + cmake --preset macos-generalsmd-sdl3-bgfx -DRTS_CRASHDUMP_ENABLE=OFF -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + + - name: Build and Stage GeneralsMD + run: | + bash scripts/build/macos/build-macos-generalsmd.sh + ccache -s + + - name: Zip + run: | + { + echo "Command & Conquer Generals Zero Hour (GeneralsMD) bgfx build" + echo "preset: macos-generalsmd-sdl3-bgfx (Release, arm64)" + echo "ref: ${{ inputs.ref || github.ref_name }}" + echo "commit: $(git rev-parse HEAD)" + echo "built: $(date -u +%Y-%m-%dT%H:%M:%SZ)" + } > "${GGC_MACOS_RUNTIME_DIR}/BUILD_INFO.txt" + cd "$(dirname "${GGC_MACOS_RUNTIME_DIR}")" + zip -ry "${GITHUB_WORKSPACE}/GeneralsZH-macos-arm64.zip" "$(basename "${GGC_MACOS_RUNTIME_DIR}")" + + - name: Upload Artifact + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: GeneralsZH-macos-arm64 + path: GeneralsZH-macos-arm64.zip + retention-days: 30 + if-no-files-found: error + + linux: + name: linux-generalsmd-sdl3-bgfx + runs-on: ubuntu-latest + timeout-minutes: 120 + steps: + - name: Checkout Code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ inputs.ref || github.ref_name }} + + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + build-essential cmake ninja-build pkg-config ccache \ + libx11-dev libxext-dev libxrandr-dev libxi-dev libxcursor-dev \ + libxinerama-dev libxfixes-dev libxrender-dev libxss-dev libxtst-dev \ + libxkbcommon-dev libwayland-dev wayland-protocols libdecor-0-dev \ + libgl1-mesa-dev libegl1-mesa-dev libvulkan-dev \ + libfreetype6-dev libfontconfig1-dev \ + libasound2-dev libpulse-dev \ + nasm curl xz-utils + + - name: Cache CMake Dependencies + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: build/linux-generalsmd-sdl3-bgfx/_deps + key: cmake-deps-linux-generalsmd-sdl3-bgfx-${{ hashFiles('CMakePresets.json','cmake/**/*.cmake','**/CMakeLists.txt') }} + restore-keys: | + cmake-deps-linux-generalsmd-sdl3-bgfx- + + - name: Set Up ccache + uses: hendrikmuhs/ccache-action@d62db5f07c26379fc4b4e0916f098a92573c3b03 # v1.2.23 + with: + variant: ccache + key: bgfx-linux + max-size: 2G + verbose: 1 + + - name: Cache Minimal FFmpeg + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: ${{ github.workspace }}/ffmpeg-min + key: ffmpeg-min-${{ hashFiles('scripts/build/linux/build-ffmpeg-minimal.sh') }} + + - name: Build Minimal FFmpeg + run: | + bash scripts/build/linux/build-ffmpeg-minimal.sh "${GITHUB_WORKSPACE}/ffmpeg-min" + + - name: Configure with CMake + env: + PKG_CONFIG_PATH: ${{ github.workspace }}/ffmpeg-min/lib/pkgconfig + run: | + cmake --preset linux-generalsmd-sdl3-bgfx -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + + - name: Build GeneralsMD + run: | + cmake --build build/linux-generalsmd-sdl3-bgfx --config Release --target z_generals -j"$(nproc)" + ccache -s + + - name: Stage + run: | + bash scripts/build/linux/deploy-linux-generalsmd.sh linux-generalsmd-sdl3-bgfx Release + { + echo "Command & Conquer Generals Zero Hour (GeneralsMD) bgfx build" + echo "preset: linux-generalsmd-sdl3-bgfx (Release, x86_64)" + echo "ref: ${{ inputs.ref || github.ref_name }}" + echo "commit: $(git rev-parse HEAD)" + echo "built: $(date -u +%Y-%m-%dT%H:%M:%SZ)" + } > "${GITHUB_WORKSPACE}/stage-linux/GeneralsZH-linux-x64/BUILD_INFO.txt" + + - name: Zip + run: | + cd "${GITHUB_WORKSPACE}/stage-linux" + zip -ry "${GITHUB_WORKSPACE}/GeneralsZH-linux-x64.zip" GeneralsZH-linux-x64 + + - name: Upload Artifact + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: GeneralsZH-linux-x64 + path: GeneralsZH-linux-x64.zip + retention-days: 30 + if-no-files-found: error + + publish: + name: Update latest-bgfx release + needs: [win64, macos, linux] + if: ${{ always() && needs.win64.result == 'success' && needs.macos.result == 'success' && (github.event_name != 'workflow_dispatch' || inputs.publish) }} + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Download Artifacts + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + path: dist + merge-multiple: true + + - name: Publish Rolling Release + env: + GH_TOKEN: ${{ github.token }} + SHA: ${{ needs.win64.outputs.sha }} + REF: ${{ inputs.ref || github.ref_name }} + run: | + ( + cd dist + LC_ALL=C sha256sum *.zip > SHA256SUMS.txt + ) + + cat > notes.md < + //#define _CAMPEA_DEMO // ---------------------------------------------------------------------------------------------- @@ -287,23 +289,31 @@ typedef UnsignedInt VeterancyLevelFlags; const VeterancyLevelFlags VETERANCY_LEVEL_FLAGS_ALL = 0xffffffff; const VeterancyLevelFlags VETERANCY_LEVEL_FLAGS_NONE = 0x00000000; +inline UnsignedInt getVeterancyLevelFlagBit(VeterancyLevel dt) +{ + // Match the original dx8/x86 behavior for LEVEL_REGULAR: the old + // 1 << (0 - 1) expression wrapped to bit 31 there, but is undefined + // on Clang and can reject regular units from die modules. + return dt == LEVEL_REGULAR ? 31 : static_cast(dt - 1); +} + inline Bool getVeterancyLevelFlag(VeterancyLevelFlags flags, VeterancyLevel dt) { - return (flags & (1UL << (dt - 1))) != 0; + return (flags & (1UL << getVeterancyLevelFlagBit(dt))) != 0; } inline VeterancyLevelFlags setVeterancyLevelFlag(VeterancyLevelFlags flags, VeterancyLevel dt) { - return (flags | (1UL << (dt - 1))); + return (flags | (1UL << getVeterancyLevelFlagBit(dt))); } inline VeterancyLevelFlags clearVeterancyLevelFlag(VeterancyLevelFlags flags, VeterancyLevel dt) { - return (flags & ~(1UL << (dt - 1))); + return (flags & ~(1UL << getVeterancyLevelFlagBit(dt))); } // ---------------------------------------------------------------------------------------------- -#define BOGUSPTR(p) ((((unsigned int)(p)) & 1) != 0) +#define BOGUSPTR(p) (((reinterpret_cast(p)) & 1U) != 0) // ---------------------------------------------------------------------------------------------- #define MAKE_DLINK_HEAD(OBJCLASS, LISTNAME) \ diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index 7a6f4be4d11..f295bbc52aa 100644 --- a/Core/GameEngine/Include/Common/GameDefines.h +++ b/Core/GameEngine/Include/Common/GameDefines.h @@ -87,9 +87,7 @@ #define PRESERVE_RETAIL_SCRIPTED_CAMERA (1) // Retain scripted camera behavior present in retail Generals 1.08 and Zero Hour 1.04 #endif -#ifndef RETAIL_COMPATIBLE_CRC -#define RETAIL_COMPATIBLE_CRC (1) // Game is expected to be CRC compatible with retail Generals 1.08, Zero Hour 1.04 -#endif +// RETAIL_COMPATIBLE_CRC is default defined in BaseDefines.h #ifndef RETAIL_COMPATIBLE_XFER_SAVE #define RETAIL_COMPATIBLE_XFER_SAVE (1) // Game is expected to be Xfer Save compatible with retail Generals 1.08, Zero Hour 1.04 diff --git a/Core/GameEngine/Include/Common/GameMemory.h b/Core/GameEngine/Include/Common/GameMemory.h index 53c0e59619d..b301c4862c0 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 ////////////////////////////////////////////////////////////// @@ -320,6 +324,7 @@ class MemoryPool MemoryPool *getNextPoolInList(); ///< return next pool in linked list void addToList(MemoryPool **pHead); ///< add this pool to head of the linked list void removeFromList(MemoryPool **pHead); ///< remove this pool from the linked list + Bool ownsUserBlockPointer(void *pBlock); #ifdef MEMORYPOOL_DEBUG static void debugPoolInfoReport( MemoryPool *pool, FILE *fp = nullptr ); ///< dump a report about this pool to the logfile const char *debugGetBlockTagString(void *pBlock); ///< return the tagstring for the given block (assumed to belong to this pool) @@ -409,6 +414,7 @@ class DynamicMemoryAllocator /// return the best pool for the given allocSize, or null if none are suitable MemoryPool *findPoolForSize(Int allocSize); + Bool ownsUserBlockPointer(void *pBlock); public: @@ -855,6 +861,10 @@ extern void userMemoryAdjustPoolSize(const char *poolName, Int& initialAllocatio extern void * __cdecl operator new[] (size_t size); extern void __cdecl operator delete[] (void *p); +#if defined(__APPLE__) + extern void __cdecl operator delete (void *p, size_t size); + extern void __cdecl operator delete[] (void *p, size_t size); +#endif // additional overloads to account for VC/MFC funky versions extern void* __cdecl operator new(size_t nSize, const char *, int); diff --git a/Core/GameEngine/Include/Common/Radar.h b/Core/GameEngine/Include/Common/Radar.h index 5e170c261fc..6084f38a8df 100644 --- a/Core/GameEngine/Include/Common/Radar.h +++ b/Core/GameEngine/Include/Common/Radar.h @@ -212,6 +212,11 @@ class Radar : public Snapshot, virtual void newMap( TerrainLogic *terrain ); ///< reset radar for new map + // TheSuperHackers @bugfix bobtista 20/07/2026 Re-hook the radar window pointer to the current + // ControlBar.wnd:LeftHUD after the control bar tree is rebuilt (e.g. a mid-match resolution + // change), without resetting radar data the way newMap does. + void reattachWindow(); + virtual void draw( Int pixelX, Int pixelY, Int width, Int height ) = 0; ///< draw the radar /// empty the entire shroud diff --git a/Core/GameEngine/Include/Common/UnicodeString.h b/Core/GameEngine/Include/Common/UnicodeString.h index 0f3c2333c44..5bd8d6bf2e5 100644 --- a/Core/GameEngine/Include/Common/UnicodeString.h +++ b/Core/GameEngine/Include/Common/UnicodeString.h @@ -52,6 +52,13 @@ class AsciiString; +// TheSuperHackers @bugfix bobtista 10/06/2026 Portable wide vswprintf that keeps MSVC %s/%c +// semantics on every platform. Standard libc vswprintf treats %s/%c as narrow (char*) and +// %S/%C as wide, the opposite of MSVC. Game format strings (including localized .csf text) +// use MSVC semantics (%s == wide), so on non-Windows a wide argument would be truncated at +// its first embedded NUL byte. This rewrites the conversion specifiers before formatting. +Int formatStringW(WideChar* buf, size_t bufCount, const WideChar* format, va_list args); + // ----------------------------------------------------- /** UnicodeString is the fundamental double-byte string type used in the Generals diff --git a/Core/GameEngine/Include/GameClient/ControlBar.h b/Core/GameEngine/Include/GameClient/ControlBar.h index 446277a13be..eb5b7a2eee4 100644 --- a/Core/GameEngine/Include/GameClient/ControlBar.h +++ b/Core/GameEngine/Include/GameClient/ControlBar.h @@ -781,6 +781,7 @@ class ControlBar : public SubsystemInterface void setArrowImage( const Image *arrowImage ){ m_genArrow = arrowImage; } void initSpecialPowershortcutBar( Player *player); + void rebuildSpecialPowerShortcutBarForResolution( Player *player); ///< Rebuild the shortcut bar after a resolution change without replaying the slide-in animation void triggerRadarAttackGlow(); diff --git a/Core/GameEngine/Include/GameClient/Display.h b/Core/GameEngine/Include/GameClient/Display.h index 9fc937bfaa2..4c1ddb1566c 100644 --- a/Core/GameEngine/Include/GameClient/Display.h +++ b/Core/GameEngine/Include/GameClient/Display.h @@ -91,6 +91,7 @@ class Display : public SubsystemInterface virtual void setWindowed( Bool windowed ) { m_windowed = windowed; } ///< set windowed/fullscreen flag virtual Bool getWindowed() { return m_windowed; } ///< return widowed/fullscreen flag virtual Bool setDisplayMode( UnsignedInt xres, UnsignedInt yres, UnsignedInt bitdepth, Bool windowed ); /// +typedef uintptr_t WindowMsgData; //----------------------------------------------------------------------------- enum WindowMsgHandledType CPP_11(: Int) { MSG_IGNORED, MSG_HANDLED }; diff --git a/Core/GameEngine/Include/GameClient/LoadScreen.h b/Core/GameEngine/Include/GameClient/LoadScreen.h index cee1eee9766..6dd1cbefac7 100644 --- a/Core/GameEngine/Include/GameClient/LoadScreen.h +++ b/Core/GameEngine/Include/GameClient/LoadScreen.h @@ -63,6 +63,7 @@ class LoadScreen virtual void update( Int percent ); ///< Update the state of the slider bars virtual void processProgress(Int playerId, Int percentage) = 0; virtual void setProgressRange( Int min, Int max ) = 0; + virtual Bool isReadyForGameStart() const { return TRUE; } protected: void setLoadScreen( GameWindow *g ) { m_loadScreen = g; } GameWindow *m_loadScreen; ///< The GameWindow that is our loadscreen @@ -143,6 +144,7 @@ class ChallengeLoadScreen : public LoadScreen } virtual void setProgressRange( Int min, Int max ) override; + virtual Bool isReadyForGameStart() const override; private: GameWindow *m_progressBar; ///< Pointer to the Progress Bar on the window @@ -154,6 +156,7 @@ class ChallengeLoadScreen : public LoadScreen AudioEventRTS m_ambientLoop; AudioHandle m_ambientLoopHandle; + AudioHandle m_tauntHandle; GameWindow *m_bioNameLeft; GameWindow *m_bioAgeLeft; diff --git a/Core/GameEngine/Include/GameClient/Mouse.h b/Core/GameEngine/Include/GameClient/Mouse.h index 78cd06667ce..7ac3b88dffb 100644 --- a/Core/GameEngine/Include/GameClient/Mouse.h +++ b/Core/GameEngine/Include/GameClient/Mouse.h @@ -283,6 +283,15 @@ class Mouse : public SubsystemInterface virtual void draw() override; ///< draw the mouse virtual void setPosition( Int x, Int y ); ///< set the mouse position + // TheSuperHackers @bugfix bobtista 11/07/2026 Reconcile the engine's mouse + // position with the operating system's actual cursor. The default keeps the + // legacy game-client init behavior (force to the origin; Win32 immediately + // re-syncs through WM_MOUSEMOVE). Backends whose platform never delivers a + // motion event for a stationary cursor (SDL3) override this to read the + // real cursor instead: believing (0,0) with an untouched mouse edge-scrolls + // the camera into the map's top-left corner on loads that drop straight + // into gameplay. + virtual void syncPositionToSystemCursor() { setPosition( 0, 0 ); } virtual void setCursor( MouseCursor cursor ) = 0; ///< set mouse cursor void initCapture(); ///< called once to unlock the mouse capture functionality @@ -431,6 +440,7 @@ class Mouse : public SubsystemInterface CursorCaptureMode m_cursorCaptureMode; CursorCaptureBlockReasonInt m_captureBlockReasonBits; + Bool m_cursorCaptureInitialized; }; 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/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/Core/GameEngine/Include/GameNetwork/IPEnumeration.h b/Core/GameEngine/Include/GameNetwork/IPEnumeration.h index 62c3418249c..53f82079042 100644 --- a/Core/GameEngine/Include/GameNetwork/IPEnumeration.h +++ b/Core/GameEngine/Include/GameNetwork/IPEnumeration.h @@ -69,6 +69,12 @@ class IPEnumeration EnumeratedIP * getAddresses(); ///< Return a linked list of local IP addresses AsciiString getMachineName(); ///< Return the Network Neighborhood machine name + // TheSuperHackers @bugfix bobtista 12/06/2026 Return the subnet-directed broadcast address + // for the interface that owns localIP (host byte order in and out). Used instead of the limited + // broadcast 255.255.255.255 so LAN announces egress the correct interface on multi-homed hosts + // (e.g. a ZeroTier/VPN adapter alongside Wi-Fi). Falls back to INADDR_BROADCAST when unknown. + static UnsignedInt getSubnetBroadcastAddress( UnsignedInt localIP ); + protected: void addNewIP( UnsignedByte a, UnsignedByte b, UnsignedByte c, UnsignedByte d ); diff --git a/Core/GameEngine/Include/GameNetwork/LANAPI.h b/Core/GameEngine/Include/GameNetwork/LANAPI.h index a0365be185a..5b37d2a7bf8 100644 --- a/Core/GameEngine/Include/GameNetwork/LANAPI.h +++ b/Core/GameEngine/Include/GameNetwork/LANAPI.h @@ -33,6 +33,7 @@ #include "GameNetwork/NetworkDefs.h" #include "GameNetwork/LANPlayer.h" #include "GameNetwork/LANGameInfo.h" +#include "Common/UnicodeString.h" //static const Int g_lanPlayerNameLength = 20; static const Int g_lanPlayerNameLength = 12; // reduced length because of game option length @@ -171,7 +172,7 @@ struct LANMessage MSG_REQUEST_GAME_INFO, ///< For direct connect, get the game info from a specific IP Address } messageType; - WideChar name[g_lanPlayerNameLength+1]; ///< My name, for convenience + UnsignedShort name[g_lanPlayerNameLength+1]; ///< My name, for convenience char userName[g_lanLoginNameLength+1]; ///< login name, for convenience char hostName[g_lanHostNameLength+1]; ///< machine name, for convenience @@ -188,13 +189,13 @@ struct LANMessage // GameJoined is sent with REQUEST_GAME_LEAVE struct { - WideChar gameName[g_lanGameNameLength+1]; + UnsignedShort gameName[g_lanGameNameLength+1]; } GameToLeave; // GameInfo if sent with GAME_ANNOUNCE struct { - WideChar gameName[g_lanGameNameLength+1]; + UnsignedShort gameName[g_lanGameNameLength+1]; Bool inProgress; char options[m_lanMaxOptionsLength+1]; Bool isDirectConnect; @@ -204,7 +205,7 @@ struct LANMessage struct { UnsignedInt ip; - WideChar playerName[g_lanPlayerNameLength+1]; + UnsignedShort playerName[g_lanPlayerNameLength+1]; } PlayerInfo; // GameToJoin is sent with REQUEST_JOIN @@ -219,7 +220,7 @@ struct LANMessage // GameJoined is sent with JOIN_ACCEPT struct { - WideChar gameName[g_lanGameNameLength+1]; + UnsignedShort gameName[g_lanGameNameLength+1]; UnsignedInt gameIP; UnsignedInt playerIP; Int slotPosition; @@ -228,7 +229,7 @@ struct LANMessage // GameNotJoined is sent with JOIN_DENY struct { - WideChar gameName[g_lanGameNameLength+1]; + UnsignedShort gameName[g_lanGameNameLength+1]; UnsignedInt gameIP; UnsignedInt playerIP; LANAPIInterface::ReturnType reason; @@ -237,14 +238,14 @@ struct LANMessage // Accept is sent with SET_ACCEPT struct { - WideChar gameName[g_lanGameNameLength+1]; + UnsignedShort gameName[g_lanGameNameLength+1]; Bool isAccepted; } Accept; // Accept is sent with MAP_AVAILABILITY struct { - WideChar gameName[g_lanGameNameLength+1]; + UnsignedShort gameName[g_lanGameNameLength+1]; UnsignedInt mapCRC; // to make sure we're talking about the same map Bool hasMap; } MapStatus; @@ -252,9 +253,9 @@ struct LANMessage // Chat is sent with CHAT struct { - WideChar gameName[g_lanGameNameLength+1]; + UnsignedShort gameName[g_lanGameNameLength+1]; LANAPIInterface::ChatType chatType; - WideChar message[g_lanMaxChatLength+1]; + UnsignedShort message[g_lanMaxChatLength+1]; } Chat; // GameOptions is sent with GAME_OPTIONS @@ -269,6 +270,33 @@ struct LANMessage static_assert(sizeof(LANMessage) <= MAX_LANAPI_PACKET_SIZE, "LANMessage struct cannot be larger than the max packet size"); +// TheSuperHackers @bugfix bobtista 09/06/2026 The LAN wire protocol stores wide +// strings as fixed 16-bit UTF-16 code units (the retail Win32 wire format) so the +// byte layout of LANMessage is identical on every platform. WideChar is wchar_t, +// which is 2 bytes on Windows but 4 bytes on macOS/Linux; copying it onto the wire +// raw corrupted player/game names and shifted every following field, which broke +// cross-platform LAN discovery and direct-connect joins. +inline void lanWideStrCopy(UnsignedShort *dst, const WideChar *src, Int dstCount) +{ + Int i = 0; + for (; i < dstCount - 1 && src[i] != 0; ++i) + { + dst[i] = (UnsignedShort)src[i]; + } + dst[i] = 0; +} + +inline UnicodeString lanWideStrToUnicode(const UnsignedShort *src) +{ + UnicodeString result; + while (*src != 0) + { + result.concat((WideChar)*src); + ++src; + } + return result; +} + /** * The LANAPI class is used to instantiate a singleton which diff --git a/Core/GameEngine/Include/GameNetwork/NetPacketStructs.h b/Core/GameEngine/Include/GameNetwork/NetPacketStructs.h index c96a1e11b13..1049edc2808 100644 --- a/Core/GameEngine/Include/GameNetwork/NetPacketStructs.h +++ b/Core/GameEngine/Include/GameNetwork/NetPacketStructs.h @@ -117,18 +117,25 @@ inline size_t readBytes(UnsignedByte *dest, size_t destLen, NetPacketBuf src) return readLen; } +// TheSuperHackers @bugfix bobtista 10/06/2026 Serialize chat text as 16-bit UTF-16 little-endian +// (2 bytes per code unit) on every platform instead of raw wchar_t. wchar_t is 4 bytes on macOS but +// 2 on Windows, so the old raw memcpy corrupted in-game chat across a macOS<->Windows lockstep game. +// On Windows this is byte-identical to the old format (wchar_t is already 16-bit LE). inline size_t readStringWithoutNull(UnicodeString &str, size_t maxStrLen, NetPacketBuf src) { - const size_t strLen = min(maxStrLen, src.size() / sizeof(WideChar)); - const size_t cpyLen = strLen * sizeof(WideChar); + const size_t strLen = min(maxStrLen, src.size() / 2u); if (strLen > 0) { WideChar *strBuf = str.getBufferForRead(strLen); - memcpy(strBuf, src.data(), cpyLen); + const UnsignedByte *data = src.data(); + for (size_t i = 0; i < strLen; ++i) + { + strBuf[i] = (WideChar)((UnsignedShort)data[i * 2] | ((UnsignedShort)data[i * 2 + 1] << 8)); + } strBuf[strLen] = 0; } - return cpyLen; + return strLen * 2u; } inline size_t readStringWithNull(AsciiString &str, size_t maxStrLen, NetPacketBuf src) @@ -170,9 +177,14 @@ inline size_t writeBytes(UnsignedByte *dest, const UnsignedByte *src, size_t len inline size_t writeStringWithoutNull(UnsignedByte *dest, const UnicodeString &value, size_t maxLen) { const size_t copyLen = std::min(value.getLength(), maxLen); - const size_t copyBytes = copyLen * sizeof(WideChar); - memcpy(dest, value.str(), copyBytes); - return copyBytes; + const WideChar *src = value.str(); + for (size_t i = 0; i < copyLen; ++i) + { + const UnsignedShort codeUnit = (UnsignedShort)src[i]; + dest[i * 2] = (UnsignedByte)(codeUnit & 0xFF); + dest[i * 2 + 1] = (UnsignedByte)((codeUnit >> 8) & 0xFF); + } + return copyLen * 2u; } inline size_t writeStringWithNull(UnsignedByte *dest, const AsciiString &value) 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/CRCDebug.cpp b/Core/GameEngine/Source/Common/CRCDebug.cpp index 38ffbfeed06..e4004ac83f3 100644 --- a/Core/GameEngine/Source/Common/CRCDebug.cpp +++ b/Core/GameEngine/Source/Common/CRCDebug.cpp @@ -183,7 +183,15 @@ void outputCRCDumpLines() static AsciiString getFname(AsciiString path) { - return path.reverseFind('\\') + 1; + // TheSuperHackers @bugfix bobtista 09/06/2026 reverseFind returns NULL when the separator is + // absent; "+ 1" then yields (char*)1 and the AsciiString constructor strlen()s it -> crash. + // __FILE__ uses '/' on macOS/Linux (no '\\'), so handle both separators and the not-found case. + const char *lastSep = path.reverseFind('\\'); + if (lastSep == NULL) + lastSep = path.reverseFind('/'); + if (lastSep == NULL) + return path; + return lastSep + 1; } static void addCRCDebugLineInternal(bool count, const char *fmt, va_list args) diff --git a/Core/GameEngine/Source/Common/Diagnostic/SimulationMathCrc.cpp b/Core/GameEngine/Source/Common/Diagnostic/SimulationMathCrc.cpp index 1b062f44e34..cece612a1a0 100644 --- a/Core/GameEngine/Source/Common/Diagnostic/SimulationMathCrc.cpp +++ b/Core/GameEngine/Source/Common/Diagnostic/SimulationMathCrc.cpp @@ -25,8 +25,69 @@ #include "GameLogic/FPUControl.h" #include +#include +#include -static void appendSimulationMathCrc(XferCRC &xfer) +// TheSuperHackers @info bobtista 10/06/2026 Hash the exact bits of a double so a 1-ULP +// cross-platform divergence in a double-precision transcendental is caught by the CRC. +static void xferDoubleBits(XferCRC &xfer, double value) +{ + Int64 bits; + memcpy(&bits, &value, sizeof(bits)); + xfer.xferInt64(&bits); +} + +// TheSuperHackers @info bobtista 10/06/2026 The single-precision probe below never exercises +// the double-precision WWMath overloads (e.g. WWMath::Atan2(double,double) -> gm_atan2), which +// is the path the train track-follow code actually uses. Sweep them here over movement-like +// inputs so -mathCrcCheck reveals any clang/MSVC double-precision divergence directly. +static const double s_probeY[] = { 0.4, 1.3, -2.7, 187.66, -1116.46, 0.000123, 3.5, -0.841933 }; +static const double s_probeX[] = { 1.3, 0.4, 11.9, -59.13, 1412.47, 9999.5, -3.5, 2.121793 }; +static const Int s_probeCount = sizeof(s_probeY) / sizeof(s_probeY[0]); + +static void appendSimulationMathCrcDouble(XferCRC &xfer) +{ + for (Int i = 0; i < s_probeCount; ++i) + { + xferDoubleBits(xfer, WWMath::Atan2(s_probeY[i], s_probeX[i])); + xferDoubleBits(xfer, WWMath::Atan(s_probeY[i] / s_probeX[i])); + xferDoubleBits(xfer, WWMath::Sin(s_probeY[i])); + xferDoubleBits(xfer, WWMath::Cos(s_probeY[i])); + xferDoubleBits(xfer, WWMath::Sqrt(WWMath::Fabs(s_probeX[i]))); + } +} + +static void appendSimulationMathCrc_Deterministic(XferCRC &xfer) +{ + Matrix3D matrix; + Matrix3D factorsMatrix; + + matrix.Set( + 4.1f, 1.2f, 0.3f, 0.4f, + 0.5f, 3.6f, 0.7f, 0.8f, + 0.9f, 1.0f, 2.1f, 1.2f); + + factorsMatrix.Set( + WWMath::Sinf(0.7f) * WWMath::Log10f(2.3f), + WWMath::Cosf(1.1f) * WWMath::Powf(1.1f, 2.0f), + WWMath::Tanf(0.3f), + WWMath::Asinf(0.967302263f), + WWMath::Acosf(0.967302263f), + WWMath::Atanf(0.967302263f) * WWMath::Powf(1.1f, 2.0f), + WWMath::Atan2f(0.4f, 1.3f), + WWMath::Sinhf(0.2f), + WWMath::Coshf(0.4f) * WWMath::Tanhf(0.5f), + WWMath::Sqrtf(55788.84375f), + WWMath::Expf(0.1f) * WWMath::Log10f(2.3f), + WWMath::Logf(1.4f)); + + Matrix3D::Multiply(matrix, factorsMatrix, &matrix); + matrix.Get_Inverse(matrix); + + xfer.xferMatrix3D(&matrix); +} + +static void appendSimulationMathCrc_Native(XferCRC &xfer) { Matrix3D matrix; Matrix3D factorsMatrix; @@ -37,18 +98,18 @@ static void appendSimulationMathCrc(XferCRC &xfer) 0.9f, 1.0f, 2.1f, 1.2f); factorsMatrix.Set( - WWMath::Sin(0.7f) * log10f(2.3f), - WWMath::Cos(1.1f) * powf(1.1f, 2.0f), - tanf(0.3f), - asinf(0.967302263f), - acosf(0.967302263f), - atanf(0.967302263f) * powf(1.1f, 2.0f), - atan2f(0.4f, 1.3f), - sinhf(0.2f), - coshf(0.4f) * tanhf(0.5f), - sqrtf(55788.84375f), - expf(0.1f) * log10f(2.3f), - logf(1.4f)); + (float)(::sin(0.7) * ::log10(2.3)), + (float)(::cos(1.1) * ::pow(1.1, 2.0)), + (float)::tan(0.3), + (float)::asin(0.967302263), + (float)::acos(0.967302263), + (float)(::atan(0.967302263) * ::pow(1.1, 2.0)), + (float)::atan2(0.4, 1.3), + (float)::sinh(0.2), + (float)(::cosh(0.4) * ::tanh(0.5)), + (float)::sqrt(55788.84375), + (float)(::exp(0.1) * ::log10(2.3)), + (float)::log(1.4)); Matrix3D::Multiply(matrix, factorsMatrix, &matrix); matrix.Get_Inverse(matrix); @@ -63,7 +124,8 @@ UnsignedInt SimulationMathCrc::calculate() setFPMode(); - appendSimulationMathCrc(xfer); + appendSimulationMathCrc_Deterministic(xfer); + appendSimulationMathCrcDouble(xfer); _fpreset(); @@ -71,3 +133,64 @@ UnsignedInt SimulationMathCrc::calculate() return xfer.getCRC(); } + +UnsignedInt SimulationMathCrc::calculateDouble() +{ + XferCRC xfer; + xfer.open("SimulationMathCrcDouble"); + + setFPMode(); + + appendSimulationMathCrcDouble(xfer); + + _fpreset(); + + xfer.close(); + + return xfer.getCRC(); +} + +void SimulationMathCrc::runBenchmark(int iterations) +{ + int i; + clock_t startDet = clock(); + UnsignedInt crcDet = 0; + + setFPMode(); + + for (i = 0; i < iterations; ++i) + { + XferCRC xfer; + xfer.open("SimMathDet"); + appendSimulationMathCrc_Deterministic(xfer); + xfer.close(); + if (i == 0) + crcDet = xfer.getCRC(); + } + _fpreset(); + clock_t endDet = clock(); + double timeDetMs = (double)(endDet - startDet) / CLOCKS_PER_SEC * 1000.0; + + clock_t startNat = clock(); + UnsignedInt crcNat = 0; + + setFPMode(); + + for (i = 0; i < iterations; ++i) + { + XferCRC xfer; + xfer.open("SimMathNat"); + appendSimulationMathCrc_Native(xfer); + xfer.close(); + if (i == 0) + crcNat = xfer.getCRC(); + } + _fpreset(); + clock_t endNat = clock(); + double timeNatMs = (double)(endNat - startNat) / CLOCKS_PER_SEC * 1000.0; + + printf("\n================ MATH BENCHMARK (%d iterations) ================\n", iterations); + printf("Deterministic (WWMath): CRC = %08X, Time = %.2f ms\n", crcDet, timeDetMs); + printf("Native (system math): CRC = %08X, Time = %.2f ms\n", crcNat, timeNatMs); + printf("===========================================================\n\n"); +} diff --git a/Core/GameEngine/Source/Common/FramePacer.cpp b/Core/GameEngine/Source/Common/FramePacer.cpp index 8f83bff10fb..50f4c177855 100644 --- a/Core/GameEngine/Source/Common/FramePacer.cpp +++ b/Core/GameEngine/Source/Common/FramePacer.cpp @@ -42,10 +42,19 @@ FramePacer::FramePacer() m_enableLogicTimeScale = FALSE; m_isTimeFrozen = FALSE; m_isGameHalted = FALSE; + m_enablePerformanceLog = FALSE; + m_performanceLogFrameCount = 0; + m_performanceLogWindowFrames = 0; + m_performanceLogElapsedSeconds = 0.0f; + m_performanceLogWindowSeconds = 0.0f; + m_performanceLogWindowMinMs = 0.0f; + m_performanceLogWindowMaxMs = 0.0f; } FramePacer::~FramePacer() { + flushPerformanceLogWindow(); + // Restore the previous time slice for Windows. timeEndPeriod(1); } @@ -56,6 +65,91 @@ void FramePacer::update() // with higher resolution counters to cap the frame rate more accurately to the desired limit. const UnsignedInt maxFps = getActualFramesPerSecondLimit();// allowFpsLimit ? getFramesPerSecondLimit() : RenderFpsPreset::UncappedFpsValue; m_updateTime = m_frameRateLimit.wait(maxFps); + updatePerformanceLog(); +} + +void FramePacer::enablePerformanceLog(Bool enable) +{ + m_enablePerformanceLog = enable; + m_performanceLogFrameCount = 0; + m_performanceLogWindowFrames = 0; + m_performanceLogElapsedSeconds = 0.0f; + m_performanceLogWindowSeconds = 0.0f; + m_performanceLogWindowMinMs = 0.0f; + m_performanceLogWindowMaxMs = 0.0f; + + if (m_enablePerformanceLog) + { + FILE *file = fopen("PerfLog_FrameTimes.csv", "wt"); + if (file != nullptr) + { + fprintf(file, "elapsed_seconds,total_frames,window_frames,avg_ms,min_ms,max_ms,fps\n"); + fclose(file); + } + else + { + DEBUG_LOG(("FramePacer::enablePerformanceLog() - failed to open PerfLog_FrameTimes.csv")); + } + } +} + +void FramePacer::flushPerformanceLogWindow() +{ + if (!m_enablePerformanceLog || m_performanceLogWindowFrames == 0) + { + return; + } + + FILE *file = fopen("PerfLog_FrameTimes.csv", "at"); + if (file != nullptr) + { + const Real avgMs = (m_performanceLogWindowSeconds * 1000.0f) / (Real)m_performanceLogWindowFrames; + const Real fps = (Real)m_performanceLogWindowFrames / m_performanceLogWindowSeconds; + fprintf(file, "%.3f,%u,%u,%.3f,%.3f,%.3f,%.3f\n", + m_performanceLogElapsedSeconds, + m_performanceLogFrameCount, + m_performanceLogWindowFrames, + avgMs, + m_performanceLogWindowMinMs, + m_performanceLogWindowMaxMs, + fps); + fclose(file); + } + + m_performanceLogWindowFrames = 0; + m_performanceLogWindowSeconds = 0.0f; + m_performanceLogWindowMinMs = 0.0f; + m_performanceLogWindowMaxMs = 0.0f; +} + +void FramePacer::updatePerformanceLog() +{ + if (!m_enablePerformanceLog) + { + return; + } + + const Real frameMs = m_updateTime * 1000.0f; + ++m_performanceLogFrameCount; + ++m_performanceLogWindowFrames; + m_performanceLogElapsedSeconds += m_updateTime; + m_performanceLogWindowSeconds += m_updateTime; + + if (m_performanceLogWindowFrames == 1) + { + m_performanceLogWindowMinMs = frameMs; + m_performanceLogWindowMaxMs = frameMs; + } + else + { + m_performanceLogWindowMinMs = min(m_performanceLogWindowMinMs, frameMs); + m_performanceLogWindowMaxMs = max(m_performanceLogWindowMaxMs, frameMs); + } + + if (m_performanceLogWindowSeconds >= 1.0f) + { + flushPerformanceLogWindow(); + } } void FramePacer::reset() @@ -188,7 +282,10 @@ Int FramePacer::getActualLogicTimeScaleFps(LogicTimeQueryFlags flags) const return TheNetwork->getFrameRate(); } - if (isLogicTimeScaleEnabled()) + // TheSuperHackers @bugfix bobtista 12/07/2026 Ignore the user logic time scale during replay + // playback so the simulation follows the replay's recorded game speed, like the original game. + if (isLogicTimeScaleEnabled() + && (TheGameLogic == nullptr || !TheGameLogic->isInReplayGame())) { return getLogicTimeScaleFps(); } diff --git a/Core/GameEngine/Source/Common/GameUtility.cpp b/Core/GameEngine/Source/Common/GameUtility.cpp index cffb87f4842..1caea74b011 100644 --- a/Core/GameEngine/Source/Common/GameUtility.cpp +++ b/Core/GameEngine/Source/Common/GameUtility.cpp @@ -17,6 +17,7 @@ */ #include "PreRTS.h" +#include "GgcRuntimeFlags.h" #include "Common/GameUtility.h" #include "Common/PlayerList.h" @@ -32,19 +33,69 @@ #include "GameLogic/GhostObject.h" #include "GameLogic/PartitionManager.h" +#include +#include + namespace rts { +static Bool shouldLogPlayerContext() +{ + return GgcFlags::Enabled(GgcFlag_PlayerContextDiag); +} + +static int playerIndexOrMinusOne(Player *player) +{ + return player != nullptr ? player->getPlayerIndex() : -1; +} + +static const char *playerSideOrNull(Player *player) +{ + return player != nullptr ? player->getSide().str() : ""; +} + +static void logPlayerContext(const char *event, Player *subject) +{ + if (!shouldLogPlayerContext()) + return; + + Player *local = ThePlayerList != nullptr ? ThePlayerList->getLocalPlayer() : nullptr; + Player *observed = TheControlBar != nullptr ? TheControlBar->getObservedPlayer() : nullptr; + Player *lookAt = TheControlBar != nullptr ? TheControlBar->getObserverLookAtPlayer() : nullptr; + Player *effective = observed != nullptr ? observed : local; + + if (FILE *diag = std::fopen("ggc_player_context_diag.txt", "a")) + { + std::fprintf(diag, + "%s frame=%u subject=%d/%s local=%d/%s observed=%d/%s lookAt=%d/%s effective=%d/%s\n", + event, + TheGameLogic != nullptr ? TheGameLogic->getFrame() : 0, + playerIndexOrMinusOne(subject), + playerSideOrNull(subject), + playerIndexOrMinusOne(local), + playerSideOrNull(local), + playerIndexOrMinusOne(observed), + playerSideOrNull(observed), + playerIndexOrMinusOne(lookAt), + playerSideOrNull(lookAt), + playerIndexOrMinusOne(effective), + playerSideOrNull(effective)); + std::fclose(diag); + } +} + namespace detail { static void changePlayerCommon(Player* player) { + logPlayerContext("changePlayerCommon:before", player); TheParticleSystemManager->setLocalPlayerIndex(player->getPlayerIndex()); ThePartitionManager->refreshShroudForLocalPlayer(); TheGhostObjectManager->setLocalPlayerIndex(player->getPlayerIndex()); TheGameClient->updateFakeDrawables(); TheRadar->refreshObjects(); TheInGameUI->deselectAllDrawables(); + logPlayerContext("changePlayerCommon:after", player); } } // namespace detail @@ -74,6 +125,12 @@ Player* getObservedOrLocalPlayer() DEBUG_ASSERTCRASH(ThePlayerList != nullptr, ("ThePlayerList is null")); player = ThePlayerList->getLocalPlayer(); } + static Player *lastPlayer = nullptr; + if (player != lastPlayer) + { + logPlayerContext("effectivePlayerChanged", player); + lastPlayer = player; + } return player; } @@ -103,6 +160,7 @@ void changeLocalPlayer(Player* player) { DEBUG_ASSERTCRASH(player != nullptr, ("Player is null")); + logPlayerContext("changeLocalPlayer:before", player); ThePlayerList->setLocalPlayer(player); TheControlBar->setObserverLookAtPlayer(nullptr); TheControlBar->setObservedPlayer(nullptr); @@ -110,10 +168,12 @@ void changeLocalPlayer(Player* player) TheControlBar->initSpecialPowershortcutBar(player); detail::changePlayerCommon(player); + logPlayerContext("changeLocalPlayer:after", player); } void changeObservedPlayer(Player* player) { + logPlayerContext("changeObservedPlayer:before", player); TheControlBar->setObserverLookAtPlayer(player); const Bool canBeginObservePlayer = TheGlobalData->m_enablePlayerObserver && TheGhostObjectManager->trackAllPlayers(); @@ -128,6 +188,7 @@ void changeObservedPlayer(Player* player) becomePlayer = ThePlayerList->findPlayerWithNameKey(TheNameKeyGenerator->nameToKey("ReplayObserver")); detail::changePlayerCommon(becomePlayer); } + logPlayerContext("changeObservedPlayer:after", player); } } // namespace rts diff --git a/Core/GameEngine/Source/Common/INI/INI.cpp b/Core/GameEngine/Source/Common/INI/INI.cpp index b3a26a5f4c1..039f08db12a 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; @@ -1789,7 +1792,7 @@ void INI::parseDurationReal( INI *ini, void * /*instance*/, void *store, const v void INI::parseDurationUnsignedInt( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) { UnsignedInt val = scanUnsignedInt(ini->getNextToken()); - *(UnsignedInt *)store = (UnsignedInt)ceilf(ConvertDurationFromMsecsToFrames((Real)val)); + *(UnsignedInt *)store = (UnsignedInt)WWMath::Ceilf(ConvertDurationFromMsecsToFrames((Real)val)); } // ------------------------------------------------------------------------------------------------ @@ -1797,7 +1800,7 @@ void INI::parseDurationUnsignedInt( INI *ini, void * /*instance*/, void *store, void INI::parseDurationUnsignedShort( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) { UnsignedInt val = scanUnsignedInt(ini->getNextToken()); - *(UnsignedShort *)store = (UnsignedShort)ceilf(ConvertDurationFromMsecsToFrames((Real)val)); + *(UnsignedShort *)store = (UnsignedShort)WWMath::Ceilf(ConvertDurationFromMsecsToFrames((Real)val)); } //------------------------------------------------------------------------------------------------- diff --git a/Core/GameEngine/Source/Common/INI/INIMapCache.cpp b/Core/GameEngine/Source/Common/INI/INIMapCache.cpp index 5be4414bb70..c2ef34c0e9f 100644 --- a/Core/GameEngine/Source/Common/INI/INIMapCache.cpp +++ b/Core/GameEngine/Source/Common/INI/INIMapCache.cpp @@ -149,7 +149,19 @@ void INI::parseMapCacheDefinition( INI* ini ) { // maps without localized name tags AsciiString tempdisplayname; +#ifdef _WIN32 tempdisplayname = name.reverseFind('\\') + 1; +#else + { + const char* sep = name.reverseFind('\\'); + const char* fwd = name.reverseFind('/'); + if (fwd && (!sep || fwd > sep)) + { + sep = fwd; + } + tempdisplayname = sep ? sep + 1 : name.str(); + } +#endif md.m_displayName.translate(tempdisplayname); if (md.m_numPlayers >= 2) { @@ -196,6 +208,13 @@ void INI::parseMapCacheDefinition( INI* ini ) { AsciiString lowerName = name; lowerName.toLower(); +#ifndef _WIN32 + { + std::string normalized(lowerName.str()); + std::replace(normalized.begin(), normalized.end(), '\\', '/'); + lowerName.set(normalized.c_str()); + } +#endif md.m_fileName = lowerName; // DEBUG_LOG(("INI::parseMapCacheDefinition - adding %s to map cache", lowerName.str())); (*TheMapCache)[lowerName] = md; diff --git a/Core/GameEngine/Source/Common/System/Debug.cpp b/Core/GameEngine/Source/Common/System/Debug.cpp index 10207ece344..572e683421c 100644 --- a/Core/GameEngine/Source/Common/System/Debug.cpp +++ b/Core/GameEngine/Source/Common/System/Debug.cpp @@ -113,6 +113,12 @@ static char theLogFileNamePrev[ _MAX_PATH ]; #define LARGE_BUFFER 8192 static char theBuffer[ LARGE_BUFFER ]; // make it big to avoid weird overflow bugs in debug mode static int theDebugFlags = 0; +// TheSuperHackers @performance bobtista 14/06/2026 When false (default) the debug log is +// buffered and only flushed on crash/assert/shutdown. Flushing every line turned each +// DEBUG_LOG into a blocking disk write, which crippled debug-build framerate on log-heavy +// frames. Flip to true (e.g. from the debugger) for per-line durability while chasing a +// hard crash that bypasses the handled crash paths. +static bool theFlushDebugLogEachLine = false; static DWORD theMainThreadID = 0; // ---------------------------------------------------------------------------- // PUBLIC DATA @@ -244,7 +250,8 @@ static void doLogOutput(const char *buffer, const char *endline) if (theLogFile) { fprintf(theLogFile, "%s%s", buffer, endline); - fflush(theLogFile); + if (theFlushDebugLogEachLine) + fflush(theLogFile); } } @@ -259,6 +266,14 @@ static void doLogOutput(const char *buffer, const char *endline) addCRCDebugLineNoCounter("%s%s", buffer, endline); #endif } +// TheSuperHackers @performance bobtista 14/06/2026 Force the buffered debug log to disk. +// Called from the crash/assert/shutdown paths so the log tail survives even though normal +// logging no longer flushes per line. +static void flushLogFile() +{ + if (theLogFile) + fflush(theLogFile); +} #endif // DEBUG_LOGGING // ---------------------------------------------------------------------------- @@ -377,6 +392,10 @@ void DebugInit(int flags) return; char dirbuf[ _MAX_PATH ]; + // TheSuperHackers @bugfix bobtista 09/06/2026 Start empty so a platform whose + // GetModuleFileName is a no-op stub (non-Windows) leaves no uninitialized garbage in + // the path; the log then opens relative to the working directory instead of failing. + dirbuf[0] = 0; ::GetModuleFileName( nullptr, dirbuf, sizeof( dirbuf ) ); if (char *pEnd = strrchr(dirbuf, '\\')) { @@ -545,6 +564,9 @@ void DebugCrash(const char *format, ...) { doStackDump(); } +#endif +#ifdef DEBUG_LOGGING + flushLogFile(); #endif } @@ -647,7 +669,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; @@ -760,6 +782,12 @@ void ReleaseCrash(const char *reason) char prevbuf[ _MAX_PATH ]; char curbuf[ _MAX_PATH ]; +#ifdef DEBUG_LOGGING + // TheSuperHackers @performance bobtista 14/06/2026 Flush the buffered debug log before + // the fatal exit so its tail is captured alongside the release crash report. + flushLogFile(); +#endif + if (TheGlobalData==nullptr) { return; // We are shutting down, and TheGlobalData has been freed. jba. [4/15/2003] } diff --git a/Core/GameEngine/Source/Common/System/FileSystem.cpp b/Core/GameEngine/Source/Common/System/FileSystem.cpp index b8e4c4695b6..6e53fa1e52d 100644 --- a/Core/GameEngine/Source/Common/System/FileSystem.cpp +++ b/Core/GameEngine/Source/Common/System/FileSystem.cpp @@ -369,7 +369,7 @@ Bool FileSystem::isPathInDirectory(const AsciiString& testPath, const AsciiStrin basePathNormalized.concat(pathSep); } -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) if (!testPathNormalized.startsWithNoCase(basePathNormalized)) #else if (!testPathNormalized.startsWith(basePathNormalized)) diff --git a/Core/GameEngine/Source/Common/System/GameMemory.cpp b/Core/GameEngine/Source/Common/System/GameMemory.cpp index 82c2fab683c..1a53df0e15c 100644 --- a/Core/GameEngine/Source/Common/System/GameMemory.cpp +++ b/Core/GameEngine/Source/Common/System/GameMemory.cpp @@ -45,7 +45,7 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine // SYSTEM INCLUDES - +#include // USER INCLUDES #include "Common/GameMemory.h" #include "Common/CriticalSection.h" @@ -492,6 +492,7 @@ class MemoryPoolBlob Int getFreeBlockCount(); Int getUsedBlockCount(); Int getTotalBlockCount(); + Bool ownsUserBlockPointer(void *pBlock); #ifdef MEMORYPOOL_DEBUG void debugMemoryVerifyBlob(); @@ -716,6 +717,31 @@ inline Int MemoryPoolBlob::getUsedBlockCount() { return m_usedBlocksInBlob; } /// accessor inline Int MemoryPoolBlob::getTotalBlockCount() { return m_totalBlocksInBlob; } +Bool MemoryPoolBlob::ownsUserBlockPointer(void *pBlock) +{ + if (pBlock == nullptr || m_blockData == nullptr || m_owningPool == nullptr) + { + return false; + } + + const Int rawBlockSize = MemoryPoolSingleBlock::calcRawBlockSize(m_owningPool->getAllocationSize()); + const char *blockData = m_blockData; + const char *userPtr = static_cast(pBlock); + const char *end = blockData + (rawBlockSize * m_totalBlocksInBlob); + + if (userPtr < blockData || userPtr >= end) + { + return false; + } + + Int userOffset = sizeof(MemoryPoolSingleBlock); +#ifdef MEMORYPOOL_BOUNDINGWALL + userOffset += WALLSIZE; +#endif + const ptrdiff_t offset = userPtr - blockData - userOffset; + return offset >= 0 && (offset % rawBlockSize) == 0; +} + //----------------------------------------------------------------------------- // METHODS for BlockCheckpointInfo //----------------------------------------------------------------------------- @@ -1764,6 +1790,19 @@ Int MemoryPool::countBlobsInPool() return blobs; } +//----------------------------------------------------------------------------- +Bool MemoryPool::ownsUserBlockPointer(void *pBlockPtr) +{ + for (MemoryPoolBlob *blob = m_firstBlob; blob != nullptr; blob = blob->getNextInList()) + { + if (blob->ownsUserBlockPointer(pBlockPtr)) + { + return true; + } + } + return false; +} + //----------------------------------------------------------------------------- /** if the pool has any blobs that are completely unused, they are released back to the @@ -2086,6 +2125,28 @@ MemoryPool *DynamicMemoryAllocator::findPoolForSize(Int allocSize) return nullptr; } +//----------------------------------------------------------------------------- +Bool DynamicMemoryAllocator::ownsUserBlockPointer(void *pBlockPtr) +{ + for (Int i = 0; i < m_numPools; i++) + { + if (m_pools[i] != nullptr && m_pools[i]->ownsUserBlockPointer(pBlockPtr)) + { + return true; + } + } + + for (MemoryPoolSingleBlock *block = m_rawBlocks; block != nullptr; block = block->getNextRawBlock()) + { + if (block->getUserData() == pBlockPtr) + { + return true; + } + } + + return false; +} + //----------------------------------------------------------------------------- /** add this DMA to the factory's list of dmas. @@ -2279,6 +2340,19 @@ void DynamicMemoryAllocator::freeBytes(void* pBlockPtr) ScopedCriticalSection scopedCriticalSection(TheDmaCriticalSection); +#if defined(__APPLE__) || defined(__linux__) + // System frameworks and shared libraries can resolve C++ delete to the game's + // global replacement operator. Only pointers that match our pool/raw-block + // ranges may be interpreted as MemoryPoolSingleBlock user data. Do not try to + // "repair" foreign deletes with free(): some system internals pass sentinel + // or interior values through delete (e.g. Metal compiler teardown on macOS), + // and malloc_size() is not a sufficient exact-allocation test for those values. + if (!ownsUserBlockPointer(pBlockPtr)) + { + return; + } +#endif + #ifdef MEMORYPOOL_CHECK_BLOCK_OWNERSHIP DEBUG_ASSERTCRASH(debugIsBlockInDma(pBlockPtr), ("block is not in this dma")); #endif @@ -3304,6 +3378,32 @@ void operator delete[](void *p) TheDynamicMemoryAllocator->freeBytes(p); } +#if defined(__APPLE__) || defined(__linux__) +//----------------------------------------------------------------------------- +/** + Sized delete overloads used by modern libc++/libstdc++ and system library + code. Since our unsized global operator new can be interposed process-wide, + these must route back to the same allocator instead of the C++ runtime + eventually calling free(). +*/ +void operator delete(void *p, size_t) +{ + ++theLinkTester; + preMainInitMemoryManager(); + DEBUG_ASSERTCRASH(TheDynamicMemoryAllocator != nullptr, ("must init memory manager before calling global operator delete")); + TheDynamicMemoryAllocator->freeBytes(p); +} + +//----------------------------------------------------------------------------- +void operator delete[](void *p, size_t) +{ + ++theLinkTester; + preMainInitMemoryManager(); + DEBUG_ASSERTCRASH(TheDynamicMemoryAllocator != nullptr, ("must init memory manager before calling global operator delete")); + TheDynamicMemoryAllocator->freeBytes(p); +} +#endif + //----------------------------------------------------------------------------- /** overload for global operator new (MFC debug version); send requests to TheDynamicMemoryAllocator. diff --git a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_Generals.inl b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_Generals.inl index d8177c459d4..c2ab5db0864 100644 --- a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_Generals.inl +++ b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_Generals.inl @@ -646,8 +646,10 @@ static PoolSizeRec PoolSizes[] = { "TerrainTracksRenderObjClass", 128, 32 }, { "DynamicIBAccessClass", 32, 32 }, { "DX8IndexBufferClass", 128, 32 }, + { "RenderIndexBufferClass", 128, 32 }, { "SortingIndexBufferClass", 32, 32 }, { "DX8VertexBufferClass", 128, 32 }, + { "RenderVertexBufferClass", 128, 32 }, { "SortingVertexBufferClass", 32, 32 }, { "DynD3DMATERIAL8", 8192, 32 }, { "DynamicMatrix3D", 512, 32 }, diff --git a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl index f9f5b5a0a18..7aa20d66c91 100644 --- a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl +++ b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl @@ -642,8 +642,10 @@ static PoolSizeRec PoolSizes[] = { "TerrainTracksRenderObjClass", 128, 32 }, { "DynamicIBAccessClass", 32, 32 }, { "DX8IndexBufferClass", 128, 32 }, + { "RenderIndexBufferClass", 128, 32 }, { "SortingIndexBufferClass", 32, 32 }, { "DX8VertexBufferClass", 128, 32 }, + { "RenderVertexBufferClass", 128, 32 }, { "SortingVertexBufferClass", 32, 32 }, { "DynD3DMATERIAL8", 8192, 32 }, { "DynamicMatrix3D", 512, 32 }, diff --git a/Core/GameEngine/Source/Common/System/GameMemoryNull.cpp b/Core/GameEngine/Source/Common/System/GameMemoryNull.cpp index 54f8fd770fe..d718878b4e1 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" @@ -109,8 +113,8 @@ void initMemoryManager() { if (TheMemoryPoolFactory == nullptr && TheDynamicMemoryAllocator == nullptr) { - TheMemoryPoolFactory = new (malloc(sizeof MemoryPoolFactory)) MemoryPoolFactory; - TheDynamicMemoryAllocator = new (malloc(sizeof DynamicMemoryAllocator)) DynamicMemoryAllocator; + TheMemoryPoolFactory = new (malloc(sizeof(MemoryPoolFactory))) MemoryPoolFactory; + TheDynamicMemoryAllocator = new (malloc(sizeof(DynamicMemoryAllocator))) DynamicMemoryAllocator; DEBUG_INIT(DEBUG_FLAGS_DEFAULT); DEBUG_LOG(("*** Initialized the Null Memory Manager")); diff --git a/Core/GameEngine/Source/Common/System/LocalFile.cpp b/Core/GameEngine/Source/Common/System/LocalFile.cpp index 431906a8ddd..d1a3f82e433 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; } @@ -381,12 +381,21 @@ Int LocalFile::readChar() Int LocalFile::readWideChar() { +#ifdef _WIN32 WideChar character = L'\0'; Int ret = read( &character, sizeof(character) ); + if (ret == sizeof(character)) + return (Int)(intptr_t)character; +#else + UnsignedShort character = 0; + + Int ret = read( &character, sizeof(character) ); + if (ret == sizeof(character)) return (Int)character; +#endif return WEOF; } @@ -440,7 +449,14 @@ Int LocalFile::writeFormat( const WideChar* format, ... ) Int length = vswprintf(buffer, sizeof(buffer) / sizeof(WideChar), format, args); va_end(args); +#ifdef _WIN32 return write( buffer, length * sizeof(WideChar) ); +#else + UnsignedShort diskBuffer[1024]; + for( Int i = 0; i < length; ++i ) + diskBuffer[ i ] = (UnsignedShort)buffer[ i ]; + return write( diskBuffer, length * sizeof(UnsignedShort) ); +#endif } //================================================================= @@ -450,7 +466,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; @@ -462,9 +478,16 @@ Int LocalFile::writeChar( const Char* character ) Int LocalFile::writeChar( const WideChar* character ) { +#ifdef _WIN32 if ( write( character, sizeof(WideChar) ) == sizeof(WideChar) ) { - return (Int)character; + return (Int)(intptr_t)character; } +#else + UnsignedShort diskCharacter = (UnsignedShort)(*character); + if ( write( &diskCharacter, sizeof(diskCharacter) ) == sizeof(diskCharacter) ) { + return (Int)(intptr_t)character; + } +#endif return WEOF; } diff --git a/Core/GameEngine/Source/Common/System/Radar.cpp b/Core/GameEngine/Source/Common/System/Radar.cpp index 313b8f36e47..9515f5bca97 100644 --- a/Core/GameEngine/Source/Common/System/Radar.cpp +++ b/Core/GameEngine/Source/Common/System/Radar.cpp @@ -305,6 +305,14 @@ void Radar::update() } //------------------------------------------------------------------------------------------------- +/** Re-hook the radar window pointer after the control bar window tree is rebuilt */ +//------------------------------------------------------------------------------------------------- +void Radar::reattachWindow() +{ + Int id = NAMEKEY( "ControlBar.wnd:LeftHUD" ); + m_radarWindow = TheWindowManager->winGetWindowFromId( nullptr, id ); +} + /** Reset the radar for the new map data being given to it */ //------------------------------------------------------------------------------------------------- void Radar::newMap( TerrainLogic *terrain ) diff --git a/Core/GameEngine/Source/Common/System/UnicodeString.cpp b/Core/GameEngine/Source/Common/System/UnicodeString.cpp index 386778d321b..106b573194d 100644 --- a/Core/GameEngine/Source/Common/System/UnicodeString.cpp +++ b/Core/GameEngine/Source/Common/System/UnicodeString.cpp @@ -361,6 +361,137 @@ void UnicodeString::truncateTo(const Int maxLength) validate(); } +// ----------------------------------------------------- +#ifndef _WIN32 +static Bool isFlagWidthPrecChar(WideChar c) +{ + return c == L'-' || c == L'+' || c == L' ' || c == L'#' + || (c >= L'0' && c <= L'9') || c == L'.' || c == L'*'; +} + +static Bool isLengthModChar(WideChar c) +{ + return c == L'h' || c == L'l' || c == L'L' || c == L'w' + || c == L'j' || c == L'z' || c == L't' || c == L'q'; +} + +// Rewrite an MSVC-semantics wide format string into one that standard libc vswprintf reads +// the same way. MSVC: %s/%c are wide, %S/%C narrow, %hs/%hc narrow, %ls/%lc/%ws/%wc wide. +// libc: %s/%c are narrow, %ls/%lc wide, %S/%C wide. So bare %s/%c gain an 'l' (become wide), +// %ws/%wc map to %ls/%lc, and %S/%C become narrow %s/%c. +static void translateWideFormat(const WideChar* in, WideChar* out, size_t outCap) +{ + size_t o = 0; + for (const WideChar* p = in; *p != 0; ) + { + if (*p != L'%') + { + if (o + 1 < outCap) + { + out[o++] = *p; + } + ++p; + continue; + } + if (o + 1 < outCap) + { + out[o++] = *p; + } + ++p; + if (*p == L'%') + { + if (o + 1 < outCap) + { + out[o++] = *p; + } + ++p; + continue; + } + while (*p != 0 && isFlagWidthPrecChar(*p)) + { + if (o + 1 < outCap) + { + out[o++] = *p; + } + ++p; + } + Bool wideLen = FALSE; + Bool narrowLen = FALSE; + while (*p != 0 && isLengthModChar(*p)) + { + WideChar emit = *p; + if (*p == L'h') + { + narrowLen = TRUE; + } + else if (*p == L'w') + { + wideLen = TRUE; + emit = L'l'; + } + else if (*p == L'l' || *p == L'L') + { + wideLen = TRUE; + } + if (o + 1 < outCap) + { + out[o++] = emit; + } + ++p; + } + WideChar c = *p; + if (c == 0) + { + break; + } + if (c == L's' || c == L'c') + { + if (!wideLen && !narrowLen && o + 1 < outCap) + { + out[o++] = L'l'; + } + if (o + 1 < outCap) + { + out[o++] = c; + } + ++p; + } + else if (c == L'S' || c == L'C') + { + if (o + 1 < outCap) + { + out[o++] = (c == L'S') ? L's' : L'c'; + } + ++p; + } + else + { + if (o + 1 < outCap) + { + out[o++] = c; + } + ++p; + } + } + if (outCap > 0) + { + out[(o < outCap) ? o : (outCap - 1)] = 0; + } +} +#endif + +// ----------------------------------------------------- +Int formatStringW(WideChar* buf, size_t bufCount, const WideChar* format, va_list args) +{ +#ifdef _WIN32 + return vswprintf(buf, bufCount, format, args); +#else + WideChar translated[UnicodeString::MAX_FORMAT_BUF_LEN]; + translateWideFormat(format, translated, sizeof(translated) / sizeof(WideChar)); + return vswprintf(buf, bufCount, translated, args); +#endif +} + // ----------------------------------------------------- void UnicodeString::format(UnicodeString format, ...) { @@ -394,7 +525,7 @@ void UnicodeString::format_va(const WideChar* format, va_list args) { validate(); WideChar buf[MAX_FORMAT_BUF_LEN]; - const int result = vswprintf(buf, sizeof(buf)/sizeof(WideChar), format, args); + const int result = formatStringW(buf, sizeof(buf)/sizeof(WideChar), format, args); if (result >= 0) { set(buf); diff --git a/Core/GameEngine/Source/Common/System/Xfer.cpp b/Core/GameEngine/Source/Common/System/Xfer.cpp index d72a7b0d450..99fbdf10ea5 100644 --- a/Core/GameEngine/Source/Common/System/Xfer.cpp +++ b/Core/GameEngine/Source/Common/System/Xfer.cpp @@ -201,7 +201,27 @@ void Xfer::xferMarkerLabel( AsciiString asciiStringData ) void Xfer::xferUnicodeString( UnicodeString *unicodeStringData ) { +#ifdef _WIN32 xferImplementation( (void *)unicodeStringData->str(), sizeof( WideChar ) * unicodeStringData->getLength() ); +#else + // TheSuperHackers @bugfix bobtista 11/06/2026 WideChar is 4 bytes on non-Windows but 2 on + // Windows, so feeding raw WideChar bytes here makes the CRC (and any other byte sink) diverge + // across platforms - e.g. Player::m_generalName desyncs cross-platform multiplayer. Serialize + // each code unit as 16-bit little-endian to match Windows' native wchar_t layout. Chunks are a + // whole number of code units capped at an even 256 so xferImplementation folds only complete + // 4-byte words until the final (possibly odd) chunk, matching a single contiguous call. + const Int len = unicodeStringData->getLength(); + const WideChar *src = unicodeStringData->str(); + UnsignedShort diskBuffer[ 256 ]; + for( Int i = 0; i < len; ) + { + const Int chunk = (len - i < 256) ? (len - i) : 256; + for( Int j = 0; j < chunk; ++j ) + diskBuffer[ j ] = (UnsignedShort)src[ i + j ]; + xferImplementation( diskBuffer, sizeof( UnsignedShort ) * chunk ); + i += chunk; + } +#endif } diff --git a/Core/GameEngine/Source/Common/System/XferCRC.cpp b/Core/GameEngine/Source/Common/System/XferCRC.cpp index 12019d29e84..4bf1a636dda 100644 --- a/Core/GameEngine/Source/Common/System/XferCRC.cpp +++ b/Core/GameEngine/Source/Common/System/XferCRC.cpp @@ -337,7 +337,18 @@ void XferDeepCRC::xferUnicodeString( UnicodeString *unicodeStringData ) xferByte( &len ); // save string data + // TheSuperHackers @bugfix bobtista 11/06/2026 Serialize each code unit as 16-bit little-endian + // so the deep CRC matches across platforms; WideChar is 4 bytes on non-Windows but 2 on Windows. if( len > 0 ) + { +#ifdef _WIN32 xferUser( (void *)unicodeStringData->str(), sizeof( WideChar ) * len ); +#else + UnsignedShort diskBuffer[ 256 ]; + for( Int i = 0; i < len; ++i ) + diskBuffer[ i ] = (UnsignedShort)unicodeStringData->str()[ i ]; + xferUser( diskBuffer, sizeof( UnsignedShort ) * len ); +#endif + } } diff --git a/Core/GameEngine/Source/Common/System/XferLoad.cpp b/Core/GameEngine/Source/Common/System/XferLoad.cpp index 5dfb9551d76..c8d3d8ca945 100644 --- a/Core/GameEngine/Source/Common/System/XferLoad.cpp +++ b/Core/GameEngine/Source/Common/System/XferLoad.cpp @@ -80,7 +80,12 @@ void XferLoad::open( AsciiString identifier ) Xfer::open( identifier ); // open the file +#ifdef _WIN32 m_fileFP = fopen( identifier.str(), "rb" ); +#else + const std::string normalizedPath = NormalizeWin32PathForHost( identifier.str() ); + m_fileFP = fopen( normalizedPath.c_str(), "rb" ); +#endif if( m_fileFP == nullptr ) { @@ -228,8 +233,18 @@ void XferLoad::xferUnicodeString( UnicodeString *unicodeStringData ) const Int MAX_XFER_LOAD_STRING_BUFFER = 1024; static WideChar buffer[ MAX_XFER_LOAD_STRING_BUFFER ]; +#ifdef _WIN32 if( len > 0 ) xferUser( buffer, sizeof( WideChar ) * len ); +#else + static UnsignedShort diskBuffer[ MAX_XFER_LOAD_STRING_BUFFER ]; + if( len > 0 ) + { + xferUser( diskBuffer, sizeof( UnsignedShort ) * len ); + for( Int i = 0; i < len; ++i ) + buffer[ i ] = (WideChar)diskBuffer[ i ]; + } +#endif buffer[ len ] = 0; // terminate // save into unicode string @@ -257,4 +272,3 @@ void XferLoad::xferImplementation( void *data, Int dataSize ) } } - diff --git a/Core/GameEngine/Source/Common/System/XferSave.cpp b/Core/GameEngine/Source/Common/System/XferSave.cpp index 93ee8a21db1..519c99d1c84 100644 --- a/Core/GameEngine/Source/Common/System/XferSave.cpp +++ b/Core/GameEngine/Source/Common/System/XferSave.cpp @@ -120,7 +120,12 @@ void XferSave::open( AsciiString identifier ) Xfer::open( identifier ); // open the file +#ifdef _WIN32 m_fileFP = fopen( identifier.str(), "w+b" ); +#else + const std::string normalizedPath = NormalizeWin32PathForHost( identifier.str() ); + m_fileFP = fopen( normalizedPath.c_str(), "w+b" ); +#endif if( m_fileFP == nullptr ) { @@ -331,8 +336,18 @@ void XferSave::xferUnicodeString( UnicodeString *unicodeStringData ) xferUnsignedByte( &len ); // save string data +#ifdef _WIN32 if( len > 0 ) xferUser( (void *)unicodeStringData->str(), sizeof( WideChar ) * len ); +#else + if( len > 0 ) + { + UnsignedShort diskBuffer[ 256 ]; + for( Int i = 0; i < len; ++i ) + diskBuffer[ i ] = (UnsignedShort)unicodeStringData->str()[ i ]; + xferUser( diskBuffer, sizeof( UnsignedShort ) * len ); + } +#endif } 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/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/Core/GameEngine/Source/GameClient/FXList.cpp b/Core/GameEngine/Source/GameClient/FXList.cpp index cf6a47995a0..a14b7dd7c79 100644 --- a/Core/GameEngine/Source/GameClient/FXList.cpp +++ b/Core/GameEngine/Source/GameClient/FXList.cpp @@ -315,7 +315,7 @@ class LightPulseFXNugget : public FXNugget MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(LightPulseFXNugget, "LightPulseFXNugget") public: - LightPulseFXNugget() : m_radius(0), m_increaseFrames(0), m_decreaseFrames(0), m_boundingCirclePct(0) + LightPulseFXNugget() : m_radius(0), m_increaseFrames(0), m_decreaseFrames(0), m_boundingCirclePct(0), m_castsShadows(false), m_shadowBias(0.0f), m_shadowStrength(1.0f), m_heightOffset(0.0f) { m_color.red = m_color.green = m_color.blue = 0; } @@ -329,7 +329,12 @@ class LightPulseFXNugget : public FXNugget if (m_boundingCirclePct > 0) radius = (primary->getGeometryInfo().getBoundingCircleRadius() * m_boundingCirclePct); - TheDisplay->createLightPulse(primary->getPosition(), &m_color, 1, radius, m_increaseFrames, m_decreaseFrames); + // TheSuperHackers @bugfix bobtista 17/07/2026 Pass the configured ShadowStrength and lift + // the light by HeightOffset. A ground-level pulse casting at the hardcoded 1.0 strength + // draped a hard black grazing shadow off the struck object for the flash's lifetime. + Coord3D pos = *primary->getPosition(); + pos.z += m_heightOffset; + TheDisplay->createLightPulse(&pos, &m_color, 1, radius, m_increaseFrames, m_decreaseFrames, m_castsShadows, m_shadowBias, m_shadowStrength); } else { @@ -341,7 +346,9 @@ class LightPulseFXNugget : public FXNugget { if (primary) { - TheDisplay->createLightPulse(primary, &m_color, 1, m_radius, m_increaseFrames, m_decreaseFrames); + Coord3D pos = *primary; + pos.z += m_heightOffset; + TheDisplay->createLightPulse(&pos, &m_color, 1, m_radius, m_increaseFrames, m_decreaseFrames, m_castsShadows, m_shadowBias, m_shadowStrength); } else { @@ -358,6 +365,10 @@ class LightPulseFXNugget : public FXNugget { "RadiusAsPercentOfObjectSize", INI::parsePercentToReal, nullptr, offsetof( LightPulseFXNugget, m_boundingCirclePct ) }, { "IncreaseTime", INI::parseDurationUnsignedInt, nullptr, offsetof( LightPulseFXNugget, m_increaseFrames ) }, { "DecreaseTime", INI::parseDurationUnsignedInt, nullptr, offsetof( LightPulseFXNugget, m_decreaseFrames ) }, + { "CastsShadows", INI::parseBool, nullptr, offsetof( LightPulseFXNugget, m_castsShadows ) }, + { "ShadowBias", INI::parseReal, nullptr, offsetof( LightPulseFXNugget, m_shadowBias ) }, + { "ShadowStrength", INI::parseReal, nullptr, offsetof( LightPulseFXNugget, m_shadowStrength ) }, + { "HeightOffset", INI::parseReal, nullptr, offsetof( LightPulseFXNugget, m_heightOffset ) }, { nullptr, nullptr, nullptr, 0 } }; @@ -372,6 +383,10 @@ class LightPulseFXNugget : public FXNugget Real m_boundingCirclePct; UnsignedInt m_increaseFrames; UnsignedInt m_decreaseFrames; + Bool m_castsShadows; + Real m_shadowBias; + Real m_shadowStrength; + Real m_heightOffset; }; EMPTY_DTOR(LightPulseFXNugget) diff --git a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp index 697ee47a0a4..62d0d2a2791 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", @@ -3306,6 +3306,29 @@ void ControlBar::initSpecialPowershortcutBar( Player *player) } +// TheSuperHackers @bugfix bobtista 20/07/2026 On a mid-match resolution change the generals-powers +// shortcut bar must be recreated to rescale, but initSpecialPowershortcutBar leaves it hidden, which +// makes updateSpecialPowerShortcut replay the 500ms slide-in every resize - a screenshot taken right +// after a resize catches the bar mid-slide, clipped off the right edge. If the bar was already +// visible, unhide it at its (correct, flush-right) rest position and populate it directly so the +// hidden->show transition, and therefore the slide, never fires. +void ControlBar::rebuildSpecialPowerShortcutBarForResolution( Player *player ) +{ + initSpecialPowershortcutBar( player ); + + // recreateControlBar() rebuilt the whole ControlBar object just before this call, wiping any record + // of whether the shortcut bar was showing (m_specialPowerShortcutParent starts null on the new + // object). Decide from player state instead: if the local player has shortcut powers, unhide the + // freshly rebuilt bar at its rest position and populate it directly, so the hidden->show transition + // in updateSpecialPowerShortcut - and its 500ms slide-in that a resize would otherwise replay - never fires. + const Bool shouldShow = (m_specialPowerShortcutParent != nullptr) && canShowSpecialPowerShortcut(); + if( shouldShow ) + { + m_specialPowerShortcutParent->winHide( FALSE ); + populateSpecialPowerShortcut( player ); + } +} + void ControlBar::populateSpecialPowerShortcut( Player *player) { const CommandSet *commandSet; 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/Core/GameEngine/Source/GameClient/GUI/GameWindowGlobal.cpp b/Core/GameEngine/Source/GameClient/GUI/GameWindowGlobal.cpp index 67b0945a4e0..4875c8a218d 100644 --- a/Core/GameEngine/Source/GameClient/GUI/GameWindowGlobal.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/GameWindowGlobal.cpp @@ -208,7 +208,9 @@ Int GameWindowManager::winIsDigit( Int c ) Int GameWindowManager::winIsAscii( Int c ) { - return iswascii( c ); + // TheSuperHackers @build bobtista 24/07/2026 iswascii is a nonstandard libc + // extension (absent from glibc by default). Test the 7-bit ASCII range directly. + return ( c & ~0x7F ) == 0; } diff --git a/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp b/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp index 09f22640cf4..da25eb809fc 100644 --- a/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp @@ -169,12 +169,30 @@ 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. + // TheSuperHackers @bugfix bobtista 18/07/2026 Only bail for a named window that failed to + // resolve. Whole-screen fades (SCREENFADE/FULLFADE) have an empty WinName and no window by + // design, so bailing on them here dropped the load fade-out/fade-in on non-Windows builds. + if( !m_win && !m_winName.isEmpty() ) + { + 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,16 +204,27 @@ 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; - if(m_transition) + // TheSuperHackers @bugfix bobtista 09/06/2026 If the GameWindow was destroyed mid-transition it + // unlinks itself and nulls m_win, but the transition object lives on in the handler. The style + // update()/reverse()/skip() methods dereference m_win (e.g. CountUpTransition::update -> winHide), + // so stop forwarding once the window is gone, and report finished so the group does not stall. + // Windowless whole-screen fades (empty WinName) never had a window, so keep forwarding those. + if(m_transition && (m_win || m_winName.isEmpty())) m_transition->update( frame - m_currentFrameDelay); } Bool TransitionWindow::isFinished() { - if(m_transition) + if(m_transition && (m_win || m_winName.isEmpty())) return m_transition->isFinished(); return TRUE; } @@ -203,19 +232,19 @@ Bool TransitionWindow::isFinished() void TransitionWindow::reverse( Int totalFrames ) { //m_currentFrameDelay = totalFrames - (m_transition->getFrameLength() + m_frameDelay); - if(m_transition) + if(m_transition && (m_win || m_winName.isEmpty())) m_transition->reverse(); } void TransitionWindow::skip() { - if(m_transition) + if(m_transition && (m_win || m_winName.isEmpty())) m_transition->skip(); } void TransitionWindow::draw() { - if(m_transition) + if(m_transition && (m_win || m_winName.isEmpty())) m_transition->draw(); } @@ -509,6 +538,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 +643,3 @@ void GameWindowTransitionsHandler::parseWindow( INI* ini, void *instance, void * ini->initFromINI(transWin, myFieldParse); ((TransitionGroup*)instance)->addWindow(transWin); } - 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/GUI/LoadScreen.cpp b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp index e97fe8e3217..aa25a5d48e9 100644 --- a/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/LoadScreen.cpp @@ -713,6 +713,7 @@ ChallengeLoadScreen::ChallengeLoadScreen() m_overlayVsBackdrop = nullptr; m_overlayVs = nullptr; m_wndVideoManager = nullptr; + m_tauntHandle = AHSV_NoSound; } ChallengeLoadScreen::~ChallengeLoadScreen() @@ -1140,7 +1141,7 @@ void ChallengeLoadScreen::init( GameInfo *game ) AudioEventRTS event( generalOpponent->getRandomTauntSound() ); - TheAudio->addAudioEvent( &event ); + m_tauntHandle = TheAudio->addAudioEvent( &event ); m_ambientLoopHandle = TheAudio->addAudioEvent(&m_ambientLoop); TheAudio->update(); @@ -1169,6 +1170,15 @@ void ChallengeLoadScreen::setProgressRange( Int min, Int max ) } +Bool ChallengeLoadScreen::isReadyForGameStart() const +{ + // TheSuperHackers @bugfix bobtista 24/07/2026 The challenge taunt plays while the map loads. + // Keep the completed load screen visible until the taunt finishes instead of + // deleting it (and stopping the voice) as soon as the map reaches 100%. + return TheAudio == nullptr || m_tauntHandle < AHSV_FirstHandle || + !TheAudio->isCurrentlyPlaying( m_tauntHandle ); +} + // ShellGameLoadScreen Class ////////////////////////////////////////////////// //----------------------------------------------------------------------------- ShellGameLoadScreen::ShellGameLoadScreen() @@ -2008,4 +2018,3 @@ void MapTransferLoadScreen::setCurrentFilename(AsciiString filename) GadgetStaticTextSetText(m_fileNameText, txt); } } - 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/Input/Keyboard.cpp b/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp index ea1c5e4425a..9d5533dccbc 100644 --- a/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp +++ b/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp @@ -243,8 +243,12 @@ Bool Keyboard::checkKeyRepeat() for( index = 0; index< NUM_KEYS; index++ ) m_keyStatus[ index ].keyDownTimeMsec = now; - // Set repeated key so it will repeat again after the interval - m_keyStatus[ key ].keyDownTimeMsec = now - (Keyboard::KEY_REPEAT_DELAY_MSEC + Keyboard::KEY_REPEAT_INTERVAL_MSEC); + // TheSuperHackers @bugfix bobtista 09/06/2026 Schedule the next repeat one INTERVAL + // from now (time-based). The previous code subtracted DELAY+INTERVAL, leaving the key + // permanently past the repeat threshold so it repeated EVERY frame and ignored + // KEY_REPEAT_INTERVAL_MSEC. At high frame rates (e.g. macOS) that caused runaway + // repeats - one backspace tap deleting several characters. + m_keyStatus[ key ].keyDownTimeMsec = now - (Keyboard::KEY_REPEAT_DELAY_MSEC - Keyboard::KEY_REPEAT_INTERVAL_MSEC); retVal = TRUE; break; // exit for key @@ -341,7 +345,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/GameClient/Input/Mouse.cpp b/Core/GameEngine/Source/GameClient/Input/Mouse.cpp index db7c287cfe8..a2992ccc11c 100644 --- a/Core/GameEngine/Source/GameClient/Input/Mouse.cpp +++ b/Core/GameEngine/Source/GameClient/Input/Mouse.cpp @@ -546,6 +546,7 @@ Mouse::Mouse() m_tooltipBackColor.alpha = 255; m_cursorCaptureMode = 0; + m_cursorCaptureInitialized = FALSE; m_captureBlockReasonBits = (1 << CursorCaptureBlockReason_NoInit); DEBUG_LOG(("Mouse::Mouse: m_blockCaptureReason=CursorCaptureBlockReason_NoInit")); @@ -670,7 +671,17 @@ void Mouse::reset() if ( m_cursorTextDisplayString ) m_cursorTextDisplayString->reset(); - blockCapture(CursorCaptureBlockReason_NoInit); + // TheSuperHackers @bugfix bobtista 27/06/2026 Only block capture with NoInit before the first + // initCapture(). Once capture has been initialized, refresh it instead so reset() does not + // permanently re-disable cursor capture (and therefore edge-of-screen scrolling) during gameplay. + if (!m_cursorCaptureInitialized) + { + blockCapture(CursorCaptureBlockReason_NoInit); + } + else + { + refreshCursorCapture(); + } } @@ -1061,6 +1072,7 @@ void Mouse::initCapture() OptionPreferences prefs; m_cursorCaptureMode = prefs.getCursorCaptureMode(); + m_cursorCaptureInitialized = TRUE; unblockCapture(CursorCaptureBlockReason_NoInit); } 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() diff --git a/Core/GameEngine/Source/GameClient/MapUtil.cpp b/Core/GameEngine/Source/GameClient/MapUtil.cpp index 1806c50741a..638f8d47ebc 100644 --- a/Core/GameEngine/Source/GameClient/MapUtil.cpp +++ b/Core/GameEngine/Source/GameClient/MapUtil.cpp @@ -500,7 +500,11 @@ void MapCache::loadMapsFromMapCacheINI( const AsciiString &mapDir ) { INI ini; AsciiString fname; +#ifdef _WIN32 fname.format("%s\\%s", mapDir.str(), m_mapCacheName); +#else + fname.format("%s/%s", mapDir.str(), m_mapCacheName); +#endif if (TheFileSystem->doesFileExist(fname.str())) { @@ -515,7 +519,11 @@ Bool MapCache::loadMapsFromDisk( const AsciiString &mapDir, Bool isOfficial, Boo FilenameList filepathList; FilenameListIter filepathIt; AsciiString toplevelPattern; +#ifdef _WIN32 toplevelPattern.format("%s\\", mapDir.str()); +#else + toplevelPattern.format("%s/", mapDir.str()); +#endif Bool mapListChanged = FALSE; AsciiString filenamepattern; filenamepattern.format("*.%s", getMapExtension().str()); @@ -531,9 +539,16 @@ Bool MapCache::loadMapsFromDisk( const AsciiString &mapDir, Bool isOfficial, Boo filepathLower.toLower(); const char *szFilenameLower = filepathLower.reverseFind('\\'); +#ifndef _WIN32 + const char *szFwd = filepathLower.reverseFind('/'); + if (szFwd && (!szFilenameLower || szFwd > szFilenameLower)) + { + szFilenameLower = szFwd; + } +#endif if (!szFilenameLower) { - DEBUG_CRASH(("Couldn't find \\ in map name!")); + DEBUG_CRASH(("Couldn't find path separator in map name!")); continue; } @@ -547,7 +562,11 @@ Bool MapCache::loadMapsFromDisk( const AsciiString &mapDir, Bool isOfficial, Boo continue; } +#ifdef _WIN32 endingStr.format("%s\\%s%s", filenameLower.str(), filenameLower.str(), mapExtension); +#else + endingStr.format("%s/%s%s", filenameLower.str(), filenameLower.str(), mapExtension); +#endif if (!filepathLower.endsWithNoCase(endingStr.str())) { @@ -592,7 +611,19 @@ Bool MapCache::addMap( { // unofficial maps or maps without names AsciiString tempdisplayname; +#ifdef _WIN32 tempdisplayname = fname.reverseFind('\\') + 1; +#else + { + const char* sep = fname.reverseFind('\\'); + const char* fwd = fname.reverseFind('/'); + if (fwd && (!sep || fwd > sep)) + { + sep = fwd; + } + tempdisplayname = sep ? sep + 1 : fname.str(); + } +#endif (*this)[lowerFname].m_displayName.translate(tempdisplayname); if (md.m_numPlayers >= 2) { @@ -1081,6 +1112,19 @@ Bool isOfficialMap( AsciiString mapName ) const MapMetaData *MapCache::findMap(AsciiString mapName) { mapName.toLower(); + // TheSuperHackers @bugfix bobtista 11/07/2026 Normalize separators to the + // platform's cache-key form in both directions. The 09/06/2026 fix covered + // only the macOS direction ('\\'->'/'), so on Windows a forward-slash map + // name (legacy cross-platform saves, lobby map names) could never match. + { + std::string normalized(mapName.str()); +#ifdef _WIN32 + std::replace(normalized.begin(), normalized.end(), '/', '\\'); +#else + std::replace(normalized.begin(), normalized.end(), '\\', '/'); +#endif + mapName.set(normalized.c_str()); + } MapCache::iterator it = find(mapName); if (it == end()) return nullptr; @@ -1156,7 +1200,7 @@ Image *getMapPreviewImage( AsciiString mapName ) for(Int i = 0; i < portableName.getLength(); ++i) { char c = portableName.getCharAt(i); - if (c == '\\' || c == ':') + if (c == '\\' || c == '/' || c == ':') tempName.concat('_'); else tempName.concat(c); @@ -1173,7 +1217,8 @@ Image *getMapPreviewImage( AsciiString mapName ) if(!image) { - if(!TheFileSystem->doesFileExist(tgaName.str())) + Bool sourceExists = TheFileSystem->doesFileExist(tgaName.str()); + if(!sourceExists) return nullptr; AsciiString mapPreviewDir; mapPreviewDir.format(MAP_PREVIEW_DIR_PATH, TheGlobalData->getPath_UserData().str()); @@ -1194,7 +1239,7 @@ Image *getMapPreviewImage( AsciiString mapName ) if (success) { - image = newInstance(Image); + image = newInstance(Image); image->setName(tempName); //image->setFullPath("mission.tga"); image->setFilename(name); @@ -1349,4 +1394,3 @@ void findDrawPositions( Int startX, Int startY, Int width, Int height, Region3D lr->y += startY; } - diff --git a/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 7e322b28241..b0d12de8059 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -3450,6 +3450,13 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage #endif { TheWritableGlobalData->m_TiVOFastMode = 1 - TheGlobalData->m_TiVOFastMode; + // TheSuperHackers @bugfix bobtista 12/07/2026 Unpause the game when fast forward is + // engaged, otherwise a replay paused by a CRC mismatch cannot be resumed now that + // fast forward no longer overrides the paused game. + if (TheGlobalData->m_TiVOFastMode && TheGameLogic->isGamePaused()) + { + TheGameLogic->setGamePaused(FALSE); + } TheInGameUI->messageNoFormat( TheGlobalData->m_TiVOFastMode ? TheGameText->FETCH_OR_SUBSTITUTE("GUI:FF_ON", L"Fast Forward is on") : TheGameText->FETCH_OR_SUBSTITUTE("GUI:FF_OFF", L"Fast Forward is off") diff --git a/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp b/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp index b4ef8bfdbbb..9bc70024463 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/LookAtXlat.cpp @@ -374,7 +374,7 @@ GameMessageDisposition LookAtTranslator::translateGameMessage(const GameMessage if (TheInGameUI->isInForceAttackMode()) { const Real snapRadians = DEG_TO_RADF(45); - targetAngle = WWMath::Round(targetAngle / snapRadians) * snapRadians; + targetAngle = WWMath::Roundf(targetAngle / snapRadians) * snapRadians; } TheTacticalView->userSetAngle(targetAngle); diff --git a/Core/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp b/Core/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp index 31a954e67b5..3c4f173d75d 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp @@ -827,6 +827,18 @@ void MetaMap::generateMetaMap() map->m_usableIn = COMMANDUSABLE_EVERYWHERE; } } + { + // TheSuperHackers @feature bobtista 09/07/2026 Default F12 to take a screenshot, only when the + // user has not already bound the key. Useful for Generals and Zero Hour. + MetaMapRec *map = getMetaMapRec(GameMessage::MSG_META_TAKE_SCREENSHOT); + if (map->m_key == MK_NONE) + { + map->m_key = MK_F12; + map->m_transition = DOWN; + map->m_modState = NONE; + map->m_usableIn = COMMANDUSABLE_EVERYWHERE; + } + } { // Is useful for Generals and Zero Hour. MetaMapRec *map = getMetaMapRec(GameMessage::MSG_META_INCREASE_LOGIC_TIME_SCALE); diff --git a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp index 1a79b3c2b6d..ffc4cdc742f 100644 --- a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp +++ b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp @@ -481,7 +481,7 @@ Bool Particle::update() else if (m_color.red > 1.0f) m_color.red = 1.0f; - if (m_color.red < 0.0f) + if (m_color.green < 0.0f) m_color.green = 0.0f; else if (m_color.green > 1.0f) m_color.green = 1.0f; @@ -1838,6 +1838,20 @@ const ParticleInfo *ParticleSystem::generateParticleInfo( Int particleNum, Int p info.m_vel.z = vr.Z; } +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @bugfix bobtista 28/05/2026 Pin ground-aligned particles over water to + // just above the water surface; the parent bone can sit at hull-deck height, stranding + // the foam above the waterline. BGFX-only compensation; DX8 rendered them correctly. + if (m_isGroundAligned && TheTerrainLogic != nullptr) + { + Real waterZ = 0.0f; + if (TheTerrainLogic->isUnderwater(info.m_pos.x, info.m_pos.y, &waterZ, nullptr)) + { + info.m_pos.z = waterZ + 0.5f; + } + } +#endif + info.m_velDamping = m_velDamping.getValue(); info.m_angularDamping = m_angularDamping.getValue(); diff --git a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index 0614563bd43..b9098228932 100644 --- a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -727,9 +727,9 @@ inline Bool isReallyClose(const Coord3D& a, const Coord3D& b) { const Real CLOSE_ENOUGH = 0.1f; return - fabs(a.x-b.x) <= CLOSE_ENOUGH && - fabs(a.y-b.y) <= CLOSE_ENOUGH && - fabs(a.z-b.z) <= CLOSE_ENOUGH; + WWMath::Fabs(a.x-b.x) <= CLOSE_ENOUGH && + WWMath::Fabs(a.y-b.y) <= CLOSE_ENOUGH && + WWMath::Fabs(a.z-b.z) <= CLOSE_ENOUGH; } /** @@ -921,14 +921,17 @@ void Path::computePointOnPath( // compute distance of point from this path segment Real toDistSqr = sqr(toPos.x) + sqr(toPos.y); Real offsetDistSq = toDistSqr - sqr(alongPathDist); - Real offsetDist = (offsetDistSq <= 0.0) ? 0.0 : sqrt(offsetDistSq); + Real offsetDist = (offsetDistSq <= 0.0) ? 0.0 : WWMath::Sqrtf(offsetDistSq); // If we are basically on the path, return the next path node as the movement goal. // However, the farther off the path we get, the movement goal becomes closer to our // projected position on the path. If we are very far off the path, we will move // directly towards the nearest point on the path, and not the next path node. const Real maxPathError = 3.0f * PATHFIND_CELL_SIZE_F; - const Real maxPathErrorInv = 1.0 / maxPathError; + // TheSuperHackers @bugfix bobtista 10/06/2026 1.0 is a double literal, forcing a double-precision + // reciprocal that diverges between x87 (32-bit Windows) and arm64; use 1.0f so this movement-goal + // math stays single-precision and deterministic across platforms. + const Real maxPathErrorInv = 1.0f / maxPathError; Real k = offsetDist * maxPathErrorInv; if (k > 1.0f) k = 1.0f; @@ -1011,8 +1014,8 @@ void Path::computePointOnPath( out.posOnPath.x = closeNodePos->x + alongPathDist * segmentDirNorm.x; out.posOnPath.y = closeNodePos->y + alongPathDist * segmentDirNorm.y; out.posOnPath.z = closeNodePos->z; - Real dx = fabs(pos.x - out.posOnPath.x); - Real dy = fabs(pos.y - out.posOnPath.y); + Real dx = WWMath::Fabs(pos.x - out.posOnPath.x); + Real dy = WWMath::Fabs(pos.y - out.posOnPath.y); if (dx<1 && dy<1 && closeNode->getNextOptimized() && closeNode->getNextOptimized()->getNextOptimized()) { out.posOnPath = *closeNode->getNextOptimized()->getNextOptimized()->getPosition(); } @@ -2070,7 +2073,7 @@ UnsignedInt PathfindCell::costToGoal( PathfindCell *goal ) Int dy = m_info->m_pos.y - goal->getYIndex(); #define NO_REAL_DIST #ifdef REAL_DIST - Int cost = COST_ORTHOGONAL*sqrt(dx*dx + dy*dy); + Int cost = COST_ORTHOGONAL*WWMath::Sqrtf(dx*dx + dy*dy); #else if (dx<0) dx = -dx; if (dy<0) dy = -dy; @@ -2096,7 +2099,7 @@ UnsignedInt PathfindCell::costToHierGoal( PathfindCell *goal ) } Int dx = m_info->m_pos.x - goal->getXIndex(); Int dy = m_info->m_pos.y - goal->getYIndex(); - Int cost = REAL_TO_INT_FLOOR(COST_ORTHOGONAL*sqrt(dx*dx + dy*dy) + 0.5f); + Int cost = REAL_TO_INT_FLOOR(COST_ORTHOGONAL*WWMath::Sqrtf(dx*dx + dy*dy) + 0.5f); return cost; } @@ -3963,8 +3966,8 @@ Bool PathfindLayer::isPointOnWall(ObjectID *wallPieces, Int numPieces, const Coo Real pty = pt->y - obj->getPosition()->y; // inverse-rotate it to the right coord system - Real ptx_new = (Real)fabs(ptx*c - pty*s); - Real pty_new = (Real)fabs(ptx*s + pty*c); + Real ptx_new = (Real)WWMath::Fabs(ptx*c - pty*s); + Real pty_new = (Real)WWMath::Fabs(ptx*s + pty*c); if (ptx_new <= major && pty_new <= minor) { @@ -6419,7 +6422,7 @@ Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell * toPos.y = newCellCoord.y * PATHFIND_CELL_SIZE_F ; toPos.z = TheTerrainLogic->getGroundHeight(toPos.x , toPos.y); - if ( fabs(fromPos.z - toPos.z)getPinched()) { @@ -6444,7 +6447,7 @@ Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell * } else { dx = newCellCoord.x - goalCell->getXIndex(); dy = newCellCoord.y - goalCell->getYIndex(); - costRemaining = COST_ORTHOGONAL*sqrt(dx*dx + dy*dy); + costRemaining = COST_ORTHOGONAL*WWMath::Sqrtf(dx*dx + dy*dy); costRemaining -= attackDistance/2; if (costRemaining<0) costRemaining=0; @@ -6768,7 +6771,7 @@ Path *Pathfinder::internalFindPath( Object *obj, const LocomotorSet& locomotorSe dx = from->x - to->x; dy = from->y - to->y; - Int count = sqrt(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2); + Int count = WWMath::Sqrtf(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2); if (count<2) count = 2; Int i; color.green = 0; @@ -7459,7 +7462,7 @@ Path *Pathfinder::findGroundPath( const Coord3D *from, dx = from->x - to->x; dy = from->y - to->y; - Int count = sqrt(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2); + Int count = WWMath::Sqrtf(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2); if (count<2) count = 2; Int i; color.green = 0; @@ -8163,7 +8166,7 @@ Path *Pathfinder::internal_findHierarchicalPath( Bool isHuman, const LocomotorSu dx = from->x - to->x; dy = from->y - to->y; - Int count = sqrt(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2); + Int count = WWMath::Sqrtf(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2); if (count<2) count = 2; Int i; color.green = 0; @@ -11216,7 +11219,7 @@ Path *Pathfinder::findSafePath( const Object *obj, const LocomotorSet& locomotor farthestDistanceSqr = distSqr; if (cellCount > MAX_CELLS) { #ifdef INTENSE_DEBUG - DEBUG_LOG(("Took intermediate path, dist %f, goal dist %f", sqrt(farthestDistanceSqr), repulsorRadius)); + DEBUG_LOG(("Took intermediate path, dist %f, goal dist %f", WWMath::Sqrtf(farthestDistanceSqr), repulsorRadius)); #endif ok = true; // Already a big search, just take this one. } diff --git a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index c9c7cbb7602..b0432c38f1e 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; @@ -870,6 +870,21 @@ bool GameLogic::onNewGame(MAYBE_UNUSED GameMessage *msg) //DEBUG_ASSERTCRASH(msg->getArgumentCount() == 1 || msg->getArgumentCount() == 2, ("%d arguments to MSG_NEW_GAME", msg->getArgumentCount())); GameMode gameMode = (GameMode)msg->getArgument( 0 )->integer; + + // TheSuperHackers @bugfix bobtista 13/06/2026 A GAME_SHELL new-game exists only to show the menu-background + // shell map. Shell::showShellMap queues it with m_pendingFile = m_shellMapName, but a real game request + // (e.g. Generals Challenge "Continue" -> startNextCampaignGame) can overwrite m_pendingFile and queue its + // own MSG_NEW_GAME right after. If this now-stale shell message still runs first it would load the real map + // in GAME_SHELL mode (no human player created -> instant defeat) and then block the real request via the + // isInGame()/isLoadingMap() guard above. Skip the stale shell start so the real new-game wins. + if ( gameMode == GAME_SHELL + && !TheGlobalData->m_shellMapName.isEmpty() + && TheGlobalData->m_pendingFile != TheGlobalData->m_shellMapName ) + { + DEBUG_LOG(("onNewGame: skipping stale GAME_SHELL new-game (pendingFile=%s != shellMap=%s) superseded by a real game start", + TheGlobalData->m_pendingFile.str(), TheGlobalData->m_shellMapName.str())); + return false; + } Int rankPoints = 0; GameDifficulty diff = DIFFICULTY_NORMAL; if ( msg->getArgumentCount() >= 2 ) 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/GameInfo.cpp b/Core/GameEngine/Source/GameNetwork/GameInfo.cpp index 5b05e9eb369..8f2203ccf71 100644 --- a/Core/GameEngine/Source/GameNetwork/GameInfo.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameInfo.cpp @@ -623,6 +623,9 @@ void GameInfo::setMapCRC( UnsignedInt mapCRC ) lowerMap.toLower(); //DEBUG_LOG(("GameInfo::setMapCRC - looking for map file \"%s\" in the map cache", lowerMap.str())); std::map::iterator it = TheMapCache->find(lowerMap); + DEBUG_LOG(("GameInfo::setMapCRC - map='%s' found=%d wantCRC=0x%08x cachedCRC=0x%08x", + lowerMap.str(), (it != TheMapCache->end())?1:0, m_mapCRC, + (it != TheMapCache->end())?it->second.m_CRC:0)); if (it == TheMapCache->end()) { /* @@ -920,6 +923,9 @@ AsciiString GameInfoToAsciiString( const GameInfo *game ) DEBUG_LOG(("Map name is %s", mapName.str())); } + DEBUG_LOG(("GameInfoToAsciiString - portableMap='%s' encodedDir='%s'", + TheGameState->realMapPathToPortableMapPath(game->getMap()).str(), newMapName.str())); + AsciiString optionsString; #if RTS_GENERALS optionsString.format("M=%2.2x%s;MC=%X;MS=%d;SD=%d;C=%d;", game->getMapContentsMask(), newMapName.str(), 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..67ed803a3aa 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!")); @@ -733,7 +733,7 @@ DWORD WINAPI asyncGethostbynameThreadFunc( void * szName ) int asyncGethostbyname(char * szName) { static int stat = 0; - static unsigned long threadid; + static DWORD threadid; if( stat == 0 ) { 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/GameResultsThread.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/GameResultsThread.cpp index 161db067202..e57b1009a80 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/GameResultsThread.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/GameResultsThread.cpp @@ -275,6 +275,11 @@ void GameResultsThreadClass::Thread_Function() static const char *getWSAErrorString( Int error ) { +#ifndef _WIN32 + // TheSuperHackers @build bobtista 09/06/2026 The WSA* error names are Windows-only; on + // other platforms socket errors are POSIX errno values, so report those instead. + return strerror(error); +#else switch (error) { CASE(WSABASEERR) @@ -332,6 +337,7 @@ static const char *getWSAErrorString( Int error ) default: return "Not a Winsock error"; } +#endif } #undef CASE 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/GameSpy/Thread/PingThread.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/PingThread.cpp index 6040a0e2415..33ee3fa7f2f 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/PingThread.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/PingThread.cpp @@ -463,8 +463,8 @@ Int PingThreadClass::doPing(UnsignedInt IP, Int timeout) */ lpfnIcmpCreateFile = (void * (__stdcall *)())GetProcAddress( (HINSTANCE)hICMP_DLL, "IcmpCreateFile"); lpfnIcmpCloseHandle = (int (__stdcall *)(void *))GetProcAddress( (HINSTANCE)hICMP_DLL, "IcmpCloseHandle"); - lpfnIcmpSendEcho = (unsigned long (__stdcall *)(void *, unsigned long, void *, unsigned short, - void *, void *, unsigned long, unsigned long))GetProcAddress( (HINSTANCE)hICMP_DLL, "IcmpSendEcho" ); + lpfnIcmpSendEcho = (DWORD (__stdcall *)(HANDLE, DWORD, LPVOID, WORD, + LPVOID, LPVOID, DWORD, DWORD))GetProcAddress( (HINSTANCE)hICMP_DLL, "IcmpSendEcho" ); if ((!lpfnIcmpCreateFile) || (!lpfnIcmpCloseHandle) || diff --git a/Core/GameEngine/Source/GameNetwork/IPEnumeration.cpp b/Core/GameEngine/Source/GameNetwork/IPEnumeration.cpp index 0a6829b831b..c55f462f4ca 100644 --- a/Core/GameEngine/Source/GameNetwork/IPEnumeration.cpp +++ b/Core/GameEngine/Source/GameNetwork/IPEnumeration.cpp @@ -28,6 +28,15 @@ #include "GameNetwork/networkutil.h" #include "GameClient/ClientInstance.h" +#ifndef _WIN32 +#include +#include +#else +// TheSuperHackers @build bobtista 12/06/2026 INTERFACE_INFO / SIO_GET_INTERFACE_LIST (used by +// getSubnetBroadcastAddress) are declared in ws2ipdef.h, which winsock2.h does not include by default. +#include +#endif + IPEnumeration::IPEnumeration() { m_IPlist = nullptr; @@ -58,6 +67,11 @@ EnumeratedIP * IPEnumeration::getAddresses() if (!m_isWinsockInitialized) { + // TheSuperHackers @bugfix bobtista 09/06/2026 Only validate the Winsock + // version on Windows. On other platforms WSAStartup is a no-op that never + // fills wsadata, so reading wsadata.wVersion returns uninitialized memory + // and this check spuriously fails, leaving the machine with no IP list. +#ifdef _WIN32 WORD verReq = MAKEWORD(2, 2); WSADATA wsadata; @@ -70,9 +84,22 @@ EnumeratedIP * IPEnumeration::getAddresses() WSACleanup(); return nullptr; } +#endif m_isWinsockInitialized = true; } + // TheSuperHackers @feature Add one unique local host IP address for each multi client instance. + if (rts::ClientInstance::isMultiInstance()) + { + const UnsignedInt id = rts::ClientInstance::getInstanceId(); + addNewIP( + 127, + (UnsignedByte)(id >> 16), + (UnsignedByte)(id >> 8), + (UnsignedByte)(id)); + } + +#ifdef _WIN32 // get the local machine's host name char hostname[256]; if (gethostname(hostname, sizeof(hostname))) @@ -97,17 +124,6 @@ EnumeratedIP * IPEnumeration::getAddresses() return nullptr; } - // TheSuperHackers @feature Add one unique local host IP address for each multi client instance. - if (rts::ClientInstance::isMultiInstance()) - { - const UnsignedInt id = rts::ClientInstance::getInstanceId(); - addNewIP( - 127, - (UnsignedByte)(id >> 16), - (UnsignedByte)(id >> 8), - (UnsignedByte)(id)); - } - // construct a list of addresses int numAddresses = 0; char *entry; @@ -119,10 +135,102 @@ EnumeratedIP * IPEnumeration::getAddresses() (UnsignedByte)entry[2], (UnsignedByte)entry[3]); } +#else + // TheSuperHackers @feature bobtista 09/06/2026 Enumerate every local IPv4 interface via + // getifaddrs. gethostbyname(hostname) only returns the addresses the host name resolves to + // on non-Windows, which omits VPN/tunnel adapters (Hamachi, ZeroTier, utun) that are needed + // to host or join an internet "LAN" or direct-connect game. Loopback is skipped so it is + // never offered as the local IP. + struct ifaddrs *ifaddrList = nullptr; + if (getifaddrs(&ifaddrList) == 0) + { + for (struct ifaddrs *ifa = ifaddrList; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr == nullptr || ifa->ifa_addr->sa_family != AF_INET) + { + continue; + } + if ((ifa->ifa_flags & IFF_UP) == 0 || (ifa->ifa_flags & IFF_LOOPBACK) != 0) + { + continue; + } + const struct sockaddr_in *sin = (const struct sockaddr_in *)ifa->ifa_addr; + const UnsignedInt addr = ntohl(sin->sin_addr.s_addr); + addNewIP( + (UnsignedByte)(addr >> 24), + (UnsignedByte)(addr >> 16), + (UnsignedByte)(addr >> 8), + (UnsignedByte)(addr)); + } + freeifaddrs(ifaddrList); + } +#endif return m_IPlist; } +// TheSuperHackers @bugfix bobtista 12/06/2026 The LAN protocol broadcasts discovery announces and +// JOIN_ACCEPT to the limited broadcast 255.255.255.255, which only egresses the single default-route +// interface. On a multi-homed host (a ZeroTier/VPN overlay adapter alongside Wi-Fi) those packets +// never reach peers on the overlay subnet, so machines can't see each other in the LAN lobby and a +// direct-connect joiner times out waiting for its (broadcast) accept even though the host has already +// added it. Sending to the subnet-directed broadcast of the selected local IP instead routes the +// packet out the interface that owns that subnet. Returns host byte order; falls back to the limited +// broadcast when the netmask can't be resolved (preserving the original behavior). +UnsignedInt IPEnumeration::getSubnetBroadcastAddress( UnsignedInt localIP ) +{ + if (localIP == 0) + { + return INADDR_BROADCAST; + } + +#ifdef _WIN32 + SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock != INVALID_SOCKET) + { + INTERFACE_INFO ifList[32]; + DWORD bytesReturned = 0; + if (WSAIoctl(sock, SIO_GET_INTERFACE_LIST, nullptr, 0, ifList, sizeof(ifList), &bytesReturned, nullptr, nullptr) == 0) + { + const int count = (int)(bytesReturned / sizeof(INTERFACE_INFO)); + for (int i = 0; i < count; ++i) + { + const UnsignedInt ifaceIP = ntohl(((struct sockaddr_in *)&ifList[i].iiAddress)->sin_addr.s_addr); + if (ifaceIP == localIP) + { + const UnsignedInt mask = ntohl(((struct sockaddr_in *)&ifList[i].iiNetmask)->sin_addr.s_addr); + closesocket(sock); + return (localIP & mask) | (~mask); + } + } + } + closesocket(sock); + } +#else + struct ifaddrs *ifaddrList = nullptr; + if (getifaddrs(&ifaddrList) == 0) + { + for (struct ifaddrs *ifa = ifaddrList; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr == nullptr || ifa->ifa_addr->sa_family != AF_INET || ifa->ifa_netmask == nullptr) + { + continue; + } + const UnsignedInt ifaceIP = ntohl(((const struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr); + if (ifaceIP == localIP) + { + const UnsignedInt mask = ntohl(((const struct sockaddr_in *)ifa->ifa_netmask)->sin_addr.s_addr); + freeifaddrs(ifaddrList); + return (localIP & mask) | (~mask); + } + } + freeifaddrs(ifaddrList); + } +#endif + + return INADDR_BROADCAST; +} + void IPEnumeration::addNewIP( UnsignedByte a, UnsignedByte b, UnsignedByte c, UnsignedByte d ) { EnumeratedIP *newIP = newInstance(EnumeratedIP); @@ -167,6 +275,10 @@ AsciiString IPEnumeration::getMachineName() { if (!m_isWinsockInitialized) { + // TheSuperHackers @bugfix bobtista 09/06/2026 Only validate the Winsock + // version on Windows. On other platforms WSAStartup is a no-op that never + // fills wsadata, so reading wsadata.wVersion returns uninitialized memory. +#ifdef _WIN32 WORD verReq = MAKEWORD(2, 2); WSADATA wsadata; @@ -179,6 +291,7 @@ AsciiString IPEnumeration::getMachineName() WSACleanup(); return ""; } +#endif m_isWinsockInitialized = true; } diff --git a/Core/GameEngine/Source/GameNetwork/LANAPI.cpp b/Core/GameEngine/Source/GameNetwork/LANAPI.cpp index 8cbfbdea6c5..d30766bcb8f 100644 --- a/Core/GameEngine/Source/GameNetwork/LANAPI.cpp +++ b/Core/GameEngine/Source/GameNetwork/LANAPI.cpp @@ -30,6 +30,7 @@ #include "Common/GameState.h" #include "Common/Registry.h" #include "GameNetwork/LANAPI.h" +#include "GameNetwork/IPEnumeration.h" #include "GameNetwork/networkutil.h" #include "Common/GlobalData.h" #include "Common/RandomValue.h" @@ -100,9 +101,20 @@ void LANAPI::init() m_gameStartTime = 0; m_gameStartSeconds = 0; m_transport->reset(); + // TheSuperHackers @bugfix bobtista 09/06/2026 On macOS/BSD a UDP socket bound to a + // specific local IP does not receive limited broadcasts (255.255.255.255), which + // broke LAN discovery and broadcast join requests on the Mac. Bind to INADDR_ANY so + // broadcasts are received; m_localIP is still used as our identity in the protocol. +#ifdef _WIN32 m_transport->init(m_localIP, lobbyPort); +#else + m_transport->init((UnsignedInt)0, lobbyPort); +#endif m_transport->allowBroadcasts(true); + DEBUG_LOG(("LANAPI::init - identity localIP=%d.%d.%d.%d broadcast=%d.%d.%d.%d port=%d sizeof(LANMessage)=%d", + PRINTF_IP_AS_4_INTS(m_localIP), PRINTF_IP_AS_4_INTS(m_broadcastAddr), (int)lobbyPort, (int)sizeof(LANMessage))); + m_pendingAction = ACT_NONE; m_expiration = 0; m_inLobby = true; @@ -183,6 +195,8 @@ void LANAPI::sendMessage(LANMessage *msg, UnsignedInt ip /* = 0 */) { if (ip != 0) { + DEBUG_LOG(("LANAPI::sendMessage - unicast type=%d to %d.%d.%d.%d:%d len=%d", + (int)msg->messageType, PRINTF_IP_AS_4_INTS(ip), (int)lobbyPort, (int)sizeof(LANMessage))); m_transport->queueSend(ip, lobbyPort, (unsigned char *)msg, sizeof(LANMessage) /*, 0, 0 */); } else if ((m_currentGame != nullptr) && (m_currentGame->getIsDirectConnect())) @@ -200,6 +214,8 @@ void LANAPI::sendMessage(LANMessage *msg, UnsignedInt ip /* = 0 */) } else { + DEBUG_LOG(("LANAPI::sendMessage - broadcast type=%d to %d.%d.%d.%d:%d len=%d", + (int)msg->messageType, PRINTF_IP_AS_4_INTS(m_broadcastAddr), (int)lobbyPort, (int)sizeof(LANMessage))); m_transport->queueSend(m_broadcastAddr, lobbyPort, (unsigned char *)msg, sizeof(LANMessage) /*, 0, 0 */); } } @@ -354,8 +370,9 @@ void LANAPI::update() } LANMessage *msg = (LANMessage *)(m_transport->m_inBuffer[i].data); - //DEBUG_LOG(("LAN message type %s from %ls (%s@%s)", GetMessageTypeString(msg->messageType).str(), - // msg->name, msg->userName, msg->hostName)); + DEBUG_LOG(("LANAPI::update - recv %d bytes from %d.%d.%d.%d:%d type=%d", + m_transport->m_inBuffer[i].length, PRINTF_IP_AS_4_INTS(senderIP), + (int)m_transport->m_inBuffer[i].port, (int)msg->messageType)); switch (msg->messageType) { // Location specification @@ -522,7 +539,7 @@ void LANAPI::update() LANMessage msg; fillInLANMessage( &msg ); msg.messageType = LANMessage::MSG_REQUEST_GAME_LEAVE; - wcslcpy(msg.name, m_currentGame->getPlayerName(0).str(), ARRAY_SIZE(msg.name)); + lanWideStrCopy(msg.name, m_currentGame->getPlayerName(0).str(), ARRAY_SIZE(msg.name)); handleRequestGameLeave(&msg, m_currentGame->getIP(0)); UnicodeString text; text = TheGameText->fetch("LAN:HostNotResponding"); @@ -540,7 +557,7 @@ void LANAPI::update() UnicodeString theStr; theStr.format(TheGameText->fetch("LAN:PlayerDropped"), m_currentGame->getPlayerName(p).str()); msg.messageType = LANMessage::MSG_REQUEST_GAME_LEAVE; - wcslcpy(msg.name, m_currentGame->getPlayerName(p).str(), ARRAY_SIZE(msg.name)); + lanWideStrCopy(msg.name, m_currentGame->getPlayerName(p).str(), ARRAY_SIZE(msg.name)); handleRequestGameLeave(&msg, m_currentGame->getIP(p)); OnChat(UnicodeString::TheEmptyString, m_localIP, theStr, LANCHAT_SYSTEM); } @@ -645,6 +662,7 @@ void LANAPI::RequestGameJoin( LANGameInfo *game, UnsignedInt ip /* = 0 */ ) GetStringFromRegistry("\\ergc", "", s); strlcpy(msg.GameToJoin.serial, s.str(), ARRAY_SIZE(msg.GameToJoin.serial)); + DEBUG_LOG(("RequestGameJoin - REQUEST_JOIN gameIP=0x%08x dest=0x%08x (0=bcast)", msg.GameToJoin.gameIP, ip)); sendMessage(&msg, ip); m_pendingAction = ACT_JOIN; @@ -671,7 +689,7 @@ void LANAPI::RequestGameJoinDirectConnect(UnsignedInt ipaddress) msg.messageType = LANMessage::MSG_REQUEST_GAME_INFO; fillInLANMessage(&msg); msg.PlayerInfo.ip = GetLocalIP(); - wcslcpy(msg.PlayerInfo.playerName, m_name.str(), ARRAY_SIZE(msg.PlayerInfo.playerName)); + lanWideStrCopy(msg.PlayerInfo.playerName, m_name.str(), ARRAY_SIZE(msg.PlayerInfo.playerName)); sendMessage(&msg, ipaddress); @@ -684,7 +702,7 @@ void LANAPI::RequestGameLeave() LANMessage msg; msg.messageType = LANMessage::MSG_REQUEST_GAME_LEAVE; fillInLANMessage( &msg ); - wcslcpy(msg.PlayerInfo.playerName, m_name.str(), ARRAY_SIZE(msg.PlayerInfo.playerName)); + lanWideStrCopy(msg.PlayerInfo.playerName, m_name.str(), ARRAY_SIZE(msg.PlayerInfo.playerName)); sendMessage(&msg); m_transport->update(); // Send immediately, before OnPlayerLeave below resets everything. @@ -716,7 +734,7 @@ void LANAPI::RequestGameAnnounce() AsciiString gameOpts = GameInfoToAsciiString(m_currentGame); strlcpy(reply.GameInfo.options,gameOpts.str(), ARRAY_SIZE(reply.GameInfo.options)); - wcslcpy(reply.GameInfo.gameName, m_currentGame->getName().str(), ARRAY_SIZE(reply.GameInfo.gameName)); + lanWideStrCopy(reply.GameInfo.gameName, m_currentGame->getName().str(), ARRAY_SIZE(reply.GameInfo.gameName)); reply.GameInfo.inProgress = m_currentGame->isGameInProgress(); reply.GameInfo.isDirectConnect = m_currentGame->getIsDirectConnect(); @@ -734,7 +752,7 @@ void LANAPI::RequestAccept() fillInLANMessage( &msg ); msg.messageType = LANMessage::MSG_SET_ACCEPT; msg.Accept.isAccepted = true; - wcslcpy(msg.Accept.gameName, m_currentGame->getName().str(), ARRAY_SIZE(msg.Accept.gameName)); + lanWideStrCopy(msg.Accept.gameName, m_currentGame->getName().str(), ARRAY_SIZE(msg.Accept.gameName)); sendMessage(&msg); } @@ -747,7 +765,7 @@ void LANAPI::RequestHasMap() fillInLANMessage( &msg ); msg.messageType = LANMessage::MSG_MAP_AVAILABILITY; msg.MapStatus.hasMap = m_currentGame->getSlot(m_currentGame->getLocalSlotNum())->hasMap(); - wcslcpy(msg.MapStatus.gameName, m_currentGame->getName().str(), ARRAY_SIZE(msg.MapStatus.gameName)); + lanWideStrCopy(msg.MapStatus.gameName, m_currentGame->getName().str(), ARRAY_SIZE(msg.MapStatus.gameName)); CRC mapNameCRC; //mapNameCRC.computeCRC(m_currentGame->getMap().str(), m_currentGame->getMap().getLength()); AsciiString portableMapName = TheGameState->realMapPathToPortableMapPath(m_currentGame->getMap()); @@ -784,10 +802,10 @@ void LANAPI::RequestChat( UnicodeString message, ChatType format ) { LANMessage msg; fillInLANMessage( &msg ); - wcslcpy(msg.Chat.gameName, (m_currentGame) ? m_currentGame->getName().str() : L"", ARRAY_SIZE(msg.Chat.gameName)); + lanWideStrCopy(msg.Chat.gameName, (m_currentGame) ? m_currentGame->getName().str() : L"", ARRAY_SIZE(msg.Chat.gameName)); msg.messageType = LANMessage::MSG_CHAT; msg.Chat.chatType = format; - wcslcpy(msg.Chat.message, message.str(), ARRAY_SIZE(msg.Chat.message)); + lanWideStrCopy(msg.Chat.message, message.str(), ARRAY_SIZE(msg.Chat.message)); sendMessage(&msg); OnChat(m_name, m_localIP, message, format); @@ -936,7 +954,7 @@ void LANAPI::RequestGameCreate( UnicodeString gameName, Bool isDirectConnect ) //RequestSlotList(); /* LANMessage msg; - wcslcpy(msg.name, m_name.str(), ARRAY_SIZE(msg.name)); + lanWideStrCopy(msg.name, m_name.str(), ARRAY_SIZE(msg.name)); wcscpy(msg.GameInfo.gameName, myGame->getName().str()); for (player=0; playergetPlayerName(player).str(), ARRAY_SIZE(reply.GameInfo.name[player])); + lanWideStrCopy(reply.GameInfo.name[player], m_currentGame->getPlayerName(player).str(), ARRAY_SIZE(reply.GameInfo.name[player])); reply.GameInfo.ip[player] = m_currentGame->getIP(player); reply.GameInfo.playerAccepted[player] = m_currentGame->getSlot(player)->isAccepted(); } - wcslcpy(reply.GameInfo.gameName, m_currentGame->getName().str(), ARRAY_SIZE(reply.GameInfo.gameName)); + lanWideStrCopy(reply.GameInfo.gameName, m_currentGame->getName().str(), ARRAY_SIZE(reply.GameInfo.gameName)); reply.GameInfo.inProgress = m_currentGame->isGameInProgress(); sendMessage(&reply); @@ -1071,7 +1089,7 @@ void LANAPI::fillInLANMessage( LANMessage *msg ) if (!msg) return; - wcslcpy(msg->name, m_name.str(), ARRAY_SIZE(msg->name)); + lanWideStrCopy(msg->name, m_name.str(), ARRAY_SIZE(msg->name)); strlcpy(msg->userName, m_userName.str(), ARRAY_SIZE(msg->userName)); strlcpy(msg->hostName, m_hostName.str(), ARRAY_SIZE(msg->hostName)); } @@ -1267,10 +1285,27 @@ Bool LANAPI::SetLocalIP( UnsignedInt localIP ) Bool retval = TRUE; m_localIP = localIP; + // TheSuperHackers @bugfix bobtista 12/06/2026 Derive the broadcast target from the selected + // interface so announces egress the interface that owns m_localIP rather than the default route. + // On a multi-homed host (e.g. ZeroTier/VPN overlay alongside Wi-Fi) the limited broadcast + // 255.255.255.255 only leaves the default-route interface, so peers on the overlay subnet never + // see our lobby/join broadcasts. Falls back to INADDR_BROADCAST when the netmask can't be resolved. + m_broadcastAddr = IPEnumeration::getSubnetBroadcastAddress(m_localIP); + m_transport->reset(); + // TheSuperHackers @bugfix bobtista 09/06/2026 Bind to INADDR_ANY on macOS/BSD so the + // LAN socket receives limited broadcasts; binding to the specific m_localIP silently + // drops them on those platforms. m_localIP remains our identity in the protocol. +#ifdef _WIN32 retval = m_transport->init(m_localIP, lobbyPort); +#else + retval = m_transport->init((UnsignedInt)0, lobbyPort); +#endif m_transport->allowBroadcasts(true); + DEBUG_LOG(("LANAPI::SetLocalIP - identity localIP=%d.%d.%d.%d broadcast=%d.%d.%d.%d (socket bound INADDR_ANY on non-Windows)", + PRINTF_IP_AS_4_INTS(m_localIP), PRINTF_IP_AS_4_INTS(m_broadcastAddr))); + return retval; } 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/LANAPIhandlers.cpp b/Core/GameEngine/Source/GameNetwork/LANAPIhandlers.cpp index 7f88b835f86..ad9f7a22b83 100644 --- a/Core/GameEngine/Source/GameNetwork/LANAPIhandlers.cpp +++ b/Core/GameEngine/Source/GameNetwork/LANAPIhandlers.cpp @@ -63,7 +63,7 @@ void LANAPI::handleRequestLocations( LANMessage *msg, UnsignedInt senderIP ) reply.messageType = LANMessage::MSG_GAME_ANNOUNCE; AsciiString gameOpts = GenerateGameOptionsString(); strlcpy(reply.GameInfo.options, gameOpts.str(), ARRAY_SIZE(reply.GameInfo.options)); - wcslcpy(reply.GameInfo.gameName, m_currentGame->getName().str(), ARRAY_SIZE(reply.GameInfo.gameName)); + lanWideStrCopy(reply.GameInfo.gameName, m_currentGame->getName().str(), ARRAY_SIZE(reply.GameInfo.gameName)); reply.GameInfo.inProgress = m_currentGame->isGameInProgress(); reply.GameInfo.isDirectConnect = m_currentGame->getIsDirectConnect(); @@ -86,7 +86,7 @@ void LANAPI::handleRequestLocations( LANMessage *msg, UnsignedInt senderIP ) { removePlayer(player); } - player->setName(UnicodeString(msg->name)); + player->setName(lanWideStrToUnicode(msg->name)); player->setHost(msg->hostName); player->setLogin(msg->userName); player->setLastHeard(timeGetTime()); @@ -98,6 +98,8 @@ void LANAPI::handleRequestLocations( LANMessage *msg, UnsignedInt senderIP ) void LANAPI::handleGameAnnounce( LANMessage *msg, UnsignedInt senderIP ) { + DEBUG_LOG(("handleGameAnnounce - from 0x%08x game='%ls' options='%s'", + senderIP, lanWideStrToUnicode(msg->GameInfo.gameName).str(), AsciiString(msg->GameInfo.options).str())); if (senderIP == m_localIP) { return; // Don't try to update own info @@ -111,11 +113,11 @@ void LANAPI::handleGameAnnounce( LANMessage *msg, UnsignedInt senderIP ) if (m_currentGame == nullptr) { - LANGameInfo *game = LookupGame(UnicodeString(msg->GameInfo.gameName)); + LANGameInfo *game = LookupGame(lanWideStrToUnicode(msg->GameInfo.gameName)); if (!game) { game = NEW LANGameInfo; - game->setName(UnicodeString(msg->GameInfo.gameName)); + game->setName(lanWideStrToUnicode(msg->GameInfo.gameName)); addGame(game); } Bool success = ParseGameOptionsString(game,AsciiString(msg->GameInfo.options)); @@ -134,11 +136,11 @@ void LANAPI::handleGameAnnounce( LANMessage *msg, UnsignedInt senderIP ) } else { - LANGameInfo *game = LookupGame(UnicodeString(msg->GameInfo.gameName)); + LANGameInfo *game = LookupGame(lanWideStrToUnicode(msg->GameInfo.gameName)); if (!game) { game = NEW LANGameInfo; - game->setName(UnicodeString(msg->GameInfo.gameName)); + game->setName(lanWideStrToUnicode(msg->GameInfo.gameName)); addGame(game); } Bool success = ParseGameOptionsString(game,AsciiString(msg->GameInfo.options)); @@ -171,7 +173,7 @@ void LANAPI::handleLobbyAnnounce( LANMessage *msg, UnsignedInt senderIP ) { removePlayer(player); } - player->setName(UnicodeString(msg->name)); + player->setName(lanWideStrToUnicode(msg->name)); player->setHost(msg->hostName); player->setLogin(msg->userName); player->setLastHeard(timeGetTime()); @@ -194,7 +196,7 @@ void LANAPI::handleRequestGameInfo( LANMessage *msg, UnsignedInt senderIP ) AsciiString gameOpts = GameInfoToAsciiString(m_currentGame); strlcpy(reply.GameInfo.options,gameOpts.str(), ARRAY_SIZE(reply.GameInfo.options)); - wcslcpy(reply.GameInfo.gameName, m_currentGame->getName().str(), ARRAY_SIZE(reply.GameInfo.gameName)); + lanWideStrCopy(reply.GameInfo.gameName, m_currentGame->getName().str(), ARRAY_SIZE(reply.GameInfo.gameName)); reply.GameInfo.inProgress = m_currentGame->isGameInProgress(); reply.GameInfo.isDirectConnect = m_currentGame->getIsDirectConnect(); @@ -253,12 +255,17 @@ void LANAPI::handleRequestJoin( LANMessage *msg, UnsignedInt senderIP ) UnsignedInt responseIP = senderIP; // need this cause the player may or may not be // in the player list at the sendMessage. + DEBUG_LOG(("handleRequestJoin - from 0x%08x gameIP=0x%08x m_localIP=0x%08x inLobby=%d game=%d slot0=0x%08x", + senderIP, msg->GameToJoin.gameIP, m_localIP, (int)m_inLobby, m_currentGame?1:0, + m_currentGame?m_currentGame->getIP(0):0)); if (msg->GameToJoin.gameIP != m_localIP) { + DEBUG_LOG(("handleRequestJoin - DROPPED: gameIP 0x%08x != m_localIP 0x%08x", msg->GameToJoin.gameIP, m_localIP)); return; // Not us. Ignore it. } LANMessage reply; fillInLANMessage( &reply ); + UnicodeString incomingName = lanWideStrToUnicode(msg->name); if (!m_inLobby && m_currentGame && m_currentGame->getIP(0) == m_localIP) { if (m_currentGame->isGameInProgress()) @@ -341,7 +348,7 @@ void LANAPI::handleRequestJoin( LANMessage *msg, UnsignedInt senderIP ) // should not be in a player name. It should also not consist of only space characters. if (canJoin) { - if (ContainsInvalidChars(msg->name) || !ContainsAnyReadableChars(msg->name)) + if (ContainsInvalidChars(incomingName.str()) || !ContainsAnyReadableChars(incomingName.str())) { // Just deny with a duplicate name reason, for backwards compatibility with retail reply.messageType = LANMessage::MSG_JOIN_DENY; @@ -358,7 +365,7 @@ void LANAPI::handleRequestJoin( LANMessage *msg, UnsignedInt senderIP ) for (player = 0; canJoin && playergetLANSlot(player); - if (slot->isHuman() && slot->getName().compare(msg->name) == 0) + if (slot->isHuman() && slot->getName().compare(incomingName.str()) == 0) { // just deny duplicates reply.messageType = LANMessage::MSG_JOIN_DENY; @@ -379,21 +386,21 @@ void LANAPI::handleRequestJoin( LANMessage *msg, UnsignedInt senderIP ) { // OK, add him in. reply.messageType = LANMessage::MSG_JOIN_ACCEPT; - wcslcpy(reply.GameJoined.gameName, m_currentGame->getName().str(), ARRAY_SIZE(reply.GameJoined.gameName)); + lanWideStrCopy(reply.GameJoined.gameName, m_currentGame->getName().str(), ARRAY_SIZE(reply.GameJoined.gameName)); reply.GameJoined.slotPosition = player; reply.GameJoined.gameIP = m_localIP; reply.GameJoined.playerIP = senderIP; LANGameSlot newSlot; - newSlot.setState(SLOT_PLAYER, UnicodeString(msg->name)); + newSlot.setState(SLOT_PLAYER, incomingName); newSlot.setIP(senderIP); newSlot.setPort(NETWORK_BASE_PORT_NUMBER); newSlot.setLastHeard(timeGetTime()); newSlot.setSerial(msg->GameToJoin.serial); m_currentGame->setSlot(player,newSlot); - DEBUG_LOG(("LANAPI::handleRequestJoin - added player %ls at ip 0x%08x to the game", msg->name, senderIP)); + DEBUG_LOG(("LANAPI::handleRequestJoin - added player %ls at ip 0x%08x to the game", incomingName.str(), senderIP)); - OnPlayerJoin(player, UnicodeString(msg->name)); + OnPlayerJoin(player, incomingName); responseIP = 0; break; @@ -403,7 +410,7 @@ void LANAPI::handleRequestJoin( LANMessage *msg, UnsignedInt senderIP ) if (canJoin && player == MAX_SLOTS) { reply.messageType = LANMessage::MSG_JOIN_DENY; - wcslcpy(reply.GameNotJoined.gameName, m_currentGame->getName().str(), ARRAY_SIZE(reply.GameNotJoined.gameName)); + lanWideStrCopy(reply.GameNotJoined.gameName, m_currentGame->getName().str(), ARRAY_SIZE(reply.GameNotJoined.gameName)); reply.GameNotJoined.reason = LANAPIInterface::RET_GAME_FULL; reply.GameNotJoined.gameIP = m_localIP; reply.GameNotJoined.playerIP = senderIP; @@ -418,17 +425,20 @@ void LANAPI::handleRequestJoin( LANMessage *msg, UnsignedInt senderIP ) reply.GameNotJoined.gameIP = m_localIP; reply.GameNotJoined.playerIP = senderIP; } + DEBUG_LOG(("handleRequestJoin - reply type=%d to 0x%08x (0=bcast)", (int)reply.messageType, responseIP)); sendMessage(&reply, responseIP); RequestGameOptions(GenerateGameOptionsString(), true); } void LANAPI::handleJoinAccept( LANMessage *msg, UnsignedInt senderIP ) { + DEBUG_LOG(("handleJoinAccept - from 0x%08x playerIP=0x%08x m_localIP=0x%08x pending=%d", + senderIP, msg->GameJoined.playerIP, m_localIP, (int)m_pendingAction)); if (msg->GameJoined.playerIP == m_localIP) // Is it for us? { if (m_pendingAction == ACT_JOIN) // Are we trying to join? { - m_currentGame = LookupGame(UnicodeString(msg->GameJoined.gameName)); + m_currentGame = LookupGame(lanWideStrToUnicode(msg->GameJoined.gameName)); if (!m_currentGame) { @@ -478,7 +488,7 @@ void LANAPI::handleJoinDeny( LANMessage *msg, UnsignedInt senderIP ) { if (m_pendingAction == ACT_JOIN) // Are we trying to join? { - OnGameJoin(msg->GameNotJoined.reason, LookupGame(UnicodeString(msg->GameNotJoined.gameName))); + OnGameJoin(msg->GameNotJoined.reason, LookupGame(lanWideStrToUnicode(msg->GameNotJoined.gameName))); m_pendingAction = ACT_NONE; m_expiration = 0; } @@ -528,7 +538,7 @@ void LANAPI::handleRequestGameLeave( LANMessage *msg, UnsignedInt senderIP ) slot.setState(SLOT_OPEN); m_currentGame->setSlot( player, slot ); } - OnPlayerLeave(UnicodeString(msg->name)); + OnPlayerLeave(lanWideStrToUnicode(msg->name)); m_currentGame->getLANSlot(player)->setState(SLOT_OPEN); m_currentGame->resetAccepted(); RequestGameOptions(GenerateGameOptionsString(), false, senderIP); @@ -545,7 +555,7 @@ void LANAPI::handleRequestGameLeave( LANMessage *msg, UnsignedInt senderIP ) LANGameInfo *game = m_games; while (game) { - if (game->getName().compare(msg->GameToLeave.gameName) == 0) + if (game->getName().compare(lanWideStrToUnicode(msg->GameToLeave.gameName).str()) == 0) { removeGame(game); delete game; @@ -599,6 +609,8 @@ void LANAPI::handleHasMap( LANMessage *msg, UnsignedInt senderIP ) // mapNameCRC.computeCRC(m_currentGame->getMap().str(), m_currentGame->getMap().getLength()); AsciiString portableMapName = TheGameState->realMapPathToPortableMapPath(m_currentGame->getMap()); mapNameCRC.computeCRC(portableMapName.str(), portableMapName.getLength()); + DEBUG_LOG(("handleHasMap - from 0x%08x localNameCRC=0x%08x msgNameCRC=0x%08x hasMap=%d name='%s'", + senderIP, mapNameCRC.get(), msg->MapStatus.mapCRC, (int)msg->MapStatus.hasMap, portableMapName.str())); if (mapNameCRC.get() != msg->MapStatus.mapCRC) { return; @@ -623,15 +635,15 @@ void LANAPI::handleChat( LANMessage *msg, UnsignedInt senderIP ) LANPlayer *player; if((player=LookupPlayer(senderIP)) != nullptr) { - OnChat(UnicodeString(player->getName()), player->getIP(), UnicodeString(msg->Chat.message), msg->Chat.chatType); + OnChat(UnicodeString(player->getName()), player->getIP(), lanWideStrToUnicode(msg->Chat.message), msg->Chat.chatType); player->setLastHeard(timeGetTime()); } } else { - if (LookupGame(UnicodeString(msg->Chat.gameName)) != m_currentGame) + if (LookupGame(lanWideStrToUnicode(msg->Chat.gameName)) != m_currentGame) { - DEBUG_LOG(("Game '%ls' is not my game", msg->Chat.gameName)); + DEBUG_LOG(("Game '%ls' is not my game", lanWideStrToUnicode(msg->Chat.gameName).str())); if (m_currentGame) { DEBUG_LOG(("Current game is '%ls'", m_currentGame->getName().str())); @@ -644,7 +656,7 @@ void LANAPI::handleChat( LANMessage *msg, UnsignedInt senderIP ) { if (m_currentGame && m_currentGame->getIP(player) == senderIP) { - OnChat(UnicodeString(msg->name), m_currentGame->getIP(player), UnicodeString(msg->Chat.message), msg->Chat.chatType); + OnChat(lanWideStrToUnicode(msg->name), m_currentGame->getIP(player), lanWideStrToUnicode(msg->Chat.message), msg->Chat.chatType); break; } } @@ -694,7 +706,7 @@ void LANAPI::handleInActive(LANMessage *msg, UnsignedInt senderIP) { } UnicodeString playerName; - playerName = msg->name; + playerName = lanWideStrToUnicode(msg->name); Int slotNum = m_currentGame->getSlotNum(playerName); if (slotNum < 0) diff --git a/Core/GameEngine/Source/GameNetwork/NetPacketStructs.cpp b/Core/GameEngine/Source/GameNetwork/NetPacketStructs.cpp index 500fcdbee88..ca385be59a9 100644 --- a/Core/GameEngine/Source/GameNetwork/NetPacketStructs.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetPacketStructs.cpp @@ -655,7 +655,7 @@ size_t NetPacketChatCommandData::getSize(const NetCommandMsg &msg) size_t size = 0; size += sizeof(UnsignedByte); - size += textLength * sizeof(WideChar); + size += textLength * sizeof(UnsignedShort); size += sizeof(Int); return size; } @@ -714,7 +714,7 @@ size_t NetPacketDisconnectChatCommandData::getSize(const NetCommandMsg &msg) size_t size = 0; size += sizeof(UnsignedByte); - size += textLength * sizeof(WideChar); + size += textLength * sizeof(UnsignedShort); return size; } diff --git a/Core/GameEngine/Source/GameNetwork/Transport.cpp b/Core/GameEngine/Source/GameNetwork/Transport.cpp index cbdbcb88222..1c105ea7c0f 100644 --- a/Core/GameEngine/Source/GameNetwork/Transport.cpp +++ b/Core/GameEngine/Source/GameNetwork/Transport.cpp @@ -88,6 +88,11 @@ Bool Transport::init( UnsignedInt ip, UnsignedShort port ) // ----- Initialize Winsock ----- if (!m_winsockInit) { + // TheSuperHackers @bugfix bobtista 09/06/2026 Only validate the Winsock + // version on Windows. On other platforms WSAStartup is a no-op that never + // fills wsadata, so reading wsadata.wVersion returns uninitialized memory + // and this check spuriously fails, breaking all network socket creation. +#ifdef _WIN32 WORD verReq = MAKEWORD(2, 2); WSADATA wsadata; @@ -100,6 +105,7 @@ Bool Transport::init( UnsignedInt ip, UnsignedShort port ) WSACleanup(); return false; } +#endif m_winsockInit = true; } @@ -349,14 +355,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 +372,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; @@ -512,5 +517,3 @@ Real Transport::getUnknownPacketsPerSecond() return val / (MAX_TRANSPORT_STATISTICS_SECONDS-1); } - - 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..3109b9b5afa 100644 --- a/Core/GameEngine/Source/GameNetwork/udp.cpp +++ b/Core/GameEngine/Source/GameNetwork/udp.cpp @@ -35,6 +35,10 @@ //#include "GameNetwork/NetworkInterface.h" #include "GameNetwork/udp.h" +#ifdef _WIN32 +typedef int socklen_t; +#endif + //------------------------------------------------------------------------- @@ -44,6 +48,13 @@ AsciiString GetWSAErrorString( Int error ) { +#ifndef _WIN32 + // TheSuperHackers @build bobtista 09/06/2026 The WSA* error names are Windows-only; on + // other platforms socket errors are POSIX errno values, so report those instead. + AsciiString ret; + ret.format("%s (%d)", strerror(error), error); + return ret; +#else switch (error) { CASE(WSABASEERR) @@ -106,6 +117,7 @@ AsciiString GetWSAErrorString( Int error ) } } return AsciiString::TheEmptyString; // will not be hit, ever. +#endif } #undef CASE @@ -178,7 +190,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 +278,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 +385,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 +522,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 +532,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); } diff --git a/Core/GameEngineDevice/CMakeLists.txt b/Core/GameEngineDevice/CMakeLists.txt index a667a31ef19..0f717983af2 100644 --- a/Core/GameEngineDevice/CMakeLists.txt +++ b/Core/GameEngineDevice/CMakeLists.txt @@ -264,9 +264,14 @@ target_link_libraries(corei_gameenginedevice_public INTERFACE if(WIN32) target_link_libraries(corei_gameenginedevice_public INTERFACE binkstub - d3d8lib milesstub ) + # TheSuperHackers @build bobtista 12/06/2026 d3d8lib only exists on 32-bit (dx8.cmake is x86-only); + # the bgfx x64 build does not use D3D8. Guard by target existence so the x64 link does not pull a + # missing lib, while every 32-bit build keeps linking it exactly as before. + if(TARGET d3d8lib) + target_link_libraries(corei_gameenginedevice_public INTERFACE d3d8lib) + endif() endif() if(SAGE_USE_OPENAL) diff --git a/Core/GameEngineDevice/Include/StdDevice/Common/StdBIGFile.h b/Core/GameEngineDevice/Include/StdDevice/Common/StdBIGFile.h index 36538853352..128345e907a 100644 --- a/Core/GameEngineDevice/Include/StdDevice/Common/StdBIGFile.h +++ b/Core/GameEngineDevice/Include/StdDevice/Common/StdBIGFile.h @@ -31,6 +31,7 @@ #include "Common/ArchiveFile.h" #include "Common/AsciiString.h" #include "Common/List.h" +#include "mutex.h" class StdBIGFile : public ArchiveFile { @@ -48,6 +49,7 @@ class StdBIGFile : public ArchiveFile protected: - AsciiString m_name; ///< BIG file name - AsciiString m_path; ///< BIG file path -}; + AsciiString m_name; ///< BIG file name + AsciiString m_path; ///< BIG file path + CriticalSectionClass m_fileLock; + }; diff --git a/Core/GameEngineDevice/Include/VideoDevice/Bink/BinkVideoPlayer.h b/Core/GameEngineDevice/Include/VideoDevice/Bink/BinkVideoPlayer.h index 38047636245..5100f4946ef 100644 --- a/Core/GameEngineDevice/Include/VideoDevice/Bink/BinkVideoPlayer.h +++ b/Core/GameEngineDevice/Include/VideoDevice/Bink/BinkVideoPlayer.h @@ -46,7 +46,14 @@ //---------------------------------------------------------------------------- #include "GameClient/VideoPlayer.h" +// TheSuperHackers @build bobtista 29/04/2026 Bink SDK is Win-only and we +// don't ship it on macOS/Linux; FFmpeg is the active backend there. Provide +// stub HBINK so the class declarations parse. +#ifdef _WIN32 #include "bink.h" +#else +typedef struct BINK *HBINK; +#endif //---------------------------------------------------------------------------- // Forward References diff --git a/Core/GameEngineDevice/Include/VideoDevice/FFmpeg/FFmpegFile.h b/Core/GameEngineDevice/Include/VideoDevice/FFmpeg/FFmpegFile.h index 628374c4ef9..ef184a30f56 100644 --- a/Core/GameEngineDevice/Include/VideoDevice/FFmpeg/FFmpegFile.h +++ b/Core/GameEngineDevice/Include/VideoDevice/FFmpeg/FFmpegFile.h @@ -72,7 +72,7 @@ class FFmpegFile Int getNumFrames() const; Int getCurrentFrame() const; Int getPixelFormat() const; - UnsignedInt getFrameTime() const; + double getFrameTime() const; private: struct FFmpegStream @@ -85,6 +85,7 @@ class FFmpegFile }; static Int readPacket(void *opaque, UnsignedByte *buf, Int buf_size); + static Int64 seekPacket(void *opaque, Int64 offset, Int whence); const FFmpegStream *findMatch(int type) const; FFmpegFrameCallback m_frameCallback = nullptr; ///< Callback for frame processing diff --git a/Core/GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h b/Core/GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h index 66effff6a97..3199df33622 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h +++ b/Core/GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h @@ -37,7 +37,6 @@ // FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// class TextureClass; -class SurfaceClass; class TerrainLogic; // PROTOTYPES ///////////////////////////////////////////////////////////////////////////////////// @@ -109,7 +108,7 @@ class W3DRadar : public Radar WW3DFormat m_shroudTextureFormat; ///< format to use for shroud texture Image *m_shroudImage; ///< shroud image abstraction for drawing TextureClass *m_shroudTexture; ///< shroud texture - SurfaceClass *m_shroudSurface; ///< surface to shroud texture + Bool m_shroudMipActive; ///< true while bulk shroud mip writes are active void *m_shroudSurfaceBits; ///< shroud surface bits int m_shroudSurfacePitch; ///< shroud surface pitch WW3DFormat m_shroudSurfaceFormat; ///< shroud surface format diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/BaseHeightMap.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/BaseHeightMap.h index 425c152c948..f20e8d0082e 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/BaseHeightMap.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/BaseHeightMap.h @@ -27,9 +27,9 @@ #include "WWLib/always.h" #include "WW3D2/rendobj.h" #include "WW3D2/w3d_file.h" -#include "WW3D2/dx8vertexbuffer.h" -#include "WW3D2/dx8indexbuffer.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/dx8fvf.h" +#include "WW3D2/renderbufferclasses.h" +#include "WW3D2/RenderDeviceCleanupHook.h" #include "WW3D2/shader.h" #include "WW3D2/vertmaterial.h" #include "Lib/BaseType.h" @@ -87,7 +87,7 @@ Custom W3D render object that's used to process the terrain. It handles virtually everything to do with the terrain, including: drawing, lighting, scorchmarks and intersection tests. */ -class BaseHeightMapRenderObjClass : public RenderObjClass, public DX8_CleanupHook, public Snapshot +class BaseHeightMapRenderObjClass : public RenderObjClass, public RenderDeviceCleanupHook, public Snapshot { public: @@ -246,8 +246,8 @@ class BaseHeightMapRenderObjClass : public RenderObjClass, public DX8_CleanupHoo MAX_SCORCH_MARKS=500, SCORCH_MARKS_IN_TEXTURE=9, SCORCH_PER_ROW = 3}; - DX8VertexBufferClass *m_vertexScorch; /// m_lastCapturePixels; diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DShaderManager.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DShaderManager.h index bb86bca8057..4ef71207c38 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DShaderManager.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DShaderManager.h @@ -79,6 +79,8 @@ class W3DShaderManager static void init(); /// *m_sizeBuffer; ///< array of particle sizes TextureClass *m_backgroundTexture; - DX8IndexBufferClass *m_indexBuffer; + RenderIndexBufferClass *m_indexBuffer; Int m_backBufferWidth; Int m_backBufferHeight; }; diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DSnow.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DSnow.h index c994c5a2758..a14cf803e35 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DSnow.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DSnow.h @@ -22,10 +22,11 @@ #include "GameClient/Snow.h" -class DX8IndexBufferClass; class RenderInfoClass; +// TheSuperHackers @build bobtista 01/06/2026 RenderIndexBufferClass is a type +// alias on the dx8 backend; include the shared header. +#include "WW3D2/renderbufferclasses.h" class TextureClass; -struct IDirect3DVertexBuffer8; class W3DSnowManager : public SnowManager { @@ -41,20 +42,13 @@ class W3DSnowManager : public SnowManager void render(RenderInfoClass &rinfo); void renderAsQuads(RenderInfoClass &rinfo, Int cubeOriginX, Int cubeOriginY, Int cubeDimX, Int cubeDimY); - void renderSubBox(RenderInfoClass &rinfo, Int originX, Int originY, Int cubeDimX, Int cubeDimY ); void ReleaseResources(); Bool ReAcquireResources(); - private: - DX8IndexBufferClass *m_indexBuffer; +private: + RenderIndexBufferClass *m_indexBuffer; TextureClass *m_snowTexture; - IDirect3DVertexBuffer8* m_VertexBufferD3D; - Int m_dwBase; /// + #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 @@ -50,6 +50,9 @@ class PolygonTrigger; class WaterTracksRenderSystem; +// TheSuperHackers @build bobtista 01/06/2026 RenderIndexBufferClass is a type +// alias on the dx8 backend; include the shared header. +#include "WW3D2/renderbufferclasses.h" class Xfer; /// Custom render object that draws mirrors, water, and skies. /** @@ -125,7 +128,8 @@ class WaterRenderObjClass : public Snapshot, void replaceSkyboxTexture(const AsciiString& oldTexName, const AsciiString& newTextName); protected: - DX8IndexBufferClass *m_indexBuffer; /// &trapezoids); void loadSetting ( Setting *skySetting, TimeOfDay timeOfDay ); /// @@ -148,7 +149,7 @@ ALuint OpenALAudioFileCache::getBufferForFile(const OpenFileInfo &fileInfo) // TheSuperHackers @diag bobtista 04/06/2026 GGC_AUDIO_DIAG logs every cache miss // (the expensive FFmpeg decode) so we can see which sound re-decodes per frame. - const bool ggcAudioDiag = (getenv("GGC_AUDIO_DIAG") != nullptr); + const bool ggcAudioDiag = GgcFlags::Enabled(GgcFlag_AudioDiag); auto it = m_openFiles.find(strToFind); diff --git a/Core/GameEngineDevice/Source/OpenALAudioDevice/OpenALAudioManager.cpp b/Core/GameEngineDevice/Source/OpenALAudioDevice/OpenALAudioManager.cpp index 2b8e00aad4a..db999af9d3e 100644 --- a/Core/GameEngineDevice/Source/OpenALAudioDevice/OpenALAudioManager.cpp +++ b/Core/GameEngineDevice/Source/OpenALAudioDevice/OpenALAudioManager.cpp @@ -42,6 +42,7 @@ #include "OpenALAudioDevice/OpenALAudioManager.h" #include "OpenALAudioDevice/OpenALAudioStream.h" #include "OpenALAudioCache.h" +#include "GgcRuntimeFlags.h" #include "Common/AudioAffect.h" #include "Common/AudioHandleSpecialValues.h" @@ -538,7 +539,7 @@ void OpenALAudioManager::init() { cacheBytes = minCacheBytes; } - if (const char *cacheEnv = getenv("GGC_AUDIO_CACHE_MB")) + if (const char *cacheEnv = GgcFlags::StringValue(GgcFlag_AudioCacheMb)) { const int cacheMb = atoi(cacheEnv); if (cacheMb > 0) diff --git a/Core/GameEngineDevice/Source/StdDevice/Common/StdBIGFile.cpp b/Core/GameEngineDevice/Source/StdDevice/Common/StdBIGFile.cpp index c873b733524..1273a631855 100644 --- a/Core/GameEngineDevice/Source/StdDevice/Common/StdBIGFile.cpp +++ b/Core/GameEngineDevice/Source/StdDevice/Common/StdBIGFile.cpp @@ -74,10 +74,13 @@ File* StdBIGFile::openFile( const Char *filename, Int access ) ramFile = newInstance( RAMFile ); ramFile->deleteOnClose(); - if (ramFile->openFromArchive(m_file, fileInfo->m_filename, fileInfo->m_offset, fileInfo->m_size) == FALSE) { - ramFile->close(); - ramFile = nullptr; - return nullptr; + { + CriticalSectionClass::LockClass lock(m_fileLock); + if (ramFile->openFromArchive(m_file, fileInfo->m_filename, fileInfo->m_offset, fileInfo->m_size) == FALSE) { + ramFile->close(); + ramFile = nullptr; + return nullptr; + } } if ((access & File::WRITE) == 0) { @@ -165,4 +168,3 @@ Bool StdBIGFile::getFileInfo(const AsciiString& filename, FileInfo *fileInfo) co return TRUE; } - diff --git a/Core/GameEngineDevice/Source/StdDevice/Common/StdBIGFileSystem.cpp b/Core/GameEngineDevice/Source/StdDevice/Common/StdBIGFileSystem.cpp index ffd0150f4c5..56825b0be7e 100644 --- a/Core/GameEngineDevice/Source/StdDevice/Common/StdBIGFileSystem.cpp +++ b/Core/GameEngineDevice/Source/StdDevice/Common/StdBIGFileSystem.cpp @@ -42,8 +42,72 @@ #include "StdDevice/Common/StdBIGFileSystem.h" #include "Utility/endian_compat.h" +#include +#include +#include +#include +#include +#include + static const char *BIGFileIdentifier = "BIGF"; +// Zero Hour can run with both Generals and ZH archives in one directory. +// Load the higher-priority archives first because ArchiveFileSystem keeps the +// first copy of a path when overwrite is false. +static AsciiString GetBigFilename(AsciiString filename) +{ + const char *path = filename.str(); + const char *slash = strrchr(path, '\\'); + const char *forwardSlash = strrchr(path, '/'); + if (forwardSlash != nullptr && (slash == nullptr || forwardSlash > slash)) + { + slash = forwardSlash; + } + + AsciiString result = slash != nullptr ? slash + 1 : path; + result.toLower(); + return result; +} + +static Int GetBIGLoadPriority(AsciiString filename) +{ + AsciiString baseName = GetBigFilename(filename); + + if (baseName.compareNoCase("patchzh.big") == 0) + { + return 10; + } + if (baseName.endsWithNoCase("zh.big")) + { + return 20; + } + if (baseName.compareNoCase("patch.big") == 0 + || baseName.compareNoCase("patchdata.big") == 0 + || baseName.compareNoCase("patchini.big") == 0) + { + return 30; + } + if (baseName.compareNoCase("audio.big") == 0 + || baseName.compareNoCase("audioenglish.big") == 0 + || baseName.compareNoCase("english.big") == 0 + || baseName.compareNoCase("gensec.big") == 0 + || baseName.compareNoCase("ini.big") == 0 + || baseName.compareNoCase("maps.big") == 0 + || baseName.compareNoCase("music.big") == 0 + || baseName.compareNoCase("shaders.big") == 0 + || baseName.compareNoCase("speech.big") == 0 + || baseName.compareNoCase("speechenglish.big") == 0 + || baseName.compareNoCase("terrain.big") == 0 + || baseName.compareNoCase("textures.big") == 0 + || baseName.compareNoCase("w3d.big") == 0 + || baseName.compareNoCase("window.big") == 0) + { + return 40; + } + + return 0; +} + StdBIGFileSystem::StdBIGFileSystem() : ArchiveFileSystem() { } @@ -59,6 +123,20 @@ void StdBIGFileSystem::init() { loadBigFilesFromDirectory("", "*.big"); #if RTS_ZEROHOUR +#if defined(_UNIX) + // TheSuperHackers @feature bobtista 09/06/2026 On non-Windows the engine binary often + // lives apart from the game data, so honor $CNC_ZH_INSTALLPATH (resolved through + // GetStringFromRegistry) as an additional Zero Hour archive root alongside the working + // directory, mirroring the base Generals load below. Lets a build elsewhere point at an + // existing install without copying the .big files next to the executable. + AsciiString zhInstallPath; + GetStringFromRegistry("", "InstallPath", zhInstallPath ); + if (!zhInstallPath.isEmpty()) + { + loadBigFilesFromDirectory(zhInstallPath, "*.big"); + } +#endif + // load original Generals assets AsciiString installPath; GetStringFromGeneralsRegistry("", "InstallPath", installPath ); @@ -79,15 +157,29 @@ void StdBIGFileSystem::postProcessLoad() { } ArchiveFile * StdBIGFileSystem::openArchiveFile(const Char *filename) { - File *fp = TheLocalFileSystem->openFile(filename, File::READ | File::BINARY); + // TheSuperHackers @bugfix bobtista 11/07/2026 Retry transient archive-open + // failures. Archives mount exactly once at startup and a failed mount was + // silently skipped in release builds (the crash above is debug-only), + // leaving every asset in that archive unavailable for the whole run — + // observed on Windows as save-restored objects loading with no visible + // models when the game launches during heavy disk activity (antivirus + // scan or a previous process still tearing down holds the file briefly). + File *fp = nullptr; + for (Int attempt = 0; attempt < 10; ++attempt) { + fp = TheLocalFileSystem->openFile(filename, File::READ | File::BINARY); + if (fp != nullptr) { + break; + } + std::fprintf(stderr, "[ggc] archive open failed (attempt %d/10): %s\n", attempt + 1, filename); + std::fflush(stderr); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } AsciiString archiveFileName; archiveFileName = filename; archiveFileName.toLower(); Int archiveFileSize = 0; Int numLittleFiles = 0; - ArchiveFile *archiveFile = NEW StdBIGFile(filename, AsciiString::TheEmptyString); - DEBUG_LOG(("StdBIGFileSystem::openArchiveFile - opening BIG file %s", filename)); if (fp == nullptr) { @@ -97,9 +189,19 @@ ArchiveFile * StdBIGFileSystem::openArchiveFile(const Char *filename) { AsciiString asciibuf; char buffer[_MAX_PATH]; - fp->read(buffer, 4); // read the "BIG" at the beginning of the file. + // TheSuperHackers @bugfix bobtista 11/07/2026 Check the identifier read; + // a short read previously compared uninitialized bytes. + if (fp->read(buffer, 4) != 4) { + std::fprintf(stderr, "[ggc] archive header read failed: %s\n", filename); + std::fflush(stderr); + fp->close(); + fp = nullptr; + return nullptr; + } buffer[4] = 0; if (strcmp(buffer, BIGFileIdentifier) != 0) { + std::fprintf(stderr, "[ggc] archive identifier mismatch: %s\n", filename); + std::fflush(stderr); DEBUG_CRASH(("Error reading BIG file identifier in file %s", filename)); fp->close(); fp = nullptr; @@ -129,6 +231,9 @@ ArchiveFile * StdBIGFileSystem::openArchiveFile(const Char *filename) { fp->seek(0x10, File::START); // read in each directory listing. ArchivedFileInfo *fileInfo = NEW ArchivedFileInfo; + // TheSuperHackers @fix bobtista 12/06/2026 Allocate the archive file only after the early-return + // validity checks above (mirrors the Win32BIGFileSystem fix) so a missing/invalid BIG doesn't leak it. + ArchiveFile *archiveFile = NEW StdBIGFile(filename, AsciiString::TheEmptyString); for (Int i = 0; i < numLittleFiles; ++i) { Int filesize = 0; @@ -209,12 +314,31 @@ void StdBIGFileSystem::closeAllFiles() { Bool StdBIGFileSystem::loadBigFilesFromDirectory(AsciiString dir, AsciiString fileMask, Bool overwrite) { + // TheSuperHackers @bugfix bobtista 30/07/2026 Normalize external archive + // roots before getFileListInDirectory concatenates the root and filename. + // Environment paths without a trailing separator otherwise enumerate the + // directory but try to open "/path/to/dataINIZH.big". + if (!dir.isEmpty() && !dir.endsWith("/") && !dir.endsWith("\\")) + { + dir.concat('/'); + } + FilenameList filenameList; TheLocalFileSystem->getFileListInDirectory(dir, "", fileMask, filenameList, TRUE); + std::vector sortedFiles(filenameList.begin(), filenameList.end()); + std::sort(sortedFiles.begin(), sortedFiles.end(), [](const AsciiString& a, const AsciiString& b) { + Int priorityA = GetBIGLoadPriority(a); + Int priorityB = GetBIGLoadPriority(b); + if (priorityA != priorityB) + { + return priorityA < priorityB; + } + return a.compareNoCase(b) < 0; + }); Bool actuallyAdded = FALSE; - FilenameListIter it = filenameList.begin(); - while (it != filenameList.end()) { + std::vector::iterator it = sortedFiles.begin(); + while (it != sortedFiles.end()) { #if RTS_ZEROHOUR // TheSuperHackers @bugfix bobtista 18/11/2025 Skip duplicate INIZH.big in Data\INI to prevent CRC mismatches. // English, Chinese, and Korean SKUs shipped with two INIZH.big files (one in Run directory, one in Run\Data\INI). @@ -234,6 +358,14 @@ Bool StdBIGFileSystem::loadBigFilesFromDirectory(AsciiString dir, AsciiString fi DEBUG_LOG(("StdBIGFileSystem::loadBigFilesFromDirectory - %s inserted into the archive file map.", (*it).str())); actuallyAdded = TRUE; } + else { + // TheSuperHackers @bugfix bobtista 11/07/2026 A failed mount used to be + // skipped silently in release builds; every asset in the archive then + // stayed unavailable for the whole run. Say so where a release build + // can see it. + std::fprintf(stderr, "[ggc] ARCHIVE MOUNT FAILED, contents unavailable this run: %s\n", (*it).str()); + std::fflush(stderr); + } it++; } diff --git a/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp b/Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp index 89da7828983..50ae3cabbc8 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 @@ -71,7 +128,10 @@ static std::filesystem::path fixFilenameFromWindowsPath(const Char *filename, In std::filesystem::path pathFixed; std::filesystem::path pathCurrent; - for (auto& p : path) + // TheSuperHackers @build bobtista 29/04/2026 std::filesystem::path's + // iterator dereferences to a temporary on Apple Clang's libc++; use + // auto-by-value (or const ref) instead of non-const ref. + for (auto p : path) { std::filesystem::path pathFixedPart; @@ -109,9 +169,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.string().c_str())); - DEBUG_LOG(("StdLocalFileSystem::fixFilenameFromWindowsPath - Got so far %s", pathCurrent.string().c_str())); - return std::filesystem::path(); } @@ -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,22 +291,30 @@ 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(); 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; } 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; + newFilename = originalDirectory; + newFilename.concat(currentDirectory); + newFilename.concat(filenameStr.c_str()); if (filenameList.find(newFilename) == filenameList.end()) { filenameList.insert(newFilename); } @@ -260,7 +325,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,10 +336,13 @@ 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()); + AsciiString tempsearchstr; + tempsearchstr.concat(currentDirectory); + tempsearchstr.concat(filenameStr.c_str()); + tempsearchstr.concat('\\'); // recursively add files in subdirectories if required. getFileListInDirectory(tempsearchstr, originalDirectory, searchName, filenameList, searchSubdirectories); @@ -330,7 +398,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 +414,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()); } diff --git a/Core/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegFile.cpp b/Core/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegFile.cpp index a06c3dcb291..7263c4a25a7 100644 --- a/Core/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegFile.cpp +++ b/Core/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegFile.cpp @@ -27,7 +27,7 @@ ///////////////////////////////////////////////// #include "VideoDevice/FFmpeg/FFmpegFile.h" -#include "Common/File.h" +#include "Common/file.h" extern "C" { #include @@ -51,9 +51,10 @@ Bool FFmpegFile::open(File *file) { DEBUG_ASSERTCRASH(m_file == nullptr, ("already open")); DEBUG_ASSERTCRASH(file != nullptr, ("null file pointer")); -#if LOGGING_LEVEL != LOGLEVEL_NONE - av_log_set_level(AV_LOG_INFO); -#endif + // FFmpeg/swscale can emit per-frame warnings for valid software color + // conversions during startup movies. Keep actual failures visible without + // flooding stderr while the game is launched from a terminal. + av_log_set_level(AV_LOG_ERROR); // This is required for FFmpeg older than 4.0 -> deprecated afterwards though #if LIBAVFORMAT_VERSION_MAJOR < 58 @@ -78,7 +79,9 @@ Bool FFmpegFile::open(File *file) return false; } - m_avioCtx = avio_alloc_context(buffer, avio_ctx_buffer_size, 0, file, &readPacket, nullptr, nullptr); + // TheSuperHackers @bugfix bobtista 13/07/2026 Provide a seek callback; without one the demuxer + // cannot reposition, so av_seek_frame (frameGoto) always failed on this custom IO context. + m_avioCtx = avio_alloc_context(buffer, avio_ctx_buffer_size, 0, file, &readPacket, nullptr, &seekPacket); if (m_avioCtx == nullptr) { DEBUG_LOG(("Failed to alloc AVIOContext")); close(); @@ -169,6 +172,43 @@ int FFmpegFile::readPacket(void *opaque, uint8_t *buf, int buf_size) return read; } +/** + * Seek within the file for the FFmpeg demuxer + */ +int64_t FFmpegFile::seekPacket(void *opaque, int64_t offset, int whence) +{ + File *file = static_cast(opaque); + + if ((whence & AVSEEK_SIZE) != 0) + { + return file->size(); + } + + File::seekMode mode; + switch (whence & ~AVSEEK_FORCE) + { + case SEEK_SET: + mode = File::START; + break; + case SEEK_CUR: + mode = File::CURRENT; + break; + case SEEK_END: + mode = File::END; + break; + default: + return -1; + } + + const Int pos = file->seek(static_cast(offset), mode); + if (pos < 0) + { + return -1; + } + + return pos; +} + /** * close all the open FFmpeg handles for an open file. */ @@ -255,15 +295,39 @@ Bool FFmpegFile::decodePacket() void FFmpegFile::seekFrame(int frame_idx) { - // Note: not tested, since not used ingame - for (const auto &stream : m_streams) { - Int64 timestamp = av_q2d(m_fmtCtx->streams[stream.stream_idx]->time_base) * frame_idx - * av_q2d(m_fmtCtx->streams[stream.stream_idx]->avg_frame_rate); - int result = av_seek_frame(m_fmtCtx, stream.stream_idx, timestamp, AVSEEK_FLAG_ANY); - if (result < 0) { - char error_buffer[1024]; - av_strerror(result, error_buffer, sizeof(error_buffer)); - DEBUG_LOG(("Failed 'av_seek_frame': %s", error_buffer)); + // TheSuperHackers @bugfix bobtista 13/07/2026 Seek on the video stream with a correct timestamp; + // the previous math multiplied by the frame rate and time base instead of dividing, which + // truncated every seek target to the start of the file. Flush all decoders afterwards so + // packets from the new position decode cleanly. + const FFmpegStream *video = findMatch(AVMEDIA_TYPE_VIDEO); + if (m_fmtCtx == nullptr || video == nullptr) + { + return; + } + + const AVStream *avStream = m_fmtCtx->streams[video->stream_idx]; + const double fps = av_q2d(avStream->avg_frame_rate); + const double timeBase = av_q2d(avStream->time_base); + if (fps <= 0.0 || timeBase <= 0.0) + { + return; + } + + const Int64 timestamp = static_cast(frame_idx / (fps * timeBase)); + const int result = av_seek_frame(m_fmtCtx, video->stream_idx, timestamp, AVSEEK_FLAG_ANY); + if (result < 0) + { + char error_buffer[1024]; + av_strerror(result, error_buffer, sizeof(error_buffer)); + DEBUG_LOG(("Failed 'av_seek_frame': %s", error_buffer)); + return; + } + + for (const auto &stream : m_streams) + { + if (stream.codec_ctx != nullptr) + { + avcodec_flush_buffers(stream.codec_ctx); } } } @@ -344,6 +408,12 @@ Int FFmpegFile::getNumFrames() const if (m_fmtCtx == nullptr || stream == nullptr || m_fmtCtx->streams[stream->stream_idx] == nullptr) return 0; + // TheSuperHackers @bugfix bobtista 13/07/2026 Prefer the exact frame count from the container + // (the Bink demuxer stores it); the duration*fps product is only an estimate, which broke + // callers doing exact end-of-movie math against frameIndex(). + if (m_fmtCtx->streams[stream->stream_idx]->nb_frames > 0) + return static_cast(m_fmtCtx->streams[stream->stream_idx]->nb_frames); + return (m_fmtCtx->duration / (double)AV_TIME_BASE) * av_q2d(m_fmtCtx->streams[stream->stream_idx]->avg_frame_rate); } @@ -352,7 +422,14 @@ Int FFmpegFile::getCurrentFrame() const const FFmpegStream *stream = findMatch(AVMEDIA_TYPE_VIDEO); if (stream == nullptr) return 0; + // TheSuperHackers @build bobtista 24/07/2026 AVCodecContext::frame_number was + // renamed to frame_num in FFmpeg 6.0 (libavcodec 60). Support both so the + // video device builds against FFmpeg 5.1 (Debian 12) through 7.x. +#if LIBAVCODEC_VERSION_MAJOR >= 60 return stream->codec_ctx->frame_num; +#else + return stream->codec_ctx->frame_number; +#endif } Int FFmpegFile::getPixelFormat() const @@ -364,10 +441,17 @@ Int FFmpegFile::getPixelFormat() const return stream->codec_ctx->pix_fmt; } -UnsignedInt FFmpegFile::getFrameTime() const +// TheSuperHackers @bugfix bobtista 13/07/2026 Return the frame time unrounded; truncating to whole +// milliseconds ran video ~1% fast against its audio, drifting seconds apart over a full movie. +double FFmpegFile::getFrameTime() const { const FFmpegStream *stream = findMatch(AVMEDIA_TYPE_VIDEO); if (stream == nullptr) - return 0u; - return 1000u / av_q2d(m_fmtCtx->streams[stream->stream_idx]->avg_frame_rate); + return 0.0; + + const double fps = av_q2d(m_fmtCtx->streams[stream->stream_idx]->avg_frame_rate); + if (fps <= 0.0) + return 0.0; + + return 1000.0 / fps; } diff --git a/Core/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp b/Core/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp index ae5384a92a1..1f558f2e2b1 100644 --- a/Core/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp +++ b/Core/GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp @@ -330,6 +330,18 @@ FFmpegVideoStream::FFmpegVideoStream(FFmpegFile* file) FFmpegVideoStream::~FFmpegVideoStream() { +#ifdef RTS_USE_OPENAL + // TheSuperHackers @bugfix bobtista 13/07/2026 Cut this movie's audio immediately like BinkClose + // did; otherwise up to a second of already queued audio keeps playing after the stream closes. + if (TheAudio != NULL) + { + OpenALAudioStream* audioStream = (OpenALAudioStream*)TheAudio->getHandleForBink(); + if (audioStream != NULL) + { + audioStream->reset(); + } + } +#endif av_freep(&m_audioBuffer); av_frame_free(&m_frame); sws_freeContext(m_swsContext); @@ -390,9 +402,12 @@ void FFmpegVideoStream::onFrame(AVFrame *frame, int stream_idx, int stream_type, void FFmpegVideoStream::update() { #ifdef RTS_USE_OPENAL - // Start audio playback + // TheSuperHackers @bugfix bobtista 13/07/2026 Service the audio stream instead of restarting it. + // alSourcePlay on an already playing source rewinds it to the head of its queued buffers every + // client frame, machine-gunning movie audio; update() unqueues played buffers and only restarts + // a genuinely underrun source. OpenALAudioStream* audioStream = (OpenALAudioStream*)TheAudio->getHandleForBink(); - audioStream->play(); + audioStream->update(); #endif //BinkWait( m_handle ); } @@ -441,7 +456,9 @@ void FFmpegVideoStream::frameRender( VideoBuffer *buffer ) switch (buffer->format()) { case VideoBuffer::TYPE_R8G8B8: - dst_pix_fmt = AV_PIX_FMT_RGB24; + // TheSuperHackers @bugfix bobtista 13/07/2026 D3D R8G8B8 memory byte order is B,G,R + // on little endian, which is FFmpeg BGR24; RGB24 rendered with red and blue swapped. + dst_pix_fmt = AV_PIX_FMT_BGR24; break; case VideoBuffer::TYPE_X8R8G8B8: dst_pix_fmt = AV_PIX_FMT_BGR0; @@ -500,7 +517,22 @@ void FFmpegVideoStream::frameNext() Int FFmpegVideoStream::frameIndex() { - return m_ffmpegFile->getCurrentFrame(); + // TheSuperHackers @bugfix bobtista 13/07/2026 Match the Bink contract: zero-based index while + // playing (FFmpeg's frame_num is one-based) and 0 once the stream is exhausted, like Bink + // wrapping FrameNum past the last frame. Callers depend on both: Display stops fullscreen + // movies at frameCount()-1 and WindowVideoManager detects completion via frameIndex()==0 + // after frameNext. + if (!m_good) + { + return 0; + } + + Int index = m_ffmpegFile->getCurrentFrame() - 1; + if (index < 0) + { + return 0; + } + return index; } //============================================================================ @@ -518,7 +550,15 @@ Int FFmpegVideoStream::frameCount() void FFmpegVideoStream::frameGoto( Int index ) { + // TheSuperHackers @bugfix bobtista 13/07/2026 Reset the stream state and rebase the pacing + // clock after a seek, then prime the frame at the new position like BinkGoto did. Without the + // rebase isFrameReady() compares against frameTime * index and stalls for the skipped span. m_ffmpegFile->seekFrame(index); + m_good = true; + m_gotFrame = false; + frameNext(); + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + m_startTime = now - (uint64_t)(m_ffmpegFile->getFrameTime() * frameIndex()); } //============================================================================ diff --git a/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp b/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp index 056af0d6b9b..b3808af6e9d 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp @@ -53,14 +53,56 @@ #include "W3DDevice/GameClient/HeightMap.h" #include "W3DDevice/GameClient/W3DShroud.h" #include "WW3D2/texture.h" -#include "WW3D2/dx8caps.h" +#include "WW3D2/surfaceclass.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "WWMath/vector2i.h" +#include + // PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// enum { OVERLAY_REFRESH_RATE = 6 }; ///< over updates once this many frames +static void W3DRadar_DrawPixel(TextureClass::MutableTextureMipView &mip, Int x, Int y, unsigned int color) +{ + if (!mip.Is_Valid() || x < 0 || y < 0 || x >= static_cast(mip.Width) || y >= static_cast(mip.Height)) + { + return; + } + + const unsigned int bytesPerPixel = Get_Bytes_Per_Pixel(mip.Format); + if (bytesPerPixel == 0) + { + return; + } + + unsigned char *dst = mip.Data + static_cast(y) * mip.Pitch + static_cast(x) * bytesPerPixel; + std::memcpy(dst, &color, bytesPerPixel); +} + +static void W3DRadar_ClearTexture(TextureClass *texture) +{ + if (texture == nullptr) + { + return; + } + + TextureClass::MutableTextureMipView mip = texture->Begin_Mip_Write(0); + if (!mip.Is_Valid()) + { + return; + } + + const unsigned int rowBytes = mip.Width * Get_Bytes_Per_Pixel(mip.Format); + for (unsigned int y = 0; y < mip.Height; ++y) + { + std::memset(mip.Data + static_cast(y) * mip.Pitch, 0, rowBytes); + } + texture->End_Mip_Write(0); +} + //------------------------------------------------------------------------------------------------- /** Is the point legal, that is, inside the resolution of the radar cells */ //------------------------------------------------------------------------------------------------- @@ -81,7 +123,7 @@ static WW3DFormat findFormat(const WW3DFormat formats[]) for( Int i = 0; formats[ i ] != WW3D_FORMAT_UNKNOWN; i++ ) { - if( DX8Wrapper::Get_Current_Caps()->Support_Texture_Format( formats[ i ] ) ) + if( g_renderBackend && g_renderBackend->Supports_Texture_Format( formats[ i ] ) ) { return formats[ i ]; @@ -93,6 +135,14 @@ static WW3DFormat findFormat(const WW3DFormat formats[]) return WW3D_FORMAT_UNKNOWN; } +static TextureClass *createWritableRadarTexture(unsigned width, unsigned height, WW3DFormat format) +{ + SurfaceClass *surface = NEW_REF(SurfaceClass, (width, height, format)); + TextureClass *texture = MSGNEW("TextureClass") TextureClass(surface, MIP_LEVELS_1); + REF_PTR_RELEASE(surface); + return texture; +} + //------------------------------------------------------------------------------------------------- /** Find the texture format we're going to use for the radar. The texture format must * be supported by the hardware. The "more preferred" formats appear at the top of @@ -102,8 +152,12 @@ void W3DRadar::initializeTextureFormats() { const WW3DFormat terrainFormats[] = { - WW3D_FORMAT_R8G8B8, + // TheSuperHackers @bugfix bobtista 26/04/2026 Prefer an opaque + // 32-bit radar terrain texture in standalone. bgfx can upload + // X8R8G8B8 directly, while R8G8B8 has no native bgfx texture + // format and falls back to the backend's white placeholder. WW3D_FORMAT_X8R8G8B8, + WW3D_FORMAT_R8G8B8, WW3D_FORMAT_R5G6B5, WW3D_FORMAT_X1R5G5B5, WW3D_FORMAT_UNKNOWN // keep this one last @@ -168,7 +222,7 @@ void W3DRadar::deleteResources() deleteInstance(m_shroudImage); m_shroudImage = nullptr; - DEBUG_ASSERTCRASH(m_shroudSurface == nullptr, ("W3DRadar::deleteResources: m_shroudSurface is expected null")); + DEBUG_ASSERTCRASH(!m_shroudMipActive, ("W3DRadar::deleteResources: m_shroud mip write is expected inactive")); DEBUG_ASSERTCRASH(m_shroudSurfaceBits == nullptr, ("W3DRadar::deleteResources: m_shroudSurfaceBits is expected null")); } @@ -620,9 +674,7 @@ void W3DRadar::drawIcons( Int pixelX, Int pixelY, Int width, Int height ) void W3DRadar::updateObjectTexture(TextureClass *texture) { // reset the overlay texture - SurfaceClass *surface = texture->Get_Surface_Level(); - surface->Clear(); - REF_PTR_RELEASE(surface); + W3DRadar_ClearTexture(texture); // rebuild the object overlay renderObjectList( m_objectList, texture ); @@ -687,20 +739,17 @@ void W3DRadar::renderObjectList( const RadarObject *listHead, TextureClass *text if( listHead == nullptr || texture == nullptr ) return; - // get surface for texture to render into - SurfaceClass *surface = texture->Get_Surface_Level(); + TextureClass::MutableTextureMipView mip = texture->Begin_Mip_Write(0); + if (!mip.Is_Valid()) + { + return; + } // loop through all objects and draw ICoord2D radarPoint; Player *player = rts::getObservedOrLocalPlayer(); - SurfaceClass::SurfaceDescription surfaceDesc; - surface->Get_Description(surfaceDesc); - int pitch; - void *pBits = surface->Lock(&pitch); - const unsigned int bytesPerPixel = Get_Bytes_Per_Pixel(surfaceDesc.Format); - for( const RadarObject *rObj = listHead; rObj; rObj = rObj->friend_getNext() ) { if (!canRenderObject(rObj, player)) @@ -738,29 +787,27 @@ void W3DRadar::renderObjectList( const RadarObject *listHead, TextureClass *text } - const unsigned int pixelColor = ARGB_Color_To_WW3D_Color(surfaceDesc.Format, argbColor); + const unsigned int pixelColor = ARGB_Color_To_WW3D_Color(mip.Format, argbColor); // draw the blip, but make sure the points are legal if( legalRadarPoint( radarPoint.x, radarPoint.y ) ) - surface->Draw_Pixel( radarPoint.x, radarPoint.y, pixelColor, bytesPerPixel, pBits, pitch ); + W3DRadar_DrawPixel(mip, radarPoint.x, radarPoint.y, pixelColor); radarPoint.y++; if( legalRadarPoint( radarPoint.x, radarPoint.y ) ) - surface->Draw_Pixel( radarPoint.x, radarPoint.y, pixelColor, bytesPerPixel, pBits, pitch ); + W3DRadar_DrawPixel(mip, radarPoint.x, radarPoint.y, pixelColor); radarPoint.x++; if( legalRadarPoint( radarPoint.x, radarPoint.y ) ) - surface->Draw_Pixel( radarPoint.x, radarPoint.y, pixelColor, bytesPerPixel, pBits, pitch ); + W3DRadar_DrawPixel(mip, radarPoint.x, radarPoint.y, pixelColor); radarPoint.y--; if( legalRadarPoint( radarPoint.x, radarPoint.y ) ) - surface->Draw_Pixel( radarPoint.x, radarPoint.y, pixelColor, bytesPerPixel, pBits, pitch ); + W3DRadar_DrawPixel(mip, radarPoint.x, radarPoint.y, pixelColor); } - surface->Unlock(); - REF_PTR_RELEASE(surface); - + texture->End_Mip_Write(0); } //------------------------------------------------------------------------------------------------- @@ -856,7 +903,7 @@ W3DRadar::W3DRadar() m_shroudTextureFormat = WW3D_FORMAT_UNKNOWN; m_shroudImage = nullptr; m_shroudTexture = nullptr; - m_shroudSurface = nullptr; + m_shroudMipActive = FALSE; m_shroudSurfaceBits = nullptr; m_shroudSurfacePitch = 0; m_shroudSurfaceFormat = WW3D_FORMAT_UNKNOWN; @@ -907,13 +954,11 @@ void W3DRadar::init() // allocate our terrain texture // poolify - m_terrainTexture = MSGNEW("TextureClass") TextureClass( m_textureWidth, m_textureHeight, - m_terrainTextureFormat, MIP_LEVELS_1 ); + m_terrainTexture = createWritableRadarTexture( m_textureWidth, m_textureHeight, m_terrainTextureFormat ); DEBUG_ASSERTCRASH( m_terrainTexture, ("W3DRadar: Unable to allocate terrain texture") ); // allocate our overlay texture - m_overlayTexture = MSGNEW("TextureClass") TextureClass( m_textureWidth, m_textureHeight, - m_overlayTextureFormat, MIP_LEVELS_1 ); + m_overlayTexture = createWritableRadarTexture( m_textureWidth, m_textureHeight, m_overlayTextureFormat ); DEBUG_ASSERTCRASH( m_overlayTexture, ("W3DRadar: Unable to allocate overlay texture") ); // set filter type for the overlay texture, try it and see if you like it, I don't ;) @@ -921,8 +966,7 @@ void W3DRadar::init() // m_overlayTexture->Set_Mag_Filter( TextureFilterClass::FILTER_TYPE_NONE ); // allocate our shroud texture - m_shroudTexture = MSGNEW("TextureClass") TextureClass( m_textureWidth, m_textureHeight, - m_shroudTextureFormat, MIP_LEVELS_1 ); + m_shroudTexture = createWritableRadarTexture( m_textureWidth, m_textureHeight, m_shroudTextureFormat ); DEBUG_ASSERTCRASH( m_shroudTexture, ("W3DRadar: Unable to allocate shroud texture") ); m_shroudTexture->Get_Filter().Set_Min_Filter( TextureFilterClass::FILTER_TYPE_DEFAULT ); m_shroudTexture->Get_Filter().Set_Mag_Filter( TextureFilterClass::FILTER_TYPE_DEFAULT ); @@ -992,21 +1036,8 @@ void W3DRadar::reset() Radar::reset(); // clear our texture data, but do not delete the resources - SurfaceClass *surface; - - surface = m_terrainTexture->Get_Surface_Level(); - if( surface ) - { - surface->Clear(); - REF_PTR_RELEASE(surface); - } - - surface = m_overlayTexture->Get_Surface_Level(); - if( surface ) - { - surface->Clear(); - REF_PTR_RELEASE(surface); - } + W3DRadar_ClearTexture(m_terrainTexture); + W3DRadar_ClearTexture(m_overlayTexture); // don't call Clear(); that wips to transparent. do this instead. //gs Dude, it's called CLEARshroud. It needs to clear the shroud. @@ -1050,7 +1081,6 @@ void W3DRadar::newMap( TerrainLogic *terrain ) // ------------------------------------------------------------------------------------------------ void W3DRadar::buildTerrainTexture( TerrainLogic *terrain ) { - SurfaceClass *surface; RGBColor waterColor; // we will want to reconstruct our new view box now @@ -1062,8 +1092,12 @@ void W3DRadar::buildTerrainTexture( TerrainLogic *terrain ) waterColor.blue = TheWaterTransparency->m_radarColor.blue; // get the terrain surface to draw in - surface = m_terrainTexture->Get_Surface_Level(); - DEBUG_ASSERTCRASH( surface, ("W3DRadar: Can't get surface for terrain texture") ); + TextureClass::MutableTextureMipView mip = m_terrainTexture->Begin_Mip_Write(0); + DEBUG_ASSERTCRASH(mip.Is_Valid(), ("W3DRadar: Can't get writable mip for terrain texture")); + if (!mip.Is_Valid()) + { + return; + } // build the terrain RGBColor sampleColor; @@ -1074,12 +1108,6 @@ void W3DRadar::buildTerrainTexture( TerrainLogic *terrain ) Coord3D worldPoint; Bridge *bridge; - SurfaceClass::SurfaceDescription surfaceDesc; - surface->Get_Description(surfaceDesc); - int pitch; - void *pBits = surface->Lock(&pitch); - const unsigned int bytesPerPixel = Get_Bytes_Per_Pixel(surfaceDesc.Format); - for( y = 0; y < m_textureHeight; y++ ) { @@ -1268,16 +1296,14 @@ void W3DRadar::buildTerrainTexture( TerrainLogic *terrain ) // draw the pixel for the terrain at this point, note that because of the orientation // of our world we draw it with positive y in the "up" direction const Color argbColor = GameMakeColor( color.red * 255, color.green * 255, color.blue * 255, 255 ); - const unsigned int pixelColor = ARGB_Color_To_WW3D_Color(surfaceDesc.Format, argbColor); - surface->Draw_Pixel( x, y, pixelColor, bytesPerPixel, pBits, pitch ); + const unsigned int pixelColor = ARGB_Color_To_WW3D_Color(mip.Format, argbColor); + W3DRadar_DrawPixel(mip, x, y, pixelColor); } } - // all done with the surface - surface->Unlock(); - REF_PTR_RELEASE(surface); + m_terrainTexture->End_Mip_Write(0); } @@ -1290,22 +1316,8 @@ void W3DRadar::clearShroud() return; #endif - SurfaceClass *surface = m_shroudTexture->Get_Surface_Level(); - // fill to clear, shroud will make black. Don't want to make something black that logic can't clear - - int pitch; - void *pBits = surface->Lock(&pitch); - const unsigned int bytesPerPixel = surface->Get_Bytes_Per_Pixel(); - const Color color = GameMakeColor( 0, 0, 0, 0 ); - - for( Int y = 0; y < m_textureHeight; y++ ) - { - surface->Draw_H_Line(y, 0, m_textureWidth-1, color, bytesPerPixel, pBits, pitch); - } - - surface->Unlock(); - REF_PTR_RELEASE(surface); + W3DRadar_ClearTexture(m_shroudTexture); } // ------------------------------------------------------------------------------------------------ @@ -1358,29 +1370,27 @@ void W3DRadar::setShroudLevel(Int shroudX, Int shroudY, CellShroudStatus setting else alpha = 0; - if (m_shroudSurface == nullptr) + if (!m_shroudMipActive) { // This is expensive. - SurfaceClass* surface = m_shroudTexture->Get_Surface_Level(); - DEBUG_ASSERTCRASH( surface, ("W3DRadar: Can't get surface for Shroud texture") ); - SurfaceClass::SurfaceDescription surfaceDesc; - surface->Get_Description(surfaceDesc); - int pitch; - void *pBits = surface->Lock(&pitch); - const unsigned int bytesPerPixel = Get_Bytes_Per_Pixel(surfaceDesc.Format); + TextureClass::MutableTextureMipView mip = m_shroudTexture->Begin_Mip_Write(0); + DEBUG_ASSERTCRASH(mip.Is_Valid(), ("W3DRadar: Can't get writable mip for Shroud texture")); + if (!mip.Is_Valid()) + { + return; + } const Color argbColor = GameMakeColor( 0, 0, 0, alpha ); - const unsigned int pixelColor = ARGB_Color_To_WW3D_Color(surfaceDesc.Format, argbColor); + const unsigned int pixelColor = ARGB_Color_To_WW3D_Color(mip.Format, argbColor); for( Int y = radarMinY; y <= radarMaxY; ++y ) { for( Int x = radarMinX; x <= radarMaxX; ++x ) { - surface->Draw_Pixel( x, y, pixelColor, bytesPerPixel, pBits, pitch ); + W3DRadar_DrawPixel(mip, x, y, pixelColor); } } - surface->Unlock(); - REF_PTR_RELEASE(surface); + m_shroudTexture->End_Mip_Write(0); } else { @@ -1395,7 +1405,14 @@ void W3DRadar::setShroudLevel(Int shroudX, Int shroudY, CellShroudStatus setting { for( Int x = radarMinX; x <= radarMaxX; ++x ) { - m_shroudSurface->Draw_Pixel( x, y, pixelColor, m_shroudSurfacePixelSize, m_shroudSurfaceBits, m_shroudSurfacePitch ); + if (x < 0 || y < 0 || x >= m_textureWidth || y >= m_textureHeight) + { + continue; + } + unsigned char *dst = static_cast(m_shroudSurfaceBits) + + static_cast(y) * m_shroudSurfacePitch + + static_cast(x) * m_shroudSurfacePixelSize; + std::memcpy(dst, &pixelColor, m_shroudSurfacePixelSize); } } } @@ -1403,29 +1420,32 @@ void W3DRadar::setShroudLevel(Int shroudX, Int shroudY, CellShroudStatus setting void W3DRadar::beginSetShroudLevel() { - DEBUG_ASSERTCRASH( m_shroudSurface == nullptr, ("W3DRadar::beginSetShroudLevel: m_shroudSurface is expected null") ); - m_shroudSurface = m_shroudTexture->Get_Surface_Level(); - DEBUG_ASSERTCRASH( m_shroudSurface != nullptr, ("W3DRadar::beginSetShroudLevel: Can't get surface for Shroud texture") ); - - SurfaceClass::SurfaceDescription surfaceDesc; - m_shroudSurface->Get_Description(surfaceDesc); - m_shroudSurfaceBits = m_shroudSurface->Lock(&m_shroudSurfacePitch); - m_shroudSurfaceFormat = surfaceDesc.Format; - m_shroudSurfacePixelSize = Get_Bytes_Per_Pixel(surfaceDesc.Format); + DEBUG_ASSERTCRASH(!m_shroudMipActive, ("W3DRadar::beginSetShroudLevel: shroud mip write is expected inactive")); + TextureClass::MutableTextureMipView mip = m_shroudTexture->Begin_Mip_Write(0); + DEBUG_ASSERTCRASH(mip.Is_Valid(), ("W3DRadar::beginSetShroudLevel: Can't get writable mip for Shroud texture")); + if (!mip.Is_Valid()) + { + return; + } + m_shroudMipActive = TRUE; + m_shroudSurfaceBits = mip.Data; + m_shroudSurfacePitch = static_cast(mip.Pitch); + m_shroudSurfaceFormat = mip.Format; + m_shroudSurfacePixelSize = Get_Bytes_Per_Pixel(mip.Format); } void W3DRadar::endSetShroudLevel() { - DEBUG_ASSERTCRASH( m_shroudSurface != nullptr, ("W3DRadar::endSetShroudLevel: m_shroudSurface is not expected null") ); + DEBUG_ASSERTCRASH(m_shroudMipActive, ("W3DRadar::endSetShroudLevel: shroud mip write is expected active")); if (m_shroudSurfaceBits != nullptr) { - m_shroudSurface->Unlock(); + m_shroudTexture->End_Mip_Write(0); m_shroudSurfaceBits = nullptr; m_shroudSurfacePitch = 0; m_shroudSurfaceFormat = WW3D_FORMAT_UNKNOWN; m_shroudSurfacePixelSize = 0; } - REF_PTR_RELEASE(m_shroudSurface); + m_shroudMipActive = FALSE; } //------------------------------------------------------------------------------------------------- diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp index 6a47cbf4775..9a875273540 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp @@ -54,7 +54,6 @@ #include #include #include -#include #include "Common/GlobalData.h" #include "Common/PerfTimer.h" @@ -82,7 +81,11 @@ #include "W3DDevice/GameClient/W3DShadow.h" #include "W3DDevice/GameClient/W3DWater.h" #include "W3DDevice/GameClient/W3DShroud.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/indexbuffer.h" +#include "WW3D2/vertexbuffer.h" +#include "WW3D2/renderdebugstats.h" #include "WW3D2/light.h" #include "WW3D2/scene.h" #include "W3DDevice/GameClient/W3DPoly.h" @@ -176,13 +179,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 @@ -309,7 +312,9 @@ BaseHeightMapRenderObjClass::BaseHeightMapRenderObjClass() #else m_shroud = NEW W3DShroud; #endif - DX8Wrapper::SetCleanupHook(this); + if (g_renderBackend != nullptr) { + g_renderBackend->Set_Device_Cleanup_Hook(this); + } } void BaseHeightMapRenderObjClass::scheduleFullUpdate() @@ -1443,7 +1448,7 @@ RenderObjClass * BaseHeightMapRenderObjClass::Clone() const //============================================================================= void BaseHeightMapRenderObjClass::loadRoadsAndBridges(W3DTerrainLogic *pTerrainLogic, Bool saveGame) { - if (DX8Wrapper::_Get_D3D_Device8() && (DX8Wrapper::_Get_D3D_Device8()->TestCooperativeLevel()) != D3D_OK) + if (g_renderBackend != nullptr && g_renderBackend->Is_Device_Lost()) return; //device not ready to render anything #ifdef DO_ROADS @@ -1720,7 +1725,7 @@ void BaseHeightMapRenderObjClass::updateViewImpassableAreas(Bool partial, Int mi } // save calculating the tangent over and over again. - Real tanImpassableRad = tan(m_curImpassableSlope / 360.f * 2 * PI); + Real tanImpassableRad = WWMath::Tan(m_curImpassableSlope / 360.f * 2 * PI); for (Int j = minY; j < maxY; ++j) { for (Int i = minX; i < maxX; ++i) { m_showAsVisibleCliff[i + j * xSize] = evaluateAsVisibleCliff(i, j, tanImpassableRad); @@ -1736,33 +1741,30 @@ void BaseHeightMapRenderObjClass::initDestAlphaLUT() if (!m_destAlphaTexture) return; - SurfaceClass *surf=m_destAlphaTexture->Get_Surface_Level(); - - if (surf) + TextureClass::MutableTextureMipView mip = m_destAlphaTexture->Begin_Mip_Write(0); + if (mip.Is_Valid() && mip.Format == WW3D_FORMAT_A8R8G8B8 && mip.Width >= 256) { - Int pitch; - UnsignedInt *pData=(UnsignedInt*)surf->Lock(&pitch); + UnsignedInt *pData = reinterpret_cast(mip.Data); Int maxOpacity=(Int)(TheWaterTransparency->m_minWaterOpacity * 255.0f); Int alpha; - if (pData) + // Fill texture with alpha gradient. + for (Int x=0; x<256; x++) { - //Fill texture with alpha gradient - for (Int x=0; x<256; x++) - { - alpha = x; - if (alpha > maxOpacity) - alpha = maxOpacity; - *pData=(alpha<<24)|0x00ffffff; - pData++; - } - surf->Unlock(); + alpha = x; + if (alpha > maxOpacity) + alpha = maxOpacity; + *pData=(alpha<<24)|0x00ffffff; + pData++; } + m_destAlphaTexture->End_Mip_Write(0); + m_destAlphaTexture->Get_Filter().Set_Min_Filter(TextureFilterClass::FILTER_TYPE_FAST); + m_destAlphaTexture->Get_Filter().Set_Mag_Filter(TextureFilterClass::FILTER_TYPE_FAST); + m_destAlphaTexture->Get_Filter().Set_Mip_Mapping(TextureFilterClass::FILTER_TYPE_NONE); m_destAlphaTexture->Get_Filter().Set_U_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); m_destAlphaTexture->Get_Filter().Set_V_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); - REF_PTR_RELEASE(surf); m_currentMinWaterOpacity = TheWaterTransparency->m_minWaterOpacity; } } @@ -1882,8 +1884,8 @@ void BaseHeightMapRenderObjClass::freeScorchBuffers() //============================================================================= void BaseHeightMapRenderObjClass::allocateScorchBuffers() { - m_vertexScorch=NEW_REF(DX8VertexBufferClass,(DX8_FVF_XYZDUV1,MAX_SCORCH_VERTEX,DX8VertexBufferClass::USAGE_DEFAULT)); - m_indexScorch=NEW_REF(DX8IndexBufferClass,(MAX_SCORCH_INDEX)); + m_vertexScorch=NEW_REF(RenderVertexBufferClass,(DX8_FVF_XYZDUV1,MAX_SCORCH_VERTEX,RenderVertexBufferClass::USAGE_DEFAULT)); + m_indexScorch=NEW_REF(RenderIndexBufferClass,(MAX_SCORCH_INDEX)); m_scorchTexture=NEW ScorchTextureClass; m_scorchesInBuffer = 0; // If we just allocated the buffers, we got no scorches in the buffer. m_curNumScorchVertices=0; @@ -1905,7 +1907,13 @@ void BaseHeightMapRenderObjClass::allocateScorchBuffers() //============================================================================= void BaseHeightMapRenderObjClass::updateScorches() { - if (m_scorchesInBuffer > 1) { + // TheSuperHackers @bugfix bobtista 02/06/2026 Skip the rebuild when the buffer already + // reflects every current scorch. The old `> 1` test never matched when exactly one scorch + // existed, so updateScorches re-locked and re-uploaded the (identical) 8194-vertex / + // 49164-index buffers every frame. addScorch resets m_scorchesInBuffer to 0 to force a + // rebuild, so "buffer holds all scorches" is m_scorchesInBuffer == m_numScorches. Harmless + // on DX8 (cheap managed re-lock) but on bgfx it recreated an immutable GPU buffer per frame. + if (m_scorchesInBuffer == m_numScorches) { return; } if (m_numScorches==0) { @@ -1916,11 +1924,11 @@ void BaseHeightMapRenderObjClass::updateScorches() } m_curNumScorchVertices = 0; m_curNumScorchIndices = 0; - DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexScorch); + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(m_indexScorch); UnsignedShort *ib=lockIdxBuffer.Get_Index_Array(); UnsignedShort *curIb = ib; - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexScorch); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexScorch); VertexFormatXYZDUV1 *vb = (VertexFormatXYZDUV1*)lockVtxBuffer.Get_Vertex_Array(); VertexFormatXYZDUV1 *curVb = vb; @@ -1969,7 +1977,6 @@ void BaseHeightMapRenderObjClass::updateScorches() for (j=minY; j= 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. @@ -1982,6 +1989,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++; } @@ -2457,11 +2485,16 @@ void BaseHeightMapRenderObjClass::renderShoreLines(CameraClass *pCamera) m_numVisibleShoreLineTiles=0; - if (!TheGlobalData->m_showSoftWaterEdge || TheWaterTransparency->m_transparentWaterDepth==0 || m_numShoreLineTiles == 0) + // TheSuperHackers @bugfix bobtista 22/06/2026 The dx8 reference authors the + // shoreline dest-alpha gradient here for the soft water edge and restores the + // RGB mask on exit, same as the original. Do not gate this off for dx8. + if (!TheGlobalData->m_showSoftWaterEdge + || TheWaterTransparency->m_transparentWaterDepth==0 + || m_numShoreLineTiles == 0) return; //Check if video card is capable of using this effect - if (DX8Wrapper::getBackBufferFormat() != WW3D_FORMAT_A8R8G8B8) + if (g_renderBackend->Get_Back_Buffer_Format() != WW3D_FORMAT_A8R8G8B8) return; //can't apply effect on cards without destination alpha Int vertexCount = 0; @@ -2478,21 +2511,27 @@ 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); + g_renderBackend->Set_Color_Write_Enable(false, false, false, true); + g_renderBackend->Set_Texture_Coord_Source(0, RB_TEXCOORD_MESH_UV, 0); + g_renderBackend->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_SELECTARG1); + g_renderBackend->Set_Texture_Alpha_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_SELECTARG1); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); while (j != m_numShoreLineTiles) { - DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,DEFAULT_MAX_BATCH_SHORELINE_TILES*4); - DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC_DX8,DEFAULT_MAX_BATCH_SHORELINE_TILES*6); + DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,DEFAULT_MAX_BATCH_SHORELINE_TILES*4); + DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC,DEFAULT_MAX_BATCH_SHORELINE_TILES*6); { //Need to put this in another code block so vb/ib gets automatically locked/unlocked by destructors DynamicVBAccessClass::WriteLockClass lock(&vb_access); @@ -2500,7 +2539,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; } @@ -2597,9 +2636,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; } @@ -2608,7 +2647,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(); } @@ -2620,11 +2659,13 @@ void BaseHeightMapRenderObjClass::renderShoreLinesSorted(CameraClass *pCamera) { m_numVisibleShoreLineTiles=0; - if (!TheGlobalData->m_showSoftWaterEdge || TheWaterTransparency->m_transparentWaterDepth==0 || m_numShoreLineTiles == 0) + if (!TheGlobalData->m_showSoftWaterEdge + || TheWaterTransparency->m_transparentWaterDepth==0 + || m_numShoreLineTiles == 0) return; //Check if video card is capable of using this effect - if (DX8Wrapper::getBackBufferFormat() != WW3D_FORMAT_A8R8G8B8) + if (g_renderBackend->Get_Back_Buffer_Format() != WW3D_FORMAT_A8R8G8B8) return; //can't apply effect on cards without destination alpha Int vertexCount = 0; @@ -2662,23 +2703,29 @@ 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); + g_renderBackend->Set_Color_Write_Enable(false, false, false, true); + g_renderBackend->Set_Texture_Coord_Source(0, RB_TEXCOORD_MESH_UV, 0); + g_renderBackend->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_SELECTARG1); + g_renderBackend->Set_Texture_Alpha_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_SELECTARG1); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); Bool isDone=FALSE; Int lastRenderedTile=0; while (!isDone) { - DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,DEFAULT_MAX_BATCH_SHORELINE_TILES*4); - DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC_DX8,DEFAULT_MAX_BATCH_SHORELINE_TILES*6); + DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,DEFAULT_MAX_BATCH_SHORELINE_TILES*4); + DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC,DEFAULT_MAX_BATCH_SHORELINE_TILES*6); { //Need to put this in another code block so vb/ib gets automatically locked/unlocked by destructors DynamicVBAccessClass::WriteLockClass lock(&vb_access); @@ -2686,7 +2733,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; } @@ -2937,9 +2984,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; } @@ -2948,7 +2995,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(); } @@ -2961,15 +3008,15 @@ called after flush. */ void BaseHeightMapRenderObjClass::renderTrees(CameraClass * camera) { #ifdef EXTENDED_STATS - if (DX8Wrapper::stats.m_disableObjects) { + if (g_renderDebugStats.m_disableObjects) { return; } #endif 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/CameraShakeSystem.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/CameraShakeSystem.cpp index 771a47a6581..37d39a2bb43 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/CameraShakeSystem.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/CameraShakeSystem.cpp @@ -44,7 +44,6 @@ #include #include #include -#include #include "Common/GlobalData.h" #include "Common/PerfTimer.h" @@ -69,7 +68,6 @@ #include "W3DDevice/GameClient/W3DShadow.h" #include "W3DDevice/GameClient/W3DWater.h" #include "W3DDevice/GameClient/W3DShroud.h" -#include "WW3D2/dx8wrapper.h" #include "WW3D2/light.h" #include "WW3D2/scene.h" #include "W3DDevice/GameClient/W3DPoly.h" @@ -160,10 +158,10 @@ void CameraShakeSystemClass::CameraShakerClass::Compute_Rotations(const Vector3 ** omega(t) = start_omega + (end_omega - start_omega) * t ** phi = random(0..start_omega) */ - float intensity = Intensity * (1.0f - WWMath::Sqrt(len2) / Radius) * (1.0f - ElapsedTime / Duration); + float intensity = Intensity * (1.0f - WWMath::Sqrt_Legacy(len2) / Radius) * (1.0f - ElapsedTime / Duration); for (int i=0; i<3; i++) { float omega = Omega[i] + (END_OMEGA - Omega[i]) * ElapsedTime; - (*set_angles)[i] += AXIS_ROTATION[i] * intensity * WWMath::Sin(omega * ElapsedTime + Phi[i]); + (*set_angles)[i] += AXIS_ROTATION[i] * intensity * WWMath::Sinf_Legacy(omega * ElapsedTime + Phi[i]); //WST 11/14/2002. Add in additional random fudge. There seems to be a too mathematical pattern of shake with the above Vector3 secondary_angles; @@ -300,4 +298,3 @@ void CameraShakeSystemClass::Update_Camera_Shaker(Vector3 camera_position, Vecto // The Instance of the system CameraShakeSystemClass CameraShakerSystem; //WST 11/12/2002 This is the new Camera Shaker system upgrade - diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DLaserDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DLaserDraw.cpp index 5146f1b0a61..8490caf5582 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DLaserDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DLaserDraw.cpp @@ -29,7 +29,9 @@ /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include #include +#include #include "Common/Thing.h" #include "Common/ThingTemplate.h" @@ -50,11 +52,50 @@ #include "WW3D2/segline.h" #include "WWMath/vector3.h" #include "WW3D2/assetmgr.h" +#include "GgcRuntimeFlags.h" // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// +static Bool shouldLogLaserDrawForDrawable(const Drawable *draw) +{ + if ((!GgcFlags::Enabled(GgcFlag_LaserDiag) && !GgcFlags::Enabled(GgcFlag_LaserDiagAll)) || draw == nullptr || draw->getTemplate() == nullptr) + return FALSE; + if (GgcFlags::Enabled(GgcFlag_LaserDiagAll)) + return TRUE; + + const char *name = draw->getTemplate()->getName().str(); + return name != nullptr && std::strstr(name, "PatriotBinaryDataStream") != nullptr; +} + +static void logLaserDrawEvent(const char *event, const Drawable *draw, const Coord3D *startPos, const Coord3D *endPos, Real widthScale) +{ + if (!shouldLogLaserDrawForDrawable(draw)) + return; + + const Object *obj = draw->getObject(); + if (FILE *diag = std::fopen("ggc_laser_draw_diag.txt", "a")) + { + std::fprintf(diag, + "%s frame=%u drawable=%u object=%u template=%s visibleObjDestroyed=%d widthScale=%.3f start=(%.2f,%.2f,%.2f) end=(%.2f,%.2f,%.2f)\n", + event, + TheGameLogic != nullptr ? TheGameLogic->getFrame() : 0, + static_cast(draw->getID()), + obj != nullptr ? static_cast(obj->getID()) : 0, + draw->getTemplate() != nullptr ? draw->getTemplate()->getName().str() : "", + obj != nullptr ? obj->isDestroyed() : 0, + widthScale, + startPos != nullptr ? startPos->x : 0.0f, + startPos != nullptr ? startPos->y : 0.0f, + startPos != nullptr ? startPos->z : 0.0f, + endPos != nullptr ? endPos->x : 0.0f, + endPos != nullptr ? endPos->y : 0.0f, + endPos != nullptr ? endPos->z : 0.0f); + std::fclose(diag); + } +} + //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- W3DLaserDrawModuleData::W3DLaserDrawModuleData() @@ -120,6 +161,7 @@ W3DLaserDraw::W3DLaserDraw( Thing *thing, const ModuleData* moduleData ) : Int i; const W3DLaserDrawModuleData *data = getW3DLaserDrawModuleData(); + logLaserDrawEvent("draw-create", getDrawable(), nullptr, nullptr, 0.0f); m_texture = WW3DAssetManager::Get_Instance()->Get_Texture( data->m_textureName.str() ); if (m_texture) @@ -220,6 +262,7 @@ W3DLaserDraw::W3DLaserDraw( Thing *thing, const ModuleData* moduleData ) : W3DLaserDraw::~W3DLaserDraw() { const W3DLaserDrawModuleData *data = getW3DLaserDrawModuleData(); + logLaserDrawEvent("draw-destroy", getDrawable(), nullptr, nullptr, 0.0f); for( UnsignedInt i = 0; i < data->m_numBeams * data->m_segments; i++ ) { @@ -262,6 +305,8 @@ void W3DLaserDraw::doDrawModule(const Matrix3D* transformMtx) DEBUG_CRASH( ("W3DLaserDraw::doDrawModule() expects its owner drawable %s to have a ClientUpdate = LaserUpdate module.", draw->getTemplate()->getName().str() )); return; } + if (TheGameLogic != nullptr && (TheGameLogic->getFrame() % 30) == 0) + logLaserDrawEvent("draw-frame", draw, update->getStartPos(), update->getEndPos(), update->getWidthScale()); //If the update has moved the laser, it requires a reset of the laser. if (update->isDirty() || m_selfDirty) @@ -414,6 +459,7 @@ void W3DLaserDraw::doDrawModule(const Matrix3D* transformMtx) } m_line3D[ index ]->Set_Width( width ); + m_line3D[ index ]->Set_Shader( ShaderClass::_PresetAdditiveShader ); m_line3D[ index ]->Set_Points( 2, &laserPoints[0] ); } } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp index 6b9d2bd3095..6ed16bd9985 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp @@ -1233,7 +1233,7 @@ enum AnimParseType CPP_11(: Int) //------------------------------------------------------------------------------------------------- static void parseAnimation(INI* ini, void *instance, void * /*store*/, const void* userData) { - AnimParseType animType = (AnimParseType)(UnsignedInt)userData; + AnimParseType animType = (AnimParseType)(UnsignedInt)(uintptr_t)userData; AsciiString animName = ini->getNextAsciiString(); animName.toLower(); @@ -1447,7 +1447,7 @@ void W3DModelDrawModuleData::parseConditionState(INI* ini, void *instance, void ModelConditionInfo info; W3DModelDrawModuleData* self = (W3DModelDrawModuleData*)instance; - ParseCondStateType cst = (ParseCondStateType)(UnsignedInt)userData; + ParseCondStateType cst = (ParseCondStateType)(UnsignedInt)(uintptr_t)userData; switch (cst) { case PARSE_DEFAULT: @@ -1870,7 +1870,7 @@ void W3DModelDraw::allocateShadows() shadowInfo.m_sizeY = tmplate->getShadowSizeY(); shadowInfo.m_offsetX = tmplate->getShadowOffsetX(); shadowInfo.m_offsetY = tmplate->getShadowOffsetY(); - m_shadow = TheW3DShadowManager->addShadow(m_renderObject, &shadowInfo); + m_shadow = TheW3DShadowManager->addShadow(m_renderObject, &shadowInfo, getDrawable()); if (m_shadow) { m_shadow->enableShadowInvisible(m_fullyObscuredByShroud); if (m_renderObject->Is_Hidden() || !m_shadowEnabled) @@ -3026,6 +3026,20 @@ void W3DModelDraw::setModelState(const ModelConditionInfo* newState) { m_renderObject = W3DDisplay::m_assetManager->Create_Render_Obj(newState->m_modelName.str(), draw->getScale(), m_hexColor); DEBUG_ASSERTCRASH(m_renderObject, ("*** ASSET ERROR: Model %s not found!",newState->m_modelName.str())); + // TheSuperHackers @bugfix bobtista 11/07/2026 The assert above is + // debug-only; release builds silently accepted the null and the + // drawable stayed invisible while its object simulated normally. + // Say so where a release build can see it (throttled). + if (m_renderObject == nullptr) + { + static int s_nullModelLogCount = 0; + if (++s_nullModelLogCount <= 20) + { + std::fprintf(stderr, "[ggc] model load failed, drawable will be invisible: %s\n", + newState->m_modelName.str()); + std::fflush(stderr); + } + } } //BONEPOS_LOG(("validateStuff() from within W3DModelDraw::setModelState()")); @@ -4320,4 +4334,3 @@ void W3DModelDrawModuleData::xfer( Xfer *x ) void W3DModelDrawModuleData::loadPostProcess() { } - diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp index a00746a0fb9..e900c30e4b9 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp @@ -224,7 +224,7 @@ void W3DTankDraw::updateTreadPositions(Real uvDelta) } // ensure coordinates of offset are in [0, 1] range: - offset_u = offset_u - WWMath::Floor(offset_u); + offset_u = offset_u - WWMath::Floorf(offset_u); pTread->m_materialSettings.customUVOffset.Set(offset_u,0); pTread++; } @@ -398,7 +398,7 @@ void W3DTankDraw::doDrawModule(const Matrix3D* transformMtx) { offset_u = pTread->m_materialSettings.customUVOffset.X - treadScrollSpeed; // ensure coordinates of offset are in [0, 1] range: - offset_u = offset_u - WWMath::Floor(offset_u); + offset_u = offset_u - WWMath::Floorf(offset_u); pTread->m_materialSettings.customUVOffset.Set(offset_u,0); pTread++; } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp index 8d936948ed6..07f288a72a6 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp @@ -403,7 +403,7 @@ void W3DTankTruckDraw::updateTreadPositions(Real uvDelta) } // ensure coordinates of offset are in [0, 1] range: - offset_u = offset_u - WWMath::Floor(offset_u); + offset_u = offset_u - WWMath::Floorf(offset_u); pTread->m_materialSettings.customUVOffset.Set(offset_u,0); pTread++; } @@ -720,7 +720,7 @@ void W3DTankTruckDraw::doDrawModule(const Matrix3D* transformMtx) { offset_u = pTread->m_materialSettings.customUVOffset.X - treadScrollSpeed; // ensure coordinates of offset are in [0, 1] range: - offset_u = offset_u - WWMath::Floor(offset_u); + offset_u = offset_u - WWMath::Floorf(offset_u); pTread->m_materialSettings.customUVOffset.Set(offset_u,0); pTread++; } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/FlatHeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/FlatHeightMap.cpp index c2daacdf927..68ee6a3ae7d 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/FlatHeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/FlatHeightMap.cpp @@ -56,7 +56,6 @@ #include #include #include -#include #include "Common/GlobalData.h" #include "Common/PerfTimer.h" @@ -82,7 +81,8 @@ #include "W3DDevice/GameClient/W3DShadow.h" #include "W3DDevice/GameClient/W3DWater.h" #include "W3DDevice/GameClient/W3DShroud.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/renderdebugstats.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/light.h" #include "WW3D2/scene.h" #include "W3DDevice/GameClient/W3DPoly.h" @@ -482,25 +482,30 @@ void FlatHeightMapRenderObjClass::Render(RenderInfoClass & rinfo) #endif #ifdef EXTENDED_STATS - if (DX8Wrapper::stats.m_disableTerrain) { + if (g_renderDebugStats.m_disableTerrain) { return; } #endif + // TheSuperHackers @bugfix bobtista 28/04/2026 Keep flat terrain in sync + // with the bgfx cloudmap path used by regular terrain. + W3DShaderManager::pushCloudShadowToBackend(doCloud, doCloud ? m_stageTwoTexture : nullptr); - 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 @@ -527,6 +532,13 @@ void FlatHeightMapRenderObjClass::Render(RenderInfoClass & rinfo) if (m_disableTextures) devicePasses=1; //force to 1 lighting-only pass + // TheSuperHackers @bugfix bobtista 24/04/2026 Same rationale as HeightMap: + // shader pipeline cannot emulate legacy camera-space texcoord generation. + if (g_renderBackend->Has_Shader_Pipeline()) + { + devicePasses = 1; + } + //Specify all textures that this shader may need. W3DShaderManager::setTexture(0,m_stageZeroTexture); if (m_shroud && rinfo.Additional_Pass_Count() && !m_disableTextures) @@ -538,8 +550,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 +562,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 +602,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 +619,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 +628,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 +640,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); + W3DShaderManager::pushCloudShadowToBackend(false, nullptr); } - diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/GUICallbacks/W3DControlBar.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/GUICallbacks/W3DControlBar.cpp index 3310b688c5c..a95b135ed7a 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/GUICallbacks/W3DControlBar.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/GUICallbacks/W3DControlBar.cpp @@ -41,6 +41,10 @@ #include "GameClient/ControlBarScheme.h" #include "GameClient/MapUtil.h" #include "GameLogic/GameLogic.h" +#include "GgcRuntimeFlags.h" + +#include +#include //------------------------------------------------------------------------------------------------- void W3DCameoMovieDraw( GameWindow *window, WinInstanceData *instData ) @@ -730,10 +734,27 @@ void W3DDrawMapPreview( GameWindow *window, WinInstanceData *instData) } - if(!BitIsSet(window->winGetStatus(), WIN_STATUS_IMAGE) || !window->winGetEnabledImage(0)) - TheDisplay->drawFillRect(ul.x, ul.y, lr.x -ul.x, lr.y-ul.y, lineColor); - else - TheDisplay->drawImage(window->winGetEnabledImage(0) , ul.x, ul.y, lr.x, lr.y ); + if(!BitIsSet(window->winGetStatus(), WIN_STATUS_IMAGE) || !window->winGetEnabledImage(0)) + TheDisplay->drawFillRect(ul.x, ul.y, lr.x -ul.x, lr.y-ul.y, lineColor); + else + { + if (GgcFlags::Enabled(GgcFlag_MapPreviewDiag)) + { + const Image *preview = window->winGetEnabledImage(0); + FILE *f = fopen("ggc_map_preview_diag.txt", "a"); + if (f != nullptr) + { + fprintf(f, "draw preview name='%s' filename='%s' win=(%d,%d %dx%d) draw=(%d,%d)-(%d,%d) extent=(%f,%f)-(%f,%f)\n", + preview ? preview->getName().str() : "(null)", + preview ? preview->getFilename().str() : "(null)", + pixelX, pixelY, width, height, ul.x, ul.y, lr.x, lr.y, + mmData->m_extent.lo.x, mmData->m_extent.lo.y, + mmData->m_extent.hi.x, mmData->m_extent.hi.y); + fclose(f); + } + } + TheDisplay->drawImage(window->winGetEnabledImage(0) , ul.x, ul.y, lr.x, lr.y ); + } const Image *image = TheMappedImageCollection->findImageByName("TecBuilding"); ICoord2DList::iterator it = TheSupplyAndTechImageLocations.m_techPosList.begin(); 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/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp index e118956987d..b7bc9e5620b 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp @@ -57,7 +57,6 @@ #include #include #include -#include #include "Common/GlobalData.h" #include "Common/PerfTimer.h" @@ -83,7 +82,11 @@ #include "W3DDevice/GameClient/W3DShadow.h" #include "W3DDevice/GameClient/W3DWater.h" #include "W3DDevice/GameClient/W3DShroud.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/indexbuffer.h" +#include "WW3D2/vertexbuffer.h" +#include "WW3D2/renderdebugstats.h" #include "WW3D2/light.h" #include "WW3D2/scene.h" #include "W3DDevice/GameClient/W3DPoly.h" @@ -154,7 +157,7 @@ Int HeightMapRenderObjClass::freeMapResources() //============================================================================= -DX8VertexBufferClass *HeightMapRenderObjClass::getVertexBufferTile(Int x, Int y) +RenderVertexBufferClass *HeightMapRenderObjClass::getVertexBufferTile(Int x, Int y) { return m_vertexBufferTiles[y*m_numVBTilesX+x]; } @@ -302,7 +305,7 @@ data is expected to be an array same dimensions as current heightmap mapped into this VB. */ //============================================================================= -Int HeightMapRenderObjClass::updateVB(DX8VertexBufferClass *pVB, VERTEX_FORMAT *data, Int x0, Int y0, Int x1, Int y1, Int originX, Int originY, WorldHeightMap *pMap, RefRenderObjListIterator *pLightsIterator) +Int HeightMapRenderObjClass::updateVB(RenderVertexBufferClass *pVB, VERTEX_FORMAT *data, Int x0, Int y0, Int x1, Int y1, Int originX, Int originY, WorldHeightMap *pMap, RefRenderObjListIterator *pLightsIterator) { Int i,j; Vector3 lightRay[MAX_GLOBAL_LIGHTS]; @@ -325,7 +328,7 @@ Int HeightMapRenderObjClass::updateVB(DX8VertexBufferClass *pVB, VERTEX_FORMAT * lightRay[lightIndex].Set(-lightPos.x, -lightPos.y, -lightPos.z); } - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(pVB); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(pVB); VERTEX_FORMAT *vbHardware = (VERTEX_FORMAT*)lockVtxBuffer.Get_Vertex_Array(); VERTEX_FORMAT *vBase = data; // Note that we are building the vertex buffer data in the memory buffer, data. @@ -546,7 +549,7 @@ Int HeightMapRenderObjClass::updateVB(DX8VertexBufferClass *pVB, VERTEX_FORMAT * /** Update the dynamic lighting values only in a rectangular block of the given Vertex Buffer. The vertex locations and texture coords are unchanged. */ -Int HeightMapRenderObjClass::updateVBForLight(DX8VertexBufferClass *pVB, VERTEX_FORMAT *data, Int x0, Int y0, Int x1, Int y1, Int originX, Int originY, W3DDynamicLight *pLights[], Int numLights) +Int HeightMapRenderObjClass::updateVBForLight(RenderVertexBufferClass *pVB, VERTEX_FORMAT *data, Int x0, Int y0, Int x1, Int y1, Int originX, Int originY, W3DDynamicLight *pLights[], Int numLights) { #if (OPTIMIZED_HEIGHTMAP_LIGHTING) // (gth) if optimizations are enabled, jump over to the "optimized" version of this function. @@ -564,7 +567,7 @@ Int HeightMapRenderObjClass::updateVBForLight(DX8VertexBufferClass *pVB, VERTEX_ assert(x0 >= originX && y0 >= originY && x1>x0 && y1>y0 && x1<=originX+VERTEX_BUFFER_TILE_LENGTH && y1<=originY+VERTEX_BUFFER_TILE_LENGTH); #endif - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(pVB); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(pVB); VERTEX_FORMAT *vBase = (VERTEX_FORMAT*)lockVtxBuffer.Get_Vertex_Array(); VERTEX_FORMAT *vb; @@ -691,7 +694,7 @@ Int HeightMapRenderObjClass::updateVBForLight(DX8VertexBufferClass *pVB, VERTEX_ } -Int HeightMapRenderObjClass::updateVBForLightOptimized(DX8VertexBufferClass *pVB, VERTEX_FORMAT *data, Int x0, Int y0, Int x1, Int y1, Int originX, Int originY, W3DDynamicLight *pLights[], Int numLights) +Int HeightMapRenderObjClass::updateVBForLightOptimized(RenderVertexBufferClass *pVB, VERTEX_FORMAT *data, Int x0, Int y0, Int x1, Int y1, Int originX, Int originY, W3DDynamicLight *pLights[], Int numLights) { Int i,j,k; Int vn0,un0,vp1,up1; @@ -704,7 +707,7 @@ Int HeightMapRenderObjClass::updateVBForLightOptimized(DX8VertexBufferClass *pVB assert(x0 >= originX && y0 >= originY && x1>x0 && y1>y0 && x1<=originX+VERTEX_BUFFER_TILE_LENGTH && y1<=originY+VERTEX_BUFFER_TILE_LENGTH); #endif - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(pVB); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(pVB); VERTEX_FORMAT *vBase = (VERTEX_FORMAT*)lockVtxBuffer.Get_Vertex_Array(); VERTEX_FORMAT *vb; @@ -717,7 +720,7 @@ Int HeightMapRenderObjClass::updateVBForLightOptimized(DX8VertexBufferClass *pVB // constexpr const Int quad_right_offset = 4; constexpr const Int quad_below_offset = vertsPerRow; - //constexpr const Int quad_below_right_offset = vertsPerRow + 4; + constexpr const Int quad_below_right_offset = vertsPerRow + 4; // // i,j loop over the quads affected by the light. Each quad has its *own* 4 vertices. This @@ -833,13 +836,11 @@ Int HeightMapRenderObjClass::updateVBForLightOptimized(DX8VertexBufferClass *pVB } if (j < y1-1) { // copy light to (down,1) - //(vBase + offset + quad_below_offset + 1)->diffuse = light_copy; - (vBase + offset + quad_right_offset + 1)->diffuse = (light_copy&0x00FFFFFF) | ((vbaseMirror + offset + quad_right_offset + 1)->diffuse&0xff000000) ; + (vBase + offset + quad_below_offset + 1)->diffuse = (light_copy&0x00FFFFFF) | ((vbaseMirror + offset + quad_below_offset + 1)->diffuse&0xff000000) ; } if ((i < x1-1) && (j < y1-1)) { // copy light to (right+down,0) - //(vBase + offset + quad_below_right_offset)->diffuse = light_copy; - (vBase + offset + quad_right_offset)->diffuse = (light_copy&0x00FFFFFF) | ((vbaseMirror + offset + quad_right_offset)->diffuse&0xff000000) ; + (vBase + offset + quad_below_right_offset)->diffuse = (light_copy&0x00FFFFFF) | ((vbaseMirror + offset + quad_below_right_offset)->diffuse&0xff000000) ; } vb++; vbMirror++; @@ -1006,7 +1007,7 @@ Int HeightMapRenderObjClass::updateBlock(Int x0, Int y0, Int x1, Int y1, WorldH if (xMin >= xMax) { continue; } - DX8VertexBufferClass *pVB = getVertexBufferTile(i, j); + RenderVertexBufferClass *pVB = getVertexBufferTile(i, j); VERTEX_FORMAT *pData = getVertexBufferBackup(i, j); updateVB(pVB, pData, xMin, yMin, xMax, yMax, originX, originY, pMap, pLightsIterator); } @@ -1278,10 +1279,10 @@ Int HeightMapRenderObjClass::initHeightData(Int x, Int y, WorldHeightMap *pMap, { //requested heightmap different from old one. freeIndexVertexBuffers(); //Create static index buffers. These will index the vertex buffers holding the map. - m_indexBuffer=NEW_REF(DX8IndexBufferClass,(VERTEX_BUFFER_TILE_LENGTH*VERTEX_BUFFER_TILE_LENGTH*2*3)); + m_indexBuffer=NEW_REF(RenderIndexBufferClass,(VERTEX_BUFFER_TILE_LENGTH*VERTEX_BUFFER_TILE_LENGTH*2*3)); // Fill up the IB - DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); UnsignedShort *ib=lockIdxBuffer.Get_Index_Array(); for (j=0; j<(VERTEX_BUFFER_TILE_LENGTH*VERTEX_BUFFER_TILE_LENGTH*4); j+=VERTEX_BUFFER_TILE_LENGTH*4) @@ -1320,14 +1321,14 @@ Int HeightMapRenderObjClass::initHeightData(Int x, Int y, WorldHeightMap *pMap, m_x=x; m_y=y; - m_vertexBufferTiles = NEW DX8VertexBufferClass*[m_numVertexBufferTiles]; + m_vertexBufferTiles = NEW RenderVertexBufferClass*[m_numVertexBufferTiles]; m_vertexBufferBackup = NEW VERTEX_FORMAT [m_numVertexBufferTiles * HEIGHTMAP_VERTEX_NUM]; for (i=0; im_processMe = false; +#ifdef RTS_ZEROHOUR + // TheSuperHackers @bugfix bobtista 15/07/2026 Lights that illuminate through the dedicated + // shadowed point-light path skip the legacy CPU terrain vertex lighting too: its per-channel + // clamp bleaches already-bright daylight vertex colors to white, flashing the terrain toward + // its raw (warm) albedo instead of adding the light's own colour. + if (pLight->getExcludeFromLightEnv()) { + continue; + } +#endif if (pLight->m_enabled || pLight->m_priorEnable) { Real range = pLight->Get_Attenuation_Range(); if (pLight->m_priorEnable) { @@ -1520,7 +1530,7 @@ void HeightMapRenderObjClass::On_Frame_Update() if (!intersect) { continue; } - DX8VertexBufferClass *pVB = getVertexBufferTile(i, j); + RenderVertexBufferClass *pVB = getVertexBufferTile(i, j); VERTEX_FORMAT *pData = getVertexBufferBackup(i, j); updateVBForLight(pVB, pData, xMin, yMin, xMax, yMax, originX,originY, enabledLights, numDynaLights); } @@ -1768,8 +1778,8 @@ void HeightMapRenderObjClass::updateCenter(CameraClass *camera, const Vector3 *c shiftPivot.X = viewDir.X * magicEdgeLenScale; shiftPivot.Y = viewDir.Y * magicEdgeLenScale; - newOrgX = WWMath::Round((cameraPivot->X + shiftPivot.X)/MAP_XY_FACTOR) - m_x/2 + m_map->getBorderSizeInline(); - newOrgY = WWMath::Round((cameraPivot->Y + shiftPivot.Y)/MAP_XY_FACTOR) - m_y/2 + m_map->getBorderSizeInline(); + newOrgX = WWMath::Roundf((cameraPivot->X + shiftPivot.X)/MAP_XY_FACTOR) - m_x/2 + m_map->getBorderSizeInline(); + newOrgY = WWMath::Roundf((cameraPivot->Y + shiftPivot.Y)/MAP_XY_FACTOR) - m_y/2 + m_map->getBorderSizeInline(); } WorldHeightMap::DrawArea newDrawArea = m_map->createDrawArea(newOrgX, newOrgY); @@ -1885,6 +1895,14 @@ 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); + const Bool doLightMap = TheGlobalData->m_useLightMap; + W3DShaderManager::pushLightMapToBackend(doLightMap, doLightMap ? m_stageThreeTexture : nullptr); + Matrix3D tm(Transform); #if 0 // There is some weirdness sometimes with the dx8 static buffers. // This usually fixes terrain flashing. jba. @@ -1897,7 +1915,7 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) if (ndx>=m_numVertexBufferTiles) { ndx = 0; } - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexBufferTiles + ndx); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexBufferTiles + ndx); VERTEX_FORMAT *vb = (VERTEX_FORMAT*)lockVtxBuffer.Get_Vertex_Array(); vb = 0; } @@ -1914,25 +1932,25 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) #endif #ifdef EXTENDED_STATS - if (DX8Wrapper::stats.m_disableTerrain) { + if (g_renderDebugStats.m_disableTerrain) { return; } #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; @@ -1950,29 +1968,35 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) rinfo.Peek_Additional_Pass(0)->Install_Materials(); renderTerrainPass(&rinfo.Camera); rinfo.Peek_Additional_Pass(0)->UnInstall_Materials(); + // TheSuperHackers @bugfix bobtista 17/07/2026 Clear the lightmap push on this + // early return so it does not leak onto draws after the terrain pass. + W3DShaderManager::pushLightMapToBackend(FALSE, nullptr); return; } } 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(); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG2, D3DTA_TFACTOR ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR,0xff808080); + g_renderBackend->Apply_Render_State_Changes(); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_TFACTOR); + 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); + // TheSuperHackers @bugfix bobtista 17/07/2026 Clear the lightmap push on this + // early return so it does not leak onto draws after the terrain pass. + W3DShaderManager::pushLightMapToBackend(FALSE, nullptr); return; } } 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 @@ -2003,15 +2027,35 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) if (m_disableTextures) devicePasses=1; //force to 1 lighting-only pass + // TheSuperHackers @bugfix bobtista 23/04/2026 Force single-pass + // terrain when using the shader pipeline. The legacy multipass + // path relies on fixed-function camera-space texcoord generation + // that the uber shader does not emulate. Cloud shadowing is already + // handled in a single pass via pushCloudShadowToBackend. + if (g_renderBackend->Has_Shader_Pipeline()) + { + devicePasses = 1; + } //Specify all textures that this shader may need. W3DShaderManager::setTexture(0,m_stageZeroTexture); W3DShaderManager::setTexture(1,m_stageZeroTexture); W3DShaderManager::setTexture(2,m_stageTwoTexture); //cloud W3DShaderManager::setTexture(3,m_stageThreeTexture);//noise + + // TheSuperHackers @bugfix bobtista 22/04/2026 Explicitly bind + // terrain textures to the shader pipeline so 2D-UI atlas bindings + // from the previous pass cannot leak into the 3D terrain draw. + if (g_renderBackend->Has_Shader_Pipeline()) + { + g_renderBackend->Set_Texture(0, m_stageZeroTexture); + g_renderBackend->Set_Texture(1, m_stageZeroTexture); + g_renderBackend->Set_Texture(2, m_stageTwoTexture); + g_renderBackend->Set_Texture(3, m_stageThreeTexture); + } //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; @@ -2021,8 +2065,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,26 +2075,9 @@ void HeightMapRenderObjClass::Render(RenderInfoClass & rinfo) for (j=0; jProcessVertices(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(); - DX8Wrapper::_Get_D3D_Device8()->SetStreamSource( - 0, - m_xformedVertexBuffer[j*m_numVBTilesX+i], - D3DXGetFVFVertexSize(D3DFVF_XYZRHW |D3DFVF_DIFFUSE|D3DFVF_TEX2)); - DX8Wrapper::_Get_D3D_Device8()->SetVertexShader(D3DFVF_XYZRHW |D3DFVF_DIFFUSE|D3DFVF_TEX2); - } -#endif + g_renderBackend->Set_Vertex_Buffer(getVertexBufferTile(i, j)); 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 +2101,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 +2127,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 +2136,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 +2155,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,12 +2166,16 @@ 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); + // 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); + W3DShaderManager::pushLightMapToBackend(false, nullptr); } @@ -2152,35 +2183,18 @@ 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; jProcessVertices(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(); - DX8Wrapper::_Get_D3D_Device8()->SetStreamSource( - 0, - m_xformedVertexBuffer[j*m_numVBTilesX+i], - D3DXGetFVFVertexSize(D3DFVF_XYZRHW |D3DFVF_DIFFUSE|D3DFVF_TEX2)); - DX8Wrapper::_Get_D3D_Device8()->SetVertexShader(D3DFVF_XYZRHW |D3DFVF_DIFFUSE|D3DFVF_TEX2); - } -#endif + g_renderBackend->Set_Vertex_Buffer(getVertexBufferTile(i, j)); 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); } } } @@ -2206,8 +2220,8 @@ void HeightMapRenderObjClass::renderExtraBlendTiles() if (maxBlendTiles > 10000) //we can only fit about 10000 tiles into a single VB. maxBlendTiles = 10000; - DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,DX8_FVF_XYZNDUV2,maxBlendTiles*4); - DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC_DX8,maxBlendTiles*6); + DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC,DX8_FVF_XYZNDUV2,maxBlendTiles*4); + DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC,maxBlendTiles*6); { DynamicVBAccessClass::WriteLockClass lock(&vb_access); @@ -2244,6 +2258,10 @@ void HeightMapRenderObjClass::renderExtraBlendTiles() y >= drawStartY && y < drawEdgeY && m_map->getExtraAlphaUVData(x,y,U,V,alpha,&flipState, &cliffState)) { //this tile is inside visible region and has 3rd blend layer. + if ((alpha[0] | alpha[1] | alpha[2] | alpha[3]) == 0) + { + continue; + } Int idx = x+y*xExtent; @@ -2338,23 +2356,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; } } @@ -2382,14 +2400,29 @@ void HeightMapRenderObjClass::renderExtraBlendTiles() } Int devicePasses=W3DShaderManager::getShaderPasses(st); + // TheSuperHackers @bugfix bobtista 24/04/2026 Same rationale as + // the main terrain pass: shader pipeline cannot emulate the + // fixed-function camera-space texcoord generation. + if (g_renderBackend->Has_Shader_Pipeline()) + { + devicePasses = 1; + } for (Int pass=0; pass < devicePasses; pass++) { W3DShaderManager::setShader(st, pass); + if (g_renderBackend->Has_Shader_Pipeline()) + { + g_renderBackend->Set_Projected_Decal_Mode(RB_PROJECTED_DECAL_ALPHA); + } 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; } + if (g_renderBackend->Has_Shader_Pipeline()) + { + g_renderBackend->Set_Projected_Decal_Mode(RB_PROJECTED_DECAL_NONE); + } } W3DShaderManager::resetShader(st); } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/TerrainTex.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/TerrainTex.cpp index ae2e0b3e548..5109165e62c 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/TerrainTex.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/TerrainTex.cpp @@ -51,12 +51,240 @@ #include "W3DDevice/GameClient/WorldHeightMap.h" #include "W3DDevice/GameClient/TileData.h" #include "Common/GlobalData.h" -#include "WW3D2/dx8wrapper.h" -#include "d3dx8tex.h" +#include "WW3D2/ww3d.h" +#include "WW3D2/RenderBackend.h" /****************************************************************************** TerrainTextureClass ******************************************************************************/ +static void InvalidateGeneratedTerrainTexture(TextureBaseClass *texture) +{ + if (g_renderBackend != nullptr) + { + // TheSuperHackers @bugfix bobtista 28/04/2026 Generated terrain + // textures are populated through direct surface Lock writes. + // Tell bgfx to re-upload after the atlas and mip chain are complete, + // otherwise standalone can keep sampling an earlier partially-black + // cache entry. + g_renderBackend->Invalidate_Cached_Texture(texture); + } +} + +static RenderBackendTextureSampleFilter GetTerrainMinMagFilter() +{ + return (TheGlobalData && (TheGlobalData->m_bilinearTerrainTex || TheGlobalData->m_trilinearTerrainTex)) ? + RB_TEXTURE_SAMPLE_LINEAR : + RB_TEXTURE_SAMPLE_POINT; +} + +static RenderBackendTextureSampleFilter GetTerrainMipFilter() +{ + return (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) ? + RB_TEXTURE_SAMPLE_LINEAR : + RB_TEXTURE_SAMPLE_POINT; +} + +static void ApplyTerrainFilter(unsigned int stage) +{ + const RenderBackendTextureSampleFilter min_mag_filter = GetTerrainMinMagFilter(); + g_renderBackend->Set_Texture_Sample_Filter(stage, min_mag_filter, min_mag_filter, GetTerrainMipFilter()); +} + +static void SetTerrainTexcoordSource(unsigned int stage, unsigned int uv_index) +{ + g_renderBackend->Set_Texture_Coord_Source(stage, RB_TEXCOORD_MESH_UV, uv_index); +} + +static void DisableTerrainTextureTransform(unsigned int stage) +{ + g_renderBackend->Set_Texture_Transform_Mode(stage, 0, false); +} + +void TerrainTextureClass::UpdateTerrainAtlasRegions(WorldHeightMap *htMap, unsigned int textureWidth, unsigned int textureHeight, WW3DFormat textureFormat) +{ + Clear_Atlas_Regions(); + if (htMap == nullptr) + { + return; + } + + const Int borderPixels = TILE_OFFSET / 2; + if (textureFormat != WW3D_FORMAT_A1R5G5B5) + { + return; + } + + for (Int texClass = 0; texClass < htMap->m_numTextureClasses; texClass++) + { + Int width = htMap->m_textureClasses[texClass].width * TILE_PIXEL_EXTENT; + ICoord2D origin = htMap->m_textureClasses[texClass].positionInTexture; + if (origin.x <= 0) + { + continue; + } + + Int x = origin.x - borderPixels; + Int y = origin.y - borderPixels; + Int regionWidth = width + borderPixels * 2; + Int regionHeight = width + borderPixels * 2; + if (x < 0) + { + regionWidth += x; + x = 0; + } + if (y < 0) + { + regionHeight += y; + y = 0; + } + if (x + regionWidth > static_cast(textureWidth)) + { + regionWidth = static_cast(textureWidth) - x; + } + if (y + regionHeight > static_cast(textureHeight)) + { + regionHeight = static_cast(textureHeight) - y; + } + if (regionWidth <= 0 || regionHeight <= 0) + { + continue; + } + + Add_Atlas_Region(static_cast(x), static_cast(y), + static_cast(regionWidth), static_cast(regionHeight)); + } +} + +void TerrainTextureClass::WriteTerrainAtlasMipLevel(WorldHeightMap *htMap, unsigned int level) +{ + if (htMap == nullptr || level == 0) + { + return; + } + + const Int tilePixelExtent = TILE_PIXEL_EXTENT >> level; + if (tilePixelExtent <= 0) + { + return; + } + + MutableTextureMipView mip = Begin_Mip_Write(level); + if (!mip.Is_Valid() || mip.Format != WW3D_FORMAT_A1R5G5B5) + { + return; + } + + const Int surface_pitch = static_cast(mip.Pitch); + UnsignedByte *surface_bits = mip.Data; + + const Int pixelBytes = 2; + for (Int tileNdx = 0; tileNdx < htMap->m_numBitmapTiles; tileNdx++) + { + TileData *pTile = htMap->getSourceTile(tileNdx); + if (!pTile) + { + continue; + } + UnsignedByte *pTileData = pTile->getRGBDataForWidth(tilePixelExtent); + if (pTileData == nullptr) + { + continue; + } + + ICoord2D position = pTile->m_tileLocationInTexture; + if (position.x <= 0) + { + continue; + } + + const Int mipColumn = position.x >> level; + const Int mipRow = position.y >> level; + for (Int j = 0; j < tilePixelExtent; j++) + { + const Int row = mipRow + j; + if (row < 0 || row >= static_cast(mip.Height)) + { + continue; + } + UnsignedByte *pBGR = pTileData + (tilePixelExtent - 1 - j) * TILE_BYTES_PER_PIXEL * tilePixelExtent; + UnsignedByte *pBGRX = surface_bits + row * surface_pitch + mipColumn * pixelBytes; + for (Int i = 0; i < tilePixelExtent; i++) + { + const Int column = mipColumn + i; + if (column >= 0 && column < static_cast(mip.Width)) + { + *((Short*)pBGRX) = 0x8000 + ((pBGR[2]>>3)<<10) + ((pBGR[1]>>3)<<5) + (pBGR[0]>>3); + } + pBGRX += pixelBytes; + pBGR += TILE_BYTES_PER_PIXEL; + } + } + } + + const Int borderPixels = ((TILE_OFFSET / 2) >> level) > 0 ? ((TILE_OFFSET / 2) >> level) : 1; + for (Int texClass = 0; texClass < htMap->m_numTextureClasses; texClass++) + { + Int width = htMap->m_textureClasses[texClass].width * tilePixelExtent; + ICoord2D origin = htMap->m_textureClasses[texClass].positionInTexture; + if (origin.x <= 0) + { + continue; + } + + origin.x >>= level; + origin.y >>= level; + if (origin.x - borderPixels < 0 + || origin.y - borderPixels < 0 + || origin.x + width + borderPixels > static_cast(mip.Width) + || origin.y + width + borderPixels > static_cast(mip.Height)) + { + continue; + } + + for (Int y = 0; y < width; y++) + { + UnsignedByte *row = surface_bits + (origin.y + y) * surface_pitch; + for (Int b = 1; b <= borderPixels; b++) + { + memcpy(row + (origin.x - b) * pixelBytes, + row + (origin.x + width - b) * pixelBytes, + pixelBytes); + } + for (Int b = 0; b < borderPixels; b++) + { + memcpy(row + (origin.x + width + b) * pixelBytes, + row + (origin.x + b) * pixelBytes, + pixelBytes); + } + } + + const Int copyBytes = (width + borderPixels * 2) * pixelBytes; + for (Int b = 1; b <= borderPixels; b++) + { + UnsignedByte *dst = surface_bits + (origin.y - b) * surface_pitch + (origin.x - borderPixels) * pixelBytes; + UnsignedByte *src = surface_bits + (origin.y + width - b) * surface_pitch + (origin.x - borderPixels) * pixelBytes; + memcpy(dst, src, copyBytes); + } + for (Int b = 0; b < borderPixels; b++) + { + UnsignedByte *dst = surface_bits + (origin.y + width + b) * surface_pitch + (origin.x - borderPixels) * pixelBytes; + UnsignedByte *src = surface_bits + (origin.y + b) * surface_pitch + (origin.x - borderPixels) * pixelBytes; + memcpy(dst, src, copyBytes); + } + } + + End_Mip_Write(level); +} + +void TerrainTextureClass::WriteTerrainAtlasMipLevels(WorldHeightMap *htMap) +{ + const unsigned int mipCount = Get_Level_Count(); + for (unsigned int level = 1; level < mipCount; level++) + { + WriteTerrainAtlasMipLevel(htMap, level); + } +} + //----------------------------------------------------------------------------- // Public Functions //----------------------------------------------------------------------------- @@ -95,29 +323,26 @@ TerrainTextureClass::TerrainTextureClass(int height, int width) : //============================================================================= int TerrainTextureClass::update(WorldHeightMap *htMap) { - // D3DTexture is our texture; - - IDirect3DSurface8 *surface_level; - D3DSURFACE_DESC surface_desc; - D3DLOCKED_RECT locked_rect; - DX8_ErrorCode(Peek_D3D_Texture()->GetSurfaceLevel(0, &surface_level)); - DX8_ErrorCode(surface_level->GetDesc(&surface_desc)); - if (surface_desc.Width < TEXTURE_WIDTH) { - surface_level->Release(); + MutableTextureMipView mip = Begin_Mip_Write(0); + if (!mip.Is_Valid()) { + return 0; + } + if (mip.Width < TEXTURE_WIDTH) { return 0; } - DX8_ErrorCode(surface_level->LockRect(&locked_rect, nullptr, 0)); + const Int surface_pitch = static_cast(mip.Pitch); + UnsignedByte *surface_bits = mip.Data; Int tilePixelExtent = TILE_PIXEL_EXTENT; - Int tilesPerRow = surface_desc.Width/(2*TILE_PIXEL_EXTENT+TILE_OFFSET); + Int tilesPerRow = mip.Width/(2*TILE_PIXEL_EXTENT+TILE_OFFSET); tilesPerRow *= 2; // Int numRows = surface_desc.Height/(tilePixelExtent+TILE_OFFSET); #ifdef RTS_DEBUG //DEBUG_ASSERTCRASH(tilesPerRow*numRows >= htMap->m_numBitmapTiles, ("Too many tiles.")); - DEBUG_ASSERTCRASH((Int)surface_desc.Width >= tilePixelExtent*tilesPerRow, ("Bitmap too small.")); + DEBUG_ASSERTCRASH((Int)mip.Width >= tilePixelExtent*tilesPerRow, ("Bitmap too small.")); #endif - if (surface_desc.Format == D3DFMT_A1R5G5B5) { + if (mip.Format == WW3D_FORMAT_A1R5G5B5) { #if 0 UnsignedInt cellX, cellY; for (cellX = 0; cellX < surface_desc.Width; cellX++) { @@ -140,8 +365,7 @@ int TerrainTextureClass::update(WorldHeightMap *htMap) UnsignedByte *pBGR = pTile->getRGBDataForWidth(tilePixelExtent); pBGR += (tilePixelExtent-1-j)*TILE_BYTES_PER_PIXEL*tilePixelExtent; // invert to match. Int row = position.y+j; - UnsignedByte *pBGRX = ((UnsignedByte*)locked_rect.pBits) + - (row)*surface_desc.Width*pixelBytes; + UnsignedByte *pBGRX = surface_bits + row * surface_pitch; Int column = position.x; pBGRX += column*pixelBytes; @@ -163,8 +387,7 @@ int TerrainTextureClass::update(WorldHeightMap *htMap) Int j; for (j=0; jUnlockRect(); - surface_level->Release(); - DX8_ErrorCode(D3DXFilterTexture(Peek_D3D_Texture(), nullptr, 0, D3DX_FILTER_BOX)); + End_Mip_Write(0); + Generate_Mip_Levels(); + UpdateTerrainAtlasRegions(htMap, mip.Width, mip.Height, mip.Format); + WriteTerrainAtlasMipLevels(htMap); + InvalidateGeneratedTerrainTexture(this); if (WW3D::Get_Texture_Reduction()) { - Peek_D3D_Texture()->SetLOD(WW3D::Get_Texture_Reduction()); + Set_LOD(WW3D::Get_Texture_Reduction()); } - return(surface_desc.Height); + return(mip.Height); } -#if 0 // old version. -//============================================================================= -// TerrainTextureClass::update -//============================================================================= -/** Sets the tile bitmap data into the texture. The tiles are placed with 4 - pixel borders around them, so that when the tiles are scaled and bilinearly - interpolated, you don't get seams between the tiles. */ -//============================================================================= -int TerrainTextureClass::update(WorldHeightMap *htMap) -{ - // D3DTexture is our texture; - - IDirect3DSurface8 *surface_level; - D3DSURFACE_DESC surface_desc; - D3DLOCKED_RECT locked_rect; - DX8_ErrorCode(D3DTexture->GetSurfaceLevel(0, &surface_level)); - DX8_ErrorCode(surface_level->GetDesc(&surface_desc)); - if (surface_desc.Width < TEXTURE_WIDTH) { - surface_level->Release(); - if (surface_desc.Width == 256) { - return update256(htMap); - } - return false; - } - - DX8_ErrorCode(surface_level->LockRect(&locked_rect, nullptr, 0)); - - Int tilePixelExtent = TILE_PIXEL_EXTENT; - Int tilesPerRow = surface_desc.Width/(2*TILE_PIXEL_EXTENT+TILE_OFFSET); - tilesPerRow *= 2; - Int numRows = surface_desc.Height/(tilePixelExtent+TILE_OFFSET); -#ifdef RTS_DEBUG - assert(tilesPerRow*numRows >= htMap->m_numBitmapTiles); - assert((Int)surface_desc.Width >= tilePixelExtent*tilesPerRow); -#endif - if (surface_desc.Format == D3DFMT_A1R5G5B5) { - Int cellX, cellY; -#if 0 - for (cellX = 0; cellX < surface_desc.Width; cellX++) { - for (cellY = 0; cellY < surface_desc.Height; cellY++) { - UnsignedByte *pBGR = ((UnsignedByte *)locked_rect.pBits)+(cellY*surface_desc.Width+cellX)*2; - *((Short*)pBGR) = (((255-2*cellY)>>3)<<10) + ((4*cellX)>>4); - } - } -#endif - Int pixelBytes = 2; - for (cellY = 0; cellY < numRows; cellY++) { - for (cellX = 0; cellX < tilesPerRow; cellX++) { - Int tileNdx = cellX/2 + (tilesPerRow/2)*(cellY/2); - tileNdx *=4; - if (cellX&1) tileNdx++; - if (!(cellY&1)) tileNdx += 2; -#define ADD_EXTRA_TILES 1 -#if ADD_EXTRA_TILES // Fills in an extra 2 columns and 1 row of tiles if there is room. - if (!htMap->getSourceTile(tileNdx) && htMap->getSourceTile(tileNdx-4)) { - tileNdx -= 4; - } - if (!htMap->getSourceTile(tileNdx) && htMap->getSourceTile(tileNdx-8)) { - tileNdx -= 8; - } - if (!htMap->getSourceTile(tileNdx) && htMap->getSourceTile(tileNdx-2*tilesPerRow)) { - tileNdx -= 2*tilesPerRow; - } -#endif - if (htMap->getSourceTile(tileNdx)) { - Int i,j; - for (j=0; jgetSourceTile(tileNdx)->getRGBDataForWidth(tilePixelExtent); - pBGR += (tilePixelExtent-1-j)*TILE_BYTES_PER_PIXEL*tilePixelExtent; // invert to match. - Int row = cellY*tilePixelExtent+j; - row += TILE_OFFSET/2; - row += TILE_OFFSET*(cellY/2); - UnsignedByte *pBGRX = ((UnsignedByte*)locked_rect.pBits) + - (row)*surface_desc.Width*pixelBytes; - - Int column = cellX*tilePixelExtent; - column += TILE_OFFSET*(cellX/2); - pBGRX += column*pixelBytes; - pBGRX += (TILE_OFFSET/2)*pixelBytes; - for (i=0; i>3)<<10) + ((pBGR[1]>>3)<<5) + (pBGR[0]>>3); - pBGRX +=pixelBytes; - pBGR +=TILE_BYTES_PER_PIXEL; - } - } - - } - } - - } - - - for (cellY = 0; cellY < numRows; cellY++) { - for (cellX = 0; cellX < tilesPerRow; cellX++) { - // Duplicate 4 rows of pixels before and after. - Int j; - for (j=0; jUnlockRect(); - surface_level->Release(); - DX8_ErrorCode(D3DXFilterTexture(D3DTexture, nullptr, 0, D3DX_FILTER_BOX)); - return(surface_desc.Height); -} -#endif - //============================================================================= // TerrainTextureClass::setLOD //============================================================================= @@ -364,7 +432,7 @@ int TerrainTextureClass::update(WorldHeightMap *htMap) //============================================================================= void TerrainTextureClass::setLOD(Int LOD) { - if (Peek_D3D_Texture()) Peek_D3D_Texture()->SetLOD(LOD); + Set_LOD(static_cast(LOD)); } //============================================================================= // TerrainTextureClass::update @@ -375,23 +443,22 @@ void TerrainTextureClass::setLOD(Int LOD) //============================================================================= Bool TerrainTextureClass::updateFlat(WorldHeightMap *htMap, Int xCell, Int yCell, Int cellWidth, Int pixelsPerCell) { - // D3DTexture is our texture; - - IDirect3DSurface8 *surface_level; - D3DSURFACE_DESC surface_desc; - D3DLOCKED_RECT locked_rect; - DX8_ErrorCode(Peek_D3D_Texture()->GetSurfaceLevel(0, &surface_level)); - DX8_ErrorCode(surface_level->GetDesc(&surface_desc)); - DEBUG_ASSERTCRASH((Int)surface_desc.Width == cellWidth*pixelsPerCell, ("Bitmap too small.")); - DEBUG_ASSERTCRASH((Int)surface_desc.Height == cellWidth*pixelsPerCell, ("Bitmap too small.")); - if (surface_desc.Width != cellWidth*pixelsPerCell) { + MutableTextureMipView mip = Begin_Mip_Write(0); + if (!mip.Is_Valid()) + { return false; } - DX8_ErrorCode(surface_level->LockRect(&locked_rect, nullptr, 0)); + DEBUG_ASSERTCRASH((Int)mip.Width == cellWidth*pixelsPerCell, ("Bitmap too small.")); + DEBUG_ASSERTCRASH((Int)mip.Height == cellWidth*pixelsPerCell, ("Bitmap too small.")); + if (mip.Width != cellWidth*pixelsPerCell) { + return false; + } + const Int surface_pitch = static_cast(mip.Pitch); + UnsignedByte *surface_bits = mip.Data; - if (surface_desc.Format == D3DFMT_A1R5G5B5) { + if (mip.Format == WW3D_FORMAT_A1R5G5B5) { Int pixelBytes = 2; Int cellX, cellY; @@ -406,12 +473,11 @@ Bool TerrainTextureClass::updateFlat(WorldHeightMap *htMap, Int xCell, Int yCell #endif for (cellX = 0; cellX < cellWidth; cellX++) { for (cellY = 0; cellY < cellWidth; cellY++) { - UnsignedByte *pBGRX_data = ((UnsignedByte*)locked_rect.pBits); UnsignedByte *pBGR = htMap->getPointerToTileData(xCell+cellX, yCell+cellY, pixelsPerCell); if (pBGR == nullptr) continue; // past end of defined terrain. [3/24/2003] Int k, l; for (k=pixelsPerCell-1; k>=0; k--) { - UnsignedByte *pBGRX = pBGRX_data + (pixelsPerCell*(cellWidth-cellY-1)+k)*surface_desc.Width*pixelBytes + + UnsignedByte *pBGRX = surface_bits + (pixelsPerCell*(cellWidth-cellY-1)+k)*surface_pitch + cellX*pixelsPerCell*pixelBytes; for (l=0; l>3)<<10) + ((pBGR[1]>>3)<<5) + (pBGR[0]>>3); @@ -423,51 +489,22 @@ Bool TerrainTextureClass::updateFlat(WorldHeightMap *htMap, Int xCell, Int yCell } } - surface_level->UnlockRect(); - surface_level->Release(); - DX8_ErrorCode(D3DXFilterTexture(Peek_D3D_Texture(), nullptr, 0, D3DX_FILTER_BOX)); - return(surface_desc.Height); + End_Mip_Write(0); + Generate_Mip_Levels(); + InvalidateGeneratedTerrainTexture(this); + return(mip.Height); } //============================================================================= // TerrainTextureClass::Apply //============================================================================= -/** Sets the texture as the current D3D texture, and does some custom setup +/** Sets the texture as the current texture, and does some custom setup (standard D3D setup, but beyond the scope of W3D). */ //============================================================================= void TerrainTextureClass::Apply(unsigned int stage) { // Do the base apply. TextureClass::Apply(stage); -#if 0 // obsolete [4/1/2003] - if (TheGlobalData && (TheGlobalData->m_bilinearTerrainTex || TheGlobalData->m_trilinearTerrainTex)) { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MAGFILTER, D3DTEXF_POINT); - } - if (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MIPFILTER, D3DTEXF_POINT); - } - // Now setup the texture pipeline. - if (stage==0) { - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - 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_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); - - } -#endif } /****************************************************************************** @@ -480,8 +517,8 @@ void TerrainTextureClass::Apply(unsigned int stage) //============================================================================= // AlphaTerrainTextureClass::AlphaTerrainTextureClass //============================================================================= -/** Constructor. Calls parent constructor to creat a throw away 8x8 texture, -then uses the base texture's D3D texture. This way the base tiles pass, drawn +/** Constructor. Calls parent constructor to create a throwaway 8x8 texture, +then shares the base texture resource. This way the base tiles pass, drawn using TerrainTextureClass shares the same texture with the blended edges pass, saving lots of texture memory, and preventing seams between blended tiles. */ //============================================================================= @@ -489,16 +526,15 @@ AlphaTerrainTextureClass::AlphaTerrainTextureClass( TextureClass *pBaseTex ): TextureClass(8, 8, WW3D_FORMAT_A1R5G5B5, MIP_LEVELS_1 ) { - // Attach the base texture's d3d texture. - IDirect3DTexture8 * d3d_tex = pBaseTex->Peek_D3D_Texture(); - Set_D3D_Base_Texture(d3d_tex); + Copy_Atlas_Regions_From(pBaseTex); + Share_Texture_Storage_With(pBaseTex); } //============================================================================= // AlphaTerrainTextureClass::Apply //============================================================================= -/** Sets the texture as the current D3D texture, and does some custom setup. +/** Sets the texture as the current texture, and does some custom setup. This may be applied in either single pass, as the second texture in the pipe, or multipass. If stage==0, we are doing multipass and we set up the pipe for a single texture. If stage==1, then we are doing a single pass, and we @@ -510,37 +546,23 @@ void AlphaTerrainTextureClass::Apply(unsigned int stage) // Do the base apply. TextureClass::Apply(stage); - // Set the bilinear or trilinear filtering. - if (TheGlobalData && (TheGlobalData->m_bilinearTerrainTex || TheGlobalData->m_trilinearTerrainTex)) { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MAGFILTER, D3DTEXF_POINT); - } - if (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MIPFILTER, D3DTEXF_POINT); - } // Since we are using multiple distinct tiles, the textures doesn't wrap, so clamp it. - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); + ApplyTerrainFilter(stage); + g_renderBackend->Set_Texture_Address_Mode(0, RB_TEXTURE_ADDRESS_CLAMP, RB_TEXTURE_ADDRESS_CLAMP, RB_TEXTURE_ADDRESS_WRAP); // Now setup the texture pipeline. if (stage==0) { // Modulate the diffuse color with the texture as lighting comes from diffuse. - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - 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->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_MODULATE); + SetTerrainTexcoordSource(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->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 ); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); } else if (stage==1) { if (TheGlobalData && !TheGlobalData->m_multiPassTerrain) @@ -548,88 +570,88 @@ void AlphaTerrainTextureClass::Apply(unsigned int stage) ///@todo: Remove 8-Stage Nvidia hack after drivers are fixed. //This method is a backdoor specific to Nvidia based cards. It will fail on //other hardware. Allows single pass blend of 2 textures and post modulate diffuse. - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLOROP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, 0); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_ADD); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, 1); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_DIFFUSE | D3DTA_COMPLEMENT | D3DTA_ALPHAREPLICATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_ADD); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG1, D3DTA_TFACTOR | D3DTA_COMPLEMENT); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); - - DX8Wrapper::Set_DX8_Texture(2, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_COLOROP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_TEXCOORDINDEX, 2); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_COLORARG1, D3DTA_TEXTURE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_COLORARG2, D3DTA_TEXTURE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_ALPHAOP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_ALPHAARG1, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); - - DX8Wrapper::Set_DX8_Texture(3, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_COLOROP, D3DTOP_SELECTARG1); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_TEXCOORDINDEX, 3); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_COLORARG1, D3DTA_DIFFUSE | 0 | D3DTA_ALPHAREPLICATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_ALPHAARG1, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); - - DX8Wrapper::Set_DX8_Texture(4, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_COLOROP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_TEXCOORDINDEX, 4); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_COLORARG1, D3DTA_CURRENT); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_ALPHAOP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_ALPHAARG1, D3DTA_CURRENT); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); - - DX8Wrapper::Set_DX8_Texture(5, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 5, D3DTSS_COLOROP, D3DTOP_ADD); - DX8Wrapper::Set_DX8_Texture_Stage_State( 5, D3DTSS_TEXCOORDINDEX, 5); - DX8Wrapper::Set_DX8_Texture_Stage_State( 5, D3DTSS_COLORARG1, D3DTA_DIFFUSE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 5, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 5, D3DTSS_ALPHAOP, D3DTOP_ADD); - DX8Wrapper::Set_DX8_Texture_Stage_State( 5, D3DTSS_ALPHAARG1, D3DTA_TFACTOR | D3DTA_COMPLEMENT); - DX8Wrapper::Set_DX8_Texture_Stage_State( 5, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); - - DX8Wrapper::Set_DX8_Texture(6, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 6, D3DTSS_COLOROP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 6, D3DTSS_TEXCOORDINDEX, 6); - DX8Wrapper::Set_DX8_Texture_Stage_State( 6, D3DTSS_COLORARG1, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 6, D3DTSS_COLORARG2, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 6, D3DTSS_ALPHAOP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 6, D3DTSS_ALPHAARG1, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 6, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); - - DX8Wrapper::Set_DX8_Texture(7, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 7, D3DTSS_COLOROP, D3DTOP_SELECTARG1); - DX8Wrapper::Set_DX8_Texture_Stage_State( 7, D3DTSS_TEXCOORDINDEX, 7); - DX8Wrapper::Set_DX8_Texture_Stage_State( 7, D3DTSS_COLORARG1, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 7, D3DTSS_COLORARG2, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 7, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); - DX8Wrapper::Set_DX8_Texture_Stage_State( 7, D3DTSS_ALPHAARG1, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 7, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); + SetTerrainTexcoordSource(0, 0); + g_renderBackend->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_MODULATE); + + SetTerrainTexcoordSource(1, 1); + g_renderBackend->Set_Texture_Color_Argument(1, 1, RB_TEXARG_DIFFUSE | RB_TEXARG_COMPLEMENT | RB_TEXARG_ALPHAREPLICATE); + g_renderBackend->Set_Texture_Color_Argument(1, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_ADD); + g_renderBackend->Set_Texture_Alpha_Argument(1, 1, RB_TEXARG_TFACTOR | RB_TEXARG_COMPLEMENT); + g_renderBackend->Set_Texture_Alpha_Argument(1, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_ADD); + + g_renderBackend->Set_Texture(2, nullptr); + SetTerrainTexcoordSource(2, 2); + g_renderBackend->Set_Texture_Color_Argument(2, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(2, 2, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Operation(2, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Argument(2, 1, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Argument(2, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Operation(2, RB_TEXOP_MODULATE); + + g_renderBackend->Set_Texture(3, nullptr); + SetTerrainTexcoordSource(3, 3); + g_renderBackend->Set_Texture_Color_Argument(3, 1, RB_TEXARG_DIFFUSE | RB_TEXARG_ALPHAREPLICATE); + g_renderBackend->Set_Texture_Color_Argument(3, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(3, RB_TEXOP_SELECTARG1); + g_renderBackend->Set_Texture_Alpha_Argument(3, 1, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Argument(3, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Operation(3, RB_TEXOP_SELECTARG1); + + g_renderBackend->Set_Texture(4, nullptr); + SetTerrainTexcoordSource(4, 4); + g_renderBackend->Set_Texture_Color_Argument(4, 1, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Color_Argument(4, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(4, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Argument(4, 1, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Alpha_Argument(4, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Alpha_Operation(4, RB_TEXOP_MODULATE); + + g_renderBackend->Set_Texture(5, nullptr); + SetTerrainTexcoordSource(5, 5); + g_renderBackend->Set_Texture_Color_Argument(5, 1, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Argument(5, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(5, RB_TEXOP_ADD); + g_renderBackend->Set_Texture_Alpha_Argument(5, 1, RB_TEXARG_TFACTOR | RB_TEXARG_COMPLEMENT); + g_renderBackend->Set_Texture_Alpha_Argument(5, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Operation(5, RB_TEXOP_ADD); + + g_renderBackend->Set_Texture(6, nullptr); + SetTerrainTexcoordSource(6, 6); + g_renderBackend->Set_Texture_Color_Argument(6, 1, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Color_Argument(6, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Color_Operation(6, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Argument(6, 1, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Argument(6, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Operation(6, RB_TEXOP_MODULATE); + + g_renderBackend->Set_Texture(7, nullptr); + SetTerrainTexcoordSource(7, 7); + g_renderBackend->Set_Texture_Color_Argument(7, 1, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Color_Argument(7, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Color_Operation(7, RB_TEXOP_SELECTARG1); + g_renderBackend->Set_Texture_Alpha_Argument(7, 1, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Argument(7, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Operation(7, RB_TEXOP_SELECTARG1); } else { - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); - 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( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_MODULATE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); + g_renderBackend->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_SELECTARG1); + g_renderBackend->Set_Texture_Alpha_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_SELECTARG1); + + g_renderBackend->Set_Texture_Color_Argument(1, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(1, 2, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Argument(1, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_SELECTARG1); } } } @@ -661,7 +683,7 @@ TextureClass(name.isEmpty()?"TSNoiseUrb.tga":name.str(),name.isEmpty()?"TSNoiseU //============================================================================= // LightMapTerrainTextureClass::Apply //============================================================================= -/** Sets the texture as the current D3D texture, and does some custom setup. +/** Sets the texture as the current texture, and does some custom setup. The LightMapTerrainTextureClass may be applied by itself, or with the CloudMapTerrainTextureClass. This may be applied in either single pass, as the second texture in the pipe, @@ -675,65 +697,6 @@ yet another set of uv coordinates. void LightMapTerrainTextureClass::Apply(unsigned int stage) { TextureClass::Apply(stage); -#if 0 // obsolete [4/1/2003] - // Do the base apply. - /* previous setup */ - if (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MIPFILTER, D3DTEXF_POINT); - } - - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - - // Disable 3rd stage just in case. - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_COLOROP, D3DTOP_DISABLE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - - // Now setup the texture pipeline. - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_COLORARG2, D3DTA_CURRENT ); - if (stage == 0) { - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); - //Disable second stage - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_COLOROP, D3DTOP_MODULATE ); - } - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); - // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); - - - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); - - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); - - D3DXMATRIX inv; - float det; - D3DXMatrixInverse(&inv, &det, &curView); - - D3DXMATRIX scale; - D3DXMatrixScaling(&scale, STRETCH_FACTOR, STRETCH_FACTOR,1); - inv *=scale; - if (stage==0) { - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE0, inv); - } if (stage==1) { - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE1, inv); - } - - - 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); - } -#endif } @@ -769,27 +732,26 @@ int AlphaEdgeTextureClass::update256(WorldHeightMap *htMap) int AlphaEdgeTextureClass::update(WorldHeightMap *htMap) { - // D3DTexture is our texture; - - IDirect3DSurface8 *surface_level; - D3DSURFACE_DESC surface_desc; - D3DLOCKED_RECT locked_rect; - DX8_ErrorCode(Peek_D3D_Texture()->GetSurfaceLevel(0, &surface_level)); - DX8_ErrorCode(surface_level->LockRect(&locked_rect, nullptr, 0)); - DX8_ErrorCode(surface_level->GetDesc(&surface_desc)); + MutableTextureMipView mip = Begin_Mip_Write(0); + if (!mip.Is_Valid()) + { + return 0; + } + const Int surface_pitch = static_cast(mip.Pitch); + UnsignedByte *surface_bits = mip.Data; Int tilePixelExtent = TILE_PIXEL_EXTENT; // blend tiles are 1/4 tiles. // Int tilesPerRow = surface_desc.Width / (tilePixelExtent+8); // Int numRows = surface_desc.Height/(tilePixelExtent+8); - if (surface_desc.Format == D3DFMT_A8R8G8B8) { + if (mip.Format == WW3D_FORMAT_A8R8G8B8) { #if 1 #if 1 Int cellX, cellY; - for (cellX = 0; (UnsignedInt)cellX < surface_desc.Width; cellX++) { - for (cellY = 0; cellY < surface_desc.Height; cellY++) { - UnsignedByte *pBGR = ((UnsignedByte *)locked_rect.pBits)+(cellY*surface_desc.Width+cellX)*4; + for (cellX = 0; (UnsignedInt)cellX < mip.Width; cellX++) { + for (cellY = 0; cellY < mip.Height; cellY++) { + UnsignedByte *pBGR = surface_bits + cellY * surface_pitch + cellX * 4; pBGR[2] = 255-cellY/2; pBGR[0] = cellX/2; pBGR[3] = cellX/2; // alpha. @@ -811,8 +773,7 @@ int AlphaEdgeTextureClass::update(WorldHeightMap *htMap) Int row = position.y+j; UnsignedByte *pBGR = htMap->getEdgeTile(tileNdx)->getRGBDataForWidth(tilePixelExtent); pBGR += (tilePixelExtent-1-j)*TILE_BYTES_PER_PIXEL*tilePixelExtent; // invert to match. - UnsignedByte *pBGRX = ((UnsignedByte*)locked_rect.pBits) + - (row)*surface_desc.Width*pixelBytes; + UnsignedByte *pBGRX = surface_bits + row * surface_pitch; pBGRX += column*pixelBytes; for (i=0; iUnlockRect(); - surface_level->Release(); - DX8_ErrorCode(D3DXFilterTexture(Peek_D3D_Texture(), nullptr, 0, D3DX_FILTER_BOX)); - return(surface_desc.Height); + End_Mip_Write(0); + Generate_Mip_Levels(); + InvalidateGeneratedTerrainTexture(this); + return(mip.Height); } void AlphaEdgeTextureClass::Apply(unsigned int stage) { // Do the base apply. TextureClass::Apply(stage); -#if 0 // obsolete [4/1/2003] - - if (TheGlobalData && (TheGlobalData->m_bilinearTerrainTex || TheGlobalData->m_trilinearTerrainTex)) { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MAGFILTER, D3DTEXF_POINT); - } - if (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MIPFILTER, D3DTEXF_POINT); - } - - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); - // Now setup the texture pipeline. - if (stage==0) { - - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - 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_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); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - - } else if (stage==1) { - // Drawing texture through the mask. - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAARG1, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG1, D3DTA_CURRENT ); - 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); - - } -#endif } @@ -928,7 +838,7 @@ CloudMapTerrainTextureClass::CloudMapTerrainTextureClass(MipCountType mipLevelCo //============================================================================= // CloudMapTerrainTextureClass::Apply //============================================================================= -/** Sets the texture as the current D3D texture, and does some custom setup. +/** Sets the texture as the current texture, and does some custom setup. The CloudMapTerrainTextureClass may be applied by itself, or with the LightMapTerrainTexture. This may be applied in either single pass, as the first texture in the pipe with LightMapTerrainTextureClass as the @@ -945,82 +855,6 @@ void CloudMapTerrainTextureClass::Apply(unsigned int stage) // Do the base apply. TextureClass::Apply(stage); -#if 0 // obsolete - /* previous setup */ - if (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MIPFILTER, D3DTEXF_POINT); - } - - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - - // Now setup the texture pipeline. - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); - // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); - - - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); - - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); - - D3DXMATRIX inv; - float det; - D3DXMatrixInverse(&inv, &det, &curView); - - D3DXMATRIX scale; - D3DXMatrixScaling(&scale, STRETCH_FACTOR, STRETCH_FACTOR,1); - inv *=scale; - D3DXMATRIX offset; - - Int delta = m_curTick; - m_curTick = ::GetTickCount(); - delta = m_curTick-delta; - m_xOffset += m_xSlidePerSecond*delta/1000; - m_yOffset += m_ySlidePerSecond*delta/1000; - - if (m_xOffset > 1) m_xOffset -= 1; - if (m_yOffset > 1) m_yOffset -= 1; - if (m_xOffset < -1) m_xOffset += 1; - if (m_yOffset < -1) m_yOffset += 1; - - - D3DXMatrixTranslation(&offset, m_xOffset, m_yOffset,0); - - inv *= offset; - - if (stage==0) { - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE0, inv); - - // Disable 3rd stage just in case. - 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( 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); - } 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 ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_MODULATE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG1, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); - - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE1, inv); - } -#endif } //============================================================================= @@ -1031,28 +865,25 @@ understood by w3d. */ //============================================================================= void CloudMapTerrainTextureClass::restore() { - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - 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_DISABLE ); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, 0 ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_MODULATE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - 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_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_DISABLE); + + g_renderBackend->Set_Texture_Address_Mode(0, RB_TEXTURE_ADDRESS_WRAP, RB_TEXTURE_ADDRESS_WRAP, RB_TEXTURE_ADDRESS_WRAP); + SetTerrainTexcoordSource(0, 0); + DisableTerrainTextureTransform(0); + + g_renderBackend->Set_Texture_Color_Argument(1, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(1, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); + + g_renderBackend->Set_Texture_Address_Mode(1, RB_TEXTURE_ADDRESS_WRAP, RB_TEXTURE_ADDRESS_WRAP, RB_TEXTURE_ADDRESS_WRAP); + SetTerrainTexcoordSource(1, 0); + DisableTerrainTextureTransform(1); + 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) @@ -1062,15 +893,15 @@ void CloudMapTerrainTextureClass::restore() //other hardware. Allows single pass blend of 2 textures and post modulate diffuse. Int i; for (i=0; i<8; i++) { - DX8Wrapper::Set_DX8_Texture_Stage_State( i, D3DTSS_COLOROP, D3DTOP_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( i, D3DTSS_TEXCOORDINDEX, i); - DX8Wrapper::Set_DX8_Texture_Stage_State( i, D3DTSS_COLORARG1, D3DTA_TEXTURE); - DX8Wrapper::Set_DX8_Texture_Stage_State( i, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - DX8Wrapper::Set_DX8_Texture_Stage_State( i, D3DTSS_ALPHAOP, D3DTOP_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( i, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); - DX8Wrapper::Set_DX8_Texture_Stage_State( i, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); - - DX8Wrapper::Set_DX8_Texture(i, nullptr); + g_renderBackend->Set_Texture_Color_Operation(i, RB_TEXOP_DISABLE); + SetTerrainTexcoordSource(i, i); + g_renderBackend->Set_Texture_Color_Argument(i, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(i, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Alpha_Argument(i, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Argument(i, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Alpha_Operation(i, RB_TEXOP_DISABLE); + + g_renderBackend->Set_Texture(i, nullptr); } } } @@ -1098,7 +929,7 @@ ScorchTextureClass::ScorchTextureClass(MipCountType mipLevelCount) : //============================================================================= // ScorchTextureClass::Apply //============================================================================= -/** Sets the texture as the current D3D texture, and does some custom setup. +/** Sets the texture as the current texture, and does some custom setup. The ScorchTextureClass is applied by iteself, as it's mesh is a subset of the terrain mesh. (standard D3D setup, but beyond the scope of W3D). */ @@ -1108,35 +939,20 @@ void ScorchTextureClass::Apply(unsigned int stage) // Do the base apply. TextureClass::Apply(stage); // Setup bilinear or trilinear filtering as specified in global data. - if (TheGlobalData && (TheGlobalData->m_bilinearTerrainTex || TheGlobalData->m_trilinearTerrainTex)) { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MAGFILTER, D3DTEXF_POINT); - } - if (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MIPFILTER, D3DTEXF_POINT); - } + ApplyTerrainFilter(stage); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); + DisableTerrainTextureTransform(0); + g_renderBackend->Set_Texture_Address_Mode(0, RB_TEXTURE_ADDRESS_CLAMP, RB_TEXTURE_ADDRESS_CLAMP, RB_TEXTURE_ADDRESS_WRAP); // Now setup the texture pipeline. - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - 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_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); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); -} - + g_renderBackend->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_SELECTARG1); + SetTerrainTexcoordSource(0, 0); + g_renderBackend->Set_Alpha_Blend_Enable(true); + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); +} diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DMouse.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DMouse.cpp index 814bb83cce5..2357146a846 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DMouse.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DMouse.cpp @@ -29,7 +29,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" @@ -56,6 +56,36 @@ static const Image *cursorImages[Mouse::NUM_MOUSE_CURSORS]; /// &mips = texture->Get_CPU_Texture_Mips(); + if (mips.empty()) + return FALSE; + + const TextureBaseClass::TextureMipSnapshot &mip = mips[0]; + const unsigned bytes_per_pixel = Get_Bytes_Per_Pixel(mip.Format); + if (mip.Format == WW3D_FORMAT_UNKNOWN || + mip.Width == 0 || + mip.Height == 0 || + bytes_per_pixel == 0 || + mip.Pitch < mip.Width * bytes_per_pixel || + mip.Data.size() < static_cast(mip.Pitch) * mip.Height) + { + return FALSE; + } + + image.Width = mip.Width; + image.Height = mip.Height; + image.Format = mip.Format; + image.Pitch = mip.Pitch; + image.Bytes = mip.Data; + return TRUE; +} + ///Mouse polling/update thread function static class MouseThreadClass : public ThreadClass { @@ -93,11 +123,11 @@ W3DMouse::W3DMouse() cursorAnims[i]=nullptr; } - m_currentD3DCursor=NONE; + m_currentHardwareCursor=NONE; m_currentW3DCursor=NONE; m_currentPolygonCursor=NONE; m_currentAnimFrame = 0; - m_currentD3DFrame = 0; + m_currentHardwareFrame = 0; m_currentFrames = 0; m_currentFMS= 1.0f/1000.0f; @@ -108,15 +138,17 @@ 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 the legacy device 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); Win32Mouse::setCursor(ARROW); //enable default windows cursor } - freeD3DAssets(); + freeHardwareCursorAssets(); freeW3DAssets(); thread.Stop(); @@ -154,14 +186,14 @@ void W3DMouse::freePolygonAssets() } /**Release the textures required to display the selected cursor*/ -Bool W3DMouse::releaseD3DCursorTextures(MouseCursor cursor) +Bool W3DMouse::releaseHardwareCursorTextures(MouseCursor cursor) { if (cursor == NONE || !cursorTextures[cursor][0]) return TRUE; //no texture for this cursor or texture never loaded for (Int i=0; iGet_Texture(FrameName); - m_currentD3DSurface[0]=cursorTextures[cursor][0]->Get_Surface_Level(); - m_currentFrames = 1; + if (CopyTextureBaseMipToBackendImage(cursorTextures[cursor][0], m_currentHardwareImage[0])) + m_currentFrames = 1; } else for (Int i=0; iGet_Texture(FrameName)) != nullptr) - { m_currentD3DSurface[m_currentFrames]=cursorTextures[cursor][i]->Get_Surface_Level(); + { + if (!CopyTextureBaseMipToBackendImage(cursorTextures[cursor][i], m_currentHardwareImage[m_currentFrames])) + continue; m_currentFrames++; } } return TRUE; } -void W3DMouse::initD3DAssets() +void W3DMouse::initHardwareCursorAssets() { //Nothing to do here unless we want to preload all possible cursors which would //probably not be practical for memory reasons. @@ -234,16 +268,16 @@ void W3DMouse::initD3DAssets() } for (Int x = 0; x < MAX_2D_CURSOR_ANIM_FRAMES; x++) - m_currentD3DSurface[x]=nullptr; + m_currentHardwareImage[x] = RenderBackendImage(); } } -void W3DMouse::freeD3DAssets() +void W3DMouse::freeHardwareCursorAssets() { //free pointers to texture surfaces. Int i=0; for (; iShowCursor(FALSE); //disable DX8 cursor - if (cursor != m_currentD3DCursor) + g_renderBackend->Show_Hardware_Cursor(false); + if (cursor != m_currentHardwareCursor) { if (!isThread) - { releaseD3DCursorTextures(m_currentD3DCursor); - //Since this type of cursor is updated from a non-D3D thread, we need + { releaseHardwareCursorTextures(m_currentHardwareCursor); + // Since this cursor is updated from a non-render thread, we need //to preallocate all surfaces in main thread. - loadD3DCursorTextures(cursor); + loadHardwareCursorTextures(cursor); } } - if (m_currentD3DSurface[0]) + if (m_currentHardwareImage[0].Is_Valid()) doImageChange=TRUE; } - //For DX8 Cursors, we continually set the image on every call even when + // For hardware cursors, we continually set the image on every call even when //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 - m_currentD3DFrame=(Int)m_currentAnimFrame; - m_currentD3DCursor = cursor; + g_renderBackend->Set_Hardware_Cursor_Image( + m_currentHotSpot.x, m_currentHotSpot.y, + m_currentHardwareImage[(Int)m_currentAnimFrame]); + g_renderBackend->Show_Hardware_Cursor(true); + m_currentHardwareFrame=(Int)m_currentAnimFrame; + m_currentHardwareCursor = cursor; m_lastAnimTime=timeGetTime(); } } else if (m_currentRedrawMode == RM_POLYGON) { - SetCursor(nullptr); //Kill Windows Cursor - m_currentD3DCursor=NONE; + ::SetCursor(nullptr); //Kill Windows Cursor + m_currentHardwareCursor=NONE; m_currentW3DCursor=NONE; m_currentPolygonCursor = cursor; m_currentHotSpot = m_cursorInfo[cursor].hotSpotPosition; } else if (m_currentRedrawMode == RM_W3D) { - SetCursor(nullptr); //Kill Windows Cursor - m_currentD3DCursor=NONE; + ::SetCursor(nullptr); //Kill Windows Cursor + m_currentHardwareCursor=NONE; m_currentPolygonCursor=NONE; if (cursor != m_currentW3DCursor) { @@ -481,21 +518,23 @@ void W3DMouse::draw() //make sure the correct cursor image is selected setCursor(m_currentCursor); - if (m_currentRedrawMode == RM_DX8 && m_currentD3DCursor != NONE) + if (m_currentRedrawMode == RM_DX8 && m_currentHardwareCursor != NONE) { - //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 + // 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 the backend where + // to draw the hardware cursor. + if (g_renderBackend != nullptr) + { g_renderBackend->Show_Hardware_Cursor(true); if (TheDisplay && !TheDisplay->getWindowed()) { //if we're full-screen, need to manually move cursor image POINT ptCursor; - GetCursorPos( &ptCursor ); - ScreenToClient( ApplicationHWnd, &ptCursor ); - m_pDev->SetCursorPosition( ptCursor.x, ptCursor.y, D3DCURSOR_IMMEDIATE_UPDATE); + ::GetCursorPos( &ptCursor ); + ::ScreenToClient( ApplicationHWnd, &ptCursor ); + g_renderBackend->Set_Hardware_Cursor_Position(ptCursor.x, ptCursor.y); } //Check if animated cursor and new frame if (m_currentFrames > 1) @@ -505,10 +544,12 @@ void W3DMouse::draw() m_currentAnimFrame=fmod(m_currentAnimFrame,m_currentFrames); m_lastAnimTime=msTime; - if ((Int)m_currentAnimFrame != m_currentD3DFrame) + if ((Int)m_currentAnimFrame != m_currentHardwareFrame) { - m_currentD3DFrame=(Int)m_currentAnimFrame; - m_pDev->SetCursorProperties(m_currentHotSpot.x,m_currentHotSpot.y,m_currentD3DSurface[m_currentD3DFrame]->Peek_D3D_Surface()); + m_currentHardwareFrame=(Int)m_currentAnimFrame; + g_renderBackend->Set_Hardware_Cursor_Image( + m_currentHotSpot.x, m_currentHotSpot.y, + m_currentHardwareImage[m_currentHardwareFrame]); } } } @@ -568,7 +609,7 @@ void W3DMouse::draw() offset = TheInGameUI->getScrollAmount(); offset.normalize(); Real theta = atan2(-offset.y, offset.x); - theta -= (Real)M_PI/2; + theta -= (Real)WWMATH_HALF_PI; tm.Rotate_Z(theta); } cursorModels[m_currentW3DCursor]->Set_Transform(tm); @@ -578,8 +619,8 @@ void W3DMouse::draw() } } - //@todo: In DX8 mode the mouse is drawn in another thread which isn't allowed - //access to D3D so we can't do any drawing here. + //@todo: In hardware cursor mode the mouse is drawn in another thread, so + //we can't do any rendering here. // draw the cursor text if (!isThread) drawCursorText(); @@ -606,10 +647,10 @@ void W3DMouse::setRedrawMode(RedrawMode mode) { //Windows mouse doesn't need an update thread. if (thread.Is_Running()) thread.Stop(); - freeD3DAssets(); //using Windows resources + freeHardwareCursorAssets(); //using Windows resources freeW3DAssets(); freePolygonAssets(); - m_currentD3DCursor = NONE; + m_currentHardwareCursor = NONE; m_currentW3DCursor = NONE; m_currentPolygonCursor = NONE; } @@ -620,9 +661,9 @@ void W3DMouse::setRedrawMode(RedrawMode mode) //require thread. if (thread.Is_Running()) thread.Stop(); - freeD3DAssets(); //using packed Image data, not textures. + freeHardwareCursorAssets(); //using packed Image data, not textures. freePolygonAssets(); - m_currentD3DCursor = NONE; + m_currentHardwareCursor = NONE; m_currentPolygonCursor = NONE; initW3DAssets(); } @@ -633,9 +674,9 @@ void W3DMouse::setRedrawMode(RedrawMode mode) //require thread. if (thread.Is_Running()) thread.Stop(); - freeD3DAssets(); //using packed Image data, not textures. + freeHardwareCursorAssets(); //using packed Image data, not textures. freeW3DAssets(); - m_currentD3DCursor = NONE; + m_currentHardwareCursor = NONE; m_currentW3DCursor = NONE; m_currentPolygonCursor = NONE; initPolygonAssets(); @@ -643,10 +684,10 @@ void W3DMouse::setRedrawMode(RedrawMode mode) break; case RM_DX8: - { //this cursor type is drawn by DX8 and can be refreshed + { //this cursor type is drawn by the backend and can be refreshed //independent of rendering rate. Uses another thread to do //position updates. - initD3DAssets(); //make sure textures loaded. + initHardwareCursorAssets(); //make sure textures loaded. freeW3DAssets(); freePolygonAssets(); if (!thread.Is_Running()) @@ -672,12 +713,12 @@ void W3DMouse::setCursorDirection(MouseCursor cursor) { offset.normalize(); Real theta = atan2(offset.y, offset.x); - theta = fmod(theta+M_PI*2,M_PI*2); + theta = fmod(theta+WWMATH_TWO_PI,WWMATH_TWO_PI); Int numDirections=m_cursorInfo[m_currentCursor].numDirections; //Figure out which of our predrawn cursor orientations best matches the //actual cursor direction. Frame 0 is assumed to point right and continue //clockwise. - m_directionFrame=(Int)(theta/(2.0f*M_PI/(Real)numDirections)+0.5f); + m_directionFrame=(Int)(theta/(WWMATH_TWO_PI/(Real)numDirections)+0.5f); if (m_directionFrame >= numDirections) m_directionFrame = 0; } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp index b1b792b3ac9..ff9b3282e77 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp @@ -28,14 +28,64 @@ #include "Common/GlobalData.h" #include "GameClient/Color.h" +#include "GameLogic/TerrainLogic.h" #include "W3DDevice/GameClient/W3DParticleSys.h" #include "W3DDevice/GameClient/W3DAssetManager.h" #include "W3DDevice/GameClient/W3DDisplay.h" #include "W3DDevice/GameClient/HeightMap.h" #include "W3DDevice/GameClient/W3DSmudge.h" #include "W3DDevice/GameClient/W3DSnow.h" +#include "WW3D2/BgfxRenderProfile.h" +#include "W3DDevice/GameClient/W3DWater.h" #include "WW3D2/camera.h" +#include "WW3D2/RenderBackend.h" +#include "GgcRuntimeFlags.h" +#include +#include +#include + +// The shader pipeline renders these authored hull-contact foam systems smaller and dimmer than +// the retail DX8 path. Keep the empirical compensation restricted to the known wake templates; +// ground-aligned additive particles are also used by explosions and superweapon ground glows. +static const float BGFX_WATER_WAKE_SIZE_BOOST = 2.0f; +static const float BGFX_WATER_WAKE_COLOR_BOOST = 1.5f; + +static bool needsBgfxWaterWakeCompensation(ParticleSystem *sys) +{ + const ParticleSystemTemplate *particleTemplate = sys != nullptr ? sys->getTemplate() : nullptr; + if (particleTemplate == nullptr) + { + return false; + } + + const AsciiString name = particleTemplate->getName(); + return name.compareNoCase("BattleShipWaterRipples") == 0 + || name.compareNoCase("AirCarrierWaterRipples") == 0 + || name.compareNoCase("AmphibWaveRest") == 0; +} + +static ShaderClass getParticlePointGroupShader(ParticleSystemInfo::ParticleShaderType shaderType) +{ + ShaderClass shader = ShaderClass::_PresetAlphaSpriteShader; + switch( shaderType ) + { + case ParticleSystemInfo::ADDITIVE: + shader = ShaderClass::_PresetAdditiveSpriteShader; + break; + case ParticleSystemInfo::ALPHA: + shader = ShaderClass::_PresetAlphaSpriteShader; + break; + case ParticleSystemInfo::ALPHA_TEST: + shader = ShaderClass::_PresetATestSpriteShader; + break; + case ParticleSystemInfo::MULTIPLY: + shader = ShaderClass::_PresetMultiplicativeSpriteShader; + break; + } + + return shader; +} //------------------------------------------------------------------------------ Performance Timers //#include "Common/PerfMetrics.h" @@ -102,11 +152,42 @@ void DoParticles( RenderInfoClass &rinfo ) TheParticleSystemManager->doParticles(rinfo); } +// TheSuperHackers @perf bobtista 03/06/2026 Render the accumulated [0,count) range of the shared +// scratch buffers as a single point-group draw. The buffers already hold world-space vertices from +// one or more emitters that share the bucket key, so no per-emitter transform is needed. +void W3DParticleSystemManager::flushPointGroupBatch(RenderInfoClass &rinfo, TextureClass *texture, + ParticleSystemInfo::ParticleShaderType shaderType, Bool billboard, UnsignedInt volumeDepth, Int count) +{ + if (count == 0 || texture == nullptr || m_pointGroup == nullptr) + { + return; + } + + m_pointGroup->Set_Texture( texture ); + m_pointGroup->Set_Flag( PointGroupClass::TRANSFORM, true ); // transform to screen space + m_pointGroup->Set_Shader( getParticlePointGroupShader(shaderType) ); + + m_pointGroup->Set_Point_Mode( PointGroupClass::QUADS ); + m_pointGroup->Set_Arrays( m_posBuffer, m_RGBABuffer, nullptr, m_sizeBuffer, m_angleBuffer, nullptr, count ); + m_pointGroup->Set_Billboard( billboard ); + m_pointGroup->Set_Point_Frame( 0 ); + + if( volumeDepth > 1 ) + { + m_pointGroup->RenderVolumeParticle( rinfo, volumeDepth ); + } + else + m_pointGroup->Render( rinfo ); +} + void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) { + const bool particleDiag = GgcFlags::Enabled(GgcFlag_ParticleDiag); if (m_readyToRender == false) + { return; + } // external mechanism must tell us when it's OK to render again... m_readyToRender = false; @@ -144,6 +225,19 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) TheSmudgeManager->resetDraw(); } + // TheSuperHackers @perf bobtista 03/06/2026 Only the shader-pipeline backend batches emitter draws. + // The fixed-function DX8 reference path keeps batchBase==0 so its buffer indexing, particle cap and + // per-emitter submission stay bit-for-bit identical to the original code. + // TheSuperHackers @perf bobtista 03/06/2026 GGC_NO_PARTICLE_BATCH=1 forces the + // per-emitter path for A/B visual verification and as a runtime safety escape. + static const bool s_particleBatchDisabled = GgcFlags::Enabled(GgcFlag_NoParticleBatch); + const bool batchPointGroups = (!s_particleBatchDisabled && g_renderBackend != nullptr && g_renderBackend->Has_Shader_Pipeline()); + Int batchBase = 0; + TextureClass *batchTexture = nullptr; + ParticleSystemInfo::ParticleShaderType batchShader = ParticleSystemInfo::INVALID_SHADER; + Bool batchBillboard = FALSE; + UnsignedInt batchVolumeDepth = 0; + ParticleSystemManager::ParticleSystemList &particleSysList = TheParticleSystemManager->getAllParticleSystems(); for( ParticleSystemManager::ParticleSystemListIt it = particleSysList.begin(); it != particleSysList.end(); ++it) { @@ -154,7 +248,9 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) // only look at particle/point style systems if (sys->isUsingDrawables()) + { continue; + } //temporary hack that checks if texture name starts with "SMUD" - if so, we can assume it's a smudge type if (/*sys->isUsingSmudge()*/ *((DWORD *)sys->getParticleTypeName().str()) == 0x44554D53) @@ -167,13 +263,13 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) Real psize = p->getSize(); //Cull particle to edges of screen and terrain. - if (WWMath::Fabs( pos->x - bcX ) > ( beX + psize ) ) + if (WWMath::Fabsf_Legacy( pos->x - bcX ) > ( beX + psize ) ) continue; - if (WWMath::Fabs( pos->y - bcY ) > ( beY + psize ) ) + if (WWMath::Fabsf_Legacy( pos->y - bcY ) > ( beY + psize ) ) continue; - if (WWMath::Fabs( pos->z - bcZ ) > ( beZ + psize ) ) + if (WWMath::Fabsf_Legacy( pos->z - bcZ ) > ( beZ + psize ) ) continue; if (Smudge *smudge = TheSmudgeManager->findSmudge(p)) @@ -190,10 +286,42 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) // initialize them here still, of course // build W3D particle buffer Int count = 0; - Vector3 *posArray = m_posBuffer->Get_Array(); - Real *sizeArray = m_sizeBuffer->Get_Array(); - Vector4 *RGBAArray = m_RGBABuffer->Get_Array(); - uint8 *angleArray = m_angleBuffer->Get_Array(); + TextureClass *texture = nullptr; + + // TheSuperHackers @perf bobtista 03/06/2026 In batch mode resolve this emitter's bucket key up + // front so we can flush the open batch (and reset the accumulation base) before appending into + // the shared scratch buffers. Streak emitters never batch; flush before handing them off below. + if (batchPointGroups) + { + { + GGC_RPROFILE(PARTICLE_TEX_FETCH); + texture = W3DDisplay::m_assetManager->Get_Texture( sys->getParticleTypeName().str() ); + } + const Bool isStreak = (m_streakLine != nullptr && sys->isUsingStreak()); + // TheSuperHackers @bugfix bobtista 03/06/2026 Volume particles must NOT batch: + // RenderVolumeParticle derives its per-layer shift from current_size[0] (the + // first particle's size, pointgr.cpp:1880), so merging multiple volume emitters + // applies the first emitter's size to every layer of every merged particle, + // smearing volumetric blobs across the screen (over-bright clouds). Treat them + // like streaks: flush the open batch and render this emitter per-emitter. + const Bool isVolume = (sys->getVolumeParticleDepth() > 1); + const Bool keyMatches = (batchTexture == texture + && batchShader == sys->getShaderType() + && batchBillboard == sys->shouldBillboard() + && batchVolumeDepth == sys->getVolumeParticleDepth()); + if (batchBase > 0 && (isStreak || isVolume || !keyMatches)) + { + flushPointGroupBatch( rinfo, batchTexture, batchShader, batchBillboard, batchVolumeDepth, batchBase ); + batchTexture->Release_Ref(); + batchTexture = nullptr; + batchBase = 0; + } + } + + Vector3 *posArray = m_posBuffer->Get_Array() + batchBase; + Real *sizeArray = m_sizeBuffer->Get_Array() + batchBase; + Vector4 *RGBAArray = m_RGBABuffer->Get_Array() + batchBase; + uint8 *angleArray = m_angleBuffer->Get_Array() + batchBase; const Coord3D *pos; const RGBColor *color; Real psize; @@ -207,13 +335,13 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) psize = p->getSize(); //Cull particle to edges of screen and terrain. - if (WWMath::Fabs(pos->x - bcX) > (beX + psize)) + if (WWMath::Fabsf_Legacy(pos->x - bcX) > (beX + psize)) continue; - if (WWMath::Fabs(pos->y - bcY) > (beY + psize)) + if (WWMath::Fabsf_Legacy(pos->y - bcY) > (beY + psize)) continue; - if (WWMath::Fabs(pos->z - bcZ) > (beZ + psize)) + if (WWMath::Fabsf_Legacy(pos->z - bcZ) > (beZ + psize)) continue; m_fieldParticleCount += ( sys->getPriority() == AREA_EFFECT && sys->m_isGroundAligned != FALSE ); @@ -231,20 +359,137 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) RGBAArray[count].X = color->red; RGBAArray[count].Y = color->green; RGBAArray[count].Z = color->blue; - RGBAArray[count].W = p->getAlpha(); + + if (batchPointGroups && needsBgfxWaterWakeCompensation(sys)) + { + sizeArray[count] *= BGFX_WATER_WAKE_SIZE_BOOST; + RGBAArray[count].X = MIN(1.0f, color->red * BGFX_WATER_WAKE_COLOR_BOOST); + RGBAArray[count].Y = MIN(1.0f, color->green * BGFX_WATER_WAKE_COLOR_BOOST); + RGBAArray[count].Z = MIN(1.0f, color->blue * BGFX_WATER_WAKE_COLOR_BOOST); + } + + // TheSuperHackers @bugfix bobtista 27/05/2026 ADDITIVE particles keep m_alpha at the + // initial keyframe (often 0) and fs_uber's alpha-aware paths would discard them; force + // vertex alpha to 1.0, gated on batchPointGroups so DX8 keeps per-particle alpha. + if (batchPointGroups && sys->getShaderType() == ParticleSystemInfo::ADDITIVE) + { + RGBAArray[count].W = 1.0f; + } + else + { + RGBAArray[count].W = p->getAlpha(); + } angleArray[count] = (uint8)(p->getAngle() * 255.0f / (2.0f * PI)); - if (++count == MAX_POINTS_PER_GROUP) + if (batchBase + (++count) == MAX_POINTS_PER_GROUP) + { + // TheSuperHackers @perf bobtista 03/06/2026 The shared buffer filled while a same-key + // batch was already accumulated in front of this emitter. Flush that prior batch, then + // shift this emitter's particles to the front and keep filling from offset 0 so no + // particle is dropped (the DX8 path keeps batchBase==0 and simply breaks as before). + if (batchPointGroups && batchBase > 0) + { + flushPointGroupBatch( rinfo, batchTexture, batchShader, batchBillboard, batchVolumeDepth, batchBase ); + batchTexture->Release_Ref(); + batchTexture = nullptr; + std::memmove( m_posBuffer->Get_Array(), posArray, count * sizeof(Vector3) ); + std::memmove( m_sizeBuffer->Get_Array(), sizeArray, count * sizeof(Real) ); + std::memmove( m_RGBABuffer->Get_Array(), RGBAArray, count * sizeof(Vector4) ); + std::memmove( m_angleBuffer->Get_Array(), angleArray, count * sizeof(uint8) ); + batchBase = 0; + posArray = m_posBuffer->Get_Array(); + sizeArray = m_sizeBuffer->Get_Array(); + RGBAArray = m_RGBABuffer->Get_Array(); + angleArray = m_angleBuffer->Get_Array(); + continue; + } break; + } } - if ( count == 0 ) - continue; //this system has no particles to render + if ( count == 0 ) + { + // TheSuperHackers @perf bobtista 03/06/2026 In batch mode the bucket texture was acquired + // up front; release that reference when this emitter contributes nothing. + if (batchPointGroups && texture != nullptr) + { + texture->Release_Ref(); + } + continue; //this system has no particles to render + } - TextureClass *texture = W3DDisplay::m_assetManager->Get_Texture( sys->getParticleTypeName().str() ); + if (!batchPointGroups) + { + GGC_RPROFILE(PARTICLE_TEX_FETCH); + texture = W3DDisplay::m_assetManager->Get_Texture( sys->getParticleTypeName().str() ); + } + if (particleDiag) + { + if (FILE *diag = std::fopen("ggc_particle_diag.txt", "a")) + { + const Coord3D *firstPos = sys->getFirstParticle() != nullptr + ? sys->getFirstParticle()->getPosition() + : nullptr; + float minSz = (count > 0) ? sizeArray[0] : 0.0f; + float maxSz = minSz; + float minA = (count > 0) ? RGBAArray[0].W : 0.0f; + float maxA = minA; + for (Int idx = 1; idx < count; ++idx) { + if (sizeArray[idx] < minSz) { + minSz = sizeArray[idx]; + } + if (sizeArray[idx] > maxSz) { + maxSz = sizeArray[idx]; + } + if (RGBAArray[idx].W < minA) { + minA = RGBAArray[idx].W; + } + if (RGBAArray[idx].W > maxA) { + maxA = RGBAArray[idx].W; + } + } + Real waterZ = 0.0f; + Real terrainZ = 0.0f; + Bool underwater = FALSE; + if (TheWaterRenderObj != nullptr && firstPos != nullptr) { + waterZ = TheWaterRenderObj->getWaterHeight(firstPos->x, firstPos->y); + } + if (TheTerrainLogic != nullptr && firstPos != nullptr) { + Real tw = 0.0f, tt = 0.0f; + underwater = TheTerrainLogic->isUnderwater(firstPos->x, firstPos->y, &tw, &tt); + terrainZ = tt; + } + std::fprintf(diag, + "particle frame=%u type=%s texture=%s count=%d shader=%d streak=%d volume=%u billboard=%d ground=%d first=(%.2f,%.2f,%.2f) waterZ=%.2f terrainZ=%.2f under=%d sizeRange=[%.2f..%.2f] alphaRange=[%.3f..%.3f] firstRGB=(%.2f,%.2f,%.2f) texMissing=%d\n", + 0u, + sys->getParticleTypeName().str(), + texture != nullptr ? texture->Get_Full_Path().str() : "", + count, + static_cast(sys->getShaderType()), + sys->isUsingStreak() ? 1 : 0, + static_cast(sys->getVolumeParticleDepth()), + sys->shouldBillboard() ? 1 : 0, + sys->m_isGroundAligned ? 1 : 0, + firstPos != nullptr ? firstPos->x : 0.0f, + firstPos != nullptr ? firstPos->y : 0.0f, + firstPos != nullptr ? firstPos->z : 0.0f, + waterZ, + terrainZ, + (int)(underwater ? 1 : 0), + minSz, + maxSz, + minA, + maxA, + (count > 0) ? RGBAArray[0].X : 0.0f, + (count > 0) ? RGBAArray[0].Y : 0.0f, + (count > 0) ? RGBAArray[0].Z : 0.0f, + texture != nullptr && texture->Is_Missing_Texture() ? 1 : 0); + std::fclose(diag); + } + } - if ( m_streakLine && sys->isUsingStreak() && (count >= 2) ) + if ( m_streakLine && sys->isUsingStreak() && (count >= 2) ) { m_streakLine->Reset_Line(); @@ -288,6 +533,24 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) m_streakLine->Render( rinfo ); } + else if (batchPointGroups && sys->getVolumeParticleDepth() <= 1) + { + // TheSuperHackers @perf bobtista 03/06/2026 Accumulate this emitter into the open bucket + // instead of issuing a draw. The actual submission happens in flushPointGroupBatch when the + // next emitter changes the bucket key, the buffer overflows, or the loop ends. + if (batchBase == 0) + { + batchTexture = texture; // take ownership of the reference acquired for the bucket key + } + else + { + texture->Release_Ref(); // same bucket: drop the duplicate reference, batchTexture holds one + } + batchShader = sys->getShaderType(); + batchBillboard = sys->shouldBillboard(); + batchVolumeDepth = sys->getVolumeParticleDepth(); + batchBase += count; + } else { @@ -300,21 +563,7 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) texture->Release_Ref();//release reference since it's held by pointGroup m_pointGroup->Set_Flag( PointGroupClass::TRANSFORM, true ); // transform to screen space - switch( sys->getShaderType() ) - { - case ParticleSystemInfo::ADDITIVE: - m_pointGroup->Set_Shader( ShaderClass::_PresetAdditiveSpriteShader ); - break; - case ParticleSystemInfo::ALPHA: - m_pointGroup->Set_Shader( ShaderClass::_PresetAlphaSpriteShader ); - break; - case ParticleSystemInfo::ALPHA_TEST: - m_pointGroup->Set_Shader( ShaderClass::_PresetATestSpriteShader ); - break; - case ParticleSystemInfo::MULTIPLY: - m_pointGroup->Set_Shader( ShaderClass::_PresetMultiplicativeSpriteShader ); - break; - } + m_pointGroup->Set_Shader( getParticlePointGroupShader(sys->getShaderType()) ); /// @todo Use both QUADS and TRIS for particles m_pointGroup->Set_Point_Mode( PointGroupClass::QUADS ); @@ -359,6 +608,15 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) */ + } + + // TheSuperHackers @perf bobtista 03/06/2026 Submit whatever remains in the open point-group bucket. + if (batchPointGroups && batchBase > 0) + { + flushPointGroupBatch( rinfo, batchTexture, batchShader, batchBillboard, batchVolumeDepth, batchBase ); + batchTexture->Release_Ref(); + batchTexture = nullptr; + batchBase = 0; } /// @todo lorenzen sez: this should be debug only: diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DProfilerFrameCapture.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DProfilerFrameCapture.cpp index 90a8d931982..b34089cf6ec 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DProfilerFrameCapture.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DProfilerFrameCapture.cpp @@ -20,14 +20,9 @@ #include "../../../Include/W3DDevice/GameClient/W3DProfilerFrameCapture.h" -#include "WW3D2/dx8wrapper.h" -#include "WW3D2/surfaceclass.h" -#include "WW3D2/texture.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/ww3d.h" -#include "WW3D2/ww3dformat.h" -#include "WWMath/wwmath.h" -#include -#include W3DProfilerFrameCapture::W3DProfilerFrameCapture() { @@ -35,11 +30,6 @@ W3DProfilerFrameCapture::W3DProfilerFrameCapture() W3DProfilerFrameCapture::~W3DProfilerFrameCapture() { - if (m_swizzleShader) - { - DX8Wrapper::_Get_D3D_Device8()->DeletePixelShader(m_swizzleShader); - m_swizzleShader = 0; - } } bool W3DProfilerFrameCapture::ShouldReuseLastCapture(UnsignedInt currentTimeMs) const @@ -62,194 +52,29 @@ void W3DProfilerFrameCapture::Capture(UnsignedInt displayWidth, UnsignedInt disp return; } - // compile swizzle shader convert BGRA to RGBA - // TheSuperHackers @todo In DX9 with ps2.0 this shader will be much simpler - if (!m_swizzleShader) - { - ID3DXBuffer *compiledShader = nullptr; - const char *shader = - "ps.1.4\n" - "texld r0, t0\n" - "mov r1.a, r0.r\n" - "mov r2.a, r0.g\n" - "mov r3.a, r0.b\n" - "mul r0.rgb, r3.a, c0\n" - "mad r0.rgb, r2.a, c1, r0\n" - "mad r0.rgb, r1.a, c2, r0\n"; - - HRESULT hr = D3DXAssembleShader(shader, strlen(shader), 0, nullptr, &compiledShader, nullptr); - if (FAILED(hr)) - return; - - hr = DX8Wrapper::_Get_D3D_Device8()->CreatePixelShader((DWORD *)compiledShader->GetBufferPointer(), &m_swizzleShader); - compiledShader->Release(); - - if (FAILED(hr)) - return; - } - - // allocate render target - TextureClass *renderTarget = DX8Wrapper::Create_Render_Target(PROFILER_FRAME_IMAGE_SIZE, PROFILER_FRAME_IMAGE_SIZE, WW3D_FORMAT_A8R8G8B8); - if (!renderTarget) - return; - - // allocate surface class - const Real aspectRatio = (Real)displayHeight / (Real)displayWidth; - unsigned int profilerImageHeight = min((int)WWMath::Round(PROFILER_FRAME_IMAGE_SIZE * aspectRatio), PROFILER_FRAME_IMAGE_SIZE); - SurfaceClass *surfaceClass = NEW_REF(SurfaceClass, (PROFILER_FRAME_IMAGE_SIZE, profilerImageHeight, WW3D_FORMAT_A8R8G8B8)); - if (!surfaceClass) - { - REF_PTR_RELEASE(renderTarget); - return; - } - - // get the backbuffer - SurfaceClass *backBuffer = DX8Wrapper::_Get_DX8_Back_Buffer(); - if (!backBuffer) - { - REF_PTR_RELEASE(surfaceClass); - REF_PTR_RELEASE(renderTarget); - return; - } - - IDirect3DSurface8 *backBufferSurface = backBuffer->Peek_D3D_Surface(); - D3DSURFACE_DESC backBufferSurfaceDesc; - HRESULT hr = backBufferSurface->GetDesc(&backBufferSurfaceDesc); - if (FAILED(hr)) - { - REF_PTR_RELEASE(backBuffer); - REF_PTR_RELEASE(surfaceClass); - REF_PTR_RELEASE(renderTarget); + if (g_renderBackend == nullptr) return; - } - // allocate intermediate texture - IDirect3DTexture8 *intermediateTexture = nullptr; - hr = DX8Wrapper::_Get_D3D_Device8()->CreateTexture( - backBufferSurfaceDesc.Width, - backBufferSurfaceDesc.Height, - 1, - D3DUSAGE_RENDERTARGET, - backBufferSurfaceDesc.Format, - D3DPOOL_DEFAULT, - &intermediateTexture); - if (FAILED(hr)) + m_lastCapturePixels.resize(PROFILER_FRAME_IMAGE_SIZE * PROFILER_FRAME_IMAGE_SIZE * 4); + UnsignedInt capturedWidth = 0; + UnsignedInt capturedHeight = 0; + if (!g_renderBackend->Capture_Back_Buffer_RGBA( + displayWidth, + displayHeight, + PROFILER_FRAME_IMAGE_SIZE, + m_lastCapturePixels.data(), + static_cast(m_lastCapturePixels.size()), + &capturedWidth, + &capturedHeight)) { - REF_PTR_RELEASE(backBuffer); - REF_PTR_RELEASE(surfaceClass); - REF_PTR_RELEASE(renderTarget); + m_lastCapturePixels.clear(); return; } - // draw backbuffer to intermediate texture - IDirect3DSurface8 *intermediateTextureSurface; - hr = intermediateTexture->GetSurfaceLevel(0, &intermediateTextureSurface); - if (FAILED(hr)) - { - REF_PTR_RELEASE(backBuffer); - REF_PTR_RELEASE(surfaceClass); - REF_PTR_RELEASE(renderTarget); - intermediateTexture->Release(); - return; - } - DX8Wrapper::_Copy_DX8_Rects(backBufferSurface, nullptr, 0, intermediateTextureSurface, nullptr); - intermediateTextureSurface->Release(); - intermediateTextureSurface = nullptr; - - // release the backbuffer - backBufferSurface = nullptr; - REF_PTR_RELEASE(backBuffer); - - // set render target to a small surface - IDirect3DSurface8 *smallRenderTargetSurface = renderTarget->Get_D3D_Surface_Level(); - WWASSERT(smallRenderTargetSurface != nullptr); - DX8Wrapper::Set_Render_Target(smallRenderTargetSurface, false); - - // set viewport - IDirect3DDevice8 *device = DX8Wrapper::_Get_D3D_Device8(); - D3DVIEWPORT8 restoreViewport; - device->GetViewport(&restoreViewport); - - SurfaceClass::SurfaceDescription smallRenderDesc; - surfaceClass->Get_Description(smallRenderDesc); - - D3DVIEWPORT8 viewport; - viewport.X = 0; - viewport.Y = 0; - viewport.Width = PROFILER_FRAME_IMAGE_SIZE; - viewport.Height = smallRenderDesc.Height; - viewport.MinZ = 0.0f; - viewport.MaxZ = 1.0f; - DX8Wrapper::Set_Viewport(&viewport); - - // bind swizzle shader - DX8Wrapper::Set_Pixel_Shader(m_swizzleShader); - static const Real kMaskR[4] = {1.0f, 0.0f, 0.0f, 0.0f}; - static const Real kMaskG[4] = {0.0f, 1.0f, 0.0f, 0.0f}; - static const Real kMaskB[4] = {0.0f, 0.0f, 1.0f, 0.0f}; - device->SetPixelShaderConstant(0, kMaskR, 1); - device->SetPixelShaderConstant(1, kMaskG, 1); - device->SetPixelShaderConstant(2, kMaskB, 1); - - // draw texture scaled-down onto a small surface - struct QuadVertex - { - Real x, y, z, rhw; - Real u, v; - } vtx[4]; - const Real left = -0.5f; - const Real top = -0.5f; - const Real right = (Real)PROFILER_FRAME_IMAGE_SIZE - 0.5f; - const Real bottom = (Real)smallRenderDesc.Height - 0.5f; - vtx[0] = {right, bottom, 0.0f, 1.0f, 1.0f, 1.0f}; - vtx[1] = {right, top, 0.0f, 1.0f, 1.0f, 0.0f}; - vtx[2] = {left, bottom, 0.0f, 1.0f, 0.0f, 1.0f}; - vtx[3] = {left, top, 0.0f, 1.0f, 0.0f, 0.0f}; - DX8Wrapper::Set_DX8_Texture(0, intermediateTexture); - DX8Wrapper::Set_Vertex_Shader(D3DFVF_XYZRHW | D3DFVF_TEX1); - device->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, vtx, sizeof(QuadVertex)); - DX8Wrapper::Set_Pixel_Shader(0); - DX8Wrapper::Set_DX8_Texture(0, nullptr); - DX8Wrapper::Set_Viewport(&restoreViewport); - DX8Wrapper::Set_Render_Target(static_cast(nullptr)); - - // copy the small surface pixels from GPU to CPU - RECT srcRect = { 0, 0, PROFILER_FRAME_IMAGE_SIZE, smallRenderDesc.Height }; - POINT dstPoint = { 0, 0 }; - DX8Wrapper::_Copy_DX8_Rects( - smallRenderTargetSurface, - &srcRect, - 1, - surfaceClass->Peek_D3D_Surface(), - &dstPoint); - smallRenderTargetSurface->Release(); - - // send pixels to the profiler backend - int pitch = 0; - void *bits = surfaceClass->Lock(&pitch); - if (bits) - { - const size_t rowBytes = (size_t)PROFILER_FRAME_IMAGE_SIZE * 4; - m_lastCaptureHeight = smallRenderDesc.Height; - m_lastCapturePixels.resize(rowBytes * m_lastCaptureHeight); - - const UnsignedByte *source = static_cast(bits); - UnsignedByte *destination = m_lastCapturePixels.data(); - for (UnsignedInt row = 0; row < m_lastCaptureHeight; ++row) - { - std::memcpy(destination + row * rowBytes, source + row * pitch, rowBytes); - } - - PROFILER_FRAME_IMAGE(m_lastCapturePixels.data(), PROFILER_FRAME_IMAGE_SIZE, m_lastCaptureHeight, 0, false); - surfaceClass->Unlock(); - m_lastCaptureTimeMs = currentTimeMs; - } - - // cleanup - intermediateTexture->Release(); - intermediateTexture = nullptr; - REF_PTR_RELEASE(surfaceClass); - REF_PTR_RELEASE(renderTarget); + m_lastCaptureHeight = capturedHeight; + m_lastCapturePixels.resize(static_cast(capturedWidth) * capturedHeight * 4); + PROFILER_FRAME_IMAGE(m_lastCapturePixels.data(), capturedWidth, capturedHeight, 0, false); + m_lastCaptureTimeMs = currentTimeMs; } #endif // PROFILER_ENABLED diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DPropBuffer.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DPropBuffer.cpp index 089908ba5d8..56ddf7dac6a 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DPropBuffer.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DPropBuffer.cpp @@ -57,8 +57,7 @@ #include "WW3D2/camera.h" #include "WW3D2/rinfo.h" #include "WW3D2/light.h" -#include "WW3D2/dx8wrapper.h" -#include "WW3D2/dx8renderer.h" +#include "WW3D2/lightenvironment.h" #include "W3DDevice/GameClient/Module/W3DPropDraw.h" #include "W3DDevice/GameClient/W3DShroud.h" #include "W3DDevice/GameClient/BaseHeightMap.h" @@ -417,4 +416,3 @@ void W3DPropBuffer::loadPostProcess() { // empty. jba [8/11/2003] } - diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp index 0d0d851eb63..6cdcb8a2783 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp @@ -20,8 +20,11 @@ #include "Common/GlobalData.h" #include "GameClient/GameText.h" #include "GameClient/InGameUI.h" +#include "WW3D2/RenderBackend.h" +#if !defined(GGC_RENDER_BACKEND_BGFX) #include "WW3D2/dx8wrapper.h" #include "WW3D2/surfaceclass.h" +#endif #include "WWLib/mpsc_intrusive_queue.h" #include @@ -166,6 +169,29 @@ void W3D_TakeCompressedScreenshot(ScreenshotFormat format, Int jpegQuality) sprintf(leafname, "sshot_%04d%02d%02d_%02d%02d%02d_%03d.%s", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, extension); +#if defined(GGC_RENDER_BACKEND_BGFX) + // bgfx performs the back-buffer readback asynchronously and currently exposes PNG as its + // native compressed screenshot format. Keep F12 functional when it requests the legacy JPEG + // format by emitting PNG instead; Ctrl+F12 already requests PNG explicitly. + char pathname[_MAX_PATH]; + strlcpy(pathname, TheGlobalData->getPath_UserData().str(), ARRAY_SIZE(pathname)); + strlcat(pathname, "Screenshots\\", ARRAY_SIZE(pathname)); + CreateDirectory(pathname, nullptr); + + char* extensionStart = strrchr(leafname, '.'); + if (extensionStart != nullptr) + { + strlcpy(extensionStart + 1, "png", ARRAY_SIZE(leafname) - (extensionStart + 1 - leafname)); + } + strlcat(pathname, leafname, ARRAY_SIZE(pathname)); + + if (g_renderBackend != nullptr && g_renderBackend->Request_Native_Screen_Shot(pathname)) + { + UnicodeString ufileName; + ufileName.translate(leafname); + TheInGameUI->message(TheGameText->fetch("GUI:ScreenCapture"), ufileName.str()); + } +#else // TheSuperHackers @bugfix xezon 21/05/2025 Get the back buffer and create a copy of the surface. // Originally this code took the front buffer and tried to lock it. This does not work when the // render view clips outside the desktop boundaries. It crashed the game. @@ -233,4 +259,5 @@ void W3D_TakeCompressedScreenshot(ScreenshotFormat format, Int jpegQuality) { delete threadData; } +#endif } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp index e55fee5bde9..b369cd28be1 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp @@ -53,7 +53,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" @@ -70,13 +71,212 @@ #include "GameLogic/GameLogic.h" #include "Common/GlobalData.h" #include "Common/GameLOD.h" -#include "d3dx8tex.h" -#include "WW3D2/dx8caps.h" +#include "WWLib/cpudetect.h" // 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. Custom shader passes draw immediately after +// setup, without an Apply_Render_State_Changes step, so the bind must +// reach the active backend immediately instead of only dirtying deferred +// wrapper state. +static inline void W3DShaderManager_BindStageTexture(unsigned stage, TextureClass * tex) +{ + if (g_renderBackend != nullptr) + { + g_renderBackend->Bind_Texture_Immediate(stage, tex); + } +} + +static inline void W3DShaderManager_SetTextureTransform(unsigned stage, const Matrix4x4 & matrix) +{ + if (g_renderBackend != nullptr) + g_renderBackend->Set_Texture_Transform(stage, matrix); +} + +static inline Matrix4x4 W3DShaderManager_MakeTextureScale(float sx, float sy, float sz) +{ + return Matrix4x4( + sx, 0.0f, 0.0f, 0.0f, + 0.0f, sy, 0.0f, 0.0f, + 0.0f, 0.0f, sz, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); +} + +static inline Matrix4x4 W3DShaderManager_MakeTextureTranslation(float x, float y, float z) +{ + return Matrix4x4( + 1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f); +} + +static inline void W3DShaderManager_SetCameraSpaceTexcoord2(unsigned stage) +{ + if (g_renderBackend != nullptr) { + g_renderBackend->Set_Texture_Coord_Source(stage, RB_TEXCOORD_CAMERA_SPACE_POSITION, 0); + g_renderBackend->Set_Texture_Transform_Mode(stage, 2, false); + } +} + +static inline void W3DShaderManager_ResetMeshTexcoord(unsigned stage, unsigned uv_index) +{ + if (g_renderBackend != nullptr) { + g_renderBackend->Set_Texture_Coord_Source(stage, RB_TEXCOORD_MESH_UV, uv_index); + g_renderBackend->Set_Texture_Transform_Mode(stage, 0, false); + } +} + +static inline void W3DShaderManager_SetShroudTextureParams(float offset_x, float offset_y, + float scale_x, float scale_y) +{ + if (g_renderBackend != nullptr) + g_renderBackend->Set_Shroud_Texture_Params(offset_x, offset_y, scale_x, scale_y); +} + +static inline void W3DShaderManager_SetShroudTextureTransform(unsigned stage, W3DShroud *shroud) +{ + Matrix4x4 view; + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, view); + + float xoffset = 0.0f; + float yoffset = 0.0f; + const Real cell_width = shroud->getCellWidth(); + const Real cell_height = shroud->getCellHeight(); + + if (TheTerrainRenderObject->getMap()) + { // Origin is shifted by 1 cell width/height to allow for unused border texels. + xoffset = -(float)shroud->getDrawOriginX() + cell_width; + yoffset = -(float)shroud->getDrawOriginY() + cell_height; + } + + const Real scale_x = 1.0f/(cell_width*shroud->getTextureWidth()); + const Real scale_y = 1.0f/(cell_height*shroud->getTextureHeight()); + W3DShaderManager_SetShroudTextureParams(xoffset, yoffset, scale_x, scale_y); + + const Matrix4x4 transform = + W3DShaderManager_MakeTextureScale(scale_x, scale_y, 1.0f) * + W3DShaderManager_MakeTextureTranslation(xoffset, yoffset, 0.0f) * + view.Inverse(); + W3DShaderManager_SetTextureTransform(stage, transform); +} + +static inline RenderBackendTextureSampleFilter W3DShaderManager_GetTerrainMinMagFilter() +{ + return (TheGlobalData && (TheGlobalData->m_bilinearTerrainTex || TheGlobalData->m_trilinearTerrainTex)) ? + RB_TEXTURE_SAMPLE_LINEAR : + RB_TEXTURE_SAMPLE_POINT; +} + +static inline RenderBackendTextureSampleFilter W3DShaderManager_GetTerrainStage0MipFilter() +{ + return (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) ? + RB_TEXTURE_SAMPLE_LINEAR : + RB_TEXTURE_SAMPLE_POINT; +} + +static inline void W3DShaderManager_SetTerrainBaseSamplers() +{ + const RenderBackendTextureSampleFilter min_mag_filter = W3DShaderManager_GetTerrainMinMagFilter(); + g_renderBackend->Set_Texture_Sample_Filter( + 0, + min_mag_filter, + min_mag_filter, + W3DShaderManager_GetTerrainStage0MipFilter()); + g_renderBackend->Set_Texture_Sample_Filter( + 1, + min_mag_filter, + min_mag_filter, + RB_TEXTURE_SAMPLE_LINEAR); +} + +static inline void W3DShaderManager_SetFlatTerrainBaseSamplers() +{ + const RenderBackendTextureSampleFilter min_mag_filter = W3DShaderManager_GetTerrainMinMagFilter(); + const RenderBackendTextureSampleFilter mip_filter = W3DShaderManager_GetTerrainStage0MipFilter(); + g_renderBackend->Set_Texture_Sample_Filter(0, min_mag_filter, min_mag_filter, mip_filter); + g_renderBackend->Set_Texture_Sample_Filter(1, min_mag_filter, min_mag_filter, mip_filter); +} + +static inline void W3DShaderManager_SetStageAddress2D(unsigned stage, RenderBackendTextureAddressMode address_mode) +{ + g_renderBackend->Set_Texture_Address_Mode(stage, address_mode, address_mode, RB_TEXTURE_ADDRESS_WRAP); +} + +static inline void W3DShaderManager_SetStageMinMagFilter(unsigned stage, + RenderBackendTextureSampleFilter min_filter, + RenderBackendTextureSampleFilter mag_filter) +{ + g_renderBackend->Set_Texture_Min_Mag_Filter(stage, min_filter, mag_filter); +} + +static inline void W3DShaderManager_SetStageMipFilter(unsigned stage, RenderBackendTextureSampleFilter mip_filter) +{ + g_renderBackend->Set_Texture_Mip_Filter(stage, mip_filter); +} + +static inline void W3DShaderManager_FillViewportQuad(RenderBackendScreenVertex (&v)[4], DWORD diffuse, Bool use_second_uv, Real second_uv_radius = 0.0f) +{ + Int xpos, ypos, width, height; + + TheTacticalView->getOrigin(&xpos,&ypos); + width=TheTacticalView->getWidth(); + height=TheTacticalView->getHeight(); + + // bottom right + v[0].x = xpos+width-0.5f; + v[0].y = ypos+height-0.5f; + v[0].z = 0.0f; + v[0].w = 1.0f; + v[0].u0 = (Real)(xpos+width)/(Real)TheDisplay->getWidth(); + v[0].v0 = (Real)(ypos+height)/(Real)TheDisplay->getHeight(); + v[0].u1 = 0.5f+second_uv_radius; + v[0].v1 = 0.5f+second_uv_radius; + + // top right + v[1].x = xpos+width-0.5f; + v[1].y = ypos-0.5f; + v[1].z = 0.0f; + v[1].w = 1.0f; + v[1].u0 = (Real)(xpos+width)/(Real)TheDisplay->getWidth(); + v[1].v0 = (Real)(ypos)/(Real)TheDisplay->getHeight(); + v[1].u1 = 0.5f+second_uv_radius; + v[1].v1 = 0.5f-second_uv_radius; + + // bottom left + v[2].x = xpos-0.5f; + v[2].y = ypos+height-0.5f; + v[2].z = 0.0f; + v[2].w = 1.0f; + v[2].u0 = (Real)(xpos)/(Real)TheDisplay->getWidth(); + v[2].v0 = (Real)(ypos+height)/(Real)TheDisplay->getHeight(); + v[2].u1 = 0.5f-second_uv_radius; + v[2].v1 = 0.5f+second_uv_radius; + + // top left + v[3].x = xpos-0.5f; + v[3].y = ypos-0.5f; + v[3].z = 0.0f; + v[3].w = 1.0f; + v[3].u0 = (Real)(xpos)/(Real)TheDisplay->getWidth(); + v[3].v0 = (Real)(ypos)/(Real)TheDisplay->getHeight(); + v[3].u1 = 0.5f-second_uv_radius; + v[3].v1 = 0.5f-second_uv_radius; + + for (Int i = 0; i < 4; ++i) + { + v[i].diffuse = diffuse; + if (!use_second_uv) + { + v[i].u1 = 0.0f; + v[i].v1 = 0.0f; + } + } +} + /** 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. */ @@ -88,8 +288,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; ///SetTexture(0,tex); //previously rendered frame inside this texture - TheTacticalView->getOrigin(&xpos,&ypos); - width=TheTacticalView->getWidth(); - height=TheTacticalView->getHeight(); - - //bottom right - v[0].p = D3DXVECTOR4( xpos+width-0.5f, ypos+height-0.5f, 0.0f, 1.0f ); - v[0].u = (Real)(xpos+width)/(Real)TheDisplay->getWidth(); v[0].v = (Real)(ypos+height)/(Real)TheDisplay->getHeight(); - //top right - v[1].p = D3DXVECTOR4( xpos+width-0.5f, ypos-0.5f, 0.0f, 1.0f ); - v[1].u = (Real)(xpos+width)/(Real)TheDisplay->getWidth(); v[1].v = (Real)(ypos)/(Real)TheDisplay->getHeight(); - //bottom left - v[2].p = D3DXVECTOR4( xpos-0.5f, ypos+height-0.5f, 0.0f, 1.0f ); - v[2].u = (Real)(xpos)/(Real)TheDisplay->getWidth(); v[2].v = (Real)(ypos+height)/(Real)TheDisplay->getHeight(); - //top left - v[3].p = D3DXVECTOR4( xpos-0.5f, ypos-0.5f, 0.0f, 1.0f ); - v[3].u = (Real)(xpos)/(Real)TheDisplay->getWidth(); v[3].v = (Real)(ypos)/(Real)TheDisplay->getHeight(); - v[0].color = 0xffffffff; - v[1].color = 0xffffffff; - v[2].color = 0xffffffff; - v[3].color = 0xffffffff; - - //draw polygons like this is very inefficient but for only 2 triangles, it's - //not worth bothering with index/vertex buffers. - pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1); - - pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX)); + RenderBackendScreenVertex v[4]; + W3DShaderManager_FillViewportQuad(v, 0xffffffff, FALSE); + if (g_renderBackend == nullptr || + !g_renderBackend->Draw_View_Capture_Quad(RB_VIEW_CAPTURE_TACTICAL, v, 4, false)) + { + reset(); + return false; + } reset(); return true; @@ -233,23 +399,23 @@ 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->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; } void ScreenDefaultFilter::reset() { - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); //previously rendered frame inside this texture - DX8Wrapper::Invalidate_Cached_Render_States(); + W3DShaderManager_BindStageTexture(0, nullptr); + g_renderBackend->Invalidate_Cached_Render_States(); } /*========= ScreenBWFilter =============================================================*/ @@ -288,19 +454,8 @@ Int ScreenBWFilter::init() { if (res >= DC_GENERIC_PIXEL_SHADER_1_1) { - //this shader needs some assets that need to be loaded - //shader decleration - DWORD Declaration[]= - { - (D3DVSD_STREAM(0)), - (D3DVSD_REG(0, D3DVSDT_FLOAT3)), // Position - (D3DVSD_REG(1, D3DVSDT_D3DCOLOR)), // Diffuse - (D3DVSD_REG(2, D3DVSDT_FLOAT2)), // Texture Coordinates - (D3DVSD_END()) - }; - //Monochrome pixel shader. - hr = W3DShaderManager::LoadAndCreateD3DShader("shaders\\monochrome.pso", &Declaration[0], 0, false, &m_dwBWPixelShader); + hr = W3DShaderManager::LoadAndCreateLegacyShader("shaders\\monochrome.pso", nullptr, 0, false, &m_dwBWPixelShader); if (FAILED(hr)) return FALSE; @@ -321,49 +476,19 @@ Bool ScreenBWFilter::preRender(Bool &skipRender, CustomScenePassModes &scenePass Bool ScreenBWFilter::postRender(FilterModes mode, Coord2D &scrollDelta,Bool &doExtraRender) { - IDirect3DTexture8 * tex = W3DShaderManager::endRenderToTexture(); - DEBUG_ASSERTCRASH(tex, ("Require rendered texture.")); - if (!tex) return false; + Bool captured = W3DShaderManager::endRenderToTexture(); + DEBUG_ASSERTCRASH(captured, ("Require rendered texture.")); + if (!captured) return false; if (!set(mode)) return false; - LPDIRECT3DDEVICE8 pDev=DX8Wrapper::_Get_D3D_Device8(); - - struct _TRANS_LIT_TEX_VERTEX { - D3DXVECTOR4 p; - DWORD color; // diffuse color - float u; - float v; - } v[4]; - - Int xpos, ypos, width, height; - - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,tex); //previously rendered frame inside this texture - TheTacticalView->getOrigin(&xpos,&ypos); - width=TheTacticalView->getWidth(); - height=TheTacticalView->getHeight(); - - //bottom right - v[0].p = D3DXVECTOR4( xpos+width-0.5f, ypos+height-0.5f, 0.0f, 1.0f ); - v[0].u = (Real)(xpos+width)/(Real)TheDisplay->getWidth(); v[0].v = (Real)(ypos+height)/(Real)TheDisplay->getHeight(); - //top right - v[1].p = D3DXVECTOR4( xpos+width-0.5f, ypos-0.5f, 0.0f, 1.0f ); - v[1].u = (Real)(xpos+width)/(Real)TheDisplay->getWidth(); v[1].v = (Real)(ypos)/(Real)TheDisplay->getHeight(); - //bottom left - v[2].p = D3DXVECTOR4( xpos-0.5f, ypos+height-0.5f, 0.0f, 1.0f ); - v[2].u = (Real)(xpos)/(Real)TheDisplay->getWidth(); v[2].v = (Real)(ypos+height)/(Real)TheDisplay->getHeight(); - //top left - v[3].p = D3DXVECTOR4( xpos-0.5f, ypos-0.5f, 0.0f, 1.0f ); - v[3].u = (Real)(xpos)/(Real)TheDisplay->getWidth(); v[3].v = (Real)(ypos)/(Real)TheDisplay->getHeight(); - v[0].color = 0xffffffff; - v[1].color = 0xffffffff; - v[2].color = 0xffffffff; - v[3].color = 0xffffffff; - - //draw polygons like this is very inefficient but for only 2 triangles, it's - //not worth bothering with index/vertex buffers. - pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1); - - pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX)); + RenderBackendScreenVertex v[4]; + W3DShaderManager_FillViewportQuad(v, 0xffffffff, FALSE); + if (g_renderBackend == nullptr || + !g_renderBackend->Draw_View_Capture_Quad(RB_VIEW_CAPTURE_TACTICAL, v, 4, false)) + { + reset(); + return false; + } reset(); return true; @@ -371,8 +496,6 @@ Bool ScreenBWFilter::postRender(FilterModes mode, Coord2D &scrollDelta,Bool &doE Int ScreenBWFilter::set(FilterModes mode) { - HRESULT hr; - if (mode > FM_NULL_MODE) { //rendering a quad with redirected rendering surface tinted by pixel shader @@ -411,54 +534,49 @@ 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->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); - DX8Wrapper::_Get_D3D_Device8()->SetPixelShaderConstant(0, D3DXVECTOR4(0.3f, 0.59f, 0.11f, 1.0f), 1); + g_renderBackend->Set_Pixel_Shader(m_dwBWPixelShader); + const float luminanceWeights[4] = { 0.3f, 0.59f, 0.11f, 1.0f }; + g_renderBackend->Set_Pixel_Shader_Constant(0, &luminanceWeights, 1); - D3DXVECTOR4 color(1.0f,1.0f,1.0f,1.0f); //multiply color + float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; //multiply color if (mode == FM_VIEW_BW_BLACK_AND_WHITE) { //back & white mode - color.x=1.0f; - color.y=1.0f; - color.z=1.0f; + color[0]=1.0f; + color[1]=1.0f; + color[2]=1.0f; } if (mode == FM_VIEW_BW_RED_AND_WHITE) { //red is on - color.x = 1.0f; - color.y = 0.0f; - color.z = 0.0f; + color[0] = 1.0f; + color[1] = 0.0f; + color[2] = 0.0f; //inverse red is on //red is on -// color.x = 0.0f; -// color.y = 1.0f; -// color.z = 1.0f; +// color[0] = 0.0f; +// color[1] = 1.0f; +// color[2] = 1.0f; } if (mode == FM_VIEW_BW_GREEN_AND_WHITE) { - color.x = 0.0f; - color.y = 1.0f; - color.z = 0.0f; + color[0] = 0.0f; + color[1] = 1.0f; + color[2] = 0.0f; } - DX8Wrapper::_Get_D3D_Device8()->SetPixelShaderConstant(1, color, 1); - DX8Wrapper::_Get_D3D_Device8()->SetPixelShaderConstant(2, D3DXVECTOR4(m_curFadeValue, m_curFadeValue, m_curFadeValue, 1.0f), 1); -/* DX8Wrapper::_Get_D3D_Device8()->SetPixelShaderConstant(2, D3DXVECTOR4(150.0f/255.0f, 150.0f/255.0f, 150.0f/255.0f, 0.0f), 1); - DX8Wrapper::_Get_D3D_Device8()->SetPixelShaderConstant(3, D3DXVECTOR4((765.0f/450.0f)/3, (765.0f/450.0f)/3, (765.0f/450.0f)/3, 1.0f), 1); - DX8Wrapper::_Get_D3D_Device8()->SetPixelShaderConstant(4, D3DXVECTOR4(0.5f, 0.5f, 0.5f, 0), 1); - DX8Wrapper::_Get_D3D_Device8()->SetPixelShaderConstant(5, D3DXVECTOR4((60.0f)/255.0f, (60.0f)/255.0f, (60.0f)/255.0f, 0), 1); - DX8Wrapper::_Get_D3D_Device8()->SetPixelShaderConstant(6, D3DXVECTOR4((157.0f)/255.0f, (157.0f)/255.0f, (157.0f)/255.0f, 0), 1); - DX8Wrapper::_Get_D3D_Device8()->SetPixelShaderConstant(7, D3DXVECTOR4((30.0f)/255.0f, (30.0f)/255.0f, (30.0f)/255.0f, 0), 1); -*/ + g_renderBackend->Set_Pixel_Shader_Constant(1, &color, 1); + const float fadeValue[4] = { m_curFadeValue, m_curFadeValue, m_curFadeValue, 1.0f }; + g_renderBackend->Set_Pixel_Shader_Constant(2, &fadeValue, 1); return true; } return false; @@ -466,15 +584,15 @@ 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()->SetPixelShader(0); //turn off pixel shader - DX8Wrapper::Invalidate_Cached_Render_States(); + W3DShaderManager_BindStageTexture(0, nullptr); + g_renderBackend->Set_Pixel_Shader(0); //turn off pixel shader + g_renderBackend->Invalidate_Cached_Render_States(); } Int ScreenBWFilter::shutdown() { if (m_dwBWPixelShader) - DX8Wrapper::_Get_D3D_Device8()->DeletePixelShader(m_dwBWPixelShader); + g_renderBackend->Delete_Pixel_Shader(m_dwBWPixelShader); m_dwBWPixelShader=0; @@ -510,84 +628,56 @@ Bool ScreenBWFilterDOT3::preRender(Bool &skipRender, CustomScenePassModes &scene Bool ScreenBWFilterDOT3::postRender(FilterModes mode, Coord2D &scrollDelta,Bool &doExtraRender) { - IDirect3DTexture8 * tex = W3DShaderManager::endRenderToTexture(); - DEBUG_ASSERTCRASH(tex, ("Require rendered texture.")); - if (!tex) return false; + Bool captured = W3DShaderManager::endRenderToTexture(); + DEBUG_ASSERTCRASH(captured, ("Require rendered texture.")); + if (!captured) return false; if (!set(mode)) return false; - LPDIRECT3DDEVICE8 pDev=DX8Wrapper::_Get_D3D_Device8(); - - struct _TRANS_LIT_TEX_VERTEX { - D3DXVECTOR4 p; - DWORD color; // diffuse color - float u; - float v; - } v[4]; - - Int xpos, ypos, width, height; - - TheTacticalView->getOrigin(&xpos,&ypos); - width=TheTacticalView->getWidth(); - height=TheTacticalView->getHeight(); - - //bottom right - v[0].p = D3DXVECTOR4( xpos+width-0.5f, ypos+height-0.5f, 0.0f, 1.0f ); - v[0].u = (Real)(xpos+width)/(Real)TheDisplay->getWidth(); v[0].v = (Real)(ypos+height)/(Real)TheDisplay->getHeight(); - //top right - v[1].p = D3DXVECTOR4( xpos+width-0.5f, ypos-0.5f, 0.0f, 1.0f ); - v[1].u = (Real)(xpos+width)/(Real)TheDisplay->getWidth(); v[1].v = (Real)(ypos)/(Real)TheDisplay->getHeight(); - //bottom left - v[2].p = D3DXVECTOR4( xpos-0.5f, ypos+height-0.5f, 0.0f, 1.0f ); - v[2].u = (Real)(xpos)/(Real)TheDisplay->getWidth(); v[2].v = (Real)(ypos+height)/(Real)TheDisplay->getHeight(); - //top left - v[3].p = D3DXVECTOR4( xpos-0.5f, ypos-0.5f, 0.0f, 1.0f ); - v[3].u = (Real)(xpos)/(Real)TheDisplay->getWidth(); v[3].v = (Real)(ypos)/(Real)TheDisplay->getHeight(); - DWORD currentFade=(((Int)((1.0f-m_curFadeValue) * 255.0f))<<24) | 0x00ffffff; //store alpha value - - v[0].color = currentFade; - v[1].color = currentFade; - v[2].color = currentFade; - v[3].color = currentFade; - - //draw polygons like this is very inefficient but for only 2 triangles, it's - //not worth bothering with index/vertex buffers. - pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1); + RenderBackendScreenVertex v[4]; + W3DShaderManager_FillViewportQuad(v, currentFade, FALSE); //Draw B&W version first - if (DX8Wrapper::Get_Current_Caps()->Support_Dot3()) + if (g_renderBackend != nullptr && g_renderBackend->Supports_Dot3()) { //Override W3D states with customizations for grayscale - DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, 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); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLOROP, D3DTOP_MULTIPLYADD); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_CURRENT); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DOTPRODUCT3); + g_renderBackend->Set_Texture_Factor(0x80A5CA8E); + g_renderBackend->Set_Texture_Color_Argument(0, 0, RB_TEXARG_TFACTOR | RB_TEXARG_ALPHAREPLICATE); + g_renderBackend->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_TFACTOR | RB_TEXARG_ALPHAREPLICATE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_MULTIPLYADD); + g_renderBackend->Set_Texture_Color_Argument(1, 1, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Color_Argument(1, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_DOTPRODUCT3); } else { //doesn't have DOT3 blend mode so fake it another way. - DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, 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); + g_renderBackend->Set_Texture_Factor(0x60606060); + g_renderBackend->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_MODULATE); } - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,tex); //previously rendered frame inside this texture - - pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX)); + if (g_renderBackend == nullptr || + !g_renderBackend->Draw_View_Capture_Quad(RB_VIEW_CAPTURE_TACTICAL, v, 4, false)) + { + reset(); + return false; + } //Draw normal view blended by current fade level 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); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_SELECTARG2); - pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX)); + if (!g_renderBackend->Draw_View_Capture_Quad(RB_VIEW_CAPTURE_TACTICAL, v, 4, false)) + { + reset(); + return false; + } reset(); return true; @@ -633,15 +723,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->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; } @@ -650,8 +740,8 @@ 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(); + W3DShaderManager_BindStageTexture(0, nullptr); + g_renderBackend->Invalidate_Cached_Render_States(); } Int ScreenBWFilterDOT3::shutdown() @@ -759,41 +849,24 @@ Bool ScreenCrossFadeFilter::preRender(Bool &skipRender, CustomScenePassModes &sc Bool ScreenCrossFadeFilter::postRender(FilterModes mode, Coord2D &scrollDelta,Bool &doExtraRender) { - IDirect3DTexture8 * tex; - if (m_skipRender) { //don't render anything to frame buffer because we still need to draw the new scene //that we're fading into. Okay to render on the next call. m_skipRender = false; doExtraRender = TRUE; - tex = W3DShaderManager::endRenderToTexture(); + W3DShaderManager::endRenderToTexture(); return true; } - tex=W3DShaderManager::getRenderTexture(); - - DEBUG_ASSERTCRASH(tex, ("Require last rendered texture.")); - if (!tex) return false; + DEBUG_ASSERTCRASH(W3DShaderManager::hasRenderTexture(), ("Require last rendered texture.")); + if (!W3DShaderManager::hasRenderTexture()) return false; if (!set(mode)) return false; - LPDIRECT3DDEVICE8 pDev=DX8Wrapper::_Get_D3D_Device8(); - - struct _TRANS_LIT_TEX_VERTEX { - D3DXVECTOR4 p; - DWORD color; // diffuse color - float u; - float v; - float u1; - float v1; - } v[4]; - - Int xpos, ypos, width, height; Real radius = 0.0f; - 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; @@ -802,47 +875,15 @@ Bool ScreenCrossFadeFilter::postRender(FilterModes mode, Coord2D &scrollDelta,Bo radius = 0.5f/radius; } - TheTacticalView->getOrigin(&xpos,&ypos); - width=TheTacticalView->getWidth(); - height=TheTacticalView->getHeight(); - -/* Real radius = (1.0f-m_curFadeValue); - if (radius <= 0) - radius = 0.01f; - radius = 25.0f-radius*24.75f; -*/ - //bottom right - v[0].p = D3DXVECTOR4( xpos+width-0.5f, ypos+height-0.5f, 0.0f, 1.0f ); - v[0].u = (Real)(xpos+width)/(Real)TheDisplay->getWidth(); v[0].v = (Real)(ypos+height)/(Real)TheDisplay->getHeight(); - v[0].u1 = 0.5f+radius; v[0].v1 = 0.5f+radius; - //top right - v[1].p = D3DXVECTOR4( xpos+width-0.5f, ypos-0.5f, 0.0f, 1.0f ); - v[1].u = (Real)(xpos+width)/(Real)TheDisplay->getWidth(); v[1].v = (Real)(ypos)/(Real)TheDisplay->getHeight(); - v[1].u1 = 0.5f+radius; v[1].v1 = 0.5f-radius; - //bottom left - v[2].p = D3DXVECTOR4( xpos-0.5f, ypos+height-0.5f, 0.0f, 1.0f ); - v[2].u = (Real)(xpos)/(Real)TheDisplay->getWidth(); v[2].v = (Real)(ypos+height)/(Real)TheDisplay->getHeight(); - v[2].u1 = 0.5f-radius; v[2].v1 = 0.5f+radius; - //top left - v[3].p = D3DXVECTOR4( xpos-0.5f, ypos-0.5f, 0.0f, 1.0f ); - v[3].u = (Real)(xpos)/(Real)TheDisplay->getWidth(); v[3].v = (Real)(ypos)/(Real)TheDisplay->getHeight(); - v[3].u1 = 0.5f-radius; v[3].v1 = 0.5f-radius; - DWORD diffuse = 0xffffffff;//((Int)((m_curFadeValue) * 255.0f) << 24) | 0x00ffffff; //store alpha value in vertex diffuse - - v[0].color = diffuse; - v[1].color = diffuse; - v[2].color = diffuse; - v[3].color = diffuse; - - //draw polygons like this is very inefficient but for only 2 triangles, it's - //not worth bothering with index/vertex buffers. - pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX2); - -// m_pDev->SetTextureStageState(0,D3DTSS_MAGFILTER,D3DTEXF_POINT); -// m_pDev->SetTextureStageState(0,D3DTSS_MINFILTER,D3DTEXF_POINT); - - pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX)); + RenderBackendScreenVertex v[4]; + W3DShaderManager_FillViewportQuad(v, diffuse, mode == FM_VIEW_CROSSFADE_CIRCLE, radius); + if (g_renderBackend == nullptr || + !g_renderBackend->Draw_View_Capture_Quad(RB_VIEW_CAPTURE_TACTICAL, v, 4, mode == FM_VIEW_CROSSFADE_CIRCLE)) + { + reset(); + return false; + } reset(); return true; @@ -853,32 +894,30 @@ 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); + W3DShaderManager_SetStageAddress2D(0, RB_TEXTURE_ADDRESS_CLAMP); if (mode == FM_VIEW_CROSSFADE_CIRCLE) { //cross-fading using circle mask stored in stage 1 - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_MODULATE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, 1 ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_MIPFILTER, D3DTEXF_NONE); + g_renderBackend->Set_Texture_Color_Argument(1, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(1, 2, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Argument(1, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Argument(1, 2, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Coord_Source(1, RB_TEXCOORD_MESH_UV, 1); + W3DShaderManager_SetStageAddress2D(1, RB_TEXTURE_ADDRESS_CLAMP); + W3DShaderManager_SetStageMipFilter(1, RB_TEXTURE_SAMPLE_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; } @@ -887,10 +926,10 @@ Int ScreenCrossFadeFilter::set(FilterModes mode) 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->Set_Texture_Color_Operation(1, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); + W3DShaderManager_BindStageTexture(0, nullptr); + g_renderBackend->Invalidate_Cached_Render_States(); } Int ScreenCrossFadeFilter::shutdown() @@ -941,58 +980,26 @@ Bool ScreenMotionBlurFilter::preRender(Bool &skipRender, CustomScenePassModes &s Bool ScreenMotionBlurFilter::postRender(FilterModes mode, Coord2D &scrollDelta,Bool &doExtraRender) { - IDirect3DTexture8 * tex = W3DShaderManager::endRenderToTexture(); - DEBUG_ASSERTCRASH(tex, ("Require rendered texture.")); - if (!tex) return false; + Bool captured = W3DShaderManager::endRenderToTexture(); + DEBUG_ASSERTCRASH(captured, ("Require rendered texture.")); + if (!captured) return false; if (!set(mode)) return false; - LPDIRECT3DDEVICE8 pDev=DX8Wrapper::_Get_D3D_Device8(); - Bool continueEffect = true; - struct _TRANS_LIT_TEX_VERTEX { - D3DXVECTOR4 p; - DWORD color; // diffuse color - float u; - float v; - } v[4]; - - Int xpos, ypos, width, height; - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,tex); //previously rendered frame inside this texture - TheTacticalView->getOrigin(&xpos,&ypos); - width=TheTacticalView->getWidth(); - height=TheTacticalView->getHeight(); - - //bottom right - v[0].p = D3DXVECTOR4( xpos+width-0.5f, ypos+height-0.5f, 0.0f, 1.0f ); - v[0].u = (Real)(xpos+width)/(Real)TheDisplay->getWidth(); v[0].v = (Real)(ypos+height)/(Real)TheDisplay->getHeight(); - //top right - v[1].p = D3DXVECTOR4( xpos+width-0.5f, ypos-0.5f, 0.0f, 1.0f ); - v[1].u = (Real)(xpos+width)/(Real)TheDisplay->getWidth(); v[1].v = (Real)(ypos)/(Real)TheDisplay->getHeight(); - //bottom left - v[2].p = D3DXVECTOR4( xpos-0.5f, ypos+height-0.5f, 0.0f, 1.0f ); - v[2].u = (Real)(xpos)/(Real)TheDisplay->getWidth(); v[2].v = (Real)(ypos+height)/(Real)TheDisplay->getHeight(); - //top left - v[3].p = D3DXVECTOR4( xpos-0.5f, ypos-0.5f, 0.0f, 1.0f ); - v[3].u = (Real)(xpos)/(Real)TheDisplay->getWidth(); v[3].v = (Real)(ypos)/(Real)TheDisplay->getHeight(); - v[0].color = 0xffffffff; - v[1].color = 0xffffffff; - v[2].color = 0xffffffff; - v[3].color = 0xffffffff; + RenderBackendScreenVertex v[4]; + W3DShaderManager_FillViewportQuad(v, 0xffffffff, FALSE); 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. - DX8Wrapper::Apply_Render_State_Changes(); - pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1); + g_renderBackend->Apply_Render_State_Changes(); Coord2D center; center.x = 0.5f; @@ -1052,17 +1059,21 @@ Bool ScreenMotionBlurFilter::postRender(FilterModes mode, Coord2D &scrollDelta,B for (i=0; i<4; i++) { Real factor = 1.0f - (m_maxCount/(Real)MAX_COUNT)*0.90f; factor = sqrt(factor); - v[i].u = ((v[i].u-center.x)*factor) + center.x; - v[i].v = ((v[i].v-center.y)*factor) + center.y; + v[i].u0 = ((v[i].u0-center.x)*factor) + center.x; + v[i].v0 = ((v[i].v0-center.y)*factor) + center.y; } } - pDev->SetTextureStageState(0,D3DTSS_ALPHAARG1, D3DTA_CURRENT); - 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_Texture_Alpha_Argument(0, 1, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Alpha_Argument(0, 2, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_SELECTARG1); + if (!g_renderBackend->Draw_View_Capture_Quad(RB_VIEW_CAPTURE_TACTICAL, v, 4, false)) + { + reset(); + return false; + } + g_renderBackend->Set_Alpha_Blend_Enable(true); - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); { Int limit = m_maxCount; if (m_maxCount>30) limit = 30; @@ -1076,18 +1087,22 @@ Bool ScreenMotionBlurFilter::postRender(FilterModes mode, Coord2D &scrollDelta,B if (m_maxCount>limit) { alpha += (m_maxCount-limit)/5; } - if (m_maxCount==MAX_COUNT) alpha += 60; + if (m_maxCount==MAX_COUNT) alpha += 60; } - v[i].color = (alpha<<24)|0x00ffffff; // + v[i].diffuse = (alpha<<24)|0x00ffffff; // if (pan) { - v[i].u = ((v[i].u-center.x)*(factor+.006)) + center.x; - v[i].v = ((v[i].v-center.y)*factor) + center.y; + v[i].u0 = ((v[i].u0-center.x)*(factor+.006)) + center.x; + v[i].v0 = ((v[i].v0-center.y)*factor) + center.y; } else { - v[i].u = ((v[i].u-center.x)*factor) + center.x; - v[i].v = ((v[i].v-center.y)*factor) + center.y; + v[i].u0 = ((v[i].u0-center.x)*factor) + center.x; + v[i].v0 = ((v[i].v0-center.y)*factor) + center.y; } } - pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX)); + if (!g_renderBackend->Draw_View_Capture_Quad(RB_VIEW_CAPTURE_TACTICAL, v, 4, false)) + { + reset(); + return false; + } } } @@ -1143,24 +1158,24 @@ 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->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; } void ScreenMotionBlurFilter::reset() { - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0,nullptr); //previously rendered frame inside this texture - DX8Wrapper::Invalidate_Cached_Render_States(); + W3DShaderManager_BindStageTexture(0, nullptr); + g_renderBackend->Invalidate_Cached_Render_States(); } Int ScreenMotionBlurFilter::shutdown() @@ -1201,64 +1216,41 @@ Int ShroudTextureShader::init() //Setup a texture projection in the given stage that applies our shroud. Int ShroudTextureShader::set(Int stage) { + // TheSuperHackers @bugfix bobtista 28/04/2026 Shroud reuses terrain + // vertex buffers, but it is a projected multiplicative overlay, not the + // terrain pixel-shader blend pass. Clear the bgfx terrain override so the + // shroud pass cannot inherit terrain sampling state from the base pass. + g_renderBackend->Override_Terrain_Blend(false); + //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 + W3DShaderManager_BindStageTexture(stage, W3DShaderManager::getShaderTexture(0)); //shroud always stored in texture 0 + g_renderBackend->Set_Shroud_Texture_Pass_Active(true, stage); 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); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMP_EQUAL); + W3DShaderManager_SetCameraSpaceTexcoord2(stage); + 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). W3DShroud *shroud; if ((shroud=TheTerrainRenderObject->getShroud()) != nullptr) { ///@todo: All this code really only need to be done once per camera/view. Find a way to optimize it out. - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); - - D3DXMATRIX inv; - float det; - D3DXMatrixInverse(&inv, &det, &curView); - - D3DXMATRIX scale,offset; - - //We need to make all world coordinates be relative to the heightmap data origin since that - //is where the shroud begins. - - float xoffset = 0; - float yoffset = 0; - Real width=shroud->getCellWidth(); - Real height=shroud->getCellHeight(); - - if (TheTerrainRenderObject->getMap()) - { //subtract origin position from all coordinates. Origin is shifted by 1 cell width/height to allow for unused border texels. - xoffset = -(float)shroud->getDrawOriginX() + width; - yoffset = -(float)shroud->getDrawOriginY() + height; - } - - D3DXMatrixTranslation(&offset, xoffset, yoffset,0); - - width = 1.0f/(width*shroud->getTextureWidth()); - height = 1.0f/(height*shroud->getTextureHeight()); - D3DXMatrixScaling(&scale, width, height, 1); - curView = (inv * offset) * scale; - DX8Wrapper::_Set_DX8_Transform((D3DTRANSFORMSTATETYPE )(D3DTS_TEXTURE0+stage), curView); + W3DShaderManager_SetShroudTextureTransform(stage, shroud); } m_stageOfSet=stage; return TRUE; @@ -1266,10 +1258,10 @@ Int ShroudTextureShader::set(Int stage) void ShroudTextureShader::reset() { - DX8Wrapper::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); + g_renderBackend->Set_Shroud_Texture_Pass_Active(false, m_stageOfSet); + g_renderBackend->Set_Texture(m_stageOfSet,nullptr); + g_renderBackend->Set_Depth_Func(RB_CMP_LESS_EQUAL); + W3DShaderManager_ResetMeshTexcoord(m_stageOfSet, m_stageOfSet); } ///Shroud layer rendering shader @@ -1301,56 +1293,30 @@ Int FlatShroudTextureShader::init() //Setup a texture projection in the given stage that applies our shroud. Int FlatShroudTextureShader::set(Int stage) { + // TheSuperHackers @bugfix bobtista 28/04/2026 Flat shroud is also a + // projected overlay and must not inherit the bgfx terrain blend branch. + g_renderBackend->Override_Terrain_Blend(false); + //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()); + W3DShaderManager_BindStageTexture(stage, W3DShaderManager::getShaderTexture(stage)); - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - 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->Set_Texture_Color_Argument(stage, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(stage, 2, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Color_Operation(stage, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Operation(stage, RB_TEXOP_DISABLE); + //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); + W3DShaderManager_SetCameraSpaceTexcoord2(stage); //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). W3DShroud *shroud; if ((shroud=TheTerrainRenderObject->getShroud()) != nullptr) { ///@todo: All this code really only need to be done once per camera/view. Find a way to optimize it out. - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); - - D3DXMATRIX inv; - float det; - D3DXMatrixInverse(&inv, &det, &curView); - - D3DXMATRIX scale,offset; - - //We need to make all world coordinates be relative to the heightmap data origin since that - //is where the shroud begins. - - float xoffset = 0; - float yoffset = 0; - Real width=shroud->getCellWidth(); - Real height=shroud->getCellHeight(); - - if (TheTerrainRenderObject->getMap()) - { //subtract origin position from all coordinates. Origin is shifted by 1 cell width/height to allow for unused border texels. - xoffset = -(float)shroud->getDrawOriginX() + width; - yoffset = -(float)shroud->getDrawOriginY() + height; - } - - D3DXMatrixTranslation(&offset, xoffset, yoffset,0); - - width = 1.0f/(width*shroud->getTextureWidth()); - height = 1.0f/(height*shroud->getTextureHeight()); - D3DXMatrixScaling(&scale, width, height, 1); - curView = (inv * offset) * scale; - DX8Wrapper::_Set_DX8_Transform((D3DTRANSFORMSTATETYPE )(D3DTS_TEXTURE0+stage), curView); + W3DShaderManager_SetShroudTextureTransform(stage, shroud); } m_stageOfSet=stage; return TRUE; @@ -1358,11 +1324,10 @@ Int FlatShroudTextureShader::set(Int stage) void FlatShroudTextureShader::reset() { - if (m_stageOfSet < MAX_TEXTURE_STAGES) - DX8Wrapper::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); + if (m_stageOfSet < RB_MAX_TEXTURE_STAGES) + g_renderBackend->Set_Texture(m_stageOfSet,nullptr); + g_renderBackend->Set_Depth_Func(RB_CMP_LESS_EQUAL); + W3DShaderManager_ResetMeshTexcoord(m_stageOfSet, m_stageOfSet); } ///Mask layer rendering shader @@ -1400,29 +1365,24 @@ 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(); - - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); + g_renderBackend->Set_Shader(shader); + g_renderBackend->Apply_Render_State_Changes(); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); + Matrix4x4 curView; + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, curView); - D3DXMATRIX inv; - float det; + W3DShaderManager_SetCameraSpaceTexcoord2(0); //Get inverse view matrix so we can transform camera space points back to world space - D3DXMatrixInverse(&inv, &det, &curView); + Matrix4x4 inv = curView.Inverse(); - D3DXMATRIX scale,offset,offsetTextureCenter; Coord3D centerPos; centerPos.zero(); @@ -1439,37 +1399,42 @@ Int MaskTextureShader::set(Int pass) TheTacticalView->screenToTerrain(&screenPos,¢erPos); } - D3DXMatrixTranslation(&offset, -centerPos.x, -centerPos.y,0); + Matrix4x4 offset = W3DShaderManager_MakeTextureTranslation(-centerPos.x, -centerPos.y, 0); - D3DXMatrixTranslation(&offsetTextureCenter, 0.5f, 0.5f, 0); //shift coordinates so center of projection falls at uv 0.5,0.5 + //shift coordinates so center of projection falls at uv 0.5,0.5 + Matrix4x4 offsetTextureCenter = W3DShaderManager_MakeTextureTranslation(0.5f, 0.5f, 0); Real worldTexelWidth=(1.0f-fadeLevel)*25.0f; //9 worked well for circle but weird shape requires more stretch to cover. Real worldTexelHeight=(1.0f-fadeLevel)*25.0f; ///@todo: Fix this to work with non 128x128 textures. + // TheSuperHackers @bugfix bobtista 17/07/2026 Reverse the factor order for this port's + // column-vector Matrix4x4, exactly like the shroud texture transform + // (W3DShaderManager_SetShroudTextureTransform): the view-inverse must be the rightmost + // (first-applied) factor. Retail's row-vector order left the crossfade mask misprojected, + // so the circle-wipe did not track the screen-center terrain point or scale with fade. if (worldTexelWidth != 0 && worldTexelHeight != 0) { Real widthScale = 1.0f/(worldTexelWidth*128.0f); Real heightScale = 1.0f/(worldTexelHeight*128.0f); - D3DXMatrixScaling(&scale, widthScale, heightScale, 1); - curView = ((inv * offset) * scale)*offsetTextureCenter; + Matrix4x4 scale = W3DShaderManager_MakeTextureScale(widthScale, heightScale, 1); + curView = offsetTextureCenter*(scale*(offset*inv)); } else { - D3DXMatrixScaling(&scale, 0, 0, 1); //scaling by 0 will set uv coordinates to 0,0 - curView = ((inv * offset) * scale); + Matrix4x4 scale = W3DShaderManager_MakeTextureScale(0, 0, 1); //scaling by 0 will set uv coordinates to 0,0 + curView = scale*(offset*inv); } - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE0, curView); + W3DShaderManager_SetTextureTransform(0, curView); return TRUE; } void MaskTextureShader::reset() { - DX8Wrapper::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); + g_renderBackend->Set_Texture(0,nullptr); + W3DShaderManager_ResetMeshTexcoord(0, 0); } /*===========================================================================================*/ @@ -1484,14 +1449,15 @@ class TerrainShader2Stage : public W3DShaderInterface float m_ySlidePerSecond ; ///< How far the clouds move per second. float m_xOffset; float m_yOffset; + unsigned int m_lastCloudUpdateFrame; virtual Int set(Int pass) override; ///Override_Terrain_Blend(false); ShaderClass::Invalidate(); //Free references to textures - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, nullptr); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(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); + W3DShaderManager_BindStageTexture(0, nullptr); + W3DShaderManager_BindStageTexture(1, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|1); + W3DShaderManager_ResetMeshTexcoord(0, 0); + W3DShaderManager_ResetMeshTexcoord(1, 1); } void TerrainShader2Stage::updateCloud() { + const unsigned int frame = WW3D::Get_Frame_Count(); + if (m_lastCloudUpdateFrame == frame) + { + return; + } + m_lastCloudUpdateFrame = frame; + const float frame_time = WW3D::Get_Logic_Frame_Time_Seconds(); m_xOffset += m_xSlidePerSecond * frame_time; m_yOffset += m_ySlidePerSecond * frame_time; @@ -1606,168 +1578,133 @@ void TerrainShader2Stage::updateCloud() m_yOffset -= (Int)m_yOffset; } -void TerrainShader2Stage::updateNoise1(D3DXMATRIX *destMatrix,D3DXMATRIX *curViewInverse, Bool doUpdate) +void TerrainShader2Stage::updateNoise1(Matrix4x4 *destMatrix, const Matrix4x4 *curViewInverse, Bool doUpdate) { #define STRETCH_FACTOR ((float)(1/(63.0*MAP_XY_FACTOR/2))) /* covers 63/2 tiles */ - D3DXMATRIX scale; - - D3DXMatrixScaling(&scale, STRETCH_FACTOR, STRETCH_FACTOR,1); - *destMatrix = *curViewInverse * scale; - - D3DXMATRIX offset; - D3DXMatrixTranslation(&offset, m_xOffset, m_yOffset,0); - *destMatrix *= offset; + Matrix4x4 scale = W3DShaderManager_MakeTextureScale(STRETCH_FACTOR, STRETCH_FACTOR, 1); + Matrix4x4 offset = W3DShaderManager_MakeTextureTranslation(m_xOffset, m_yOffset, 0); + // TheSuperHackers @bugfix bobtista 19/06/2026 Column-vector Matrix4x4 multiply order is the + // reverse of the original D3DX row-vector order; with To_D3DMATRIX transposing on the way out, + // curViewInverse must be the rightmost (first-applied) factor or the cloud projection skews + // into scrolling diagonal bands on the terrain. + *destMatrix = offset * scale * (*curViewInverse); } -void TerrainShader2Stage::updateNoise2(D3DXMATRIX *destMatrix,D3DXMATRIX *curViewInverse, Bool doUpdate) +void TerrainShader2Stage::updateNoise2(Matrix4x4 *destMatrix, const Matrix4x4 *curViewInverse, Bool doUpdate) { - - D3DXMATRIX scale; - - D3DXMatrixScaling(&scale, STRETCH_FACTOR, STRETCH_FACTOR,1); - *destMatrix = *curViewInverse * scale; + Matrix4x4 scale = W3DShaderManager_MakeTextureScale(STRETCH_FACTOR, STRETCH_FACTOR, 1); + *destMatrix = scale * (*curViewInverse); } 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); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MAGFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_POINT); - } - if (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) { - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MIPFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - } + W3DShaderManager_SetTerrainBaseSamplers(); switch (pass) { - case 0: - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(0)->Peek_D3D_Texture()); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); + case 0: + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(0)); + W3DShaderManager_SetStageAddress2D(0, RB_TEXTURE_ADDRESS_CLAMP); // Modulate the diffuse color with the texture as lighting comes from diffuse. - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - 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_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->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); + 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()); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); + case 1: + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(1)); + W3DShaderManager_SetStageAddress2D(0, RB_TEXTURE_ADDRESS_CLAMP); // Modulate the diffuse color with the texture as lighting comes from diffuse. - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - 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->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_MODULATE); + 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(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 ); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); break; case 2: // Noise/cloud pass - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); + Matrix4x4 curView; + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, curView); //these states apply to all noise/cloud combination passes - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); + g_renderBackend->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_SELECTARG1); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); + W3DShaderManager_SetCameraSpaceTexcoord2(0); + W3DShaderManager_SetStageAddress2D(0, RB_TEXTURE_ADDRESS_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; - D3DXMatrixInverse(&inv, &det, &curView); + Matrix4x4 inv = curView.Inverse(); if (W3DShaderManager::getCurrentShader() == W3DShaderManager::ST_TERRAIN_BASE_NOISE12) - { - //setup cloud pass - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(2)->Peek_D3D_Texture()); + { + //setup cloud pass + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(2)); - updateNoise1(&curView,&inv); //update curView with texture matrix - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE0, curView); - //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); + updateNoise1(&curView,&inv); //update curView with texture matrix + W3DShaderManager_SetTextureTransform(0, curView); + //clouds always need bilinear filtering + W3DShaderManager_SetStageMinMagFilter(0, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_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); - //noise always needs point/linear filtering. Why point!? - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_MODULATE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); + updateNoise2(&curView,&inv); + W3DShaderManager_SetTextureTransform(1, curView); + //noise always needs point/linear filtering. Why point!? + W3DShaderManager_SetStageMinMagFilter(1, RB_TEXTURE_SAMPLE_POINT, RB_TEXTURE_SAMPLE_LINEAR); + + g_renderBackend->Set_Texture_Color_Argument(1, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(1, 2, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); + W3DShaderManager_SetCameraSpaceTexcoord2(1); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); + W3DShaderManager_SetStageAddress2D(1, RB_TEXTURE_ADDRESS_WRAP); } else { //only 1 noise or cloud texture // 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()); - 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); + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(2)); + updateNoise1(&curView,&inv); //update curView with texture matrix + W3DShaderManager_SetStageMinMagFilter(0, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); } else { - //setup noise pass - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(3)->Peek_D3D_Texture()); - 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); + //setup noise pass + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(3)); + updateNoise2(&curView,&inv); //update curView with texture matrix + W3DShaderManager_SetStageMinMagFilter(1, RB_TEXTURE_SAMPLE_POINT, RB_TEXTURE_SAMPLE_LINEAR); } - 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_Transform(D3DTS_TEXTURE0, curView); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); + W3DShaderManager_SetTextureTransform(0, curView); } break; } @@ -1800,113 +1737,95 @@ 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(); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); - - if (TheGlobalData && (TheGlobalData->m_bilinearTerrainTex || TheGlobalData->m_trilinearTerrainTex)) { - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MAGFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_POINT); - } - if (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) { - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MIPFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - } + g_renderBackend->Override_Terrain_Blend(true); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(0)->Peek_D3D_Texture()); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(1, W3DShaderManager::getShaderTexture(1)->Peek_D3D_Texture()); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLOROP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, 0); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_ADD); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, 1); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_DIFFUSE | D3DTA_COMPLEMENT | D3DTA_ALPHAREPLICATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_ADD); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG1, D3DTA_TFACTOR | D3DTA_COMPLEMENT); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); - - DX8Wrapper::Set_DX8_Texture(2, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_COLOROP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_TEXCOORDINDEX, 2); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_COLORARG1, D3DTA_TEXTURE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_COLORARG2, D3DTA_TEXTURE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_ALPHAOP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_ALPHAARG1, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); - - DX8Wrapper::Set_DX8_Texture(3, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_COLOROP, D3DTOP_SELECTARG1); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_TEXCOORDINDEX, 3); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_COLORARG1, D3DTA_DIFFUSE | 0 | D3DTA_ALPHAREPLICATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_ALPHAARG1, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); - - DX8Wrapper::Set_DX8_Texture(4, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_COLOROP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_TEXCOORDINDEX, 4); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_COLORARG1, D3DTA_CURRENT); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_ALPHAOP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_ALPHAARG1, D3DTA_CURRENT); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); - - DX8Wrapper::Set_DX8_Texture(5, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 5, D3DTSS_COLOROP, D3DTOP_ADD); - DX8Wrapper::Set_DX8_Texture_Stage_State( 5, D3DTSS_TEXCOORDINDEX, 5); - DX8Wrapper::Set_DX8_Texture_Stage_State( 5, D3DTSS_COLORARG1, D3DTA_DIFFUSE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 5, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 5, D3DTSS_ALPHAOP, D3DTOP_ADD); - DX8Wrapper::Set_DX8_Texture_Stage_State( 5, D3DTSS_ALPHAARG1, D3DTA_TFACTOR | D3DTA_COMPLEMENT); - DX8Wrapper::Set_DX8_Texture_Stage_State( 5, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); - - DX8Wrapper::Set_DX8_Texture(6, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 6, D3DTSS_COLOROP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 6, D3DTSS_TEXCOORDINDEX, 6); - DX8Wrapper::Set_DX8_Texture_Stage_State( 6, D3DTSS_COLORARG1, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 6, D3DTSS_COLORARG2, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 6, D3DTSS_ALPHAOP, D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 6, D3DTSS_ALPHAARG1, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 6, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); - - DX8Wrapper::Set_DX8_Texture(7, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 7, D3DTSS_COLOROP, D3DTOP_SELECTARG1); - DX8Wrapper::Set_DX8_Texture_Stage_State( 7, D3DTSS_TEXCOORDINDEX, 7); - DX8Wrapper::Set_DX8_Texture_Stage_State( 7, D3DTSS_COLORARG1, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 7, D3DTSS_COLORARG2, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 7, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); - DX8Wrapper::Set_DX8_Texture_Stage_State( 7, D3DTSS_ALPHAARG1, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 7, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); + //force WW3D2 system to set it's states so it won't later overwrite our custom settings. + g_renderBackend->Apply_Render_State_Changes(); + + W3DShaderManager_SetStageAddress2D(0, RB_TEXTURE_ADDRESS_CLAMP); + W3DShaderManager_SetStageAddress2D(1, RB_TEXTURE_ADDRESS_CLAMP); + W3DShaderManager_SetTerrainBaseSamplers(); + + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(0)); + W3DShaderManager_BindStageTexture(1, W3DShaderManager::getShaderTexture(1)); + + g_renderBackend->Set_Texture_Coord_Source(0, RB_TEXCOORD_MESH_UV, 0); + g_renderBackend->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_MODULATE); + + g_renderBackend->Set_Texture_Coord_Source(1, RB_TEXCOORD_MESH_UV, 1); + g_renderBackend->Set_Texture_Color_Argument(1, 1, RB_TEXARG_DIFFUSE | RB_TEXARG_COMPLEMENT | RB_TEXARG_ALPHAREPLICATE); + g_renderBackend->Set_Texture_Color_Argument(1, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_ADD); + g_renderBackend->Set_Texture_Alpha_Argument(1, 1, RB_TEXARG_TFACTOR | RB_TEXARG_COMPLEMENT); + g_renderBackend->Set_Texture_Alpha_Argument(1, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_ADD); + + W3DShaderManager_BindStageTexture(2, nullptr); + g_renderBackend->Set_Texture_Coord_Source(2, RB_TEXCOORD_MESH_UV, 2); + g_renderBackend->Set_Texture_Color_Argument(2, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(2, 2, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Operation(2, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Argument(2, 1, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Argument(2, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Operation(2, RB_TEXOP_MODULATE); + + W3DShaderManager_BindStageTexture(3, nullptr); + g_renderBackend->Set_Texture_Coord_Source(3, RB_TEXCOORD_MESH_UV, 3); + g_renderBackend->Set_Texture_Color_Argument(3, 1, RB_TEXARG_DIFFUSE | RB_TEXARG_ALPHAREPLICATE); + g_renderBackend->Set_Texture_Color_Argument(3, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(3, RB_TEXOP_SELECTARG1); + g_renderBackend->Set_Texture_Alpha_Argument(3, 1, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Argument(3, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Operation(3, RB_TEXOP_SELECTARG1); + + W3DShaderManager_BindStageTexture(4, nullptr); + g_renderBackend->Set_Texture_Coord_Source(4, RB_TEXCOORD_MESH_UV, 4); + g_renderBackend->Set_Texture_Color_Argument(4, 1, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Color_Argument(4, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(4, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Argument(4, 1, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Alpha_Argument(4, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Alpha_Operation(4, RB_TEXOP_MODULATE); + + W3DShaderManager_BindStageTexture(5, nullptr); + g_renderBackend->Set_Texture_Coord_Source(5, RB_TEXCOORD_MESH_UV, 5); + g_renderBackend->Set_Texture_Color_Argument(5, 1, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Argument(5, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(5, RB_TEXOP_ADD); + g_renderBackend->Set_Texture_Alpha_Argument(5, 1, RB_TEXARG_TFACTOR | RB_TEXARG_COMPLEMENT); + g_renderBackend->Set_Texture_Alpha_Argument(5, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Operation(5, RB_TEXOP_ADD); + + W3DShaderManager_BindStageTexture(6, nullptr); + g_renderBackend->Set_Texture_Coord_Source(6, RB_TEXCOORD_MESH_UV, 6); + g_renderBackend->Set_Texture_Color_Argument(6, 1, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Color_Argument(6, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Color_Operation(6, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Argument(6, 1, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Argument(6, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Operation(6, RB_TEXOP_MODULATE); + + W3DShaderManager_BindStageTexture(7, nullptr); + g_renderBackend->Set_Texture_Coord_Source(7, RB_TEXCOORD_MESH_UV, 7); + g_renderBackend->Set_Texture_Color_Argument(7, 1, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Color_Argument(7, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Color_Operation(7, RB_TEXOP_SELECTARG1); + g_renderBackend->Set_Texture_Alpha_Argument(7, 1, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Argument(7, 2, RB_TEXARG_TFACTOR); + g_renderBackend->Set_Texture_Alpha_Operation(7, RB_TEXOP_SELECTARG1); } else { //setup cloud noise/pass - 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); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_ALPHAOP, D3DTOP_DISABLE); - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Set_Texture_Color_Operation(2, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(2, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Color_Operation(3, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(3, RB_TEXOP_DISABLE); + g_renderBackend->Invalidate_Cached_Render_States(); terrainShader2Stage.set(2); } @@ -1914,29 +1833,30 @@ Int TerrainShader8Stage::set(Int pass) } void TerrainShader8Stage::reset() -{ - 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); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_ALPHAOP, D3DTOP_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_COLOROP, D3DTOP_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 4, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + { + g_renderBackend->Override_Terrain_Blend(false); + g_renderBackend->Set_Texture_Color_Operation(2, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(2, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Color_Operation(3, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(3, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Color_Operation(4, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(4, RB_TEXOP_DISABLE); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, nullptr); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(1, nullptr); - DX8Wrapper::Invalidate_Cached_Render_States(); + W3DShaderManager_BindStageTexture(0, nullptr); + W3DShaderManager_BindStageTexture(1, nullptr); + g_renderBackend->Invalidate_Cached_Render_States(); } Int TerrainShaderPixelShader::shutdown() { if (m_dwBasePixelShader) - DX8Wrapper::_Get_D3D_Device8()->DeletePixelShader(m_dwBasePixelShader); + g_renderBackend->Delete_Pixel_Shader(m_dwBasePixelShader); if (m_dwBaseNoise1PixelShader) - DX8Wrapper::_Get_D3D_Device8()->DeletePixelShader(m_dwBaseNoise1PixelShader); + g_renderBackend->Delete_Pixel_Shader(m_dwBaseNoise1PixelShader); if (m_dwBaseNoise2PixelShader) - DX8Wrapper::_Get_D3D_Device8()->DeletePixelShader(m_dwBaseNoise2PixelShader); + g_renderBackend->Delete_Pixel_Shader(m_dwBaseNoise2PixelShader); m_dwBasePixelShader=0; m_dwBaseNoise1PixelShader=0; @@ -1956,30 +1876,18 @@ Int TerrainShaderPixelShader::init() { if (res >= DC_GENERIC_PIXEL_SHADER_1_1) { - //this shader needs some assets that need to be loaded - //shader decleration - DWORD Declaration[]= - { - (D3DVSD_STREAM(0)), - (D3DVSD_REG(0, D3DVSDT_FLOAT3)), // Position - (D3DVSD_REG(1, D3DVSDT_D3DCOLOR)), // Diffuse - (D3DVSD_REG(2, D3DVSDT_FLOAT2)), // Texture Coordinates - (D3DVSD_REG(3, D3DVSDT_FLOAT2)), // Texture Coordinates - (D3DVSD_END()) - }; - //base version which doesn't apply any noise textures. - HRESULT hr = W3DShaderManager::LoadAndCreateD3DShader("shaders\\terrain.pso", &Declaration[0], 0, false, &m_dwBasePixelShader); + HRESULT hr = W3DShaderManager::LoadAndCreateLegacyShader("shaders\\terrain.pso", nullptr, 0, false, &m_dwBasePixelShader); if (FAILED(hr)) return FALSE; //version which blends 1 noise texture. - hr = W3DShaderManager::LoadAndCreateD3DShader("shaders\\terrainnoise.pso", &Declaration[0], 0, false, &m_dwBaseNoise1PixelShader); + hr = W3DShaderManager::LoadAndCreateLegacyShader("shaders\\terrainnoise.pso", nullptr, 0, false, &m_dwBaseNoise1PixelShader); if (FAILED(hr)) return FALSE; //version which blends 2 noise textures. - hr = W3DShaderManager::LoadAndCreateD3DShader("shaders\\terrainnoise2.pso", &Declaration[0], 0, false, &m_dwBaseNoise2PixelShader); + hr = W3DShaderManager::LoadAndCreateLegacyShader("shaders\\terrainnoise2.pso", nullptr, 0, false, &m_dwBaseNoise2PixelShader); if (FAILED(hr)) return FALSE; @@ -1999,105 +1907,82 @@ Int TerrainShaderPixelShader::init() Int TerrainShaderPixelShader::set(Int pass) { + // 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. - 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()); - 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); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); + W3DShaderManager_SetStageAddress2D(0, RB_TEXTURE_ADDRESS_CLAMP); + W3DShaderManager_SetStageAddress2D(1, RB_TEXTURE_ADDRESS_CLAMP); //tell pixel shader which UV set to use for each stage - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, 0 ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, 1 ); - - if (TheGlobalData && (TheGlobalData->m_bilinearTerrainTex || TheGlobalData->m_trilinearTerrainTex)) { - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MAGFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_POINT); - } - if (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) { - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MIPFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - } + g_renderBackend->Set_Texture_Coord_Source(0, RB_TEXCOORD_MESH_UV, 0); + g_renderBackend->Set_Texture_Coord_Source(1, RB_TEXCOORD_MESH_UV, 1); + + W3DShaderManager_SetTerrainBaseSamplers(); if (W3DShaderManager::getCurrentShader() >= W3DShaderManager::ST_TERRAIN_BASE_NOISE1) { - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); + Matrix4x4 curView; + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, curView); - D3DXMATRIX inv; - float det; - D3DXMatrixInverse(&inv, &det, &curView); + Matrix4x4 inv = curView.Inverse(); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); + W3DShaderManager_SetCameraSpaceTexcoord2(2); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); + W3DShaderManager_SetStageAddress2D(2, RB_TEXTURE_ADDRESS_WRAP); if (W3DShaderManager::getCurrentShader() == W3DShaderManager::ST_TERRAIN_BASE_NOISE12) { //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()); - DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_dwBaseNoise2PixelShader); + W3DShaderManager_SetStageAddress2D(3, RB_TEXTURE_ADDRESS_WRAP); + W3DShaderManager_BindStageTexture(2, W3DShaderManager::getShaderTexture(2)); + W3DShaderManager_BindStageTexture(3, W3DShaderManager::getShaderTexture(3)); + g_renderBackend->Set_Pixel_Shader(m_dwBaseNoise2PixelShader); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); + W3DShaderManager_SetStageMinMagFilter(2, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(3, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(3, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); + W3DShaderManager_SetStageMinMagFilter(3, RB_TEXTURE_SAMPLE_POINT, RB_TEXTURE_SAMPLE_LINEAR); terrainShader2Stage.updateNoise1(&curView,&inv); //update curView with texture matrix - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE2, curView); + W3DShaderManager_SetTextureTransform(2, curView); terrainShader2Stage.updateNoise2(&curView,&inv); //update curView with texture matrix - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE3, curView); + W3DShaderManager_SetTextureTransform(3, curView); - DX8Wrapper::Set_DX8_Texture_Stage_State(3, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State(3, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); + W3DShaderManager_SetCameraSpaceTexcoord2(3); } else { //single noise texture shader - DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_dwBaseNoise1PixelShader); + g_renderBackend->Set_Pixel_Shader(m_dwBaseNoise1PixelShader); 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); + W3DShaderManager_SetStageMinMagFilter(2, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_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); + W3DShaderManager_SetStageMinMagFilter(2, RB_TEXTURE_SAMPLE_POINT, RB_TEXTURE_SAMPLE_LINEAR); } - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE2, curView); + W3DShaderManager_SetTextureTransform(2, curView); } } else { //just base texturing - DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_dwBasePixelShader); + g_renderBackend->Set_Pixel_Shader(m_dwBasePixelShader); } return TRUE; @@ -2105,28 +1990,22 @@ Int TerrainShaderPixelShader::set(Int pass) void TerrainShaderPixelShader::reset() { - DX8Wrapper::_Get_D3D_Device8()->SetTexture(2,nullptr); //release reference to any texture - DX8Wrapper::_Get_D3D_Device8()->SetTexture(3,nullptr); //release reference to any texture - - DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(0); //turn off pixel shader - - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, nullptr); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(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); + g_renderBackend->Override_Terrain_Blend(false); + W3DShaderManager_BindStageTexture(2, nullptr); + W3DShaderManager_BindStageTexture(3, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|1); + g_renderBackend->Set_Pixel_Shader(0); //turn off pixel shader - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|2); + W3DShaderManager_BindStageTexture(0, nullptr); + W3DShaderManager_BindStageTexture(1, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|3); + W3DShaderManager_ResetMeshTexcoord(0, 0); + W3DShaderManager_ResetMeshTexcoord(1, 1); + W3DShaderManager_ResetMeshTexcoord(2, 2); + W3DShaderManager_ResetMeshTexcoord(3, 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. @@ -2156,33 +2035,27 @@ Int CloudTextureShader::init() /**Setup a certain texture stage to project our cloud texture*/ Int CloudTextureShader::set(Int stage) { - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); + Matrix4x4 curView; + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, curView); - D3DXMATRIX inv; - float det; - - D3DXMatrixInverse(&inv, &det, &curView); + Matrix4x4 inv = curView.Inverse(); //Get a texture matrix that applies the current cloud position terrainShader2Stage.updateNoise1(&curView,&inv,false); //update curView with texture matrix - 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_Transform((D3DTRANSFORMSTATETYPE )(D3DTS_TEXTURE0+stage), curView); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); + W3DShaderManager_SetCameraSpaceTexcoord2(stage); + W3DShaderManager_SetTextureTransform(stage, curView); + W3DShaderManager_SetStageMinMagFilter(stage, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); + W3DShaderManager_SetStageAddress2D(stage, RB_TEXTURE_ADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - 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_ALPHAARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); + g_renderBackend->Set_Texture_Color_Argument(stage, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(stage, 2, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Color_Operation(stage, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Argument(stage, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Argument(stage, 2, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Alpha_Operation(stage, RB_TEXOP_MODULATE); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(stage, W3DShaderManager::getShaderTexture(stage)->Peek_D3D_Texture()); + W3DShaderManager_BindStageTexture(stage, W3DShaderManager::getShaderTexture(stage)); m_stageOfSet=stage; return TRUE; @@ -2191,13 +2064,12 @@ 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); + W3DShaderManager_ResetMeshTexcoord(m_stageOfSet, m_stageOfSet); - DX8Wrapper::Set_DX8_Texture_Stage_State( m_stageOfSet, D3DTSS_COLOROP, D3DTOP_DISABLE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( m_stageOfSet, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); + g_renderBackend->Set_Texture_Color_Operation(m_stageOfSet, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(m_stageOfSet, RB_TEXOP_DISABLE); } /*===========================================================================================*/ @@ -2232,7 +2104,7 @@ W3DShaderInterface *RoadShaderList[]= Int RoadShaderPixelShader::shutdown() { if (m_dwBaseNoise2PixelShader) - DX8Wrapper::_Get_D3D_Device8()->DeletePixelShader(m_dwBaseNoise2PixelShader); + g_renderBackend->Delete_Pixel_Shader(m_dwBaseNoise2PixelShader); m_dwBaseNoise2PixelShader=0; @@ -2241,6 +2113,13 @@ Int RoadShaderPixelShader::shutdown() Int RoadShaderPixelShader::init() { +#if defined(GGC_RENDER_BACKEND_BGFX) + // bgfx cannot execute the legacy roadnoise2.pso bytecode. Let the + // two-stage road shader register the road variants so bgfx receives a + // fixed-function state cascade it can translate. + roadShader2Stage.init(); + return FALSE; +#else Int res; //this shader will also use the 2Stage shader for some of the passes so initialize it too. @@ -2248,19 +2127,8 @@ Int RoadShaderPixelShader::init() { if (res >= DC_GENERIC_PIXEL_SHADER_1_1) { - //this shader needs some assets that need to be loaded - //shader declaration - DWORD Declaration[]= - { - (D3DVSD_STREAM(0)), - (D3DVSD_REG(0, D3DVSDT_FLOAT3)), // Position - (D3DVSD_REG(1, D3DVSDT_D3DCOLOR)), // Diffuse - (D3DVSD_REG(2, D3DVSDT_FLOAT2)), // Texture Coordinates - (D3DVSD_END()) - }; - //version which blends 2 noise textures. - HRESULT hr = W3DShaderManager::LoadAndCreateD3DShader("shaders\\roadnoise2.pso", &Declaration[0], 0, false, &m_dwBaseNoise2PixelShader); + HRESULT hr = W3DShaderManager::LoadAndCreateLegacyShader("shaders\\roadnoise2.pso", nullptr, 0, false, &m_dwBaseNoise2PixelShader); if (FAILED(hr)) return FALSE; @@ -2272,71 +2140,67 @@ Int RoadShaderPixelShader::init() } } return FALSE; +#endif } Int RoadShaderPixelShader::set(Int pass) { - DX8Wrapper::Set_Texture(0,W3DShaderManager::getShaderTexture(0)); + if (g_renderBackend != nullptr && g_renderBackend->Has_Shader_Pipeline()) + { + g_renderBackend->Override_Terrain_Blend(false); + } + 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 ); + g_renderBackend->Set_Texture_Coord_Source(0, RB_TEXCOORD_MESH_UV, 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); + Matrix4x4 curView; + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, curView); - D3DXMATRIX inv; - float det; - D3DXMatrixInverse(&inv, &det, &curView); + Matrix4x4 inv = curView.Inverse(); if (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) - { DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); + { W3DShaderManager_SetStageMipFilter(0, RB_TEXTURE_SAMPLE_LINEAR); + W3DShaderManager_SetStageMipFilter(1, RB_TEXTURE_SAMPLE_LINEAR); } else - { DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MIPFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_POINT); + { W3DShaderManager_SetStageMipFilter(0, RB_TEXTURE_SAMPLE_POINT); + W3DShaderManager_SetStageMipFilter(1, RB_TEXTURE_SAMPLE_POINT); } - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); - - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); + W3DShaderManager_SetCameraSpaceTexcoord2(1); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); + W3DShaderManager_SetStageAddress2D(1, RB_TEXTURE_ADDRESS_WRAP); + W3DShaderManager_SetStageAddress2D(2, RB_TEXTURE_ADDRESS_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); + g_renderBackend->Set_Pixel_Shader(m_dwBaseNoise2PixelShader); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); + W3DShaderManager_SetStageMinMagFilter(1, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); + W3DShaderManager_SetStageMinMagFilter(2, RB_TEXTURE_SAMPLE_POINT, RB_TEXTURE_SAMPLE_LINEAR); terrainShader2Stage.updateNoise1(&curView,&inv, false); //get texture projection matrix - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE1, curView); + W3DShaderManager_SetTextureTransform(1, curView); terrainShader2Stage.updateNoise2(&curView,&inv, false); //get texture projection matrix - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE2, curView); + W3DShaderManager_SetTextureTransform(2, curView); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); + W3DShaderManager_SetCameraSpaceTexcoord2(2); return TRUE; } @@ -2344,22 +2208,15 @@ Int RoadShaderPixelShader::set(Int pass) void RoadShaderPixelShader::reset() { - DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(0); //turn off pixel shader - - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|0); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|1); + g_renderBackend->Set_Pixel_Shader(0); //turn off pixel shader - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|2); + W3DShaderManager_ResetMeshTexcoord(0, 0); + W3DShaderManager_ResetMeshTexcoord(1, 1); + W3DShaderManager_ResetMeshTexcoord(2, 2); + W3DShaderManager_ResetMeshTexcoord(3, 3); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - 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() @@ -2379,142 +2236,139 @@ Int RoadShader2Stage::init() Int RoadShader2Stage::set(Int pass) { + if (g_renderBackend != nullptr && g_renderBackend->Has_Shader_Pipeline()) + { + g_renderBackend->Override_Terrain_Blend(false); + } //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); - 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 ); - 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_ALPHAARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); + g_renderBackend->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_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_Texture_Coord_Source(0, RB_TEXCOORD_MESH_UV, 0); + 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 - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); + Matrix4x4 curView; + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, curView); - D3DXMATRIX inv; - float det; - D3DXMatrixInverse(&inv, &det, &curView); + Matrix4x4 inv = curView.Inverse(); - if (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - else - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_POINT); + W3DShaderManager_SetStageMipFilter( + 1, + (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) ? + RB_TEXTURE_SAMPLE_LINEAR : + RB_TEXTURE_SAMPLE_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); - - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_MODULATE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); - - if (W3DShaderManager::getCurrentShader() == W3DShaderManager::ST_ROAD_BASE_NOISE12) - { //full shader, apply noise 1 in pass 0. - DX8Wrapper::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); + W3DShaderManager_SetCameraSpaceTexcoord2(1); + + W3DShaderManager_SetStageAddress2D(1, RB_TEXTURE_ADDRESS_WRAP); + + g_renderBackend->Set_Texture_Color_Argument(1, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(1, 2, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Argument(1, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Argument(1, 2, RB_TEXARG_CURRENT); + // TheSuperHackers @refactor bobtista 16/07/2026 Restore the retail MODULATE alpha op. + // A bgfx-only DISABLE crept in with the DX8 usage strip; the branch is currently dead + // on bgfx (roads are forced to ST_ROAD_BASE and the extra-blend caller early-outs in + // the projected-decal shader path), so keep the retail text unconditionally. + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_MODULATE); + + if (W3DShaderManager::getCurrentShader() == W3DShaderManager::ST_ROAD_BASE_NOISE12) + { //full shader, apply noise 1 in pass 0. + g_renderBackend->Set_Texture(1,W3DShaderManager::getShaderTexture(1)); + W3DShaderManager_SetStageMinMagFilter(1, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); terrainShader2Stage.updateNoise1(&curView, &inv, false); //get texture projection matrix - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE1, curView); + W3DShaderManager_SetTextureTransform(1, curView); } else { //single noise texture shader if (W3DShaderManager::getCurrentShader() == W3DShaderManager::ST_ROAD_BASE_NOISE1) - { //cloud map - DX8Wrapper::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)); - 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); - } - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE1, curView); + { //cloud map + g_renderBackend->Set_Texture(1,W3DShaderManager::getShaderTexture(1)); + terrainShader2Stage.updateNoise1(&curView, &inv, false); //update curView with texture matrix + W3DShaderManager_SetStageMinMagFilter(1, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); + } + else + { //light map + g_renderBackend->Set_Texture(1,W3DShaderManager::getShaderTexture(2)); + terrainShader2Stage.updateNoise2(&curView,&inv, false); //update curView with texture matrix + W3DShaderManager_SetStageMinMagFilter(1, RB_TEXTURE_SAMPLE_POINT, RB_TEXTURE_SAMPLE_LINEAR); + } + W3DShaderManager_SetTextureTransform(1, curView); } } else { //just base texturing - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); } } else { //pass 1, apply additional noise pass - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); + Matrix4x4 curView; + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, curView); - D3DXMATRIX inv; - float det; - D3DXMatrixInverse(&inv, &det, &curView); + Matrix4x4 inv = curView.Inverse(); - if (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - else - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_POINT); + W3DShaderManager_SetStageMipFilter( + 1, + (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) ? + RB_TEXTURE_SAMPLE_LINEAR : + RB_TEXTURE_SAMPLE_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); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); + W3DShaderManager_SetStageMinMagFilter(1, RB_TEXTURE_SAMPLE_POINT, RB_TEXTURE_SAMPLE_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); + W3DShaderManager_SetCameraSpaceTexcoord2(1); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); + W3DShaderManager_SetStageAddress2D(1, RB_TEXTURE_ADDRESS_WRAP); //Copy alpha channel into stage 1 but mask out color channel by replacing with white. - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); + g_renderBackend->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); //Force color channel to white by copying the alpha into RGB - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE|D3DTA_ALPHAREPLICATE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_BLENDCURRENTALPHA); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_DIFFUSE | RB_TEXARG_ALPHAREPLICATE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_SELECTARG2); + g_renderBackend->Set_Texture_Alpha_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_SELECTARG1); + + g_renderBackend->Set_Texture_Color_Argument(1, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(1, 2, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_BLENDCURRENTALPHA); + g_renderBackend->Set_Texture_Alpha_Argument(1, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Argument(1, 2, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); //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); + W3DShaderManager_SetTextureTransform(0, curView); } return TRUE; @@ -2524,11 +2378,8 @@ void RoadShader2Stage::reset() { ShaderClass::Invalidate(); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|0); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|1); + W3DShaderManager_ResetMeshTexcoord(0, 0); + W3DShaderManager_ResetMeshTexcoord(1, 1); } /** List of all custom shader lists - each list in this list contains variations of the same @@ -2565,10 +2416,6 @@ W3DShaderManager::W3DShaderManager() { m_currentShader = ST_INVALID; m_currentFilter = FT_NULL_FILTER; - m_oldRenderSurface = nullptr; - m_renderTexture = nullptr; - m_newRenderSurface = nullptr; - m_oldDepthSurface = nullptr; m_renderingToTexture = false; Int i; for (i=0; iGetRenderTarget(&m_oldRenderSurface); - - if (hr != S_OK || !m_oldRenderSurface) - return; - - m_oldRenderSurface->GetDesc(&desc); - - // TheSuperHackers @bugfix Redirecting rendering to a non-multisampled texture - // while using a multisampled depth buffer is an API violation in DX8. - if (desc.MultiSampleType == D3DMULTISAMPLE_NONE) - { - hr=DX8Wrapper::_Get_D3D_Device8()->CreateTexture(desc.Width,desc.Height,1,D3DUSAGE_RENDERTARGET,desc.Format,D3DPOOL_DEFAULT,&m_renderTexture); - } - else - { - // Force failure path to avoid MSAA mismatch - hr = E_FAIL; - } - - if (hr != S_OK) + if (g_renderBackend != nullptr) { - SAFE_RELEASE(m_oldRenderSurface); - m_renderTexture = nullptr; - } else { - hr = m_renderTexture->GetSurfaceLevel(0, &m_newRenderSurface); - if (hr != S_OK) - { - SAFE_RELEASE(m_renderTexture); - m_newRenderSurface = nullptr; - } else { - hr = DX8Wrapper::_Get_D3D_Device8()->GetDepthStencilSurface(&m_oldDepthSurface); - if (hr != S_OK) - { - SAFE_RELEASE(m_newRenderSurface); - SAFE_RELEASE(m_renderTexture); - m_oldDepthSurface = nullptr; - } - } + g_renderBackend->Initialize_View_Capture(RB_VIEW_CAPTURE_TACTICAL); } } @@ -2673,10 +2484,11 @@ void W3DShaderManager::init() //============================================================================= void W3DShaderManager::shutdown() { - SAFE_RELEASE(m_newRenderSurface); - SAFE_RELEASE(m_renderTexture); - SAFE_RELEASE(m_oldRenderSurface); - SAFE_RELEASE(m_oldDepthSurface); + if (g_renderBackend != nullptr) + { + g_renderBackend->Release_View_Capture(RB_VIEW_CAPTURE_TACTICAL); + } + m_renderingToTexture = false; m_currentShader = ST_INVALID; m_currentFilter = FT_NULL_FILTER; //release any assets associated with a shader (vertex/pixel shaders, textures, etc.) @@ -2702,6 +2514,40 @@ 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); +} + +// TheSuperHackers @feature bobtista 17/07/2026 Push the static noise/lightmap layer +// (DX8 ST_TERRAIN_BASE_NOISE2) through the backend the same way as the cloud shadow; +// the uber shader multiplies it into terrain and other ground draws in the same single +// pass. DX8Backend ignores this and keeps its own multi-pass TSS cascade. +void W3DShaderManager::pushLightMapToBackend(Bool enabled, TextureClass * noiseTex) +{ + if (g_renderBackend == nullptr) + { + return; + } + const float stretch = (float)(1.0 / (63.0 * MAP_XY_FACTOR / 2.0)); + g_renderBackend->Set_Light_Map_Params(enabled ? true : false, stretch, noiseTex); +} + // 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 @@ -2786,43 +2632,14 @@ Bool W3DShaderManager::filterSetup(FilterTypes filter, FilterModes mode) /*Draws 2 triangles covering the viewport given the current render states*/ void W3DShaderManager::drawViewport(Int color) { - LPDIRECT3DDEVICE8 pDev=DX8Wrapper::_Get_D3D_Device8(); - - struct _TRANS_LIT_TEX_VERTEX { - D3DXVECTOR4 p; - DWORD color; // diffuse color - float u; - float v; - } v[4]; - - Int xpos, ypos, width, height; - - TheTacticalView->getOrigin(&xpos,&ypos); - width=TheTacticalView->getWidth(); - height=TheTacticalView->getHeight(); - - //bottom right - v[0].p = D3DXVECTOR4( xpos+width-0.5f, ypos+height-0.5f, 0.0f, 1.0f ); - v[0].u = (Real)(xpos+width)/(Real)TheDisplay->getWidth(); v[0].v = (Real)(ypos+height)/(Real)TheDisplay->getHeight(); - //top right - v[1].p = D3DXVECTOR4( xpos+width-0.5f, ypos-0.5f, 0.0f, 1.0f ); - v[1].u = (Real)(xpos+width)/(Real)TheDisplay->getWidth(); v[1].v = (Real)(ypos)/(Real)TheDisplay->getHeight(); - //bottom left - v[2].p = D3DXVECTOR4( xpos-0.5f, ypos+height-0.5f, 0.0f, 1.0f ); - v[2].u = (Real)(xpos)/(Real)TheDisplay->getWidth(); v[2].v = (Real)(ypos+height)/(Real)TheDisplay->getHeight(); - //top left - v[3].p = D3DXVECTOR4( xpos-0.5f, ypos-0.5f, 0.0f, 1.0f ); - v[3].u = (Real)(xpos)/(Real)TheDisplay->getWidth(); v[3].v = (Real)(ypos)/(Real)TheDisplay->getHeight(); - v[0].color = color; - v[1].color = color; - v[2].color = color; - v[3].color = color; - - //draw polygons like this is very inefficient but for only 2 triangles, it's - //not worth bothering with index/vertex buffers. - pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1); + if (g_renderBackend == nullptr) + { + return; + } - pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX)); + RenderBackendScreenVertex v[4]; + W3DShaderManager_FillViewportQuad(v, color, FALSE); + g_renderBackend->Draw_Screen_Quad(v, 4, false); } // W3DShaderManager::startRenderToTexture ======================================================= @@ -2833,19 +2650,10 @@ void W3DShaderManager::startRenderToTexture() { DEBUG_ASSERTCRASH(!m_renderingToTexture, ("Already rendering to texture - cannot nest calls.")); - if (m_renderingToTexture || m_newRenderSurface==nullptr || m_oldDepthSurface==nullptr) return; - HRESULT hr = DX8Wrapper::_Get_D3D_Device8()->SetRenderTarget(m_newRenderSurface,m_oldDepthSurface); - - // TheSuperHackers @bugfix If SetRenderTarget fails (e.g. due to MSAA forced by driver - // profile causing a depth buffer mismatch that D3DSURFACE_DESC doesn't report), permanently - // disable RTT to prevent repeated failures and accidental backbuffer clears. - if (hr != S_OK) + if (m_renderingToTexture || + g_renderBackend == nullptr || + !g_renderBackend->Begin_View_Capture(RB_VIEW_CAPTURE_TACTICAL)) { - // Permanently disable RTT - SAFE_RELEASE(m_newRenderSurface); - SAFE_RELEASE(m_renderTexture); - SAFE_RELEASE(m_oldRenderSurface); - SAFE_RELEASE(m_oldDepthSurface); return; } @@ -2855,56 +2663,59 @@ 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); - 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 + g_renderBackend->Set_Color_Write_Enable(true, true, true, false); //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); } } -// W3DShaderManager::startRenderToTexture ======================================================= +// W3DShaderManager::endRenderToTexture ======================================================= /** Ends rendering to a texture. */ //============================================================================= -IDirect3DTexture8 *W3DShaderManager::endRenderToTexture() +Bool W3DShaderManager::endRenderToTexture() { DEBUG_ASSERTCRASH(m_renderingToTexture, ("Not rendering to texture.")); - if (!m_renderingToTexture) return nullptr; - HRESULT hr = DX8Wrapper::_Get_D3D_Device8()->SetRenderTarget(m_oldRenderSurface,m_oldDepthSurface); //restore original render target - DEBUG_ASSERTCRASH(hr==S_OK, ("Set target failed unexpectedly.")); - if (hr == S_OK) + if (!m_renderingToTexture || g_renderBackend == nullptr) { - //assume render target texture will be in stage 0. Most hardware has "conditional" support for - //non-power-of-2 textures so we must force some required states: - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSW, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_MIPFILTER, D3DTEXF_NONE); - - m_renderingToTexture = false; + return FALSE; } - return m_renderTexture; + + Bool result = g_renderBackend->End_View_Capture(RB_VIEW_CAPTURE_TACTICAL); + DEBUG_ASSERTCRASH(result, ("Set target failed unexpectedly.")); + m_renderingToTexture = false; + return result; +} + +Bool W3DShaderManager::canRenderToTexture() +{ + return g_renderBackend != nullptr && + g_renderBackend->Supports_View_Capture(RB_VIEW_CAPTURE_TACTICAL); } -/**Returns texture containing the image that was last rendered using any of the effects requiring render target -textures. Used mostly for cross-fading effects that need an unmodified version of the view before the effect -was applied. NOTE: This texture does not survive device reset.. so quit effect on reset!*/ -IDirect3DTexture8 *W3DShaderManager::getRenderTexture() +Bool W3DShaderManager::hasRenderTexture() { - return m_renderTexture; + return g_renderBackend != nullptr && + g_renderBackend->Has_View_Capture(RB_VIEW_CAPTURE_TACTICAL); +} + +Bool W3DShaderManager::isRenderingToTexture() +{ + return m_renderingToTexture || + (g_renderBackend != nullptr && + g_renderBackend->Is_View_Capture_Active(RB_VIEW_CAPTURE_TACTICAL)); } enum GraphicsVenderID CPP_11(: Int) @@ -2926,72 +2737,69 @@ ChipsetType W3DShaderManager::getChipset() return (ChipsetType)TheGlobalData->m_chipSetType; ChipsetType chip=DC_UNKNOWN; - IDirect3D8* d3d8Interface=DX8Wrapper::_Get_D3D8(); + RenderBackendDeviceIdentity deviceIdentity = {}; - if (d3d8Interface && DX8Wrapper::_Get_D3D_Device8()) + if (g_renderBackend != nullptr && + g_renderBackend->Get_Device_Identity(deviceIdentity)) { + m_driverVersion = static_cast<__int64>(deviceIdentity.driver_version); - D3DADAPTER_IDENTIFIER8 did; - ::ZeroMemory(&did, sizeof(D3DADAPTER_IDENTIFIER8)); - /* HRESULT res = */ d3d8Interface->GetAdapterIdentifier(0,D3DENUM_NO_WHQL_LEVEL,&did); - *((LARGE_INTEGER*)&m_driverVersion) = did.DriverVersion; - - if(did.VendorId == DC_NVIDIA_VENDOR_ID) + if(deviceIdentity.vendor_id == DC_NVIDIA_VENDOR_ID) { m_currentVendor = DC_NVIDIA_VENDOR_ID; - if (did.DeviceId == 0x20) + if (deviceIdentity.device_id == 0x20) return DC_TNT; - if (did.DeviceId >= 0x28 && did.DeviceId < 0x100) + if (deviceIdentity.device_id >= 0x28 && deviceIdentity.device_id < 0x100) return DC_TNT2; - if ( (did.DeviceId >= 0x100 && did.DeviceId <= 0x103) || //GeForce - (did.DeviceId >= 0x110 && did.DeviceId <= 0x113) || //GeForce2 MX - (did.DeviceId >= 0x150 && did.DeviceId <= 0x153) ) //GeForce2 + if ( (deviceIdentity.device_id >= 0x100 && deviceIdentity.device_id <= 0x103) || //GeForce + (deviceIdentity.device_id >= 0x110 && deviceIdentity.device_id <= 0x113) || //GeForce2 MX + (deviceIdentity.device_id >= 0x150 && deviceIdentity.device_id <= 0x153) ) //GeForce2 return DC_GEFORCE2; - if (did.DeviceId >= 0x200 && did.DeviceId < 0x250) + if (deviceIdentity.device_id >= 0x200 && deviceIdentity.device_id < 0x250) return DC_GEFORCE3; - if (did.DeviceId >= 0x250) + if (deviceIdentity.device_id >= 0x250) return DC_GEFORCE4; } else - if(did.VendorId == DC_3DFX_VENDOR_ID) + if(deviceIdentity.vendor_id == DC_3DFX_VENDOR_ID) { m_currentVendor = DC_3DFX_VENDOR_ID; - if (did.DeviceId == 0x0002) + if (deviceIdentity.device_id == 0x0002) return DC_VOODOO2; - if (did.DeviceId == 0x0005) + if (deviceIdentity.device_id == 0x0005) return DC_VOODOO3; - if (did.DeviceId == 0x0008) ///@todo: Just guessing on this one - find actual Voodoo4 deviceID. + if (deviceIdentity.device_id == 0x0008) ///@todo: Just guessing on this one - find actual Voodoo4 deviceID. return DC_VOODOO4; - if (did.DeviceId == 0x0009) + if (deviceIdentity.device_id == 0x0009) return DC_VOODOO5; } else - if(did.VendorId == DC_ATI_VENDOR_ID) + if(deviceIdentity.vendor_id == DC_ATI_VENDOR_ID) { m_currentVendor = DC_ATI_VENDOR_ID; - if (did.DeviceId == 0x5144) + if (deviceIdentity.device_id == 0x5144) return DC_RADEON; - if (did.DeviceId == 0x514C) + if (deviceIdentity.device_id == 0x514C) return DC_RADEON_8500; - if (did.DeviceId == 0x4e44) + if (deviceIdentity.device_id == 0x4e44) return DC_RADEON_9700; } //None of the vendor specific ID's matched so use generic means to classify the card - Int maxTextures=DX8Wrapper::Get_Current_Caps()->Get_Max_Simultaneous_Textures(); + Int maxTextures = deviceIdentity.max_simultaneous_textures; Real pixelShaderVersion; char buf[256]; //Convert version to Real - sprintf(buf,"%d.%d",DX8Wrapper::Get_Current_Caps()->Get_Pixel_Shader_Major_Version(),DX8Wrapper::Get_Current_Caps()->Get_Pixel_Shader_Minor_Version()); + sprintf(buf,"%d.%d", deviceIdentity.pixel_shader_major, deviceIdentity.pixel_shader_minor); sscanf(buf,"%f",&pixelShaderVersion); if (maxTextures >= 4) @@ -3008,15 +2816,31 @@ ChipsetType W3DShaderManager::getChipset() } //============================================================================= -// WaterRenderObjClass::LoadAndCreateShader +// W3DShaderManager::LoadAndCreateLegacyShader //============================================================================= -/** Loads and creates a D3D pixel or vertex shader.*/ +/** Loads and creates a backend pixel or vertex shader from a legacy shader file.*/ //============================================================================= -HRESULT W3DShaderManager::LoadAndCreateD3DShader(const char* strFilePath, const DWORD* pDeclaration, DWORD Usage, Bool ShaderType, DWORD* pHandle) +HRESULT W3DShaderManager::LoadAndCreateLegacyShader(const char* strFilePath, const DWORD* pDeclaration, DWORD Usage, Bool ShaderType, DWORD* pHandle) { if (getChipset() < DC_GENERIC_PIXEL_SHADER_1_1) return E_FAIL; //don't allow loading any shaders if hardware can't handle it. + if (pHandle == nullptr) + return E_FAIL; + + const RenderBackendShaderKind shaderKind = ShaderType ? RB_SHADER_VERTEX : RB_SHADER_PIXEL; + unsigned long backendHandle = 0; + if (g_renderBackend != nullptr && + g_renderBackend->Load_Legacy_Shader(strFilePath, + reinterpret_cast(pDeclaration), + static_cast(Usage), + shaderKind, + &backendHandle)) + { + *pHandle = static_cast(backendHandle); + return S_OK; + } + try { File *file = nullptr; @@ -3047,11 +2871,19 @@ HRESULT W3DShaderManager::LoadAndCreateD3DShader(const char* strFilePath, const if (ShaderType) // SHADERTYPE_VERTEX { - hr = DX8Wrapper::_Get_D3D_Device8()->CreateVertexShader(pDeclaration, pShader, pHandle, Usage); + hr = (g_renderBackend != nullptr && + g_renderBackend->Create_Vertex_Shader( + reinterpret_cast(pDeclaration), + reinterpret_cast(pShader), + static_cast(Usage), + &backendHandle)) ? S_OK : E_FAIL; } else // SHADERTYPE_PIXEL { - hr = DX8Wrapper::_Get_D3D_Device8()->CreatePixelShader(pShader, pHandle); + hr = (g_renderBackend != nullptr && + g_renderBackend->Create_Pixel_Shader( + reinterpret_cast(pShader), + &backendHandle)) ? S_OK : E_FAIL; } HeapFree(GetProcessHeap(), 0, (void*)pShader); @@ -3061,6 +2893,7 @@ HRESULT W3DShaderManager::LoadAndCreateD3DShader(const char* strFilePath, const OutputDebugString( "Failed to create shader\n "); return E_FAIL; } + *pHandle = static_cast(backendHandle); } catch(...) { @@ -3203,47 +3036,17 @@ Int W3DShaderManager::setShroudTex(Int stage) W3DShroud *shroud; if ((shroud=TheTerrainRenderObject->getShroud()) != nullptr) { - DX8Wrapper::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); - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_COLORARG2, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_COLOROP, D3DTOP_MODULATE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( stage, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 ); - - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); - - D3DXMATRIX inv; - float det; - D3DXMatrixInverse(&inv, &det, &curView); - - D3DXMATRIX scale,offset; - - //We need to make all world coordinates be relative to the heightmap data origin since that - //is where the shroud begins. - - float xoffset = 0; - float yoffset = 0; - Real width=shroud->getCellWidth(); - Real height=shroud->getCellHeight(); - - if (TheTerrainRenderObject->getMap()) - { //subtract origin position from all coordinates. Origin is shifted by 1 cell width/height to allow for unused border texels. - xoffset = -(float)shroud->getDrawOriginX() + width; - yoffset = -(float)shroud->getDrawOriginY() + height; - } + g_renderBackend->Set_Texture(stage, shroud->getShroudTexture()); - D3DXMatrixTranslation(&offset, xoffset, yoffset,0); + W3DShaderManager_SetCameraSpaceTexcoord2(stage); + g_renderBackend->Set_Texture_Color_Argument(stage, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(stage, 2, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Alpha_Argument(stage, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Argument(stage, 2, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Color_Operation(stage, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Operation(stage, RB_TEXOP_SELECTARG2); - width = 1.0f/(width*shroud->getTextureWidth()); - height = 1.0f/(height*shroud->getTextureHeight()); - D3DXMatrixScaling(&scale, width, height, 1); - curView = (inv * offset) * scale; - DX8Wrapper::_Set_DX8_Transform((D3DTRANSFORMSTATETYPE )(D3DTS_TEXTURE0+stage), curView); + W3DShaderManager_SetShroudTextureTransform(stage, shroud); return TRUE; } return FALSE; @@ -3256,12 +3059,12 @@ 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; + 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; return TRUE; @@ -3269,198 +3072,138 @@ Int FlatTerrainShader2Stage::init() void FlatTerrainShader2Stage::reset() { + g_renderBackend->Override_Terrain_Blend(false); ShaderClass::Invalidate(); //Free references to textures - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, nullptr); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(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); + W3DShaderManager_BindStageTexture(0, nullptr); + W3DShaderManager_BindStageTexture(1, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|1); + W3DShaderManager_ResetMeshTexcoord(0, 0); + W3DShaderManager_ResetMeshTexcoord(1, 1); } Int FlatTerrainShader2Stage::set(Int pass) { + 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. - 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); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MAGFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_POINT); - } - if (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) { - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MIPFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MIPFILTER, D3DTEXF_POINT); - } + W3DShaderManager_SetFlatTerrainBaseSamplers(); switch (pass) { case 0: - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); + W3DShaderManager_SetStageAddress2D(0, RB_TEXTURE_ADDRESS_CLAMP); // Modulate the diffuse color with the texture as lighting comes from diffuse. - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); + g_renderBackend->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_DIFFUSE); if (W3DShaderManager::getShaderTexture(0)) { - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, W3DShaderManager::getShaderTexture(0)->Peek_D3D_Texture()); - 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 ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(0)); + g_renderBackend->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); + W3DShaderManager_SetCameraSpaceTexcoord2(0); //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). W3DShroud *shroud; if ((shroud=TheTerrainRenderObject->getShroud()) != nullptr) { - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); - - D3DXMATRIX inv; - float det; - D3DXMatrixInverse(&inv, &det, &curView); - - D3DXMATRIX scale,offset; - - //We need to make all world coordinates be relative to the heightmap data origin since that - //is where the shroud begins. - - float xoffset = 0; - float yoffset = 0; - Real width=shroud->getCellWidth(); - Real height=shroud->getCellHeight(); - - if (TheTerrainRenderObject->getMap()) - { //subtract origin position from all coordinates. Origin is shifted by 1 cell width/height to allow for unused border texels. - xoffset = -(float)shroud->getDrawOriginX() + width; - yoffset = -(float)shroud->getDrawOriginY() + height; - } - - D3DXMatrixTranslation(&offset, xoffset, yoffset,0); - - width = 1.0f/(width*shroud->getTextureWidth()); - height = 1.0f/(height*shroud->getTextureHeight()); - D3DXMatrixScaling(&scale, width, height, 1); - curView = (inv * offset) * scale; - DX8Wrapper::_Set_DX8_Transform((D3DTRANSFORMSTATETYPE )(D3DTS_TEXTURE0), curView); + W3DShaderManager_SetShroudTextureTransform(0, shroud); } } else { - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG2 ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_TEXCOORDINDEX, 0 ); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_SELECTARG2); + g_renderBackend->Set_Texture_Coord_Source(0, RB_TEXCOORD_MESH_UV, 0); } - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); + W3DShaderManager_SetStageAddress2D(1, RB_TEXTURE_ADDRESS_CLAMP); // Modulate the diffuse color with the texture as lighting comes from diffuse. - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_MODULATE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - 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_Texture_Color_Argument(1, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(1, 2, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); + W3DShaderManager_ResetMeshTexcoord(1, 0); + g_renderBackend->Set_Alpha_Blend_Enable(false); break; case 1: // Noise/cloud pass - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); + Matrix4x4 curView; + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, curView); //these states apply to all noise/cloud combination passes - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); + g_renderBackend->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_SELECTARG1); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); + W3DShaderManager_SetCameraSpaceTexcoord2(0); + W3DShaderManager_SetStageAddress2D(0, RB_TEXTURE_ADDRESS_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; - D3DXMatrixInverse(&inv, &det, &curView); + Matrix4x4 inv = curView.Inverse(); if (W3DShaderManager::getCurrentShader() == W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE12) { //setup cloud pass terrainShader2Stage.updateNoise1(&curView,&inv); //update curView with texture matrix - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE0, curView); + W3DShaderManager_SetTextureTransform(0, curView); //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_SetStageMinMagFilter(0, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); + W3DShaderManager_BindStageTexture(0, W3DShaderManager::getShaderTexture(2)); //setup noise pass terrainShader2Stage.updateNoise2(&curView,&inv); - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE1, curView); + W3DShaderManager_SetTextureTransform(1, curView); //noise always needs point/linear filtering. Why point!? - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_MODULATE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); + W3DShaderManager_SetStageMinMagFilter(1, RB_TEXTURE_SAMPLE_POINT, RB_TEXTURE_SAMPLE_LINEAR); + + g_renderBackend->Set_Texture_Color_Argument(1, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(1, 2, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); + W3DShaderManager_SetCameraSpaceTexcoord2(1); - 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_SetStageAddress2D(1, RB_TEXTURE_ADDRESS_WRAP); + 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); + W3DShaderManager_SetStageMinMagFilter(0, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); } 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); + W3DShaderManager_SetStageMinMagFilter(1, RB_TEXTURE_SAMPLE_POINT, RB_TEXTURE_SAMPLE_LINEAR); } - 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_Transform(D3DTS_TEXTURE0, curView); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); + W3DShaderManager_SetTextureTransform(0, curView); } break; } @@ -3476,16 +3219,16 @@ Int FlatTerrainShader2Stage::set(Int pass) Int FlatTerrainShaderPixelShader::shutdown() { if (m_dwBasePixelShader) - DX8Wrapper::_Get_D3D_Device8()->DeletePixelShader(m_dwBasePixelShader); + g_renderBackend->Delete_Pixel_Shader(m_dwBasePixelShader); if (m_dwBase0PixelShader) - DX8Wrapper::_Get_D3D_Device8()->DeletePixelShader(m_dwBase0PixelShader); + g_renderBackend->Delete_Pixel_Shader(m_dwBase0PixelShader); if (m_dwBaseNoise1PixelShader) - DX8Wrapper::_Get_D3D_Device8()->DeletePixelShader(m_dwBaseNoise1PixelShader); + g_renderBackend->Delete_Pixel_Shader(m_dwBaseNoise1PixelShader); if (m_dwBaseNoise2PixelShader) - DX8Wrapper::_Get_D3D_Device8()->DeletePixelShader(m_dwBaseNoise2PixelShader); + g_renderBackend->Delete_Pixel_Shader(m_dwBaseNoise2PixelShader); m_dwBasePixelShader=0; m_dwBase0PixelShader=0; @@ -3508,35 +3251,23 @@ Int FlatTerrainShaderPixelShader::init() { if (res >= DC_GENERIC_PIXEL_SHADER_1_1) { - //this shader needs some assets that need to be loaded - //shader decleration - DWORD Declaration[]= - { - (D3DVSD_STREAM(0)), - (D3DVSD_REG(0, D3DVSDT_FLOAT3)), // Position - (D3DVSD_REG(1, D3DVSDT_D3DCOLOR)), // Diffuse - (D3DVSD_REG(2, D3DVSDT_FLOAT2)), // Texture Coordinates - (D3DVSD_REG(3, D3DVSDT_FLOAT2)), // Texture Coordinates - (D3DVSD_END()) - }; - //base version which doesn't apply any noise textures. - HRESULT hr = W3DShaderManager::LoadAndCreateD3DShader("shaders\\fterrain.pso", &Declaration[0], 0, false, &m_dwBasePixelShader); + HRESULT hr = W3DShaderManager::LoadAndCreateLegacyShader("shaders\\fterrain.pso", nullptr, 0, false, &m_dwBasePixelShader); if (FAILED(hr)) return FALSE; //base version which doesn't apply any shroud textures. - hr = W3DShaderManager::LoadAndCreateD3DShader("shaders\\fterrain0.pso", &Declaration[0], 0, false, &m_dwBase0PixelShader); + hr = W3DShaderManager::LoadAndCreateLegacyShader("shaders\\fterrain0.pso", nullptr, 0, false, &m_dwBase0PixelShader); if (FAILED(hr)) return FALSE; //version which blends 1 noise texture. - hr = W3DShaderManager::LoadAndCreateD3DShader("shaders\\fterrainnoise.pso", &Declaration[0], 0, false, &m_dwBaseNoise1PixelShader); + hr = W3DShaderManager::LoadAndCreateLegacyShader("shaders\\fterrainnoise.pso", nullptr, 0, false, &m_dwBaseNoise1PixelShader); if (FAILED(hr)) return FALSE; //version which blends 2 noise textures. - hr = W3DShaderManager::LoadAndCreateD3DShader("shaders\\fterrainnoise2.pso", &Declaration[0], 0, false, &m_dwBaseNoise2PixelShader); + hr = W3DShaderManager::LoadAndCreateLegacyShader("shaders\\fterrainnoise2.pso", nullptr, 0, false, &m_dwBaseNoise2PixelShader); if (FAILED(hr)) return FALSE; @@ -3556,37 +3287,32 @@ Int FlatTerrainShaderPixelShader::init() Int FlatTerrainShaderPixelShader::set(Int pass) { + // 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] - 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)); + W3DShaderManager_SetStageAddress2D(0, RB_TEXTURE_ADDRESS_CLAMP); + 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(); - DX8Wrapper::Set_DX8_Texture_Stage_State( curStage, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( curStage, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); + W3DShaderManager_SetStageAddress2D(curStage, RB_TEXTURE_ADDRESS_CLAMP); //tell pixel shader which UV set to use for each stage - DX8Wrapper::Set_DX8_Texture_Stage_State( curStage, D3DTSS_TEXCOORDINDEX, 0 ); - DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); + W3DShaderManager_ResetMeshTexcoord(curStage, 0); - if (TheGlobalData && (TheGlobalData->m_bilinearTerrainTex || TheGlobalData->m_trilinearTerrainTex)) { - DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_MAGFILTER, D3DTEXF_POINT); - } - if (TheGlobalData && TheGlobalData->m_trilinearTerrainTex) { - DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); - } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_MIPFILTER, D3DTEXF_POINT); + { + const RenderBackendTextureSampleFilter min_mag_filter = W3DShaderManager_GetTerrainMinMagFilter(); + g_renderBackend->Set_Texture_Sample_Filter( + curStage, + min_mag_filter, + min_mag_filter, + W3DShaderManager_GetTerrainStage0MipFilter()); } curStage = 0; @@ -3594,48 +3320,14 @@ Int FlatTerrainShaderPixelShader::set(Int pass) W3DShroud *shroud = TheTerrainRenderObject->getShroud(); if (shroud) { - DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); - DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); + W3DShaderManager_SetCameraSpaceTexcoord2(curStage); //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). - { - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); - - D3DXMATRIX inv; - float det; - D3DXMatrixInverse(&inv, &det, &curView); - - D3DXMATRIX scale,offset; - - //We need to make all world coordinates be relative to the heightmap data origin since that - //is where the shroud begins. - - float xoffset = 0; - float yoffset = 0; - Real width=shroud->getCellWidth(); - Real height=shroud->getCellHeight(); - - if (TheTerrainRenderObject->getMap()) - { //subtract origin position from all coordinates. Origin is shifted by 1 cell width/height to allow for unused border texels. - xoffset = -(float)shroud->getDrawOriginX() + width; - yoffset = -(float)shroud->getDrawOriginY() + height; - } - - D3DXMatrixTranslation(&offset, xoffset, yoffset,0); - - width = 1.0f/(width*shroud->getTextureWidth()); - height = 1.0f/(height*shroud->getTextureHeight()); - D3DXMatrixScaling(&scale, width, height, 1); - curView = (inv * offset) * scale; - DX8Wrapper::_Set_DX8_Transform((D3DTRANSFORMSTATETYPE )(D3DTS_TEXTURE0+curStage), curView); - } - DX8Wrapper::Set_DX8_Texture_Stage_State( curStage, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - 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_SetShroudTextureTransform(curStage, shroud); + W3DShaderManager_SetStageAddress2D(curStage, RB_TEXTURE_ADDRESS_CLAMP); + W3DShaderManager_SetStageMinMagFilter(curStage, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); + W3DShaderManager_BindStageTexture(curStage, shroud->getShroudTexture()); curStage++; if (curStage==1) curStage++; } @@ -3643,24 +3335,19 @@ Int FlatTerrainShaderPixelShader::set(Int pass) Bool doNoise1 = (W3DShaderManager::getCurrentShader() == W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE1 || W3DShaderManager::getCurrentShader() == W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE12); if (doNoise1) { // Cloud pass. - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); + Matrix4x4 curView; + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, curView); - D3DXMATRIX inv; - float det; - D3DXMatrixInverse(&inv, &det, &curView); + Matrix4x4 inv = curView.Inverse(); - DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); + W3DShaderManager_SetCameraSpaceTexcoord2(curStage); - 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_SetStageAddress2D(curStage, RB_TEXTURE_ADDRESS_WRAP); + 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); - DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); + W3DShaderManager_SetTextureTransform(curStage, curView); + W3DShaderManager_SetStageMinMagFilter(curStage, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); curStage++; if (curStage==1) curStage++; @@ -3670,70 +3357,54 @@ Int FlatTerrainShaderPixelShader::set(Int pass) W3DShaderManager::getCurrentShader() == W3DShaderManager::ST_FLAT_TERRAIN_BASE_NOISE12); if (doNoise2) { - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); + Matrix4x4 curView; + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, curView); - D3DXMATRIX inv; - float det; - D3DXMatrixInverse(&inv, &det, &curView); + Matrix4x4 inv = curView.Inverse(); - DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); + W3DShaderManager_SetCameraSpaceTexcoord2(curStage); - 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_SetStageAddress2D(curStage, RB_TEXTURE_ADDRESS_WRAP); + 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); - DX8Wrapper::Set_DX8_Texture_Stage_State(curStage, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); + W3DShaderManager_SetTextureTransform(curStage, curView); + W3DShaderManager_SetStageMinMagFilter(curStage, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); curStage++; if (curStage==1) curStage++; } if (curStage<2) { - DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_dwBase0PixelShader); + g_renderBackend->Set_Pixel_Shader(m_dwBase0PixelShader); } else if (curStage==2) { - DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_dwBasePixelShader); + g_renderBackend->Set_Pixel_Shader(m_dwBasePixelShader); } else if (curStage==3) { - DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_dwBaseNoise1PixelShader); + g_renderBackend->Set_Pixel_Shader(m_dwBaseNoise1PixelShader); }else if (curStage==4) { - DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_dwBaseNoise2PixelShader); + g_renderBackend->Set_Pixel_Shader(m_dwBaseNoise2PixelShader); } - DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ALPHABLENDENABLE, false); - DX8Wrapper::Apply_Render_State_Changes(); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(curStage, W3DShaderManager::getShaderTexture(3)->Peek_D3D_Texture()); + g_renderBackend->Set_Alpha_Blend_Enable(false); + g_renderBackend->Apply_Render_State_Changes(); + W3DShaderManager_BindStageTexture(curStage, W3DShaderManager::getShaderTexture(3)); return TRUE; } void FlatTerrainShaderPixelShader::reset() { - DX8Wrapper::_Get_D3D_Device8()->SetTexture(2,nullptr); //release reference to any texture - DX8Wrapper::_Get_D3D_Device8()->SetTexture(3,nullptr); //release reference to any texture - - DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(0); //turn off pixel shader - - DX8Wrapper::_Get_D3D_Device8()->SetTexture(0, nullptr); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(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); + g_renderBackend->Override_Terrain_Blend(false); + W3DShaderManager_BindStageTexture(2, nullptr); + W3DShaderManager_BindStageTexture(3, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|1); + g_renderBackend->Set_Pixel_Shader(0); //turn off pixel shader - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 2, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|2); + W3DShaderManager_BindStageTexture(0, nullptr); + W3DShaderManager_BindStageTexture(1, nullptr); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State( 3, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|3); + W3DShaderManager_ResetMeshTexcoord(0, 0); + W3DShaderManager_ResetMeshTexcoord(1, 1); + W3DShaderManager_ResetMeshTexcoord(2, 2); + W3DShaderManager_ResetMeshTexcoord(3, 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..d46a8e93764 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp @@ -30,16 +30,23 @@ #include "Lib/BaseType.h" #include "WWLib/always.h" #include "W3DDevice/GameClient/W3DSmudge.h" +#include "Common/GlobalData.h" #include "W3DDevice/GameClient/W3DShaderManager.h" #include "Common/GameMemory.h" #include "GameClient/View.h" #include "GameClient/Display.h" #include "WW3D2/texture.h" -#include "WW3D2/dx8indexbuffer.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBufferTypes.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/renderbufferclasses.h" #include "WW3D2/rinfo.h" #include "WW3D2/camera.h" #include "WW3D2/sortingrenderer.h" +#include "WW3D2/surfaceclass.h" +#include "WW3D2/vertexbuffer.h" +#include "WW3D2/vertmaterial.h" +#include "WWMath/vector2i.h" SmudgeManager *TheSmudgeManager=nullptr; @@ -80,22 +87,27 @@ void W3DSmudgeManager::ReAcquireResources() { ReleaseResources(); - SurfaceClass *surface=DX8Wrapper::_Get_DX8_Back_Buffer(); - SurfaceClass::SurfaceDescription surface_desc; + RenderBackendSurfaceDescription surface_desc; - surface->Get_Description(surface_desc); - REF_PTR_RELEASE(surface); +#if defined(GGC_RENDER_BACKEND_BGFX) + surface_desc.Format = WW3D_FORMAT_UNKNOWN; + surface_desc.Width = TheDisplay ? TheDisplay->getWidth() : 0; + surface_desc.Height = TheDisplay ? TheDisplay->getHeight() : 0; +#else + if (!g_renderBackend || !g_renderBackend->Get_Back_Buffer_Description(0, surface_desc)) + return; m_backgroundTexture = MSGNEW("TextureClass") TextureClass(surface_desc.Width,surface_desc.Height,surface_desc.Format,MIP_LEVELS_1,TextureClass::POOL_DEFAULT, true); +#endif m_backBufferWidth = surface_desc.Width; m_backBufferHeight = surface_desc.Height; - m_indexBuffer=NEW_REF(DX8IndexBufferClass,(SMUDGE_DRAW_SIZE*4*3)); //allocate 4 triangles per smudge, each with 3 indices. + m_indexBuffer=NEW_REF(RenderIndexBufferClass,(SMUDGE_DRAW_SIZE*4*3)); //allocate 4 triangles per smudge, each with 3 indices. // Fill up the IB with static vertex indices that will be used for all smudges. { - DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); UnsignedShort *ib=lockIdxBuffer.Get_Index_Array(); //quad of 4 triangles: // 0-----3 @@ -132,69 +144,39 @@ void W3DSmudgeManager::ReAcquireResources() /*Copies a portion of the current render target into a specified buffer*/ Int copyRect(unsigned char *buf, Int bufSize, int oX, int oY, int width, int height) { - IDirect3DSurface8 *surface=nullptr; ///GetRenderTarget(&surface); - - if (!surface) - goto error; - - D3DSURFACE_DESC desc; - - surface->GetDesc(&desc); - - RECT srcRect; - srcRect.left=oX; - srcRect.top=oY; - srcRect.right=oX+width; - srcRect.bottom=oY+height; - - POINT dstPoint; - dstPoint.x=0; - dstPoint.y=0; - - hr=m_pDev->CreateImageSurface( width, height, desc.Format, &tempSurface); - - if (hr != S_OK) - goto error; - - hr=m_pDev->CopyRects(surface,&srcRect,1,tempSurface,&dstPoint); - - if (hr != S_OK) - goto error; - - D3DLOCKED_RECT lrect; - - hr=tempSurface->LockRect(&lrect,nullptr,D3DLOCK_READONLY); - - if (hr != S_OK) - goto error; - - tempSurface->GetDesc(&desc); + if (buf == nullptr || bufSize <= 0 || width <= 0 || height <= 0 || g_renderBackend == nullptr) { + return 0; + } - if (desc.Size < bufSize) - bufSize = desc.Size; + RenderBackendImage image; + if (!g_renderBackend->Capture_Back_Buffer_Image(0, image)) { + return 0; + } - memcpy(buf,lrect.pBits,bufSize); - result = bufSize; + if (oX < 0 || oY < 0 || + oX + width > static_cast(image.Width) || + oY + height > static_cast(image.Height)) { + return 0; + } - tempSurface->UnlockRect(); + const int bytesPerPixel = Get_Bytes_Per_Pixel(image.Format); + const int rowBytes = width * bytesPerPixel; + const int totalBytes = rowBytes * height; + const int copyBytes = (bufSize < totalBytes) ? bufSize : totalBytes; + if (bytesPerPixel <= 0 || copyBytes <= 0) { + return 0; + } -error: - if (surface) - surface->Release(); - if (tempSurface) - tempSurface->Release(); + int copied = 0; + for (int row = 0; row < height && copied < copyBytes; ++row) { + const int rowCopy = (copyBytes - copied < rowBytes) ? copyBytes - copied : rowBytes; + const size_t sourceOffset = + (static_cast(oY + row) * image.Pitch) + (static_cast(oX) * bytesPerPixel); + memcpy(buf + copied, image.Bytes.data() + sourceOffset, rowCopy); + copied += rowCopy; + } - return result; + return copied; } #define UNIQUE_COLOR (0x12345678) @@ -202,11 +184,17 @@ Int copyRect(unsigned char *buf, Int bufSize, int oX, int oY, int width, int hei Bool W3DSmudgeManager::testHardwareSupport() { +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @feature bobtista 27/04/2026 Standalone bgfx resolves + // the scene framebuffer internally for smudge distortion, so it no longer + // needs the old DX8 CopyRects support test. + m_hardwareSupportStatus = SMUDGE_SUPPORT_YES; + return TRUE; +#else if (m_hardwareSupportStatus == SMUDGE_SUPPORT_UNKNOWN) { //we have not done the test yet. - IDirect3DTexture8 *backTexture=W3DShaderManager::getRenderTexture(); - if (!backTexture || !W3DShaderManager::isRenderingToTexture()) + if (!W3DShaderManager::hasRenderTexture() || !W3DShaderManager::isRenderingToTexture()) { // TheSuperHackers @bugfix When Render-To-Texture is disabled globally, we fallback // to copying the backbuffer to a texture. @@ -221,52 +209,59 @@ 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; - DWORD color; // diffuse color - float u; - float v; - } v[4]; + RenderBackendScreenVertex v[4]; //bottom right - v[0].p = Vector4( BLOCK_SIZE-0.5f, BLOCK_SIZE-0.5f, 0.0f, 1.0f ); - v[0].u = BLOCK_SIZE/(Real)TheDisplay->getWidth(); - v[0].v = BLOCK_SIZE/(Real)TheDisplay->getHeight(); + v[0].x = BLOCK_SIZE-0.5f; + v[0].y = BLOCK_SIZE-0.5f; + v[0].z = 0.0f; + v[0].w = 1.0f; + v[0].u0 = BLOCK_SIZE/(Real)TheDisplay->getWidth(); + v[0].v0 = BLOCK_SIZE/(Real)TheDisplay->getHeight(); //top right - v[1].p = Vector4( BLOCK_SIZE-0.5f, 0-0.5f, 0.0f, 1.0f ); - v[1].u = BLOCK_SIZE/(Real)TheDisplay->getWidth(); - v[1].v = 0; + v[1].x = BLOCK_SIZE-0.5f; + v[1].y = 0-0.5f; + v[1].z = 0.0f; + v[1].w = 1.0f; + v[1].u0 = BLOCK_SIZE/(Real)TheDisplay->getWidth(); + v[1].v0 = 0; //bottom left - v[2].p = Vector4( 0-0.5f, BLOCK_SIZE-0.5f, 0.0f, 1.0f ); - v[2].u = 0; - v[2].v = BLOCK_SIZE/(Real)TheDisplay->getHeight(); + v[2].x = 0-0.5f; + v[2].y = BLOCK_SIZE-0.5f; + v[2].z = 0.0f; + v[2].w = 1.0f; + v[2].u0 = 0; + v[2].v0 = BLOCK_SIZE/(Real)TheDisplay->getHeight(); //top left - v[3].p = Vector4( 0-0.5f, 0-0.5f, 0.0f, 1.0f ); - v[3].u = 0; - v[3].v = 0; - - v[0].color = UNIQUE_COLOR; - v[1].color = UNIQUE_COLOR; - v[2].color = UNIQUE_COLOR; - v[3].color = UNIQUE_COLOR; - - LPDIRECT3DDEVICE8 pDev=DX8Wrapper::_Get_D3D_Device8(); - - //draw polygons like this is very inefficient but for only 2 triangles, it's - //not worth bothering with index/vertex buffers. - pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1); + v[3].x = 0-0.5f; + v[3].y = 0-0.5f; + v[3].z = 0.0f; + v[3].w = 1.0f; + v[3].u0 = 0; + v[3].v0 = 0; + + for (Int i = 0; i < 4; ++i) + { + v[i].diffuse = UNIQUE_COLOR; + v[i].u1 = 0.0f; + v[i].v1 = 0.0f; + } - pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX)); + if (g_renderBackend == nullptr || !g_renderBackend->Draw_Screen_Quad(v, 4, false)) + { + m_hardwareSupportStatus = SMUDGE_SUPPORT_NO; + return FALSE; + } DWORD refData[BLOCK_SIZE*BLOCK_SIZE]; memset(refData,0,sizeof(refData)); @@ -277,17 +272,26 @@ Bool W3DSmudgeManager::testHardwareSupport() return FALSE; } - DX8Wrapper::Set_DX8_Texture(0,backTexture); + if (g_renderBackend == nullptr || + !g_renderBackend->Bind_View_Capture_Texture(RB_VIEW_CAPTURE_TACTICAL, 0)) + { + m_hardwareSupportStatus = SMUDGE_SUPPORT_NO; + return FALSE; + } DWORD testData[BLOCK_SIZE*BLOCK_SIZE]; memset(testData,0xff,sizeof(testData)); - v[0].color = 0xffffffff; - v[1].color = 0xffffffff; - v[2].color = 0xffffffff; - v[3].color = 0xffffffff; + v[0].diffuse = 0xffffffff; + v[1].diffuse = 0xffffffff; + v[2].diffuse = 0xffffffff; + v[3].diffuse = 0xffffffff; - pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANS_LIT_TEX_VERTEX)); + if (!g_renderBackend->Draw_Screen_Quad(v, 4, false)) + { + m_hardwareSupportStatus = SMUDGE_SUPPORT_NO; + return FALSE; + } bufSize=copyRect((unsigned char *)testData,sizeof(testData),0,0,BLOCK_SIZE,BLOCK_SIZE); if (!bufSize) @@ -306,6 +310,7 @@ Bool W3DSmudgeManager::testHardwareSupport() } return (SMUDGE_SUPPORT_YES == m_hardwareSupportStatus); +#endif // GGC_RENDER_BACKEND_BGFX } void W3DSmudgeManager::render(RenderInfoClass &rinfo) @@ -314,21 +319,17 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) if (!testHardwareSupport()) return; - SurfaceClass *backBuffer = DX8Wrapper::_Get_DX8_Back_Buffer(); + RenderBackendSurfaceDescription surface_desc; + Bool bgfxSmudgeActive = FALSE; - if (!backBuffer) +#if defined(GGC_RENDER_BACKEND_BGFX) + surface_desc.Format = WW3D_FORMAT_UNKNOWN; + surface_desc.Width = TheDisplay->getWidth(); + surface_desc.Height = TheDisplay->getHeight(); +#else + if (!g_renderBackend || !g_renderBackend->Get_Back_Buffer_Description(0, surface_desc)) return; - - SurfaceClass *background=m_backgroundTexture ? m_backgroundTexture->Get_Surface_Level() : nullptr; - - if (!background) - { - REF_PTR_RELEASE(backBuffer); - return; - } - - SurfaceClass::SurfaceDescription surface_desc; - backBuffer->Get_Description(surface_desc); +#endif CameraClass &camera=rinfo.Camera; Vector3 vsVert; @@ -341,7 +342,11 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) Vector3(0.5f, 0.5f, 0.0f) }; +#if defined(GGC_RENDER_BACKEND_BGFX) +#define THE_COLOR (0x00ffffff) +#else #define THE_COLOR (0x00ffeedd) +#endif UnsignedInt vertexDiffuse[5]={THE_COLOR,THE_COLOR,THE_COLOR,THE_COLOR,THE_COLOR}; @@ -353,6 +358,11 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) Real texClampX = (Real)TheTacticalView->getWidth()/(Real)surface_desc.Width; Real texClampY = (Real)TheTacticalView->getHeight()/(Real)surface_desc.Height; +#if defined(GGC_RENDER_BACKEND_BGFX) + const ViewportClass &viewport = camera.Get_Viewport(); + texClampX = viewport.Max.X; + texClampY = viewport.Max.Y; +#endif Real texScaleX = texClampX*0.5f; Real texScaleY = texClampY*0.5f; @@ -403,12 +413,10 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) Vector2 &thisUV=verts[i].uv; - // Zero coordinates that fall outside valid texel bounds - if (thisUV.X < 0 || thisUV.X > texClampX) - offset.X = 0; - - if (thisUV.Y < 0 || thisUV.Y > texClampY) - offset.Y = 0; + // Clamp corner sample coordinates without damping the center offset; + // this matches the fixed DX8 smudge behavior from PR #1073. + thisUV.X = WWMath::Clamp(thisUV.X, 0.0f, texClampX); + thisUV.Y = WWMath::Clamp(thisUV.Y, 0.0f, texClampY); } //Finish center vertex @@ -428,42 +436,67 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) if (!count) { - REF_PTR_RELEASE(background); - REF_PTR_RELEASE(backBuffer); return; //nothing to render. } - //Copy the area of backbuffer occupied by smudges into an alternate buffer. - background->Copy(0,0,0,0,surface_desc.Width,surface_desc.Height,backBuffer); +#if !defined(GGC_RENDER_BACKEND_BGFX) + if (m_backgroundTexture == nullptr || g_renderBackend == nullptr) + { + return; + } + + // TheSuperHackers @bugfix bobtista 03/06/2026 Prefer the GPU-direct + // back-buffer→texture copy when the backend supports it (DX8Backend + // implements via CopyRects, no per-frame allocation). Commit 312261d93 + // switched this path to Capture_Back_Buffer_Image, which on dx8 creates + // a fresh 4 MB IDirect3DSurface8 + 4 MB std::vector per call and + // leaks ~78 MB/s of virtual address space — exhausting the 2 GB 32-bit + // limit in ~14 s of gameplay (silent stall at frame ~300). bgfx and any + // backend without the direct copy fall through to the image readback path. + if (!g_renderBackend->Copy_Back_Buffer_To_Texture(0, m_backgroundTexture)) + { + RenderBackendImage back_buffer_image; + if (!g_renderBackend->Capture_Back_Buffer_Image(0, back_buffer_image)) + { + return; + } + + SurfaceClass::SurfaceImageData image; + image.Format = back_buffer_image.Format; + image.Width = back_buffer_image.Width; + image.Height = back_buffer_image.Height; + image.Pitch = back_buffer_image.Pitch; + image.Data = back_buffer_image.Bytes; + m_backgroundTexture->Update_Surface_Level_From_Surface(0, image); + } +#else + if (!g_renderBackend || !g_renderBackend->Begin_Smudge_Distortion(texClampX, texClampY)) + return; - REF_PTR_RELEASE(background); - REF_PTR_RELEASE(backBuffer); + bgfxSmudgeActive = TRUE; +#endif 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); + g_renderBackend->Set_Transform(RB_TRANSFORM_PROJECTION,proj); - 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,bgfxSmudgeActive ? nullptr : 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); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ADDRESSW, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_MIPFILTER, D3DTEXF_NONE); + g_renderBackend->Set_Texture_Address_Mode(0, RB_TEXTURE_ADDRESS_CLAMP, RB_TEXTURE_ADDRESS_CLAMP, RB_TEXTURE_ADDRESS_CLAMP); + g_renderBackend->Set_Texture_Sample_Filter(0, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_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); - DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_ALPHAOP,D3DTOP_SELECTARG2); + // Disable reading texture alpha since it's undefined. + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_SELECTARG2); Int smudgesRemaining=count; set=m_usedSmudgeSetList.Head(); //first smudge set that needs rendering. @@ -479,7 +512,7 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) Int smudgesInRenderBatch=0; - DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,count*5); //allocate 5 verts per smudge. + DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,count*5); //allocate 5 verts per smudge. { DynamicVBAccessClass::WriteLockClass lock(&vb_access); VertexFormatXYZNDUV2* verts=lock.Get_Formatted_Vertex_Array(); @@ -503,7 +536,8 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) } //Set center vertex opacity. - vertexDiffuse[4] = ((Int)(smudge->m_opacity * 255.0f) << 24) | THE_COLOR; + Real opacity = smudge->m_opacity; + vertexDiffuse[4] = ((Int)(opacity * 255.0f) << 24) | THE_COLOR; for (Int i=0; i<5; i++) { @@ -533,23 +567,16 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) } flushSmudges: - DX8Wrapper::Set_Vertex_Buffer(vb_access); - - DX8Wrapper::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); - 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); -*/ + g_renderBackend->Set_Vertex_Buffer(vb_access); + + g_renderBackend->Draw_Triangles(0,smudgesInRenderBatch*4, 0, smudgesInRenderBatch*5); + smudgesRemaining -= smudgesInRenderBatch; } - DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLOROP,D3DTOP_MODULATE); - DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_ALPHAOP,D3DTOP_MODULATE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_MODULATE); + if (bgfxSmudgeActive) + g_renderBackend->End_Smudge_Distortion(); } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSnow.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSnow.cpp index 62ece01ed24..f1804d5cbf9 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSnow.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSnow.cpp @@ -21,27 +21,23 @@ #include "W3DDevice/GameClient/W3DSnow.h" #include "W3DDevice/GameClient/HeightMap.h" #include "GameClient/View.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/renderbufferclasses.h" +#include "WW3D2/vertexbuffer.h" #include "WW3D2/rinfo.h" #include "WW3D2/camera.h" #include "WW3D2/assetmgr.h" -#define D3DFVF_POINTVERTEX (D3DFVF_XYZ) #define SNOW_BUFFER_SIZE 4096 //size of vertex buffer holding particles. #define SNOW_BATCH_SIZE 2048 //we render at most this many particles per drawprimitive call. This number * 6 must be less than 65536 to fit into index buffer. -struct POINTVERTEX -{ - Vector3 v; //center of particle. -}; - W3DSnowManager::W3DSnowManager() { m_indexBuffer=nullptr; m_snowTexture=nullptr; - m_VertexBufferD3D=nullptr; } W3DSnowManager::~W3DSnowManager() @@ -55,20 +51,14 @@ void W3DSnowManager::init() ReAcquireResources(); } -/** Releases all W3D/D3D assets before a reset.. */ +/** Releases all W3D snow assets before a reset. */ void W3DSnowManager::ReleaseResources() { REF_PTR_RELEASE(m_snowTexture); - - if (m_VertexBufferD3D) - m_VertexBufferD3D->Release(); - - m_VertexBufferD3D=nullptr; - REF_PTR_RELEASE(m_indexBuffer); } -/** (Re)allocates all W3D/D3D assets after a reset.. */ +/** (Re)allocates all W3D snow assets after a reset. */ Bool W3DSnowManager::ReAcquireResources() { ReleaseResources(); @@ -76,64 +66,37 @@ Bool W3DSnowManager::ReAcquireResources() if (!TheWeatherSetting->m_snowEnabled) return TRUE; //no need for resources if snow is disabled. - if (TheWeatherSetting->m_usePointSprites && DX8Wrapper::Get_Current_Caps()->Support_PointSprites()) - { - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); - - DEBUG_ASSERTCRASH(m_pDev, ("Trying to ReAcquireResources on W3DSnowManager without device")); - - if (m_VertexBufferD3D == nullptr) - { // Create vertex buffer - - if (FAILED(m_pDev->CreateVertexBuffer - ( - SNOW_BUFFER_SIZE*sizeof(POINTVERTEX), - D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC|D3DUSAGE_POINTS, - D3DFVF_POINTVERTEX, - D3DPOOL_DEFAULT, - &m_VertexBufferD3D - ))) - return FALSE; - } - } - else - { - m_indexBuffer=NEW_REF(DX8IndexBufferClass,(SNOW_BATCH_SIZE *6)); //allocate 2 triangles per flake, each with 3 indices. + m_indexBuffer=NEW_REF(RenderIndexBufferClass,(SNOW_BATCH_SIZE *6)); //allocate 2 triangles per flake, each with 3 indices. - // Fill up the IB with static vertex indices that will be used for all smudges. + // Fill up the IB with static vertex indices that will be used for all smudges. + { + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); + UnsignedShort *ib=lockIdxBuffer.Get_Index_Array(); + //quad of 4 triangles: + // 0-----3 + // |\ /| + // | X | + // |/ \| + // 1-----2 + Int vbCount=0; + for (Int i=0; iGet_Texture(TheWeatherSetting->m_snowTexture.str()); - m_dwBase = SNOW_BUFFER_SIZE; - m_dwDiscard = SNOW_BUFFER_SIZE; - m_dwFlush = SNOW_BATCH_SIZE; - return TRUE; } @@ -167,166 +130,11 @@ void W3DSnowManager::update() #define ISPOW2(x) (x && (x & (x-1)) == 0) //is a number a power of 2? #define MODPOW2(x,y) ((x) & (y-1)) //mod '%' operator for powers of 2. -// Helper function to stuff a FLOAT into a DWORD argument -inline DWORD FtoDW( FLOAT f ) { return *((DWORD*)&f); } - -/*Recursively subdivide the large snow box enclosing the camera until we reach some predefined leaf size. This -method is used so that very few off-screen particles end up getting rendered. Culling them individually would -be too expensive since we're dealing with 1000's for this effect.*/ -void W3DSnowManager::renderSubBox(RenderInfoClass &rinfo, Int originX, Int originY, Int cubeDimX, Int cubeDimY ) -{ - //check if this box is too large and needs subdivision - Int boxDimX=cubeDimX - originX; - Int boxDimY=cubeDimY - originY; - Int halfX=REAL_TO_INT_CEIL(boxDimX*0.5f); - Int halfY=REAL_TO_INT_CEIL(boxDimY*0.5f); - - CameraClass &camera=rinfo.Camera; - MinMaxAABoxClass mmbox; - - if (boxDimX > m_leafDim) - { //subdivide the box - if (boxDimY > m_leafDim) - { //subdivide in both directions - //Upper left - mmbox.MinCorner.Set(originX*m_emitterSpacing-m_cullOverscan, (originY + halfY)*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions); - mmbox.MaxCorner.Set((originX + halfX)*m_emitterSpacing+m_cullOverscan, cubeDimY*m_emitterSpacing+m_cullOverscan, m_snowCeiling); - if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE) - renderSubBox(rinfo, originX, originY + halfY, originX + halfX, cubeDimY); - //Upper right - mmbox.MinCorner.Set((originX + halfX)*m_emitterSpacing-m_cullOverscan, (originY + halfY)*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions); - mmbox.MaxCorner.Set(cubeDimX*m_emitterSpacing+m_cullOverscan, cubeDimY*m_emitterSpacing+m_cullOverscan, m_snowCeiling); - if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE) - renderSubBox(rinfo, originX + halfX, originY + halfY,cubeDimX, cubeDimY); - //Lower left - mmbox.MinCorner.Set(originX*m_emitterSpacing-m_cullOverscan, originY*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions); - mmbox.MaxCorner.Set((originX + halfX)*m_emitterSpacing+m_cullOverscan, (originY + halfY)*m_emitterSpacing+m_cullOverscan, m_snowCeiling); - if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE) - renderSubBox(rinfo, originX,originY,originX + halfX, originY + halfY); - //Lower right - mmbox.MinCorner.Set((originX + halfX)*m_emitterSpacing-m_cullOverscan, originY*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions); - mmbox.MaxCorner.Set(cubeDimX*m_emitterSpacing+m_cullOverscan, (originY + halfY)*m_emitterSpacing+m_cullOverscan, m_snowCeiling); - if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE) - renderSubBox(rinfo, originX + halfX, originY, cubeDimX, originY + halfY); - return; - } - else - { //only subdivide in x direction. - //Left - mmbox.MinCorner.Set(originX*m_emitterSpacing-m_cullOverscan, originY*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions); - mmbox.MaxCorner.Set((originX + halfX)*m_emitterSpacing+m_cullOverscan, cubeDimY*m_emitterSpacing+m_cullOverscan, m_snowCeiling); - if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE) - renderSubBox(rinfo, originX, originY, originX + halfX, cubeDimY); - //Right - mmbox.MinCorner.Set((originX + halfX)*m_emitterSpacing-m_cullOverscan, originY*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions); - mmbox.MaxCorner.Set(cubeDimX*m_emitterSpacing+m_cullOverscan, cubeDimY*m_emitterSpacing+m_cullOverscan, m_snowCeiling); - if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE) - renderSubBox(rinfo, originX + halfX, originY, cubeDimX, cubeDimY); - return; - } - } - else - if (boxDimY > m_leafDim) - { //only subdivide in y direction - //Top - mmbox.MinCorner.Set(originX*m_emitterSpacing-m_cullOverscan, (originY+halfY)*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions); - mmbox.MaxCorner.Set(cubeDimX*m_emitterSpacing+m_cullOverscan, cubeDimY*m_emitterSpacing+m_cullOverscan, m_snowCeiling); - if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE) - renderSubBox(rinfo, originX, originY+halfY,cubeDimX, cubeDimY); - //Bottom - mmbox.MinCorner.Set(originX*m_emitterSpacing-m_cullOverscan, originY*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions); - mmbox.MaxCorner.Set(cubeDimX*m_emitterSpacing+m_cullOverscan, (originY + halfY)*m_emitterSpacing+m_cullOverscan, m_snowCeiling); - if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE) - renderSubBox(rinfo, originX, originY, cubeDimX, originY + halfY); - return; - } - - //Box too small to subdivide so render it. - - //Find total number of particles that need rendering. - Int totalPart=(cubeDimY-originY)*(cubeDimX-originX); - - if (!totalPart) - return; //nothing to render. - - Int y=originY; //loop counter. - Int cubeOriginXRemainder = originX; //loop counter - adjusted when not all particles fit into render buffer. - Vector3 snowCenter; - - m_totalRendered += totalPart; - - while (totalPart) - { - Int batchSize=totalPart; - - if (batchSize > m_dwFlush) - batchSize = m_dwFlush; - - if((m_dwBase + batchSize) > m_dwDiscard) - m_dwBase = 0; - - POINTVERTEX* verts; - - if(m_VertexBufferD3D->Lock(m_dwBase * sizeof(POINTVERTEX), batchSize * sizeof(POINTVERTEX), - (unsigned char **) &verts, m_dwBase ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD) != D3D_OK ) - return; //couldn't lock buffer. - - Int numberInBatch=0; - - for (;y= batchSize) - { cubeOriginXRemainder = x; - goto flush_particles; - } - - //Get initial height from noise table. We add a large value to make sure it's positive. Then - //modulate by table dimensions to find a value. - Int noiseOffset=MODPOW2(x+MAXIMUM_CAMERA_DISTANCE,SNOW_NOISE_X)+MODPOW2(y+MAXIMUM_CAMERA_DISTANCE,SNOW_NOISE_Y)*SNOW_NOISE_X; - if (noiseOffset > (SNOW_NOISE_X * SNOW_NOISE_Y)) - noiseOffset = 0; //this should never happen but check to prevent buffer over/under flow. - - //find current height - Real h0=m_snowCeiling-fmod(m_heightTraveled+m_startingHeights[noiseOffset],m_boxDimensions); - - //find world-space position of snow flake - snowCenter.Set(x*m_emitterSpacing,y*m_emitterSpacing,h0); - - //Adjust position so snow flakes don't fall straight down. - snowCenter.X += m_amplitude * WWMath::Fast_Sin( h0 * m_frequencyScaleX + (Real)x); - snowCenter.Y += m_amplitude * WWMath::Fast_Sin( h0 * m_frequencyScaleY + (Real)y); - - *(Vector3 *)verts=snowCenter; - verts++; - - numberInBatch++; - } - //getting here means we did not overflow the render buffer, so reset x origin to normal. - cubeOriginXRemainder = originX; //reset to normal amount - } - -flush_particles: - m_VertexBufferD3D->Unlock(); - //Render any particles that may be queued up. - if (numberInBatch) - { - Debug_Statistics::Record_DX8_Polys_And_Vertices(numberInBatch*2,numberInBatch*4,ShaderClass::_PresetOpaqueShader); - DX8Wrapper::_Get_D3D_Device8()->DrawPrimitive( D3DPT_POINTLIST, m_dwBase, numberInBatch); - totalPart -= numberInBatch; - m_dwBase += numberInBatch; - } - } -} - void W3DSnowManager::render(RenderInfoClass &rinfo) { if (!TheWeatherSetting->m_snowEnabled || !m_isVisible) return; - Int usePointSprites = DX8Wrapper::Get_Current_Caps()->Support_PointSprites() && TheWeatherSetting->m_usePointSprites; - //make sure the noise table is powers of 2 in dimensions. WWASSERT(ISPOW2(SNOW_NOISE_X) && ISPOW2(SNOW_NOISE_Y)); @@ -392,59 +200,19 @@ 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 - if (usePointSprites && !m_VertexBufferD3D) - ReAcquireResources(); - - if (!usePointSprites && !m_indexBuffer) + if (!m_indexBuffer) ReAcquireResources(); - DX8Wrapper::Set_Texture(0,m_snowTexture); - - if (!usePointSprites) - { - renderAsQuads(rinfo,cubeOriginX,cubeOriginY,cubeDimX,cubeDimY); - return; - } - - Vector3 snowCenter; - - DX8Wrapper::Apply_Render_State_Changes(); - - // Set the render states for using point sprites - DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSPRITEENABLE, TRUE ); - DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSCALEENABLE, TRUE ); - DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSIZE, FtoDW(m_pointSize) ); - DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSIZE_MIN, FtoDW(m_minPointSize) ); - DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSIZE_MAX, FtoDW(m_maxPointSize) ); - DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSCALE_A, FtoDW(0.00f) ); - DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSCALE_B, FtoDW(0.00f) ); - DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSCALE_C, FtoDW(1.00f) ); - - DX8Wrapper::_Get_D3D_Device8()->SetStreamSource( 0, m_VertexBufferD3D, sizeof(POINTVERTEX) ); - DX8Wrapper::_Get_D3D_Device8()->SetVertexShader( D3DFVF_POINTVERTEX ); - m_dwBase = SNOW_BUFFER_SIZE; //start with a new vertex buffer each frame. - - m_leafDim = 45; //cull boxes that are 20x20 emitters in size. Making them much smaller will result in too many draw calls. - m_totalRendered = 0; //keep track of how many particles were rendered. - - //Particle centers can deviate from center by by amplitude of sine offset. They also have radius m_quadSize. - //Enlarge culling bounds to compensate. - m_cullOverscan = m_amplitude+m_quadSize; - renderSubBox(rinfo,cubeOriginX,cubeOriginY,cubeDimX,cubeDimY); - - // Reset render states - DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSPRITEENABLE, FALSE ); - DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSCALEENABLE, FALSE ); - + g_renderBackend->Set_Texture(0,m_snowTexture); + renderAsQuads(rinfo,cubeOriginX,cubeOriginY,cubeDimX,cubeDimY); } /**For hardware that doesn't support point sprites*/ @@ -483,9 +251,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. @@ -504,7 +272,7 @@ void W3DSnowManager::renderAsQuads(RenderInfoClass &rinfo, Int cubeOriginX, Int Int numberInBatch=0; - DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,batchSize*4); //allocate 4 verts per flake + DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,batchSize*4); //allocate 4 verts per flake { DynamicVBAccessClass::WriteLockClass lock(&vb_access); VertexFormatXYZNDUV2* verts=lock.Get_Formatted_Vertex_Array(); @@ -563,8 +331,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..e75dae939fd 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainBackground.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainBackground.cpp @@ -50,12 +50,14 @@ #include #include +#include "WW3D2/dx8fvf.h" +#include "WW3D2/renderbufferclasses.h" #include "Common/GlobalData.h" #include "GameClient/View.h" #include "W3DDevice/GameClient/TerrainTex.h" #include "W3DDevice/GameClient/HeightMap.h" -#include "WW3D2/dx8wrapper.h" -#include "WW3D2/dx8renderer.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/camera.h" @@ -125,14 +127,14 @@ void W3DTerrainBackground::doPartialUpdate(const IRegion2D &partialRange, WorldH m_vertexTerrainSize = requiredVertexSize; REF_PTR_RELEASE(m_vertexTerrain); REF_PTR_RELEASE(m_indexTerrain); - m_vertexTerrain=NEW_REF(DX8VertexBufferClass,(DX8_FVF_XYZDUV1,m_vertexTerrainSize+4,DX8VertexBufferClass::USAGE_DEFAULT)); + m_vertexTerrain=NEW_REF(RenderVertexBufferClass,(DX8_FVF_XYZDUV1,m_vertexTerrainSize+4,RenderVertexBufferClass::USAGE_DEFAULT)); } Int requiredIndexSize = (m_width+1) * (m_width+1) + 6; if (m_indexTerrainSizeSet_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,22 +785,17 @@ 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/W3DTerrainTracks.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainTracks.cpp index c5c557d2d2c..58b12f8e6db 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainTracks.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainTracks.cpp @@ -50,11 +50,14 @@ #include "Common/Debug.h" #include "WW3D2/texture.h" #include "WWMath/colmath.h" +#include "GgcRuntimeFlags.h" #include "WW3D2/coltest.h" #include "WW3D2/rinfo.h" #include "WW3D2/camera.h" #include "WW3D2/assetmgr.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/dx8fvf.h" +#include "WW3D2/renderbufferclasses.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/scene.h" #include "GameLogic/TerrainLogic.h" #include "GameLogic/Object.h" @@ -87,6 +90,7 @@ TerrainTracksRenderObjClass::TerrainTracksRenderObjClass() m_bottomIndex=0; m_activeEdgeCount=0; m_totalEdgesAdded=0; + m_groupedThisFlush=false; m_bound=false; m_ownerDrawable = nullptr; } @@ -594,11 +598,11 @@ void TerrainTracksRenderObjClassSystem::ReAcquireResources() REF_PTR_RELEASE(m_vertexBuffer); //Create static index buffers. These will index the vertex buffers holding the track segments - m_indexBuffer=NEW_REF(DX8IndexBufferClass,((m_maxTankTrackEdges-1)*6)); + m_indexBuffer=NEW_REF(RenderIndexBufferClass,((m_maxTankTrackEdges-1)*6)); // Fill up the IB { - DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); UnsignedShort *ib=lockIdxBuffer.Get_Index_Array(); for (i=0; i<(m_maxTankTrackEdges-1); i++) @@ -613,7 +617,7 @@ void TerrainTracksRenderObjClassSystem::ReAcquireResources() DEBUG_ASSERTCRASH(numModules*m_maxTankTrackEdges*2 < 65535, ("Too many terrain track edges")); - m_vertexBuffer=NEW_REF(DX8VertexBufferClass,(DX8_FVF_XYZDUV1,numModules*m_maxTankTrackEdges*2,DX8VertexBufferClass::USAGE_DYNAMIC)); + m_vertexBuffer=NEW_REF(RenderVertexBufferClass,(DX8_FVF_XYZDUV1,numModules*m_maxTankTrackEdges*2,RenderVertexBufferClass::USAGE_DYNAMIC)); } //============================================================================= @@ -823,7 +827,7 @@ Try improving the fit to vertical surfaces like cliffs. //check if there is anything to draw and fill vertex buffer if (m_edgesToFlush >= 2) { - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexBuffer); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexBuffer); VertexFormatXYZDUV1 *verts = (VertexFormatXYZDUV1*)lockVtxBuffer.Get_Vertex_Array(); trackStartIndex=0; @@ -888,25 +892,119 @@ 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); - while (mod) + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,mod->Transform); + + // TheSuperHackers @perf bobtista 03/06/2026 Coalesce per-module track draws into one + // Draw_Triangles per stage-zero texture; the grouped index buffer reproduces the strip + // pattern bit-for-bit. The DX8 reference path keeps the per-module loop. + Int totalVerts=0; + Int totalTris=0; + for (TerrainTracksRenderObjClass *countMod=m_usedModules; countMod; countMod=countMod->m_nextSystem) { - if (mod->m_activeEdgeCount >= 2 && mod->Is_Really_Visible()) + if (countMod->m_activeEdgeCount >= 2 && countMod->Is_Really_Visible()) + { + totalVerts += countMod->m_activeEdgeCount*2; + totalTris += (countMod->m_activeEdgeCount-1)*2; + } + } + + // Draw_Triangles takes the index start as a 16-bit value, so the grouped + // index buffer must stay within 65535 indices; fall back to per-module + // draws when a very large scene would overflow that range. + // GGC_NO_TRACK_BATCH=1 forces the per-module path for A/B verification. + static const bool s_trackBatchDisabled = GgcFlags::Enabled(GgcFlag_NoTrackBatch); + if (!s_trackBatchDisabled && g_renderBackend->Has_Shader_Pipeline() && totalVerts > 0 && totalTris*3 <= 65535) + { + RenderIndexBufferClass *groupIndexBuffer=NEW_REF(RenderIndexBufferClass,((UnsignedShort)(totalTris*3))); + { + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(groupIndexBuffer); + UnsignedShort *ib=lockIdxBuffer.Get_Index_Array(); + for (TerrainTracksRenderObjClass *texMod=m_usedModules; texMod; texMod=texMod->m_nextSystem) + { + if (!(texMod->m_activeEdgeCount >= 2 && texMod->Is_Really_Visible())) + { + continue; + } + if (texMod->m_groupedThisFlush) + { + continue; + } + TextureClass *groupTexture=texMod->m_stageZeroTexture; + Int baseVert=0; + for (TerrainTracksRenderObjClass *runMod=m_usedModules; runMod; runMod=runMod->m_nextSystem) + { + if (runMod->m_activeEdgeCount >= 2 && runMod->Is_Really_Visible()) + { + if (runMod->m_stageZeroTexture == groupTexture && !runMod->m_groupedThisFlush) + { + for (Int e=0; e<(runMod->m_activeEdgeCount-1); e++) + { + ib[3]=ib[0]=(UnsignedShort)(baseVert+e*2); + ib[1]=(UnsignedShort)(baseVert+e*2+1); + ib[4]=ib[2]=(UnsignedShort)(baseVert+(e+1)*2+1); + ib[5]=(UnsignedShort)(baseVert+(e+1)*2); + ib+=6; + } + runMod->m_groupedThisFlush=true; + } + baseVert += runMod->m_activeEdgeCount*2; + } + } + } + } + + g_renderBackend->Set_Index_Buffer(groupIndexBuffer,0); + g_renderBackend->Set_Index_Buffer_Index_Offset(0); + + Int drawStartTri=0; + for (TerrainTracksRenderObjClass *drawMod=m_usedModules; drawMod; drawMod=drawMod->m_nextSystem) + { + if (!drawMod->m_groupedThisFlush) + { + continue; + } + TextureClass *groupTexture=drawMod->m_stageZeroTexture; + Int groupTris=0; + for (TerrainTracksRenderObjClass *sumMod=drawMod; sumMod; sumMod=sumMod->m_nextSystem) + { + if (sumMod->m_groupedThisFlush && sumMod->m_stageZeroTexture == groupTexture) + { + groupTris += (sumMod->m_activeEdgeCount-1)*2; + sumMod->m_groupedThisFlush=false; + } + } + g_renderBackend->Set_Texture(0,groupTexture); + g_renderBackend->Draw_Triangles((UnsignedShort)(drawStartTri*3),(UnsignedShort)groupTris,0,(UnsignedShort)totalVerts); + drawStartTri += groupTris; + } + + REF_PTR_RELEASE(groupIndexBuffer); + // Restore the shared per-module index buffer for the next flush. + g_renderBackend->Set_Index_Buffer(m_indexBuffer,0); + } + else + { + while (mod) { - 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); + if (mod->m_activeEdgeCount >= 2 && mod->Is_Really_Visible()) + { + 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; + trackStartIndex += mod->m_activeEdgeCount*2; + } + mod=mod->m_nextSystem; } - mod=mod->m_nextSystem; } } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp index 7ac29bec1b1..8f3b588ed90 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp @@ -59,6 +59,10 @@ enum #include #include +#include "WW3D2/dx8fvf.h" +#include "WW3D2/indexbuffer.h" +#include "WW3D2/renderbufferclasses.h" +#include "WW3D2/vertexbuffer.h" #include "Common/FramePacer.h" #include "Common/GameUtility.h" #include "Common/MapReaderWriterInfo.h" @@ -74,6 +78,10 @@ enum #include "GameClient/ClientRandomValue.h" #include "GameClient/FXList.h" #include "W3DDevice/GameClient/TerrainTex.h" + +#if defined(GGC_RENDER_BACKEND_BGFX) +extern "C" int GGC_GetBgfxSunShadowCullBox(float * center3, float * radius); +#endif #include "W3DDevice/GameClient/HeightMap.h" #include "W3DDevice/GameClient/W3DDynamicLight.h" #include "W3DDevice/GameClient/Module/W3DTreeDraw.h" @@ -82,13 +90,12 @@ enum #include "W3DDevice/GameClient/W3DShroud.h" #include "W3DDevice/GameClient/W3DProjectedShadow.h" #include "WW3D2/camera.h" -#include "WW3D2/dx8wrapper.h" -#include "WW3D2/dx8renderer.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/matinfo.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" -#include "d3dx8tex.h" - +#include "WW3D2/ww3d.h" // If TEST_AND_BLEND is defined, it will do an alpha test and blend. Otherwise just alpha test. jba. [5/30/2003] #define dontTEST_AND_BLEND 1 @@ -114,7 +121,7 @@ texture of the desired height and mip level. */ //============================================================================= W3DTreeBuffer::W3DTreeTextureClass::W3DTreeTextureClass(unsigned width, unsigned height) : TextureClass(width, height, - WW3D_FORMAT_A8R8G8B8, MIP_LEVELS_ALL ) + WW3D_FORMAT_A8R8G8B8, MIP_LEVELS_ALL ) { } @@ -132,21 +139,21 @@ int W3DTreeBuffer::W3DTreeTextureClass::update(W3DTreeBuffer *buffer) Get_Filter().Set_U_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); Get_Filter().Set_V_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); - IDirect3DSurface8 *surface_level; - D3DSURFACE_DESC surface_desc; - D3DLOCKED_RECT locked_rect; - DX8_ErrorCode(Peek_D3D_Texture()->GetSurfaceLevel(0, &surface_level)); - DX8_ErrorCode(surface_level->GetDesc(&surface_desc)); - - DX8_ErrorCode(surface_level->LockRect(&locked_rect, nullptr, 0)); + MutableTextureMipView mip = Begin_Mip_Write(0); + if (!mip.Is_Valid()) + { + return 0; + } + const Int surface_pitch = static_cast(mip.Pitch); + UnsignedByte *surface_bits = mip.Data; Int tilePixelExtent = TILE_PIXEL_EXTENT; // Int numRows = surface_desc.Height/(tilePixelExtent+TILE_OFFSET); #ifdef RTS_DEBUG //DASSERT_MSG(tilesPerRow*numRows >= htMap->m_numBitmapTiles,Debug::Format ("Too many tiles.")); //DEBUG_ASSERTCRASH((Int)surface_desc.Width >= tilePixelExtent*tilesPerRow, ("Bitmap too small.")); #endif - if (surface_desc.Format == D3DFMT_A8R8G8B8) { + if (mip.Format == WW3D_FORMAT_A8R8G8B8) { Int tileNdx; Int pixelBytes = 4; #if 0 // Fill unused texture for debug display. @@ -172,8 +179,7 @@ int W3DTreeBuffer::W3DTreeTextureClass::update(W3DTreeBuffer *buffer) UnsignedByte *pBGR = pTile->getRGBDataForWidth(tilePixelExtent); pBGR += (tilePixelExtent-(1+j))*TILE_BYTES_PER_PIXEL*tilePixelExtent; // invert to match. Int row = position.y+j; - UnsignedByte *pBGRA = ((UnsignedByte*)locked_rect.pBits) + - (row)*surface_desc.Width*pixelBytes; + UnsignedByte *pBGRA = surface_bits + row * surface_pitch; Int column = position.x; pBGRA += column*pixelBytes; @@ -187,13 +193,12 @@ int W3DTreeBuffer::W3DTreeTextureClass::update(W3DTreeBuffer *buffer) } } - DX8_ErrorCode(surface_level->UnlockRect()); - surface_level->Release(); - DX8_ErrorCode(D3DXFilterTexture(Peek_D3D_Texture(), nullptr, (UINT)0, D3DX_FILTER_BOX)); + End_Mip_Write(0); + Generate_Mip_Levels(); if (WW3D::Get_Texture_Reduction()) { - DX8_ErrorCode(Peek_D3D_Texture()->SetLOD((DWORD)WW3D::Get_Texture_Reduction())); + Set_LOD(WW3D::Get_Texture_Reduction()); } - return(surface_desc.Height); + return(mip.Height); } @@ -204,9 +209,7 @@ int W3DTreeBuffer::W3DTreeTextureClass::update(W3DTreeBuffer *buffer) //============================================================================= void W3DTreeBuffer::W3DTreeTextureClass::setLOD(Int LOD) const { - if (Peek_D3D_Texture()) { - DX8_ErrorCode(Peek_D3D_Texture()->SetLOD((DWORD)LOD)); - } + Set_LOD(static_cast(LOD)); } //============================================================================= // W3DTreeBuffer::W3DTreeTextureClass::Apply @@ -288,6 +291,42 @@ ShaderClass ShaderClass::_PresetAlpha2DShader(SC_ALPHA_2D); // Private Functions //----------------------------------------------------------------------------- +static Bool TreeShadowDecalIntersectsCamera(const CameraClass *camera, const TTree &tree, const TTreeType &treeType) +{ + if (tree.visible) + { + return true; + } + + // Tree decals are flat ground quads centered at the tree origin. Cull them with their own + // footprint instead of the tree mesh sphere so an edge-overlapping shadow is queued before + // the tree itself enters the camera frustum. + SphereClass shadowBounds; + shadowBounds.Center = tree.location; + shadowBounds.Center.Z = 0.0f; + shadowBounds.Radius = treeType.m_shadowSize * 0.75f + 8.0f; + return !camera->Cull_Sphere(shadowBounds); +} + +static Bool TreeShadowCasterIntersectsSunCull(const TTree &tree) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + float shadowCullCenter[3] = { 0.0f, 0.0f, 0.0f }; + float shadowCullRadius = 0.0f; + if (GGC_GetBgfxSunShadowCullBox(shadowCullCenter, &shadowCullRadius) == 0 || shadowCullRadius <= 0.0f) { + return false; + } + + const float dx = tree.bounds.Center.X - shadowCullCenter[0]; + const float dy = tree.bounds.Center.Y - shadowCullCenter[1]; + const float dz = tree.bounds.Center.Z - shadowCullCenter[2]; + const float reach = shadowCullRadius + tree.bounds.Radius; + return (dx * dx + dy * dy + dz * dz) <= (reach * reach); +#else + return false; +#endif +} + //============================================================================= // W3DTreeBuffer::cull //============================================================================= @@ -309,6 +348,11 @@ void W3DTreeBuffer::cull(const CameraClass * camera) for (curTree=0; curTreeCull_Sphere(m_trees[curTree].bounds); + Bool shadowCasterVisible = false; + Int type = m_trees[curTree].treeType; + if (!visible && type >= 0 && type < m_numTreeTypes && m_treeTypes[type].m_mesh != nullptr) { + shadowCasterVisible = TreeShadowCasterIntersectsSunCull(m_trees[curTree]); + } if (visible != m_trees[curTree].visible) { m_trees[curTree].visible=visible; m_anythingChanged = true; @@ -316,6 +360,10 @@ void W3DTreeBuffer::cull(const CameraClass * camera) doKey = true; } } + if (shadowCasterVisible != m_trees[curTree].shadowCasterVisible) { + m_trees[curTree].shadowCasterVisible = shadowCasterVisible; + m_anythingChanged = true; + } // Also calculate sort key if a tree is visible, and the view changed setting m_updateAllKeys to true. if (doKey || (visible&&m_updateAllKeys)) { // The sort key is essentially the distance of location in the direction of the @@ -723,11 +771,11 @@ void W3DTreeBuffer::loadTreesInVertexAndIndexBuffers(RefRenderObjListIterator *p UnsignedShort *ib; // Lock the buffers. #ifdef USE_STATIC - DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexTree[bNdx], 0); - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexTree[bNdx], 0); + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(m_indexTree[bNdx], 0); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexTree[bNdx], 0); #else - DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexTree[bNdx], D3DLOCK_DISCARD); - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexTree[bNdx], D3DLOCK_DISCARD); + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(m_indexTree[bNdx], RB_LOCK_DISCARD); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexTree[bNdx], RB_LOCK_DISCARD); #endif vb=(VertexFormatXYZNDUV1*)lockVtxBuffer.Get_Vertex_Array(); ib = lockIdxBuffer.Get_Index_Array(); @@ -748,7 +796,7 @@ void W3DTreeBuffer::loadTreesInVertexAndIndexBuffers(RefRenderObjListIterator *p if (type<0 || m_treeTypes[type].m_mesh == nullptr) { continue; // Deleted tree or missing mesh. [6/9/2003] } - if (!m_trees[curTree].visible) continue; + if (!m_trees[curTree].visible && !m_trees[curTree].shadowCasterVisible) continue; Real scale = m_trees[curTree].scale; Vector3 loc = m_trees[curTree].location; Real theSin = m_trees[curTree].sin; @@ -921,9 +969,9 @@ void W3DTreeBuffer::updateVertexBuffer() VertexFormatXYZNDUV1 *vb; // Lock the buffers. #ifdef USE_STATIC - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexTree[bNdx], 0); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexTree[bNdx], 0); #else - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexTree[bNdx], D3DLOCK_DISCARD); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexTree[bNdx], RB_LOCK_DISCARD); #endif vb=(VertexFormatXYZNDUV1*)lockVtxBuffer.Get_Vertex_Array(); if (!vb) { @@ -942,7 +990,7 @@ void W3DTreeBuffer::updateVertexBuffer() continue; // not toppling or pushed, no need to update. jba [7/11/2003] } m_anyPushChanged = true; - if (!m_trees[curTree].visible) continue; + if (!m_trees[curTree].visible && !m_trees[curTree].shadowCasterVisible) continue; Real scale = m_trees[curTree].scale; Vector3 loc = m_trees[curTree].location; Real theSin = m_trees[curTree].sin; @@ -1055,11 +1103,11 @@ void W3DTreeBuffer::freeTreeBuffers() } if (m_dwTreePixelShader) - DX8Wrapper::_Get_D3D_Device8()->DeletePixelShader(m_dwTreePixelShader); + g_renderBackend->Delete_Pixel_Shader(m_dwTreePixelShader); m_dwTreePixelShader = 0; if (m_dwTreeVertexShader) - DX8Wrapper::_Get_D3D_Device8()->DeleteVertexShader(m_dwTreeVertexShader); + g_renderBackend->Delete_Vertex_Shader(m_dwTreeVertexShader); m_dwTreeVertexShader = 0; } @@ -1152,34 +1200,26 @@ void W3DTreeBuffer::allocateTreeBuffers() Int i; for (i=0; i( + g_renderBackend != nullptr ? + g_renderBackend->Get_Legacy_Vertex_Shader_Declaration(RB_LEGACY_VERTEX_DECL_XYZNDUV1) : + nullptr); + hr = W3DShaderManager::LoadAndCreateLegacyShader("shaders\\Trees.vso", declaration, 0, true, &m_dwTreeVertexShader); if (FAILED(hr)) return; - hr = W3DShaderManager::LoadAndCreateD3DShader("shaders\\Trees.pso", &Declaration[0], 0, false, &m_dwTreePixelShader); + hr = W3DShaderManager::LoadAndCreateLegacyShader("shaders\\Trees.pso", nullptr, 0, false, &m_dwTreePixelShader); if (FAILED(hr)) return; } @@ -1351,8 +1391,8 @@ void W3DTreeBuffer::addTree(DrawableID id, Coord3D location, Real scale, Real an } Real randomScale = GameClientRandomValueReal( 1.0f - randomScaleAmount, 1.0f+ randomScaleAmount ); - m_trees[m_numTrees].sin = WWMath::Sin(angle); - m_trees[m_numTrees].cos = WWMath::Cos(angle); + m_trees[m_numTrees].sin = WWMath::Sinf_Legacy(angle); + m_trees[m_numTrees].cos = WWMath::Cosf_Legacy(angle); if (randomScaleAmount>0.0f) { // Randomizes the scale and orientation of trees. m_trees[m_numTrees].scale = scale*randomScale; @@ -1369,6 +1409,7 @@ void W3DTreeBuffer::addTree(DrawableID id, Coord3D location, Real scale, Real an m_trees[m_numTrees].bounds.Center += m_trees[m_numTrees].location; // Initially set it invisible. cull will update it's visibility flag. m_trees[m_numTrees].visible = false; + m_trees[m_numTrees].shadowCasterVisible = false; m_trees[m_numTrees].drawableID = id; m_trees[m_numTrees].firstIndex = 0; @@ -1396,8 +1437,8 @@ Bool W3DTreeBuffer::updateTreePosition(DrawableID id, Coord3D location, Real ang for (i=0; isetSize(m_treeTypes[type].m_shadowSize, m_treeTypes[type].m_shadowSize); m_shadow->setPosition(m_trees[curTree].location.X, m_trees[curTree].location.Y, m_trees[curTree].location.Z); TheW3DProjectedShadowManager->queueDecal(m_shadow); @@ -1569,11 +1613,11 @@ 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); - DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC_DX8, 6); + g_renderBackend->Set_Shader(detailAlphaShader); + g_renderBackend->Set_Texture(0,m_treeTexture); + DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC, 6); //draw an infinite sky plane - DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8, DX8_FVF_XYZNDUV2, 4); + DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC, DX8_FVF_XYZNDUV2, 4); { DynamicIBAccessClass::WriteLockClass ibLock(&ib_access); UnsignedShort *ndx = ibLock.Get_Index_Array(); @@ -1622,49 +1666,48 @@ 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); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXCOORDINDEX, 0); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_TEXCOORDINDEX, 1); + g_renderBackend->Set_Texture(0,m_treeTexture); + g_renderBackend->Set_Texture(1,nullptr); + g_renderBackend->Set_Texture_Coord_Source(0, RB_TEXCOORD_MESH_UV, 0); + g_renderBackend->Set_Texture_Coord_Source(1, RB_TEXCOORD_MESH_UV, 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; - DX8Wrapper::_Get_DX8_Transform(D3DTS_WORLD, matWorld); - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, matView); - DX8Wrapper::_Get_DX8_Transform(D3DTS_PROJECTION, matProj); - D3DXMATRIX mat; - D3DXMatrixMultiply( &mat, &matView, &matProj ); - D3DXMatrixMultiply( &mat, &matWorld, &mat ); - D3DXMatrixTranspose( &mat, &mat ); + Matrix4x4 worldTransform; + Matrix4x4 viewTransform; + Matrix4x4 projectionTransform; + g_renderBackend->Get_Transform(RB_TRANSFORM_WORLD, worldTransform); + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, viewTransform); + g_renderBackend->Get_Transform(RB_TRANSFORM_PROJECTION, projectionTransform); + Matrix4x4 mat = projectionTransform * viewTransform * worldTransform; // c4 - Composite World-View-Projection Matrix - DX8Wrapper::_Get_D3D_Device8()->SetVertexShaderConstant( 4, &mat, 4 ); + g_renderBackend->Set_Vertex_Shader_Constant( 4, &mat, 4 ); Vector4 noSway(0,0,0,0); - DX8Wrapper::_Get_D3D_Device8()->SetVertexShaderConstant( 8, &noSway, 1 ); + g_renderBackend->Set_Vertex_Shader_Constant( 8, &noSway, 1 ); // c8 - c8+MAX_SWAY_TYPES - the sway amount. for (i=0; iSetVertexShaderConstant( 9+i, &sway4, 1 ); + g_renderBackend->Set_Vertex_Shader_Constant( 9+i, &sway4, 1 ); } W3DShroud *shroud; @@ -1678,30 +1721,53 @@ void W3DTreeBuffer::drawTrees(CameraClass * camera, RefRenderObjListIterator *pD xoffset = -(float)shroud->getDrawOriginX() + width; yoffset = -(float)shroud->getDrawOriginY() + height; Vector4 offset(xoffset, yoffset, 0, 0); - DX8Wrapper::_Get_D3D_Device8()->SetVertexShaderConstant( 32, &offset, 1 ); + g_renderBackend->Set_Vertex_Shader_Constant( 32, &offset, 1 ); width = 1.0f/(width*shroud->getTextureWidth()); height = 1.0f/(height*shroud->getTextureHeight()); offset.Set(width, height, 1, 1); - DX8Wrapper::_Get_D3D_Device8()->SetVertexShaderConstant( 33, &offset, 1 ); + g_renderBackend->Set_Vertex_Shader_Constant( 33, &offset, 1 ); } else { Vector4 offset(0,0,0,0); - DX8Wrapper::_Get_D3D_Device8()->SetVertexShaderConstant( 32, &offset, 1 ); - DX8Wrapper::_Get_D3D_Device8()->SetVertexShaderConstant( 33, &offset, 1 ); + g_renderBackend->Set_Vertex_Shader_Constant( 32, &offset, 1 ); + g_renderBackend->Set_Vertex_Shader_Constant( 33, &offset, 1 ); } - DX8Wrapper::Set_Vertex_Shader(m_dwTreeVertexShader); -#if 0 - DX8Wrapper::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) - mulTwoX = 1.0f; - DX8Wrapper::_Get_D3D_Device8()->SetPixelShaderConstant(1, D3DXVECTOR4(mulTwoX, mulTwoX, mulTwoX, mulTwoX), 1); -#endif + 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); + } } else { - DX8Wrapper::Set_Vertex_Shader(DX8_FVF_XYZNDUV1); + g_renderBackend->Set_Vertex_Shader(DX8_FVF_XYZNDUV1); } @@ -1710,22 +1776,23 @@ 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(); - 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->Apply_Render_State_Changes(); + if (m_dwTreeVertexShader) { + g_renderBackend->Set_Vertex_Shader(m_dwTreeVertexShader); + g_renderBackend->Set_Texture_Coord_Source(0, RB_TEXCOORD_MESH_UV, 0); + g_renderBackend->Set_Texture_Coord_Source(1, RB_TEXCOORD_MESH_UV, 1); + g_renderBackend->Set_Texture_Transform_Mode(1, 0, false); + } + 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->Set_Tree_Vertex_Shader_Active(false); + g_renderBackend->Invalidate_Cached_Render_States(); //code above mucks around with W3D states so make sure we reset } @@ -1966,7 +2033,3 @@ void W3DTreeBuffer::loadPostProcess() { // empty. jba [8/11/2003] } - - - - diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DVideoBuffer.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DVideoBuffer.cpp index 977cf899c96..1c9b3c48e66 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DVideoBuffer.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DVideoBuffer.cpp @@ -90,8 +90,6 @@ // Private Functions //---------------------------------------------------------------------------- - - //---------------------------------------------------------------------------- // Public Functions //---------------------------------------------------------------------------- @@ -104,7 +102,7 @@ W3DVideoBuffer::W3DVideoBuffer( VideoBuffer::Type format ) : VideoBuffer(format), m_texture(nullptr), - m_surface(nullptr) + m_lockedTexture(FALSE) { } @@ -168,16 +166,20 @@ void* W3DVideoBuffer::lock() { void *mem = nullptr; - if ( m_surface != nullptr ) + if ( m_lockedTexture ) { unlock(); } - m_surface = m_texture->Get_Surface_Level(); - - if ( m_surface ) + if ( m_texture ) { - mem = m_surface->Lock( (Int*) &m_pitch ); + TextureClass::MutableTextureMipView mip = m_texture->Begin_Mip_Write(0); + if ( mip.Is_Valid() ) + { + m_pitch = mip.Pitch; + mem = mip.Data; + m_lockedTexture = TRUE; + } } return mem; @@ -189,11 +191,13 @@ void* W3DVideoBuffer::lock() void W3DVideoBuffer::unlock() { - if ( m_surface != nullptr ) + if ( m_lockedTexture ) { - m_surface->Unlock(); - m_surface->Release_Ref(); - m_surface = nullptr; + if ( m_texture != nullptr ) + { + m_texture->End_Mip_Write(0); + } + m_lockedTexture = FALSE; } } @@ -220,7 +224,7 @@ void W3DVideoBuffer::free() m_texture->Release_Ref(); m_texture = nullptr; } - m_surface = nullptr; + m_lockedTexture = FALSE; VideoBuffer::free(); } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index e65724084c5..ab4e94792fe 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -33,6 +33,7 @@ /////////////////////////////////////////////////////////////////////////////////////////////////// // SYSTEM INCLUDES //////////////////////////////////////////////////////////////////////////////// +#include #include #include @@ -85,12 +86,13 @@ #include "W3DDevice/GameClient/W3DDisplay.h" #include "W3DDevice/GameClient/W3DScene.h" #include "W3DDevice/GameClient/W3DView.h" -#include "d3dx8math.h" #include "W3DDevice/GameClient/W3DShaderManager.h" #include "W3DDevice/GameClient/Module/W3DModelDraw.h" #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" @@ -269,6 +271,7 @@ void W3DView::setOrigin( Int x, Int y) #define MIN_CAPPED_ZOOM (0.5f) //WST 10.19.2002. JSC integrated 5/20/03. void W3DView::buildCameraPosition( Vector3& sourcePos, Vector3& targetPos ) { + const Real zoom = getZoom(); const Real angle = getAngle(); const Real pitch = getPitch(); @@ -1525,7 +1528,7 @@ void W3DView::update() if (cameraLockObj->isUsingAirborneLocomotor() && cameraLockObj->isAboveTerrainOrWater()) { Matrix3D camXForm; - Real idealZRot = cameraLockObj->getOrientation() - M_PI_2; + Real idealZRot = cameraLockObj->getOrientation() - WWMATH_HALF_PI; if (m_snapImmediate) { @@ -1914,7 +1917,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; @@ -2234,7 +2237,7 @@ void W3DView::setPitchToDefault() void W3DView::setDefaultView(Real pitch, Real angle, Real maxHeight) { // MDC - we no longer want to rotate maps (design made all of them right to begin with) - // m_defaultAngle = angle * M_PI/180.0f; + // m_defaultAngle = angle * WWMATH_PI/180.0f; setDefaultPitch(pitch); m_maxHeightAboveGround = TheGlobalData->m_maxCameraHeight*maxHeight; if (m_minHeightAboveGround > m_maxHeightAboveGround) @@ -2779,7 +2782,7 @@ void W3DView::rotateCameraTowardPosition(const Coord3D *pLoc, Int milliseconds, Vector2 dir(pLoc->x-curPos.x, pLoc->y-curPos.y); const Real dirLength = dir.Length(); if (dirLength<0.1f) return; - Real angle = WWMath::Acos(dir.X/dirLength); + Real angle = WWMath::Acos_Legacy(dir.X/dirLength); if (dir.Y<0.0f) { angle = -angle; } @@ -2922,7 +2925,7 @@ void W3DView::cameraModLookToward(Coord3D *pLoc) Vector2 dir(pLoc->x-result.x, pLoc->y-result.y); const Real dirLength = dir.Length(); if (dirLength<0.1f) continue; - Real angle = WWMath::Acos(dir.X/dirLength); + Real angle = WWMath::Acos_Legacy(dir.X/dirLength); if (dir.Y<0.0f) { angle = -angle; } @@ -3003,7 +3006,7 @@ void W3DView::cameraModFinalLookToward(Coord3D *pLoc) Vector2 dir(pLoc->x-result.x, pLoc->y-result.y); const Real dirLength = dir.Length(); if (dirLength<0.1f) continue; - Real angle = WWMath::Acos(dir.X/dirLength); + Real angle = WWMath::Acos_Legacy(dir.X/dirLength); if (dir.Y<0.0f) { angle = -angle; } @@ -3184,7 +3187,7 @@ void W3DView::setupWaypointPath(Bool orient) m_mcwpInfo.waySegLength[i] = dirLength; m_mcwpInfo.totalDistance += m_mcwpInfo.waySegLength[i]; if (orient && dirLength >= 0.1f) { - angle = WWMath::Acos(dir.X/dirLength); + angle = WWMath::Acos_Legacy(dir.X/dirLength); if (dir.Y<0.0f) { angle = -angle; } @@ -3260,7 +3263,7 @@ static Real makeQuadraticS(Real t) tPrime = 0.5 * (2*t*2*t); } else { tPrime = (t-0.5)*2; - tPrime = WWMath::Sqrt(tPrime); + tPrime = WWMath::Sqrt_Legacy(tPrime); tPrime = 0.5 + 0.5*(tPrime); } return tPrime*0.5 + t*0.5; @@ -3294,7 +3297,7 @@ void W3DView::rotateCameraOneFrame() const Real dirLength = dir.Length(); if (dirLength>=0.1f) { - Real angle = WWMath::Acos(dir.X/dirLength); + Real angle = WWMath::Acos_Legacy(dir.X/dirLength); if (dir.Y<0.0f) { angle = -angle; } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index 4d4946c2b3a..6fe7d942ada 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -42,12 +42,18 @@ #include "WW3D2/rinfo.h" #include "WW3D2/camera.h" #include "WW3D2/scene.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/renderdebugstats.h" +#include "WW3D2/statistics.h" #include "WW3D2/light.h" -#include "d3dx8math.h" #include "WWLib/simplevec.h" #include "WW3D2/mesh.h" #include "WW3D2/matinfo.h" +#include "WW3D2/dx8fvf.h" +#include "WW3D2/indexbuffer.h" +#include "WW3D2/renderbufferclasses.h" +#include "WW3D2/vertexbuffer.h" #include "Common/FramePacer.h" #include "Common/GameState.h" @@ -69,6 +75,158 @@ +// 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) +{ + if (g_renderBackend != nullptr) + g_renderBackend->Bind_Texture_Immediate(stage, tex); +} + +static inline bool W3DWater_UseBackendWater() +{ + return g_renderBackend != nullptr && g_renderBackend->Has_Shader_Pipeline(); +} + +static inline UnsignedInt W3DWater_ScaleDiffuseAlpha(UnsignedInt diffuse, Real scale) +{ + Int alpha = (diffuse >> 24) & 0xff; + alpha = static_cast(alpha * WWMath::Clamp(scale, 0.0f, 1.0f) + 0.5f); + return (diffuse & 0x00ffffff) | (static_cast(alpha) << 24); +} + +static inline Real W3DWater_GetBgfxShoreAlpha(Real x, Real y, Real waterZ, Real fadeDepthScale, Bool quadraticFade) +{ + if (!W3DWater_UseBackendWater() + || !TheGlobalData + || !TheGlobalData->m_showSoftWaterEdge + || !TheWaterTransparency + || TheWaterTransparency->m_transparentWaterDepth <= 0.0f + || !TheTerrainRenderObject + || !TheTerrainRenderObject->getMap()) + { + return 1.0f; + } + + const Real terrainZ = TheTerrainRenderObject->getHeightMapHeight(x, y, nullptr); + const Real depth = waterZ - terrainZ; + if (depth <= 0.0f) + { + return 0.0f; + } + + const Real fadeDepth = TheWaterTransparency->m_transparentWaterDepth * fadeDepthScale; + Real alpha = WWMath::Clamp(depth / fadeDepth, 0.0f, 1.0f); + if (quadraticFade) + { + return alpha * alpha; + } + return alpha * alpha * (3.0f - 2.0f * alpha); +} + +static void W3DWater_FillWhiteTexture(TextureClass *texture) +{ + if (texture == nullptr) + { + return; + } + + TextureClass::MutableTextureMipView mip = texture->Begin_Mip_Write(0); + if (!mip.Is_Valid()) + { + return; + } + + if (mip.Format == WW3D_FORMAT_A4R4G4B4) + { + *reinterpret_cast(mip.Data) = 0xffff; + } + else if (mip.Format == WW3D_FORMAT_A8R8G8B8) + { + *reinterpret_cast(mip.Data) = 0xffffffff; + } + texture->End_Mip_Write(0); +} + +static inline void W3DWater_SetTextureTransform(unsigned stage, const Matrix4x4 & matrix) +{ + if (g_renderBackend != nullptr) + g_renderBackend->Set_Texture_Transform(stage, matrix); +} + +static inline Matrix4x4 W3DWater_MakeScaleTextureMatrix(float sx, float sy, float sz) +{ + return Matrix4x4( + sx, 0.0f, 0.0f, 0.0f, + 0.0f, sy, 0.0f, 0.0f, + 0.0f, 0.0f, sz, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); +} + +static inline Matrix4x4 W3DWater_MakeTranslationTextureMatrix(float x, float y, float z) +{ + return Matrix4x4( + 1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f); +} + +static inline void W3DWater_SetNoiseTextureTransform(unsigned stage, float repeat, float origin) +{ + if (g_renderBackend == nullptr) + return; + + Matrix4x4 view; + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, view); + const Matrix4x4 destMatrix = + W3DWater_MakeTranslationTextureMatrix(origin, origin, 0.0f) * + W3DWater_MakeScaleTextureMatrix(repeat, repeat, 1.0f) * + view.Inverse(); + W3DWater_SetTextureTransform(stage, destMatrix); +} + +static inline void W3DWater_DisableTextureTransform(unsigned stage) +{ + if (g_renderBackend != nullptr) + g_renderBackend->Set_Texture_Transform_Mode(stage, 0, false); +} + +static inline void W3DWater_SetCameraSpaceTexcoord2(unsigned stage, unsigned uv_index) +{ + if (g_renderBackend != nullptr) { + g_renderBackend->Set_Texture_Coord_Source(stage, RB_TEXCOORD_CAMERA_SPACE_POSITION, uv_index); + g_renderBackend->Set_Texture_Transform_Mode(stage, 2, false); + } +} + +static inline void W3DWater_ResetMeshTexcoord(unsigned stage, unsigned uv_index) +{ + if (g_renderBackend != nullptr) { + g_renderBackend->Set_Texture_Coord_Source(stage, RB_TEXCOORD_MESH_UV, uv_index); + g_renderBackend->Set_Texture_Transform_Mode(stage, 0, false); + } +} + +static inline void W3DWater_SetStageAddress2D(unsigned stage, RenderBackendTextureAddressMode address_mode) +{ + g_renderBackend->Set_Texture_Address_Mode(stage, address_mode, address_mode, RB_TEXTURE_ADDRESS_WRAP); +} + +static inline void W3DWater_SetStageMinMagFilter(unsigned stage, + RenderBackendTextureSampleFilter min_filter, + RenderBackendTextureSampleFilter mag_filter) +{ + g_renderBackend->Set_Texture_Min_Mag_Filter(stage, min_filter, mag_filter); +} + +static inline void W3DWater_SetStageMipFilter(unsigned stage, RenderBackendTextureSampleFilter mip_filter) +{ + g_renderBackend->Set_Texture_Mip_Filter(stage, mip_filter); +} + #define MIPMAP_BUMP_TEXTURE // DEFINES //////////////////////////////////////////////////////////////////////////////////////// @@ -115,9 +273,6 @@ typedef VertexFormatXYZNDUV2 MaterMeshVertexFormat; typedef VertexFormatXYZDUV2 MaterMeshVertexFormat; #endif -// Converts a FLOAT to a DWORD for use in SetRenderState() calls -static inline DWORD F2DW( FLOAT f ) { return *((DWORD*)&f); } - #define DRAW_WATER_WAKES /// @todo: Fix clipping of objects that intersect the mirror surface //#define CLIP_GEOMETRY_TO_PLANE // this enables clipping of objects that intersect the mirror surfaces @@ -208,12 +363,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,61 +378,44 @@ void WaterRenderObjClass::setupJbaWaterShader() // Setting *setting=&m_settings[m_tod]; - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_ADD ); + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_ADD); if (!m_riverAlphaEdge->Is_Initialized()) m_riverAlphaEdge->Init(); - DX8Wrapper::_Get_D3D_Device8()->SetTexture(3,m_riverAlphaEdge->Peek_D3D_Texture()); - 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); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_TEXCOORDINDEX, 0); - DX8Wrapper::Set_DX8_Texture_Stage_State(3, D3DTSS_TEXCOORDINDEX, 1); + W3DWater_BindTexture(3, m_riverAlphaEdge); + W3DWater_SetStageAddress2D(3, RB_TEXTURE_ADDRESS_WRAP); + g_renderBackend->Set_Texture_Coord_Source(0, RB_TEXCOORD_MESH_UV, 0); + g_renderBackend->Set_Texture_Coord_Source(1, RB_TEXCOORD_MESH_UV, 0); + g_renderBackend->Set_Texture_Coord_Source(3, RB_TEXCOORD_MESH_UV, 1); Bool doSparkles = true; 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); + W3DWater_SetStageAddress2D(1, RB_TEXTURE_ADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); - - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); - D3DXMATRIX inv; - float det; - D3DXMatrixInverse(&inv, &det, &curView); - D3DXMATRIX scale; - D3DXMatrixScaling(&scale, NOISE_REPEAT_FACTOR, NOISE_REPEAT_FACTOR,1); - D3DXMATRIX destMatrix = inv * scale; - D3DXMatrixTranslation(&scale, m_riverVOrigin, m_riverVOrigin,0); - destMatrix = destMatrix*scale; - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE2, destMatrix); - - } - m_pDev->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); - m_pDev->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); - m_pDev->SetTextureStageState( 1, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); - m_pDev->SetTextureStageState( 1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); - m_pDev->SetTextureStageState( 2, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); - m_pDev->SetTextureStageState( 2, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); - m_pDev->SetTextureStageState( 3, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); - m_pDev->SetTextureStageState( 3, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); + W3DWater_SetCameraSpaceTexcoord2(2, 0); + W3DWater_SetStageAddress2D(2, RB_TEXTURE_ADDRESS_WRAP); + + W3DWater_SetNoiseTextureTransform(2, NOISE_REPEAT_FACTOR, m_riverVOrigin); + + } + W3DWater_SetStageMinMagFilter(0, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); + W3DWater_SetStageMinMagFilter(1, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); + W3DWater_SetStageMinMagFilter(2, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); + W3DWater_SetStageMinMagFilter(3, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); if (m_riverWaterPixelShader){ - DX8Wrapper::_Get_D3D_Device8()->SetPixelShaderConstant(0, D3DXVECTOR4(REFLECTION_FACTOR, REFLECTION_FACTOR, REFLECTION_FACTOR, 1.0f), 1); - DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_riverWaterPixelShader); + const float reflectionFactor[4] = { REFLECTION_FACTOR, REFLECTION_FACTOR, REFLECTION_FACTOR, 1.0f }; + g_renderBackend->Set_Pixel_Shader_Constant(0, &reflectionFactor, 1); + g_renderBackend->Set_Pixel_Shader(m_riverWaterPixelShader); } } @@ -308,12 +446,6 @@ WaterRenderObjClass::~WaterRenderObjClass() REF_PTR_RELEASE(m_settings[i].waterTexture); } - i=NUM_BUMP_FRAMES; - while (i--) - { SAFE_RELEASE( m_pBumpTexture[i]); - SAFE_RELEASE( m_pBumpTexture2[i]); - } - delete [] m_meshData; m_meshData = nullptr; m_meshDataSize = 0; @@ -339,6 +471,7 @@ WaterRenderObjClass::WaterRenderObjClass() m_dx=0; m_dy=0; m_indexBuffer=nullptr; + m_waterMeshIndexBuffer=nullptr; m_waterTrackSystem = nullptr; m_doWaterGrid = FALSE; m_meshVertexMaterialClass=nullptr; @@ -350,12 +483,6 @@ WaterRenderObjClass::WaterRenderObjClass() m_tod=TIME_OF_DAY_AFTERNOON; m_pReflectionTexture=nullptr; m_skyBox=nullptr; - m_vertexBufferD3D=nullptr; - m_indexBufferD3D=nullptr; - m_vertexBufferD3DOffset=0; - - m_dwWavePixelShader=0; - m_dwWaveVertexShader=0; m_meshData=nullptr; m_meshDataSize = 0; m_meshInMotion = FALSE; @@ -369,18 +496,14 @@ WaterRenderObjClass::WaterRenderObjClass() m_gridWidth = m_gridCellsX * m_gridCellSize; m_gridHeight = m_gridCellsY * m_gridCellSize; - Int i=NUM_BUMP_FRAMES; - while (i--) - m_pBumpTexture[i]=nullptr; - m_riverVOrigin=0; m_riverTexture=nullptr; m_whiteTexture=nullptr; m_waterNoiseTexture=nullptr; m_riverAlphaEdge=nullptr; - m_waterPixelShader=0; ///Get_Level_Description(d3dsd); - - if (Get_Bytes_Per_Pixel(d3dsd.Format) != 4) - { - // LORENZEN WAS BUGGED BY THIS, - // DEBUG_CRASH(("WaterRenderObjClass::Invalid BumpMap format - Was it compressed?") ); - return S_OK; - } - - if (pBumpSource->Peek_D3D_Texture()) - { - numLevels=pBumpSource->Peek_D3D_Texture()->GetLevelCount(); - } - else - return S_OK; - - pTex[0]=DX8Wrapper::_Create_DX8_Texture(d3dsd.Width,d3dsd.Height,WW3D_FORMAT_U8V8,MIP_LEVELS_ALL,D3DPOOL_MANAGED,false); + Int i,j,k; - for (Int level=0; level < numLevels; level++) + for (i=0,j=0,k=0; iGet_Surface_Level(level); - surf->Get_Description(d3dsd); - pSrc=(unsigned char *)surf->Lock((int *)&dwSrcPitch); - - pTex[0]->LockRect( level, &d3dlr, nullptr, 0 ); - DWORD dwDstPitch = (DWORD)d3dlr.Pitch; - BYTE* pDst = (BYTE*)d3dlr.pBits; - - for( DWORD y=0; y1 ) ? 63 : 127; - - switch( D3DFMT_V8U8)//m_BumpMapFormat ) - { - case D3DFMT_V8U8: - *pDstT++ = (BYTE)iDu; - *pDstT++ = (BYTE)iDv; - break; - - case D3DFMT_L6V5U5: - *(WORD*)pDstT = (WORD)( ( (iDu>>3) & 0x1f ) << 0 ); - *(WORD*)pDstT |= (WORD)( ( (iDv>>3) & 0x1f ) << 5 ); - *(WORD*)pDstT |= (WORD)( ( ( uL>>2) & 0x3f ) << 10 ); - pDstT += 2; - break; - - case D3DFMT_X8L8V8U8: - *pDstT++ = (BYTE)iDu; - *pDstT++ = (BYTE)iDv; - *pDstT++ = (BYTE)uL; - *pDstT++ = (BYTE)0L; - break; - } - - // Move one pixel to the left (src is 32-bpp) - pSrcB0+=4; pSrcB1+=4; pSrcB2+=4; + legacyIndices[i]=(UnsignedShort) k+sizeX; + legacyIndices[i+1]=(UnsignedShort) k; + } + if (backendIndices != nullptr) + { + backendIndices[i]=(UnsignedShort) k+sizeX; + backendIndices[i+1]=(UnsignedShort) k; } - - // Move to the next line - pSrc += dwSrcPitch; pDst += dwDstPitch; } - - pTex[0]->UnlockRect(level); - surf->Unlock(); - REF_PTR_RELEASE (surf); - } - -#else - surf=pBumpSource->Get_Surface_Level(); - surf->Get_Description(d3dsd); - pSrc=(unsigned char *)surf->Lock((int *)&dwSrcPitch); - - // Create the bumpmap's surface and texture objects - m_pBumpTexture[i]=DX8Wrapper::_Create_DX8_Texture(d3dsd.Width,d3dsd.Height,WW3D_FORMAT_U8V8,TextureClass::MIP_LEVELS_1,D3DPOOL_MANAGED,false); - - // Fill the bits of the new texture surface with bits from - // a private format. - - m_pBumpTexture[i]->LockRect( 0, &d3dlr, 0, 0 ); - DWORD dwDstPitch = (DWORD)d3dlr.Pitch; - BYTE* pDst = (BYTE*)d3dlr.pBits; - - for( DWORD y=0; y1 ) ? 63 : 127; - - switch( D3DFMT_V8U8)//m_BumpMapFormat ) - { - case D3DFMT_V8U8: - *pDstT++ = (BYTE)iDu; - *pDstT++ = (BYTE)iDv; - break; - - case D3DFMT_L6V5U5: - *(WORD*)pDstT = (WORD)( ( (iDu>>3) & 0x1f ) << 0 ); - *(WORD*)pDstT |= (WORD)( ( (iDv>>3) & 0x1f ) << 5 ); - *(WORD*)pDstT |= (WORD)( ( ( uL>>2) & 0x3f ) << 10 ); - pDstT += 2; - break; - - case D3DFMT_X8L8V8U8: - *pDstT++ = (BYTE)iDu; - *pDstT++ = (BYTE)iDv; - *pDstT++ = (BYTE)uL; - *pDstT++ = (BYTE)0L; - break; - } - - // Move one pixel to the left (src is 32-bpp) - pSrcB0+=4; pSrcB1+=4; pSrcB2+=4; - } - - // Move to the next line - pSrc += dwSrcPitch; pDst += dwDstPitch; - } - - m_pBumpTexture[i]->UnlockRect(0); - surf->Unlock(); -#endif - - return S_OK; -} - -//------------------------------------------------------------------------------------------------- -/** Create and fill a D3D vertex buffer with water surface vertices */ -//------------------------------------------------------------------------------------------------- -HRESULT WaterRenderObjClass::generateVertexBuffer( Int sizeX, Int sizeY, Int vertexSize, Bool doStatic) -{ - m_numVertices=sizeX*sizeY; - //Assuming dynamic vertex buffer, allocate maximum multiple of required size to allow rendering from - //different parts of the buffer. 5-15-03: Disabled this since we use DISCARD mode instead to avoid Nvidia Runtime bug. -MW - //m_numVertices=(65536 / (sizeX*sizeY))*sizeX*sizeY; - - SEA_PATCH_VERTEX* pVertices; - - Setting *setting=&m_settings[m_tod]; - - HRESULT hr; - - //default setting for a dynamic vertex buffer - D3DPOOL pool = D3DPOOL_DEFAULT; - DWORD usage = D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC; - DWORD fvf = WATER_MESH_FVF; - - if (doStatic) - { //change settings for a static vertex buffer - pool = D3DPOOL_MANAGED; - usage = D3DUSAGE_WRITEONLY; - fvf=0;// DX8 Docs confusing on this. Say no FVF for vertex shaders. Else DX8_FVF_XYZDUV1; - m_numVertices=sizeX*sizeY; - } - - if (m_vertexBufferD3D == nullptr) - { // Create vertex buffer - - if (FAILED(hr=m_pDev->CreateVertexBuffer - ( - m_numVertices*vertexSize, - usage, - fvf, - pool, - &m_vertexBufferD3D - ))) - return hr; - } - - m_vertexBufferD3DOffset=0; - - if (!doStatic) - return S_OK; //only create the buffer, other code will fill it. - - // load results into buffer - if (FAILED(hr=m_vertexBufferD3D->Lock - ( - 0, - m_numVertices*sizeof(SEA_PATCH_VERTEX), - (BYTE**)&pVertices, - 0//D3DLOCK_DISCARD - ))) - return hr; - - Int x,z; - for (z=0; zx=(float)x; - pVertices->y=m_level; - pVertices->z=(float)z; - - pVertices->tu=(float)x*PATCH_UV_SCALE; - pVertices->tv=(float)z*PATCH_UV_SCALE; - pVertices->c=setting->transparentWaterDiffuse; //vertex alpha/color - pVertices++; + if (legacyIndices != nullptr) + { + legacyIndices[i]=k-1; + legacyIndices[i+1]=k+sizeX; + } + if (backendIndices != nullptr) + { + backendIndices[i]=k-1; + backendIndices[i+1]=k+sizeX; + } + i+=2; } } - - if (FAILED(hr=m_vertexBufferD3D->Unlock())) return hr; - - return S_OK; } //------------------------------------------------------------------------------------------------- -/** Create and fill a D3D index buffer with water surface strip indices */ +/** Create and fill a backend index buffer with water surface strip indices */ //------------------------------------------------------------------------------------------------- -HRESULT WaterRenderObjClass::generateIndexBuffer(Int sizeX, Int sizeY) +bool WaterRenderObjClass::generateIndexBuffer(Int sizeX, Int sizeY) { - HRESULT hr; - //Will need SizeY-1 strips, each of length SizeX*2 (2 indices per strip segment). //Will also need 2 extra indices to connect each strip to next one (except last strip) //Total index buffer size = (SizeY-1)*(SizeX*2+2) - 2 (drop the extra 2 indices from last strip) m_numIndices=(sizeY-1)*(sizeX*2+2) - 2; - //old way - - // Create index buffer - WORD* pIndices; - - if (FAILED(hr=m_pDev->CreateIndexBuffer - ( - (m_numIndices+2)*sizeof(WORD), - D3DUSAGE_WRITEONLY, - D3DFMT_INDEX16, - D3DPOOL_MANAGED, - &m_indexBufferD3D - ))) - return hr; - - if (FAILED(hr=m_indexBufferD3D->Lock - ( - 0, - m_numIndices*sizeof(WORD), - (BYTE**)&pIndices, - 0 - ))) - return hr; - - Int i,j,k; - - for (i=0,j=0,k=0; iUnlock())) return hr; - - return S_OK; + return true; } //------------------------------------------------------------------------------------------------- @@ -815,31 +662,22 @@ void WaterRenderObjClass::ReleaseResources() { REF_PTR_RELEASE(m_indexBuffer); + REF_PTR_RELEASE(m_waterMeshIndexBuffer); REF_PTR_RELEASE(m_pReflectionTexture); - SAFE_RELEASE(m_vertexBufferD3D); - SAFE_RELEASE(m_indexBufferD3D); if (m_waterTrackSystem) m_waterTrackSystem->ReleaseResources(); - if (m_dwWavePixelShader) - m_pDev->DeletePixelShader(m_dwWavePixelShader); - - if (m_dwWaveVertexShader) - m_pDev->DeleteVertexShader(m_dwWaveVertexShader); - if (m_waterPixelShader) - m_pDev->DeletePixelShader(m_waterPixelShader); + g_renderBackend->Delete_Pixel_Shader(m_waterPixelShader); if (m_trapezoidWaterPixelShader) - m_pDev->DeletePixelShader(m_trapezoidWaterPixelShader); + g_renderBackend->Delete_Pixel_Shader(m_trapezoidWaterPixelShader); if (m_riverWaterPixelShader) - m_pDev->DeletePixelShader(m_riverWaterPixelShader); + g_renderBackend->Delete_Pixel_Shader(m_riverWaterPixelShader); - m_dwWavePixelShader=0; - m_dwWaveVertexShader=0; m_waterPixelShader = 0; m_trapezoidWaterPixelShader=0; m_riverWaterPixelShader=0; @@ -850,12 +688,10 @@ void WaterRenderObjClass::ReleaseResources() //------------------------------------------------------------------------------------------------- void WaterRenderObjClass::ReAcquireResources() { - HRESULT hr; - - m_indexBuffer=NEW_REF(DX8IndexBufferClass,(6)); + m_indexBuffer=NEW_REF(RenderIndexBufferClass,(6)); // Fill up the IB { - DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); UnsignedShort *ib=lockIdxBuffer.Get_Index_Array(); //quad of 2 triangles: // 3-----2 @@ -871,47 +707,18 @@ void WaterRenderObjClass::ReAcquireResources() ib[5]=1; } - m_pDev=DX8Wrapper::_Get_D3D_Device8(); - //We're using the same grid for either 3D Water Mesh or Pixel/Vertex shader. Just //allocate the right size depending on usage if (m_meshData) { //Create new grid data - if (FAILED(generateIndexBuffer(m_gridCellsX+1,m_gridCellsY+1))) - return; - if (FAILED(generateVertexBuffer(m_gridCellsX+1,m_gridCellsY+1,sizeof(MaterMeshVertexFormat),false))) + if (!generateIndexBuffer(m_gridCellsX+1,m_gridCellsY+1)) return; } else if (m_waterType == WATER_TYPE_2_PVSHADER) - { //pixel/vertex shader based water assets. - if (FAILED(hr=generateIndexBuffer(PATCH_SIZE,PATCH_SIZE))) - return; - - if (FAILED(hr=generateVertexBuffer(PATCH_SIZE,PATCH_SIZE,sizeof(SEA_PATCH_VERTEX),true))) - return; - - //shader decleration - DWORD Declaration[]= - { - (D3DVSD_STREAM(0)), - (D3DVSD_REG(0, D3DVSDT_FLOAT3)), // Position - (D3DVSD_REG(1, D3DVSDT_D3DCOLOR)), // Diffuse - (D3DVSD_REG(2, D3DVSDT_FLOAT2)), // Bump map texture - (D3DVSD_END()) - }; - - hr = W3DShaderManager::LoadAndCreateD3DShader("shaders\\wave.pso", &Declaration[0], 0, false, &m_dwWavePixelShader); - if (FAILED(hr)) - return; - - hr = W3DShaderManager::LoadAndCreateD3DShader("shaders\\wave.vso", &Declaration[0], 0, true, &m_dwWaveVertexShader); - if (FAILED(hr)) - return; - - // Create reflection texture - m_pReflectionTexture = DX8Wrapper::Create_Render_Target (SEA_REFLECTION_SIZE, SEA_REFLECTION_SIZE); + { + // Sea water is submitted through backend transient batches. } if (m_waterTrackSystem) @@ -919,53 +726,13 @@ void WaterRenderObjClass::ReAcquireResources() if (W3DShaderManager::getChipset() >= DC_GENERIC_PIXEL_SHADER_1_1) { - ID3DXBuffer *compiledShader; - const char *shader = - "ps.1.1\n \ - tex t0 \n\ - tex t1 \n\ - tex t2 \n\ - tex t3\n\ - mul r0.rgb, v0, t0 ; blend vertex color into t0. \n\ - mov r0.a, t0 ; keep vertex alpha from fading the base water. \n\ - mul r1, t1, t2 ; mul\n\ - add r1.rgb, r1, t3\n\ - mul r1.rgb, r1, v0.a\n\ - +mul r0.a, r0, t3\n\ - add r0.rgb, r0, r1\n"; - hr = D3DXAssembleShader( shader, strlen(shader), 0, nullptr, &compiledShader, nullptr); - if (hr==0) { - hr = DX8Wrapper::_Get_D3D_Device8()->CreatePixelShader((DWORD*)compiledShader->GetBufferPointer(), &m_riverWaterPixelShader); - compiledShader->Release(); - } - shader = - "ps.1.1\n \ - tex t0 \n\ - tex t1 \n\ - texbem t2, t1 ; use t1 as env map adjustment on t2.\n\ - mul r0,v0,t0 ; blend vertex color into t0. \n\ - mul r1.rgb,t2,c0 ; reduce t2 (environment mapped reflection) by constant\n\ - add r0.rgb, r0, r1"; - hr = D3DXAssembleShader( shader, strlen(shader), 0, nullptr, &compiledShader, nullptr); - if (hr==0) { - hr = DX8Wrapper::_Get_D3D_Device8()->CreatePixelShader((DWORD*)compiledShader->GetBufferPointer(), &m_waterPixelShader); - compiledShader->Release(); - } - shader = - "ps.1.1\n \ - tex t0 ;get water texture\n\ - tex t1 ;get white highlights on black background\n\ - tex t2 ;get white highlights with more tiling\n\ - tex t3 ; get black shroud \n\ - mul r0,v0,t0 ; blend vertex color and alpha into base texture. \n\ - mad r0.rgb, t1, t2, r0 ; blend sparkles and noise \n\ - mul r0.rgb, r0, t3 ; blend in black shroud \n\ - ;\n"; - hr = D3DXAssembleShader( shader, strlen(shader), 0, nullptr, &compiledShader, nullptr); - if (hr==0) { - hr = DX8Wrapper::_Get_D3D_Device8()->CreatePixelShader((DWORD*)compiledShader->GetBufferPointer(), &m_trapezoidWaterPixelShader); - compiledShader->Release(); - } + unsigned long legacyHandle = 0; + if (g_renderBackend->Create_Legacy_Pixel_Shader(RB_LEGACY_PIXEL_SHADER_RIVER_WATER, &legacyHandle)) + m_riverWaterPixelShader = legacyHandle; + if (g_renderBackend->Create_Legacy_Pixel_Shader(RB_LEGACY_PIXEL_SHADER_REFLECTIVE_WATER, &legacyHandle)) + m_waterPixelShader = legacyHandle; + if (g_renderBackend->Create_Legacy_Pixel_Shader(RB_LEGACY_PIXEL_SHADER_TRAPEZOID_WATER, &legacyHandle)) + m_trapezoidWaterPixelShader = legacyHandle; } //W3D Invalidate textures after losing the device and since we peek at the textures directly, it won't @@ -980,13 +747,7 @@ void WaterRenderObjClass::ReAcquireResources() m_waterSparklesTexture->Init(); if (m_whiteTexture && !m_whiteTexture->Is_Initialized()) { m_whiteTexture->Init(); - SurfaceClass *surface=m_whiteTexture->Get_Surface_Level(); - int pitch; - void *pBits = surface->Lock(&pitch); - const unsigned int bytesPerPixel = surface->Get_Bytes_Per_Pixel(); - surface->Draw_Pixel(0, 0, 0xffffffff, bytesPerPixel, pBits, pitch); - surface->Unlock(); - REF_PTR_RELEASE(surface); + W3DWater_FillWhiteTexture(m_whiteTexture); } } @@ -1058,38 +819,6 @@ Int WaterRenderObjClass::init(Real waterLevel, Real dx, Real dy, SceneClass *par Set_Force_Visible(TRUE); //water is always visible since it's a composite object made of multiple planes all over the map. ReAcquireResources(); -#if 0 //MD does not support the old bump-mapped water at all so no point loading textures. -MW 8-11-03 - if (type == WATER_TYPE_2_PVSHADER || (W3DShaderManager::getChipset() >= DC_GENERIC_PIXEL_SHADER_1_1)) - { //geforce3 specific water requires some extra D3D assets - m_pDev=DX8Wrapper::_Get_D3D_Device8(); - //save previous thumbnail mode - bool thumbnails_enabled = WW3D::Get_Thumbnail_Enabled(); - WW3D::Set_Thumbnail_Enabled(false); - - //load bump map textures off disk - TextureClass *pBumpSource; //temporary textures in a format W3D understands - TextureClass *pBumpSource2; //temporary textures in a format W3D understands - Int i; - i=NUM_BUMP_FRAMES; - while (i--) - { - char bump_name[128]; - - sprintf(bump_name,"caust%.2d.tga",i); - pBumpSource=WW3DAssetManager::Get_Instance()->Get_Texture(bump_name); - sprintf(bump_name,"caustS%.2d.tga",i); - pBumpSource2=WW3DAssetManager::Get_Instance()->Get_Texture(bump_name); - initBumpMap(m_pBumpTexture+i, pBumpSource); - initBumpMap(m_pBumpTexture2+i, pBumpSource2); - WW3DAssetManager::Get_Instance()->Release_Texture(pBumpSource); - WW3DAssetManager::Get_Instance()->Release_Texture(pBumpSource2); - REF_PTR_RELEASE(pBumpSource); - REF_PTR_RELEASE(pBumpSource2); - } - //restore previous thumpnail mode - WW3D::Set_Thumbnail_Enabled(thumbnails_enabled); - } -#endif //Setup material for regular water m_vertexMaterialClass=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); @@ -1101,11 +830,6 @@ Int WaterRenderObjClass::init(Real waterLevel, Real dx, Real dy, SceneClass *par //Assets used for all types of water m_alphaClippingTexture=WW3DAssetManager::Get_Instance()->Get_Texture(SKYBODY_TEXTURE); - -#ifdef CLIP_GEOMETRY_TO_PLANE - m_alphaClippingTexture=WW3DAssetManager::Get_Instance()->Get_Texture("alphaclip.tga"); -#endif - m_skyBox = ((W3DAssetManager*)W3DAssetManager::Get_Instance())->Create_Render_Obj( "new_skybox", TheGlobalData->m_skyBoxScale, 0); //Enable clamping on all textures used by the skybox (to reduce corner seams). @@ -1129,14 +853,8 @@ Int WaterRenderObjClass::init(Real waterLevel, Real dx, Real dy, SceneClass *par m_riverTexture=WW3DAssetManager::Get_Instance()->Get_Texture(TheWaterTransparency->m_standingWaterTexture.str()); //For some reason setting a null texture does not result in 0xffffffff for pixel shaders so using explicit "white" texture. - m_whiteTexture=MSGNEW("TextureClass") TextureClass(1,1,WW3D_FORMAT_A4R4G4B4,MIP_LEVELS_1); - SurfaceClass *surface=m_whiteTexture->Get_Surface_Level(); - int pitch; - void *pBits = surface->Lock(&pitch); - const unsigned int bytesPerPixel = surface->Get_Bytes_Per_Pixel(); - surface->Draw_Pixel(0, 0, 0xffffffff, bytesPerPixel, pBits, pitch); - surface->Unlock(); - REF_PTR_RELEASE(surface); + m_whiteTexture=MSGNEW("TextureClass") TextureClass(1, 1, WW3D_FORMAT_A4R4G4B4, MIP_LEVELS_1); + W3DWater_FillWhiteTexture(m_whiteTexture); m_waterNoiseTexture=WW3DAssetManager::Get_Instance()->Get_Texture("Noise0000.tga"); m_riverAlphaEdge=WW3DAssetManager::Get_Instance()->Get_Texture("TWAlphaEdge.tga"); @@ -1216,14 +934,8 @@ void WaterRenderObjClass::enableWaterGrid(Bool state) memset(m_meshData,0,sizeof(WaterMeshData)*(m_gridCellsX+1+2)*(m_gridCellsY+1+2)); reset(); - //Release existing grid data - SAFE_RELEASE(m_vertexBufferD3D); - SAFE_RELEASE(m_indexBufferD3D); - //Create new grid data - if (FAILED(generateIndexBuffer(m_gridCellsX+1,m_gridCellsY+1))) - return; - if (FAILED(generateVertexBuffer(m_gridCellsX+1,m_gridCellsY+1,sizeof(MaterMeshVertexFormat),false))) + if (!generateIndexBuffer(m_gridCellsX+1,m_gridCellsY+1)) return; } } @@ -1350,6 +1062,7 @@ void WaterRenderObjClass::replaceSkyboxTexture(const AsciiString& oldTexName, co material->Peek_Texture(i)->Get_Filter().Set_V_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); } } + REF_PTR_RELEASE(material); } } @@ -1360,8 +1073,6 @@ void WaterRenderObjClass::replaceSkyboxTexture(const AsciiString& oldTexName, co void WaterRenderObjClass::setTimeOfDay(TimeOfDay tod) { m_tod=tod; - if (m_waterType == WATER_TYPE_2_PVSHADER) - generateVertexBuffer(PATCH_SIZE,PATCH_SIZE,sizeof(SEA_PATCH_VERTEX),true); //update the water mesh with new lighting/alpha } //------------------------------------------------------------------------------------------------- @@ -1428,13 +1139,16 @@ void WaterRenderObjClass::loadSetting( Setting *setting, TimeOfDay timeOfDay ) //------------------------------------------------------------------------------------------------- /** Our water may use effects that require run-time rendered textures. These * textures need to be updated before we start rendering to the main screen - * render target because D3D doesn't multiple render targets. */ + * render target because the legacy renderer did not support multiple render targets. */ //------------------------------------------------------------------------------------------------- void WaterRenderObjClass::updateRenderTargetTextures(CameraClass *cam) { - if (m_waterType == WATER_TYPE_2_PVSHADER && getClippedWaterPlane(cam, nullptr) && +#if !defined(GGC_RENDER_BACKEND_BGFX) + if (!W3DWater_UseBackendWater() && + m_waterType == WATER_TYPE_2_PVSHADER && getClippedWaterPlane(cam, nullptr) && TheTerrainRenderObject && TheTerrainRenderObject->getMap()) renderMirror(cam); //generate texture containing reflected scene +#endif } //------------------------------------------------------------------------------------------------- @@ -1443,7 +1157,7 @@ void WaterRenderObjClass::updateRenderTargetTextures(CameraClass *cam) void WaterRenderObjClass::renderMirror(CameraClass *cam) { #ifdef EXTENDED_STATS - if (DX8Wrapper::stats.m_disableWater) { + if (g_renderDebugStats.m_disableWater) { return; } #endif @@ -1476,7 +1190,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 @@ -1511,8 +1225,11 @@ void WaterRenderObjClass::renderMirror(CameraClass *cam) WW3D::End_Render(false); - // Change the rendertarget back to the main backbuffer - DX8Wrapper::Set_Render_Target((IDirect3DSurface8 *)nullptr); + // TheSuperHackers @fix bobtista 21/04/2026 Route through g_renderBackend + // so the bgfx backend's renderToTexture flag gets reset. Same pattern as + // TexProjectClass::Compute_Texture. The old direct-device bypass left + // renderToTexture stuck at true after the reflection pass. + g_renderBackend->Set_Render_Target_With_Z(nullptr, nullptr); } //------------------------------------------------------------------------------------------------- @@ -1539,7 +1256,7 @@ void WaterRenderObjClass::Render(RenderInfoClass & rinfo) return; //water is not drawn in wireframe or custom scene passes #ifdef EXTENDED_STATS - if (DX8Wrapper::stats.m_disableWater) { + if (g_renderDebugStats.m_disableWater) { return; } #endif @@ -1571,7 +1288,7 @@ void WaterRenderObjClass::Render(RenderInfoClass & rinfo) case WATER_TYPE_2_PVSHADER: //Pixel/Vertex Shader based water which uses an off-screen rendered reflection texture - drawSea(rinfo); //draw water surface + drawSeaBatch(rinfo); break; case WATER_TYPE_1_FB_REFLECTION: @@ -1608,78 +1325,6 @@ void WaterRenderObjClass::Render(RenderInfoClass & rinfo) //flip the winding order of polygons to draw the reflected back sides. ShaderClass::Invert_Backface_Culling(true); - #ifdef CLIP_GEOMETRY_TO_PLANE - // Set a clip plane, so that only objects above the water are reflected - WaterPlane.W *= -1.0f; //flip sign of plane distance for D3D use. - - // DX8Wrapper::Set_DX8_Clip_Plane( 0, &WaterPlane.X ); - // DX8Wrapper::Set_DX8_Render_State(D3DRS_CLIPPLANEENABLE, D3DCLIPPLANE0 ); //turn on first clip plane - - // Alternate Clipping Method using alpha testing hack! - /**************************************************************************************/ - - //get current view matrix - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); - - //get inverse of view matrix(= view to world matrix) - D3DXMATRIX inv; - Real det; - D3DXMatrixInverse(&inv, &det, &curView); - - //create clipping matrix by inserting our plane equation into the 1st column - D3DXMATRIX clipMatrix; - D3DXMatrixIdentity(&clipMatrix); - clipMatrix(0,0)=WaterNormal.X; - clipMatrix(1,0)=WaterNormal.Y; - clipMatrix(2,0)=WaterNormal.Z; - clipMatrix(3,0)=WaterPlane.W+0.5f; - inv *=clipMatrix; - - // Change texture wrapping mode to 'clamp' for texture stage 1 - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); - - // Use CameraSpace vertices as input to matrix and use texture wrap mode from stage 1 - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION|1); - // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); - - // Set texture generation matrix for stage 1 - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE1, inv); - - // Disable bilinear filtering - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MINFILTER, D3DTEXF_POINT); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_MAGFILTER, D3DTEXF_POINT); - - // Pass stage 0 texture data untouched(by modulating with white) - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE ); //stage 1 texture - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_CURRENT ); //previous stage texture - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_MODULATE ); //module with white => does nothing - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); //stage 1 texture - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG2, D3DTA_CURRENT ); //previous stage texture - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); //modulate with clipping texture - - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0x00); - 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. - - // Set clipping texture - m_alphaClippingTexture->Set_U_Addr_Mode(TextureClass::TEXTURE_ADDRESS_CLAMP); - m_alphaClippingTexture->Set_V_Addr_Mode(TextureClass::TEXTURE_ADDRESS_CLAMP); - m_alphaClippingTexture->Set_Min_Filter(TextureClass::FILTER_TYPE_NONE); - m_alphaClippingTexture->Set_Mag_Filter(TextureClass::FILTER_TYPE_NONE); - m_alphaClippingTexture->Set_Mip_Mapping(TextureClass::FILTER_TYPE_NONE); - - DX8Wrapper::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 - - /*************************************************************************************/ - #endif - #if 0 // No longer do simple rendering. if (TheGlobalData->m_useWaterPlane) { @@ -1706,19 +1351,10 @@ 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 - #ifdef CLIP_GEOMETRY_TO_PLANE - //restore default culling mode - // DX8Wrapper::Set_DX8_Render_State(D3DRS_CLIPPLANEENABLE, 0 ); //turn off first clip plane - - //disable texture coordinate generation - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, false); //disable alpha testing - #endif - ShaderClass::Invert_Backface_Culling(false); //return culling back to normal ShaderClass::Invalidate(); //reset shading system so it forces full state set. @@ -1740,9 +1376,9 @@ 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(); + // Force render state apply so that the null texture releases the shroud reference count. + g_renderBackend->Apply_Render_State_Changes(); + g_renderBackend->Invalidate_Cached_Render_States(); if (m_waterTrackSystem) m_waterTrackSystem->flush(rinfo); @@ -1791,213 +1427,197 @@ Bool WaterRenderObjClass::getClippedWaterPlane(CameraClass *cam, AABoxClass *box return FALSE; //water plane is not visible } -//------------------------------------------------------------------------------------------------- -/** Draws the water surface using a custom D3D vertex/pixel shader and a - * reflection texture. Only tested to work on GeForce3. */ -//------------------------------------------------------------------------------------------------- -void WaterRenderObjClass::drawSea(RenderInfoClass & rinfo) +void WaterRenderObjClass::drawSeaBatch(RenderInfoClass & rinfo) { AABoxClass seaBox; if (!getClippedWaterPlane(&rinfo.Camera,&seaBox)) + { return; //the sea is not visible + } - D3DXMATRIX matProj, matView, matWW3D; - - //create a transform which will flip the y and z coordinates to fit our system - memset(&matWW3D,0,sizeof(D3DMATRIX)); - matWW3D._11=1.0f; - matWW3D._32=1.0f; - 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 - - - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices - - Vector3 camTran; - - rinfo.Camera.Get_Transform().Get_Translation(&camTran); - - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, matView); - DX8Wrapper::_Get_DX8_Transform(D3DTS_PROJECTION, matProj); - - //default setup from Kenny's demo - m_pDev->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - m_pDev->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); - m_pDev->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE); - m_pDev->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - m_pDev->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, 0 ); - - m_pDev->SetTextureStageState( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - m_pDev->SetTextureStageState( 1, D3DTSS_COLORARG2, D3DTA_CURRENT ); - m_pDev->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_MODULATE); - m_pDev->SetTextureStageState( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - m_pDev->SetTextureStageState( 1, D3DTSS_TEXCOORDINDEX, 1 ); - - m_pDev->SetTextureStageState( 2, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - m_pDev->SetTextureStageState( 2, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|2); - - m_pDev->SetTextureStageState( 3, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - m_pDev->SetTextureStageState( 3, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|3); - -// m_pDev->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); -// m_pDev->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); -// m_pDev->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_POINT ); - -// m_pDev->SetTextureStageState( 1, D3DTSS_MINFILTER, D3DTEXF_POINT ); -// m_pDev->SetTextureStageState( 1, D3DTSS_MAGFILTER, D3DTEXF_POINT ); -// m_pDev->SetTextureStageState( 1, D3DTSS_MIPFILTER, D3DTEXF_NONE ); - //end of default setup - - m_pDev->SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - m_pDev->SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); - m_pDev->SetRenderState( D3DRS_WRAP0, D3DWRAP_U | D3DWRAP_V); - - m_pDev->SetTextureStageState(1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - m_pDev->SetTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); - - m_pDev->SetTexture( 0, m_pBumpTexture[(Int)m_fBumpFrame]); -#ifdef MIPMAP_BUMP_TEXTURE - m_pDev->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_POINT ); - m_pDev->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); - m_pDev->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); -#endif - m_pDev->SetTextureStageState( 1, D3DTSS_BUMPENVMAT00, F2DW(m_fBumpScale) ); - m_pDev->SetTextureStageState( 1, D3DTSS_BUMPENVMAT01, F2DW(0.0f) ); - m_pDev->SetTextureStageState( 1, D3DTSS_BUMPENVMAT10, F2DW(0.0f) ); - m_pDev->SetTextureStageState( 1, D3DTSS_BUMPENVMAT11, F2DW(m_fBumpScale) ); - m_pDev->SetTextureStageState( 1, D3DTSS_BUMPENVLSCALE, F2DW(1.0f) ); - m_pDev->SetTextureStageState( 1, D3DTSS_BUMPENVLOFFSET, F2DW(0.0f) ); - - m_pDev->SetTextureStageState( 2, D3DTSS_COLOROP, D3DTOP_DISABLE ); - m_pDev->SetTextureStageState( 2, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - - m_pDev->SetRenderState(D3DRS_ZWRITEENABLE , FALSE); - - D3DXMATRIX mat; - memset(&mat,0,sizeof(D3DXMATRIX)); - - mat._11 = 0.5f; mat._12 = -0.5f; mat._13 = 0.5f; mat._14=0.5f; - mat._21 = 0.5f; mat._22 = 0.5f; mat._23 = 0.0f; mat._24=0.0f; - mat._31 = 0.0f; mat._32 = 0.0f; mat._33 = 0.0f; mat._34=1.0f; - mat._41 = 0.0f; mat._42 = 0.0f; mat._43 = 0.0f; mat._44=1.0f; - - m_pDev->SetVertexShaderConstant(CV_TEXPROJ_0, &mat, 4); - - // Setup constants - m_pDev->SetVertexShaderConstant(CV_ZERO, D3DXVECTOR4(0.0f, 0.0f, 0.0f, 0.0f), 1); - m_pDev->SetVertexShaderConstant(CV_ONE, D3DXVECTOR4(1.0f, 1.0f, 1.0f, 1.0f), 1); - - m_pDev->SetVertexShader(m_dwWaveVertexShader); - m_pDev->SetPixelShader(m_dwWavePixelShader); - -// Make reflection brighter to compensate for darker coloring on sea floor -// m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); -// m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR ); - - m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); - m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); - - m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE , TRUE); - m_pDev->SetTexture( 1, m_pReflectionTexture->Peek_D3D_Texture()); - -// m_pDev->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);//LORENZEN - - Int patchX,patchY,startX,startY; - - D3DXMATRIX patchMatrix; - memset(&patchMatrix,0,sizeof(D3DXMATRIX)); - patchMatrix._11=PATCH_SCALE; - patchMatrix._22=1.0f; - patchMatrix._33=PATCH_SCALE; - patchMatrix._44=1.0f; + std::vector patches; - m_pDev->SetStreamSource(0,m_vertexBufferD3D,sizeof(WaterRenderObjClass::SEA_PATCH_VERTEX)); - m_pDev->SetIndices(m_indexBufferD3D,0); + Int patchX; + Int patchY; + Real patchWorldWidth = PATCH_WIDTH * PATCH_SCALE; - for (startY=patchY=(seaBox.Center.Y-seaBox.Extent.Y)/(PATCH_WIDTH*PATCH_SCALE); (patchY*PATCH_WIDTH*PATCH_SCALE)<(seaBox.Center.Y+seaBox.Extent.Y); patchY++) + for (patchY=(Int)((seaBox.Center.Y-seaBox.Extent.Y)/patchWorldWidth); + (patchY*patchWorldWidth)<(seaBox.Center.Y+seaBox.Extent.Y); patchY++) { - for (startX=patchX=(seaBox.Center.X-seaBox.Extent.X)/(PATCH_WIDTH*PATCH_SCALE); (patchX*PATCH_WIDTH*PATCH_SCALE)<(seaBox.Center.X+seaBox.Extent.X); patchX++) + for (patchX=(Int)((seaBox.Center.X-seaBox.Extent.X)/patchWorldWidth); + (patchX*patchWorldWidth)<(seaBox.Center.X+seaBox.Extent.X); patchX++) { - D3DXMATRIX matWorldViewProj, matTemp, matTempWorld; - patchMatrix._41=(float)(patchX*PATCH_WIDTH*PATCH_SCALE ); - patchMatrix._43=(float)(patchY*PATCH_WIDTH*PATCH_SCALE ); - //convert the default D3D coordinate system into ours - D3DXMatrixMultiply(&matTempWorld, &patchMatrix, &matWW3D); - - D3DXMatrixMultiply(&matTemp, &matTempWorld, &matView); - D3DXMatrixMultiply(&matWorldViewProj, &matTemp, &matProj); - //matrices must be transposed before loading into vertex shader registers - D3DXMatrixTranspose(&matWorldViewProj, &matWorldViewProj); - m_pDev->SetVertexShaderConstant(CV_WORLDVIEWPROJ_0, &matWorldViewProj, 4); //pass transform matrix into shader - - m_pDev->DrawIndexedPrimitive(D3DPT_TRIANGLESTRIP,0,m_numVertices,0,m_numIndices); + SeaPatchBatchEntry entry; + entry.patchX = patchX; + entry.patchY = patchY; + patches.push_back(entry); } } -// 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 - - m_pDev->SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - m_pDev->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|0); - m_pDev->SetTextureStageState( 1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - m_pDev->SetTextureStageState( 1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU|1); - m_pDev->SetRenderState(D3DRS_ZWRITEENABLE , TRUE); - m_pDev->SetTextureStageState(1, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - m_pDev->SetTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); + if (patches.empty()) + { + return; + } - m_pDev->SetRenderState( D3DRS_WRAP0, 0); //turn off texture wrapping + const size_t maxBatchElements = 60000; + const size_t patchVertexCount = PATCH_SIZE * PATCH_SIZE; + const size_t patchRectangleCount = PATCH_WIDTH * PATCH_WIDTH; + const size_t patchIndexCount = patchRectangleCount * 6; + const Real inverseBumpSize = 1.0f / BUMP_SIZE; + size_t batchStart = 0; + while (batchStart < patches.size()) + { + size_t batchEnd = batchStart; + size_t totalVertices = 0; + size_t totalIndices = 0; + while (batchEnd < patches.size()) + { + if (batchEnd > batchStart + && (totalVertices + patchVertexCount > maxBatchElements + || totalIndices + patchIndexCount > maxBatchElements)) + { + break; + } + totalVertices += patchVertexCount; + totalIndices += patchIndexCount; + batchEnd++; + } - m_pDev->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_DISABLE ); - m_pDev->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - m_pDev->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); - m_pDev->SetTextureStageState( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - m_pDev->SetTextureStageState( 2, D3DTSS_COLOROP, D3DTOP_DISABLE ); - m_pDev->SetTextureStageState( 2, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); + UnsignedShort batchIndexCount = static_cast(totalIndices); + UnsignedShort batchVertexCount = static_cast(totalVertices); + UnsignedShort batchTriangleCount = static_cast(totalIndices / 3); - //Restore old transforms - DX8Wrapper::_Set_DX8_Transform(D3DTS_VIEW, matView); - DX8Wrapper::_Set_DX8_Transform(D3DTS_PROJECTION, matProj); + DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC,batchIndexCount); + { + DynamicIBAccessClass::WriteLockClass lockib(&ib_access); + UnsignedShort *curIb = lockib.Get_Index_Array(); + UnsignedShort vertexBase = 0; + for (size_t patchIndex = batchStart; patchIndex < batchEnd; ++patchIndex) + { + for (Int j=0; jSetPixelShader(0); //turn off pixel shader - m_pDev->SetVertexShader(DX8_FVF_XYZDUV1); //turn off custom vertex shader + curIb[3] = vertexBase + (j)*PATCH_SIZE + i; + curIb[4] = vertexBase + (j)*PATCH_SIZE + i+1; + curIb[5] = vertexBase + (j+1)*PATCH_SIZE + i+1; - DX8Wrapper::Invalidate_Cached_Render_States(); + curIb += 6; + } + } + vertexBase += static_cast(patchVertexCount); + } + } - if (TheTerrainRenderObject->getShroud()) - { - //do second pass to apply the shroud on water plane - W3DShaderManager::setTexture(0,TheTerrainRenderObject->getShroud()->getShroudTexture()); - W3DShaderManager::setShader(W3DShaderManager::ST_SHROUD_TEXTURE, 0); - m_pDev->SetStreamSource(0,m_vertexBufferD3D,sizeof(WaterRenderObjClass::SEA_PATCH_VERTEX)); - m_pDev->SetIndices(m_indexBufferD3D,0); - for (startY=patchY=(seaBox.Center.Y-seaBox.Extent.Y)/(PATCH_WIDTH*PATCH_SCALE); (patchY*PATCH_WIDTH*PATCH_SCALE)<(seaBox.Center.Y+seaBox.Extent.Y); patchY++) + DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,batchVertexCount); { - for (startX=patchX=(seaBox.Center.X-seaBox.Extent.X)/(PATCH_WIDTH*PATCH_SCALE); (patchX*PATCH_WIDTH*PATCH_SCALE)<(seaBox.Center.X+seaBox.Extent.X); patchX++) + DynamicVBAccessClass::WriteLockClass lock(&vb_access); + VertexFormatXYZNDUV2* vb=lock.Get_Formatted_Vertex_Array(); + for (size_t patchIndex = batchStart; patchIndex < batchEnd; ++patchIndex) { - D3DXMATRIX matTemp; - patchMatrix._41=(float)(patchX*PATCH_WIDTH*PATCH_SCALE); - patchMatrix._43=(float)(patchY*PATCH_WIDTH*PATCH_SCALE); + Real originX = patches[patchIndex].patchX * patchWorldWidth; + Real originY = patches[patchIndex].patchY * patchWorldWidth; - D3DXMatrixMultiply(&matTemp, &patchMatrix, &matWW3D); + for (Int j=0; jx=x; + vb->y=y; + vb->z=m_level; + UnsignedInt diffuse = m_settings[m_tod].transparentWaterDiffuse; + if (W3DWater_UseBackendWater()) + { + diffuse = W3DWater_ScaleDiffuseAlpha( + diffuse, + W3DWater_GetBgfxShoreAlpha(x, y, m_level, 4.0f, TRUE)); + } + vb->diffuse=diffuse; + vb->u1=(Real)i*PATCH_UV_SCALE + m_uOffset; + vb->v1=(Real)j*PATCH_UV_SCALE + m_vOffset; + vb->u2=x*inverseBumpSize; + vb->v2=(y+0.3f*x)*inverseBumpSize; + vb->nx=0.0f; + vb->ny=0.0f; + vb->nz=1.0f; + vb++; + } + } + } + } - m_pDev->DrawIndexedPrimitive(D3DPT_TRIANGLESTRIP,0,m_numVertices,0,m_numIndices); + Matrix3D tm(1); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,tm); + g_renderBackend->Set_Index_Buffer(ib_access,0); + g_renderBackend->Set_Vertex_Buffer(vb_access); + g_renderBackend->Set_Texture(0,m_settings[m_tod].waterTexture); + g_renderBackend->Set_Texture(1,nullptr); + g_renderBackend->Set_Texture(2,nullptr); + g_renderBackend->Set_Texture(3,nullptr); + g_renderBackend->Set_Material(m_vertexMaterialClass); + g_renderBackend->Set_Texture_Color_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Color_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Argument(0, 1, RB_TEXARG_TEXTURE); + g_renderBackend->Set_Texture_Alpha_Argument(0, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_DISABLE); + { + 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); + if (g_renderBackend->Get_Back_Buffer_Format() == WW3D_FORMAT_A8R8G8B8 + && TheGlobalData->m_showSoftWaterEdge + && TheWaterTransparency->m_transparentWaterDepth !=0 + && !g_renderBackend->Has_Shader_Pipeline()) + { + 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); } } - W3DShaderManager::resetShader(W3DShaderManager::ST_SHROUD_TEXTURE); - } + g_renderBackend->Set_Cull_Mode(RB_CULL_NONE); + g_renderBackend->Draw_Triangles(0,batchTriangleCount,0,batchVertexCount); -} + if (TheTerrainRenderObject->getShroud()) + { + W3DShaderManager::setTexture(0,TheTerrainRenderObject->getShroud()->getShroudTexture()); + W3DShaderManager::setShader(W3DShaderManager::ST_SHROUD_TEXTURE, 0); + g_renderBackend->Set_Cull_Mode(RB_CULL_NONE); + g_renderBackend->Set_Depth_Func(RB_CMP_LESS_EQUAL); + g_renderBackend->Draw_Triangles(0,batchTriangleCount,0,batchVertexCount); + g_renderBackend->Set_Depth_Func(RB_CMP_EQUAL); + W3DShaderManager::resetShader(W3DShaderManager::ST_SHROUD_TEXTURE); + } + if (!TheWaterTransparency->m_additiveBlend) + { + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); + } + else + { + g_renderBackend->Set_Blend_Factors(RB_BLEND_ONE, RB_BLEND_ONE); + } + g_renderBackend->Clear_State_Overrides(); + batchStart = batchEnd; + } +} #define FEATHER_LAYER_COUNT (5.0f) #define FEATHER_THICKNESS (4.0f) @@ -2007,6 +1627,8 @@ void WaterRenderObjClass::drawSea(RenderInfoClass & rinfo) //------------------------------------------------------------------------------------------------- void WaterRenderObjClass::renderWater() { + std::vector trapezoids; + for (PolygonTrigger *pTrig=PolygonTrigger::getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) { if (pTrig->isWaterArea()) { if (pTrig->getNumPoints()>2) { @@ -2033,20 +1655,31 @@ void WaterRenderObjClass::renderWater() { for (int r = 0; r < TheGlobalData->m_featherWater; ++r) { - drawTrapezoidWater(points); + WaterTrapezoidBatchEntry entry; + entry.points[0] = points[0]; + entry.points[1] = points[1]; + entry.points[2] = points[2]; + entry.points[3] = points[3]; + trapezoids.push_back(entry); points[0].Z += (FEATHER_THICKNESS/TheGlobalData->m_featherWater); } } else - drawTrapezoidWater(points); - - + { + WaterTrapezoidBatchEntry entry; + entry.points[0] = points[0]; + entry.points[1] = points[1]; + entry.points[2] = points[2]; + entry.points[3] = points[3]; + trapezoids.push_back(entry); + } } } } } + drawTrapezoidWaterBatch(trapezoids); } //------------------------------------------------------------------------------------------------- @@ -2077,7 +1710,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,12 +1718,12 @@ 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); + DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,4); { DynamicVBAccessClass::WriteLockClass lock(&vb_access); VertexFormatXYZNDUV2* verts=lock.Get_Formatted_Vertex_Array(); @@ -2126,14 +1759,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 +1806,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,16 +1818,16 @@ 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); + DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,4); { DynamicVBAccessClass::WriteLockClass lock(&vb_access); VertexFormatXYZNDUV2* verts=lock.Get_Formatted_Vertex_Array(); @@ -2230,10 +1863,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. @@ -2251,10 +1884,6 @@ void WaterRenderObjClass::renderWaterMesh() if (!m_doWaterGrid) return; //the water grid is disabled. - //According to Nvidia there's a D3D bug that happens if you don't start with a - //new dynamic VB each frame - so we force a DISCARD by overflowing the counter. - m_vertexBufferD3DOffset = 0xffff; - Setting *setting=&m_settings[m_tod]; WaterMeshData *pData; @@ -2302,18 +1931,14 @@ void WaterRenderObjClass::renderWaterMesh() PhasePerFrameY -= 0.1f; #endif - MaterMeshVertexFormat *vb; - if (m_vertexBufferD3DOffset < m_numVertices) - { //we have room in current VB, append new verts - if(m_vertexBufferD3D->Lock(m_vertexBufferD3DOffset*sizeof(MaterMeshVertexFormat),mx*my*sizeof(MaterMeshVertexFormat),(unsigned char**)&vb,D3DLOCK_NOOVERWRITE) != D3D_OK) - return; - } - else - { //ran out of room in last VB, request a substitute VB. - if(m_vertexBufferD3D->Lock(0,mx*my*sizeof(MaterMeshVertexFormat),(unsigned char**)&vb,D3DLOCK_DISCARD) != D3D_OK) + DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,(unsigned short)(mx*my)); + { + DynamicVBAccessClass::WriteLockClass lock(&vb_access); + VertexFormatXYZNDUV2 *vb=lock.Get_Formatted_Vertex_Array(); + if (vb == nullptr) + { return; - m_vertexBufferD3DOffset=0; //reset start of page to first vertex - } + } Int diffuse; diffuse = setting->waterDiffuse&0x00ffffff; Int alpha = (setting->waterDiffuse & 0xff000000)>>24; @@ -2346,8 +1971,12 @@ void WaterRenderObjClass::renderWaterMesh() Vector3::Cross_Product(nx,ny,&C); C.Normalize(); vb->nx = C.X; - vb->ny = C.X; - vb->nz = C.X; + vb->ny = C.Y; + vb->nz = C.Z; +#elif defined(GGC_RENDER_BACKEND_BGFX) + vb->nx = 0.0f; + vb->ny = 0.0f; + vb->nz = 1.0f; #endif Real x = (float)i*cellSizeX; vb->x= x; @@ -2378,10 +2007,10 @@ 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,33 +2019,24 @@ void WaterRenderObjClass::renderWaterMesh() m_shaderClass.Set_Cull_Mode(ShaderClass::CULL_MODE_ENABLE); //water should be visible from both sides - DX8Wrapper::Set_Shader(m_shaderClass); -#if 1 + g_renderBackend->Set_Shader(m_shaderClass); setupFlatWaterShader(); -#else - //DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); - DX8Wrapper::Set_Texture(0,setting->waterTexture); - DX8Wrapper::Set_Texture(1,setting->waterTexture); - - DX8Wrapper::Set_Light(0,*m_meshLight); - DX8Wrapper::Set_Light(1,nullptr); - DX8Wrapper::Set_Light(2,nullptr); - DX8Wrapper::Set_Light(3,nullptr); -/* - DX8Wrapper::Set_DX8_Render_State(D3DRS_AMBIENT,0); //turn off scene ambient - DX8Wrapper::Set_DX8_Render_State(D3DRS_SPECULARENABLE,TRUE); - DX8Wrapper::Set_DX8_Render_State(D3DRS_LOCALVIEWER,TRUE); -*/ - - DX8Wrapper::Apply_Render_State_Changes(); //force update of view and projection matrices -#endif - -// m_pDev->SetRenderState(D3DRS_ZFUNC,D3DCMP_ALWAYS); //used to display grid under map. + if (m_waterMeshIndexBuffer == nullptr) + { + return; + } - m_pDev->SetIndices(m_indexBufferD3D,m_vertexBufferD3DOffset); - m_pDev->SetStreamSource(0,m_vertexBufferD3D,sizeof(MaterMeshVertexFormat)); - m_pDev->SetVertexShader(WATER_MESH_FVF); + // TheSuperHackers @bugfix bobtista 17/07/2026 Mark the deforming grid mesh as a water + // draw like the trapezoid and river paths do. Retail applied WATER_MESH_OPACITY to this + // very material; on the shader pipeline the override doubles as the routing marker that + // sends the draw to the water view, so without it the grid rendered in the earlier + // engine view and the flat trapezoid layer and wakes composited over it. Set after the + // index-buffer check so the one-shot override is always consumed by the draw below. + g_renderBackend->Override_Alpha_Blend_Enable(true); + g_renderBackend->Override_Material_Opacity(WATER_MESH_OPACITY); + g_renderBackend->Set_Index_Buffer(m_waterMeshIndexBuffer,0); + g_renderBackend->Set_Vertex_Buffer(vb_access); if (TheTerrainRenderObject->getShroud() && !m_trapezoidWaterPixelShader) @@ -2426,31 +2046,28 @@ void WaterRenderObjClass::renderWaterMesh() W3DShaderManager::setShader(W3DShaderManager::ST_SHROUD_TEXTURE, 1); //modulate with shroud texture - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE ); //stage 1 texture - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_CURRENT ); //previous stage texture - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_MODULATE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); + g_renderBackend->Set_Texture_Color_Argument(1, 1, RB_TEXARG_TEXTURE); //stage 1 texture + g_renderBackend->Set_Texture_Color_Argument(1, 2, RB_TEXARG_CURRENT); //previous stage texture + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_MODULATE); //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); - m_pDev->DrawIndexedPrimitive(D3DPT_TRIANGLESTRIP,0,mx*my,0,m_numIndices-2); - DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ZFUNC, D3DCMP_EQUAL); + g_renderBackend->Set_Depth_Func(RB_CMP_LESS_EQUAL); + g_renderBackend->Draw_Strip(0,m_numIndices-2,0,mx*my); + g_renderBackend->Set_Depth_Func(RB_CMP_EQUAL); W3DShaderManager::resetShader(W3DShaderManager::ST_SHROUD_TEXTURE); } else - m_pDev->DrawIndexedPrimitive(D3DPT_TRIANGLESTRIP,0,mx*my,0,m_numIndices-2); + { + g_renderBackend->Draw_Strip(0,m_numIndices-2,0,mx*my); + } Debug_Statistics::Record_DX8_Polys_And_Vertices(m_numIndices-2,mx*my,ShaderClass::_PresetOpaqueShader); + if (m_trapezoidWaterPixelShader) g_renderBackend->Set_Pixel_Shader(0); -// m_pDev->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); - - if (m_trapezoidWaterPixelShader) DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(0); - - 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 +2334,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--; @@ -2729,7 +2346,7 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) m_drawingRiver = true; //allocate 2 triangles per side with 3 indices per triangle - DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC_DX8,(rectangleCount+1)*2*3); + DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC,(rectangleCount+1)*2*3); { DynamicIBAccessClass::WriteLockClass lockib(&ib_access); UnsignedShort *curIb = lockib.Get_Index_Array(); @@ -2826,7 +2443,7 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) #define HEIGHT_TO_USE (0.5f) if (innerNdx >= pTrig->getNumPoints()-1) return; //allocate 2 vertices per side - DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,(rectangleCount+1)*2); + DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,(rectangleCount+1)*2); { DynamicVBAccessClass::WriteLockClass lock(&vb_access); VertexFormatXYZNDUV2* vb=lock.Get_Formatted_Vertex_Array(); @@ -2900,93 +2517,105 @@ 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(); + { + 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(WATER_MESH_OPACITY); //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; - DX8Wrapper::_Get_D3D_Device8()->GetRenderState(D3DRS_CULLMODE, &cull); - DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + if (m_riverWaterPixelShader) g_renderBackend->Set_Pixel_Shader(m_riverWaterPixelShader); + CullMode cull = g_renderBackend->Get_Cull_Mode(); + g_renderBackend->Set_Cull_Mode(RB_CULL_NONE); if (wireframeForDebug) { - DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME); + g_renderBackend->Set_Fill_Mode(RB_FILL_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); + g_renderBackend->Set_Fill_Mode(RB_FILL_SOLID); } - if (m_riverWaterPixelShader) DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(0); + if (m_riverWaterPixelShader) g_renderBackend->Set_Pixel_Shader(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) - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_ONE ); + 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); + g_renderBackend->Set_Cull_Mode(cull); } 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) - { if (TheTerrainRenderObject->getShroud()) + { if (TheTerrainRenderObject->getShroud() && TheTerrainRenderObject->getShroud()->getShroudTexture()) { - W3DShaderManager::setTexture(0,TheTerrainRenderObject->getShroud()->getShroudTexture()); + TextureClass *shroudTexture = TheTerrainRenderObject->getShroud()->getShroudTexture(); + W3DShaderManager::setTexture(0, shroudTexture); //Use stage 3 to apply the shroud W3DShaderManager::setShader(W3DShaderManager::ST_SHROUD_TEXTURE, 3); + W3DWater_BindTexture(3, shroudTexture); //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); + g_renderBackend->Set_Depth_Func(RB_CMP_LESS_EQUAL); } else { //Assume no shroud, so stage 3 will be null texture but using actual white because //pixel shader on GF4 generates random colors with SetTexture(3,nullptr). if (!m_whiteTexture->Is_Initialized()) { m_whiteTexture->Init(); - SurfaceClass *surface=m_whiteTexture->Get_Surface_Level(); - int pitch; - void *pBits = surface->Lock(&pitch); - const unsigned int bytesPerPixel = surface->Get_Bytes_Per_Pixel(); - surface->Draw_Pixel(0, 0, 0xffffffff, bytesPerPixel, pBits, pitch); - surface->Unlock(); - REF_PTR_RELEASE(surface); + W3DWater_FillWhiteTexture(m_whiteTexture); } - DX8Wrapper::_Get_D3D_Device8()->SetTexture(3,m_whiteTexture->Peek_D3D_Texture()); + W3DWater_BindTexture(3, m_whiteTexture); } } - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_ADD ); - DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_TEXCOORDINDEX, 0); - DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_TEXCOORDINDEX, 0); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_ADD); + g_renderBackend->Set_Texture_Coord_Source(0, RB_TEXCOORD_MESH_UV, 0); + W3DWater_SetStageAddress2D(0, RB_TEXTURE_ADDRESS_WRAP); + g_renderBackend->Set_Texture_Coord_Source(1, RB_TEXCOORD_MESH_UV, 0); Bool doSparkles = true; @@ -2995,51 +2624,36 @@ 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); + W3DWater_SetStageAddress2D(1, RB_TEXTURE_ADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION); // Two output coordinates are used. - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - DX8Wrapper::Set_DX8_Texture_Stage_State(2, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); - - D3DXMATRIX curView; - DX8Wrapper::_Get_DX8_Transform(D3DTS_VIEW, curView); - D3DXMATRIX inv; - float det; - D3DXMatrixInverse(&inv, &det, &curView); - D3DXMATRIX scale; - D3DXMatrixScaling(&scale, NOISE_REPEAT_FACTOR, NOISE_REPEAT_FACTOR,1); - D3DXMATRIX destMatrix = inv * scale; - D3DXMatrixTranslation(&scale, m_riverVOrigin, m_riverVOrigin,0); - destMatrix = destMatrix*scale; - DX8Wrapper::_Set_DX8_Transform(D3DTS_TEXTURE2, destMatrix); - - } - m_pDev->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); - m_pDev->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); - m_pDev->SetTextureStageState( 1, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); - m_pDev->SetTextureStageState( 1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); - m_pDev->SetTextureStageState( 2, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); - m_pDev->SetTextureStageState( 2, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); + W3DWater_SetCameraSpaceTexcoord2(2, 0); + W3DWater_SetStageAddress2D(2, RB_TEXTURE_ADDRESS_WRAP); + + W3DWater_SetNoiseTextureTransform(2, NOISE_REPEAT_FACTOR, m_riverVOrigin); + + } + W3DWater_SetStageMinMagFilter(0, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); + W3DWater_SetStageMinMagFilter(1, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); + W3DWater_SetStageMinMagFilter(2, RB_TEXTURE_SAMPLE_LINEAR, RB_TEXTURE_SAMPLE_LINEAR); if (m_trapezoidWaterPixelShader){ - DX8Wrapper::_Get_D3D_Device8()->SetPixelShaderConstant(0, D3DXVECTOR4(REFLECTION_FACTOR, REFLECTION_FACTOR, REFLECTION_FACTOR, 1.0f), 1); - DX8Wrapper::_Get_D3D_Device8()->SetPixelShader(m_trapezoidWaterPixelShader); + const float reflectionFactor[4] = { REFLECTION_FACTOR, REFLECTION_FACTOR, REFLECTION_FACTOR, 1.0f }; + g_renderBackend->Set_Pixel_Shader_Constant(0, &reflectionFactor, 1); + g_renderBackend->Set_Pixel_Shader(m_trapezoidWaterPixelShader); } } //------------------------------------------------------------------------------------------------- //Draw a 4 sided flat water area. //------------------------------------------------------------------------------------------------- -void WaterRenderObjClass::drawTrapezoidWater(Vector3 points[4]) +static void GetWaterTrapezoidCounts(Vector3 points[4], Int &uCount, Int &vCount, Int &rectangleCount) { Vector3 origin(points[0]); Vector3 uVec1(points[1]); @@ -3050,43 +2664,36 @@ void WaterRenderObjClass::drawTrapezoidWater(Vector3 points[4]) vVec2 -= uVec1; uVec1 -= origin; vVec1 -= origin; - Int uCount = (uVec1.Length()+uVec2.Length()) / (8*MAP_XY_FACTOR); - if (uCount<1) uCount = 1; - Int vCount = (vVec1.Length()+vVec2.Length()) / (8*MAP_XY_FACTOR); - if (vCount<1) vCount = 1; - - if (uCount>50) uCount = 50; - if (vCount>50) vCount = 50; - - static Bool doWobble = true; + uCount = (uVec1.Length()+uVec2.Length()) / (8*MAP_XY_FACTOR); + if (uCount<1) + { + uCount = 1; + } + vCount = (vVec1.Length()+vVec2.Length()) / (8*MAP_XY_FACTOR); + if (vCount<1) + { + vCount = 1; + } - Int rectangleCount = uCount*vCount; + if (uCount>50) + { + uCount = 50; + } + if (vCount>50) + { + vCount = 50; + } + rectangleCount = uCount*vCount; uCount++; vCount++; +} - Int i, j; - //allocate 2 triangles per side with 3 indices per triangle - DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC_DX8,(rectangleCount+1)*2*3); +void WaterRenderObjClass::drawTrapezoidWaterBatch(const std::vector &trapezoids) +{ + if (trapezoids.empty()) { - DynamicIBAccessClass::WriteLockClass lockib(&ib_access); - UnsignedShort *curIb = lockib.Get_Index_Array(); - for (j=0; jm_numGlobalLights; lightIndex++) { if (-TheGlobalData->m_terrainLightPos[lightIndex].z > 0) - { shadeR += -TheGlobalData->m_terrainLightPos[lightIndex].z * TheGlobalData->m_terrainDiffuse[lightIndex].red; + { + shadeR += -TheGlobalData->m_terrainLightPos[lightIndex].z * TheGlobalData->m_terrainDiffuse[lightIndex].red; shadeG += -TheGlobalData->m_terrainLightPos[lightIndex].z * TheGlobalData->m_terrainDiffuse[lightIndex].green; shadeB += -TheGlobalData->m_terrainLightPos[lightIndex].z * TheGlobalData->m_terrainDiffuse[lightIndex].blue; } @@ -3127,7 +2735,8 @@ void WaterRenderObjClass::drawTrapezoidWater(Vector3 points[4]) shadeB=shadeB*255.0f; if (shadeR == 0 && shadeG == 0 && shadeB == 0) - { //special case where we disable lighting + { + //special case where we disable lighting shadeR=255; shadeG=255; shadeB=255; @@ -3139,219 +2748,312 @@ void WaterRenderObjClass::drawTrapezoidWater(Vector3 points[4]) //Keep diffuse from lighting calculations but substitute custom alpha diffuse |= m_settings[m_tod].waterDiffuse & 0xff000000; //copy alpha/opacity from ini setting - DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,(rectangleCount+1)*2); - -//#define WAVY_WATER -//#define FEATHER_LAYER_COUNT (3) //LORENZEN -//#define FEATHER_LAYER_THICKNESS (2.5f) -//#define FEATHER_WATER - -//#ifdef WAVY_WATER // the NEW WATER a'la LORENZEN - if ( TheGlobalData->m_featherWater ) + const size_t maxBatchElements = 60000; + size_t batchStart = 0; + while (batchStart < trapezoids.size()) { - - DynamicVBAccessClass::WriteLockClass lock(&vb_access); - VertexFormatXYZNDUV2* vb=lock.Get_Formatted_Vertex_Array(); - - Real phase = 0; - Real mapCoeff = PI/(4*MAP_XY_FACTOR); - Real wave = 0; - Real amplitude = 0.5f; - - //The first (high order) byte is the Alpha value for this patch - // It needs to be set proportional to the number of feather layers - // this comes from TheGlobalData->m_featherWater, which is a count of layers - - - Int Alpha = 0; - if ( TheGlobalData->m_featherWater == 5) Alpha = 80; - if ( TheGlobalData->m_featherWater == 4) Alpha = 110; - if ( TheGlobalData->m_featherWater == 3) Alpha = 140; - if ( TheGlobalData->m_featherWater == 2) Alpha = 200; - if ( TheGlobalData->m_featherWater == 1) Alpha = 255; - - //Keep diffuse from lighting calculations but substitute custom alpha - Int customDiffuse = (diffuse & 0x00ffffff) | (Alpha<< 24);//(0x80 << 16)|(0x90 << 8)|0xa0; - - for (j=0; j batchStart + && (totalVertices + patchVertices > maxBatchElements + || totalIndices + patchIndices > maxBatchElements)) { - Real du = i; - du /= (uCount-1); - Vector3 vertex = origin; - vertex += uVec1*du; - vertex += vVec1*dv; - vertex += (dv)*(du)*(vVec2-vVec1); - - vb->x=vertex.X; - vb->y=vertex.Y; - - // common to all the waving effects - phase = 25 * m_riverVOrigin + vertex.X * mapCoeff; - wave = (sin(phase) - 1.0f) * amplitude; - - vb->z = (vertex.Z + wave); - vb->diffuse = customDiffuse; - vb->u1 = (vertex.X/waterFactor) + 0.02*cos(11*m_riverVOrigin)*wave; - vb->v1 = (vertex.Y/waterFactor) + 0.02*cos(5*m_riverVOrigin)*wave; - vb->u2 = vertex.X/BUMP_SIZE; - vb->v2 = vertex.Y/BUMP_SIZE + 0.3f*vertex.X/BUMP_SIZE; - vb->nx = 0; - vb->ny = 0; - vb->nz = 1.0f; - vb++; + break; } + totalVertices += patchVertices; + totalIndices += patchIndices; + totalRectangleCount += rectangleCount; + batchEnd++; } - } -//#else // STILL THE OLD FLAT WATER - else - { - DynamicVBAccessClass::WriteLockClass lock(&vb_access); - VertexFormatXYZNDUV2* vb=lock.Get_Formatted_Vertex_Array(); - - //Pulling some constants out of the inner loops to improve performance -MW - Real constA=0.02*cos(11*m_riverVOrigin); - Real constB=0.02*cos(5*m_riverVOrigin); - Real constC=25*m_riverVOrigin; - Real ooWaterFactor = 1.0f/waterFactor; - const Real constD=PI/(4*MAP_XY_FACTOR); - Real constE=1.0f/(Real)(vCount-1); - Real constF=1.0f/(Real)(uCount-1); + UnsignedShort batchIndexCount = static_cast(totalIndices); + UnsignedShort batchVertexCount = static_cast(totalVertices); - for (j=0; jx=vertex.X; - vb->y=vertex.Y; - vb->z=vertex.Z; - - vb->diffuse= diffuse; - //Old slower version - //vb->u1=(vertex.X/waterFactor) + 0.02*cos(11*m_riverVOrigin)*sin(25*m_riverVOrigin+vertex.X*PI/(4*MAP_XY_FACTOR)); - //vb->v1=(vertex.Y/waterFactor) + 0.02*cos(5*m_riverVOrigin)*sin(25*m_riverVOrigin+vertex.Y*PI/(4*MAP_XY_FACTOR)); - vb->u1=vertex.X*ooWaterFactor + constA*WWMath::Fast_Sin(constC+vertex.X*constD); - vb->v1=vertex.Y*ooWaterFactor + constB*WWMath::Fast_Sin(constC+vertex.Y*constD); - vb->u2 = vertex.X/BUMP_SIZE; - //Old slower version - //vb->v2 = vertex.Y/BUMP_SIZE + 0.3f*vertex.X/BUMP_SIZE; - vb->v2 = (vertex.Y+0.3f*vertex.X)/BUMP_SIZE; - vb->nx = 0; - vb->ny = 0; - vb->nz = 1.0f; - vb++; - } - } - } - -//#endif // OLD VS NEW WATER - - - - 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); - - setupFlatWaterShader();// lorenzen sez use the alpha shader - - //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 ); - } + Vector3 points[4]; + points[0] = trapezoids[patchIndex].points[0]; + points[1] = trapezoids[patchIndex].points[1]; + points[2] = trapezoids[patchIndex].points[2]; + points[3] = trapezoids[patchIndex].points[3]; + Int uCount; + Int vCount; + Int rectangleCount; + GetWaterTrapezoidCounts(points, uCount, vCount, rectangleCount); + for (Int j=0; jGetRenderState(D3DRS_CULLMODE, &cull); - DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + curIb += 6; //skip the 6 indices we just added. + } + } + vertexBase += static_cast(uCount * vCount); + } + } + DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,batchVertexCount); + { + DynamicVBAccessClass::WriteLockClass lock(&vb_access); + VertexFormatXYZNDUV2* vb=lock.Get_Formatted_Vertex_Array(); + for (size_t patchIndex = batchStart; patchIndex < batchEnd; ++patchIndex) + { + Vector3 points[4]; + points[0] = trapezoids[patchIndex].points[0]; + points[1] = trapezoids[patchIndex].points[1]; + points[2] = trapezoids[patchIndex].points[2]; + points[3] = trapezoids[patchIndex].points[3]; + Int uCount; + Int vCount; + Int rectangleCount; + GetWaterTrapezoidCounts(points, uCount, vCount, rectangleCount); + Vector3 origin(points[0]); + Vector3 uVec1(points[1]); + Vector3 vVec1(points[3]); + Vector3 uVec2(points[2]); + Vector3 vVec2(points[2]); + uVec2 -= vVec1; + vVec2 -= uVec1; + uVec1 -= origin; + vVec1 -= origin; + + if ( TheGlobalData->m_featherWater ) + { + Real phase = 0; + Real mapCoeff = PI/(4*MAP_XY_FACTOR); + Real wave = 0; + Real amplitude = 0.5f; + Int Alpha = 0; + if ( TheGlobalData->m_featherWater == 5) + { + Alpha = 80; + } + if ( TheGlobalData->m_featherWater == 4) + { + Alpha = 110; + } + if ( TheGlobalData->m_featherWater == 3) + { + Alpha = 140; + } + if ( TheGlobalData->m_featherWater == 2) + { + Alpha = 200; + } + if ( TheGlobalData->m_featherWater == 1) + { + Alpha = 255; + } -//#ifdef FEATHER_WATER // the NEW WATER a'la LORENZEN + //Keep diffuse from lighting calculations but substitute custom alpha + Int customDiffuse = (diffuse & 0x00ffffff) | (Alpha<< 24);//(0x80 << 16)|(0x90 << 8)|0xa0; -// 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 + for (Int j=0; jx=vertex.X; + vb->y=vertex.Y; + + // common to all the waving effects + phase = 25 * m_riverVOrigin + vertex.X * mapCoeff; + wave = (sin(phase) - 1.0f) * amplitude; + + vb->z = (vertex.Z + wave); + UnsignedInt vertexDiffuse = customDiffuse; + if (W3DWater_UseBackendWater()) + { + vertexDiffuse = W3DWater_ScaleDiffuseAlpha( + vertexDiffuse, + W3DWater_GetBgfxShoreAlpha(vertex.X, vertex.Y, vertex.Z, 0.75f, FALSE)); + } + vb->diffuse = vertexDiffuse; + vb->u1 = (vertex.X/waterFactor) + 0.02*cos(11*m_riverVOrigin)*wave; + vb->v1 = (vertex.Y/waterFactor) + 0.02*cos(5*m_riverVOrigin)*wave; + vb->u2 = vertex.X/BUMP_SIZE; + vb->v2 = vertex.Y/BUMP_SIZE + 0.3f*vertex.X/BUMP_SIZE; + vb->nx = 0; + vb->ny = 0; + vb->nz = 1.0f; + vb++; + } + } + } + else + { + //Pulling some constants out of the inner loops to improve performance -MW + Real constA=0.02*cos(11*m_riverVOrigin); + Real constB=0.02*cos(5*m_riverVOrigin); + Real constC=25*m_riverVOrigin; + Real ooWaterFactor = 1.0f/waterFactor; + const Real constD=PI/(4*MAP_XY_FACTOR); + Real constE=1.0f/(Real)(vCount-1); + Real constF=1.0f/(Real)(uCount-1); + + for (Int j=0; jz *= FEATHER_LAYER_THICKNESS; -// ++vertBuf; -// } -//#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 - } + for (Int i=0; ix=vertex.X; + vb->y=vertex.Y; + vb->z=vertex.Z; + + UnsignedInt vertexDiffuse = diffuse; + if (W3DWater_UseBackendWater()) + { + vertexDiffuse = W3DWater_ScaleDiffuseAlpha( + vertexDiffuse, + W3DWater_GetBgfxShoreAlpha(vertex.X, vertex.Y, vertex.Z, 0.75f, FALSE)); + } + vb->diffuse= vertexDiffuse; + vb->u1=vertex.X*ooWaterFactor + constA*WWMath::Fast_Sin(constC+vertex.X*constD); + vb->v1=vertex.Y*ooWaterFactor + constB*WWMath::Fast_Sin(constC+vertex.Y*constD); + vb->u2 = vertex.X/BUMP_SIZE; + vb->v2 = (vertex.Y+0.3f*vertex.X)/BUMP_SIZE; + vb->nx = 0; + vb->ny = 0; + vb->nz = 1.0f; + vb++; + } + } + } + } + } + Matrix3D tm(1); + 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(); + { + 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); + } + if (m_trapezoidWaterPixelShader) + { + g_renderBackend->Set_Pixel_Shader(m_trapezoidWaterPixelShader); + } + g_renderBackend->Override_Alpha_Blend_Enable(true); + g_renderBackend->Override_Material_Opacity(WATER_MESH_OPACITY); + + // TheSuperHackers @bugfix bobtista 22/06/2026 The shoreline pass authors the + // back-buffer alpha gradient on the dx8 reference (renderShoreLines), so the + // soft-water DESTALPHA edge reads a real gradient there just like the original. + // Keep bgfx on source-alpha water: the dest-alpha mask is heightmap-only and + // treats mesh rocks as deep water, which paints opaque blue collars around them. + if (g_renderBackend->Get_Back_Buffer_Format() == WW3D_FORMAT_A8R8G8B8 + && TheGlobalData->m_showSoftWaterEdge + && TheWaterTransparency->m_transparentWaterDepth !=0 + && !g_renderBackend->Has_Shader_Pipeline()) + { + 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); + } + } - 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); - m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE , true); - DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); - } + CullMode cull = g_renderBackend->Get_Cull_Mode(); + g_renderBackend->Set_Cull_Mode(RB_CULL_NONE); - 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 ); - } - else - { - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_ONE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_ONE ); - } + g_renderBackend->Draw_Triangles( 0,totalRectangleCount*2, 0, batchVertexCount); - if (TheTerrainRenderObject->getShroud()) - { 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 - DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ZFUNC, D3DCMP_EQUAL); + { + g_renderBackend->Set_Pixel_Shader(0); + } + //Restore alpha blend to default values since we may have changed them to feather edges. + if (!TheWaterTransparency->m_additiveBlend) + { + g_renderBackend->Set_Blend_Factors(RB_BLEND_SRC_ALPHA, RB_BLEND_INV_SRC_ALPHA); } else - { //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); - //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); - DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_ZFUNC, D3DCMP_EQUAL); - W3DShaderManager::resetShader(W3DShaderManager::ST_SHROUD_TEXTURE); + { + g_renderBackend->Set_Blend_Factors(RB_BLEND_ONE, RB_BLEND_ONE); + } + + if (TheTerrainRenderObject->getShroud()) + { + if (m_trapezoidWaterPixelShader) + { + W3DShaderManager::resetShader(W3DShaderManager::ST_SHROUD_TEXTURE); + W3DWater_BindTexture(3, nullptr); + g_renderBackend->Set_Depth_Func(RB_CMP_EQUAL); + } + else + { + W3DShaderManager::setTexture(0,TheTerrainRenderObject->getShroud()->getShroudTexture()); + W3DShaderManager::setShader(W3DShaderManager::ST_SHROUD_TEXTURE, 0); + g_renderBackend->Set_Cull_Mode(RB_CULL_NONE); + g_renderBackend->Set_Depth_Func(RB_CMP_LESS_EQUAL); + g_renderBackend->Draw_Triangles( 0,totalRectangleCount*2, 0, batchVertexCount); + g_renderBackend->Set_Depth_Func(RB_CMP_EQUAL); + W3DShaderManager::resetShader(W3DShaderManager::ST_SHROUD_TEXTURE); + } } + g_renderBackend->Set_Cull_Mode(cull); + + batchStart = batchEnd; } - DX8Wrapper::_Get_D3D_Device8()->SetRenderState(D3DRS_CULLMODE, cull); } + //------------------------------------------------------------------------------------------------- //debug version where moon rotates with the camera (always upright on screen) //------------------------------------------------------------------------------------------------- @@ -3375,15 +3077,15 @@ 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); + DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC,4); { DynamicVBAccessClass::WriteLockClass lock(&vb_access); VertexFormatXYZNDUV2* verts=lock.Get_Formatted_Vertex_Array(); @@ -3419,15 +3121,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 @@ -3501,5 +3203,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..3444eeda96d 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp @@ -51,6 +51,7 @@ #include "GameClient/Water.h" #include "GameLogic/TerrainLogic.h" #include "Common/FramePacer.h" +#include "Common/GameState.h" #include "Common/GlobalData.h" #include "Common/UnicodeString.h" #include "Common/file.h" @@ -61,7 +62,12 @@ #include "WW3D2/rinfo.h" #include "WW3D2/camera.h" #include "WW3D2/assetmgr.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/dx8fvf.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/indexbuffer.h" +#include "WW3D2/renderbufferclasses.h" +#include "WW3D2/vertexbuffer.h" //number of vertex pages allocated - allows double buffering of vertex updates. //while one is being rendered, another is being updated. Improves HW parallelism. @@ -131,9 +137,30 @@ WaterTracksObj::~WaterTracksObj() //============================================================================= WaterTracksObj::WaterTracksObj() { + m_type=WaveTypePond; + m_x=0; + m_y=0; m_stageZeroTexture=nullptr; m_bound=false; m_initTimeOffset=0; + m_elapsedMs=0; + m_flipU=0; + m_nextSystem=nullptr; + m_prevSystem=nullptr; + m_waveDistance=0; + m_initialVelocity=0; + m_totalMs=0; + m_fadeMs=0; + m_waveInitialWidth=0; + m_waveInitialHeight=0; + m_waveFinalWidth=0; + m_waveFinalHeight=0; + m_timeToReachBeach=0; + m_frontSlowDownAcc=0; + m_timeToStop=0; + m_timeToRetreat=0; + m_backSlowDownAcc=0; + m_timeToCompress=0; } //============================================================================= @@ -201,30 +228,35 @@ void WaterTracksObj::init( Real width, Real length, const Vector2 &start, const //m_startPos -= m_waveDir*m_width; //move back initial tip off of wave a couple units off the final position //to give it some room to travel. Travel vector is stored in m_waveDir. - m_waveDistance = waveTypeInfo[m_type].m_waveDistance; //total distance traveled by wave front + if (m_type < WaveTypeStationary) { + m_waveDistance = waveTypeInfo[m_type].m_waveDistance; + } else { + m_waveDistance = 0; + } m_waveDir *= m_waveDistance; m_startPos -= m_waveDir; //move start point down away from shoreline - m_initialVelocity=waveTypeInfo[m_type].m_initialVelocity; //velocity per ms - m_totalMs = m_waveDistance/m_initialVelocity; //amount of time for wave to travel complete distance - - m_fadeMs = waveTypeInfo[m_type].m_fadeMs; //time for wave to fade out after it stops on beach - - m_waveInitialWidth=length * waveTypeInfo[m_type].m_initialWidthFraction;///getLogicTimeStepMilliseconds(); @@ -305,18 +337,24 @@ Int WaterTracksObj::render(DX8VertexBufferClass *vertexBuffer, Int batchStart) Real waveAlpha; Real widthFrac; Real heightFrac; + unsigned lockFlags=RB_LOCK_NOOVERWRITE; if (batchStart < (WATER_VB_PAGES*WATER_STRIP_X*WATER_STRIP_Y-m_x*m_y)) { //we have room in current VB, append new verts - if(vertexBuffer->Get_DX8_Vertex_Buffer()->Lock(batchStart*vertexBuffer->FVF_Info().Get_FVF_Size(),m_x*m_y*vertexBuffer->FVF_Info().Get_FVF_Size(),(unsigned char**)&vb,D3DLOCK_NOOVERWRITE) != D3D_OK) - return batchStart; } else { //ran out of room in last VB, request a substitute VB. - if(vertexBuffer->Get_DX8_Vertex_Buffer()->Lock(0,m_x*m_y*vertexBuffer->FVF_Info().Get_FVF_Size(),(unsigned char**)&vb,D3DLOCK_DISCARD) != D3D_OK) - return batchStart; batchStart=0; //reset start of page to first vertex + lockFlags=RB_LOCK_DISCARD; } + VertexBufferClass::AppendLockClass vertexLock( + vertexBuffer, + batchStart, + m_x*m_y, + lockFlags); + vb=static_cast(vertexLock.Get_Vertex_Array()); + if (vb == nullptr) + return batchStart; //Adjust wave position in a non-linear way so that it slows down as it hits the target. Using 1/4 sine wave //seems to work okay since it maxes out at 1.0 at our final position. @@ -469,14 +507,19 @@ Int WaterTracksObj::render(DX8VertexBufferClass *vertexBuffer, Int batchStart) vb->v1=1.0f; vb++; - vertexBuffer->Get_DX8_Vertex_Buffer()->Unlock(); - - Int idxCount=(m_y-1)*(m_x*2+2) - 2; //index count + return batchStart+m_x*m_y; //return new offset into unused area of vertex buffer +} - 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. +void WaterTracksRenderSystem::drawBatch(Int firstVertex, Int trackCount, TextureClass *texture) +{ + if (trackCount <= 0) + { + return; + } - return batchStart+m_x*m_y; //return new offset into unused area of vertex buffer + g_renderBackend->Set_Texture(0,texture); + g_renderBackend->Set_Index_Buffer(m_batchIndexBuffer,firstVertex); + g_renderBackend->Draw_Triangles(0,trackCount*2,0,trackCount*WATER_STRIP_X*WATER_STRIP_Y); } //============================================================================= @@ -602,6 +645,7 @@ WaterTracksRenderSystem::WaterTracksRenderSystem() m_usedModules = nullptr; m_freeModules = nullptr; m_indexBuffer = nullptr; + m_batchIndexBuffer = nullptr; m_vertexMaterialClass = nullptr; m_vertexBuffer = nullptr; m_stripSizeX=WATER_STRIP_X; @@ -637,6 +681,7 @@ void WaterTracksRenderSystem::ReAcquireResources() // just for paranoia's sake. REF_PTR_RELEASE(m_indexBuffer); + REF_PTR_RELEASE(m_batchIndexBuffer); REF_PTR_RELEASE(m_vertexBuffer); //Will need m_y-1 strips, each of length m_x*2. @@ -645,11 +690,11 @@ void WaterTracksRenderSystem::ReAcquireResources() Int idxCount=(m_stripSizeY-1)*(m_stripSizeX*2+2) - 2; - m_indexBuffer=NEW_REF(DX8IndexBufferClass,(idxCount)); + m_indexBuffer=NEW_REF(RenderIndexBufferClass,(idxCount)); // Fill up the IB { - DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); UnsignedShort *ib=lockIdxBuffer.Get_Index_Array(); for (i=0,j=0,k=0; i(i*WATER_STRIP_X*WATER_STRIP_Y); + ib[0] = vertexBase+2; + ib[1] = vertexBase; + ib[2] = vertexBase+3; + ib[3] = vertexBase; + ib[4] = vertexBase+1; + ib[5] = vertexBase+3; + ib += 6; + } + } + + m_vertexBuffer=NEW_REF(RenderVertexBufferClass,(DX8_FVF_XYZDUV1,m_stripSizeX*m_stripSizeY*WATER_VB_PAGES,RenderVertexBufferClass::USAGE_DYNAMIC)); m_batchStart=0; } @@ -683,6 +746,7 @@ void WaterTracksRenderSystem::ReAcquireResources() void WaterTracksRenderSystem::ReleaseResources() { REF_PTR_RELEASE(m_indexBuffer); + REF_PTR_RELEASE(m_batchIndexBuffer); REF_PTR_RELEASE(m_vertexBuffer); // Note - it is ok to not release the material, as it is a w3d object that // has no dx8 resources. jba. @@ -803,6 +867,7 @@ void WaterTracksRenderSystem::shutdown() } + REF_PTR_RELEASE(m_batchIndexBuffer); REF_PTR_RELEASE(m_indexBuffer); REF_PTR_RELEASE(m_vertexMaterialClass); REF_PTR_RELEASE(m_vertexBuffer); @@ -886,15 +951,20 @@ 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); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZBIAS,8); + g_renderBackend->Set_Vertex_Buffer(m_vertexBuffer); + g_renderBackend->Set_Z_Bias(8); //Force apply of render states so we can override them. - DX8Wrapper::Apply_Render_State_Changes(); + g_renderBackend->Apply_Render_State_Changes(); + // TheSuperHackers @bugfix bobtista 28/04/2026 Water-track wave quads + // are submitted after the water surface in legacy draw order. bgfx + // executes views by id, so keep them in the water view instead of the + // earlier opaque world view where the water pass would cover them. + g_renderBackend->Begin_Water_Overlay(); if (TheTerrainRenderObject->getShroud()) { @@ -902,37 +972,68 @@ Try improving the fit to vertical surfaces like cliffs. W3DShaderManager::setShader(W3DShaderManager::ST_SHROUD_TEXTURE, 1); //modulate with shroud texture - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE ); //stage 1 texture - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_CURRENT ); //previous stage texture - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_MODULATE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); + g_renderBackend->Set_Texture_Color_Argument(1, 1, RB_TEXARG_TEXTURE); //stage 1 texture + g_renderBackend->Set_Texture_Color_Argument(1, 2, RB_TEXARG_CURRENT); //previous stage texture + g_renderBackend->Set_Texture_Color_Operation(1, RB_TEXOP_MODULATE); + g_renderBackend->Set_Texture_Alpha_Operation(1, RB_TEXOP_MODULATE); //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; - WaterTracksObj *mod=m_usedModules; + TextureClass *batchTexture=nullptr; + Int batchTextureType=-1; + Int batchFirstVertex=0; + Int batchTrackCount=0; + const Int maxBatchVertices = WATER_VB_PAGES*WATER_STRIP_X*WATER_STRIP_Y; while( mod ) { - if (LastTextureType != mod->m_type) - DX8Wrapper::Set_Texture(0,mod->m_stageZeroTexture); + Bool textureChanged = batchTrackCount > 0 && batchTextureType != mod->m_type; + Bool pageFull = m_batchStart >= maxBatchVertices - mod->m_x*mod->m_y; + if (textureChanged || pageFull) + { + drawBatch(batchFirstVertex,batchTrackCount,batchTexture); + batchTexture=nullptr; + batchTextureType=-1; + batchTrackCount=0; + if (pageFull) + { + m_batchStart = 0xffff; + } + } + + Int trackVertexStart = m_batchStart; + if (trackVertexStart >= maxBatchVertices - mod->m_x*mod->m_y) + { + trackVertexStart = 0; + } Int vertsRendered=mod->render(m_vertexBuffer,m_batchStart); + if (batchTrackCount == 0) + { + batchTexture=mod->m_stageZeroTexture; + batchTextureType=mod->m_type; + batchFirstVertex=trackVertexStart; + } + batchTrackCount++; + m_batchStart = vertsRendered; //advance past vertices already in buffer mod = mod->m_nextSystem; } - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZBIAS,0); + drawBatch(batchFirstVertex,batchTrackCount,batchTexture); + + g_renderBackend->Set_Z_Bias(0); + g_renderBackend->End_Water_Overlay(); 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); } } @@ -996,6 +1097,28 @@ void WaterTracksRenderSystem::loadTracks() fileName.concat(".wak"); File *file = TheFileSystem->openFile(fileName.str(), File::READ | File::BINARY); + + // TheSuperHackers @bugfix bobtista 26/05/2026 On save game load, the + // terrain source filename has been rewritten to Save\.map because + // the save extracted the embedded .map into the save directory. The + // companion .wak is never extracted, so the lookup above misses the surf + // data that lives at the pristine .big-archive path. Fall back to the + // pristine map name (Maps\\.wak) when the save-relative open + // fails. + if (file == nullptr && TheGameState != nullptr) + { + AsciiString pristineName = TheGameState->getPristineMapName(); + if (!pristineName.isEmpty()) + { + FileSystem::removeExtension(pristineName); + pristineName.concat(".wak"); + if (pristineName != fileName) + { + file = TheFileSystem->openFile(pristineName.str(), File::READ | File::BINARY); + } + } + } + WaterTracksObj *umod; Int trackCount=0; Int flipU=0; @@ -1009,14 +1132,12 @@ void WaterTracksRenderSystem::loadTracks() file->seek(0, File::START); for (Int i=0; iread(&startPos,sizeof(startPos)); file->read(&endPos,sizeof(endPos)); file->read(&wtype,sizeof(wtype)); - //Check if this track already exists. if (findTrack(startPos,endPos,wtype)) - { i++; - goto tryagain; + { + continue; } umod=bindTrack(wtype); @@ -1296,7 +1417,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/GameEngineDevice/Source/Win32Device/Common/Win32BIGFile.cpp b/Core/GameEngineDevice/Source/Win32Device/Common/Win32BIGFile.cpp index 3deee01fd82..c87f1e4f45e 100644 --- a/Core/GameEngineDevice/Source/Win32Device/Common/Win32BIGFile.cpp +++ b/Core/GameEngineDevice/Source/Win32Device/Common/Win32BIGFile.cpp @@ -74,10 +74,13 @@ File* Win32BIGFile::openFile( const Char *filename, Int access ) ramFile = newInstance( RAMFile ); ramFile->deleteOnClose(); - if (ramFile->openFromArchive(m_file, fileInfo->m_filename, fileInfo->m_offset, fileInfo->m_size) == FALSE) { - ramFile->close(); - ramFile = nullptr; - return nullptr; + { + CriticalSectionClass::LockClass lock(m_fileLock); + if (ramFile->openFromArchive(m_file, fileInfo->m_filename, fileInfo->m_offset, fileInfo->m_size) == FALSE) { + ramFile->close(); + ramFile = nullptr; + return nullptr; + } } if ((access & File::WRITE) == 0) { @@ -165,4 +168,3 @@ Bool Win32BIGFile::getFileInfo(const AsciiString& filename, FileInfo *fileInfo) return TRUE; } - diff --git a/Core/GameEngineDevice/Source/Win32Device/Common/Win32BIGFileSystem.cpp b/Core/GameEngineDevice/Source/Win32Device/Common/Win32BIGFileSystem.cpp index 64401c47eff..da51403869e 100644 --- a/Core/GameEngineDevice/Source/Win32Device/Common/Win32BIGFileSystem.cpp +++ b/Core/GameEngineDevice/Source/Win32Device/Common/Win32BIGFileSystem.cpp @@ -42,9 +42,69 @@ #include "Win32Device/Common/Win32BIGFileSystem.h" #include "Utility/endian_compat.h" +#include +#include +#include static const char *BIGFileIdentifier = "BIGF"; +// Zero Hour can run with both Generals and ZH archives in one directory. +// Load the higher-priority archives first because ArchiveFileSystem keeps the +// first copy of a path when overwrite is false. +static AsciiString GetBigFilename(AsciiString filename) +{ + const char *path = filename.str(); + const char *slash = strrchr(path, '\\'); + const char *forwardSlash = strrchr(path, '/'); + if (forwardSlash != nullptr && (slash == nullptr || forwardSlash > slash)) + { + slash = forwardSlash; + } + + AsciiString result = slash != nullptr ? slash + 1 : path; + result.toLower(); + return result; +} + +static Int GetBIGLoadPriority(AsciiString filename) +{ + AsciiString baseName = GetBigFilename(filename); + + if (baseName.compareNoCase("patchzh.big") == 0) + { + return 10; + } + if (baseName.endsWithNoCase("zh.big")) + { + return 20; + } + if (baseName.compareNoCase("patch.big") == 0 + || baseName.compareNoCase("patchdata.big") == 0 + || baseName.compareNoCase("patchini.big") == 0) + { + return 30; + } + if (baseName.compareNoCase("audio.big") == 0 + || baseName.compareNoCase("audioenglish.big") == 0 + || baseName.compareNoCase("english.big") == 0 + || baseName.compareNoCase("gensec.big") == 0 + || baseName.compareNoCase("ini.big") == 0 + || baseName.compareNoCase("maps.big") == 0 + || baseName.compareNoCase("music.big") == 0 + || baseName.compareNoCase("shaders.big") == 0 + || baseName.compareNoCase("speech.big") == 0 + || baseName.compareNoCase("speechenglish.big") == 0 + || baseName.compareNoCase("terrain.big") == 0 + || baseName.compareNoCase("textures.big") == 0 + || baseName.compareNoCase("w3d.big") == 0 + || baseName.compareNoCase("window.big") == 0) + { + return 40; + } + + return 0; +} + Win32BIGFileSystem::Win32BIGFileSystem() : ArchiveFileSystem() { } @@ -212,10 +272,20 @@ Bool Win32BIGFileSystem::loadBigFilesFromDirectory(AsciiString dir, AsciiString FilenameList filenameList; TheLocalFileSystem->getFileListInDirectory(dir, "", fileMask, filenameList, TRUE); + std::vector sortedFiles(filenameList.begin(), filenameList.end()); + std::sort(sortedFiles.begin(), sortedFiles.end(), [](const AsciiString& a, const AsciiString& b) { + Int priorityA = GetBIGLoadPriority(a); + Int priorityB = GetBIGLoadPriority(b); + if (priorityA != priorityB) + { + return priorityA < priorityB; + } + return a.compareNoCase(b) < 0; + }); Bool actuallyAdded = FALSE; - FilenameListIter it = filenameList.begin(); - while (it != filenameList.end()) { + std::vector::iterator it = sortedFiles.begin(); + while (it != sortedFiles.end()) { #if RTS_ZEROHOUR // TheSuperHackers @bugfix bobtista 18/11/2025 Skip duplicate INIZH.big in Data\INI to prevent CRC mismatches. // English, Chinese, and Korean SKUs shipped with two INIZH.big files (one in Run directory, one in Run\Data\INI). diff --git a/Core/Libraries/Include/Lib/BaseDefines.h b/Core/Libraries/Include/Lib/BaseDefines.h new file mode 100644 index 00000000000..61fd7d7584d --- /dev/null +++ b/Core/Libraries/Include/Lib/BaseDefines.h @@ -0,0 +1,40 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2026 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 + +#ifndef RETAIL_COMPATIBLE_CRC +// TheSuperHackers @tweak bobtista 09/06/2026 Temporarily default to 0 so USE_DETERMINISTIC_MATH +// stays active (the two are mutually exclusive), enabling Mac<->Windows cross-architecture lockstep +// parity. Drops sync with retail 1.04 clients. Revert with the deterministic-math squash when PR #8 lands. +#define RETAIL_COMPATIBLE_CRC (0) // Game is expected to be CRC compatible with retail Generals 1.08, Zero Hour 1.04 +#endif + +#ifndef USE_DETERMINISTIC_MATH +#define USE_DETERMINISTIC_MATH (1) // Game uses deterministic math for game simulation compatibility among different system architectures in peer to peer networks +#endif + +#if defined(__has_include) +#if __has_include("gmath.h") +#define HAS_GAMEMATH (1) +#endif +#endif + +#if !HAS_GAMEMATH || RETAIL_COMPATIBLE_CRC +#undef USE_DETERMINISTIC_MATH // Cannot actually use deterministic math :( +#endif diff --git a/Core/Libraries/Include/Lib/BaseType.h b/Core/Libraries/Include/Lib/BaseType.h index 8b5e760aff4..2b6e5507219 100644 --- a/Core/Libraries/Include/Lib/BaseType.h +++ b/Core/Libraries/Include/Lib/BaseType.h @@ -29,8 +29,9 @@ #pragma once -#include "Lib/BaseTypeCore.h" -#include "Lib/trig.h" +#include "BaseDefines.h" +#include "BaseTypeCore.h" +#include "trig.h" //----------------------------------------------------------------------------- typedef wchar_t WideChar; ///< multi-byte character representations @@ -224,7 +225,7 @@ __forceinline float fast_float_ceil(float f) #define INT_TO_REAL(x) ((Real)(x)) // once we've ceiled/floored, trunc and round are identical, and currently, round is faster... (srj) -#if RTS_GENERALS /*&& RETAIL_COMPATIBLE_CRC*/ +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC #define REAL_TO_INT_CEIL(x) (fast_float2long_round(ceilf(x))) #define REAL_TO_INT_FLOOR(x) (fast_float2long_round(floorf(x))) #else @@ -283,7 +284,7 @@ struct Coord2D return x == value && y == value; } - Real length() const { return (Real)sqrt( x*x + y*y ); } + Real length() const { return Sqrt( x*x + y*y ); } Real lengthSqr() const { return x*x + y*y; } void normalize() @@ -355,7 +356,7 @@ inline Real Coord2D::toAngle() const vector.x = x; vector.y = y; - Real dist = (Real)sqrt(vector.x * vector.x + vector.y * vector.y); + Real dist = Sqrt(vector.x * vector.x + vector.y * vector.y); // normalize if (dist == 0.0f) @@ -422,7 +423,7 @@ struct ICoord2D return x == value && y == value; } - Int length() const { return (Int)sqrt( (double)(x*x + y*y) ); } + Int length() const { return (Int)Sqrt( (double)(x*x + y*y) ); } Int lengthSqr() const { return x*x + y*y; } void add( const ICoord2D &a ) @@ -519,7 +520,16 @@ struct Coord3D { Real x, y, z; - Real length() const { return (Real)sqrt( x*x + y*y + z*z ); } + Real length() const + { +#if RETAIL_COMPATIBLE_CRC + // Must not touch this function because it affects its inline-ability + // and therefore changes the logic at an unknown call site that relies on it. It is a bug. + return (Real)sqrt( x*x + y*y + z*z ); +#else + return Sqrt( x*x + y*y + z*z ); +#endif + } Real lengthSqr() const { return ( x*x + y*y + z*z ); } void normalize() @@ -631,7 +641,7 @@ struct ICoord3D { Int x, y, z; - Int length() const { return (Int)sqrt( (double)(x*x + y*y + z*z) ); } + Int length() const { return (Int)Sqrt( (double)(x*x + y*y + z*z) ); } Int lengthSqr() const { return x*x + y*y + z*z; } void zero() diff --git a/Core/Libraries/Include/Lib/trig.h b/Core/Libraries/Include/Lib/trig.h index d6f3fa22cd8..27fa8cb8391 100644 --- a/Core/Libraries/Include/Lib/trig.h +++ b/Core/Libraries/Include/Lib/trig.h @@ -28,3 +28,5 @@ Real Cos(Real); Real Tan(Real); Real ACos(Real); Real ASin(Real x); +Real Sqrt(Real x); +double Sqrt(double x); diff --git a/Core/Libraries/Source/WWVegas/CMakeLists.txt b/Core/Libraries/Source/WWVegas/CMakeLists.txt index e8a6beeb0f2..d010f404b2a 100644 --- a/Core/Libraries/Source/WWVegas/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/CMakeLists.txt @@ -7,15 +7,37 @@ target_compile_definitions(core_wwcommon INTERFACE ) target_link_libraries(core_wwcommon INTERFACE - d3d8lib - milesstub stlport ) +if(NOT GGC_RENDER_BACKEND STREQUAL "bgfx") + target_link_libraries(core_wwcommon INTERFACE + d3d8lib + ) +endif() + +# 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 +54,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/WW3D2/BgfxBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp new file mode 100644 index 00000000000..a6b30a13f69 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.cpp @@ -0,0 +1,14813 @@ +/* +** 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. +// IRenderBackend implementation that drives bgfx as the primary +// rendering backend, translating the engine's DX8-era draw calls +// into bgfx submits with a fixed-function-emulating uber shader. + +#include "BgfxBackend.h" + +#include "BgfxRenderProfile.h" +#include "DrawCallLog.h" +#include "RenderDocTrigger.h" +#include "RenderStateDefs.h" +#include "DXTUtils.h" +#include "dx8fvf.h" +#include "dx8indexbuffer.h" +#include "dx8vertexbuffer.h" +#include "dx8wrapper.h" +#include "FixedFunctionState.h" +#include "GgcRuntimeFlags.h" +#include "indexbuffer.h" +#include "WW3D2/light.h" +#include "WW3D2/lightenvironment.h" +#include "WW3D2/mapper.h" +#include "WWMath/matrix3d.h" +#include "WWMath/matrix4.h" +#include "WW3D2/render2d.h" +#include "FixedFunctionState.h" +#include "RenderStateDefs.h" +#include "WW3D2/shader.h" +#include "shdlib.h" +#include "texture.h" +#include "texturefilter.h" +#include "textureloader.h" +#include "TextureResourceManager.h" +#include "vertexbuffer.h" +#include "WWMath/vector3.h" +#include "WW3DDeviceInit.h" +#include "WW3D2/ww3d.h" +#include "ww3dformat.h" +#include "WWDebug/wwdebug.h" +#include "WWMath/wwmath.h" + +#include +#include +#include +#include +#include + +#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 + +// 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 +#include +#include +#include +#ifdef __APPLE__ +#include +#endif +#if defined(SAGE_USE_SDL3) +#include +#endif + +// TheSuperHackers @refactor bobtista 16/04/2026 bgfx renders into the single +// game window. The old DX8 reference popup window has been removed. +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif + +// TheSuperHackers @refactor bobtista 11/06/2026 On bgfx builds dx8wrapper.cpp (the home of these +// globals on the DX8 reference build) is not compiled, so define them here. External code declares +// them extern locally (CommandLine.cpp, GameEngine.cpp, Debug.cpp). g_device.windowed is the source +// of truth; DX8Wrapper_IsWindowed mirrors it for the legacy headless/quickstart checks. +bool DX8Wrapper_IsWindowed = true; +int DX8Wrapper_PreserveFPU = 0; + +// 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. +#if defined(GGC_BGFX_RENDERER_METAL) +#include "vs_passthrough_metal.bin.h" +#include "fs_passthrough_metal.bin.h" +#include "vs_uber_metal.bin.h" +#include "vs_uber_instanced_metal.bin.h" +#include "vs_trees_metal.bin.h" +#include "fs_uber_metal.bin.h" +#include "fs_uber_array_metal.bin.h" +#include "fs_uber_frameconst_metal.bin.h" +#include "vs_uber_array_metal.bin.h" +#include "vs_shadow_volume_metal.bin.h" +#include "fs_shadow_volume_metal.bin.h" +#include "vs_shadow_apply_metal.bin.h" +#include "fs_shadow_apply_metal.bin.h" +#include "vs_scene_composite_metal.bin.h" +#include "fs_scene_composite_metal.bin.h" +#include "fs_bloom_bright_metal.bin.h" +#include "fs_bloom_blur_metal.bin.h" +#include "fs_ssao_metal.bin.h" +#include "fs_copy_metal.bin.h" +#include "vs_scene_depth_metal.bin.h" +#include "vs_scene_depth_instanced_metal.bin.h" +#include "fs_scene_depth_metal.bin.h" +#include "vs_shadow_caster_metal.bin.h" +#include "vs_shadow_caster_instanced_metal.bin.h" +#include "fs_shadow_caster_metal.bin.h" +#include "vs_smudge_metal.bin.h" +#include "fs_smudge_metal.bin.h" +#define GGC_BGFX_SHADER(name) name##_metal +#elif defined(GGC_BGFX_RENDERER_VULKAN) +#include "vs_passthrough_spirv.bin.h" +#include "fs_passthrough_spirv.bin.h" +#include "vs_uber_spirv.bin.h" +#include "vs_uber_instanced_spirv.bin.h" +#include "vs_trees_spirv.bin.h" +#include "fs_uber_spirv.bin.h" +#include "fs_uber_array_spirv.bin.h" +#include "fs_uber_frameconst_spirv.bin.h" +#include "vs_uber_array_spirv.bin.h" +#include "vs_shadow_volume_spirv.bin.h" +#include "fs_shadow_volume_spirv.bin.h" +#include "vs_shadow_apply_spirv.bin.h" +#include "fs_shadow_apply_spirv.bin.h" +#include "vs_scene_composite_spirv.bin.h" +#include "fs_scene_composite_spirv.bin.h" +#include "fs_bloom_bright_spirv.bin.h" +#include "fs_bloom_blur_spirv.bin.h" +#include "fs_ssao_spirv.bin.h" +#include "fs_copy_spirv.bin.h" +#include "vs_scene_depth_spirv.bin.h" +#include "vs_scene_depth_instanced_spirv.bin.h" +#include "fs_scene_depth_spirv.bin.h" +#include "vs_shadow_caster_spirv.bin.h" +#include "vs_shadow_caster_instanced_spirv.bin.h" +#include "fs_shadow_caster_spirv.bin.h" +#include "vs_smudge_spirv.bin.h" +#include "fs_smudge_spirv.bin.h" +#define GGC_BGFX_SHADER(name) name##_spirv +#else +#include "vs_passthrough_dx11.bin.h" +#include "fs_passthrough_dx11.bin.h" + +// TheSuperHackers @refactor bobtista 12/04/2026 Uber shader pair. +// Single program handles all TSS combinations via uniforms. +#include "vs_uber_dx11.bin.h" +#include "vs_uber_instanced_dx11.bin.h" +#include "vs_trees_dx11.bin.h" +#include "fs_uber_dx11.bin.h" +#include "fs_uber_array_dx11.bin.h" +#include "fs_uber_frameconst_dx11.bin.h" +#include "vs_uber_array_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" + +#include "vs_scene_composite_dx11.bin.h" +#include "fs_scene_composite_dx11.bin.h" +#include "fs_bloom_bright_dx11.bin.h" +#include "fs_bloom_blur_dx11.bin.h" +#include "fs_ssao_dx11.bin.h" +#include "fs_copy_dx11.bin.h" +#include "vs_scene_depth_dx11.bin.h" +#include "vs_scene_depth_instanced_dx11.bin.h" +#include "fs_scene_depth_dx11.bin.h" +#include "vs_shadow_caster_dx11.bin.h" +#include "vs_shadow_caster_instanced_dx11.bin.h" +#include "fs_shadow_caster_dx11.bin.h" +#include "vs_smudge_dx11.bin.h" +#include "fs_smudge_dx11.bin.h" +#define GGC_BGFX_SHADER(name) name##_dx11 +#endif + +#include "BgfxBackendState.h" + +#ifdef RTS_ZEROHOUR +extern "C" void GGC_GetBgfxPostProcessParams(float * params); +extern "C" void GGC_GetBgfxWipeParams(float * params); +extern "C" void GGC_GetBgfxColorGradeParams(float * params); +extern "C" void GGC_GetBgfxBloomParams(float * params); +extern "C" int GGC_GetBgfxHdrEnabled(); +extern "C" void GGC_GetBgfxPostFx2Params(float * params); +extern "C" void GGC_GetBgfxSSAOParams(float * params); +extern "C" void GGC_GetBgfxMaterialFxParams(float * params); +extern "C" int GGC_GetBgfxShadowMapEnabled(); +extern "C" void GGC_GetBgfxShadowMapParams(float * params); +extern "C" int GGC_GetBgfxPointFilter(); +extern "C" const char * GGC_GetBgfxRenderer(); +extern "C" int GGC_GetBgfxSrgb(); +extern "C" int GGC_GetBgfxShadowFullPcf(); +extern "C" int GGC_GetBgfxStencilShadowsEnabled(); +extern "C" int GGC_GetBgfxMsaaSamples(); +extern "C" float GGC_GetBgfxRenderScale(); +extern "C" void GGC_GetBgfxDiagnosticFlags(int * logStats, int * noSceneFramebuffer, int * noPostFx); +extern "C" void GGC_GetBgfxSoftParticleParams(float * params); +extern "C" int GGC_GetBgfxScreenshotFrame(); +extern "C" const char * GGC_GetBgfxScreenshotPath(); +extern "C" void GGC_ClearBgfxScreenshotRequest(); +extern "C" int GGC_GetCurrentLogicFrame(); +// TheSuperHackers @feature bobtista 23/06/2026 Strongest shadow-casting dynamic light in the live +// scene. Fills outPosRange={x,y,z,range} and outDiffuseBias={r,g,b,bias}; returns non-zero +// when a caster light is active. Drives SetupPointShadowView's perspective shadow map. +extern "C" int GGC_GetBgfxPointShadowLight(float * outPosRange, float * outDiffuseBias, float * outShadowStrength); +// TheSuperHackers @feature bobtista 14/07/2026 Second-strongest shadow-casting dynamic light, +// for the second point-shadow slot (transient lightning-flash lights next to the beam). +extern "C" int GGC_GetBgfxPointShadowLight2(float * outPosRange, float * outDiffuseBias, float * outShadowStrength); +// TheSuperHackers @feature bobtista 23/06/2026 Global toggle for the dynamic-light shadow-map pass. +extern "C" int GGC_GetBgfxDynamicLightShadowsEnabled(); +// TheSuperHackers @feature bobtista 16/07/2026 INI toggle for the dramatic Particle Cannon lighting +// (Data/INI/Bgfx.ini PCannonEnhanced=Yes); the GGC_PCANNON_ENHANCED env flag still overrides. +extern "C" int GGC_GetPCannonEnhancedEnabled(); +extern "C" float GGC_GetPCannonDimTarget(); +#endif + +// Render-state globals. Defined here (external linkage), declared `extern` +// in BgfxBackendState.h so BgfxBackendTextures.cpp can reference them. +BgfxDevice g_device; +BgfxUniforms g_uniforms; + +// TheSuperHackers @bugfix bobtista 08/07/2026 Static render objects in other +// translation units (e.g. the DX8MeshRendererClass instance) destroy their +// vertex/index buffers from destructors that __cxa_finalize may run after this +// file's g_resourceRegistry/g_caches maps are already destroyed, which +// segfaulted on any std::exit (GGC_AUTO_EXIT_SECONDS). The handler is +// registered in Initialize, which the standard orders BEFORE the destruction +// of every static initialized at load time, so the flag always trips first. +// Leaking the GPU handles at process exit is intentional; the OS reclaims them. +static bool s_exitTeardownActive = false; + +static void MarkExitTeardownActive() +{ + s_exitTeardownActive = true; +} + +bool BgfxExitTeardownActive() +{ + return s_exitTeardownActive; +} + +// TheSuperHackers @feature bobtista 09/07/2026 The automated capture triggers name files +// .NNNNNN.. Honor a .png base path so the capture emits PNG directly (encoded in the +// screenShot callback) instead of a BMP that has to be converted afterwards; anything else stays BMP. +// Returns the extension and sets baseLen to the base length excluding a recognized .png suffix. +static const char * BgfxScreenshotBaseExtension(const char * basePath, size_t * baseLen) +{ + const size_t n = strlen(basePath); + *baseLen = n; + if (n >= 4 + && basePath[n - 4] == '.' + && tolower(static_cast(basePath[n - 3])) == 'p' + && tolower(static_cast(basePath[n - 2])) == 'n' + && tolower(static_cast(basePath[n - 1])) == 'g') + { + *baseLen = n - 4; + return "png"; + } + return "bmp"; +} + +// TheSuperHackers @performance bobtista 15/06/2026 Slot layout for the packed +// per-draw material array uniform (u_material). MUST stay in lockstep with the +// #define block in fs_uber.sc / vs_uber.sc / vs_uber_instanced.sc. +enum MaterialUniformSlot +{ + MU_MatDiffuse = 0, + MU_MatAmbient, + MU_MatEmissive, + MU_TssOps0, + MU_TssOps1, + MU_AtestParams, + MU_TexcoordSource, + MU_VertexColorFlags, + MU_TexcoordSelect2, + MU_ProjectedDecalMode, + MU_GrayscaleEnable, + MU_ObjectShroudDim, + MU_CloudParams, + MU_TexTransform0, + MU_TexTransform1, + MU_TexTransform0Z, + MU_Tex1Transform0, + MU_Tex1Transform1, + MU_Tex1TransformZ, + MU_Tex2Transform0, + MU_Tex2Transform1, + MU_TexProjected, + MU_LegacyPixelShaderMode, + MU_ZBias, + MU_LightMapParams, + MU_COUNT +}; +BgfxDraw g_draw; +BgfxOverrides g_overrides; + +// TheSuperHackers @feature bobtista 15/06/2026 LOCAL DEV AID (uncommitted): lets a +// hotkey request a scene-framebuffer rebuild so opt-in HDR (RGBA8<->RGBA16F format) +// can be toggled live. The rebuild runs at the safe resize point in Begin_Scene. +static bool g_requestSceneFramebufferRebuild = false; +extern "C" void GGC_RequestBgfxFramebufferRebuild() +{ + g_requestSceneFramebufferRebuild = true; +} +BgfxViewFlags g_views; +BgfxFrame g_frame; +BgfxStats g_stats; +BgfxCaches g_caches; +// Asset-ingress resource side-table. id 0 is reserved invalid. +BgfxResourceRegistry g_resourceRegistry = { {}, 1 }; + +// Defined in BgfxBackendTextures.cpp. +bgfx::TextureFormat::Enum TranslateWW3DFormat(WW3DFormat fmt); + +#if defined(__APPLE__) && defined(SAGE_USE_SDL3) +// TheSuperHackers @bugfix bobtista 30/04/2026 Owned by SDL3Main.cpp +// (filled in main() right after SDL_Metal_CreateView). Kept at global +// scope here so GetNativeWindowHandle below can reach it across the +// surrounding anonymous namespace. +extern void *TheSDL3MetalLayer; +#endif + + +namespace +{ +// TSS operation IDs matching fs_uber.sc #defines. Used in BuildTssOpsForShader +// and UpdateTextureStageOps to encode fixed-function texture stage state as +// float uniforms consumed by the uber fragment shader. +static const float kTssDisable = 0.0f; +static const float kTssSelectArg1 = 1.0f; +static const float kTssSelectArg2 = 2.0f; +static const float kTssModulate = 3.0f; +static const float kTssModulate2x = 4.0f; +static const float kTssAdd = 5.0f; +static const float kTssAddSigned = 6.0f; +static const float kTssSubtract = 7.0f; +static const float kTssBlendTexAlpha = 8.0f; +static const float kTssBlendCurAlpha = 9.0f; +static const float kTssAddSmooth = 10.0f; +static const float kTssAddSigned2x = 11.0f; +static const float kTssModAlphaAddColor = 12.0f; +static const float kTssSubtractRev = 13.0f; + +// TSS argument source IDs (packed into arg1/arg2 uniform channels). +static const float kTssArgTexture = 0.0f; +static const float kTssArgDiffuse = 1.0f; +static const float kTssArgCurrent = 2.0f; +static const unsigned kTextureArgumentSelectMask = 0x0000000f; + +constexpr unsigned kTextureAddressWrap = 1, kTextureAddressClamp = 3, kTextureAddressBorder = 4; +constexpr unsigned kTextureSampleNone = 0, kTextureSamplePoint = 1, kTextureSampleLinear = 2, kTextureSampleAnisotropic = 3; +constexpr unsigned kTexcoordGenPassthru = 0x00000000, kTexcoordGenCameraNormal = 0x00010000, kTexcoordGenCameraReflection = 0x00030000, kTexcoordGenCameraPosition = 0x00020000; +constexpr unsigned kTextureTransformDisable = 0, kTextureTransformProjected = 256, kTextureTransformCount2 = 2, kTextureTransformCount3 = 3; +constexpr unsigned kTextureTransformStage0 = 16, kTransformView = 2; +constexpr unsigned kTextureArgCurrent = static_cast(RB_TEXARG_CURRENT), kTextureArgTexture = static_cast(RB_TEXARG_TEXTURE), kTextureArgDiffuse = static_cast(RB_TEXARG_DIFFUSE); +constexpr unsigned kTextureOpDisable = static_cast(RB_TEXOP_DISABLE), kTextureOpSelectArg1 = static_cast(RB_TEXOP_SELECTARG1), kTextureOpSelectArg2 = static_cast(RB_TEXOP_SELECTARG2); + +static float TextureOpToTssOp(unsigned value) +{ + switch (static_cast(value)) + { + case RB_TEXOP_DISABLE: return kTssDisable; + case RB_TEXOP_SELECTARG1: return kTssSelectArg1; + case RB_TEXOP_SELECTARG2: return kTssSelectArg2; + case RB_TEXOP_MODULATE: return kTssModulate; + case RB_TEXOP_MODULATE2X: return kTssModulate2x; + case RB_TEXOP_ADD: return kTssAdd; + case RB_TEXOP_ADDSIGNED: return kTssAddSigned; + case RB_TEXOP_SUBTRACT: return kTssSubtract; + case RB_TEXOP_BLENDTEXTUREALPHA: return kTssBlendTexAlpha; + case RB_TEXOP_BLENDCURRENTALPHA: return kTssBlendCurAlpha; + case RB_TEXOP_ADDSMOOTH: return kTssAddSmooth; + case RB_TEXOP_ADDSIGNED2X: return kTssAddSigned2x; + case RB_TEXOP_MODULATEALPHA_ADDCOLOR: return kTssModAlphaAddColor; + default: return kTssSelectArg1; + } +} + +static float TextureArgToTssArg(unsigned value) +{ + switch (static_cast(value & kTextureArgumentSelectMask)) + { + case RB_TEXARG_TEXTURE: return kTssArgTexture; + case RB_TEXARG_CURRENT: return kTssArgCurrent; + case RB_TEXARG_DIFFUSE: + default: return kTssArgDiffuse; + } +} + +static unsigned long AllocateLegacyShaderHandle() +{ + static unsigned long nextHandle = 1; + return nextHandle++; +} + +static bool CoalesceDynamicRangeUploadsEnabled() +{ + static int s_enabled = -1; + if (s_enabled < 0) + { + const char * env = GgcFlags::StringValue(GgcFlag_BgfxCoalesceDynamicRangeUploads); + s_enabled = (env == nullptr || env[0] == '\0' || std::atoi(env) != 0) ? 1 : 0; + } + return s_enabled != 0; +} + +static std::unordered_map g_legacyPixelShaderModes; + +static void ResetFrameStats() +{ + const uint32_t nextFrame = g_stats.frameIndex + 1; + std::memset(&g_stats, 0, sizeof(g_stats)); + g_stats.frameIndex = nextFrame; +} + +struct BgfxDiagnosticFlags +{ + bool logStats; + bool noSceneFramebuffer; + bool noPostFx; +}; + +static BgfxDiagnosticFlags GetBgfxDiagnosticFlags() +{ + BgfxDiagnosticFlags flags = { false, false, false }; +#ifdef RTS_ZEROHOUR + int logStats = 0; + int noSceneFramebuffer = 0; + int noPostFx = 0; + GGC_GetBgfxDiagnosticFlags(&logStats, &noSceneFramebuffer, &noPostFx); + flags.logStats = logStats != 0; + flags.noSceneFramebuffer = noSceneFramebuffer != 0; + flags.noPostFx = noPostFx != 0; +#endif + return flags; +} + +enum class BgfxShadowMode +{ + Stencil, + None +}; + +static BgfxShadowMode GetBgfxShadowMode() +{ + if (const char * mode = GgcFlags::StringValue(GgcFlag_BgfxShadowMode)) + { + if (std::strcmp(mode, "stencil") == 0) + { + return BgfxShadowMode::Stencil; + } + if (std::strcmp(mode, "none") == 0 || std::strcmp(mode, "off") == 0) + { + return BgfxShadowMode::None; + } + } + if (GGC_GetBgfxStencilShadowsEnabled() == 0) + { + return BgfxShadowMode::None; + } + return BgfxShadowMode::Stencil; +} + +static bool BgfxStencilShadowsEnabled() +{ + const BgfxShadowMode mode = GetBgfxShadowMode(); + return mode == BgfxShadowMode::Stencil; +} + +// TheSuperHackers @performance bobtista 04/06/2026 bgfx multithreaded mode. +// The backend forces single-threaded mode by calling bgfx::renderFrame() once before +// bgfx::init() (see Initialize); skipping it lets bgfx::init() create its own internal +// render thread, pipelining the Metal command-buffer build against the next frame's +// encode. On macOS this is the default (validated pixel-identical + ~17% frame win, and +// up to ~48% on GPU-flush-bound heavy scenes); opt out with GGC_BGFX_NO_RENDER_THREAD. +// On other platforms it stays opt-in via GGC_BGFX_RENDER_THREAD. The DX8 path is unchanged. +static bool BgfxUseRenderThread() +{ + static int cached = -1; + if (cached < 0) + { + // TheSuperHackers @performance bobtista 05/06/2026 Default the render thread ON for + // Windows/DX11 too (measured +27% on save 67, 24.75 -> 31.56 fps): it overlaps the + // next frame's CPU encode with the current frame's DX11 submit. The single-bgfx-API-thread + // invariant already holds (create/destroy/update/frame and the deferred-destroy queues run + // on the Begin/End_Scene thread; the texture loader worker is CPU-only). Opt out via + // GGC_BGFX_NO_RENDER_THREAD. + cached = (!GgcFlags::Enabled(GgcFlag_BgfxNoRenderThread)) ? 1 : 0; + } + return cached != 0; +} + +static bool g_triangleDrawEnabled = true; + +static bool IsBgfxStatsLoggingEnabled() +{ + if (GgcFlags::Enabled(GgcFlag_BgfxPerfLog)) { + return true; + } + return GetBgfxDiagnosticFlags().logStats; +} + +static double BgfxTicksToMs(int64_t ticks, int64_t frequency) +{ + if (frequency <= 0) + { + return -1.0; + } + return (static_cast(ticks) * 1000.0) / static_cast(frequency); +} + +struct BgfxStatsLogWindow +{ + bool initialized; + LARGE_INTEGER frequency; + LARGE_INTEGER lastCounter; + double elapsedSeconds; + double windowSeconds; + uint32_t frames; + double bgfxNumDraw; + double bgfxNumBlit; + double bgfxCpuFrameMs; + double bgfxGpuFrameMs; + double bgfxWaitRenderMs; + double bgfxWaitSubmitMs; + uint32_t bgfxGpuFrameCount; + uint32_t backendDraws; + uint32_t backendSkipped; + uint32_t baseSubmits; + uint32_t sceneDepthSubmits; + uint32_t shadowVolumeSubmits; + uint32_t shadowApplySubmits; + uint32_t smudgeSubmits; + uint32_t sceneCompositeSubmits; + uint32_t debugSubmits; + uint32_t worldDraws; + uint32_t uiDraws; + uint32_t waterDraws; + uint32_t sortedDraws; + uint32_t effectDraws; + uint32_t rttDraws; + uint32_t smudgeDraws; + uint32_t textureBinds; + uint32_t textureCreates; + uint32_t textureUploads; + uint32_t textureCopies; + uint32_t materialUniformUploads; + uint32_t lightUniformUploads; + uint32_t uniformCommands; + uint32_t materialUniformCommands; + uint32_t lightUniformCommands; + uint32_t shadowUniformCommands; + uint32_t pointShadowUniformCommands; + uint32_t textureTransformUpdates; + uint32_t renderStateCopies; + uint32_t transientVbAllocations; + uint32_t transientIbAllocations; + uint32_t transientVbDraws; + uint32_t transientIbDraws; + uint32_t dynamicVbAllocations; + uint32_t dynamicIbAllocations; + uint32_t instancedSavedDrawCalls; + uint32_t sortedReplayCalls; + long long sortedReplayTotalTicks; + long long sortedReplayShaderTicks; + long long sortedReplayMaterialTicks; + long long sortedReplayTextureTicks; + long long sortedReplayTransformTicks; + long long sortedReplayLightTicks; + double bgfxTransientVbUsed; + double bgfxTransientIbUsed; + int64_t textureMemoryUsed; + int64_t rtMemoryUsed; + uint16_t numTextures; + uint16_t numFrameBuffers; + long long renderPhaseTicks[GGCRenderProfile::PHASE_COUNT]; +}; + +static BgfxStatsLogWindow g_bgfxStatsLog = {}; + +struct BgfxPerfSession +{ + uint32_t windows; + uint32_t totalFrames; + double totalSeconds; + double cpuMsMin; + double cpuMsMax; + double cpuMsSum; + double fpsMin; + double fpsMax; + uint32_t drawsMin; + uint32_t drawsMax; + uint64_t drawsSum; + uint64_t uploadsSum; + int64_t peakTexMem; + double transientVbSum; + double transientIbSum; +}; + +static BgfxPerfSession g_perfSession = {}; + +static void PerfSessionAccumulate(double windowSeconds, uint32_t windowFrames, + double cpuMsAvg, double fps, + uint32_t drawsAvg, uint32_t uploads, + int64_t texMem, double transVb, double transIb) +{ + if (g_perfSession.windows == 0) + { + g_perfSession.cpuMsMin = cpuMsAvg; + g_perfSession.cpuMsMax = cpuMsAvg; + g_perfSession.fpsMin = fps; + g_perfSession.fpsMax = fps; + g_perfSession.drawsMin = drawsAvg; + g_perfSession.drawsMax = drawsAvg; + } + else + { + if (cpuMsAvg < g_perfSession.cpuMsMin) { g_perfSession.cpuMsMin = cpuMsAvg; } + if (cpuMsAvg > g_perfSession.cpuMsMax) { g_perfSession.cpuMsMax = cpuMsAvg; } + if (fps < g_perfSession.fpsMin) { g_perfSession.fpsMin = fps; } + if (fps > g_perfSession.fpsMax) { g_perfSession.fpsMax = fps; } + if (drawsAvg < g_perfSession.drawsMin) { g_perfSession.drawsMin = drawsAvg; } + if (drawsAvg > g_perfSession.drawsMax) { g_perfSession.drawsMax = drawsAvg; } + } + g_perfSession.windows++; + g_perfSession.totalFrames += windowFrames; + g_perfSession.totalSeconds += windowSeconds; + g_perfSession.cpuMsSum += cpuMsAvg * windowFrames; + g_perfSession.drawsSum += static_cast(drawsAvg) * windowFrames; + g_perfSession.uploadsSum += uploads; + if (texMem > g_perfSession.peakTexMem) { g_perfSession.peakTexMem = texMem; } + g_perfSession.transientVbSum += transVb * windowFrames; + g_perfSession.transientIbSum += transIb * windowFrames; +} + +static void PerfSessionPrintSummary() +{ + if (g_perfSession.totalFrames == 0) { return; } + const double frames = static_cast(g_perfSession.totalFrames); + const double avgFps = frames / g_perfSession.totalSeconds; + const double avgCpu = g_perfSession.cpuMsSum / frames; + const double avgDraws = static_cast(g_perfSession.drawsSum) / frames; + std::fprintf(stderr, + "\nBGFX_PERF_SUMMARY: %.1fs %u frames\n" + " fps: avg=%.1f min=%.1f max=%.1f\n" + " cpu: avg=%.2fms min=%.2fms max=%.2fms\n" + " draws: avg=%.0f min=%u max=%u\n" + " uploads: %llu total (%.2f/frame)\n" + " texMem: peak=%lldKB\n" + " transVB: avg=%.0f bytes/frame\n" + " transIB: avg=%.0f bytes/frame\n", + g_perfSession.totalSeconds, g_perfSession.totalFrames, + avgFps, g_perfSession.fpsMin, g_perfSession.fpsMax, + avgCpu, g_perfSession.cpuMsMin, g_perfSession.cpuMsMax, + avgDraws, g_perfSession.drawsMin, g_perfSession.drawsMax, + static_cast(g_perfSession.uploadsSum), + static_cast(g_perfSession.uploadsSum) / frames, + static_cast(g_perfSession.peakTexMem / 1024), + g_perfSession.transientVbSum / frames, + g_perfSession.transientIbSum / frames); +} + +static double AverageOrMinusOne(double total, uint32_t count) +{ + if (count == 0) + { + return -1.0; + } + return total / static_cast(count); +} + +static uint32_t MakeBgfxClearColor(const Vector3 & color, float alpha) +{ + const uint32_t r = static_cast(WWMath::Clamp(color.X, 0.0f, 1.0f) * 255.0f + 0.5f); + const uint32_t g = static_cast(WWMath::Clamp(color.Y, 0.0f, 1.0f) * 255.0f + 0.5f); + const uint32_t b = static_cast(WWMath::Clamp(color.Z, 0.0f, 1.0f) * 255.0f + 0.5f); + const uint32_t a = static_cast(WWMath::Clamp(alpha, 0.0f, 1.0f) * 255.0f + 0.5f); + return (r << 24) | (g << 16) | (b << 8) | a; +} + +static uint32_t MakeLegacyARGBColor(const Vector3 & color, float alpha) +{ + const uint32_t r = static_cast(WWMath::Clamp(color.X, 0.0f, 1.0f) * 255.0f + 0.5f); + const uint32_t g = static_cast(WWMath::Clamp(color.Y, 0.0f, 1.0f) * 255.0f + 0.5f); + const uint32_t b = static_cast(WWMath::Clamp(color.Z, 0.0f, 1.0f) * 255.0f + 0.5f); + const uint32_t a = static_cast(WWMath::Clamp(alpha, 0.0f, 1.0f) * 255.0f + 0.5f); + return (a << 24) | (r << 16) | (g << 8) | b; +} + +static uint32_t FloatAsDword(float value) +{ + uint32_t bits = 0; + std::memcpy(&bits, &value, sizeof(bits)); + return bits; +} + +static void ResetBgfxStatsLogWindow() +{ + g_bgfxStatsLog.windowSeconds = 0.0; + g_bgfxStatsLog.frames = 0; + g_bgfxStatsLog.bgfxNumDraw = 0.0; + g_bgfxStatsLog.bgfxNumBlit = 0.0; + g_bgfxStatsLog.bgfxCpuFrameMs = 0.0; + g_bgfxStatsLog.bgfxGpuFrameMs = 0.0; + g_bgfxStatsLog.bgfxWaitRenderMs = 0.0; + g_bgfxStatsLog.bgfxWaitSubmitMs = 0.0; + g_bgfxStatsLog.bgfxGpuFrameCount = 0; + g_bgfxStatsLog.backendDraws = 0; + g_bgfxStatsLog.backendSkipped = 0; + g_bgfxStatsLog.baseSubmits = 0; + g_bgfxStatsLog.sceneDepthSubmits = 0; + g_bgfxStatsLog.shadowVolumeSubmits = 0; + g_bgfxStatsLog.shadowApplySubmits = 0; + g_bgfxStatsLog.smudgeSubmits = 0; + g_bgfxStatsLog.sceneCompositeSubmits = 0; + g_bgfxStatsLog.debugSubmits = 0; + g_bgfxStatsLog.worldDraws = 0; + g_bgfxStatsLog.uiDraws = 0; + g_bgfxStatsLog.waterDraws = 0; + g_bgfxStatsLog.sortedDraws = 0; + g_bgfxStatsLog.effectDraws = 0; + g_bgfxStatsLog.rttDraws = 0; + g_bgfxStatsLog.smudgeDraws = 0; + g_bgfxStatsLog.textureBinds = 0; + g_bgfxStatsLog.textureCreates = 0; + g_bgfxStatsLog.textureUploads = 0; + g_bgfxStatsLog.textureCopies = 0; + g_bgfxStatsLog.materialUniformUploads = 0; + g_bgfxStatsLog.lightUniformUploads = 0; + g_bgfxStatsLog.uniformCommands = 0; + g_bgfxStatsLog.materialUniformCommands = 0; + g_bgfxStatsLog.lightUniformCommands = 0; + g_bgfxStatsLog.shadowUniformCommands = 0; + g_bgfxStatsLog.pointShadowUniformCommands = 0; + g_bgfxStatsLog.textureTransformUpdates = 0; + g_bgfxStatsLog.renderStateCopies = 0; + g_bgfxStatsLog.transientVbAllocations = 0; + g_bgfxStatsLog.transientIbAllocations = 0; + g_bgfxStatsLog.transientVbDraws = 0; + g_bgfxStatsLog.transientIbDraws = 0; + g_bgfxStatsLog.dynamicVbAllocations = 0; + g_bgfxStatsLog.dynamicIbAllocations = 0; + g_bgfxStatsLog.sortedReplayCalls = 0; + g_bgfxStatsLog.sortedReplayTotalTicks = 0; + g_bgfxStatsLog.sortedReplayShaderTicks = 0; + g_bgfxStatsLog.sortedReplayMaterialTicks = 0; + g_bgfxStatsLog.sortedReplayTextureTicks = 0; + g_bgfxStatsLog.sortedReplayTransformTicks = 0; + g_bgfxStatsLog.sortedReplayLightTicks = 0; + g_bgfxStatsLog.bgfxTransientVbUsed = 0.0; + g_bgfxStatsLog.bgfxTransientIbUsed = 0.0; + g_bgfxStatsLog.textureMemoryUsed = 0; + g_bgfxStatsLog.rtMemoryUsed = 0; + g_bgfxStatsLog.numTextures = 0; + g_bgfxStatsLog.numFrameBuffers = 0; + std::memset(g_bgfxStatsLog.renderPhaseTicks, 0, sizeof(g_bgfxStatsLog.renderPhaseTicks)); +} + +// TheSuperHackers @bugfix bobtista 28/05/2026 Allow the perf-log directory to be overridden by GGC_BGFX_PERF_DIR and fall back to the current working directory; the previous hard-coded "C:\\tmp\\bgfx_perf" only ever worked on Windows. +static std::string GetBgfxPerfLogPath() +{ + std::filesystem::path dir; + if (const char *env = GgcFlags::StringValue(GgcFlag_BgfxPerfDir)) + { + dir = env; + } + else + { + dir = std::filesystem::current_path(); + } + std::error_code ec; + std::filesystem::create_directories(dir, ec); + return (dir / "PerfLog_BgfxStats.csv").string(); +} + +static void InitializeBgfxStatsLog() +{ + g_bgfxStatsLog.initialized = true; + g_bgfxStatsLog.elapsedSeconds = 0.0; + QueryPerformanceFrequency(&g_bgfxStatsLog.frequency); + QueryPerformanceCounter(&g_bgfxStatsLog.lastCounter); + ResetBgfxStatsLogWindow(); + + const std::string logPath = GetBgfxPerfLogPath(); + FILE * file = fopen(logPath.c_str(), "wt"); + if (file != nullptr) + { + fprintf(file, "elapsed_seconds,window_seconds,window_frames,bgfx_num_draw_avg,bgfx_num_blit_avg,bgfx_cpu_frame_ms_avg,bgfx_gpu_ms_avg,bgfx_wait_render_ms_avg,bgfx_wait_submit_ms_avg,backend_draws_avg,backend_skipped_avg,base_submits_avg,scene_depth_submits_avg,shadow_volume_submits_avg,shadow_apply_submits_avg,smudge_submits_avg,scene_composite_submits_avg,debug_submits_avg,world_draws_avg,ui_draws_avg,water_draws_avg,sorted_draws_avg,effect_draws_avg,rtt_draws_avg,smudge_draws_avg,texture_binds_avg,texture_creates_avg,texture_uploads_avg,texture_copies_avg,material_uniforms_avg,light_uniforms_avg,uniform_cmds_avg,material_uniform_cmds_avg,light_uniform_cmds_avg,shadow_uniform_cmds_avg,pointshadow_uniform_cmds_avg,texture_transform_updates_avg,render_state_copies_avg,transient_vb_alloc_avg,transient_ib_alloc_avg,transient_vb_draw_avg,transient_ib_draw_avg,dynamic_vb_alloc_avg,dynamic_ib_alloc_avg,bgfx_transient_vb_used_avg,bgfx_transient_ib_used_avg,bgfx_texture_memory,bgfx_rt_memory,bgfx_num_textures,bgfx_num_framebuffers,phase_frame_draw_us_avg,phase_draw_views_us_avg,phase_render_total_us_avg,phase_traversal_us_avg,phase_mesh_flush_us_avg,phase_sort_flush_us_avg,phase_particles_us_avg,phase_terrain_us_avg,phase_pointgroup_update_arrays_us_avg,phase_pointgroup_vb_fill_us_avg,phase_sort_pool_build_us_avg,phase_sort_pool_sort_us_avg,phase_sort_pool_draw_us_avg,sorted_replay_calls_avg,sorted_replay_total_us_avg,sorted_replay_shader_us_avg,sorted_replay_material_us_avg,sorted_replay_texture_us_avg,sorted_replay_transform_us_avg,sorted_replay_light_us_avg,phase_sorted_insert_us_avg,phase_sorted_capture_us_avg,phase_pg_compress_us_avg,phase_pg_view_xform_us_avg,phase_pg_ground_fixup_us_avg,phase_particle_tex_fetch_us_avg\n"); + fclose(file); + } + else + { + WWDEBUG_SAY(("[BgfxBackend] Failed to open PerfLog_BgfxStats.csv")); + } +} + +static void FlushBgfxStatsLogWindow() +{ + if (g_bgfxStatsLog.frames == 0) + { + return; + } + + const std::string logPath = GetBgfxPerfLogPath(); + FILE * file = fopen(logPath.c_str(), "at"); + if (file != nullptr) + { + const double frames = static_cast(g_bgfxStatsLog.frames); + // TheSuperHackers @bugfix bobtista 28/05/2026 Format string and arg list got out of sync: drop one stray %.3f and use portable %lld/%llu instead of MSVC-only %I64d. + const double phaseUsPerFrame = g_bgfxStatsLog.frequency.QuadPart > 0 + ? 1000000.0 / static_cast(g_bgfxStatsLog.frequency.QuadPart) / frames + : 0.0; + fprintf(file, "%.3f,%.3f,%u,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%lld,%lld,%u,%u,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f\n", + g_bgfxStatsLog.elapsedSeconds, + g_bgfxStatsLog.windowSeconds, + g_bgfxStatsLog.frames, + g_bgfxStatsLog.bgfxNumDraw / frames, + g_bgfxStatsLog.bgfxNumBlit / frames, + g_bgfxStatsLog.bgfxCpuFrameMs / frames, + AverageOrMinusOne(g_bgfxStatsLog.bgfxGpuFrameMs, g_bgfxStatsLog.bgfxGpuFrameCount), + g_bgfxStatsLog.bgfxWaitRenderMs / frames, + g_bgfxStatsLog.bgfxWaitSubmitMs / frames, + static_cast(g_bgfxStatsLog.backendDraws) / frames, + static_cast(g_bgfxStatsLog.backendSkipped) / frames, + static_cast(g_bgfxStatsLog.baseSubmits) / frames, + static_cast(g_bgfxStatsLog.sceneDepthSubmits) / frames, + static_cast(g_bgfxStatsLog.shadowVolumeSubmits) / frames, + static_cast(g_bgfxStatsLog.shadowApplySubmits) / frames, + static_cast(g_bgfxStatsLog.smudgeSubmits) / frames, + static_cast(g_bgfxStatsLog.sceneCompositeSubmits) / frames, + static_cast(g_bgfxStatsLog.debugSubmits) / frames, + static_cast(g_bgfxStatsLog.worldDraws) / frames, + static_cast(g_bgfxStatsLog.uiDraws) / frames, + static_cast(g_bgfxStatsLog.waterDraws) / frames, + static_cast(g_bgfxStatsLog.sortedDraws) / frames, + static_cast(g_bgfxStatsLog.effectDraws) / frames, + static_cast(g_bgfxStatsLog.rttDraws) / frames, + static_cast(g_bgfxStatsLog.smudgeDraws) / frames, + static_cast(g_bgfxStatsLog.textureBinds) / frames, + static_cast(g_bgfxStatsLog.textureCreates) / frames, + static_cast(g_bgfxStatsLog.textureUploads) / frames, + static_cast(g_bgfxStatsLog.textureCopies) / frames, + static_cast(g_bgfxStatsLog.materialUniformUploads) / frames, + static_cast(g_bgfxStatsLog.lightUniformUploads) / frames, + static_cast(g_bgfxStatsLog.uniformCommands) / frames, + static_cast(g_bgfxStatsLog.materialUniformCommands) / frames, + static_cast(g_bgfxStatsLog.lightUniformCommands) / frames, + static_cast(g_bgfxStatsLog.shadowUniformCommands) / frames, + static_cast(g_bgfxStatsLog.pointShadowUniformCommands) / frames, + static_cast(g_bgfxStatsLog.textureTransformUpdates) / frames, + static_cast(g_bgfxStatsLog.renderStateCopies) / frames, + static_cast(g_bgfxStatsLog.transientVbAllocations) / frames, + static_cast(g_bgfxStatsLog.transientIbAllocations) / frames, + static_cast(g_bgfxStatsLog.transientVbDraws) / frames, + static_cast(g_bgfxStatsLog.transientIbDraws) / frames, + static_cast(g_bgfxStatsLog.dynamicVbAllocations) / frames, + static_cast(g_bgfxStatsLog.dynamicIbAllocations) / frames, + g_bgfxStatsLog.bgfxTransientVbUsed / frames, + g_bgfxStatsLog.bgfxTransientIbUsed / frames, + static_cast(g_bgfxStatsLog.textureMemoryUsed), + static_cast(g_bgfxStatsLog.rtMemoryUsed), + g_bgfxStatsLog.numTextures, + g_bgfxStatsLog.numFrameBuffers, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::FRAME_DRAW]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::DRAW_VIEWS]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::RENDER_TOTAL]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::TRAVERSAL]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::MESH_FLUSH]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::SORT_FLUSH]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::PARTICLES]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::TERRAIN]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::POINTGROUP_UPDATE_ARRAYS]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::POINTGROUP_VB_FILL]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::SORT_POOL_BUILD]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::SORT_POOL_SORT]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::SORT_POOL_DRAW]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.sortedReplayCalls) / frames, + static_cast(g_bgfxStatsLog.sortedReplayTotalTicks) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.sortedReplayShaderTicks) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.sortedReplayMaterialTicks) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.sortedReplayTextureTicks) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.sortedReplayTransformTicks) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.sortedReplayLightTicks) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::SORTED_INSERT]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::SORTED_CAPTURE]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::POINTGROUP_COMPRESS]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::POINTGROUP_VIEW_XFORM]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::POINTGROUP_GROUND_FIXUP]) * phaseUsPerFrame, + static_cast(g_bgfxStatsLog.renderPhaseTicks[GGCRenderProfile::PARTICLE_TEX_FETCH]) * phaseUsPerFrame); + fclose(file); + } + + if (GgcFlags::Enabled(GgcFlag_BgfxPerfLog)) + { + const double frames = static_cast(g_bgfxStatsLog.frames); + const double fps = frames / g_bgfxStatsLog.windowSeconds; + const double cpuMs = g_bgfxStatsLog.bgfxCpuFrameMs / frames; + const uint32_t draws = static_cast(g_bgfxStatsLog.backendDraws / g_bgfxStatsLog.frames); + const double uploads = static_cast(g_bgfxStatsLog.textureUploads) / frames; + const double transVb = g_bgfxStatsLog.bgfxTransientVbUsed / frames; + const double transIb = g_bgfxStatsLog.bgfxTransientIbUsed / frames; + std::fprintf(stderr, + "BGFX_PERF: %.1fs fps=%.1f cpu=%.2fms draws=%u uploads=%.0f texMem=%lldKB transVB=%.0f transIB=%.0f instSaved=%u\n", + g_bgfxStatsLog.elapsedSeconds, fps, cpuMs, draws, uploads, + static_cast(g_bgfxStatsLog.textureMemoryUsed / 1024), + transVb, transIb, g_bgfxStatsLog.instancedSavedDrawCalls); + PerfSessionAccumulate(g_bgfxStatsLog.windowSeconds, g_bgfxStatsLog.frames, + cpuMs, fps, draws, g_bgfxStatsLog.textureUploads, + g_bgfxStatsLog.textureMemoryUsed, transVb, transIb); + } + + ResetBgfxStatsLogWindow(); +} + +static void UpdateBgfxStatsLog() +{ + if (!IsBgfxStatsLoggingEnabled()) + { + return; + } + if (!g_bgfxStatsLog.initialized) + { + InitializeBgfxStatsLog(); + } + + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + double deltaSeconds = 0.0; + if (g_bgfxStatsLog.frequency.QuadPart > 0) + { + deltaSeconds = + static_cast(now.QuadPart - g_bgfxStatsLog.lastCounter.QuadPart) / + static_cast(g_bgfxStatsLog.frequency.QuadPart); + } + g_bgfxStatsLog.lastCounter = now; + g_bgfxStatsLog.elapsedSeconds += deltaSeconds; + g_bgfxStatsLog.windowSeconds += deltaSeconds; + ++g_bgfxStatsLog.frames; + + const bgfx::Stats * stats = bgfx::getStats(); + if (stats != nullptr) + { + g_bgfxStatsLog.bgfxNumDraw += stats->numDraw; + g_bgfxStatsLog.bgfxNumBlit += stats->numBlit; + g_bgfxStatsLog.bgfxCpuFrameMs += BgfxTicksToMs(stats->cpuTimeFrame, stats->cpuTimerFreq); + g_bgfxStatsLog.bgfxWaitRenderMs += BgfxTicksToMs(stats->waitRender, stats->cpuTimerFreq); + g_bgfxStatsLog.bgfxWaitSubmitMs += BgfxTicksToMs(stats->waitSubmit, stats->cpuTimerFreq); + if (stats->gpuTimerFreq > 0 && stats->gpuTimeEnd >= stats->gpuTimeBegin) + { + g_bgfxStatsLog.bgfxGpuFrameMs += BgfxTicksToMs(stats->gpuTimeEnd - stats->gpuTimeBegin, stats->gpuTimerFreq); + ++g_bgfxStatsLog.bgfxGpuFrameCount; + } + g_bgfxStatsLog.bgfxTransientVbUsed += stats->transientVbUsed; + g_bgfxStatsLog.bgfxTransientIbUsed += stats->transientIbUsed; + g_bgfxStatsLog.textureMemoryUsed = stats->textureMemoryUsed; + g_bgfxStatsLog.rtMemoryUsed = stats->rtMemoryUsed; + g_bgfxStatsLog.numTextures = stats->numTextures; + g_bgfxStatsLog.numFrameBuffers = stats->numFrameBuffers; + } + + g_bgfxStatsLog.backendDraws += g_stats.drawCalls; + g_bgfxStatsLog.backendSkipped += g_stats.skippedDraws; + g_bgfxStatsLog.baseSubmits += g_stats.baseSubmits; + g_bgfxStatsLog.sceneDepthSubmits += g_stats.sceneDepthSubmits; + g_bgfxStatsLog.shadowVolumeSubmits += g_stats.shadowVolumeSubmits; + g_bgfxStatsLog.shadowApplySubmits += g_stats.shadowApplySubmits; + g_bgfxStatsLog.smudgeSubmits += g_stats.smudgeSubmits; + g_bgfxStatsLog.sceneCompositeSubmits += g_stats.sceneCompositeSubmits; + g_bgfxStatsLog.debugSubmits += g_stats.debugSubmits; + g_bgfxStatsLog.worldDraws += g_stats.worldDraws; + g_bgfxStatsLog.uiDraws += g_stats.uiDraws; + g_bgfxStatsLog.waterDraws += g_stats.waterDraws; + g_bgfxStatsLog.sortedDraws += g_stats.sortedDraws; + g_bgfxStatsLog.effectDraws += g_stats.effectDraws; + g_bgfxStatsLog.rttDraws += g_stats.rttDraws; + g_bgfxStatsLog.smudgeDraws += g_stats.smudgeDraws; + g_bgfxStatsLog.textureBinds += g_stats.textureBinds; + g_bgfxStatsLog.textureCreates += g_stats.textureCreates; + g_bgfxStatsLog.textureUploads += g_stats.textureUploads; + g_bgfxStatsLog.textureCopies += g_stats.textureCopies; + g_bgfxStatsLog.materialUniformUploads += g_stats.materialUniformUploads; + g_bgfxStatsLog.lightUniformUploads += g_stats.lightUniformUploads; + g_bgfxStatsLog.uniformCommands += g_stats.uniformCommands; + g_bgfxStatsLog.materialUniformCommands += g_stats.materialUniformCommands; + g_bgfxStatsLog.lightUniformCommands += g_stats.lightUniformCommands; + g_bgfxStatsLog.shadowUniformCommands += g_stats.shadowUniformCommands; + g_bgfxStatsLog.pointShadowUniformCommands += g_stats.pointShadowUniformCommands; + g_bgfxStatsLog.textureTransformUpdates += g_stats.textureTransformUpdates; + g_bgfxStatsLog.renderStateCopies += g_stats.renderStateCopies; + g_bgfxStatsLog.transientVbAllocations += g_stats.transientVbAllocations; + g_bgfxStatsLog.transientIbAllocations += g_stats.transientIbAllocations; + g_bgfxStatsLog.transientVbDraws += g_stats.transientVbDraws; + g_bgfxStatsLog.transientIbDraws += g_stats.transientIbDraws; + g_bgfxStatsLog.dynamicVbAllocations += g_stats.dynamicVbAllocations; + g_bgfxStatsLog.dynamicIbAllocations += g_stats.dynamicIbAllocations; + g_bgfxStatsLog.instancedSavedDrawCalls += g_stats.instancedSavedDrawCalls; + g_bgfxStatsLog.sortedReplayCalls += g_stats.sortedReplayCalls; + g_bgfxStatsLog.sortedReplayTotalTicks += g_stats.sortedReplayTotalTicks; + g_bgfxStatsLog.sortedReplayShaderTicks += g_stats.sortedReplayShaderTicks; + g_bgfxStatsLog.sortedReplayMaterialTicks += g_stats.sortedReplayMaterialTicks; + g_bgfxStatsLog.sortedReplayTextureTicks += g_stats.sortedReplayTextureTicks; + g_bgfxStatsLog.sortedReplayTransformTicks += g_stats.sortedReplayTransformTicks; + g_bgfxStatsLog.sortedReplayLightTicks += g_stats.sortedReplayLightTicks; + for (int phase = 0; phase < GGCRenderProfile::PHASE_COUNT; ++phase) + { + g_bgfxStatsLog.renderPhaseTicks[phase] += + GGCRenderProfile::SnapshotTicks(static_cast(phase)); + } + + if (g_bgfxStatsLog.windowSeconds >= 1.0) + { + FlushBgfxStatsLogWindow(); + } +} + +static void LogFrameStats() +{ +#ifdef RTS_DEBUG + // TheSuperHackers @perf bobtista 03/06/2026 Default off; opt in via + // GGC_BGFX_PERF_LOG=1 (interval in frames, default 300). The big + // formatted string used to fire every 60 frames + first 10, costing + // measurable Debug FPS on heavy-combat saves. Cache the env probe. + static int s_perfLogInterval = -1; + if (s_perfLogInterval < 0) { + const char * env = GgcFlags::StringValue(GgcFlag_BgfxPerfLog); + if (env != nullptr) { + int v = std::atoi(env); + s_perfLogInterval = (v > 0) ? v : 300; + } else { + s_perfLogInterval = 0; // disabled + } + } + if (s_perfLogInterval > 0 && (g_stats.frameIndex % s_perfLogInterval) == 0) + { + WWDEBUG_SAY(("[BGFX PERF] frame=%u draws=%u skipped=%u submits(base/depth/vol/apply/smudge/comp/debug)=%u/%u/%u/%u/%u/%u/%u views(world/ui/water/sort/effect/rtt/smudge)=%u/%u/%u/%u/%u/%u/%u binds=%u uniforms(mat/light)=%u/%u texxf=%u rsCopies=%u transientAlloc(vb/ib)=%u/%u transientDraw(vb/ib)=%u/%u dynAlloc(vb/ib)=%u/%u", + g_stats.frameIndex, + g_stats.drawCalls, + g_stats.skippedDraws, + g_stats.baseSubmits, + g_stats.sceneDepthSubmits, + g_stats.shadowVolumeSubmits, + g_stats.shadowApplySubmits, + g_stats.smudgeSubmits, + g_stats.sceneCompositeSubmits, + g_stats.debugSubmits, + g_stats.worldDraws, + g_stats.uiDraws, + g_stats.waterDraws, + g_stats.sortedDraws, + g_stats.effectDraws, + g_stats.rttDraws, + g_stats.smudgeDraws, + g_stats.textureBinds, + g_stats.materialUniformUploads, + g_stats.lightUniformUploads, + g_stats.textureTransformUpdates, + g_stats.renderStateCopies, + g_stats.transientVbAllocations, + g_stats.transientIbAllocations, + g_stats.transientVbDraws, + g_stats.transientIbDraws, + g_stats.dynamicVbAllocations, + g_stats.dynamicIbAllocations)); + } +#endif +} + +// 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: +#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 + { + // TheSuperHackers @build bobtista 30/04/2026 Always print bgfx fatal + // messages to stderr — WWDEBUG_SAY is a no-op in release builds, but + // we want diagnostics for the macOS bring-up. + std::fprintf(stderr, "[bgfx] FATAL code=%d at %s:%u: %s\n", + static_cast(code), filePath ? filePath : "?", line, str ? str : "?"); + std::fflush(stderr); + } + void traceVargs(const char * filePath, uint16_t line, const char * format, va_list argList) override + { + // TheSuperHackers @perf bobtista 02/06/2026 A Debug-config build compiles bgfx + // itself in Debug, which turns on its internal BX_TRACE narration (per texture, + // per uniform, per bind). Printing+flushing every one of those thousands of lines + // per frame dragged the Debug build to ~5fps. Suppress the informational flood by + // default; surface WARN/ERROR lines always, and emit everything only when the + // operator opts in via GGC_TRACE. The level word is literal in bgfx's format + // string, so we can classify before formatting and skip the vsnprintf entirely + // for suppressed lines. + static const bool s_verbose = (GgcFlags::Enabled(GgcFlag_Trace)); + bool isImportant = (format != nullptr) + && (std::strstr(format, "WARN") != nullptr || std::strstr(format, "ERROR") != nullptr); + // TheSuperHackers @tweak bobtista 18/06/2026 bgfx emits a "RefCount is N (expected 0)" + // WARN for every transient vertex/index buffer still mid-flight when the device tears + // down on resize/shutdown. It is benign teardown chatter (dozens per swap), so demote + // it out of the always-on path and let it through only under GGC_TRACE. + if (isImportant && format != nullptr && std::strstr(format, "RefCount is") != nullptr) + { + isImportant = false; + } + if (!s_verbose && !isImportant) + { + return; + } + char buf[512]; + std::vsnprintf(buf, sizeof(buf), format, argList); + size_t len = std::strlen(buf); + while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) { buf[--len] = '\0'; } + std::fprintf(stderr, "[bgfx] %s:%u: %s\n", filePath ? filePath : "?", line, buf); + // Flush important lines so they survive a subsequent crash; let the opt-in verbose + // flood rely on normal stdio buffering to avoid a syscall per line. + if (isImportant) + { + std::fflush(stderr); + } + } + 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 {} + void screenShot(const char * filePath, uint32_t width, uint32_t height, uint32_t pitch, + bgfx::TextureFormat::Enum /*format*/, const void * data, uint32_t /*size*/, + bool yflip) override + { + // bgfx delivers BGRA8 pixels asynchronously. The requested path's extension chooses the + // format: .png is encoded with bimg (already linked for the bgfx backend) for the in-game + // F12 screenshots; every other extension keeps the legacy uncompressed 32-bpp BMP used by + // the offline capture tooling. + if (filePath == nullptr || data == nullptr || width == 0 || height == 0) + { + return; + } + + const size_t pathLen = strlen(filePath); + const bool wantPng = pathLen >= 4 + && filePath[pathLen - 4] == '.' + && tolower(static_cast(filePath[pathLen - 3])) == 'p' + && tolower(static_cast(filePath[pathLen - 2])) == 'n' + && tolower(static_cast(filePath[pathLen - 1])) == 'g'; + if (wantPng) + { + bx::FileWriter writer; + bx::Error err; + if (bx::open(&writer, filePath, false, &err)) + { + bimg::imageWritePng(&writer, width, height, pitch, data, + bimg::TextureFormat::BGRA8, yflip, &err); + bx::close(&writer); + WWDEBUG_SAY(("[BgfxBackend] screenShot wrote %ux%u PNG to %s", width, height, filePath)); + } + else + { + WWDEBUG_SAY(("[BgfxBackend] screenShot: open %s failed", filePath)); + } + return; + } + + FILE * f = fopen(filePath, "wb"); + if (f == nullptr) + { + WWDEBUG_SAY(("[BgfxBackend] screenShot: fopen %s failed", filePath)); + return; + } + const uint32_t rowBytes = width * 4; + const uint32_t pixelBytes = rowBytes * height; + const uint32_t fileSize = 14 + 40 + pixelBytes; + uint8_t fileHdr[14] = {0}; + fileHdr[0] = 'B'; fileHdr[1] = 'M'; + fileHdr[2] = static_cast(fileSize & 0xFF); + fileHdr[3] = static_cast((fileSize >> 8) & 0xFF); + fileHdr[4] = static_cast((fileSize >> 16) & 0xFF); + fileHdr[5] = static_cast((fileSize >> 24) & 0xFF); + fileHdr[10] = 54; // pixel data offset + uint8_t infoHdr[40] = {0}; + infoHdr[0] = 40; + infoHdr[4] = static_cast(width & 0xFF); + infoHdr[5] = static_cast((width >> 8) & 0xFF); + infoHdr[6] = static_cast((width >> 16) & 0xFF); + infoHdr[7] = static_cast((width >> 24) & 0xFF); + // BMP: positive height = bottom-up storage (first row = bottom); + // negative height = top-down storage (first row = top). bgfx + // yflip=true means data is bottom-up (OpenGL origin), yflip=false + // means top-down (D3D origin). Match accordingly so we never flip + // on the CPU. + const int32_t h = static_cast(height); + const int32_t signedH = yflip ? h : -h; + const uint32_t storeH = static_cast(signedH); + infoHdr[8] = static_cast(storeH & 0xFF); + infoHdr[9] = static_cast((storeH >> 8) & 0xFF); + infoHdr[10] = static_cast((storeH >> 16) & 0xFF); + infoHdr[11] = static_cast((storeH >> 24) & 0xFF); + infoHdr[12] = 1; // planes + infoHdr[14] = 32; // bpp + // BI_RGB compression = 0 (no compression). BGRA byte order is the + // BMP default for 32bpp BI_RGB. + fwrite(fileHdr, 1, sizeof(fileHdr), f); + fwrite(infoHdr, 1, sizeof(infoHdr), f); + const uint8_t * src = static_cast(data); + for (uint32_t y = 0; y < height; ++y) + { + fwrite(src + y * pitch, 1, rowBytes, f); + } + fclose(f); + WWDEBUG_SAY(("[BgfxBackend] screenShot wrote %ux%u to %s", width, height, filePath)); + } + 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; + +static uint32_t MapCmpFuncToBgfxStencilTest(CompareFunc f) +{ + switch (f) + { + case RB_CMP_NEVER: return BGFX_STENCIL_TEST_NEVER; + case RB_CMP_LESS: return BGFX_STENCIL_TEST_LESS; + case RB_CMP_EQUAL: return BGFX_STENCIL_TEST_EQUAL; + case RB_CMP_LESS_EQUAL: return BGFX_STENCIL_TEST_LEQUAL; + case RB_CMP_GREATER: return BGFX_STENCIL_TEST_GREATER; + case RB_CMP_NOT_EQUAL: return BGFX_STENCIL_TEST_NOTEQUAL; + case RB_CMP_GREATER_EQUAL: return BGFX_STENCIL_TEST_GEQUAL; + case RB_CMP_ALWAYS: default: return BGFX_STENCIL_TEST_ALWAYS; + } +} + +static uint32_t MapStencilOpToBgfx(StencilOp op, uint32_t shift) +{ + uint32_t ord; + switch (op) + { + case RB_STENCIL_OP_KEEP: ord = 1; break; + case RB_STENCIL_OP_ZERO: ord = 0; break; + case RB_STENCIL_OP_REPLACE: ord = 2; break; + case RB_STENCIL_OP_INCR_SAT: ord = 4; break; + case RB_STENCIL_OP_DECR_SAT: ord = 6; break; + case RB_STENCIL_OP_INVERT: ord = 7; break; + case RB_STENCIL_OP_INCR: ord = 3; break; + case RB_STENCIL_OP_DECR: ord = 5; break; + default: ord = 1; break; + } + return ord << shift; +} + +static uint32_t BuildCurrentStencilState() +{ + if (!g_draw.stencilEnabled) + { + return BGFX_STENCIL_NONE; + } + return 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; +} + +static void UpdateShadowStencilState() +{ + g_draw.shadowStencilFront = BuildCurrentStencilState(); + if (g_draw.shadowStencilFront == BGFX_STENCIL_NONE) + { + g_draw.shadowStencilBack = BGFX_STENCIL_NONE; + return; + } + g_draw.shadowStencilBack = BGFX_STENCIL_NONE; +} + +// TheSuperHackers @refactor bobtista 26/04/2026 Shader program creation +// helper. Creates a bgfx program from compiled bytecode, sets debug names, +// and cleans up on failure. +bgfx::ProgramHandle CreateShaderProgram( + const uint8_t * vsData, uint32_t vsSize, const char * vsName, + const uint8_t * fsData, uint32_t fsSize, const char * fsName) +{ + bgfx::ShaderHandle vs = bgfx::createShader(bgfx::makeRef(vsData, vsSize)); + bgfx::ShaderHandle fs = bgfx::createShader(bgfx::makeRef(fsData, fsSize)); + if (bgfx::isValid(vs) && bgfx::isValid(fs)) + { + bgfx::setName(vs, vsName); + bgfx::setName(fs, fsName); + return bgfx::createProgram(vs, fs, true); + } + if (bgfx::isValid(vs)) + { + bgfx::destroy(vs); + } + if (bgfx::isValid(fs)) + { + bgfx::destroy(fs); + } + WWDEBUG_SAY(("[BgfxBackend] %s + %s createShader FAILED.", vsName, fsName)); + return BGFX_INVALID_HANDLE; +} + +bgfx::RendererType::Enum GetConfiguredRendererType() +{ + // TheSuperHackers @perf bobtista 03/06/2026 GGC_BGFX_RENDERER selects the bgfx renderer at + // run time on any platform: dx11/d3d11, dx12/d3d12, vulkan, metal, gl. bgfx's DX11 and + // DX12 backends share SM5 DXBC shaders, so no shader recompile is needed. + const char *override_ = GgcFlags::StringValue(GgcFlag_BgfxRenderer); + if (override_ == nullptr || *override_ == '\0') + { + override_ = GGC_GetBgfxRenderer(); + } + if (override_ != nullptr) + { + if (std::strcmp(override_, "dx12") == 0 || std::strcmp(override_, "d3d12") == 0) + { + return bgfx::RendererType::Direct3D12; + } + if (std::strcmp(override_, "dx11") == 0 || std::strcmp(override_, "d3d11") == 0) + { + return bgfx::RendererType::Direct3D11; + } + if (std::strcmp(override_, "vulkan") == 0) + { + return bgfx::RendererType::Vulkan; + } + if (std::strcmp(override_, "metal") == 0) + { + return bgfx::RendererType::Metal; + } + if (std::strcmp(override_, "gl") == 0 || std::strcmp(override_, "opengl") == 0) + { + return bgfx::RendererType::OpenGL; + } + } +#if defined(GGC_BGFX_RENDERER_METAL) + return bgfx::RendererType::Metal; +#elif defined(GGC_BGFX_RENDERER_VULKAN) + return bgfx::RendererType::Vulkan; +#else + return bgfx::RendererType::Direct3D11; +#endif +} + +void *GetNativeWindowHandle(void *window) +{ + if (window == NULL) + { + return NULL; + } +#if defined(SAGE_USE_SDL3) +#if defined(__APPLE__) + // TheSuperHackers @bugfix bobtista 30/04/2026 SDL_Metal_CreateView + // gives us a CAMetalLayer that bgfx can take as platformData.nwh + // directly. Passing the NSWindow instead lets bgfx try to install + // its own CAMetalLayer on the contentView, which fights with + // SDL3's own layer setup and intermittently crashes Apple's AGX + // driver in AGCDeserializedReply during pipeline-state compile. + // GGC_MACOS_USE_NSWINDOW=1 forces the legacy NSWindow path so we + // can A/B-test which form the local Apple Silicon variant prefers. + if (::TheSDL3MetalLayer != NULL && !GgcFlags::Enabled(GgcFlag_MacosUseNsWindow)) + { + return ::TheSDL3MetalLayer; + } +#endif + SDL_Window *sdlWindow = static_cast(window); + SDL_PropertiesID props = SDL_GetWindowProperties(sdlWindow); +#if defined(__APPLE__) + void *nativeWindow = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL); + if (nativeWindow != NULL) + { + return nativeWindow; + } +#elif defined(_WIN32) + void *nativeWindow = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); + if (nativeWindow != NULL) + { + return nativeWindow; + } +#elif defined(__linux__) + // TheSuperHackers @feature bobtista 24/07/2026 Linux native surface for bgfx. + // Prefer X11: bgfx takes the X11 Window XID as platformData.nwh (with the + // Display* as ndt, set separately in Initialize). Fall back to the Wayland + // surface pointer when running under a Wayland SDL video driver. + { + const Uint64 x11Window = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); + if (x11Window != 0) + { + return reinterpret_cast(static_cast(x11Window)); + } + void *waylandSurface = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL); + if (waylandSurface != NULL) + { + return waylandSurface; + } + } +#endif +#endif + return window; +} + +// TheSuperHackers @feature bobtista 24/07/2026 Native display/device handle for +// bgfx platformData.ndt. Required on Linux (X11 Display* / Wayland wl_display*); +// returns nullptr elsewhere, where bgfx derives the device from nwh alone. +void *GetNativeDisplayHandle(void *window) +{ +#if defined(SAGE_USE_SDL3) && defined(__linux__) + if (window != NULL) + { + SDL_Window *sdlWindow = static_cast(window); + SDL_PropertiesID props = SDL_GetWindowProperties(sdlWindow); + void *x11Display = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL); + if (x11Display != NULL) + { + return x11Display; + } + void *waylandDisplay = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, NULL); + if (waylandDisplay != NULL) + { + return waylandDisplay; + } + } +#else + (void)window; +#endif + return nullptr; +} + +void BuildStandardVertexLayouts() +{ + g_device.layoutP + .begin() + .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .end(); + + g_device.layoutPN + .begin() + .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float) + .end(); + + 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_device.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_device.layoutPT1 + .begin() + .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) + .end(); + + 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_device.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_device.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(); +} + +} + +BgfxBackend::BgfxBackend() + : m_textureBitDepth(16) + , m_msaaMode(RB_MSAA_NONE) + , m_renderDeviceDescBuilt(false) + , m_deviceCreated(false) + , m_curRenderDevice(-1) +{ + WWDEBUG_SAY(("[BgfxBackend] Backend constructed.")); +} + +BgfxBackend::~BgfxBackend() +{ +} + +// -- Backend lifecycle ------------------------------------------------------- + +namespace +{ +// TheSuperHackers @refactor bobtista 11/04/2026 ShaderClass +// translation table. Maps a ShaderClass instance to (program handle, +// bgfx state bits). +// +// Mapping rules: +// - alpha-test enabled & textured & lit -> g_texturedLitAtestProgram +// - textured & lit -> g_texturedLitProgram +// - textured & !lit -> g_texturedUnlitProgram +// - !textured & lit -> g_solidLitProgram +// - else -> g_device.passthroughProgram (debug) +// +// State bit translation is mechanical: depth compare, depth write, color +// write, blend factors, cull. Detail-blend / fog / specular gradient need +// shader code rather than state bits, so they are not handled by this +// translation. + +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; + } +} + +CompareFunc MapShaderDepthCompareToBackendCompare(ShaderClass::DepthCompareType cmp) +{ + switch (cmp) + { + case ShaderClass::PASS_NEVER: return RB_CMP_NEVER; + case ShaderClass::PASS_LESS: return RB_CMP_LESS; + case ShaderClass::PASS_EQUAL: return RB_CMP_EQUAL; + case ShaderClass::PASS_LEQUAL: return RB_CMP_LESS_EQUAL; + case ShaderClass::PASS_GREATER: return RB_CMP_GREATER; + case ShaderClass::PASS_NOTEQUAL: return RB_CMP_NOT_EQUAL; + case ShaderClass::PASS_GEQUAL: return RB_CMP_GREATER_EQUAL; + case ShaderClass::PASS_ALWAYS: return RB_CMP_ALWAYS; + default: return RB_CMP_LESS_EQUAL; + } +} + +// 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 + +// Default legacy alpha-test reference (0x60/255 = 0.376) used when a shader has ALPHATEST enabled without an explicit reference. Matches the implicit behavior preserved by ShaderClass presets. +const float kDefaultAlphaTestRef = 0x60 / 255.0f; + +void BuildTssOpsForShader(const ShaderClass & shader, + float * ops0, float * ops1, float * atestRef, float * atestFunc) +{ + 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 = kTssSelectArg1; + priAlphaOp = kTssSelectArg1; + priCArg1Src = kTssArgTexture; + priAArg1Src = kTssArgTexture; + break; + default: + case ShaderClass::GRADIENT_MODULATE: + priColorOp = kTssModulate; + priAlphaOp = kTssModulate; + priCArg1Src = kTssArgTexture; + priAArg1Src = kTssArgTexture; + break; + case ShaderClass::GRADIENT_ADD: + priColorOp = kTssAdd; + priAlphaOp = kTssModulate; + priCArg1Src = kTssArgTexture; + priAArg1Src = kTssArgTexture; + break; + case ShaderClass::GRADIENT_MODULATE2X: + priColorOp = kTssModulate2x; + priAlphaOp = kTssModulate; + priCArg1Src = kTssArgTexture; + priAArg1Src = kTssArgTexture; + break; + case ShaderClass::GRADIENT_BUMPENVMAP: + case ShaderClass::GRADIENT_BUMPENVMAPLUMINANCE: + priColorOp = kTssSelectArg1; + priAlphaOp = kTssSelectArg1; + priCArg1Src = kTssArgDiffuse; + priAArg1Src = kTssArgDiffuse; + break; + } + + switch (shader.Get_Post_Detail_Color_Func()) + { + default: + case ShaderClass::DETAILCOLOR_DISABLE: + secColorOp = kTssDisable; + break; + case ShaderClass::DETAILCOLOR_DETAIL: + secColorOp = kTssSelectArg1; + secCArg1Src = kTssArgTexture; + break; + case ShaderClass::DETAILCOLOR_SCALE: + secColorOp = kTssModulate; + secCArg1Src = kTssArgTexture; + break; + case ShaderClass::DETAILCOLOR_INVSCALE: + secColorOp = kTssAddSmooth; + secCArg1Src = kTssArgTexture; + break; + case ShaderClass::DETAILCOLOR_ADD: + secColorOp = kTssAdd; + secCArg1Src = kTssArgTexture; + break; + case ShaderClass::DETAILCOLOR_SUB: + secColorOp = kTssSubtract; + secCArg1Src = kTssArgTexture; // result = tex - current (retail arg1=TEXTURE) + break; + // TheSuperHackers @bugfix bobtista 16/07/2026 SUBR needs current - tex, but the + // uber shader's detail combiner hardcodes arg1=tex, arg2=current (the arg-select + // channels u_tssOps1.zw are not read). Encode the reversal in a dedicated op + // instead; the previous kTssSubtract mapping rendered SUBR inverted. + case ShaderClass::DETAILCOLOR_SUBR: + secColorOp = kTssSubtractRev; + secCArg1Src = kTssArgCurrent; // result = current - tex + break; + case ShaderClass::DETAILCOLOR_BLEND: + secColorOp = kTssBlendTexAlpha; + secCArg1Src = kTssArgTexture; + break; + case ShaderClass::DETAILCOLOR_DETAILBLEND: + secColorOp = kTssBlendCurAlpha; + secCArg1Src = kTssArgTexture; + break; + // TheSuperHackers @bugfix bobtista 16/07/2026 These four detail funcs previously + // fell into the default branch and silently disabled the detail stage; retail + // maps them at shader.cpp:799-858. SCALE2X and ADDSIGNED reuse existing shader + // ops; ADDSIGNED2X and MODALPHAADDCOLOR have dedicated ops in fs_uber. + case ShaderClass::DETAILCOLOR_ADDSIGNED: + secColorOp = kTssAddSigned; + secCArg1Src = kTssArgTexture; + break; + case ShaderClass::DETAILCOLOR_ADDSIGNED2X: + secColorOp = kTssAddSigned2x; + secCArg1Src = kTssArgTexture; + break; + case ShaderClass::DETAILCOLOR_SCALE2X: + secColorOp = kTssModulate2x; + secCArg1Src = kTssArgTexture; + break; + case ShaderClass::DETAILCOLOR_MODALPHAADDCOLOR: + secColorOp = kTssModAlphaAddColor; + secCArg1Src = kTssArgCurrent; // retail arg1=CURRENT: current.rgb + current.a * tex.rgb + break; + } + + switch (shader.Get_Post_Detail_Alpha_Func()) + { + default: + case ShaderClass::DETAILALPHA_DISABLE: + secAlphaOp = kTssDisable; + break; + case ShaderClass::DETAILALPHA_DETAIL: + secAlphaOp = kTssSelectArg1; + secAArg1Src = kTssArgTexture; + break; + case ShaderClass::DETAILALPHA_SCALE: + secAlphaOp = kTssModulate; + secAArg1Src = kTssArgTexture; + break; + case ShaderClass::DETAILALPHA_INVSCALE: + secAlphaOp = kTssAddSmooth; + secAArg1Src = kTssArgTexture; + break; + } + } + else + { + switch (shader.Get_Primary_Gradient()) + { + case ShaderClass::GRADIENT_DISABLE: + priColorOp = kTssDisable; + priAlphaOp = kTssDisable; + break; + default: + case ShaderClass::GRADIENT_MODULATE: + case ShaderClass::GRADIENT_ADD: + priColorOp = kTssSelectArg2; + priAlphaOp = kTssSelectArg2; + priCArg1Src = kTssArgTexture; + priAArg1Src = kTssArgTexture; + break; + } + } + + 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) + { + if (shader.Get_Src_Blend_Func() == ShaderClass::SRCBLEND_ONE_MINUS_SRC_ALPHA) + { + *atestRef = 1.0f - kDefaultAlphaTestRef; + *atestFunc = static_cast(RB_CMP_LESS_EQUAL); + } + else + { + *atestRef = kDefaultAlphaTestRef; + *atestFunc = static_cast(RB_CMP_GREATER_EQUAL); + } + } + else + { + *atestRef = 0.0f; + *atestFunc = 0.0f; + } +} + +uint64_t BuildBgfxStateForShader(const ShaderClass & shader) +{ + uint64_t state = 0; + + if (shader.Get_Color_Mask() == ShaderClass::COLOR_WRITE_ENABLE) + { + state |= BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A; + } + + 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. + // TheSuperHackers @info bobtista 16/07/2026 ShaderClass::Invert_Backface_Culling + // (retail shader.cpp honors it here) is deliberately ignored: its only live setter + // is the legacy water mirror pass, which is compiled out on this backend + // (W3DWater.cpp updateRenderTargetTextures), so CW is always correct. + state |= BGFX_STATE_CULL_CW; + } + + return state; +} + +// 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; +} + +// TheSuperHackers @perf bobtista 28/04/2026 Cache built layouts keyed by +// FVF bits. FVFInfoClass derives all offsets from the FVF bits in its +// constructor, so two instances with the same Get_FVF() produce identical +// layouts. Hit on every dynamic capture (particles, lines, 2D quads) and +// every static VB upload — the engine churns through a handful of FVF +// combos repeatedly. +static bool BuildBgfxLayoutForFVFUncached(const FVFInfoClass & fvf, bgfx::VertexLayout & out); + +bool BuildBgfxLayoutForFVF(const FVFInfoClass & fvf, bgfx::VertexLayout & out) +{ + struct CachedLayout + { + bgfx::VertexLayout layout; + bool ok; + }; + static std::unordered_map s_cache; + const unsigned key = fvf.Get_FVF(); + std::unordered_map::iterator it = s_cache.find(key); + if (it != s_cache.end()) + { + out = it->second.layout; + return it->second.ok; + } + CachedLayout entry; + entry.ok = BuildBgfxLayoutForFVFUncached(fvf, entry.layout); + out = entry.layout; + s_cache[key] = entry; + return entry.ok; +} + +static bool BuildBgfxLayoutForFVFUncached(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 & DX8_FVF_FLAG_XYZ) == DX8_FVF_FLAG_XYZ) + { + AddAttribAtOffset(out, cursor, fvf.Get_Location_Offset(), + bgfx::Attrib::Position, 3, bgfx::AttribType::Float, false, + 3 * sizeof(float)); + } + else if ((bits & DX8_FVF_FLAG_XYZRHW) == DX8_FVF_FLAG_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 (fvf.Has_Normal()) + { + AddAttribAtOffset(out, cursor, fvf.Get_Normal_Offset(), + bgfx::Attrib::Normal, 3, bgfx::AttribType::Float, false, + 3 * sizeof(float)); + } + + if (fvf.Has_Diffuse()) + { + // Legacy vertex color is BGRA u8x4 packed; the shader swizzles it + // back to engine ARGB channels after bgfx normalizes the bytes. + AddAttribAtOffset(out, cursor, fvf.Get_Diffuse_Offset(), + bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true, + sizeof(uint32_t)); + } + + if (fvf.Has_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 = fvf.Get_UV_Channel_Count(); + for (unsigned i = 0; i < numTex && i < 8; ++i) + { + unsigned componentCount = 2; + if ((bits & DX8_FVF_TEXCOORDSIZE1(i)) == DX8_FVF_TEXCOORDSIZE1(i)) + { + componentCount = 1; + } + else if ((bits & DX8_FVF_TEXCOORDSIZE3(i)) == DX8_FVF_TEXCOORDSIZE3(i)) + { + componentCount = 3; + } + else if ((bits & DX8_FVF_TEXCOORDSIZE4(i)) == DX8_FVF_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; +} + +// TheSuperHackers @refactor bobtista 11/04/2026 Vertex/index buffer caches keyed by the +// source buffer pointer: copy bytes on first sight, create a bgfx buffer, destroy +// wholesale in Shutdown. + +// TheSuperHackers @refactor bobtista 11/04/2026 Dynamic bgfx VB/IB handles: AppendLockClass +// sub-range writes need in-place updates only dynamic buffers support. Cache entries store +// dimensions to detect address reuse; stale handles are destroyed two frames later. + +// The bgfx texture currently bound to stage 0 by Set_Texture. + +// 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. + +// 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. + +// 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. +// 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. + +// 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. +// +// 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. + +// 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. + +// 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 kBgfxDebugView = 0; +const bgfx::ViewId kBgfxEngineView = 1; +const int kBgfxTextureStages = 4; + +// 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_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. +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; +// TheSuperHackers @refactor bobtista 15/04/2026 Dedicated views for stencil shadow volumes +// + darken apply. Sequential order after view 1 guarantees depth is populated first and +// the INCR/DECR volume passes execute in submit order; no clears — attachments are shared. +const bgfx::ViewId kBgfxShadowVolumeView = 6; +const bgfx::ViewId kBgfxShadowApplyView = 7; +// Multiplicative shroud overlay must run after all regular 3D scene/detail +// draws, otherwise later depth-equal building/detail passes can overwrite it. +const bgfx::ViewId kBgfxShroudOverlayView = 8; +// TheSuperHackers @feature bobtista 27/04/2026 Scene composite view. World, +// water, sorted translucency, and effect overlays render into an offscreen +// scene framebuffer; this view copies scene color to the swapchain before UI. +const bgfx::ViewId kBgfxSceneCompositeView = 9; +// TheSuperHackers @feature bobtista 27/04/2026 Native bgfx smudge/heat-haze +// views. The copy view snapshots scene color, then the draw view writes +// distorted samples back into the scene framebuffer before final composite. +const bgfx::ViewId kBgfxSmudgeCopyView = 12; +const bgfx::ViewId kBgfxSmudgeView = 13; +// 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; +// TheSuperHackers @feature bobtista 27/04/2026 Readable scene-depth view. +// Opaque world draws are duplicated here into an R32F target so later post +// and particle passes can sample depth without touching the D24S8 stencil +// surface used by the main scene framebuffer. +const bgfx::ViewId kBgfxSceneDepthView = 11; +// TheSuperHackers @feature bobtista 15/06/2026 Bloom passes. After the scene is +// fully rendered, a bright-pass extracts highlights into a half-res target which +// is blurred (H then V) and added back over the scene in the composite. +const bgfx::ViewId kBgfxBloomBrightView = 14; +const bgfx::ViewId kBgfxBloomBlurHView = 15; +const bgfx::ViewId kBgfxBloomBlurVView = 16; +// TheSuperHackers @feature bobtista 15/06/2026 SSAO compute + separable blur. +const bgfx::ViewId kBgfxSsaoView = 17; +const bgfx::ViewId kBgfxSsaoBlurHView = 18; +const bgfx::ViewId kBgfxSsaoBlurVView = 19; +// TheSuperHackers @feature bobtista 15/06/2026 Sun shadow map. Caster geometry is +// duplicated here from the sun's POV into an R32F depth target (reusing the +// scene-depth program); the uber shader samples it to shadow the sun's diffuse term. +// TheSuperHackers @feature bobtista 16/06/2026 Cascaded shadow maps: NUM cascades +// share one atlas (2x2 tiles), each fit to a progressively larger concentric box +// around the camera focus. Views kBgfxShadowMapView + c render cascade c's tile. +const int kNumShadowCascades = 3; +// TheSuperHackers @performance bobtista Width of the frame-constant data texture (RGBA32F, +// 1 row). Holds the global per-frame constants the fs_uber_frameconst variant reads instead +// of per-draw uniforms. Layout (must match fs_uber.sc GGC_UBER_FRAME_TEXTURE): +// 0: sceneAmbient 1: shadowParams 2: shadowQuality 3-6: sun shadow matrix +// 7: pointShadowParams 8: pointShadowLightPos 9: pointShadowLightColor 10-13: point shadow matrix +// 14: pointShadow2Params 15: pointShadow2LightPos 16: pointShadow2LightColor 17-20: point shadow 2 matrix +const uint16_t kFrameConstTexels = 24; +const int kBgfxFrameConstSamplerStage = 9; +const bgfx::ViewId kBgfxShadowMapView = 20; +// TheSuperHackers @feature bobtista 23/06/2026 Perspective shadow map for one bright dynamic +// point light (e.g. the nuke fireball). Renders before the engine view that samples it. +const bgfx::ViewId kBgfxPointShadowView = 23; +// TheSuperHackers @feature bobtista 23/06/2026 Debug blit for GGC_POINT_SHADOW_VIZ: blits the +// point shadow map R32F texture to a screen-corner quad after the scene composite. +const bgfx::ViewId kBgfxPointShadowVizView = 24; +// TheSuperHackers @feature bobtista 14/07/2026 Second perspective point-shadow map so a transient +// second caster light (particle-cannon lightning flash) shadows alongside the primary. +const bgfx::ViewId kBgfxPointShadow2View = 25; +const uint8_t kBgfxSortedArraySamplerStage = 4; +const uint8_t kBgfxSceneDepthSamplerStage = 6; +const uint8_t kBgfxShadowMapSamplerStage = 7; +// TheSuperHackers @feature bobtista 23/06/2026 Point-light shadow map sampler (next free stage +// after the sun shadow map). +const uint8_t kBgfxPointShadowMapSamplerStage = 8; +const uint8_t kBgfxPointShadow2MapSamplerStage = 10; +const float kSoftParticleDepthFadeScale = 80.0f; +const int kSwayTableEntries = 11; +const float kPostSharpenAmount = 0.08f; +const float kPostSaturation = 1.015f; +const float kPostContrast = 1.01f; +const float kPostFxaaAmount = 0.35f; +const float kBloomDefaultThreshold = 0.75f; +const float kBloomForcedIntensity = 0.8f; +const float kVignetteForcedStrength = 0.4f; +const float kChromaForcedAmount = 0.5f; +const float kFilmGrainForcedStrength = 0.08f; +const float kSsaoDefaultRadius = 1.0f; +const float kSsaoDefaultIntensity = 1.0f; +const float kSsaoDefaultBias = 0.025f; + +static bool BgfxProbeFlag(const char *name) +{ + static const bool s_nullSubmit = GgcFlags::Enabled(GgcFlag_ProbeNullSubmit); + static const bool s_freezeState = GgcFlags::Enabled(GgcFlag_ProbeFreezeState); + static const bool s_noSorted = GgcFlags::Enabled(GgcFlag_ProbeNoSorted); + static const bool s_noTexBind = GgcFlags::Enabled(GgcFlag_ProbeNoTexBind); + static const bool s_noMaterialUniform = GgcFlags::Enabled(GgcFlag_ProbeNoMatUniform); + static const bool s_noLightUniform = GgcFlags::Enabled(GgcFlag_ProbeNoLightUniform); + + if (std::strcmp(name, "GGC_PROBE_NULL_SUBMIT") == 0) + { + return s_nullSubmit; + } + if (std::strcmp(name, "GGC_PROBE_FREEZE_STATE") == 0) + { + return s_freezeState; + } + if (std::strcmp(name, "GGC_PROBE_NO_SORTED") == 0) + { + return s_noSorted; + } + if (std::strcmp(name, "GGC_PROBE_NO_TEXBIND") == 0) + { + return s_noTexBind; + } + if (std::strcmp(name, "GGC_PROBE_NO_MATUNIFORM") == 0) + { + return s_noMaterialUniform; + } + if (std::strcmp(name, "GGC_PROBE_NO_LIGHTUNIFORM") == 0) + { + return s_noLightUniform; + } + return false; +} + +// 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. + +// True between Begin_Sorted_Batch_Pass and End_Sorted_Batch_Pass; +// SubmitEngineDraw routes to kBgfxEngineSortView and uses +// 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 sorted replay state. +// 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. + +// Water override — set by Override_Material_Opacity, consumed by +// SubmitEngineDraw to route to the water view and apply DESTALPHA blend. + +// 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. + +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; +} + +static bool GetBackendWindowSize(void *window, int &width, int &height) +{ + width = 0; + height = 0; +#if defined(SAGE_USE_SDL3) + if (window != nullptr) + { + SDL_Window *sdlWindow = static_cast(window); + SDL_GetWindowSize(sdlWindow, &width, &height); + if (width <= 0 || height <= 0) + { + SDL_GetWindowSizeInPixels(sdlWindow, &width, &height); + } + } +#else + RECT clientRect; + if (GetClientRect(static_cast(window), &clientRect)) + { + width = clientRect.right - clientRect.left; + height = clientRect.bottom - clientRect.top; + } +#endif + return width > 0 && height > 0; +} + +static bool NearlyEqual(float a, float b) +{ + const float epsilon = 0.00001f; + return a > b - epsilon && a < b + epsilon; +} + +// TheSuperHackers @feature bobtista 08/06/2026 Swapchain dimensions for the letterbox present. The +// swapchain always matches the window; g_device.width/height are the (possibly smaller) content +// size. Fall back to the content size before the first resize has populated the swap fields. +static inline int LbSwapWidth() { return g_device.swapWidth > 0 ? g_device.swapWidth : g_device.width; } +static inline int LbSwapHeight() { return g_device.swapHeight > 0 ? g_device.swapHeight : g_device.height; } + +// Compute the content rect (render size + centered offset) for a window of swapW x swapH given the +// current letterbox request. With no request the content fills the window (offset 0). With one, the +// content is the largest box of the requested aspect that fits, centered, leaving the remainder for +// black bars. active is false when the content ends up filling the window (e.g. an exact-aspect +// window), so the direct-present fast path is kept. +static void ComputeLetterboxLayout(int swapW, int swapH, + int &contentW, int &contentH, + int &offsetX, int &offsetY, bool &active) +{ + contentW = swapW; + contentH = swapH; + offsetX = 0; + offsetY = 0; + active = false; + if (!g_device.letterboxRequested || swapW <= 0 || swapH <= 0 + || g_device.letterboxAspectW <= 0.0f || g_device.letterboxAspectH <= 0.0f) + { + return; + } + const float targetAspect = g_device.letterboxAspectW / g_device.letterboxAspectH; + const float windowAspect = static_cast(swapW) / static_cast(swapH); + if (windowAspect > targetAspect) + { + // Window wider than the target: pillarbox (bars left/right). + contentH = swapH; + contentW = static_cast(static_cast(swapH) * targetAspect + 0.5f); + } + else + { + // Window taller than the target: letterbox (bars top/bottom). + contentW = swapW; + contentH = static_cast(static_cast(swapW) / targetAspect + 0.5f); + } + if (contentW < 1) { contentW = 1; } + if (contentH < 1) { contentH = 1; } + if (contentW > swapW) { contentW = swapW; } + if (contentH > swapH) { contentH = swapH; } + offsetX = (swapW - contentW) / 2; + offsetY = (swapH - contentH) / 2; + active = (contentW != swapW) || (contentH != swapH); +} + +// TheSuperHackers @info bobtista 06/06/2026 Intentionally ignores the translation row (m[12..14]): +// only the rotation/scale 3x3 and the homogeneous m[15] are checked, so a pure-translation view +// still counts as identity. That is what the 2D-overlay inference at the call site wants - it is +// not an oversight. +static bool IsIdentityViewMatrix(const float *m) +{ + return NearlyEqual(m[0], 1.0f) && NearlyEqual(m[5], 1.0f) + && NearlyEqual(m[10], 1.0f) && NearlyEqual(m[15], 1.0f) + && NearlyEqual(m[1], 0.0f) && NearlyEqual(m[2], 0.0f) + && NearlyEqual(m[3], 0.0f) && NearlyEqual(m[4], 0.0f) + && NearlyEqual(m[6], 0.0f) && NearlyEqual(m[7], 0.0f) + && NearlyEqual(m[8], 0.0f) && NearlyEqual(m[9], 0.0f) + && NearlyEqual(m[11], 0.0f); +} + +static bool IsNonPerspectiveProjection(const float *m) +{ + // W3DMatrix4ToBgfx transpose-copies into bgfx's column-major layout. + // Perspective camera projections carry m[3][3] == 0, which lands in + // slot 15. Screen/orthographic projections keep slot 15 at 1. + return NearlyEqual(m[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; +} + +auto MakeLegacyCacheMatrix(const Matrix4x4 & m) +{ + return To_D3DMATRIX(m); +} + +auto MakeLegacyCacheMatrix(const Matrix3D & m) +{ + return To_D3DMATRIX(m); +} + +auto MakeIdentityLegacyCacheMatrix() +{ + Matrix4x4 identity(true); + return MakeLegacyCacheMatrix(identity); +} + +void CacheTransform(TransformKind transform, const Matrix4x4 & m) +{ + FixedFunctionState::Set_Transform_Matrix(static_cast(transform), MakeLegacyCacheMatrix(m)); +} + +void CacheTransform(TransformKind transform, const Matrix3D & m) +{ + FixedFunctionState::Set_Transform_Matrix(static_cast(transform), MakeLegacyCacheMatrix(m)); +} + +void CacheIdentityTransform(TransformKind transform) +{ + Matrix4x4 identity(true); + CacheTransform(transform, identity); +} + +bool IsCachedTransformIdentity(TransformKind transform) +{ + auto matrix = MakeIdentityLegacyCacheMatrix(); + FixedFunctionState::Transform_Matrix(static_cast(transform), matrix); + for (int row = 0; row < 4; ++row) + { + for (int col = 0; col < 4; ++col) + { + const float expected = (row == col) ? 1.0f : 0.0f; + if (matrix.m[row][col] != expected) + { + return false; + } + } + } + return true; +} + +static void GetPostParams(float * params) +{ + params[0] = kPostSharpenAmount; + params[1] = kPostSaturation; + params[2] = kPostContrast; + params[3] = kPostFxaaAmount; +#ifdef RTS_ZEROHOUR + GGC_GetBgfxPostProcessParams(params); + params[0] = WWMath::Clamp(params[0], 0.0f, 1.0f); + params[1] = WWMath::Clamp(params[1], 0.0f, 2.0f); + params[2] = WWMath::Clamp(params[2], 0.0f, 2.0f); + params[3] = WWMath::Clamp(params[3], 0.0f, 1.0f); +#endif + if (GetBgfxDiagnosticFlags().noPostFx) + { + params[0] = 0.0f; + params[1] = 1.0f; + params[2] = 1.0f; + params[3] = 0.0f; + } +} + +// TheSuperHackers @feature bobtista 15/06/2026 Drive the composite split-screen +// wipe from engine state so any post effect can be compared before/after. +static void GetWipeParams(float * params) +{ + params[0] = 0.5f; + params[1] = 0.0f; + params[2] = 0.0f; + params[3] = 0.0f; +#ifdef RTS_ZEROHOUR + GGC_GetBgfxWipeParams(params); +#endif +} + +// TheSuperHackers @feature bobtista 15/06/2026 Pull color-grade params, with an +// env override (GGC_BGFX_COLORGRADE) for dev iteration without editing INI. +static void GetColorGradeParams(float * params) +{ + params[0] = 0.0f; + params[1] = 1.0f; + params[2] = 0.0f; + params[3] = 0.0f; +#ifdef RTS_ZEROHOUR + GGC_GetBgfxColorGradeParams(params); +#endif + static int forced = -1; + if (forced < 0) + { + forced = (GgcFlags::Enabled(GgcFlag_BgfxColorGrade)) ? 1 : 0; + } + if (forced == 1) + { + params[0] = 1.0f; + } +} + +// TheSuperHackers @feature bobtista 15/06/2026 Pull bloom params, with an env +// override (GGC_BGFX_BLOOM). params: x = enabled, y = threshold, z = intensity. +static void GetBloomParams(float * params) +{ + params[0] = 0.0f; + params[1] = kBloomDefaultThreshold; + params[2] = 0.0f; + params[3] = 0.0f; +#ifdef RTS_ZEROHOUR + GGC_GetBgfxBloomParams(params); +#endif + static int forced = -1; + if (forced < 0) + { + forced = (GgcFlags::Enabled(GgcFlag_BgfxBloom)) ? 1 : 0; + } + if (forced == 1) + { + params[0] = 1.0f; + if (params[2] <= 0.0f) + { + params[2] = kBloomForcedIntensity; + } + } +} + +// TheSuperHackers @feature bobtista 15/06/2026 Cheap fullscreen post effects with +// per-effect env overrides for dev iteration. params: x = vignette strength, +// y = chromatic aberration amount, z = film grain strength. +static void GetPostFx2Params(float * params) +{ + params[0] = 0.0f; + params[1] = 0.0f; + params[2] = 0.0f; + params[3] = 0.0f; +#ifdef RTS_ZEROHOUR + GGC_GetBgfxPostFx2Params(params); +#endif + static int forcedVig = -1; + static int forcedCa = -1; + static int forcedGrain = -1; + if (forcedVig < 0) + { + forcedVig = (GgcFlags::Enabled(GgcFlag_BgfxVignette)) ? 1 : 0; + } + if (forcedCa < 0) + { + forcedCa = (GgcFlags::Enabled(GgcFlag_BgfxChroma)) ? 1 : 0; + } + if (forcedGrain < 0) + { + forcedGrain = (GgcFlags::Enabled(GgcFlag_BgfxGrain)) ? 1 : 0; + } + if (forcedVig == 1 && params[0] <= 0.0f) + { + params[0] = kVignetteForcedStrength; + } + if (forcedCa == 1 && params[1] <= 0.0f) + { + params[1] = kChromaForcedAmount; + } + if (forcedGrain == 1 && params[2] <= 0.0f) + { + params[2] = kFilmGrainForcedStrength; + } +} + +// TheSuperHackers @feature bobtista 15/06/2026 SSAO params: x = radius, y = intensity, +// z = bias. Env GGC_BGFX_SSAO forces it on for dev iteration. +static void GetSSAOParams(float * params) +{ + params[0] = kSsaoDefaultRadius; + params[1] = kSsaoDefaultIntensity; + params[2] = kSsaoDefaultBias; + params[3] = 0.0f; +#ifdef RTS_ZEROHOUR + float bridge[4]; + GGC_GetBgfxSSAOParams(bridge); + params[0] = bridge[1]; // radius + params[1] = bridge[2]; // intensity +#endif +} + +static void GetSoftParticleParams(float * params) +{ + params[0] = 1.0f; + params[1] = kSoftParticleDepthFadeScale; + params[2] = 0.0f; + params[3] = 0.0f; +#ifdef RTS_ZEROHOUR + GGC_GetBgfxSoftParticleParams(params); + params[0] = params[0] > 0.5f ? 1.0f : 0.0f; + params[1] = WWMath::Clamp(params[1], 0.0f, 500.0f); +#endif +} + +static bool IsBgfxSSAOEnabled(); + +// TheSuperHackers @feature bobtista 15/06/2026 MSAA sample count for the offscreen +// scene framebuffer (0/2/4/8). Env GGC_BGFX_MSAA overrides the INI BgfxMSAA. +static int GetSceneMsaaSamples() +{ + int samples = 0; + const char * env = GgcFlags::StringValue(GgcFlag_BgfxMsaa); + if (env != nullptr) + { + samples = std::atoi(env); + } +#ifdef RTS_ZEROHOUR + if (samples <= 0) + { + samples = GGC_GetBgfxMsaaSamples(); + } +#endif + if (samples >= 8) { return 8; } + if (samples >= 4) { return 4; } + if (samples >= 2) { return 2; } + return 0; +} + +// TheSuperHackers @feature bobtista 15/06/2026 Internal render-scale (supersampling) +// for the 3D scene framebuffer. Env GGC_BGFX_RENDER_SCALE overrides the INI value. +// Clamped to [1.0, 2.0]. +static float GetSceneRenderScale() +{ + float scale = 1.0f; + const char * env = GgcFlags::StringValue(GgcFlag_BgfxRenderScale); + if (env != nullptr) + { + scale = static_cast(std::atof(env)); + } +#ifdef RTS_ZEROHOUR + else + { + scale = GGC_GetBgfxRenderScale(); + } +#endif + if (scale < 1.0f) { scale = 1.0f; } + if (scale > 2.0f) { scale = 2.0f; } + return scale; +} + +static bool IsReadableSceneDepthEnabled() +{ + if (GetBgfxDiagnosticFlags().noSceneFramebuffer) + { + return false; + } + + float softParticleParams[4]; + GetSoftParticleParams(softParticleParams); + return softParticleParams[0] > 0.5f || IsBgfxSSAOEnabled(); +} + +// TheSuperHackers @feature bobtista 15/06/2026 Opt-in HDR scene color (RGBA16F + +// ACES tonemap). Read at framebuffer creation, so it applies on startup/resize. +static bool IsBgfxHdrEnabled() +{ + if (GgcFlags::Enabled(GgcFlag_BgfxHdr)) + { + return true; + } +#ifdef RTS_ZEROHOUR + if (GGC_GetBgfxHdrEnabled() != 0) + { + return true; + } +#endif + return false; +} + +// TheSuperHackers @feature bobtista 15/06/2026 SSAO needs the readable scene depth, +// so this also forces that target on (see IsReadableSceneDepthEnabled). +static bool IsBgfxSSAOEnabled() +{ + if (GgcFlags::Enabled(GgcFlag_BgfxSsao)) + { + return true; + } +#ifdef RTS_ZEROHOUR + float params[4]; + GGC_GetBgfxSSAOParams(params); + if (params[0] > 0.5f) + { + return true; + } +#endif + return false; +} + +// TheSuperHackers @feature bobtista 15/06/2026 Sun shadow map toggle. Renders a +// light-POV depth pass and shadows the sun's diffuse term in the uber shader. +static bool IsBgfxShadowMapEnabled() +{ + if (const char * sm = GgcFlags::StringValue(GgcFlag_BgfxShadowMap)) + { + return sm[0] != '0'; // GGC_BGFX_SHADOWMAP=0 forces OFF (diagnostic A/B) + } +#ifdef RTS_ZEROHOUR + if (GGC_GetBgfxShadowMapEnabled() != 0) + { + return true; + } +#endif + return false; +} + +// TheSuperHackers @tweak bobtista 18/06/2026 The [ggc] render diagnostics below are +// development aids; keep them silent by default and surface them only when the operator +// opts in via GGC_TRACE (the same switch that unmutes bgfx's own informational trace). +static bool BgfxDiagVerbose() +{ + static const bool s_verbose = (GgcFlags::Enabled(GgcFlag_Trace)); + return s_verbose; +} + +// Diagnostic: number of caster draws submitted into the shadow map since the last frame. +static int s_shadowCasterSubmitCount = 0; + +// TheSuperHackers @feature bobtista 17/06/2026 Sun-shadow caster cull region, published each frame +// by SetupSunShadowView and read by the engine caster cull (RTS3DScene::Visibility_Check and +// MeshClass::Render) via GGC_GetBgfxSunShadowCullBox / GGC_GetBgfxSunShadowDir. The sphere is +// centered on the ground the camera looks at and sized to cover the cascade coverage; meshes whose +// bounds intersect it are rendered even when off the camera frustum, so a caster that has scrolled +// off-screen still casts into the visible ground (the camera frustum alone would drop it). +static int s_sunShadowCullActive = 0; +static float s_sunShadowCullCenter[3] = { 0.0f, 0.0f, 0.0f }; +static float s_sunShadowCullRadius = 0.0f; +// Clamped toward-sun direction (z > 0); the engine projects a caster's bounds down-sun with this +// to test whether its shadow actually reaches the camera view (a tight keep that avoids submitting +// every nearby object as a caster). +static float s_sunShadowCullDir[3] = { 0.0f, 0.0f, 0.0f }; + +// TheSuperHackers @bugfix bobtista 18/06/2026 Stable scene sun direction (toward the light), locked +// in the first time a draw presents a clearly sun-like primary light (pointing well above the +// horizon). The sun shadow CSM builds its cascades from this rather than the live per-draw slot-0 +// light: a bright dynamic light (e.g. the Particle Cannon charge/beam) can hijack slot 0, and the +// first opaque draw's light is not always the sun either. The sun is map-constant, so locking the +// first sun-like value keeps cast shadows steady. See MaybeCaptureSceneSun. +static float s_sceneSunDir[3] = { 0.0f, 0.0f, 0.0f }; +static int s_sceneSunValid = 0; +static void MaybeCaptureSceneSun() +{ + if (s_sceneSunValid != 0) + { + return; + } + const float x = g_draw.lightDirs[0][0]; + const float y = g_draw.lightDirs[0][1]; + const float z = g_draw.lightDirs[0][2]; + const float len = sqrtf(x * x + y * y + z * z); + if (len < 1e-3f) + { + return; + } + // Only lock a clearly sun-like light: toward-the-light well above the horizon. Ground-level + // dynamic lights (the Particle Cannon charge) point near-horizontal/down and are skipped. + if ((z / len) < 0.2f) + { + return; + } + s_sceneSunDir[0] = x / len; + s_sceneSunDir[1] = y / len; + s_sceneSunDir[2] = z / len; + s_sceneSunValid = 1; +} + +// TheSuperHackers @feature bobtista 15/06/2026 Build the sun's light view-projection +// for this frame and arm the shadow-map view. The ortho box is centered on the ground +// point the camera looks at and sized to a fixed world radius, so it follows scrolling. +static void SetupSunShadowView() +{ + // Throttled diagnostic: confirm the caster pass actually fills the map every frame. + static int s_setupCalls = 0; + if (BgfxDiagVerbose() && (s_setupCalls++ % 120) == 0) + { + std::fprintf(stderr, "[ggc] sun shadow setup call %d: casters submitted last frame=%d, enabled=%d, fbValid=%d\n", + s_setupCalls, s_shadowCasterSubmitCount, + IsBgfxShadowMapEnabled() ? 1 : 0, bgfx::isValid(g_device.shadowMapFB) ? 1 : 0); + } + s_shadowCasterSubmitCount = 0; + g_frame.shadowActive = false; + // TheSuperHackers @bugfix bobtista 18/06/2026 Do NOT clear s_sunShadowCullActive up front. + // The scene is visibility-checked more than once per frame (shadow caster pass + main color + // pass), and the off-screen-caster keep in RTS3DScene::Visibility_Check reads this flag. A + // transient mid-frame clear made some passes see "no cull region" and drop a tall off-screen + // caster (e.g. the Strategy Center antenna at full zoom), so its shadow flickered out. The + // cull region is published atomically at the end of this function on success, and set inactive + // only on the genuine shadows-disabled early returns below; between frames it holds the last + // armed region (the camera barely moves frame-to-frame, so it stays valid). + if (!IsBgfxShadowMapEnabled() || !bgfx::isValid(g_device.shadowMapFB)) + { + s_sunShadowCullActive = 0; + // Diagnostic: the toggle is on but the target is gone => the scene loses all shadows. + // Logged once per transition so a recreation that drops the target is visible. + static bool s_loggedNoFB = false; + if (IsBgfxShadowMapEnabled() && !bgfx::isValid(g_device.shadowMapFB)) + { + if (!s_loggedNoFB) + { + std::fprintf(stderr, "[ggc] sun shadow ENABLED but target missing - no shadows this frame.\n"); + s_loggedNoFB = true; + } + } + else + { + s_loggedNoFB = false; + } + // Neutralize the views so a stale framebuffer handle is never processed + // after the shadow map is toggled off and its target destroyed. + for (int c = 0; c < kNumShadowCascades; ++c) + { + bgfx::setViewFrameBuffer(kBgfxShadowMapView + c, BGFX_INVALID_HANDLE); + bgfx::setViewClear(kBgfxShadowMapView + c, BGFX_CLEAR_NONE, 0, 1.0f, 0); + } + return; + } + // Sun direction (xyz = toward the light). Use the stable scene sun locked by MaybeCaptureSceneSun + // (the first sun-like draw of the level); only fall back to the current draw's slot-0 light if it + // has not been captured yet. The per-draw light is unreliable here: a bright dynamic light - e.g. + // the Particle Cannon charge/beam - can occupy slot 0 and swing/stretch the cascades, and even + // normally the first opaque draw's light is not guaranteed to be the sun. + float sx = (s_sceneSunValid != 0) ? s_sceneSunDir[0] : g_draw.lightDirs[0][0]; + float sy = (s_sceneSunValid != 0) ? s_sceneSunDir[1] : g_draw.lightDirs[0][1]; + float sz = (s_sceneSunValid != 0) ? s_sceneSunDir[2] : g_draw.lightDirs[0][2]; + float slen = sqrtf(sx * sx + sy * sy + sz * sz); + if (slen < 1e-4f) + { + s_sunShadowCullActive = 0; + return; + } + sx /= slen; sy /= slen; sz /= slen; + + // TheSuperHackers @tweak bobtista 16/06/2026 Clamp the sun ELEVATION used for shadow + // casting (not lighting). Maps like this one have a high afternoon sun (z~0.75, ~49deg), + // which makes ground objects throw a stubby shadow that hides under them and reads as + // "no shadows". Capping the elevation lengthens those shadows into clearly visible cast + // shadows while keeping the horizontal sun direction, so they still fall the right way. + const float kMaxShadowSunZ = 0.45f; + if (sz > kMaxShadowSunZ) + { + const float xyLen = sqrtf(sx * sx + sy * sy); + if (xyLen > 1e-4f) + { + const float targetXY = sqrtf(fmaxf(0.0f, 1.0f - kMaxShadowSunZ * kMaxShadowSunZ)); + const float scale = targetXY / xyLen; + sx *= scale; sy *= scale; sz = kMaxShadowSunZ; + } + } + + // Camera eye + forward recovered from the rigid camera view matrix. + const float * v = g_frame.cameraView; + float eye[3]; + eye[0] = -(v[0] * v[12] + v[1] * v[13] + v[2] * v[14]); + eye[1] = -(v[4] * v[12] + v[5] * v[13] + v[6] * v[14]); + eye[2] = -(v[8] * v[12] + v[9] * v[13] + v[10] * v[14]); + float fwd[3] = { -v[2], -v[6], -v[10] }; + float flen = sqrtf(fwd[0] * fwd[0] + fwd[1] * fwd[1] + fwd[2] * fwd[2]); + if (flen > 1e-5f) + { + fwd[0] /= flen; fwd[1] /= flen; fwd[2] /= flen; + } + + // Ground (z=0) intersection of the camera's central ray = view center. + float center[3] = { eye[0], eye[1], 0.0f }; + if (fabsf(fwd[2]) > 1e-4f) + { + float t = -eye[2] / fwd[2]; + if (t > 0.0f && t < 100000.0f) + { + center[0] = eye[0] + fwd[0] * t; + center[1] = eye[1] + fwd[1] * t; + center[2] = eye[2] + fwd[2] * t; + } + } + + const float D = 2000.0f; // light distance along the sun direction + const bgfx::Caps * caps = bgfx::getCaps(); + const float shadowSize = static_cast(g_device.shadowMapSize); + + // TheSuperHackers @refactor bobtista 18/06/2026 Single camera-fit sun shadow map (replaces the + // 3 concentric cascades). The ortho is fit to the camera's visible ground footprint, so it is + // sharp when zoomed in (small footprint -> small texels) and only softens when zoomed far out. + // Key property: a caster and its ground shadow share the same light-space position, so fitting + // the ortho to the ground footprint automatically covers every caster whose shadow lands in + // view - no cascade, no tight/coarse caster-receiver mismatch, no per-zoom fall-through. + + // Light right/up basis from a provisional look toward the view centre. Translating the eye does + // not change this rotation, so it is a stable axis frame for fitting and texel-snapping. + const bx::Vec3 up = (fabsf(sz) > 0.95f) ? bx::Vec3(0.0f, 1.0f, 0.0f) : bx::Vec3(0.0f, 0.0f, 1.0f); + float lightBasis[16]; + bx::mtxLookAt(lightBasis, bx::Vec3(center[0] + sx * D, center[1] + sy * D, center[2] + sz * D), + bx::Vec3(center[0], center[1], center[2]), up); + const float lrx = lightBasis[0], lry = lightBasis[4], lrz = lightBasis[8]; + const float lux = lightBasis[1], luy = lightBasis[5], luz = lightBasis[9]; + + // Camera world-space basis (columns of the view rotation) + half-FOV tangents from the proj + // diagonal, used to shoot the four frustum-corner rays and intersect them with the ground. + const float * pj = g_frame.cameraProj; + const float tanX = (fabsf(pj[0]) > 1e-6f) ? (1.0f / pj[0]) : 1.0f; + const float tanY = (fabsf(pj[5]) > 1e-6f) ? (1.0f / pj[5]) : 1.0f; + const float rightW[3] = { v[0], v[4], v[8] }; + const float upW[3] = { v[1], v[5], v[9] }; + float cR0 = center[0] * lrx + center[1] * lry + center[2] * lrz; + float cU0 = center[0] * lux + center[1] * luy + center[2] * luz; + float maxExtent = 0.0f; + for (int sR = -1; sR <= 1; sR += 2) + { + for (int sU = -1; sU <= 1; sU += 2) + { + float dir[3] = { + fwd[0] + float(sR) * tanX * rightW[0] + float(sU) * tanY * upW[0], + fwd[1] + float(sR) * tanX * rightW[1] + float(sU) * tanY * upW[1], + fwd[2] + float(sR) * tanX * rightW[2] + float(sU) * tanY * upW[2] }; + float gx = center[0]; + float gy = center[1]; + if (dir[2] < -1e-3f) // ray heading down toward the ground + { + float t = -eye[2] / dir[2]; + if (t > 0.0f) + { + gx = eye[0] + dir[0] * t; + gy = eye[1] + dir[1] * t; + } + } + const float gR = gx * lrx + gy * lry; + const float gU = gx * lux + gy * luy; + maxExtent = fmaxf(maxExtent, fmaxf(fabsf(gR - cR0), fabsf(gU - cU0))); + } + } + // TheSuperHackers @bugfix bobtista 18/07/2026 Keep an active shadow-casting dynamic light (the + // enhanced Particle Cannon beam) inside the ortho + caster cull. The fit above is camera-centered, + // so as the beam sweeps toward the footprint edge its dramatic tree shadows clipped at the box. + // Grow the extent to reach the beam plus a margin for its lit foliage; feeds H and the cull radius + // below, so both track the beam. Beam pos is last frame's (SetupPointShadowView runs after) - fine + // at this scale. Only active while a beam casts, so normal scenes keep their tight camera fit. + if (g_draw.pointShadowLightValid && g_draw.pointShadowParams[0] >= 0.5f) + { + const float bR = g_draw.pointShadowLightPos[0] * lrx + g_draw.pointShadowLightPos[1] * lry; + const float bU = g_draw.pointShadowLightPos[0] * lux + g_draw.pointShadowLightPos[1] * luy; + const float kBeamShadowMargin = 300.0f; + maxExtent = fmaxf(maxExtent, fabsf(bR - cR0) + kBeamShadowMargin); + maxExtent = fmaxf(maxExtent, fabsf(bU - cU0) + kBeamShadowMargin); + } + // TheSuperHackers @bugfix bobtista 18/06/2026 Size the ortho to the visible ground + // footprint plus a small margin; an oversized ortho collapses on-screen shadow resolution + // until thin shadows wash out and vanish. + const float kOrthoMarginScale = 1.2f; + const float kOrthoMarginPad = 128.0f; + const float kOrthoMinH = 256.0f; + const float kOrthoMaxH = 2400.0f; + float H = maxExtent * kOrthoMarginScale + kOrthoMarginPad; + H = WWMath::Clamp(H, kOrthoMinH, kOrthoMaxH); + // Quantize H to discrete steps so it holds steady through a smooth zoom. With H constant, the + // texel-snapped centre lands on a fixed world grid every frame, so the shadow edge stays put + // instead of crawling/jittering as the continuously-changing H reshuffled the texel grid. + const float kHQuantum = 64.0f; + H = ceilf(H / kHQuantum) * kHQuantum; + + // Texel-snap the centre along the light right/up axes so panning does not crawl the shadow edge. + const float unitsPerTexel = (2.0f * H) / shadowSize; + const float dR = floorf(cR0 / unitsPerTexel + 0.5f) * unitsPerTexel - cR0; + const float dU = floorf(cU0 / unitsPerTexel + 0.5f) * unitsPerTexel - cU0; + const bx::Vec3 snapCenter(center[0] + dR * lrx + dU * lux, + center[1] + dR * lry + dU * luy, + center[2] + dR * lrz + dU * luz); + const bx::Vec3 snapLightPos(snapCenter.x + sx * D, snapCenter.y + sy * D, snapCenter.z + sz * D); + + float lightView[16]; + bx::mtxLookAt(lightView, snapLightPos, snapCenter, up); + float lightProj[16]; + bx::mtxOrtho(lightProj, -H, H, -H, H, 1.0f, 2.0f * D + H, 0.0f, caps->homogeneousDepth); + bx::mtxMul(&g_frame.shadowMatrices[0], lightView, lightProj); + + // One full-resolution shadow view; neutralize the two legacy cascade tiles. + bgfx::setViewFrameBuffer(kBgfxShadowMapView, g_device.shadowMapFB); + bgfx::setViewRect(kBgfxShadowMapView, 0, 0, + static_cast(g_device.shadowMapSize), static_cast(g_device.shadowMapSize)); + bgfx::setViewClear(kBgfxShadowMapView, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0xffffffff, 1.0f, 0); + bgfx::setViewTransform(kBgfxShadowMapView, lightView, lightProj); + for (int c = 1; c < kNumShadowCascades; ++c) + { + bgfx::setViewFrameBuffer(kBgfxShadowMapView + c, BGFX_INVALID_HANDLE); + bgfx::setViewClear(kBgfxShadowMapView + c, BGFX_CLEAR_NONE, 0, 1.0f, 0); + } + g_frame.shadowActive = true; + + // Publish the cull region so RTS3DScene::Visibility_Check keeps casters that are off the camera + // frustum but whose shadow lands in the footprint. It matches the ortho half-extent (plus a small + // bbox allowance) so the engine keeps exactly the casters the ortho will actually render - keeping + // more would submit casters that get clipped out of the map (wasted) and keeping fewer would drop + // casters the ortho covers. + s_sunShadowCullCenter[0] = center[0]; + s_sunShadowCullCenter[1] = center[1]; + s_sunShadowCullCenter[2] = center[2]; + // Decoupled from H: the cull radius is a 3D-distance test, so it must allow for a caster's full + // height (Z) and a generous edge slack, not just the ortho's R/U half-extent. Keeping it generous + // means a tall off-screen caster is never dropped from the caster pass; if such a caster's shadow + // is actually off-screen it simply gets clipped (in R/U) out of the tight ortho, costing nothing + // visible. Keeping H tight (above) is what preserves on-screen shadow resolution. + s_sunShadowCullRadius = fmaxf(H, maxExtent + 600.0f); + s_sunShadowCullDir[0] = sx; + s_sunShadowCullDir[1] = sy; + s_sunShadowCullDir[2] = sz; + s_sunShadowCullActive = 1; + + static bool s_loggedShadow = false; + if (BgfxDiagVerbose() && !s_loggedShadow) + { + std::fprintf(stderr, + "[ggc] sun shadow single-map %ux%u armed: H=%.0f sunDir=(%.2f,%.2f,%.2f) center=(%.0f,%.0f,%.0f)\n", + g_device.shadowMapSize, g_device.shadowMapSize, H, sx, sy, sz, center[0], center[1], center[2]); + s_loggedShadow = true; + } +} + +// TheSuperHackers @feature bobtista 23/06/2026 Build the perspective light view-projection for the +// strongest shadow-casting dynamic light (e.g. nuke fireball) and arm the point-shadow view. The +// eye is the light's world position; it looks toward the ground point the camera is focused on (the +// same focus the sun setup fits to), and the perspective near/far are derived from the light's +// attenuation range. When no caster light is active (or the feature is disabled) this sets +// pointShadowParams[0] = -1 and skips the view so the pass is a no-op. +// u_pointShadowParams layout: x=state(-1=none,0=glow only,1=light+shadow), y=bias, z=texel, +// w=shadow darkening strength (per-light, 0 = glow only). The strength comes from the light itself. +static void SetupPointShadowView() +{ + // Reset every frame so a vanished caster light cannot leave a stale position that + // accidentally matches a different point-light slot in Set_Light_Environment. + g_draw.pointShadowParams[0] = -1.0f; + g_draw.pointShadowLightValid = false; + + int featureEnabled = 0; +#ifdef RTS_ZEROHOUR + featureEnabled = GGC_GetBgfxDynamicLightShadowsEnabled(); +#endif + if (featureEnabled == 0 || !bgfx::isValid(g_device.pointShadowFB)) + { + bgfx::setViewFrameBuffer(kBgfxPointShadowView, BGFX_INVALID_HANDLE); + bgfx::setViewClear(kBgfxPointShadowView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + return; + } + + float posRange[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float diffuseBias[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float lightStrength = 1.0f; + int hasLight = 0; +#ifdef RTS_ZEROHOUR + hasLight = GGC_GetBgfxPointShadowLight(posRange, diffuseBias, &lightStrength); +#endif + if (hasLight == 0) + { + bgfx::setViewFrameBuffer(kBgfxPointShadowView, BGFX_INVALID_HANDLE); + bgfx::setViewClear(kBgfxPointShadowView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + return; + } + // A glow-only light (strength 0) lights and glints surfaces but renders no shadow map. + const bool castsShadow = (lightStrength > 0.0f); + + const float lightPos[3] = { posRange[0], posRange[1], posRange[2] }; + // Dynamic point shadows are local effects (nuke fireball, particle-cannon column). Aim the + // single perspective map down the light column instead of toward the camera focus; otherwise a + // beam near the edge of the view can light receivers while its caster silhouettes land outside + // the point-shadow cone. + const float focus[3] = { lightPos[0], lightPos[1], lightPos[2] - 1.0f }; + float toFocus[3] = { focus[0] - lightPos[0], focus[1] - lightPos[1], focus[2] - lightPos[2] }; + float dist = sqrtf(toFocus[0] * toFocus[0] + toFocus[1] * toFocus[1] + toFocus[2] * toFocus[2]); + if (dist < 1e-3f) + { + // Light sits on top of its focus; aim straight down so the lookAt is well-defined. + toFocus[0] = 0.0f; toFocus[1] = 0.0f; toFocus[2] = -1.0f; + dist = 1.0f; + } + + // Range drives the far plane; if the light has no explicit attenuation range, fall back to the + // light->focus distance so the frustum still covers the lit ground footprint. + const float range = (posRange[3] > 1.0f) ? posRange[3] : dist; + const float zfar = (range > dist) ? range : dist; + const float znear = fmaxf(1.0f, zfar * 0.01f); + + // Wide cone so the beam's local shadow map covers nearby structures/vehicles instead of only the + // death-radius footprint directly under the light. + const float kPointShadowFovDegrees = 130.0f; + + const bgfx::Caps * caps = bgfx::getCaps(); + const float pointShadowSize = static_cast(g_device.pointShadowMapSize); + + const bx::Vec3 eyeV(lightPos[0], lightPos[1], lightPos[2]); + const bx::Vec3 atV(focus[0], focus[1], focus[2]); + // Up vector chosen to avoid degeneracy when the light looks nearly straight down/up. + const float dirZ = toFocus[2] / dist; + const bx::Vec3 up = (fabsf(dirZ) > 0.95f) ? bx::Vec3(0.0f, 1.0f, 0.0f) : bx::Vec3(0.0f, 0.0f, 1.0f); + + if (castsShadow) + { + float lightView[16]; + bx::mtxLookAt(lightView, eyeV, atV, up); + float lightProj[16]; + // bx::mtxProj takes the vertical FOV in DEGREES (it applies toRad internally). + bx::mtxProj(lightProj, kPointShadowFovDegrees, 1.0f, znear, zfar, caps->homogeneousDepth); + bx::mtxMul(g_draw.pointShadowMatrix, lightView, lightProj); + + bgfx::setViewFrameBuffer(kBgfxPointShadowView, g_device.pointShadowFB); + bgfx::setViewRect(kBgfxPointShadowView, 0, 0, + static_cast(g_device.pointShadowMapSize), + static_cast(g_device.pointShadowMapSize)); + bgfx::setViewClear(kBgfxPointShadowView, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0xffffffff, 1.0f, 0); + bgfx::setViewTransform(kBgfxPointShadowView, lightView, lightProj); + } + else + { + // Glow-only light: light/glint surfaces but render no shadow map this frame. + bgfx::setViewFrameBuffer(kBgfxPointShadowView, BGFX_INVALID_HANDLE); + bgfx::setViewClear(kBgfxPointShadowView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + } + + // The dedicated light is applied in fs_uber (objects + terrain) from these uniforms; dynamic + // lights never reach the per-object LightEnvironment. params.x: 1 = light + shadow, 0 = glow + // only (no shadow sampling), -1 = no light. y = bias, z = texel, w = shadow darkening strength. + g_draw.pointShadowParams[0] = castsShadow ? 1.0f : 0.0f; + g_draw.pointShadowParams[1] = diffuseBias[3]; + g_draw.pointShadowParams[2] = (pointShadowSize > 0.0f) ? (1.0f / pointShadowSize) : 0.0f; + g_draw.pointShadowParams[3] = castsShadow ? lightStrength : 0.0f; + + // The dedicated light: world position (xyz) and outer attenuation range, plus diffuse colour. + g_draw.pointShadowLightPos[0] = lightPos[0]; + g_draw.pointShadowLightPos[1] = lightPos[1]; + g_draw.pointShadowLightPos[2] = lightPos[2]; + g_draw.pointShadowLightPos[3] = range; + g_draw.pointShadowLightColor[0] = diffuseBias[0]; + g_draw.pointShadowLightColor[1] = diffuseBias[1]; + g_draw.pointShadowLightColor[2] = diffuseBias[2]; + g_draw.pointShadowLightColor[3] = 1.0f; + g_draw.pointShadowLightValid = true; + + static bool s_loggedPointShadow = false; + if (BgfxDiagVerbose() && !s_loggedPointShadow) + { + std::fprintf(stderr, + "[ggc] point shadow map armed: pos=(%.0f,%.0f,%.0f) range=%.0f near=%.1f far=%.1f bias=%.4f\n", + lightPos[0], lightPos[1], lightPos[2], range, znear, zfar, diffuseBias[1]); + s_loggedPointShadow = true; + } +} + +// TheSuperHackers @feature bobtista 14/07/2026 Arm the second point-shadow view for the +// second-strongest shadow-casting dynamic light (transient lightning-flash pulses near the +// particle-cannon beam, or a nuke coinciding with a beam). Mirrors SetupPointShadowView with +// the slot-2 state; a no-op publishing params.x = -1 when no second caster light exists. +static void SetupPointShadowView2() +{ + g_draw.pointShadow2Params[0] = -1.0f; + + int featureEnabled = 0; +#ifdef RTS_ZEROHOUR + featureEnabled = GGC_GetBgfxDynamicLightShadowsEnabled(); +#endif + if (featureEnabled == 0 || !bgfx::isValid(g_device.pointShadow2FB)) + { + bgfx::setViewFrameBuffer(kBgfxPointShadow2View, BGFX_INVALID_HANDLE); + bgfx::setViewClear(kBgfxPointShadow2View, BGFX_CLEAR_NONE, 0, 1.0f, 0); + return; + } + + float posRange[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float diffuseBias[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float lightStrength = 0.0f; + int hasLight = 0; +#ifdef RTS_ZEROHOUR + hasLight = GGC_GetBgfxPointShadowLight2(posRange, diffuseBias, &lightStrength); +#endif + // The map renders whenever a second caster light exists; lightStrength only controls the + // occlusion-darkening term (0 = adds-only flash pulse, >0 = full beam shadows). + if (hasLight == 0) + { + bgfx::setViewFrameBuffer(kBgfxPointShadow2View, BGFX_INVALID_HANDLE); + bgfx::setViewClear(kBgfxPointShadow2View, BGFX_CLEAR_NONE, 0, 1.0f, 0); + return; + } + + const float lightPos[3] = { posRange[0], posRange[1], posRange[2] }; + const float focus[3] = { lightPos[0], lightPos[1], lightPos[2] - 1.0f }; + float toFocus[3] = { focus[0] - lightPos[0], focus[1] - lightPos[1], focus[2] - lightPos[2] }; + float dist = sqrtf(toFocus[0] * toFocus[0] + toFocus[1] * toFocus[1] + toFocus[2] * toFocus[2]); + if (dist < 1e-3f) + { + toFocus[0] = 0.0f; toFocus[1] = 0.0f; toFocus[2] = -1.0f; + dist = 1.0f; + } + const float range = (posRange[3] > 1.0f) ? posRange[3] : dist; + const float zfar = (range > dist) ? range : dist; + const float znear = fmaxf(1.0f, zfar * 0.01f); + const bgfx::Caps * caps = bgfx::getCaps(); + const bx::Vec3 eyeV(lightPos[0], lightPos[1], lightPos[2]); + const bx::Vec3 atV(focus[0], focus[1], focus[2]); + const float dirZ = toFocus[2] / dist; + const bx::Vec3 up = (fabsf(dirZ) > 0.95f) ? bx::Vec3(0.0f, 1.0f, 0.0f) : bx::Vec3(0.0f, 0.0f, 1.0f); + float lightView[16]; + bx::mtxLookAt(lightView, eyeV, atV, up); + float lightProj[16]; + bx::mtxProj(lightProj, 130.0f, 1.0f, znear, zfar, caps->homogeneousDepth); + bx::mtxMul(g_draw.pointShadow2Matrix, lightView, lightProj); + bgfx::setViewFrameBuffer(kBgfxPointShadow2View, g_device.pointShadow2FB); + bgfx::setViewRect(kBgfxPointShadow2View, 0, 0, + static_cast(g_device.pointShadowMapSize), + static_cast(g_device.pointShadowMapSize)); + bgfx::setViewClear(kBgfxPointShadow2View, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0xffffffff, 1.0f, 0); + bgfx::setViewTransform(kBgfxPointShadow2View, lightView, lightProj); + // Force the clear even when zero casters land in the frustum (matches slot 1's behaviour + // now that view 25 is in the per-frame view order). + bgfx::touch(kBgfxPointShadow2View); + + g_draw.pointShadow2Params[0] = 1.0f; + static int s_slot2Log = 0; + if (BgfxDiagVerbose() && (++s_slot2Log % 30) == 0) + { + std::fprintf(stderr, "[ggc] slot2 armed pos=(%.0f,%.0f,%.0f) range=%.0f strength=%.2f color=(%.2f,%.2f,%.2f)\n", + lightPos[0], lightPos[1], lightPos[2], range, lightStrength, + diffuseBias[0], diffuseBias[1], diffuseBias[2]); + } + g_draw.pointShadow2Params[1] = diffuseBias[3]; + g_draw.pointShadow2Params[2] = (g_device.pointShadowMapSize > 0) + ? (1.0f / static_cast(g_device.pointShadowMapSize)) : 0.0f; + g_draw.pointShadow2Params[3] = lightStrength; + g_draw.pointShadow2LightPos[0] = lightPos[0]; + g_draw.pointShadow2LightPos[1] = lightPos[1]; + g_draw.pointShadow2LightPos[2] = lightPos[2]; + g_draw.pointShadow2LightPos[3] = range; + g_draw.pointShadow2LightColor[0] = diffuseBias[0]; + g_draw.pointShadow2LightColor[1] = diffuseBias[1]; + g_draw.pointShadow2LightColor[2] = diffuseBias[2]; + g_draw.pointShadow2LightColor[3] = 1.0f; +} + +// TheSuperHackers @feature bobtista 14/07/2026 GGC_PCANNON_ENHANCED per-frame lighting drama. +// Runs right after SetupPointShadowView. While a shadow-casting dynamic light is active it +// eases a dim level toward 0.68 and publishes it as a world-space radial falloff centred on +// the light (u_dramaDim): the action around the beam keeps full brightness and the scene +// darkens with distance, instead of a flat global dim that muddied the whole battlefield. +// Eases back to 1.0 when the light goes away. Render-side only. +static void UpdateDramaLighting() +{ + bool dramaEnabled = GgcFlags::Enabled(GgcFlag_PCannonEnhanced); +#ifdef RTS_ZEROHOUR + dramaEnabled = dramaEnabled || (GGC_GetPCannonEnhancedEnabled() != 0); +#endif + static const bool s_drama = dramaEnabled + && !GgcFlags::Enabled(GgcFlag_PCannonNoDim); + if (!s_drama) + { + return; + } + const bool active = g_draw.pointShadowLightValid && (g_draw.pointShadowParams[0] >= 0.5f); + const float target = active ? GGC_GetPCannonDimTarget() : 1.0f; + if (active) + { + // Electric-pattern clock for object receivers (see fs_uber): 2.0 + seconds, wrapping + // at 30s for shader sin-hash precision. 1.0 (the plain default) keeps the pattern off. + g_draw.pointShadowLightColor[3] = 2.0f + static_cast(g_stats.frameIndex % 1800u) * (1.0f / 60.0f); + } + else + { + // Reset when the light dies: SetupPointShadowView's inactive paths never touch this + // field, and a stale clock kept frozen arcs rendering (clipping bright surfaces to + // white) long after the beam ended. + g_draw.pointShadowLightColor[3] = 1.0f; + } + static int s_dimLogCounter = 0; + if (BgfxDiagVerbose() && (++s_dimLogCounter % 120) == 0) + { + std::fprintf(stderr, "[ggc] dramaDim active=%d ease=%.3f params0=%.1f valid=%d\n", + active ? 1 : 0, g_draw.dramaAmbientDim, g_draw.pointShadowParams[0], + g_draw.pointShadowLightValid ? 1 : 0); + } + g_draw.dramaAmbientDim += (target - g_draw.dramaAmbientDim) * 0.02f; + if (g_draw.dramaAmbientDim > 0.999f) + { + g_draw.dramaAmbientDim = 1.0f; + } + if (active) + { + // Snap the primary dim centre to slot 1. When one beam ends and the other promotes from + // slot 2 to slot 1, the second beam already had its own glow pool (fs_uber reads the + // slot-2 light as a second centre), so snapping is seamless - the surviving beam's pool + // is continuous and only the ended beam's pool disappears. Easing here instead created a + // gap while the centre slid across. + g_draw.dramaDim[0] = g_draw.pointShadowLightPos[0]; + g_draw.dramaDim[1] = g_draw.pointShadowLightPos[1]; + } + // Falloff width: the dim pool eases from its floor (at the beam) back to normal over this + // many world units, so the darkening stays local to the beam area, not the whole map. + g_draw.dramaDim[2] = 1.0f / 170.0f; + g_draw.dramaDim[3] = g_draw.dramaAmbientDim; +} + +// TheSuperHackers @feature bobtista 23/06/2026 Debug blit: when GGC_POINT_SHADOW_VIZ is set, +// draw the point shadow map R32F texture into a quarter-width corner quad at the top-left of +// the screen (clear of the bottom command bar) so developers can confirm the shadow map has +// caster depth. Reuses the copyProgram +// (fs_copy) and fullscreenClearVB quad that the smudge path uses, constrained to a sub-rect. +// Reads the env var once and caches it as a static bool. +static void SubmitPointShadowViz() +{ + static const bool s_viz = (GgcFlags::Enabled(GgcFlag_PointShadowViz)); + if (!s_viz) + { + bgfx::touch(kBgfxPointShadowVizView); + return; + } + if (!bgfx::isValid(g_device.pointShadowTex) + || !bgfx::isValid(g_device.copyProgram) + || !bgfx::isValid(g_device.fullscreenClearVB) + || !bgfx::isValid(g_uniforms.sTex0)) + { + bgfx::touch(kBgfxPointShadowVizView); + return; + } + + const uint16_t w = static_cast(g_device.width / 4); + const uint16_t h = static_cast(g_device.height / 4); + const uint16_t x = static_cast(g_device.presentOffsetX); + const uint16_t y = static_cast(g_device.presentOffsetY); + + float identity[16]; + IdentityMatrix(identity); + bgfx::setViewFrameBuffer(kBgfxPointShadowVizView, BGFX_INVALID_HANDLE); + bgfx::setViewClear(kBgfxPointShadowVizView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + bgfx::setViewRect(kBgfxPointShadowVizView, x, y, w, h); + bgfx::setViewTransform(kBgfxPointShadowVizView, identity, identity); + bgfx::setTexture(0, g_uniforms.sTex0, g_device.pointShadowTex, + BGFX_SAMPLER_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + bgfx::setVertexBuffer(0, g_device.fullscreenClearVB); + bgfx::setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_DEPTH_TEST_ALWAYS); + bgfx::submit(kBgfxPointShadowVizView, g_device.copyProgram); +} + +// TheSuperHackers @refactor bobtista 16/04/2026 No aspect correction needed: +// bgfx renders into the game's window and the engine's projection matrix +// already matches the framebuffer aspect. + +// TheSuperHackers @refactor bobtista 11/04/2026 Texture +// capture. Unlike vertex buffers, W3D textures default to POOL_MANAGED, +// which is safe to lock read-only 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. + +} // end anonymous namespace (helpers moved to BgfxBackendTextures.cpp) + +// TheSuperHackers @feature bobtista 17/06/2026 Expose the sun-shadow caster cull sphere. Returns 1 +// when the sun shadow map is armed this frame and fills center3/radius with a world sphere covering +// the cascade region; 0 otherwise. MeshClass::Render keeps meshes intersecting it even when they +// fall outside the camera frustum, so off-screen casters still cast into the visible ground. +extern "C" int GGC_GetBgfxSunShadowCullBox(float * center3, float * radius) +{ + if (center3 != nullptr) + { + center3[0] = s_sunShadowCullCenter[0]; + center3[1] = s_sunShadowCullCenter[1]; + center3[2] = s_sunShadowCullCenter[2]; + } + if (radius != nullptr) + { + *radius = s_sunShadowCullRadius; + } + return s_sunShadowCullActive; +} + +// TheSuperHackers @feature bobtista 17/06/2026 Clamped toward-sun direction (z > 0) for the frame; +// returns the armed flag. The engine projects a caster down-sun with this to keep only casters +// whose shadow reaches the camera view (see RTS3DScene::Visibility_Check). +extern "C" int GGC_GetBgfxSunShadowDir(float * dir3) +{ + if (dir3 != nullptr) + { + dir3[0] = s_sunShadowCullDir[0]; + dir3[1] = s_sunShadowCullDir[1]; + dir3[2] = s_sunShadowCullDir[2]; + } + return s_sunShadowCullActive; +} + + +namespace { // reopen anonymous namespace + +static void DestroySceneFramebuffer() +{ + if (bgfx::isValid(g_device.sceneFB)) + { + bgfx::destroy(g_device.sceneFB); + } + if (bgfx::isValid(g_device.sceneReadableDepthFB)) + { + bgfx::destroy(g_device.sceneReadableDepthFB); + } + if (bgfx::isValid(g_device.sceneSmudgeCopyFB)) + { + bgfx::destroy(g_device.sceneSmudgeCopyFB); + } + if (bgfx::isValid(g_device.sceneSmudgeCopy)) + { + bgfx::destroy(g_device.sceneSmudgeCopy); + } + if (bgfx::isValid(g_device.bloomBrightFB)) + { + bgfx::destroy(g_device.bloomBrightFB); + } + if (bgfx::isValid(g_device.bloomBrightTex)) + { + bgfx::destroy(g_device.bloomBrightTex); + } + if (bgfx::isValid(g_device.bloomBlurFB)) + { + bgfx::destroy(g_device.bloomBlurFB); + } + if (bgfx::isValid(g_device.bloomBlurTex)) + { + bgfx::destroy(g_device.bloomBlurTex); + } + if (bgfx::isValid(g_device.ssaoFB)) + { + bgfx::destroy(g_device.ssaoFB); + } + if (bgfx::isValid(g_device.ssaoTex)) + { + bgfx::destroy(g_device.ssaoTex); + } + if (bgfx::isValid(g_device.ssaoBlurFB)) + { + bgfx::destroy(g_device.ssaoBlurFB); + } + if (bgfx::isValid(g_device.ssaoBlurTex)) + { + bgfx::destroy(g_device.ssaoBlurTex); + } + g_device.bloomBrightFB = BGFX_INVALID_HANDLE; + g_device.bloomBrightTex = BGFX_INVALID_HANDLE; + g_device.bloomBlurFB = BGFX_INVALID_HANDLE; + g_device.bloomBlurTex = BGFX_INVALID_HANDLE; + g_device.ssaoFB = BGFX_INVALID_HANDLE; + g_device.ssaoTex = BGFX_INVALID_HANDLE; + g_device.ssaoBlurFB = BGFX_INVALID_HANDLE; + g_device.ssaoBlurTex = BGFX_INVALID_HANDLE; + if (bgfx::isValid(g_device.shadowMapFB)) + { + bgfx::destroy(g_device.shadowMapFB); + } + g_device.shadowMapFB = BGFX_INVALID_HANDLE; + g_device.shadowMapTex = BGFX_INVALID_HANDLE; + g_device.shadowMapDepth = BGFX_INVALID_HANDLE; + g_device.shadowMapSize = 0; + if (bgfx::isValid(g_device.pointShadowFB)) + { + bgfx::destroy(g_device.pointShadowFB); + } + g_device.pointShadowFB = BGFX_INVALID_HANDLE; + g_device.pointShadowTex = BGFX_INVALID_HANDLE; + g_device.pointShadowMapSize = 0; + if (bgfx::isValid(g_device.pointShadow2FB)) + { + bgfx::destroy(g_device.pointShadow2FB); + } + g_device.pointShadow2FB = BGFX_INVALID_HANDLE; + g_device.pointShadow2Tex = BGFX_INVALID_HANDLE; + g_device.bloomWidth = 0; + g_device.bloomHeight = 0; + g_device.sceneFB = BGFX_INVALID_HANDLE; + g_device.sceneColor = BGFX_INVALID_HANDLE; + g_device.sceneDepth = BGFX_INVALID_HANDLE; + g_device.sceneSmudgeCopy = BGFX_INVALID_HANDLE; + g_device.sceneSmudgeCopyFB = BGFX_INVALID_HANDLE; + g_device.sceneReadableDepthFB = BGFX_INVALID_HANDLE; + g_device.sceneReadableDepth = BGFX_INVALID_HANDLE; + g_device.sceneReadableDepthTest = BGFX_INVALID_HANDLE; + g_device.sceneWidth = 0; + g_device.sceneHeight = 0; +} + +static bool CreateSceneFramebuffer() +{ + DestroySceneFramebuffer(); + + const uint16_t cw = static_cast(g_device.width > 0 ? g_device.width : 1); + const uint16_t ch = static_cast(g_device.height > 0 ? g_device.height : 1); + // TheSuperHackers @feature bobtista 15/06/2026 Supersampling: the scene targets + // are sized at content * render scale and the composite downsamples to native. + const float renderScale = GetSceneRenderScale(); + const uint16_t w = static_cast(static_cast(cw) * renderScale + 0.5f); + const uint16_t h = static_cast(static_cast(ch) * renderScale + 0.5f); + g_device.sceneRenderWidth = w; + g_device.sceneRenderHeight = h; + // TheSuperHackers @feature bobtista 15/06/2026 HDR opt-in: the scene, bloom, + // and smudge-copy targets share one color format. RGBA16F preserves highlights + // above 1.0 for HDR bloom + tonemap; RGBA8 is the default LDR path. + const bgfx::TextureFormat::Enum sceneColorFormat = IsBgfxHdrEnabled() + ? bgfx::TextureFormat::RGBA16F + : bgfx::TextureFormat::RGBA8; + // TheSuperHackers @feature bobtista 15/06/2026 True MSAA on the 3D scene: the + // scene color/depth are multisampled and bgfx auto-resolves the color when the + // composite samples it. RGBA16F (HDR) and MSAA combine. 0 = no MSAA. + const int msaaSamples = GetSceneMsaaSamples(); + uint64_t msaaRtFlag = BGFX_TEXTURE_RT; + if (msaaSamples >= 8) { msaaRtFlag = BGFX_TEXTURE_RT_MSAA_X8; } + else if (msaaSamples >= 4) { msaaRtFlag = BGFX_TEXTURE_RT_MSAA_X4; } + else if (msaaSamples >= 2) { msaaRtFlag = BGFX_TEXTURE_RT_MSAA_X2; } + const uint64_t colorFlags = msaaRtFlag + | BGFX_SAMPLER_POINT + | BGFX_SAMPLER_U_CLAMP + | BGFX_SAMPLER_V_CLAMP; + bgfx::TextureHandle colorTex = bgfx::createTexture2D( + w, h, false, 1, sceneColorFormat, colorFlags); + bgfx::TextureHandle depthTex = bgfx::createTexture2D( + w, h, false, 1, bgfx::TextureFormat::D24S8, msaaRtFlag | BGFX_TEXTURE_RT_WRITE_ONLY); + + bgfx::TextureHandle attachments[2] = { colorTex, depthTex }; + bgfx::FrameBufferHandle fb = bgfx::createFrameBuffer(2, attachments, true); + if (!bgfx::isValid(fb)) + { + if (bgfx::isValid(colorTex)) + { + bgfx::destroy(colorTex); + } + if (bgfx::isValid(depthTex)) + { + bgfx::destroy(depthTex); + } + WWDEBUG_SAY(("[BgfxBackend] Scene framebuffer creation FAILED (%dx%d).", + w, h)); + return false; + } + + g_device.sceneFB = fb; + g_device.sceneColor = colorTex; + g_device.sceneDepth = depthTex; + // TheSuperHackers @tweak bobtista 15/06/2026 The smudge snapshot is now an RT + // single-sample target written by a fullscreen resolve-draw (not bgfx::blit), + // so it works when the scene color is multisampled. + g_device.sceneSmudgeCopy = bgfx::createTexture2D( + w, h, false, 1, sceneColorFormat, + BGFX_TEXTURE_RT + | BGFX_SAMPLER_U_CLAMP + | BGFX_SAMPLER_V_CLAMP); + if (bgfx::isValid(g_device.sceneSmudgeCopy)) + { + g_device.sceneSmudgeCopyFB = bgfx::createFrameBuffer(1, &g_device.sceneSmudgeCopy, false); + } + g_device.sceneWidth = w; + g_device.sceneHeight = h; + bgfx::setName(g_device.sceneColor, "sceneColorRGBA8"); + bgfx::setName(g_device.sceneDepth, "sceneDepthD24S8"); + if (bgfx::isValid(g_device.sceneSmudgeCopy)) + { + bgfx::setName(g_device.sceneSmudgeCopy, "sceneSmudgeCopyRGBA8"); + } + + // TheSuperHackers @feature bobtista 15/06/2026 Half-res bloom ping-pong + // targets. The bright-pass and separable blur run here; the result is added + // back over the scene in the composite. + { + const uint16_t bloomW = static_cast(w > 1 ? w / 2 : 1); + const uint16_t bloomH = static_cast(h > 1 ? h / 2 : 1); + const uint64_t bloomFlags = BGFX_TEXTURE_RT + | BGFX_SAMPLER_U_CLAMP + | BGFX_SAMPLER_V_CLAMP; + g_device.bloomBrightTex = bgfx::createTexture2D( + bloomW, bloomH, false, 1, sceneColorFormat, bloomFlags); + if (bgfx::isValid(g_device.bloomBrightTex)) + { + g_device.bloomBrightFB = bgfx::createFrameBuffer(1, &g_device.bloomBrightTex, false); + bgfx::setName(g_device.bloomBrightTex, "bloomBright"); + } + g_device.bloomBlurTex = bgfx::createTexture2D( + bloomW, bloomH, false, 1, sceneColorFormat, bloomFlags); + if (bgfx::isValid(g_device.bloomBlurTex)) + { + g_device.bloomBlurFB = bgfx::createFrameBuffer(1, &g_device.bloomBlurTex, false); + bgfx::setName(g_device.bloomBlurTex, "bloomBlur"); + } + g_device.bloomWidth = bloomW; + g_device.bloomHeight = bloomH; + } + + // TheSuperHackers @feature bobtista 15/06/2026 Full-res SSAO targets (raw + + // blurred). Only allocated when SSAO is enabled; the AO is multiplied over the + // scene in the composite. + if (IsBgfxSSAOEnabled()) + { + const uint64_t aoFlags = BGFX_TEXTURE_RT + | BGFX_SAMPLER_U_CLAMP + | BGFX_SAMPLER_V_CLAMP; + g_device.ssaoTex = bgfx::createTexture2D(w, h, false, 1, bgfx::TextureFormat::RGBA8, aoFlags); + if (bgfx::isValid(g_device.ssaoTex)) + { + g_device.ssaoFB = bgfx::createFrameBuffer(1, &g_device.ssaoTex, false); + bgfx::setName(g_device.ssaoTex, "ssaoRGBA8"); + } + g_device.ssaoBlurTex = bgfx::createTexture2D(w, h, false, 1, bgfx::TextureFormat::RGBA8, aoFlags); + if (bgfx::isValid(g_device.ssaoBlurTex)) + { + g_device.ssaoBlurFB = bgfx::createFrameBuffer(1, &g_device.ssaoBlurTex, false); + bgfx::setName(g_device.ssaoBlurTex, "ssaoBlurRGBA8"); + } + } + + if (IsReadableSceneDepthEnabled()) + { + // TheSuperHackers @feature bobtista 27/04/2026 Keep the main scene + // D24S8 attachment write-only for stencil shadows, and build a separate + // sampleable R32F depth texture for post effects and soft particles. + // TheSuperHackers @performance bobtista 29/04/2026 Only allocate and + // submit this readable depth path while an effect actively samples it. + const uint64_t readableDepthFlags = BGFX_TEXTURE_RT + | BGFX_SAMPLER_POINT + | BGFX_SAMPLER_U_CLAMP + | BGFX_SAMPLER_V_CLAMP; + bgfx::TextureHandle readableDepthTex = bgfx::createTexture2D( + w, h, false, 1, bgfx::TextureFormat::R32F, readableDepthFlags); + bgfx::TextureHandle readableDepthTest = bgfx::createTexture2D( + w, h, false, 1, bgfx::TextureFormat::D24S8, BGFX_TEXTURE_RT_WRITE_ONLY); + bgfx::FrameBufferHandle depthFB = BGFX_INVALID_HANDLE; + if (bgfx::isValid(readableDepthTex) && bgfx::isValid(readableDepthTest)) + { + bgfx::TextureHandle depthAttachments[2] = { readableDepthTex, readableDepthTest }; + depthFB = bgfx::createFrameBuffer(2, depthAttachments, true); + } + if (bgfx::isValid(depthFB)) + { + g_device.sceneReadableDepthFB = depthFB; + g_device.sceneReadableDepth = readableDepthTex; + g_device.sceneReadableDepthTest = readableDepthTest; + bgfx::setName(g_device.sceneReadableDepth, "sceneReadableDepthR32F"); + bgfx::setName(g_device.sceneReadableDepthTest, "sceneReadableDepthD24S8"); + } + else + { + if (bgfx::isValid(readableDepthTex)) + { + bgfx::destroy(readableDepthTex); + } + if (bgfx::isValid(readableDepthTest)) + { + bgfx::destroy(readableDepthTest); + } + WWDEBUG_SAY(("[BgfxBackend] Readable scene depth creation FAILED (%dx%d).", + w, h)); + } + } + + // TheSuperHackers @feature bobtista 15/06/2026 Sun shadow map: a fixed-size + // R32F depth target (plus its own depth buffer) rendered from the sun's POV. + // TheSuperHackers @bugfix bobtista 16/06/2026 ALWAYS allocate it with the scene + // framebuffer, never gated on the toggle. The shell->game load transition recreates the + // scene framebuffer; if the shadow target is only made when the toggle reads enabled at + // that instant, any transient where it reads disabled (e.g. TheGlobalData mid-load) + // leaves the target gone for the rest of the session — the sun map then renders nothing + // and (because the toggle suppresses the legacy stencil/blob shadows) the scene loses ALL + // shadows after the first frame. The render-time toggle (SetupSunShadowView) decides + // whether to use it; the target itself is cheap (one 2048 R32F + D24S8) and harmless idle. + { + // TheSuperHackers @feature bobtista 17/06/2026 4096 atlas (2048 per cascade tile) halves the + // shadow texel size for crisp shadows when zoomed in; affordable now that the caster set is + // kept tight (only casters whose shadow reaches the view). One 4096 R32F + D24S8 ~= 96MB. + const uint16_t shadowSize = 4096; + const uint64_t shadowColorFlags = BGFX_TEXTURE_RT + | BGFX_SAMPLER_POINT + | BGFX_SAMPLER_U_CLAMP + | BGFX_SAMPLER_V_CLAMP; + bgfx::TextureHandle shadowColor = bgfx::createTexture2D( + shadowSize, shadowSize, false, 1, bgfx::TextureFormat::R32F, shadowColorFlags); + bgfx::TextureHandle shadowDepth = bgfx::createTexture2D( + shadowSize, shadowSize, false, 1, bgfx::TextureFormat::D24S8, BGFX_TEXTURE_RT_WRITE_ONLY); + bgfx::FrameBufferHandle shadowFB = BGFX_INVALID_HANDLE; + if (bgfx::isValid(shadowColor) && bgfx::isValid(shadowDepth)) + { + bgfx::TextureHandle shadowAttachments[2] = { shadowColor, shadowDepth }; + shadowFB = bgfx::createFrameBuffer(2, shadowAttachments, true); + } + if (bgfx::isValid(shadowFB)) + { + g_device.shadowMapFB = shadowFB; + g_device.shadowMapTex = shadowColor; + g_device.shadowMapDepth = shadowDepth; + g_device.shadowMapSize = shadowSize; + bgfx::setName(g_device.shadowMapTex, "sunShadowMapR32F"); + bgfx::setName(g_device.shadowMapDepth, "sunShadowMapD24S8"); + if (BgfxDiagVerbose()) + { + std::fprintf(stderr, "[ggc] sun shadow map target allocated (%ux%u).\n", + shadowSize, shadowSize); + } + } + else + { + if (bgfx::isValid(shadowColor)) + { + bgfx::destroy(shadowColor); + } + if (bgfx::isValid(shadowDepth)) + { + bgfx::destroy(shadowDepth); + } + WWDEBUG_SAY(("[BgfxBackend] Sun shadow map creation FAILED.")); + std::fprintf(stderr, "[ggc] sun shadow map target allocation FAILED.\n"); + } + } + + // TheSuperHackers @feature bobtista 23/06/2026 Point-light shadow map: 1024x1024 perspective + // depth target for the brightest dynamic point light (e.g. nuke fireball). + { + const uint16_t pointShadowSize = 1024; + const uint64_t pointShadowColorFlags = BGFX_TEXTURE_RT + | BGFX_SAMPLER_POINT + | BGFX_SAMPLER_U_CLAMP + | BGFX_SAMPLER_V_CLAMP; + bgfx::TextureHandle pointShadowColor = bgfx::createTexture2D( + pointShadowSize, pointShadowSize, false, 1, bgfx::TextureFormat::R32F, pointShadowColorFlags); + bgfx::TextureHandle pointShadowDepth = bgfx::createTexture2D( + pointShadowSize, pointShadowSize, false, 1, bgfx::TextureFormat::D24S8, BGFX_TEXTURE_RT_WRITE_ONLY); + bgfx::FrameBufferHandle pointShadowFB = BGFX_INVALID_HANDLE; + if (bgfx::isValid(pointShadowColor) && bgfx::isValid(pointShadowDepth)) + { + bgfx::TextureHandle pointShadowAttachments[2] = { pointShadowColor, pointShadowDepth }; + pointShadowFB = bgfx::createFrameBuffer(2, pointShadowAttachments, true); + } + if (bgfx::isValid(pointShadowFB)) + { + g_device.pointShadowFB = pointShadowFB; + g_device.pointShadowTex = pointShadowColor; + g_device.pointShadowMapSize = pointShadowSize; + bgfx::setName(g_device.pointShadowTex, "pointShadowMapR32F"); + bgfx::setName(pointShadowDepth, "pointShadowMapD24S8"); + if (BgfxDiagVerbose()) + { + std::fprintf(stderr, "[ggc] point shadow map target allocated (%ux%u).\n", + pointShadowSize, pointShadowSize); + } + } + else + { + if (bgfx::isValid(pointShadowColor)) + { + bgfx::destroy(pointShadowColor); + } + if (bgfx::isValid(pointShadowDepth)) + { + bgfx::destroy(pointShadowDepth); + } + WWDEBUG_SAY(("[BgfxBackend] Point shadow map creation FAILED.")); + std::fprintf(stderr, "[ggc] point shadow map target allocation FAILED.\n"); + } + + // TheSuperHackers @feature bobtista 14/07/2026 Second point-shadow slot: a transient + // flash light (particle-cannon lightning) or a second simultaneous caster shadows here + // without stealing the primary light's map. + bgfx::TextureHandle pointShadow2Color = bgfx::createTexture2D( + pointShadowSize, pointShadowSize, false, 1, bgfx::TextureFormat::R32F, pointShadowColorFlags); + bgfx::TextureHandle pointShadow2Depth = bgfx::createTexture2D( + pointShadowSize, pointShadowSize, false, 1, bgfx::TextureFormat::D24S8, BGFX_TEXTURE_RT_WRITE_ONLY); + bgfx::FrameBufferHandle pointShadow2FB = BGFX_INVALID_HANDLE; + if (bgfx::isValid(pointShadow2Color) && bgfx::isValid(pointShadow2Depth)) + { + bgfx::TextureHandle pointShadow2Attachments[2] = { pointShadow2Color, pointShadow2Depth }; + pointShadow2FB = bgfx::createFrameBuffer(2, pointShadow2Attachments, true); + } + if (bgfx::isValid(pointShadow2FB)) + { + g_device.pointShadow2FB = pointShadow2FB; + g_device.pointShadow2Tex = pointShadow2Color; + bgfx::setName(g_device.pointShadow2Tex, "pointShadow2MapR32F"); + bgfx::setName(pointShadow2Depth, "pointShadow2MapD24S8"); + } + else + { + if (bgfx::isValid(pointShadow2Color)) + { + bgfx::destroy(pointShadow2Color); + } + if (bgfx::isValid(pointShadow2Depth)) + { + bgfx::destroy(pointShadow2Depth); + } + std::fprintf(stderr, "[ggc] point shadow 2 map target allocation FAILED.\n"); + } + } + + WWDEBUG_SAY(("[BgfxBackend] Scene framebuffer created %dx%d.", w, h)); + return true; +} + +static void ApplySceneFramebufferToViews() +{ + const BgfxDiagnosticFlags diagnostics = GetBgfxDiagnosticFlags(); + const bool useSceneFramebuffer = + !diagnostics.noSceneFramebuffer + && bgfx::isValid(g_device.sceneFB) + && bgfx::isValid(g_device.sceneColor) + && bgfx::isValid(g_device.sceneCompositeProgram); + bgfx::FrameBufferHandle sceneFB = BGFX_INVALID_HANDLE; + if (useSceneFramebuffer) + { + sceneFB = g_device.sceneFB; + } + if (!useSceneFramebuffer) + { + bgfx::setViewFrameBuffer(kBgfxSceneCompositeView, BGFX_INVALID_HANDLE); + } + bgfx::setViewFrameBuffer(kBgfxEngineView, sceneFB); + bgfx::setViewFrameBuffer(kBgfxEngineSortView, sceneFB); + bgfx::setViewFrameBuffer(kBgfxWaterView, sceneFB); + bgfx::setViewFrameBuffer(kBgfxEffectOverlayView, sceneFB); + bgfx::setViewFrameBuffer(kBgfxShadowVolumeView, sceneFB); + bgfx::setViewFrameBuffer(kBgfxShadowApplyView, sceneFB); + bgfx::setViewFrameBuffer(kBgfxShroudOverlayView, sceneFB); + bgfx::setViewFrameBuffer(kBgfxSmudgeCopyView, g_device.sceneSmudgeCopyFB); + bgfx::setViewFrameBuffer(kBgfxSmudgeView, sceneFB); + bgfx::setViewFrameBuffer(kBgfxBloomBrightView, g_device.bloomBrightFB); + bgfx::setViewFrameBuffer(kBgfxBloomBlurHView, g_device.bloomBlurFB); + bgfx::setViewFrameBuffer(kBgfxBloomBlurVView, g_device.bloomBrightFB); + bgfx::setViewFrameBuffer(kBgfxSsaoView, g_device.ssaoFB); + bgfx::setViewFrameBuffer(kBgfxSsaoBlurHView, g_device.ssaoBlurFB); + bgfx::setViewFrameBuffer(kBgfxSsaoBlurVView, g_device.ssaoFB); + bgfx::setViewFrameBuffer(kBgfxSceneCompositeView, BGFX_INVALID_HANDLE); + bgfx::setViewFrameBuffer(kBgfxSceneDepthView, g_device.sceneReadableDepthFB); + bgfx::setViewFrameBuffer(kBgfxUIView, BGFX_INVALID_HANDLE); +} + +// TheSuperHackers @feature bobtista 15/06/2026 SSAO: compute an ambient-occlusion +// factor from the scene depth, then separable-blur it (reusing the bloom blur). +// The result lands in ssaoTex for the composite to multiply over the scene. +static void SubmitSSAO() +{ + const bool ready = IsBgfxSSAOEnabled() + && bgfx::isValid(g_device.ssaoFB) + && bgfx::isValid(g_device.ssaoBlurFB) + && bgfx::isValid(g_device.ssaoTex) + && bgfx::isValid(g_device.ssaoBlurTex) + && bgfx::isValid(g_device.ssaoProgram) + && bgfx::isValid(g_device.bloomBlurProgram) + && bgfx::isValid(g_device.sceneReadableDepth) + && bgfx::isValid(g_uniforms.sSceneDepth) + && bgfx::isValid(g_device.fullscreenClearVB); + if (!ready) + { + bgfx::touch(kBgfxSsaoView); + bgfx::touch(kBgfxSsaoBlurHView); + bgfx::touch(kBgfxSsaoBlurVView); + return; + } + + float identity[16]; + IdentityMatrix(identity); + const uint16_t w = g_device.sceneRenderWidth > 0 ? g_device.sceneRenderWidth : static_cast(g_device.width); + const uint16_t h = g_device.sceneRenderHeight > 0 ? g_device.sceneRenderHeight : static_cast(g_device.height); + const uint64_t sampleFlags = BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP; + const uint64_t fullscreenState = BGFX_STATE_WRITE_RGB + | BGFX_STATE_WRITE_A + | BGFX_STATE_DEPTH_TEST_ALWAYS; + + float invProj[16]; + bx::mtxInverse(invProj, g_frame.cameraProj); + float ssaoParams[4]; + GetSSAOParams(ssaoParams); + const float texel[4] = { 1.0f / static_cast(w), 1.0f / static_cast(h), 0.0f, 0.0f }; + + bgfx::setViewTransform(kBgfxSsaoView, identity, identity); + bgfx::setViewRect(kBgfxSsaoView, 0, 0, w, h); + bgfx::setViewClear(kBgfxSsaoView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + bgfx::setTexture(1, g_uniforms.sSceneDepth, g_device.sceneReadableDepth, sampleFlags); + bgfx::setUniform(g_uniforms.uSsaoInvProj, invProj); + bgfx::setUniform(g_uniforms.uSsaoProj, g_frame.cameraProj); + bgfx::setUniform(g_uniforms.uSsaoParams, ssaoParams); + bgfx::setUniform(g_uniforms.uPostTexelSize, texel); + bgfx::setVertexBuffer(0, g_device.fullscreenClearVB); + bgfx::setState(fullscreenState); + bgfx::submit(kBgfxSsaoView, g_device.ssaoProgram); + + const float dirH[4] = { 1.0f / static_cast(w), 0.0f, 0.0f, 0.0f }; + bgfx::setViewTransform(kBgfxSsaoBlurHView, identity, identity); + bgfx::setViewRect(kBgfxSsaoBlurHView, 0, 0, w, h); + bgfx::setViewClear(kBgfxSsaoBlurHView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + bgfx::setTexture(0, g_uniforms.sTex0, g_device.ssaoTex, sampleFlags); + bgfx::setUniform(g_uniforms.uBloomBlurDir, dirH); + bgfx::setVertexBuffer(0, g_device.fullscreenClearVB); + bgfx::setState(fullscreenState); + bgfx::submit(kBgfxSsaoBlurHView, g_device.bloomBlurProgram); + + const float dirV[4] = { 0.0f, 1.0f / static_cast(h), 0.0f, 0.0f }; + bgfx::setViewTransform(kBgfxSsaoBlurVView, identity, identity); + bgfx::setViewRect(kBgfxSsaoBlurVView, 0, 0, w, h); + bgfx::setViewClear(kBgfxSsaoBlurVView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + bgfx::setTexture(0, g_uniforms.sTex0, g_device.ssaoBlurTex, sampleFlags); + bgfx::setUniform(g_uniforms.uBloomBlurDir, dirV); + bgfx::setVertexBuffer(0, g_device.fullscreenClearVB); + bgfx::setState(fullscreenState); + bgfx::submit(kBgfxSsaoBlurVView, g_device.bloomBlurProgram); +} + +// TheSuperHackers @feature bobtista 15/06/2026 Bloom passes: extract highlights +// above a threshold into a half-res target, separable-blur (H then V), leaving +// the result in bloomBrightTex for the composite to add over the scene. +static void SubmitBloom(const float * bloomParams) +{ + const bool ready = bloomParams[0] > 0.5f + && bgfx::isValid(g_device.bloomBrightFB) + && bgfx::isValid(g_device.bloomBlurFB) + && bgfx::isValid(g_device.bloomBrightProgram) + && bgfx::isValid(g_device.bloomBlurProgram) + && bgfx::isValid(g_device.sceneColor) + && bgfx::isValid(g_device.fullscreenClearVB) + && bgfx::isValid(g_uniforms.sTex0) + && g_device.bloomWidth > 0 + && g_device.bloomHeight > 0; + if (!ready) + { + bgfx::touch(kBgfxBloomBrightView); + bgfx::touch(kBgfxBloomBlurHView); + bgfx::touch(kBgfxBloomBlurVView); + return; + } + + float identity[16]; + IdentityMatrix(identity); + const uint16_t bw = g_device.bloomWidth; + const uint16_t bh = g_device.bloomHeight; + const uint64_t sampleFlags = BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP; + const uint64_t fullscreenState = BGFX_STATE_WRITE_RGB + | BGFX_STATE_WRITE_A + | BGFX_STATE_DEPTH_TEST_ALWAYS; + float bloomUniform[4] = { bloomParams[1], bloomParams[2], 0.0f, 0.0f }; + + bgfx::setViewTransform(kBgfxBloomBrightView, identity, identity); + bgfx::setViewRect(kBgfxBloomBrightView, 0, 0, bw, bh); + bgfx::setViewClear(kBgfxBloomBrightView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + bgfx::setTexture(0, g_uniforms.sTex0, g_device.sceneColor, sampleFlags); + bgfx::setUniform(g_uniforms.uBloomParams, bloomUniform); + bgfx::setVertexBuffer(0, g_device.fullscreenClearVB); + bgfx::setState(fullscreenState); + bgfx::submit(kBgfxBloomBrightView, g_device.bloomBrightProgram); + + const float dirH[4] = { 1.0f / static_cast(bw), 0.0f, 0.0f, 0.0f }; + bgfx::setViewTransform(kBgfxBloomBlurHView, identity, identity); + bgfx::setViewRect(kBgfxBloomBlurHView, 0, 0, bw, bh); + bgfx::setViewClear(kBgfxBloomBlurHView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + bgfx::setTexture(0, g_uniforms.sTex0, g_device.bloomBrightTex, sampleFlags); + bgfx::setUniform(g_uniforms.uBloomBlurDir, dirH); + bgfx::setVertexBuffer(0, g_device.fullscreenClearVB); + bgfx::setState(fullscreenState); + bgfx::submit(kBgfxBloomBlurHView, g_device.bloomBlurProgram); + + const float dirV[4] = { 0.0f, 1.0f / static_cast(bh), 0.0f, 0.0f }; + bgfx::setViewTransform(kBgfxBloomBlurVView, identity, identity); + bgfx::setViewRect(kBgfxBloomBlurVView, 0, 0, bw, bh); + bgfx::setViewClear(kBgfxBloomBlurVView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + bgfx::setTexture(0, g_uniforms.sTex0, g_device.bloomBlurTex, sampleFlags); + bgfx::setUniform(g_uniforms.uBloomBlurDir, dirV); + bgfx::setVertexBuffer(0, g_device.fullscreenClearVB); + bgfx::setState(fullscreenState); + bgfx::submit(kBgfxBloomBlurVView, g_device.bloomBlurProgram); +} + +static void SubmitSceneComposite() +{ + if (GetBgfxDiagnosticFlags().noSceneFramebuffer + || !bgfx::isValid(g_device.sceneFB) + || !bgfx::isValid(g_device.sceneColor) + || !bgfx::isValid(g_device.sceneCompositeProgram) + || !bgfx::isValid(g_device.fullscreenClearVB) + || !bgfx::isValid(g_uniforms.sTex0)) + { + bgfx::touch(kBgfxSceneCompositeView); + return; + } + + float identity[16]; + IdentityMatrix(identity); + bgfx::setViewTransform(kBgfxSceneCompositeView, identity, identity); + bgfx::setViewRect(kBgfxSceneCompositeView, + static_cast(g_device.presentOffsetX), + static_cast(g_device.presentOffsetY), + static_cast(g_device.width), + static_cast(g_device.height)); + // TheSuperHackers @feature bobtista 15/06/2026 Linear (not point) so a + // supersampled scene color downsamples by averaging; identical at scale 1. + bgfx::setTexture(0, g_uniforms.sTex0, g_device.sceneColor, + BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + g_stats.textureBinds++; + if (bgfx::isValid(g_uniforms.sSceneDepth) && bgfx::isValid(g_device.sceneReadableDepth)) + { + bgfx::setTexture(1, g_uniforms.sSceneDepth, g_device.sceneReadableDepth, + BGFX_SAMPLER_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + g_stats.textureBinds++; + } + // TheSuperHackers @feature bobtista 27/04/2026 Post controls come from + // Zero Hour GameData when available; the local defaults are deliberately + // subtle so the scene keeps the original Zero Hour art direction. + float postParams[4]; + GetPostParams(postParams); + // Texel size of the (supersampled) scene color the composite samples. + const float texW = g_device.sceneRenderWidth > 0 ? static_cast(g_device.sceneRenderWidth) : static_cast(g_device.width); + const float texH = g_device.sceneRenderHeight > 0 ? static_cast(g_device.sceneRenderHeight) : static_cast(g_device.height); + const float postTexelSize[4] = { + 1.0f / texW, + 1.0f / texH, + 0.0f, + 0.0f + }; + if (bgfx::isValid(g_uniforms.uPostParams)) + { + bgfx::setUniform(g_uniforms.uPostParams, postParams); + } + if (bgfx::isValid(g_uniforms.uPostTexelSize)) + { + bgfx::setUniform(g_uniforms.uPostTexelSize, postTexelSize); + } + float wipeParams[4]; + GetWipeParams(wipeParams); + if (bgfx::isValid(g_uniforms.uWipeParams)) + { + bgfx::setUniform(g_uniforms.uWipeParams, wipeParams); + } + float colorGradeParams[4]; + GetColorGradeParams(colorGradeParams); + if (bgfx::isValid(g_uniforms.uColorGradeParams)) + { + bgfx::setUniform(g_uniforms.uColorGradeParams, colorGradeParams); + } + float bloomParams[4]; + GetBloomParams(bloomParams); + const float bloomUniform[4] = { + bloomParams[1], + bloomParams[0] > 0.5f ? bloomParams[2] : 0.0f, + 0.0f, + 0.0f + }; + if (bgfx::isValid(g_uniforms.uBloomParams)) + { + bgfx::setUniform(g_uniforms.uBloomParams, bloomUniform); + } + if (bgfx::isValid(g_uniforms.sBloom) && bgfx::isValid(g_device.bloomBrightTex)) + { + bgfx::setTexture(2, g_uniforms.sBloom, g_device.bloomBrightTex, + BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + g_stats.textureBinds++; + } + const float hdrEnabled = IsBgfxHdrEnabled() ? 1.0f : 0.0f; + const bool ssaoApply = IsBgfxSSAOEnabled() && bgfx::isValid(g_device.ssaoTex); + const float hdrParams[4] = { hdrEnabled, ssaoApply ? 1.0f : 0.0f, 0.0f, 0.0f }; + if (bgfx::isValid(g_uniforms.uHdrParams)) + { + bgfx::setUniform(g_uniforms.uHdrParams, hdrParams); + } + if (ssaoApply && bgfx::isValid(g_uniforms.sSsao)) + { + bgfx::setTexture(3, g_uniforms.sSsao, g_device.ssaoTex, + BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + g_stats.textureBinds++; + } + float postFx2[4]; + GetPostFx2Params(postFx2); + postFx2[3] = static_cast(g_stats.frameIndex & 1023); // film-grain time seed + if (bgfx::isValid(g_uniforms.uPostFx2Params)) + { + bgfx::setUniform(g_uniforms.uPostFx2Params, postFx2); + } + static bool s_loggedComposite = false; + if (BgfxDiagVerbose() && !s_loggedComposite) + { + s_loggedComposite = true; + std::fprintf(stderr, + "[ggc] composite post=(%.3f,%.3f,%.3f,%.3f) wipe=(split %.3f, enabled %.1f) grade=(on %.1f, str %.3f, temp %.3f, tint %.3f) bloom=(on %.1f, thr %.3f, int %.3f) hdr=%.1f\n", + postParams[0], postParams[1], postParams[2], postParams[3], + wipeParams[0], wipeParams[1], + colorGradeParams[0], colorGradeParams[1], colorGradeParams[2], colorGradeParams[3], + bloomParams[0], bloomParams[1], bloomParams[2], hdrEnabled); + } + bgfx::setVertexBuffer(0, g_device.fullscreenClearVB); + bgfx::setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A + | BGFX_STATE_DEPTH_TEST_ALWAYS); + bgfx::submit(kBgfxSceneCompositeView, g_device.sceneCompositeProgram); + g_stats.sceneCompositeSubmits++; +} + +} + +void BgfxBackend::Set_Present_Letterbox(bool enabled, float aspectW, float aspectH) +{ + g_device.letterboxRequested = enabled; + if (aspectW > 0.0f && aspectH > 0.0f) + { + g_device.letterboxAspectW = aspectW; + g_device.letterboxAspectH = aspectH; + } + // The per-frame resize check in Begin_Scene recomputes the content rect and rebuilds the scene + // framebuffer + view rects when the layout changes, so no immediate work is needed here. +} + +bool BgfxBackend::Is_Present_Letterbox_Active() const { return g_device.letterboxActive; } +int BgfxBackend::Get_Present_Content_Width() const { return g_device.width; } +int BgfxBackend::Get_Present_Content_Height() const { return g_device.height; } +int BgfxBackend::Get_Present_Offset_X() const { return g_device.letterboxActive ? g_device.presentOffsetX : 0; } +int BgfxBackend::Get_Present_Offset_Y() const { return g_device.letterboxActive ? g_device.presentOffsetY : 0; } + +void BgfxBackend::Initialize(void * hwnd, int /*width*/, int /*height*/) +{ +#if defined(__APPLE__) + if (GgcFlags::Enabled(GgcFlag_Trace)) + { + std::fprintf(stderr, "[ggc] BgfxBackend::Initialize hwnd=%p\n", hwnd); + std::fflush(stderr); + } +#endif + if (g_device.initialized) + { + WWDEBUG_SAY(("[BgfxBackend] Initialize called twice; ignoring.")); + return; + } + + // TheSuperHackers @feature bobtista 16/04/2026 bgfx takes the + // main game window; DX8 moves to a secondary popup for reference. + g_device.window = static_cast(hwnd); + if (g_device.window == nullptr) + { + WWDEBUG_SAY(("[BgfxBackend] hwnd is null. Backend will remain dormant.")); + return; + } + + GetBackendWindowSize(g_device.window, g_device.width, g_device.height); + if (g_device.width <= 0) + { + g_device.width = 800; + } + if (g_device.height <= 0) + { + g_device.height = 600; + } + // Letterbox starts off: swapchain == content, no offset. Begin_Scene reconciles this every frame. + g_device.swapWidth = g_device.width; + g_device.swapHeight = g_device.height; + g_device.presentOffsetX = 0; + g_device.presentOffsetY = 0; + g_device.letterboxActive = false; + WWDEBUG_SAY(("[BgfxBackend] Using main game window %p (%dx%d) for bgfx.", + g_device.window, g_device.width, g_device.height)); + + if (!BgfxUseRenderThread()) + { + // Single-threaded mode (default): this pre-init call tells bgfx::init() not to + // create an internal render thread, so bgfx::frame() runs renderFrame() inline. + bgfx::renderFrame(); + } + else + { + WWDEBUG_SAY(("[BgfxBackend] GGC_BGFX_RENDER_THREAD set: using bgfx internal render thread.")); + } + + bgfx::PlatformData pd; + pd.ndt = GetNativeDisplayHandle(g_device.window); + pd.nwh = GetNativeWindowHandle(g_device.window); + pd.context = nullptr; + pd.backBuffer = nullptr; + pd.backBufferDS = nullptr; + bgfx::setPlatformData(pd); + + bgfx::Init initArgs; + initArgs.type = GetConfiguredRendererType(); + initArgs.callback = &g_bgfxCallback; + initArgs.resolution.width = static_cast(g_device.width); + initArgs.resolution.height = static_cast(g_device.height); + initArgs.resolution.reset = BGFX_RESET_NONE; + { + int msaaLevel = 0; + const char * msaaEnv = GgcFlags::StringValue(GgcFlag_BgfxMsaa); + if (msaaEnv != nullptr) + { + msaaLevel = std::atoi(msaaEnv); + if (msaaLevel <= 0) { msaaLevel = 4; } + } + if (msaaLevel >= 16) { initArgs.resolution.reset |= BGFX_RESET_MSAA_X16; } + else if (msaaLevel >= 8) { initArgs.resolution.reset |= BGFX_RESET_MSAA_X8; } + else if (msaaLevel >= 4) { initArgs.resolution.reset |= BGFX_RESET_MSAA_X4; } + else if (msaaLevel >= 2) { initArgs.resolution.reset |= BGFX_RESET_MSAA_X2; } + g_device.msaaResetFlags = initArgs.resolution.reset & (BGFX_RESET_MSAA_X2 | BGFX_RESET_MSAA_X4 | BGFX_RESET_MSAA_X8 | BGFX_RESET_MSAA_X16); + } + g_device.srgbEnabled = GgcFlags::Enabled(GgcFlag_BgfxSrgb) || GGC_GetBgfxSrgb() != 0; + if (g_device.srgbEnabled) + { + initArgs.resolution.reset |= BGFX_RESET_SRGB_BACKBUFFER; + } + // TheSuperHackers @bugfix bobtista 05/06/2026 Depth-clamp OFF by default. It set + // DX11 DepthClipEnable=FALSE, disabling near-plane clipping, which ballooned + // near-plane-straddling sorted geometry into a fullscreen tint and forced a + // shader guard that killed the Particle Uplink Cannon beam. With clipping + // restored the beam renders and the tint is gone (matches Metal). Opt back in + // via GGC_BGFX_DEPTH_CLAMP=1 if a shadow-volume near/far-clip regression appears. + if (GgcFlags::Enabled(GgcFlag_BgfxDepthClamp)) + { + initArgs.resolution.reset |= BGFX_RESET_DEPTH_CLAMP; + } +#if defined(__APPLE__) + // TheSuperHackers @performance bobtista 04/06/2026 BGFX_RESET_FLUSH_AFTER_RENDER + // serializes Metal command-buffer submission instead of pipelining a frame ahead. + // It was added (30/04/2026) to work around AGX losing internal helper-shader compiles + // when many encoders are in flight. With the render-thread split it instead serializes + // the GPU against the CPU and dominated the frame on heavy scenes (save 69: ~48% faster + // without it). After a multi-scene artifact soak + play-test it is now OFF by default. + // Re-enable with GGC_MACOS_FLUSH=1 if AGX shader-compile artifacts ever reappear. + if (GgcFlags::Enabled(GgcFlag_MacosFlush)) + { + initArgs.resolution.reset |= BGFX_RESET_FLUSH_AFTER_RENDER; + } +#endif + initArgs.platformData = pd; + // TheSuperHackers @bugfix bobtista 27/04/2026 Keep bgfx on a normal + // native device even in game Debug builds. The backend debug layer raises + // DXGI-facility exceptions inside bgfx::frame before the engine can + // reach shellmap or command-line save loads. + // TheSuperHackers @build bobtista 30/04/2026 Allow GGC_BGFX_DEBUG=1 to + // turn on bgfx's verbose diagnostics for macOS bring-up. + initArgs.debug = GgcFlags::Enabled(GgcFlag_BgfxDebug); + +#if defined(__APPLE__) + if (GgcFlags::Enabled(GgcFlag_Trace)) + { + std::fprintf(stderr, "[ggc] calling bgfx::init nwh=%p\n", pd.nwh); + std::fflush(stderr); + } +#endif + + if (!bgfx::init(initArgs)) + { + WWDEBUG_SAY(("[BgfxBackend] bgfx::init FAILED. Backend will remain dormant.")); + g_device.window = nullptr; + return; + } + + g_device.initialized = true; + + static const bool s_exitHandlerRegistered = (std::atexit(MarkExitTeardownActive) == 0); + (void)s_exitHandlerRegistered; + +#if defined(__APPLE__) + if (GgcFlags::Enabled(GgcFlag_Trace)) + { + std::fprintf(stderr, "[ggc] bgfx::init OK\n"); + std::fflush(stderr); + } +#endif + + // 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. + // TheSuperHackers @feature bobtista 27/04/2026 Create the full-canvas + // scene framebuffer. 3D views render here first, then view 9 composites + // the scene to the swapchain before UI draws. + if (!GetBgfxDiagnosticFlags().noSceneFramebuffer) + { + CreateSceneFramebuffer(); + } + else + { + WWDEBUG_SAY(("[BgfxBackend] Diagnostic bgfxNoSceneFramebuffer enabled.")); + } + + // 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 (sentinel). + bgfx::setViewClear(kBgfxDebugView, + BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, + 0x000000ff, // black + 1.0f, + 0); + bgfx::setViewFrameBuffer(kBgfxDebugView, BGFX_INVALID_HANDLE); + // The debug view clears + covers the whole swapchain (the full window), so the letterbox bars + // outside the content rect stay black. + bgfx::setViewRect(kBgfxDebugView, 0, 0, + static_cast(LbSwapWidth()), + static_cast(LbSwapHeight())); + + // 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. + // 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_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(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 + // 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::setViewMode(kBgfxEngineSortView, bgfx::ViewMode::Sequential); + bgfx::setViewRect(kBgfxEngineSortView, 0, 0, + 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 + // backbuffer + depth from earlier views. No clear. + bgfx::setViewClear(kBgfxEffectOverlayView, + BGFX_CLEAR_NONE, + 0x00000000, + 1.0f, + 0); + bgfx::setViewRect(kBgfxEffectOverlayView, 0, 0, + static_cast(g_device.width), + static_cast(g_device.height)); + // TheSuperHackers @bugfix bobtista 17/07/2026 Sequential like the other engine-content + // views: the frame-const uniform guards elide re-uploads assuming playback order equals + // submission order within a view, and translucent effect draws must play back in engine + // order anyway. Default mode reorders by sort key, which both broke the guard invariant + // and could reorder blended dazzle draws. + bgfx::setViewMode(kBgfxEffectOverlayView, bgfx::ViewMode::Sequential); + // Same invariant for the render-to-texture view: its framebuffer/rect are bound + // dynamically per capture, but the playback-order guarantee is established here. + bgfx::setViewMode(kBgfxRTTView, bgfx::ViewMode::Sequential); + { + float identityMtx[16]; + IdentityMatrix(identityMtx); + bgfx::setViewTransform(kBgfxEffectOverlayView, identityMtx, identityMtx); + } + + // Shadow-volume view. Sequential so the two-pass algorithm + // (front INCR / back DECR) runs in submit order. Clear stencil here, + // on the same view that writes/tests it; clearing only the earlier + // engine view does not establish the Metal stencil attachment for this + // pass reliably. View transform is pushed per-frame from the engine + // camera via the dirty-flag logic alongside view 1. + bgfx::setViewClear(kBgfxShadowVolumeView, BGFX_CLEAR_STENCIL, 0, 1.0f, 0); + bgfx::setViewRect(kBgfxShadowVolumeView, 0, 0, + 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_device.width), + static_cast(g_device.height)); + bgfx::setViewMode(kBgfxShadowApplyView, bgfx::ViewMode::Sequential); + { + float identityMtx[16]; + IdentityMatrix(identityMtx); + bgfx::setViewTransform(kBgfxShadowApplyView, identityMtx, identityMtx); + } + + bgfx::setViewClear(kBgfxShroudOverlayView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + bgfx::setViewRect(kBgfxShroudOverlayView, 0, 0, + static_cast(g_device.width), + static_cast(g_device.height)); + bgfx::setViewMode(kBgfxShroudOverlayView, bgfx::ViewMode::Sequential); + + bgfx::setViewClear(kBgfxSceneDepthView, + BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, + 0xffffffffu, + 1.0f, + 0); + bgfx::setViewRect(kBgfxSceneDepthView, 0, 0, + static_cast(g_device.width), + static_cast(g_device.height)); + bgfx::setViewMode(kBgfxSceneDepthView, bgfx::ViewMode::Default); + + bgfx::setViewClear(kBgfxSceneCompositeView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + bgfx::setViewRect(kBgfxSceneCompositeView, + static_cast(g_device.presentOffsetX), + static_cast(g_device.presentOffsetY), + static_cast(g_device.width), + static_cast(g_device.height)); + bgfx::setViewMode(kBgfxSceneCompositeView, bgfx::ViewMode::Sequential); + bgfx::setViewClear(kBgfxSmudgeCopyView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + bgfx::setViewRect(kBgfxSmudgeCopyView, 0, 0, + static_cast(g_device.width), + static_cast(g_device.height)); + bgfx::setViewMode(kBgfxSmudgeCopyView, bgfx::ViewMode::Sequential); + bgfx::setViewClear(kBgfxSmudgeView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + bgfx::setViewRect(kBgfxSmudgeView, 0, 0, + static_cast(g_device.width), + static_cast(g_device.height)); + bgfx::setViewMode(kBgfxSmudgeView, bgfx::ViewMode::Sequential); + { + float identityMtx[16]; + IdentityMatrix(identityMtx); + bgfx::setViewTransform(kBgfxSceneCompositeView, identityMtx, identityMtx); + bgfx::setViewTransform(kBgfxSmudgeCopyView, identityMtx, identityMtx); + bgfx::setViewTransform(kBgfxSmudgeView, 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, + static_cast(g_device.presentOffsetX), + static_cast(g_device.presentOffsetY), + static_cast(g_device.width), + static_cast(g_device.height)); + bgfx::setViewMode(kBgfxUIView, bgfx::ViewMode::Sequential); + { + float identityMtx[16]; + IdentityMatrix(identityMtx); + bgfx::setViewTransform(kBgfxUIView, identityMtx, identityMtx); + } + ApplySceneFramebufferToViews(); + + // 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_frame.world); + IdentityMatrix(g_frame.view); + IdentityMatrix(g_frame.proj); + IdentityMatrix(g_frame.sortWorld); + IdentityMatrix(g_frame.sortViewOnly); + CacheIdentityTransform(RB_TRANSFORM_WORLD); + CacheIdentityTransform(RB_TRANSFORM_VIEW); + CacheIdentityTransform(RB_TRANSFORM_PROJECTION); + 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 + // in Set_Projection_Transform_With_Z_Bias whenever it changes. + { + float identityView[16]; + IdentityMatrix(identityView); + 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_device.triangleLayout + .begin() + .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true) + .end(); + + BuildStandardVertexLayouts(); + + g_device.passthroughProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_passthrough), sizeof(GGC_BGFX_SHADER(vs_passthrough)), "vs_passthrough", + GGC_BGFX_SHADER(fs_passthrough), sizeof(GGC_BGFX_SHADER(fs_passthrough)), "fs_passthrough"); + + g_device.sceneCompositeProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_scene_composite), sizeof(GGC_BGFX_SHADER(vs_scene_composite)), "vs_scene_composite", + GGC_BGFX_SHADER(fs_scene_composite), sizeof(GGC_BGFX_SHADER(fs_scene_composite)), "fs_scene_composite"); + g_device.bloomBrightProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_scene_composite), sizeof(GGC_BGFX_SHADER(vs_scene_composite)), "vs_scene_composite", + GGC_BGFX_SHADER(fs_bloom_bright), sizeof(GGC_BGFX_SHADER(fs_bloom_bright)), "fs_bloom_bright"); + g_device.bloomBlurProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_scene_composite), sizeof(GGC_BGFX_SHADER(vs_scene_composite)), "vs_scene_composite", + GGC_BGFX_SHADER(fs_bloom_blur), sizeof(GGC_BGFX_SHADER(fs_bloom_blur)), "fs_bloom_blur"); + g_device.ssaoProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_scene_composite), sizeof(GGC_BGFX_SHADER(vs_scene_composite)), "vs_scene_composite", + GGC_BGFX_SHADER(fs_ssao), sizeof(GGC_BGFX_SHADER(fs_ssao)), "fs_ssao"); + g_device.copyProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_scene_composite), sizeof(GGC_BGFX_SHADER(vs_scene_composite)), "vs_scene_composite", + GGC_BGFX_SHADER(fs_copy), sizeof(GGC_BGFX_SHADER(fs_copy)), "fs_copy"); + g_device.sceneDepthProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_scene_depth), sizeof(GGC_BGFX_SHADER(vs_scene_depth)), "vs_scene_depth", + GGC_BGFX_SHADER(fs_scene_depth), sizeof(GGC_BGFX_SHADER(fs_scene_depth)), "fs_scene_depth"); + g_device.sceneDepthInstancedProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_scene_depth_instanced), sizeof(GGC_BGFX_SHADER(vs_scene_depth_instanced)), "vs_scene_depth_instanced", + GGC_BGFX_SHADER(fs_scene_depth), sizeof(GGC_BGFX_SHADER(fs_scene_depth)), "fs_scene_depth"); + g_device.shadowCasterProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_shadow_caster), sizeof(GGC_BGFX_SHADER(vs_shadow_caster)), "vs_shadow_caster", + GGC_BGFX_SHADER(fs_shadow_caster), sizeof(GGC_BGFX_SHADER(fs_shadow_caster)), "fs_shadow_caster"); + g_device.shadowCasterInstancedProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_shadow_caster_instanced), sizeof(GGC_BGFX_SHADER(vs_shadow_caster_instanced)), "vs_shadow_caster_instanced", + GGC_BGFX_SHADER(fs_shadow_caster), sizeof(GGC_BGFX_SHADER(fs_shadow_caster)), "fs_shadow_caster"); + g_device.smudgeProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_smudge), sizeof(GGC_BGFX_SHADER(vs_smudge)), "vs_smudge", + GGC_BGFX_SHADER(fs_smudge), sizeof(GGC_BGFX_SHADER(fs_smudge)), "fs_smudge"); + ApplySceneFramebufferToViews(); + + // 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_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.sSceneDepth = bgfx::createUniform("s_sceneDepth", bgfx::UniformType::Sampler); + g_uniforms.sTexArray = bgfx::createUniform("s_texArray", bgfx::UniformType::Sampler); + // TheSuperHackers @performance bobtista 15/06/2026 Single packed material array + // uploaded once per draw (see UploadMaterialUniforms_Body / MaterialUniformSlot). + g_uniforms.uMaterial = bgfx::createUniform("u_material", bgfx::UniformType::Vec4, MU_COUNT); + g_uniforms.uMatDiffuse = bgfx::createUniform("u_matDiffuse", bgfx::UniformType::Vec4); + g_uniforms.uMatAmbient = bgfx::createUniform("u_matAmbient", bgfx::UniformType::Vec4); + g_uniforms.uMatEmissive = bgfx::createUniform("u_matEmissive", bgfx::UniformType::Vec4); + g_uniforms.uMatSpecular = bgfx::createUniform("u_matSpecular", bgfx::UniformType::Vec4); + g_uniforms.uMatFx = bgfx::createUniform("u_matFx", bgfx::UniformType::Vec4); + g_uniforms.uEyePos = bgfx::createUniform("u_eyePos", bgfx::UniformType::Vec4); + // TheSuperHackers @performance bobtista The single camera-fit sun shadow uses only + // matrix [0]; the old [3]-cascade tail ([1]/[2]) was dead weight in every draw's uniform + // buffer. Upload just the one matrix the shader reads. + g_uniforms.uShadowMatrices = bgfx::createUniform("u_shadowMatrices", bgfx::UniformType::Mat4, 1); + g_uniforms.uShadowParams = bgfx::createUniform("u_shadowParams", bgfx::UniformType::Vec4); + g_uniforms.uShadowQuality = bgfx::createUniform("u_shadowQuality", bgfx::UniformType::Vec4); + g_uniforms.uSunShadowReceive = bgfx::createUniform("u_sunShadowReceive", bgfx::UniformType::Vec4); + g_uniforms.sShadowMap = bgfx::createUniform("s_shadowMap", bgfx::UniformType::Sampler); + g_uniforms.uPointShadowMatrix = bgfx::createUniform("u_pointShadowMatrix", bgfx::UniformType::Mat4); + g_uniforms.uPointShadowParams = bgfx::createUniform("u_pointShadowParams", bgfx::UniformType::Vec4); + g_uniforms.uPointShadowLightPos = bgfx::createUniform("u_pointShadowLightPos", bgfx::UniformType::Vec4); + g_uniforms.uPointShadowLightColor = bgfx::createUniform("u_pointShadowLightColor", bgfx::UniformType::Vec4); + g_uniforms.sPointShadowMap = bgfx::createUniform("s_pointShadowMap", bgfx::UniformType::Sampler); + g_uniforms.uPointShadow2Matrix = bgfx::createUniform("u_pointShadow2Matrix", bgfx::UniformType::Mat4); + g_uniforms.uPointShadow2Params = bgfx::createUniform("u_pointShadow2Params", bgfx::UniformType::Vec4); + g_uniforms.uPointShadow2LightPos = bgfx::createUniform("u_pointShadow2LightPos", bgfx::UniformType::Vec4); + g_uniforms.uPointShadow2LightColor = bgfx::createUniform("u_pointShadow2LightColor", bgfx::UniformType::Vec4); + g_uniforms.sPointShadowMap2 = bgfx::createUniform("s_pointShadowMap2", bgfx::UniformType::Sampler); + g_uniforms.uDramaDim = bgfx::createUniform("u_dramaDim", 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.uLightAmbients = bgfx::createUniform("u_lightAmbients", bgfx::UniformType::Vec4, 4); + g_uniforms.uLightPositions = bgfx::createUniform("u_lightPositions", bgfx::UniformType::Vec4, 4); + g_uniforms.uLightParams = bgfx::createUniform("u_lightParams", 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.uTexcoordSelect2 = bgfx::createUniform("u_texcoordSelect2", bgfx::UniformType::Vec4); + g_uniforms.uProjectedDecalMode = bgfx::createUniform("u_projectedDecalMode", bgfx::UniformType::Vec4); + g_uniforms.uTexcoordSource = bgfx::createUniform("u_texcoordSource", bgfx::UniformType::Vec4); + g_uniforms.uVertexColorFlags = bgfx::createUniform("u_vertexColorFlags", bgfx::UniformType::Vec4); + g_uniforms.uGrayscaleEnable = bgfx::createUniform("u_grayscaleEnable", bgfx::UniformType::Vec4); + g_uniforms.uObjectShroudDim = bgfx::createUniform("u_objectShroudDim", 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.uTexTransform0 = bgfx::createUniform("u_texTransform0", bgfx::UniformType::Vec4); + g_uniforms.uTexTransform1 = bgfx::createUniform("u_texTransform1", bgfx::UniformType::Vec4); + g_uniforms.uTexTransform0Z = bgfx::createUniform("u_texTransform0Z", bgfx::UniformType::Vec4); + g_uniforms.uTex1Transform0 = bgfx::createUniform("u_tex1Transform0", bgfx::UniformType::Vec4); + g_uniforms.uTex1Transform1 = bgfx::createUniform("u_tex1Transform1", bgfx::UniformType::Vec4); + g_uniforms.uTex1TransformZ = bgfx::createUniform("u_tex1TransformZ", bgfx::UniformType::Vec4); + g_uniforms.uTex2Transform0 = bgfx::createUniform("u_tex2Transform0", bgfx::UniformType::Vec4); + g_uniforms.uTex2Transform1 = bgfx::createUniform("u_tex2Transform1", bgfx::UniformType::Vec4); + g_uniforms.uTexProjected = bgfx::createUniform("u_texProjected", bgfx::UniformType::Vec4); + g_uniforms.uLegacyPixelShaderMode = bgfx::createUniform("u_legacyPixelShaderMode", bgfx::UniformType::Vec4); + g_uniforms.uZBias = bgfx::createUniform("u_zBias", bgfx::UniformType::Vec4); + g_uniforms.sCloudMap = bgfx::createUniform("s_cloudMap", bgfx::UniformType::Sampler); + g_uniforms.sLightMap = bgfx::createUniform("s_lightMap", 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_device.defaultWhiteTexture = bgfx::createTexture2D( + 1, 1, false, 1, + bgfx::TextureFormat::RGBA8, + BGFX_TEXTURE_NONE | BGFX_SAMPLER_POINT, + bgfx::copy(kWhitePixel, sizeof(kWhitePixel))); + // Transparent fallback for missing blended textures. The legacy missing + // texture is useful on opaque geometry, but particle/effect draws can + // amplify it into large black/magenta quads. + static const uint8_t kTransparentPixel[4] = { 0x00, 0x00, 0x00, 0x00 }; + g_device.defaultTransparentTexture = bgfx::createTexture2D( + 1, 1, false, 1, + bgfx::TextureFormat::RGBA8, + BGFX_TEXTURE_NONE | BGFX_SAMPLER_POINT, + bgfx::copy(kTransparentPixel, sizeof(kTransparentPixel))); + + g_device.uberProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_uber), sizeof(GGC_BGFX_SHADER(vs_uber)), "vs_uber", + GGC_BGFX_SHADER(fs_uber), sizeof(GGC_BGFX_SHADER(fs_uber)), "fs_uber"); + + g_device.uberInstancedProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_uber_instanced), sizeof(GGC_BGFX_SHADER(vs_uber_instanced)), "vs_uber_instanced", + GGC_BGFX_SHADER(fs_uber), sizeof(GGC_BGFX_SHADER(fs_uber)), "fs_uber"); + + g_device.sortedArrayProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_uber_array), sizeof(GGC_BGFX_SHADER(vs_uber_array)), "vs_uber_array", + GGC_BGFX_SHADER(fs_uber_array), sizeof(GGC_BGFX_SHADER(fs_uber_array)), "fs_uber_array"); + + // TheSuperHackers @performance bobtista Frame-constant uber variant: same vs_uber, but the + // fragment shader reads the global per-frame constants (sun/point shadow + scene ambient) + // from frameConstTexture rather than per-draw uniforms, shrinking the fs_uber constant + // buffer by ~352B/draw so heavy scenes stay under bgfx's fixed 8MB Metal uniform arena. + g_device.uberFrameConstProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_uber), sizeof(GGC_BGFX_SHADER(vs_uber)), "vs_uber", + GGC_BGFX_SHADER(fs_uber_frameconst), sizeof(GGC_BGFX_SHADER(fs_uber_frameconst)), "fs_uber_frameconst"); + g_uniforms.sFrameConst = bgfx::createUniform("s_frameConst", bgfx::UniformType::Sampler); + g_device.frameConstTexture = bgfx::createTexture2D( + kFrameConstTexels, 1, false, 1, + bgfx::TextureFormat::RGBA32F, + BGFX_TEXTURE_NONE | BGFX_SAMPLER_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + + g_device.treeProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_trees), sizeof(GGC_BGFX_SHADER(vs_trees)), "vs_trees", + GGC_BGFX_SHADER(fs_uber), sizeof(GGC_BGFX_SHADER(fs_uber)), "fs_uber"); + + g_device.shadowVolumeProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_shadow_volume), sizeof(GGC_BGFX_SHADER(vs_shadow_volume)), "vs_shadow_volume", + GGC_BGFX_SHADER(fs_shadow_volume), sizeof(GGC_BGFX_SHADER(fs_shadow_volume)), "fs_shadow_volume"); + + g_device.shadowApplyProgram = CreateShaderProgram( + GGC_BGFX_SHADER(vs_shadow_apply), sizeof(GGC_BGFX_SHADER(vs_shadow_apply)), "vs_shadow_apply", + GGC_BGFX_SHADER(fs_shadow_apply), sizeof(GGC_BGFX_SHADER(fs_shadow_apply)), "fs_shadow_apply"); + g_uniforms.uShadowColor = bgfx::createUniform("u_shadowColor", bgfx::UniformType::Vec4); + g_uniforms.uShadowBias = bgfx::createUniform("u_shadowBias", bgfx::UniformType::Vec4); + g_uniforms.uPostParams = bgfx::createUniform("u_postParams", bgfx::UniformType::Vec4); + g_uniforms.uPostTexelSize = bgfx::createUniform("u_postTexelSize", bgfx::UniformType::Vec4); + g_uniforms.uSmudgeClip = bgfx::createUniform("u_smudgeClip", bgfx::UniformType::Vec4); + g_uniforms.uWipeParams = bgfx::createUniform("u_wipeParams", bgfx::UniformType::Vec4); + g_uniforms.uColorGradeParams = bgfx::createUniform("u_colorGradeParams", bgfx::UniformType::Vec4); + g_uniforms.uBloomParams = bgfx::createUniform("u_bloomParams", bgfx::UniformType::Vec4); + g_uniforms.uBloomBlurDir = bgfx::createUniform("u_bloomBlurDir", bgfx::UniformType::Vec4); + g_uniforms.sBloom = bgfx::createUniform("s_bloom", bgfx::UniformType::Sampler); + g_uniforms.uHdrParams = bgfx::createUniform("u_hdrParams", bgfx::UniformType::Vec4); + g_uniforms.uPostFx2Params = bgfx::createUniform("u_postFx2Params", bgfx::UniformType::Vec4); + g_uniforms.uSsaoParams = bgfx::createUniform("u_ssaoParams", bgfx::UniformType::Vec4); + g_uniforms.uSsaoInvProj = bgfx::createUniform("u_ssaoInvProj", bgfx::UniformType::Mat4); + g_uniforms.uSsaoProj = bgfx::createUniform("u_ssaoProj", bgfx::UniformType::Mat4); + g_uniforms.sSsao = bgfx::createUniform("s_ssao", bgfx::UniformType::Sampler); + g_uniforms.uSoftParticleParams = bgfx::createUniform("u_softParticleParams", bgfx::UniformType::Vec4); + + // Keep view order explicit. Stencil shadow volumes, sorted decals/effects, + // scene-depth copies, post effects, and UI all depend on stable ordering. + const bgfx::ViewId order[] = { + kBgfxDebugView, + kBgfxRTTView, + kBgfxShadowMapView, // 20 — sun shadow cascades must + static_cast(kBgfxShadowMapView + 1), // 21 render BEFORE the engine + static_cast(kBgfxShadowMapView + 2), // 22 view that samples them + kBgfxPointShadowView, // 23 — point-light shadow must also + kBgfxPointShadow2View, // 25 — second point-light shadow slot + kBgfxEngineView, + kBgfxSceneDepthView, + kBgfxShadowVolumeView, + kBgfxShadowApplyView, + kBgfxWaterView, + kBgfxEngineSortView, + kBgfxEffectOverlayView, + kBgfxShroudOverlayView, + kBgfxSmudgeCopyView, + kBgfxSmudgeView, + kBgfxBloomBrightView, + kBgfxBloomBlurHView, + kBgfxBloomBlurVView, + kBgfxSsaoView, + kBgfxSsaoBlurHView, + kBgfxSsaoBlurVView, + kBgfxSceneCompositeView, + kBgfxPointShadowVizView, // 24 — GGC_POINT_SHADOW_VIZ debug blit + kBgfxUIView, + }; + bgfx::setViewOrder(kBgfxDebugView, BX_COUNTOF(order), order); + + 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_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] & + 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)); + + // TheSuperHackers @refactor bobtista 16/04/2026 Single-window build: there is + // no secondary reference window to create or move here. + +} + +template +static void DestroyBgfxHandle(H & h) +{ + if (bgfx::isValid(h)) + { + bgfx::destroy(h); + h = BGFX_INVALID_HANDLE; + } +} + +void BgfxBackend::Shutdown() +{ + if (GgcFlags::Enabled(GgcFlag_BgfxPerfLog)) + { + PerfSessionPrintSummary(); + } + + if (g_device.initialized) + { + // TheSuperHackers @bugfix bobtista 02/06/2026 Unbind every view's framebuffer and pump + // empty frames before destroying resources: retires in-flight command-buffer references + // and flushes a partial frame that Metal would otherwise assert on at shutdown. + const bgfx::ViewId kBgfxMaxViewId = 15; + for (bgfx::ViewId v = 0; v <= kBgfxMaxViewId; ++v) + { + bgfx::setViewFrameBuffer(v, BGFX_INVALID_HANDLE); + } + const int kShutdownDrainFrames = 4; + for (int prep = 0; prep < kShutdownDrainFrames; ++prep) + { + bgfx::frame(); + } + + DestroyBgfxHandle(g_device.passthroughProgram); + DestroyBgfxHandle(g_device.sceneCompositeProgram); + DestroyBgfxHandle(g_device.bloomBrightProgram); + DestroyBgfxHandle(g_device.bloomBlurProgram); + DestroyBgfxHandle(g_device.ssaoProgram); + DestroyBgfxHandle(g_device.copyProgram); + DestroyBgfxHandle(g_device.sceneDepthProgram); + DestroyBgfxHandle(g_device.shadowCasterProgram); + DestroyBgfxHandle(g_device.smudgeProgram); + DestroyBgfxHandle(g_device.fullscreenClearVB); + DestroyBgfxHandle(g_device.uberProgram); + DestroyBgfxHandle(g_device.uberFrameConstProgram); + DestroyBgfxHandle(g_device.frameConstTexture); + DestroyBgfxHandle(g_device.sortedArrayProgram); + DestroyBgfxHandle(g_device.uberInstancedProgram); + 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.sSceneDepth); + DestroyBgfxHandle(g_uniforms.sTexArray); + DestroyBgfxHandle(g_uniforms.sFrameConst); + BgfxSortedTextureArrayShutdown(); + DestroyBgfxHandle(g_uniforms.uMatDiffuse); + DestroyBgfxHandle(g_uniforms.uMatAmbient); + DestroyBgfxHandle(g_uniforms.uAtestParams); + DestroyBgfxHandle(g_uniforms.uTssOps0); + DestroyBgfxHandle(g_uniforms.uTssOps1); + DestroyBgfxHandle(g_uniforms.uLightDirs); + DestroyBgfxHandle(g_uniforms.uLightColors); + DestroyBgfxHandle(g_uniforms.uLightAmbients); + DestroyBgfxHandle(g_uniforms.uLightPositions); + DestroyBgfxHandle(g_uniforms.uLightParams); + DestroyBgfxHandle(g_uniforms.uSceneAmbient); + DestroyBgfxHandle(g_uniforms.uLightingEnabled); + DestroyBgfxHandle(g_uniforms.uTexcoordSelect); + DestroyBgfxHandle(g_uniforms.uTexcoordSelect2); + DestroyBgfxHandle(g_uniforms.uProjectedDecalMode); + DestroyBgfxHandle(g_uniforms.uTexcoordSource); + DestroyBgfxHandle(g_uniforms.uVertexColorFlags); + DestroyBgfxHandle(g_uniforms.uObjectShroudDim); + DestroyBgfxHandle(g_uniforms.uShroudParams); + DestroyBgfxHandle(g_uniforms.uCloudParams); + DestroyBgfxHandle(g_uniforms.uTexTransform0); + DestroyBgfxHandle(g_uniforms.uTexTransform1); + DestroyBgfxHandle(g_uniforms.uTexTransform0Z); + DestroyBgfxHandle(g_uniforms.uTex1Transform0); + DestroyBgfxHandle(g_uniforms.uTex1Transform1); + DestroyBgfxHandle(g_uniforms.uTex1TransformZ); + DestroyBgfxHandle(g_uniforms.uTex2Transform0); + DestroyBgfxHandle(g_uniforms.uTex2Transform1); + DestroyBgfxHandle(g_uniforms.uTexProjected); + DestroyBgfxHandle(g_uniforms.uLegacyPixelShaderMode); + DestroyBgfxHandle(g_uniforms.uZBias); + DestroyBgfxHandle(g_uniforms.sCloudMap); + DestroyBgfxHandle(g_uniforms.sLightMap); + DestroyBgfxHandle(g_device.shadowVolumeProgram); + DestroyBgfxHandle(g_device.shadowApplyProgram); + DestroySceneFramebuffer(); + DestroyBgfxHandle(g_uniforms.uShadowColor); + DestroyBgfxHandle(g_uniforms.uPostParams); + DestroyBgfxHandle(g_uniforms.uPostTexelSize); + DestroyBgfxHandle(g_uniforms.uSmudgeClip); + DestroyBgfxHandle(g_uniforms.uWipeParams); + DestroyBgfxHandle(g_uniforms.uColorGradeParams); + DestroyBgfxHandle(g_uniforms.uBloomParams); + DestroyBgfxHandle(g_uniforms.uBloomBlurDir); + DestroyBgfxHandle(g_uniforms.sBloom); + DestroyBgfxHandle(g_uniforms.uHdrParams); + DestroyBgfxHandle(g_uniforms.uPostFx2Params); + DestroyBgfxHandle(g_uniforms.uSsaoParams); + DestroyBgfxHandle(g_uniforms.uSsaoInvProj); + DestroyBgfxHandle(g_uniforms.uSsaoProj); + DestroyBgfxHandle(g_uniforms.sSsao); + DestroyBgfxHandle(g_uniforms.uSoftParticleParams); + DestroyBgfxHandle(g_uniforms.uShadowBias); + DestroyBgfxHandle(g_uniforms.uMatEmissive); + DestroyBgfxHandle(g_uniforms.uMatSpecular); + DestroyBgfxHandle(g_uniforms.uMatFx); + DestroyBgfxHandle(g_uniforms.uEyePos); + DestroyBgfxHandle(g_uniforms.uShadowMatrices); + DestroyBgfxHandle(g_uniforms.uShadowParams); + DestroyBgfxHandle(g_uniforms.uShadowQuality); + DestroyBgfxHandle(g_uniforms.uSunShadowReceive); + DestroyBgfxHandle(g_uniforms.sShadowMap); + DestroyBgfxHandle(g_uniforms.uPointShadowMatrix); + DestroyBgfxHandle(g_uniforms.uPointShadowParams); + DestroyBgfxHandle(g_uniforms.uPointShadowLightPos); + DestroyBgfxHandle(g_uniforms.uPointShadowLightColor); + DestroyBgfxHandle(g_uniforms.sPointShadowMap); + DestroyBgfxHandle(g_uniforms.uPointShadow2Matrix); + DestroyBgfxHandle(g_uniforms.uPointShadow2Params); + DestroyBgfxHandle(g_uniforms.uPointShadow2LightPos); + DestroyBgfxHandle(g_uniforms.uPointShadow2LightColor); + DestroyBgfxHandle(g_uniforms.sPointShadowMap2); + DestroyBgfxHandle(g_uniforms.uDramaDim); + 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_caches.framebuffer.clear(); + // Cached engine buffers. Destroy before bgfx::shutdown + // so the handles outlive nothing. + for (auto & kv : g_caches.vb) + { + if (bgfx::isValid(kv.second.handle)) + { + bgfx::destroy(kv.second.handle); + } + } + g_caches.vb.clear(); + g_caches.pendingVbRangeUploads.clear(); + for (auto & kv : g_caches.ib) + { + if (bgfx::isValid(kv.second.handle)) + { + bgfx::destroy(kv.second.handle); + } + } + g_caches.ib.clear(); + g_caches.pendingIbRangeUploads.clear(); + for (auto & kv : g_caches.texture) + { + if (bgfx::isValid(kv.second)) + { + bgfx::destroy(kv.second); + } + } + g_caches.texture.clear(); + for (auto & kv : g_caches.textureBaseMip) + { + if (bgfx::isValid(kv.second)) + { + bgfx::destroy(kv.second); + } + } + g_caches.textureBaseMip.clear(); + g_caches.textureInfo.clear(); + g_caches.textureBaseMipInfo.clear(); + g_draw.vb = BGFX_INVALID_HANDLE; + g_draw.ib = BGFX_INVALID_HANDLE; + g_draw.staticVB = BGFX_INVALID_HANDLE; + g_draw.staticIB = BGFX_INVALID_HANDLE; + g_draw.vbOwner = nullptr; + g_draw.ibOwner = nullptr; + 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.sourceTextures[0] = nullptr; + g_draw.sourceTextures[1] = nullptr; + g_draw.sourceTextures[2] = nullptr; + g_draw.sourceTextures[3] = nullptr; + g_draw.sourceMaterial = nullptr; + g_draw.explicitMaterialState = false; + g_draw.ibOffset = 0; + g_draw.useStaticVB = false; + g_draw.useStaticIB = false; + g_draw.useTransientVB = false; + g_draw.useTransientIB = false; + g_draw.pendingVB.valid = false; + g_draw.pendingVB.coplanarNormalBias = false; + g_draw.pendingIB.valid = false; + g_draw.activeTransientVBOwner = nullptr; + g_draw.activeTransientIBOwner = nullptr; + g_draw.activeVertexNormalBias = 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(); + // TheSuperHackers @bugfix bobtista 02/06/2026 Drain the dynamic VB/IB deferred- + // destroy queues too, so resized-out handles do not leak past shutdown. + for (auto & h : g_caches.deferredDestroyVB) { if (bgfx::isValid(h)) { bgfx::destroy(h); } } + for (auto & h : g_caches.deferredDestroyVBPrev) { if (bgfx::isValid(h)) { bgfx::destroy(h); } } + for (auto & h : g_caches.deferredDestroyIB) { if (bgfx::isValid(h)) { bgfx::destroy(h); } } + for (auto & h : g_caches.deferredDestroyIBPrev) { if (bgfx::isValid(h)) { bgfx::destroy(h); } } + g_caches.deferredDestroyVB.clear(); + g_caches.deferredDestroyVBPrev.clear(); + g_caches.deferredDestroyIB.clear(); + g_caches.deferredDestroyIBPrev.clear(); + for (auto & h : g_caches.deferredDestroyStaticVB) { if (bgfx::isValid(h)) { bgfx::destroy(h); } } + for (auto & h : g_caches.deferredDestroyStaticVBPrev) { if (bgfx::isValid(h)) { bgfx::destroy(h); } } + for (auto & h : g_caches.deferredDestroyStaticIB) { if (bgfx::isValid(h)) { bgfx::destroy(h); } } + for (auto & h : g_caches.deferredDestroyStaticIBPrev) { if (bgfx::isValid(h)) { bgfx::destroy(h); } } + g_caches.deferredDestroyStaticVB.clear(); + g_caches.deferredDestroyStaticVBPrev.clear(); + g_caches.deferredDestroyStaticIB.clear(); + g_caches.deferredDestroyStaticIBPrev.clear(); + // TheSuperHackers @bugfix bobtista 10/07/2026 Also drain the deferred framebuffer-destroy + // queues at shutdown so a framebuffer queued in the final frame is not leaked. + for (auto & h : g_caches.deferredDestroyFB) { if (bgfx::isValid(h)) { bgfx::destroy(h); } } + for (auto & h : g_caches.deferredDestroyFBPrev) { if (bgfx::isValid(h)) { bgfx::destroy(h); } } + g_caches.deferredDestroyFB.clear(); + g_caches.deferredDestroyFBPrev.clear(); + // TheSuperHackers @bugfix bobtista 02/06/2026 Destroy remaining registry entries at + // shutdown. The isValid guards make Register_* entries (whose handles live in the caches + // drained above) a safe no-op; Create_Texture entries own their texture/fb and need it. + for (auto & kv : g_resourceRegistry.table) + { + BgfxResourceEntry & entry = kv.second; + switch (entry.kind) + { + case BGFX_RR_KIND_VB: DestroyBgfxHandle(entry.vb); break; + case BGFX_RR_KIND_IB: DestroyBgfxHandle(entry.ib); break; + case BGFX_RR_KIND_TEXTURE: + if (bgfx::isValid(entry.fb)) + { + DestroyBgfxHandle(entry.fb); + } + else + { + DestroyBgfxHandle(entry.texture); + } + break; + default: break; + } + } + g_resourceRegistry.table.clear(); + // TheSuperHackers @bugfix bobtista 02/06/2026 bgfx releases native GPU resources + // lazily across its frame-latency window, so a single bgfx::frame() after queuing + // all the destroys above leaves many still pending when bgfx::shutdown() runs, + // producing a flood of "RefCount is 1 (expected 0)" warnings at exit. Pump enough + // frames to fully drain the deferred native-release pipeline before shutdown. + // bgfx's internal BGFX_CONFIG_MAX_FRAME_LATENCY is not exposed in the public + // headers; its default is 3, so 4 frames covers the worst case with margin. + const int kShutdownFlushFrames = 4; + for (int flush = 0; flush < kShutdownFlushFrames; ++flush) + { + bgfx::frame(); + } + bgfx::shutdown(); + g_device.initialized = false; + WWDEBUG_SAY(("[BgfxBackend] bgfx::shutdown complete.")); + } + + // bgfx window is the single game window, do not destroy it. + g_device.window = nullptr; +} + +// Assemble the full bgfx reset flag set from the persisted device state. +// Used wherever a runtime bgfx::reset is issued (window resize, vsync toggle) +// so every reset path keeps the same MSAA / sRGB / depth-clamp / flush / vsync +// configuration instead of dropping bits. +static uint32_t ComputeBgfxResetFlags() +{ + uint32_t resetFlags = BGFX_RESET_NONE | g_device.msaaResetFlags + | (g_device.srgbEnabled ? BGFX_RESET_SRGB_BACKBUFFER : 0) + | (g_device.vsyncEnabled ? BGFX_RESET_VSYNC : 0); + if (GgcFlags::Enabled(GgcFlag_BgfxDepthClamp)) + { + resetFlags |= BGFX_RESET_DEPTH_CLAMP; + } +#if defined(__APPLE__) + // TheSuperHackers @bugfix bobtista 06/06/2026 Match the init-time gate (opt-in via + // GGC_MACOS_FLUSH) so a runtime reset (window resize / vsync toggle) does not silently flip + // FLUSH_AFTER_RENDER back on and revert the ~48% perf win. + if (GgcFlags::Enabled(GgcFlag_MacosFlush)) + { + resetFlags |= BGFX_RESET_FLUSH_AFTER_RENDER; + } +#endif + return resetFlags; +} + +// -- Device selection, windowing and display-mode control -------------------- +// +// TheSuperHackers @refactor bobtista 11/06/2026 The bgfx backend owns its device lifecycle and a +// single synthetic device entry natively, so dx8wrapper.cpp is no longer compiled on bgfx builds. +// Init_Render_System brings up the render-state cache and the device enumeration; the real bgfx +// device (Initialize) and the WW3D subsystems come up on the first Set_Render_Device, matching the +// legacy Init -> Set_Render_Device -> Create_Device -> Do_Onetime_Device_Dependent_Inits sequence. +// The resolution list feeds the options UI fallback (W3DDisplay::getDisplayMode enumerates SDL3 +// modes directly); caps and Set_Default_Global_Render_States are intentionally dropped because +// bgfx reads neither (it routes fog/bump-env through FixedFunctionState and ignores CurrentCaps). + +void BgfxBackend::Ensure_Render_Device_Desc() +{ + if (m_renderDeviceDescBuilt) + { + return; + } + m_renderDeviceDescBuilt = true; + m_renderDeviceDesc.set_device_name("Generals bgfx standalone"); + m_renderDeviceDesc.set_driver_name("bgfx"); + m_renderDeviceDesc.set_driver_version("0.0.0.0"); + // TheSuperHackers @refactor bobtista 12/06/2026 Only the device name/count are consumed on the + // bgfx path; the options-menu resolution list is built directly from SDL3 modes by + // W3DDisplay::Build_Options_Resolution_List. This small fixed list is only read by the non-SDL3 + // bgfx fallback (W3DDisplay::getDisplayMode's else branch). Do NOT re-enumerate SDL3 modes here - + // that was dead on SDL3 builds and duplicated the W3DDisplay logic. + m_renderDeviceDesc.reset_resolution_list(); + m_renderDeviceDesc.add_resolution(640, 480, 32); + m_renderDeviceDesc.add_resolution(800, 600, 32); + m_renderDeviceDesc.add_resolution(1024, 768, 32); + m_renderDeviceDesc.add_resolution(1280, 720, 32); + m_renderDeviceDesc.add_resolution(1280, 1024, 32); + m_renderDeviceDesc.add_resolution(1920, 1080, 32); +} + +// Equivalent of DX8Wrapper::Reset_Device on the bgfx path: release the bound buffers and let the +// texture/dynamic-buffer/shader subsystems recreate. The bgfx backbuffer and scene render targets +// are resized to the live window every frame in Begin_Scene, so no explicit device reset is needed. +bool BgfxBackend::Reset_Bgfx_Device(bool reload_assets) +{ + if (!m_deviceCreated) + { + return false; + } + WW3D::_Invalidate_Textures(); + // TheSuperHackers @refactor bobtista 12/06/2026 Clear the bound buffers through the existing + // FixedFunctionState setter (the idiom the original DX8Wrapper::Reset_Device used) instead of a + // hand-rolled release loop. The setter also resets the changed-mask / vba bookkeeping the raw + // loop dropped. + for (unsigned i = 0; i < MAX_VERTEX_STREAMS; ++i) + { + FixedFunctionState::Set_Vertex_Buffer(nullptr, i); + } + FixedFunctionState::Set_Index_Buffer(nullptr, 0); + DynamicVBAccessClass::_Deinit(); + DynamicIBAccessClass::_Deinit(); + TextureResourceManagerClass::Release_Textures(); + SHD_SHUTDOWN_SHADERS; + if (reload_assets) + { + TextureResourceManagerClass::Recreate_Textures(); + } + Invalidate_Cached_Render_States(); + SHD_INIT_SHADERS; + return true; +} + +bool BgfxBackend::Init_Render_System(void * hwnd, bool lite) +{ + FixedFunctionState::Clear_Cached_State(); + FixedFunctionState::Clear_Raw(); + g_device.window = static_cast(hwnd); + m_curRenderDevice = -1; + Render2DClass::Set_Screen_Resolution(RectClass(0, 0, 640, 480)); + g_device.windowed = false; + g_device.bits = 32; + DX8Wrapper_IsWindowed = false; + if (!lite) + { + Ensure_Render_Device_Desc(); + } + return true; +} + +void BgfxBackend::Shutdown_Render_System() +{ + if (!m_deviceCreated) + { + return; + } + FixedFunctionState::Release_Raw_Textures(); + for (unsigned i = 0; i < MAX_VERTEX_STREAMS; ++i) + { + if (FixedFunctionState::Render_State().vertex_buffers[i]) + { + FixedFunctionState::Render_State().vertex_buffers[i]->Release_Engine_Ref(); + } + REF_PTR_RELEASE(FixedFunctionState::Render_State().vertex_buffers[i]); + } + if (FixedFunctionState::Render_State().index_buffer) + { + FixedFunctionState::Render_State().index_buffer->Release_Engine_Ref(); + } + REF_PTR_RELEASE(FixedFunctionState::Render_State().index_buffer); + Shutdown(); + WW3DDeviceInit::Shutdown_Subsystems(); + m_deviceCreated = false; +} + +bool BgfxBackend::Set_Render_Device(const char * dev_name, int width, int height, int bits, int windowed, bool resize_window) +{ + Ensure_Render_Device_Desc(); + return Set_Render_Device(0, width, height, bits, windowed, resize_window, m_deviceCreated, true); +} + +bool BgfxBackend::Set_Render_Device(int dev, int width, int height, int bits, int windowed, bool resize_window, bool reset_device, bool restore_assets) +{ + Ensure_Render_Device_Desc(); + if ((m_curRenderDevice == -1) && (dev == -1)) + { + m_curRenderDevice = 0; + } + else if (dev != -1) + { + m_curRenderDevice = dev; + } + // TheSuperHackers @bugfix bobtista 11/06/2026 Do NOT write g_device.width/height here. Begin_Scene + // is their sole owner - it reads the live window every frame and recreates the scene framebuffer + // only when its contentChanged check fires. Pre-setting the new size defeats that check (scene FB + // kept at the old size while the swapchain resizes -> render freeze on resolution change). + // Initialize() sets them from the window at creation; bit-depth/windowed are mirrored here as before. + if (bits != -1) + { + g_device.bits = bits; + } + if (windowed != -1) + { + g_device.windowed = (windowed != 0); + } + DX8Wrapper_IsWindowed = g_device.windowed; + + if (!reset_device) + { + // First creation: bring up the bgfx device, then the WW3D subsystems. Order matters - the + // subsystem _Init() calls allocate static buffers captured into the bgfx caches, so the + // backend must be live first (see the original Do_Onetime_Device_Dependent_Inits note). + Initialize(g_device.window, g_device.width, g_device.height); + WW3DDeviceInit::Init_Subsystems(); + m_deviceCreated = true; + } + else + { + Reset_Bgfx_Device(restore_assets); + } + Render2DClass::Set_Screen_Resolution(RectClass(0, 0, g_device.width, g_device.height)); + return true; +} + +bool BgfxBackend::Set_Any_Render_Device() +{ + Ensure_Render_Device_Desc(); + return Set_Render_Device(0, -1, -1, -1, -1, false, m_deviceCreated, true); +} + +bool BgfxBackend::Set_Next_Render_Device() +{ + // Only one synthetic device, so "next" wraps back to it and resets. + return Set_Render_Device(m_curRenderDevice, -1, -1, -1, -1, false, true, true); +} + +bool BgfxBackend::Toggle_Windowed() +{ + // TheSuperHackers @refactor bobtista 08/06/2026 DX8Wrapper::Toggle_Windowed is a no-op on bgfx + // builds (its body is WW3D_DX8-only and unconditionally returns false); match that directly. + return false; +} + +bool BgfxBackend::Is_Windowed() const +{ + return g_device.windowed; +} + +int BgfxBackend::Get_Render_Device() const +{ + return m_curRenderDevice; +} + +const RenderDeviceDescClass & BgfxBackend::Get_Render_Device_Desc(int /*deviceidx*/) +{ + Ensure_Render_Device_Desc(); + return m_renderDeviceDesc; +} + +int BgfxBackend::Get_Render_Device_Count() const +{ + return 1; +} + +const char * BgfxBackend::Get_Render_Device_Name(int /*device_index*/) +{ + Ensure_Render_Device_Desc(); + return m_renderDeviceDesc.Get_Device_Name(); +} + +bool BgfxBackend::Set_Device_Resolution(int width, int height, int bits, int windowed, bool resize_window) +{ + if (!m_deviceCreated) + { + return false; + } + // TheSuperHackers @bugfix bobtista 11/06/2026 The caller (W3DDisplay::setDisplayMode) already + // resized the SDL window. Begin_Scene owns g_device.width/height/swap* and reconciles them against + // the live window every frame, recreating the scene framebuffer when the content size changes. + // Do NOT touch g_device here: writing the new size defeats Begin_Scene's contentChanged check, so + // the scene framebuffer is left at the old size while the swapchain resizes -> the render freezes + // (audio keeps running, the shellmap stops). The legacy D3D8 device reset is likewise unnecessary + // on bgfx (Begin_Scene does the resize) - mirrors W3DDisplay::applyExternalResize. + WWDEBUG_SAY(("BgfxBackend::Set_Device_Resolution requested %dx%d; Begin_Scene owns the resize", width, height)); + return true; +} + +// TheSuperHackers @refactor bobtista 08/06/2026 g_device is the source of truth for the device +// resolution (width/height track the window every frame) and now for the bit depth and windowed flag +// too. Reading them here - rather than DX8Wrapper's cached state, which is only refreshed on a full +// device reset - keeps the 3D camera viewport (derived from this in CameraClass::Apply) correct across +// an OS window resize. DX8Wrapper mirrors its IsWindowed/BitDepth into g_device, so no round-trip +// through the legacy wrapper is needed. width/height are 0 until Initialize() runs (a deliberate +// sentinel; see the field declaration), which is before any of these getters are read. +void BgfxBackend::Get_Render_Target_Resolution(int & set_w, int & set_h, int & set_bits, bool & set_windowed) +{ + set_w = g_device.width; + set_h = g_device.height; + set_bits = g_device.bits; + set_windowed = g_device.windowed; +} + +void BgfxBackend::Get_Device_Resolution(int & set_w, int & set_h, int & set_bits, bool & set_windowed) +{ + set_w = g_device.width; + set_h = g_device.height; + set_bits = g_device.bits; + set_windowed = g_device.windowed; +} + +int BgfxBackend::Get_Device_Resolution_Width() const +{ + return g_device.width; +} + +int BgfxBackend::Get_Device_Resolution_Height() const +{ + return g_device.height; +} + +// TheSuperHackers @refactor bobtista 08/06/2026 Registry_Save/Load_Render_Device removed - they +// only forwarded to DX8Wrapper and have no GameEngine callers on bgfx (macOS persistence is a no-op +// stub). The IRenderBackend base stubs (return false) now apply. + +void BgfxBackend::Set_Swap_Interval(int swap) +{ + // Default is vsync OFF; render fps is CPU-capped by the FramePacer. + const bool enabled = (swap != 0); + if (g_device.vsyncEnabled == enabled) + { + return; + } + g_device.vsyncEnabled = enabled; + if (g_device.initialized) + { + bgfx::reset(LbSwapWidth(), LbSwapHeight(), ComputeBgfxResetFlags()); + } +} + +int BgfxBackend::Get_Swap_Interval() const +{ + return g_device.vsyncEnabled ? 1 : 0; +} + +// -- Viewport ---------------------------------------------------------------- + +void BgfxBackend::Set_Viewport(const RenderBackendViewport & viewport) +{ + // Do NOT call the DX8 base viewport setter here - this method is called + // FROM DX8Wrapper::Set_Viewport, so the legacy viewport is already set. + // Calling the base class would cause infinite recursion. + + if (!g_device.initialized) + { + 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_device.width && + static_cast(h) == g_device.height); + if (isFullCanvas) + { + return; + } + + // TheSuperHackers @feature bobtista 15/06/2026 Scale the tactical viewport into + // the supersampled scene framebuffer. RTTView (water reflection) is a separate + // target and stays at content coords. + float renderScaleRatio = 1.0f; + if (g_device.width > 0 && g_device.sceneRenderWidth > 0) + { + renderScaleRatio = static_cast(g_device.sceneRenderWidth) / static_cast(g_device.width); + } + const uint16_t sx = static_cast(static_cast(x) * renderScaleRatio + 0.5f); + const uint16_t sy = static_cast(static_cast(y) * renderScaleRatio + 0.5f); + const uint16_t sw = static_cast(static_cast(w) * renderScaleRatio + 0.5f); + const uint16_t sh = static_cast(static_cast(h) * renderScaleRatio + 0.5f); + + bgfx::setViewRect(kBgfxEngineView, sx, sy, sw, sh); + bgfx::setViewRect(kBgfxEngineSortView, sx, sy, sw, sh); + bgfx::setViewRect(kBgfxWaterView, sx, sy, sw, sh); + bgfx::setViewRect(kBgfxEffectOverlayView, sx, sy, sw, sh); + bgfx::setViewRect(kBgfxShadowVolumeView, sx, sy, sw, sh); + bgfx::setViewRect(kBgfxShadowApplyView, sx, sy, sw, sh); + bgfx::setViewRect(kBgfxSceneDepthView, sx, sy, sw, sh); + bgfx::setViewRect(kBgfxSmudgeCopyView, sx, sy, sw, sh); + bgfx::setViewRect(kBgfxSmudgeView, sx, sy, sw, sh); + g_views.sceneViewportX = sx; + g_views.sceneViewportY = sy; + g_views.sceneViewportW = sw; + g_views.sceneViewportH = sh; + bgfx::setViewRect(kBgfxRTTView, x, y, w, h); +} + +// -- View capture / post-effect primitives ---------------------------------- + +bool BgfxBackend::Initialize_View_Capture(RenderBackendViewCaptureKind kind) +{ + (void)kind; + // Native bgfx post effects use the scene framebuffer directly. The legacy + // W3DShaderManager filter capture path is intentionally reported as + // unsupported until those filters are ported to scene-composite passes. + return false; +} + +void BgfxBackend::Release_View_Capture(RenderBackendViewCaptureKind kind) +{ + (void)kind; +} + +bool BgfxBackend::Supports_View_Capture(RenderBackendViewCaptureKind kind) const +{ + (void)kind; + return false; +} + +bool BgfxBackend::Begin_View_Capture(RenderBackendViewCaptureKind kind) +{ + (void)kind; + return false; +} + +bool BgfxBackend::End_View_Capture(RenderBackendViewCaptureKind kind) +{ + (void)kind; + return false; +} + +bool BgfxBackend::Is_View_Capture_Active(RenderBackendViewCaptureKind kind) const +{ + (void)kind; + return false; +} + +bool BgfxBackend::Has_View_Capture(RenderBackendViewCaptureKind kind) const +{ + (void)kind; + return false; +} + +bool BgfxBackend::Bind_View_Capture_Texture(RenderBackendViewCaptureKind kind, unsigned int stage) +{ + (void)kind; + (void)stage; + return false; +} + +bool BgfxBackend::Draw_View_Capture_Quad(RenderBackendViewCaptureKind kind, + const RenderBackendScreenVertex * vertices, + unsigned int vertex_count, + bool use_second_uv) +{ + (void)kind; + (void)vertices; + (void)vertex_count; + (void)use_second_uv; + return false; +} + +bool BgfxBackend::Draw_Screen_Quad(const RenderBackendScreenVertex * vertices, + unsigned int vertex_count, + bool use_second_uv) +{ + (void)vertices; + (void)vertex_count; + (void)use_second_uv; + return false; +} + +bool BgfxBackend::Capture_Back_Buffer_RGBA(unsigned int display_width, + unsigned int display_height, + unsigned int image_size, + unsigned char * output_pixels, + unsigned int output_capacity, + unsigned int * output_width, + unsigned int * output_height) +{ + (void)display_width; + (void)display_height; + (void)image_size; + (void)output_pixels; + (void)output_capacity; + (void)output_width; + (void)output_height; + return false; +} + +bool BgfxBackend::Request_Native_Screen_Shot(const char * path) +{ + if (!g_device.initialized || path == nullptr || path[0] == '\0') + { + return false; + } + + bgfx::requestScreenShot(BGFX_INVALID_HANDLE, path); + return true; +} + +// -- Frame lifecycle --------------------------------------------------------- + +// TheSuperHackers @perf bobtista 03/06/2026 Per-frame timing instrumentation. +// GGC_BGFX_FRAME_TIMING_AFTER=N + GGC_BGFX_FRAME_TIMING_PATH=base activates +// CSV logging at frame N onward, INTERVAL controls cadence (default 60). +// Records the four stamps below; from them the analyzer can extract +// inter-frame ms, end-scene work, and bgfx::frame() time. +struct BgfxFrameTiming +{ + long long t0_begin_scene = 0; // Begin_Scene entry + long long t1_end_scene = 0; // End_Scene entry + long long t2_pre_frame = 0; // just before bgfx::frame() + long long t3_post_frame = 0; // just after bgfx::frame() + long long freq = 0; + int target_frame = -1; + int interval = 60; + char base_path[512] = {}; + bool env_resolved = false; +}; +static BgfxFrameTiming g_timing; + +// TheSuperHackers @perf bobtista 03/06/2026 In-code function profiler. +// Buckets are reset at the top of every frame's End_Scene timing log and +// accumulated by ScopedSectionTimer instances placed at the top of hot +// BgfxBackend functions. Cumulative per-frame ticks + call counts get +// written to the CSV alongside the frame timing. +enum PerfSectionId { + PERF_SECT_SET_TEXTURE, + PERF_SECT_SET_VB, + PERF_SECT_SET_IB, + PERF_SECT_SUBMIT_DRAW, + PERF_SECT_DRAW_TRIANGLES, + PERF_SECT_APPLY_TEX, + PERF_SECT_UPLOAD_UNIFORMS, + PERF_SECT_UPLOAD_LIGHTS, + PERF_SECT_BEGIN_SCENE, + // TheSuperHackers @diag bobtista 04/06/2026 End_Dynamic_Vertex_Write ownership + // (the #1 sampled render self-time fn). DVW_END = whole End call; DVW_SCAN = the + // gated O(n^2) coplanar-pair scan within it; DVW_ALLOC = the transient-VB alloc in + // Begin. Proves whether the cost is the scan, the alloc, or per-call residue. + PERF_SECT_DVW_END, + PERF_SECT_DVW_SCAN, + PERF_SECT_DVW_ALLOC, + PERF_SECT_COUNT +}; +struct PerfSection { long long total_ticks = 0; uint32_t calls = 0; }; +static PerfSection g_perf_sections[PERF_SECT_COUNT]; +// TheSuperHackers @diag bobtista 04/06/2026 Total verts through End_Dynamic_Vertex_Write +// per frame (reset with g_perf_sections); with DVW_END.calls gives verts/call. +static uint64_t g_dvwVerts = 0; + +class ScopedSectionTimer +{ + PerfSection * m_section; + long long m_start; +public: + explicit ScopedSectionTimer(PerfSectionId id) + : m_section(nullptr) + , m_start(0) + { + if (g_timing.target_frame <= 0 || g_timing.base_path[0] == '\0') + { + return; + } + m_section = &g_perf_sections[id]; + LARGE_INTEGER c; QueryPerformanceCounter(&c); m_start = c.QuadPart; + } + ~ScopedSectionTimer() + { + if (m_section == nullptr) + { + return; + } + LARGE_INTEGER c; QueryPerformanceCounter(&c); + m_section->total_ticks += c.QuadPart - m_start; + m_section->calls++; + } +}; +#define PERF_TIME(id) ScopedSectionTimer _pst_##id(id) + +static long long QueryNow() +{ + LARGE_INTEGER c; + QueryPerformanceCounter(&c); + return c.QuadPart; +} + +// TheSuperHackers @diag bobtista 04/06/2026 Cross-TU render-frame attribution +// accumulators (declared in BgfxRenderProfile.h, used from W3DScene / particle +// system / sorting renderer). Reset each frame next to g_perf_sections; emitted +// to the frame-timing CSV with an unattributed remainder so the ~18ms engine-side +// render CPU is accounted top-down. +namespace GGCRenderProfile +{ + struct PhaseAcc { long long start = 0; long long total_ticks = 0; uint32_t calls = 0; }; + static PhaseAcc g_phase_acc[PHASE_COUNT]; + // Snapshot of the last fully-closed frame. EndFrame() (called at the top of the + // next W3DDisplay::draw) copies the accumulators here once every scope of the + // previous frame has closed, then zeroes them. The CSV emit reads this snapshot + // so the enclosing FRAME_DRAW / END_RENDER scopes (still open when the emit fires + // mid-frame, inside End_Render) report complete times with a one-frame lag. + static PhaseAcc g_phase_snapshot[PHASE_COUNT]; + + void Begin(Phase phase) + { + LARGE_INTEGER c; QueryPerformanceCounter(&c); + g_phase_acc[phase].start = c.QuadPart; + } + void End(Phase phase) + { + LARGE_INTEGER c; QueryPerformanceCounter(&c); + g_phase_acc[phase].total_ticks += c.QuadPart - g_phase_acc[phase].start; + g_phase_acc[phase].calls++; + } + long long SnapshotTicks(Phase phase) + { + return g_phase_snapshot[phase].total_ticks; + } + void EndFrame() + { + for (int i = 0; i < PHASE_COUNT; ++i) + { + g_phase_snapshot[i] = g_phase_acc[i]; + g_phase_acc[i].total_ticks = 0; + g_phase_acc[i].calls = 0; + } + } +} + +static void ResolveTimingEnv() +{ + if (g_timing.env_resolved) { + return; + } + g_timing.env_resolved = true; + // TheSuperHackers @perf bobtista 03/06/2026 QueryPerformanceCounter/Frequency + // are provided by the portable Windows-compat shim (already used for the + // PerfLog timers), so the per-section frame-timing profiler works on macOS + // too. The old _WIN32 guard left g_timing.freq == 0, silently disabling the + // submit_us/frame_us CSV on every non-Windows build. + LARGE_INTEGER f; + QueryPerformanceFrequency(&f); + g_timing.freq = f.QuadPart; + if (const char * e = GgcFlags::StringValue(GgcFlag_BgfxFrameTimingAfter)) { + g_timing.target_frame = std::atoi(e); + } + if (const char * e = GgcFlags::StringValue(GgcFlag_BgfxFrameTimingInterval)) { + int v = std::atoi(e); + if (v > 0) { g_timing.interval = v; } + } + if (const char * e = GgcFlags::StringValue(GgcFlag_BgfxFrameTimingPath)) { + std::strncpy(g_timing.base_path, e, sizeof(g_timing.base_path) - 1); + } +} + +static bool TimingActive() +{ + return g_timing.target_frame > 0 + && g_timing.base_path[0] != '\0' + && static_cast(g_stats.frameIndex) >= g_timing.target_frame; +} + +void BgfxBackend::Begin_Scene() +{ + PROFILER_SECTION_NAME("bgfx Begin_Scene"); + if (!g_device.initialized) + { + return; + } + + ResolveTimingEnv(); + long long prev_t3 = g_timing.t3_post_frame; + // g_phase_acc is snapshot+reset by GGCRenderProfile::EndFrame() at the top of + // W3DDisplay::draw (so the enclosing FRAME_DRAW/END_RENDER scopes are closed); + // the emit below reads g_phase_snapshot rather than resetting here. + for (int i = 0; i < PERF_SECT_COUNT; ++i) { + g_perf_sections[i].total_ticks = 0; + g_perf_sections[i].calls = 0; + } + g_dvwVerts = 0; + if (TimingActive()) { + g_timing.t0_begin_scene = QueryNow(); + } + (void)prev_t3; // captured for inter-frame log below + + const bool preserveRenderToTexture = + g_views.renderToTexture && g_views.renderTargetTexture != nullptr; + + ResetFrameStats(); + + // 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_caches.deferredDestroysPrev) + { + if (bgfx::isValid(h)) + { + bgfx::destroy(h); + } + } + g_caches.deferredDestroysPrev.clear(); + // TheSuperHackers @bugfix bobtista 02/06/2026 Same one-frame-delayed destroy for + // dynamic VB/IB handles orphaned by a resize last frame. + for (auto & h : g_caches.deferredDestroyVBPrev) + { + if (bgfx::isValid(h)) + { + bgfx::destroy(h); + } + } + g_caches.deferredDestroyVBPrev.clear(); + for (auto & h : g_caches.deferredDestroyIBPrev) + { + if (bgfx::isValid(h)) + { + bgfx::destroy(h); + } + } + g_caches.deferredDestroyIBPrev.clear(); + // TheSuperHackers @bugfix bobtista 02/06/2026 Same one-frame-delayed destroy for static + // VB/IB handles dropped by a demotion-to-dynamic last frame. + for (auto & h : g_caches.deferredDestroyStaticVBPrev) + { + if (bgfx::isValid(h)) + { + bgfx::destroy(h); + } + } + g_caches.deferredDestroyStaticVBPrev.clear(); + for (auto & h : g_caches.deferredDestroyStaticIBPrev) + { + if (bgfx::isValid(h)) + { + bgfx::destroy(h); + } + } + g_caches.deferredDestroyStaticIBPrev.clear(); + for (auto & h : g_caches.deferredDestroyFBPrev) + { + if (bgfx::isValid(h)) + { + bgfx::destroy(h); + } + } + g_caches.deferredDestroyFBPrev.clear(); + + + // 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_device.window) + { + int sw = 0; + int sh = 0; + if (GetBackendWindowSize(g_device.window, sw, sh)) + { + // TheSuperHackers @feature bobtista 08/06/2026 Reconcile the swapchain (= window) and the + // content (= render) size every frame. Without a letterbox request they are equal; with + // one the content is the centered aspect-fit box and the rest becomes black bars. The + // swapchain follows the window so bgfx presents at native size; the scene framebuffer and + // all render-target views follow the content size. + int cw = 0; + int ch = 0; + int ox = 0; + int oy = 0; + bool active = false; + ComputeLetterboxLayout(sw, sh, cw, ch, ox, oy, active); + const bool swapChanged = (sw != g_device.swapWidth || sh != g_device.swapHeight); + const bool contentChanged = (cw != g_device.width || ch != g_device.height); + if (swapChanged || contentChanged || active != g_device.letterboxActive) + { + WWDEBUG_SAY(("[BgfxBackend] Present resize: swap %dx%d -> %dx%d, content %dx%d -> %dx%d, letterbox=%d.", + g_device.swapWidth, g_device.swapHeight, sw, sh, + g_device.width, g_device.height, cw, ch, (int)active)); + if (contentChanged) + { + DestroySceneFramebuffer(); + } + g_device.swapWidth = sw; + g_device.swapHeight = sh; + g_device.width = cw; + g_device.height = ch; + g_device.presentOffsetX = ox; + g_device.presentOffsetY = oy; + g_device.letterboxActive = active; + bgfx::reset(sw, sh, ComputeBgfxResetFlags()); + if (contentChanged) + { + CreateSceneFramebuffer(); + ApplySceneFramebufferToViews(); + // TheSuperHackers @bugfix bobtista 07/06/2026 bgfx::reset only rebuilds the + // swapchain - it preserves all user textures, and TextureBaseClass::Invalidate is + // a no-op on this backend, so the texture caches (g_caches.texture/textureBaseMip) + // stay valid across a resolution change and must NOT be dropped here. Only the + // render-target framebuffers (water reflection/refraction RTTs) are resolution + // tied and need to be rebuilt; drop them deferred so the in-flight frame's command + // buffers retire cleanly, and Ensure_Render_Target_Framebuffer recreates them + // lazily at the new size. + for (auto & kv : g_caches.framebuffer) + { + if (bgfx::isValid(kv.second.fb)) + { + g_caches.deferredDestroyFB.push_back(kv.second.fb); + } + } + g_caches.framebuffer.clear(); + g_caches.renderTarget.clear(); + } + } + } + } + + // TheSuperHackers @feature bobtista 15/06/2026 LOCAL DEV AID (uncommitted): + // honor a hotkey-requested framebuffer rebuild (HDR format toggle) at the same + // safe point the resize path uses. + if (g_requestSceneFramebufferRebuild) + { + g_requestSceneFramebufferRebuild = false; + DestroySceneFramebuffer(); + CreateSceneFramebuffer(); + ApplySceneFramebufferToViews(); + } + + // 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_device.passthroughProgram) && bgfx::isValid(g_device.fullscreenClearVB)) + { + float identity[16]; + IdentityMatrix(identity); + // Cover the whole swapchain (full window) so the letterbox bars are painted black. + bgfx::setViewRect(kBgfxDebugView, 0, 0, + static_cast(LbSwapWidth()), + static_cast(LbSwapHeight())); + bgfx::setViewTransform(kBgfxDebugView, identity, identity); + 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_device.passthroughProgram); + g_stats.debugSubmits++; + } + else + { + bgfx::touch(kBgfxDebugView); + } + bgfx::touch(kBgfxEngineView); + bgfx::touch(kBgfxEngineSortView); + bgfx::touch(kBgfxWaterView); + bgfx::touch(kBgfxEffectOverlayView); + bgfx::touch(kBgfxShadowVolumeView); + bgfx::touch(kBgfxShadowApplyView); + bgfx::touch(kBgfxShroudOverlayView); + bgfx::touch(kBgfxSmudgeCopyView); + bgfx::touch(kBgfxSmudgeView); + if (bgfx::isValid(g_device.sceneReadableDepthFB)) + { + bgfx::touch(kBgfxSceneDepthView); + } + bgfx::touch(kBgfxSceneCompositeView); + bgfx::touch(kBgfxPointShadowVizView); + bgfx::touch(kBgfxUIView); + g_views.overlay2DActive = false; + // TheSuperHackers @fix bobtista 21/04/2026 Reset ALL 3D view rects to full canvas at + // Begin_Scene so a frame that never calls Set_Viewport does not inherit the last + // tactical rect on some views but not others. + // TheSuperHackers @feature bobtista 15/06/2026 Scene views render into the + // supersampled scene framebuffer (content * render scale); the composite samples + // it normalized and downsamples to the native present rect. + const uint16_t srw = g_device.sceneRenderWidth > 0 ? g_device.sceneRenderWidth : static_cast(g_device.width); + const uint16_t srh = g_device.sceneRenderHeight > 0 ? g_device.sceneRenderHeight : static_cast(g_device.height); + bgfx::setViewRect(kBgfxEngineView, 0, 0, srw, srh); + bgfx::setViewRect(kBgfxEngineSortView, 0, 0, srw, srh); + bgfx::setViewRect(kBgfxWaterView, 0, 0, srw, srh); + bgfx::setViewRect(kBgfxEffectOverlayView, 0, 0, srw, srh); + bgfx::setViewRect(kBgfxShadowVolumeView, 0, 0, srw, srh); + bgfx::setViewRect(kBgfxShadowApplyView, 0, 0, srw, srh); + bgfx::setViewRect(kBgfxShroudOverlayView, 0, 0, srw, srh); + bgfx::setViewRect(kBgfxSceneDepthView, 0, 0, srw, srh); + // Composite and UI present to the swapchain, so they go in the centered content rect; the + // remainder of the swapchain is the black-bar region painted by the debug view above. + bgfx::setViewRect(kBgfxSceneCompositeView, g_device.presentOffsetX, g_device.presentOffsetY, g_device.width, g_device.height); + bgfx::setViewRect(kBgfxSmudgeCopyView, 0, 0, srw, srh); + bgfx::setViewRect(kBgfxSmudgeView, 0, 0, srw, srh); + if (!preserveRenderToTexture) + { + bgfx::setViewRect(kBgfxRTTView, 0, 0, g_device.width, g_device.height); + } + bgfx::setViewRect(kBgfxUIView, g_device.presentOffsetX, g_device.presentOffsetY, g_device.width, g_device.height); + { + // TheSuperHackers @bugfix bobtista 30/04/2026 Keep the dedicated + // 2D UI view in sync with runtime window-size changes too. Leaving + // it at the startup rect makes control-bar/radar art render through + // an old canvas while text/widgets are laid out for the new size. + float identityMtx[16]; + IdentityMatrix(identityMtx); + bgfx::setViewTransform(kBgfxUIView, identityMtx, identityMtx); + } + // No clear on water view — it composites over the opaque scene. + bgfx::setViewClear(kBgfxWaterView, BGFX_CLEAR_NONE, 0, 1.0f, 0); + // TheSuperHackers @bugfix bobtista 28/04/2026 Preserve water submit + // order. Shore foam/water-track quads are submitted after the water + // surface; bgfx default sorting can place the surface last and cover + // the foam. + bgfx::setViewMode(kBgfxWaterView, bgfx::ViewMode::Sequential); + if (preserveRenderToTexture) + { + auto rtIt = g_caches.framebuffer.find(g_views.renderTargetTexture); + if (rtIt != g_caches.framebuffer.end()) + { + const BgfxFramebufferEntry & entry = rtIt->second; + bgfx::setViewFrameBuffer(kBgfxRTTView, entry.fb); + bgfx::setViewRect(kBgfxRTTView, 0, 0, entry.width, entry.height); + bgfx::touch(kBgfxRTTView); + g_views.renderToTexture = true; + } + } + else + { + g_views.renderToTexture = false; + g_views.renderTargetTexture = nullptr; + } + g_views.smudgeActive = false; + + // TheSuperHackers @fix bobtista 21/04/2026 Reset the terrain-blend flag each frame. + // Clear_State_Overrides deliberately preserves g_draw.texcoordSelect[1] because + // Override_Terrain_Blend runs BEFORE Set_Shader, so Begin_Scene must clear it instead. + g_draw.texcoordSelect[1] = 0.0f; + + // TheSuperHackers @bugfix bobtista 23/04/2026 Clear cached sampler bindings every frame so + // the previous frame's final UI texture cannot leak into 3D draws that skip Set_Texture. + for (int i = 0; i < 4; ++i) + { + g_draw.tex[i] = BGFX_INVALID_HANDLE; + g_draw.samplerFlags[i] = 0; + g_draw.mipFilterDisabled[i] = false; + g_draw.textureIsMissing[i] = false; + g_draw.sourceTextures[i] = nullptr; + } + // TheSuperHackers @fix bobtista 21/04/2026 Defensively reset transient view flags at + // Begin_Scene; a map transition or early exit can skip an End_* call and leak stuck state. + g_views.waterOverrideActive = false; + g_views.waterOverlayActive = false; + g_views.effectOverlayActive = false; + g_views.smudgeActive = false; + g_views.inSortFlush = false; + g_views.treeShaderActive = false; + g_views.shadowVolumeActive = false; + g_views.shroudTexturePassActive = false; + g_views.shroudTexturePassStage = 0; + g_views.projectedShadowDecalActive = false; + g_views.projectedDecalMode = RB_PROJECTED_DECAL_NONE; + g_views.skipNextSubmitEngineDraw = false; +} + +void BgfxBackend::Clear(bool clear_color, bool clear_z_stencil, + const Vector3 & color, + float dest_alpha, float z, unsigned int stencil) +{ + if (!g_device.initialized || !g_views.renderToTexture + || g_views.renderTargetTexture == nullptr) + { + return; + } + + uint16_t clearFlags = BGFX_CLEAR_NONE; + if (clear_color) + { + clearFlags |= BGFX_CLEAR_COLOR; + } + if (clear_z_stencil) + { + clearFlags |= BGFX_CLEAR_DEPTH | BGFX_CLEAR_STENCIL; + } + if (clearFlags == BGFX_CLEAR_NONE) + { + return; + } + + bgfx::setViewClear(kBgfxRTTView, + clearFlags, + MakeBgfxClearColor(color, dest_alpha), + z, + static_cast(stencil)); + bgfx::touch(kBgfxRTTView); +} + +void BgfxBackend::End_Scene(bool /*flip_frame*/) +{ + PROFILER_SECTION_NAME("bgfx End_Scene"); + if (!g_device.initialized) + { + return; + } + + if (TimingActive()) { + g_timing.t1_end_scene = QueryNow(); + } + // 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, 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_frame.cameraCaptured) + { + 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); + bgfx::setViewTransform(kBgfxShroudOverlayView, g_frame.cameraView, g_frame.cameraProj); + bgfx::setViewTransform(kBgfxSceneDepthView, g_frame.cameraView, g_frame.cameraProj); + g_frame.cameraCaptured = false; + } + if (g_frame.sortProjCaptured) + { + float identityView[16]; + IdentityMatrix(identityView); + bgfx::setViewTransform(kBgfxEngineSortView, identityView, g_frame.sortProj); + g_frame.sortProjCaptured = 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, + static_cast(g_device.presentOffsetX), + static_cast(g_device.presentOffsetY), + static_cast(g_device.width), + static_cast(g_device.height)); + + // Debug view (0) runs FIRST to emit the backbuffer clear quad. Then RTT + // (3), engine opaque (1), scene depth (11), shadow volume fill (6), shadow + // darken (7), water (4), sort (2), effect overlay (5), heat-haze smudge + // copy/draw (12/13), scene composite (9), 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 + kBgfxRTTView, // 3 + kBgfxShadowMapView, // 20 — sun shadow cascade 0 (before engine, which samples it) + static_cast(kBgfxShadowMapView + 1), // 21 — sun shadow cascade 1 + static_cast(kBgfxShadowMapView + 2), // 22 — sun shadow cascade 2 + kBgfxPointShadowView, // 23 — point-light shadow map (before engine, which samples it) + kBgfxPointShadow2View, // 25 — second point-light shadow map (two simultaneous casters) + kBgfxEngineView, // 1 + kBgfxSceneDepthView, // 11 — readable opaque scene depth + kBgfxShadowVolumeView, // 6 — stencil shadow volume fill + kBgfxShadowApplyView, // 7 — stencil shadow darken + kBgfxWaterView, // 4 + kBgfxEngineSortView, // 2 + kBgfxEffectOverlayView, // 5 + kBgfxShroudOverlayView, // 8 — shroud darkening after scene detail + kBgfxSmudgeCopyView, // 12 — scene-color snapshot for heat haze + kBgfxSmudgeView, // 13 — heat-haze/smudge distortion + kBgfxBloomBrightView, // 14 — bloom bright-pass (half-res) + kBgfxBloomBlurHView, // 15 — bloom horizontal blur + kBgfxBloomBlurVView, // 16 — bloom vertical blur + kBgfxSsaoView, // 17 — SSAO compute + kBgfxSsaoBlurHView, // 18 — SSAO horizontal blur + kBgfxSsaoBlurVView, // 19 — SSAO vertical blur + kBgfxSceneCompositeView, // 9 — scene color to swapchain + kBgfxPointShadowVizView, // 24 — GGC_POINT_SHADOW_VIZ debug blit (no-op when env unset) + kBgfxUIView, // 10 — 2D UI overlay (last) + }; + bgfx::setViewOrder(kBgfxDebugView, BX_COUNTOF(viewOrder), viewOrder); + + float bloomPass[4]; + GetBloomParams(bloomPass); + SubmitBloom(bloomPass); + SubmitSSAO(); + SubmitSceneComposite(); + SubmitPointShadowViz(); + LogFrameStats(); + UpdateBgfxStatsLog(); + + // TheSuperHackers @feature bobtista 02/05/2026 -bgfxScreenshotAfter N + // arms a periodic native back-buffer capture: once frameIndex >= N, every + // 500 bgfx frames we request bgfx::requestScreenShot into a .NNNNNN.bmp + // suffixed file derived from the configured base path. Lets a developer + // (or automated harness) pick whichever frame corresponds to the + // gameplay state of interest, since early frames are loading screens. +#ifdef RTS_ZEROHOUR + { + int captureFrame = GGC_GetBgfxScreenshotFrame(); + if (captureFrame <= 0) + { + if (const char * frameEnv = GgcFlags::StringValue(GgcFlag_BgfxScreenshotAfter)) + { + captureFrame = std::atoi(frameEnv); + } + } + uint32_t interval = 500; + if (const char * intervalEnv = GgcFlags::StringValue(GgcFlag_BgfxScreenshotInterval)) + { + const int parsedInterval = std::atoi(intervalEnv); + if (parsedInterval > 0) + { + interval = static_cast(parsedInterval); + } + } + static uint32_t s_lastShotFrame = 0; + if (captureFrame > 0 + && static_cast(g_stats.frameIndex) >= captureFrame + && (g_stats.frameIndex - s_lastShotFrame) >= interval) + { + s_lastShotFrame = g_stats.frameIndex; + const char * basePath = GGC_GetBgfxScreenshotPath(); + if ((basePath == nullptr || basePath[0] == '\0')) + { + basePath = GgcFlags::StringValue(GgcFlag_BgfxScreenshotPath); + } + if (basePath != nullptr && basePath[0] != '\0') + { + size_t baseLen = 0; + const char * ext = BgfxScreenshotBaseExtension(basePath, &baseLen); + char numbered[512]; + std::snprintf(numbered, sizeof(numbered), "%.*s.%06u.%s", + static_cast(baseLen), basePath, g_stats.frameIndex, ext); + bgfx::requestScreenShot(BGFX_INVALID_HANDLE, numbered); + } + } + + // TheSuperHackers @feature bobtista 03/06/2026 Deterministic same-moment capture. + // GGC_BGFX_SCREENSHOT_LOGICFRAME=N writes exactly one screenshot at the first render + // frame where the simulation reaches logic frame N. Save replay at a fixed logic rate + // is deterministic, so the captured scene state is identical across runs and backends + // (unlike the render-frame trigger above, whose timing drifts with render speed). Use + // this for A/B comparisons. Output: .L.bmp + static bool s_logicShotDone = false; + int targetLogicFrame = 0; + if (const char * logicEnv = GgcFlags::StringValue(GgcFlag_BgfxScreenshotLogicFrame)) + { + targetLogicFrame = std::atoi(logicEnv); + } + if (targetLogicFrame > 0 && !s_logicShotDone) + { + const int curLogicFrame = GGC_GetCurrentLogicFrame(); + if (curLogicFrame >= targetLogicFrame) + { + s_logicShotDone = true; + const char * basePath = GGC_GetBgfxScreenshotPath(); + if ((basePath == nullptr || basePath[0] == '\0')) + { + basePath = GgcFlags::StringValue(GgcFlag_BgfxScreenshotPath); + } + if (basePath != nullptr && basePath[0] != '\0') + { + size_t baseLen = 0; + const char * ext = BgfxScreenshotBaseExtension(basePath, &baseLen); + char numbered[512]; + std::snprintf(numbered, sizeof(numbered), "%.*s.L%06d.%s", + static_cast(baseLen), basePath, curLogicFrame, ext); + bgfx::requestScreenShot(BGFX_INVALID_HANDLE, numbered); + } + } + } + } +#endif + + DrawCallLog_End_Frame(); + RenderDoc_Maybe_Trigger_Capture(); + + if (TimingActive()) { + g_timing.t2_pre_frame = QueryNow(); + } + 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 + if (TimingActive()) { + g_timing.t3_post_frame = QueryNow(); + if (g_timing.freq > 0 + && (g_stats.frameIndex % g_timing.interval) == 0) + { + char path[640]; + std::snprintf(path, sizeof(path), "%s.csv", g_timing.base_path); + static bool s_headerWritten = false; + FILE * f = std::fopen(path, s_headerWritten ? "a" : "w"); + if (f != nullptr) { + if (!s_headerWritten) { + std::fprintf(f, + "frame,logic_frame,inter_us,scene_us,frame_us,total_us,draws,binds,uniforms," + "world_draws,sort_draws,shadow_submits,water_draws," + "tex_creates,tex_uploads,inst_saved," + "set_tex_us,set_tex_n,set_vb_us,set_vb_n,set_ib_us,set_ib_n," + "submit_us,submit_n,draw_us,draw_n," + "apply_tex_us,apply_tex_n,uniforms_us,uniforms_n,light_us,light_n," + "dvw_us,dvw_n,dvw_scan_us,dvw_scan_n,dvw_alloc_us,dvw_alloc_n,dvw_verts," + "frame_draw_us,update_views_us,particle_update_us,rtt_us,draw_views_us,ui_draw_us,end_render_us,render_unattributed_us,main_loop_other_us," + "render_total_us,traversal_us,mesh_flush_us,sort_flush_us,particles_us,terrain_us," + "pointgroup_compress_us,pointgroup_view_xform_us,pointgroup_update_arrays_us,pointgroup_ground_fixup_us,pointgroup_vb_fill_us," + "sort_pool_build_us,sort_pool_sort_us,sort_pool_draw_us," + "gpu_us,cpu_us,wait_sub_us,wait_ren_us,bgfx_ndraw\n"); + s_headerWritten = true; + } + const double us_per_tick = 1000000.0 / static_cast(g_timing.freq); + static long long s_prev_t3 = 0; + const long long inter_ticks = (s_prev_t3 > 0) + ? (g_timing.t0_begin_scene - s_prev_t3) : 0; + const long long scene_ticks = g_timing.t2_pre_frame - g_timing.t1_end_scene; + const long long frame_ticks = g_timing.t3_post_frame - g_timing.t2_pre_frame; + const long long total_ticks = (s_prev_t3 > 0) + ? (g_timing.t3_post_frame - s_prev_t3) : 0; + s_prev_t3 = g_timing.t3_post_frame; + int logicFrame = -1; +#ifdef RTS_ZEROHOUR + logicFrame = GGC_GetCurrentLogicFrame(); +#endif + // TheSuperHackers @perf bobtista 03/06/2026 bgfx internal timers + // separate GPU time from CPU-submit time. High wait_ren = render + // thread idle waiting on submit (CPU-submit bound); high wait_sub + // = submit thread waiting on render thread (GPU/render bound). + const bgfx::Stats * bstats = bgfx::getStats(); + double gpu_us = 0.0, cpu_us = 0.0, wait_sub_us = 0.0, wait_ren_us = 0.0; + unsigned bgfx_ndraw = 0; + if (bstats != nullptr) { + if (bstats->gpuTimerFreq > 0) { + gpu_us = double(bstats->gpuTimeEnd - bstats->gpuTimeBegin) + * 1000000.0 / double(bstats->gpuTimerFreq); + } + if (bstats->cpuTimerFreq > 0) { + cpu_us = double(bstats->cpuTimeFrame) * 1000000.0 / double(bstats->cpuTimerFreq); + wait_sub_us = double(bstats->waitSubmit) * 1000000.0 / double(bstats->cpuTimerFreq); + wait_ren_us = double(bstats->waitRender) * 1000000.0 / double(bstats->cpuTimerFreq); + } + bgfx_ndraw = bstats->numDraw; + } + std::fprintf(f, + "%u,%d,%.1f,%.1f,%.1f,%.1f,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u," + "%.1f,%u,%.1f,%u,%.1f,%u,%.1f,%u,%.1f,%u,%.1f,%u,%.1f,%u,%.1f,%u," + "%.1f,%u,%.1f,%u,%.1f,%u,%llu," + "%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f," + "%.1f,%.1f,%.1f,%.1f,%.1f,%.1f," + "%.1f,%.1f,%.1f,%.1f,%.1f," + "%.1f,%.1f,%.1f," + "%.1f,%.1f,%.1f,%.1f,%u\n", + g_stats.frameIndex, + logicFrame, + inter_ticks * us_per_tick, + scene_ticks * us_per_tick, + frame_ticks * us_per_tick, + total_ticks * us_per_tick, + g_stats.drawCalls, + g_stats.textureBinds, + g_stats.materialUniformUploads, + g_stats.worldDraws, + g_stats.sortedDraws, + g_stats.shadowVolumeSubmits, + g_stats.waterDraws, + g_stats.textureCreates, + g_stats.textureUploads, + g_stats.instancedSavedDrawCalls, + g_perf_sections[PERF_SECT_SET_TEXTURE].total_ticks * us_per_tick, + g_perf_sections[PERF_SECT_SET_TEXTURE].calls, + g_perf_sections[PERF_SECT_SET_VB].total_ticks * us_per_tick, + g_perf_sections[PERF_SECT_SET_VB].calls, + g_perf_sections[PERF_SECT_SET_IB].total_ticks * us_per_tick, + g_perf_sections[PERF_SECT_SET_IB].calls, + g_perf_sections[PERF_SECT_SUBMIT_DRAW].total_ticks * us_per_tick, + g_perf_sections[PERF_SECT_SUBMIT_DRAW].calls, + g_perf_sections[PERF_SECT_DRAW_TRIANGLES].total_ticks * us_per_tick, + g_perf_sections[PERF_SECT_DRAW_TRIANGLES].calls, + g_perf_sections[PERF_SECT_APPLY_TEX].total_ticks * us_per_tick, + g_perf_sections[PERF_SECT_APPLY_TEX].calls, + g_perf_sections[PERF_SECT_UPLOAD_UNIFORMS].total_ticks * us_per_tick, + g_perf_sections[PERF_SECT_UPLOAD_UNIFORMS].calls, + g_perf_sections[PERF_SECT_UPLOAD_LIGHTS].total_ticks * us_per_tick, + g_perf_sections[PERF_SECT_UPLOAD_LIGHTS].calls, + g_perf_sections[PERF_SECT_DVW_END].total_ticks * us_per_tick, + g_perf_sections[PERF_SECT_DVW_END].calls, + g_perf_sections[PERF_SECT_DVW_SCAN].total_ticks * us_per_tick, + g_perf_sections[PERF_SECT_DVW_SCAN].calls, + g_perf_sections[PERF_SECT_DVW_ALLOC].total_ticks * us_per_tick, + g_perf_sections[PERF_SECT_DVW_ALLOC].calls, + static_cast(g_dvwVerts), + // Top-level sequential buckets of W3DDisplay::draw. + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::FRAME_DRAW].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::UPDATE_VIEWS].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::PARTICLE_UPDATE].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::RTT].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::DRAW_VIEWS].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::UI_DRAW].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::END_RENDER].total_ticks * us_per_tick, + // render_unattributed = FRAME_DRAW minus the non-overlapping top-level buckets + // (work inside W3DDisplay::draw not in any named bucket). NOT the whole-frame remainder. + (GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::FRAME_DRAW].total_ticks + - GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::UPDATE_VIEWS].total_ticks + - GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::PARTICLE_UPDATE].total_ticks + - GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::RTT].total_ticks + - GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::DRAW_VIEWS].total_ticks + - GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::UI_DRAW].total_ticks + - GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::END_RENDER].total_ticks) * us_per_tick, + // main_loop_other = full frame (cpu_us) minus W3DDisplay::draw: the non-render + // main-loop work between bgfx frames (client/drawable update + GPU backpressure). + // 1-frame lag (cpu_us is current frame, frame_draw snapshot is previous) — fine for medians. + cpu_us - GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::FRAME_DRAW].total_ticks * us_per_tick, + // Nested detail inside DRAW_VIEWS/RTT (subsets, not subtracted above). + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::RENDER_TOTAL].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::TRAVERSAL].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::MESH_FLUSH].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::SORT_FLUSH].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::PARTICLES].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::TERRAIN].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::POINTGROUP_COMPRESS].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::POINTGROUP_VIEW_XFORM].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::POINTGROUP_UPDATE_ARRAYS].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::POINTGROUP_GROUND_FIXUP].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::POINTGROUP_VB_FILL].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::SORT_POOL_BUILD].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::SORT_POOL_SORT].total_ticks * us_per_tick, + GGCRenderProfile::g_phase_snapshot[GGCRenderProfile::SORT_POOL_DRAW].total_ticks * us_per_tick, + gpu_us, cpu_us, wait_sub_us, wait_ren_us, bgfx_ndraw); + std::fclose(f); + } + } + } + +#if defined(SAGE_USE_SDL3) + if (!g_device.mainWindowShown && g_device.window != nullptr) + { + SDL_ShowWindow(static_cast(static_cast(g_device.window))); + g_device.mainWindowShown = true; + } +#endif + + // 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. + // Begin_Scene drained prev, so it is empty here; swap is cheaper than + // insert+clear and avoids any vector growth. + g_caches.deferredDestroysPrev.swap(g_caches.deferredDestroys); + // TheSuperHackers @bugfix bobtista 02/06/2026 Rotate the dynamic VB/IB deferred- + // destroy queues the same way. + g_caches.deferredDestroyVBPrev.swap(g_caches.deferredDestroyVB); + g_caches.deferredDestroyIBPrev.swap(g_caches.deferredDestroyIB); + g_caches.deferredDestroyStaticVBPrev.swap(g_caches.deferredDestroyStaticVB); + g_caches.deferredDestroyStaticIBPrev.swap(g_caches.deferredDestroyStaticIB); + g_caches.deferredDestroyFBPrev.swap(g_caches.deferredDestroyFB); + + // 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_draw.pendingVB.valid = false; + g_draw.pendingVB.coplanarNormalBias = false; + g_draw.pendingIB.valid = false; + g_draw.useTransientVB = false; + g_draw.useTransientIB = false; + g_draw.activeTransientVBOwner = nullptr; + g_draw.activeTransientIBOwner = nullptr; + g_draw.activeVertexNormalBias = false; +} + +void BgfxBackend::Invalidate_Cached_Render_States() +{ + // TheSuperHackers @bugfix bobtista 05/06/2026 Intentionally a no-op on bgfx. + // This is called from ~20 sites (W3DShaderManager, W3DTreeBuffer) mid-frame + // after they fiddle DX8 render state. On bgfx those resets are unnecessary + // (the backend tracks its own state) and HARMFUL: forwarding to + // DX8Wrapper::Invalidate_Cached_Render_States() here resets the + // FixedFunctionState / ShaderClass caches mid-frame and collapses sorted + // eye-space effects - notably the Particle Uplink Cannon orbital beam. + // Do NOT forward this to DX8Wrapper. +} + +WW3DFormat BgfxBackend::Get_Back_Buffer_Format() const +{ + return WW3D_FORMAT_A8R8G8B8; +} + +void BgfxBackend::Set_Texture_Bitdepth(int bitdepth) +{ + WWASSERT(bitdepth == 16 || bitdepth == 32); + if (bitdepth == 16 || bitdepth == 32) + { + m_textureBitDepth = bitdepth; + } +} + +int BgfxBackend::Get_Texture_Bitdepth() const +{ + return m_textureBitDepth; +} + +bool BgfxBackend::Supports_Texture_Op(RenderBackendTextureOpCapability capability) const +{ + switch (capability) + { + case RB_TEXTURE_OP_SELECTARG1: + case RB_TEXTURE_OP_MODULATE: + case RB_TEXTURE_OP_MODULATE2X: + case RB_TEXTURE_OP_ADD: + case RB_TEXTURE_OP_ADDSMOOTH: + case RB_TEXTURE_OP_SUBTRACT: + case RB_TEXTURE_OP_BLENDTEXTUREALPHA: + case RB_TEXTURE_OP_BLENDCURRENTALPHA: + case RB_TEXTURE_OP_ADDSIGNED: + case RB_TEXTURE_OP_ADDSIGNED2X: + case RB_TEXTURE_OP_MODULATEALPHA_ADDCOLOR: + return true; + case RB_TEXTURE_OP_BUMPENVMAP: + case RB_TEXTURE_OP_BUMPENVMAPLUMINANCE: + default: + return false; + } +} + +RenderBackendTextureLimits BgfxBackend::Get_Texture_Limits() const +{ + constexpr unsigned kTextureAspectRatioLimit = 8; + const bgfx::Caps * caps = bgfx::getCaps(); + if (caps == nullptr) + { + return IRenderBackend::Get_Texture_Limits(); + } + + return { + caps->limits.maxTextureSize, + caps->limits.maxTextureSize, + caps->limits.maxTextureSize, + kTextureAspectRatioLimit + }; +} + +int BgfxBackend::Get_Max_Texture_Stages() const +{ + return kBgfxTextureStages; +} + +void BgfxBackend::Set_MSAA_Mode(RenderBackendMSAAMode mode) +{ + m_msaaMode = mode; +} + +RenderBackendMSAAMode BgfxBackend::Get_MSAA_Mode() const +{ + return m_msaaMode; +} + +bool BgfxBackend::Get_Device_Identity(RenderBackendDeviceIdentity & identity) const +{ + identity = {}; + identity.max_simultaneous_textures = kBgfxTextureStages; + identity.pixel_shader_major = 2; + identity.pixel_shader_minor = 0; + return true; +} + +static void LogBgfxTransientDiag(const char *event, + const char *kind, + const void *owner, + uint32_t count, + bool pendingValid, + bool pendingOwnerMatch, + bool active, + bool activeOwnerMatch, + const char *decision) +{ + if (!GgcFlags::Enabled(GgcFlag_BgfxTransientDiag)) + { + return; + } + + if (FILE *diag = std::fopen("ggc_bgfx_transient_diag.txt", "a")) + { + std::fprintf(diag, + "%s frame=%u kind=%s owner=%p count=%u pendingValid=%d pendingOwnerMatch=%d active=%d activeOwnerMatch=%d decision=%s inSort=%d\n", + event, + g_stats.frameIndex, + kind, + owner, + count, + pendingValid ? 1 : 0, + pendingOwnerMatch ? 1 : 0, + active ? 1 : 0, + activeOwnerMatch ? 1 : 0, + decision ? decision : "", + g_views.inSortFlush ? 1 : 0); + std::fclose(diag); + } +} + +namespace +{ +bgfx::DynamicVertexBufferHandle FindResourceVertexBufferHandle(const VertexBufferClass * vb); +bgfx::DynamicIndexBufferHandle FindResourceIndexBufferHandle(const IndexBufferClass * ib); +bgfx::VertexBufferHandle FindResourceStaticVertexBufferHandle(const VertexBufferClass * vb); +bgfx::IndexBufferHandle FindResourceStaticIndexBufferHandle(const IndexBufferClass * ib); +void FlushPendingVertexRangeUpload(const VertexBufferClass * vb); +void FlushPendingIndexRangeUpload(const IndexBufferClass * ib); +void MirrorDynamicVertexHandleToResource(const VertexBufferClass * vb, + bgfx::DynamicVertexBufferHandle handle); +void MirrorDynamicIndexHandleToResource(const IndexBufferClass * ib, + bgfx::DynamicIndexBufferHandle handle); +} + +// -- Vertex / index buffers -------------------------------------------------- + +void BgfxBackend::Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int stream) +{ + PERF_TIME(PERF_SECT_SET_VB); + FixedFunctionState::Set_Vertex_Buffer(vb, stream); + (void)stream; + // Cache is populated by Upload_Vertex_Buffer_Data on the engine's own write + // lock. Set_Vertex_Buffer just looks up whatever is already there; on a + // miss it can rebuild from the buffer object's CPU-side write snapshot. + g_draw.useTransientVB = false; + g_draw.useStaticVB = false; + g_draw.staticVB = BGFX_INVALID_HANDLE; + g_draw.vbOwner = nullptr; + g_draw.activeTransientVBOwner = nullptr; + g_draw.activeVertexNormalBias = false; + // TheSuperHackers @bugfix bobtista 27/04/2026 Legacy fixed-function + // supplies a white diffuse color when the bound FVF has no COLOR0 element. bgfx + // missing attributes read as zero, so tell the shader when it must + // substitute the fixed-function default. + g_draw.vertexColorFlags[0] = (vb != nullptr + && vb->FVF_Info().Has_Diffuse()) ? 1.0f : 0.0f; + g_draw.fvfHasNormal = (vb != nullptr + && vb->FVF_Info().Has_Normal()); + FlushPendingVertexRangeUpload(vb); + bgfx::VertexBufferHandle staticResourceHandle = FindResourceStaticVertexBufferHandle(vb); + if (bgfx::isValid(staticResourceHandle)) + { + g_draw.staticVB = staticResourceHandle; + g_draw.vb = BGFX_INVALID_HANDLE; + g_draw.useStaticVB = true; + } + else + { + bgfx::DynamicVertexBufferHandle resourceHandle = FindResourceVertexBufferHandle(vb); + if (bgfx::isValid(resourceHandle)) + { + g_draw.vb = resourceHandle; + g_draw.vbOwner = vb; + } + else + { + auto it = g_caches.vb.find(vb); + if (it != g_caches.vb.end()) + { + g_draw.vb = it->second.handle; + g_draw.vbOwner = vb; + MirrorDynamicVertexHandleToResource(vb, it->second.handle); + } + else + { + g_draw.vb = BGFX_INVALID_HANDLE; + g_draw.vbOwner = nullptr; + // Last-resort capture for static VBs that were written before bgfx + // registration/capture was active. Do not lock the legacy buffer here: + // bgfx must consume the backend-neutral CPU snapshot maintained by + // the buffer write paths. + if (vb != nullptr && g_device.initialized && vb->Has_CPU_Buffer_Data()) + { + const unsigned int bytes = + vb->Get_Vertex_Count() * vb->FVF_Info().Get_FVF_Size(); + if (vb->Get_CPU_Buffer_Size() >= bytes) + { + Upload_Vertex_Buffer_Data(vb, vb->Peek_CPU_Buffer_Data(), bytes); + bgfx::VertexBufferHandle staticHandle = FindResourceStaticVertexBufferHandle(vb); + if (bgfx::isValid(staticHandle)) + { + g_draw.staticVB = staticHandle; + g_draw.vb = BGFX_INVALID_HANDLE; + g_draw.useStaticVB = true; + } + else + { + auto it2 = g_caches.vb.find(vb); + if (it2 != g_caches.vb.end()) + { + g_draw.vb = it2->second.handle; + g_draw.vbOwner = vb; + MirrorDynamicVertexHandleToResource(vb, it2->second.handle); + } + } + } + } + } + } + } +} + +void BgfxBackend::Set_Vertex_Buffer(const DynamicVBAccessClass & vba) +{ + PERF_TIME(PERF_SECT_SET_VB); + FixedFunctionState::Set_Vertex_Buffer(vba); + g_draw.vertexColorFlags[0] = + vba.FVF_Info().Has_Diffuse() ? 1.0f : 0.0f; + g_draw.fvfHasNormal = + vba.FVF_Info().Has_Normal(); + g_draw.useStaticVB = false; + g_draw.staticVB = BGFX_INVALID_HANDLE; + g_draw.vbOwner = nullptr; + // 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_draw.pendingVB.valid && g_draw.pendingVB.owner == &vba) + { + LogBgfxTransientDiag("set", "vb", &vba, + static_cast(vba.Get_Vertex_Count()), + g_draw.pendingVB.valid, true, + g_draw.useTransientVB, + g_draw.activeTransientVBOwner == &vba, + "claim-pending"); + g_draw.useTransientVB = true; + g_draw.transientVB = g_draw.pendingVB.tvb; + g_draw.activeVertexNormalBias = g_draw.pendingVB.coplanarNormalBias; + g_draw.pendingVB.valid = false; + g_draw.activeTransientVBOwner = &vba; + } + else if (g_draw.useTransientVB && g_draw.activeTransientVBOwner == &vba) + { + LogBgfxTransientDiag("set", "vb", &vba, + static_cast(vba.Get_Vertex_Count()), + g_draw.pendingVB.valid, + g_draw.pendingVB.owner == &vba, + true, + true, + "reuse-active"); + // The sorting renderer applies a saved material/texture state for + // each sorted run, then rebinds the same per-flush transient VB. A + // transient buffer remains valid until bgfx::frame(), so allow that + // same access object to be rebound after the first claim. + } + else + { + LogBgfxTransientDiag("set", "vb", &vba, + static_cast(vba.Get_Vertex_Count()), + g_draw.pendingVB.valid, + g_draw.pendingVB.owner == &vba, + g_draw.useTransientVB, + g_draw.activeTransientVBOwner == &vba, + "miss"); + g_draw.useTransientVB = false; + g_draw.vb = BGFX_INVALID_HANDLE; + g_draw.vbOwner = nullptr; + g_draw.activeTransientVBOwner = nullptr; + g_draw.activeVertexNormalBias = false; + } +} + +void BgfxBackend::Set_Index_Buffer(const IndexBufferClass * ib, unsigned short index_base_offset) +{ + PERF_TIME(PERF_SECT_SET_IB); + FixedFunctionState::Set_Index_Buffer(ib, index_base_offset); + g_draw.useTransientIB = false; + g_draw.useStaticIB = false; + g_draw.staticIB = BGFX_INVALID_HANDLE; + g_draw.ibOwner = nullptr; + g_draw.activeTransientIBOwner = nullptr; + FlushPendingIndexRangeUpload(ib); + bgfx::IndexBufferHandle staticResourceHandle = FindResourceStaticIndexBufferHandle(ib); + if (bgfx::isValid(staticResourceHandle)) + { + g_draw.staticIB = staticResourceHandle; + g_draw.ib = BGFX_INVALID_HANDLE; + g_draw.useStaticIB = true; + } + else + { + bgfx::DynamicIndexBufferHandle resourceHandle = FindResourceIndexBufferHandle(ib); + if (bgfx::isValid(resourceHandle)) + { + g_draw.ib = resourceHandle; + g_draw.ibOwner = ib; + } + else + { + auto it = g_caches.ib.find(ib); + if (it != g_caches.ib.end()) + { + g_draw.ib = it->second.handle; + g_draw.ibOwner = ib; + MirrorDynamicIndexHandleToResource(ib, it->second.handle); + } + else + { + g_draw.ib = BGFX_INVALID_HANDLE; + g_draw.ibOwner = nullptr; + // Last-resort capture for static IBs not yet in cache. Use the + // backend-neutral CPU snapshot instead of locking a legacy index buffer. + if (ib != nullptr && g_device.initialized && ib->Has_CPU_Buffer_Data()) + { + const unsigned int bytes = ib->Get_Index_Count() * sizeof(unsigned short); + if (ib->Get_CPU_Buffer_Size() >= bytes) + { + Upload_Index_Buffer_Data(ib, ib->Peek_CPU_Buffer_Data(), bytes); + bgfx::IndexBufferHandle staticHandle = FindResourceStaticIndexBufferHandle(ib); + if (bgfx::isValid(staticHandle)) + { + g_draw.staticIB = staticHandle; + g_draw.ib = BGFX_INVALID_HANDLE; + g_draw.useStaticIB = true; + } + else + { + auto it2 = g_caches.ib.find(ib); + if (it2 != g_caches.ib.end()) + { + g_draw.ib = it2->second.handle; + g_draw.ibOwner = ib; + MirrorDynamicIndexHandleToResource(ib, it2->second.handle); + } + } + } + } + } + } + } + g_draw.ibOffset = index_base_offset; +} + +void BgfxBackend::Set_Index_Buffer(const DynamicIBAccessClass & iba, unsigned short index_base_offset) +{ + PERF_TIME(PERF_SECT_SET_IB); + FixedFunctionState::Set_Index_Buffer(iba, index_base_offset); + g_draw.useStaticIB = false; + g_draw.staticIB = BGFX_INVALID_HANDLE; + g_draw.ibOwner = nullptr; + if (g_draw.pendingIB.valid && g_draw.pendingIB.owner == &iba) + { + LogBgfxTransientDiag("set", "ib", &iba, + static_cast(iba.Get_Index_Count()), + g_draw.pendingIB.valid, true, + g_draw.useTransientIB, + g_draw.activeTransientIBOwner == &iba, + "claim-pending"); + g_draw.useTransientIB = true; + g_draw.transientIB = g_draw.pendingIB.tib; + g_draw.pendingIB.valid = false; + g_draw.activeTransientIBOwner = &iba; + } + else if (g_draw.useTransientIB && g_draw.activeTransientIBOwner == &iba) + { + LogBgfxTransientDiag("set", "ib", &iba, + static_cast(iba.Get_Index_Count()), + g_draw.pendingIB.valid, + g_draw.pendingIB.owner == &iba, + true, + true, + "reuse-active"); + // See Set_Vertex_Buffer(DynamicVBAccessClass&): sorted runs reuse + // the same transient IB several times before the frame boundary. + } + else + { + LogBgfxTransientDiag("set", "ib", &iba, + static_cast(iba.Get_Index_Count()), + g_draw.pendingIB.valid, + g_draw.pendingIB.owner == &iba, + g_draw.useTransientIB, + g_draw.activeTransientIBOwner == &iba, + "miss"); + g_draw.useTransientIB = false; + g_draw.ib = BGFX_INVALID_HANDLE; + g_draw.ibOwner = nullptr; + g_draw.activeTransientIBOwner = nullptr; + } + g_draw.ibOffset = index_base_offset; +} + +// 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 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. +void BgfxBackend::Set_Index_Buffer_Index_Offset(unsigned int offset) +{ + g_draw.ibOffset = static_cast(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. + +namespace +{ +static void LogBgfxBufferUpdate(const char *kind, + const void *owner, + const void *src, + unsigned int offset, + unsigned int size_bytes, + uint16_t handle_idx, + const bgfx::Memory *mem) +{ + if (!GgcFlags::Enabled(GgcFlag_BgfxBufferUpdateDiag)) + { + return; + } + + if (FILE *diag = std::fopen("ggc_bgfx_buffer_update_diag.txt", "a")) + { + std::fprintf(diag, + "%s frame=%u owner=%p src=%p offset=%u size=%u handle=%u mem=%p memData=%p memSize=%u\n", + kind, + g_stats.frameIndex, + owner, + src, + offset, + size_bytes, + handle_idx, + static_cast(mem), + mem != nullptr ? static_cast(mem->data) : nullptr, + mem != nullptr ? mem->size : 0); + std::fclose(diag); + } +} + +// 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. +BgfxResourceEntry * FindVertexBufferResourceEntry(const VertexBufferClass * vb) +{ + if (vb == nullptr || !vb->Has_Backend_Resource()) + { + return nullptr; + } + auto it = g_resourceRegistry.table.find(vb->Get_Backend_Resource().id); + if (it == g_resourceRegistry.table.end()) + { + return nullptr; + } + BgfxResourceEntry & entry = it->second; + if (entry.kind != BGFX_RR_KIND_VB || entry.owner != vb) + { + return nullptr; + } + return &entry; +} + +BgfxResourceEntry * FindIndexBufferResourceEntry(const IndexBufferClass * ib) +{ + if (ib == nullptr || !ib->Has_Backend_Resource()) + { + return nullptr; + } + auto it = g_resourceRegistry.table.find(ib->Get_Backend_Resource().id); + if (it == g_resourceRegistry.table.end()) + { + return nullptr; + } + BgfxResourceEntry & entry = it->second; + if (entry.kind != BGFX_RR_KIND_IB || entry.owner != ib) + { + return nullptr; + } + return &entry; +} + +void MirrorDynamicVertexHandleToResource(const VertexBufferClass * vb, + bgfx::DynamicVertexBufferHandle handle) +{ + if (BgfxResourceEntry * entry = FindVertexBufferResourceEntry(vb)) + { + entry->dvb = handle; + } +} + +void MirrorDynamicIndexHandleToResource(const IndexBufferClass * ib, + bgfx::DynamicIndexBufferHandle handle) +{ + if (BgfxResourceEntry * entry = FindIndexBufferResourceEntry(ib)) + { + entry->dib = handle; + } +} + +void ClearDynamicVertexHandleFromResource(const VertexBufferClass * vb, + bgfx::DynamicVertexBufferHandle stale) +{ + if (BgfxResourceEntry * entry = FindVertexBufferResourceEntry(vb)) + { + if (!bgfx::isValid(entry->dvb) || entry->dvb.idx == stale.idx) + { + entry->dvb = BGFX_INVALID_HANDLE; + } + } +} + +void ClearDynamicIndexHandleFromResource(const IndexBufferClass * ib, + bgfx::DynamicIndexBufferHandle stale) +{ + if (BgfxResourceEntry * entry = FindIndexBufferResourceEntry(ib)) + { + if (!bgfx::isValid(entry->dib) || entry->dib.idx == stale.idx) + { + entry->dib = BGFX_INVALID_HANDLE; + } + } +} + +bgfx::DynamicVertexBufferHandle FindResourceVertexBufferHandle(const VertexBufferClass * vb) +{ + if (BgfxResourceEntry * entry = FindVertexBufferResourceEntry(vb)) + { + if (bgfx::isValid(entry->dvb)) + { + return entry->dvb; + } + } + return BGFX_INVALID_HANDLE; +} + +bgfx::DynamicIndexBufferHandle FindResourceIndexBufferHandle(const IndexBufferClass * ib) +{ + if (BgfxResourceEntry * entry = FindIndexBufferResourceEntry(ib)) + { + if (bgfx::isValid(entry->dib)) + { + return entry->dib; + } + } + return BGFX_INVALID_HANDLE; +} + +bgfx::VertexBufferHandle FindResourceStaticVertexBufferHandle(const VertexBufferClass * vb) +{ + if (BgfxResourceEntry * entry = FindVertexBufferResourceEntry(vb)) + { + if (bgfx::isValid(entry->vb)) + { + return entry->vb; + } + } + return BGFX_INVALID_HANDLE; +} + +bgfx::IndexBufferHandle FindResourceStaticIndexBufferHandle(const IndexBufferClass * ib) +{ + if (BgfxResourceEntry * entry = FindIndexBufferResourceEntry(ib)) + { + if (bgfx::isValid(entry->ib)) + { + return entry->ib; + } + } + return BGFX_INVALID_HANDLE; +} + +void DestroyStaticVertexResource(BgfxResourceEntry & entry) +{ + if (!bgfx::isValid(entry.vb)) + { + return; + } + if (g_draw.useStaticVB + && bgfx::isValid(g_draw.staticVB) + && g_draw.staticVB.idx == entry.vb.idx) + { + g_draw.staticVB = BGFX_INVALID_HANDLE; + g_draw.useStaticVB = false; + } + bgfx::destroy(entry.vb); + entry.vb = BGFX_INVALID_HANDLE; +} + +void DestroyStaticIndexResource(BgfxResourceEntry & entry) +{ + if (!bgfx::isValid(entry.ib)) + { + return; + } + if (g_draw.useStaticIB + && bgfx::isValid(g_draw.staticIB) + && g_draw.staticIB.idx == entry.ib.idx) + { + g_draw.staticIB = BGFX_INVALID_HANDLE; + g_draw.useStaticIB = false; + } + bgfx::destroy(entry.ib); + entry.ib = BGFX_INVALID_HANDLE; +} + +// TheSuperHackers @bugfix bobtista 02/06/2026 Like DestroyStaticVertexResource but defers +// the bgfx::destroy by one frame. Used when a static-eligible buffer demotes to the dynamic +// path mid-frame: the immutable buffer may still be referenced by a draw recorded earlier +// this frame, so destroying it now triggers a "RefCount is 1 (expected 0)" warning. +void DeferDestroyStaticVertexResource(BgfxResourceEntry & entry) +{ + if (!bgfx::isValid(entry.vb)) + { + return; + } + if (g_draw.useStaticVB + && bgfx::isValid(g_draw.staticVB) + && g_draw.staticVB.idx == entry.vb.idx) + { + g_draw.staticVB = BGFX_INVALID_HANDLE; + g_draw.useStaticVB = false; + } + g_caches.deferredDestroyStaticVB.push_back(entry.vb); + entry.vb = BGFX_INVALID_HANDLE; +} + +void DeferDestroyStaticIndexResource(BgfxResourceEntry & entry) +{ + if (!bgfx::isValid(entry.ib)) + { + return; + } + if (g_draw.useStaticIB + && bgfx::isValid(g_draw.staticIB) + && g_draw.staticIB.idx == entry.ib.idx) + { + g_draw.staticIB = BGFX_INVALID_HANDLE; + g_draw.useStaticIB = false; + } + g_caches.deferredDestroyStaticIB.push_back(entry.ib); + entry.ib = BGFX_INVALID_HANDLE; +} + +// TheSuperHackers @perf bobtista 02/06/2026 FNV-1a 64-bit content hash used to detect +// byte-identical re-uploads of static-eligible buffers so the GPU buffer recreate can be +// skipped. size_bytes seeds the hash so a size change can never collide with a content match. +static uint64_t HashBufferContent(const void * data, unsigned int size_bytes) +{ + uint64_t hash = 1469598103934665603ULL ^ static_cast(size_bytes); + const unsigned char * bytes = static_cast(data); + for (unsigned int i = 0; i < size_bytes; ++i) + { + hash ^= static_cast(bytes[i]); + hash *= 1099511628211ULL; + } + return hash; +} + +bool TryCaptureStaticVertexBuffer(const VertexBufferClass * vb, + const void * data, + unsigned int size_bytes) +{ + if (vb == nullptr || data == nullptr || !vb->Is_Backend_Static_Eligible()) + { + return false; + } + BgfxResourceEntry * entry = FindVertexBufferResourceEntry(vb); + if (entry == nullptr || bgfx::isValid(entry->dvb)) + { + return false; + } + 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) + { + return false; + } + const uint64_t contentHash = HashBufferContent(data, size_bytes); + if (bgfx::isValid(entry->vb)) + { + if (entry->vbContentHash == contentHash) + { + return true; + } + // TheSuperHackers @perf bobtista 02/06/2026 Content changed, so this + // static-eligible buffer is effectively dynamic. Drop the immutable buffer and + // fall through to the in-place dynamic path, which reuses one native buffer for + // the buffer's lifetime. Recreating an immutable buffer (and orphaning the old + // one) every frame was the dominant source of the "RefCount is 1 (expected 0)" + // leak warnings at shutdown. EnsureDynamicVertexBuffer marks entry->dvb valid, + // so subsequent uploads skip this static path entirely. + DeferDestroyStaticVertexResource(*entry); + return false; + } + bgfx::VertexLayout layout; + if (!BuildBgfxLayoutForFVF(vb->FVF_Info(), layout) || layout.getStride() != stride) + { + DestroyStaticVertexResource(*entry); + return false; + } + + bgfx::VertexBufferHandle h = bgfx::createVertexBuffer(bgfx::copy(data, size_bytes), layout); + DestroyStaticVertexResource(*entry); + if (!bgfx::isValid(h)) + { + return false; + } + entry->vb = h; + entry->vbContentHash = contentHash; + return true; +} + +bool TryCaptureStaticIndexBuffer(const IndexBufferClass * ib, + const void * data, + unsigned int size_bytes) +{ + if (ib == nullptr || data == nullptr || !ib->Is_Backend_Static_Eligible()) + { + return false; + } + BgfxResourceEntry * entry = FindIndexBufferResourceEntry(ib); + if (entry == nullptr || bgfx::isValid(entry->dib)) + { + return false; + } + const uint32_t buffer_bytes = static_cast(ib->Get_Index_Count()) * sizeof(uint16_t); + if (buffer_bytes == 0 || size_bytes != buffer_bytes) + { + return false; + } + const uint64_t contentHash = HashBufferContent(data, size_bytes); + if (bgfx::isValid(entry->ib)) + { + if (entry->ibContentHash == contentHash) + { + return true; + } + // TheSuperHackers @perf bobtista 02/06/2026 Content changed: demote to the in-place + // dynamic path instead of recreating an immutable buffer every frame. See the + // matching note in TryCaptureStaticVertexBuffer. + DeferDestroyStaticIndexResource(*entry); + return false; + } + bgfx::IndexBufferHandle h = bgfx::createIndexBuffer(bgfx::copy(data, size_bytes)); + DestroyStaticIndexResource(*entry); + if (!bgfx::isValid(h)) + { + return false; + } + entry->ib = h; + entry->ibContentHash = contentHash; + return true; +} + +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_caches.vb.find(vb); + if (it != g_caches.vb.end()) + { + // TheSuperHackers @perf bobtista 02/06/2026 Grow-only reuse. The cached + // num_verts is the bgfx buffer's CAPACITY. As long as the layout (stride) is + // unchanged and the buffer is at least as large as the engine now needs, reuse + // it and let the upload write only the live sub-range. The engine resizes these + // dynamic buffers nearly every frame; recreating a GPU buffer each time wasted + // CPU and (because bgfx keeps frames in flight) produced an unbounded stream of + // "RefCount is 1 (expected 0)" destroy warnings. Only recreate to GROW, or when + // the vertex layout changes. + if (it->second.stride == engine_stride + && it->second.num_verts >= num_verts + && bgfx::isValid(it->second.handle)) + { + MirrorDynamicVertexHandleToResource(vb, it->second.handle); + return it->second.handle; + } + ClearDynamicVertexHandleFromResource(vb, it->second.handle); + if (bgfx::isValid(it->second.handle)) + { + // Defer the destroy of the grown-out handle by one frame: a draw recorded + // earlier this frame may still reference it until bgfx::frame() executes. + g_caches.deferredDestroyVB.push_back(it->second.handle); + } + g_caches.pendingVbRangeUploads.erase(vb); + g_caches.vb.erase(it); + } + + if (num_verts == 0) + { + return BGFX_INVALID_HANDLE; + } + bgfx::VertexLayout layout; + if (!BuildBgfxLayoutForFVF(vb->FVF_Info(), layout)) + { + return BGFX_INVALID_HANDLE; + } + // Mismatched stride (typically for skinned-mesh weighted-position FVFs + // that BuildBgfxLayoutForFVF does not fully cover) + // would create a too-small bgfx buffer and cause truncation + // warnings + native buffer creation 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) + { + 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_caches.vb[vb] = e; + MirrorDynamicVertexHandleToResource(vb, BGFX_INVALID_HANDLE); + return BGFX_INVALID_HANDLE; + } + bgfx::DynamicVertexBufferHandle h = bgfx::createDynamicVertexBuffer(num_verts, layout); + g_stats.dynamicVbAllocations++; + BgfxVbCacheEntry e{ h, num_verts, engine_stride }; + g_caches.vb[vb] = e; + MirrorDynamicVertexHandleToResource(vb, h); + return h; +} + +bgfx::DynamicIndexBufferHandle EnsureDynamicIndexBuffer(const IndexBufferClass * ib) +{ + const uint32_t num_indices = static_cast(ib->Get_Index_Count()); + + auto it = g_caches.ib.find(ib); + if (it != g_caches.ib.end()) + { + // TheSuperHackers @perf bobtista 02/06/2026 Grow-only reuse; cached num_indices + // is the buffer CAPACITY. See the matching note in EnsureDynamicVertexBuffer. + if (it->second.num_indices >= num_indices && bgfx::isValid(it->second.handle)) + { + MirrorDynamicIndexHandleToResource(ib, it->second.handle); + return it->second.handle; + } + ClearDynamicIndexHandleFromResource(ib, it->second.handle); + if (bgfx::isValid(it->second.handle)) + { + // Defer the destroy of the grown-out handle by one frame. + g_caches.deferredDestroyIB.push_back(it->second.handle); + } + g_caches.pendingIbRangeUploads.erase(ib); + g_caches.ib.erase(it); + } + if (num_indices == 0) + { + return BGFX_INVALID_HANDLE; + } + bgfx::DynamicIndexBufferHandle h = bgfx::createDynamicIndexBuffer(num_indices); + g_stats.dynamicIbAllocations++; + BgfxIbCacheEntry e{ h, num_indices }; + g_caches.ib[ib] = e; + MirrorDynamicIndexHandleToResource(ib, h); + return h; +} + +void MarkPendingRangeUpload(BgfxPendingRangeUpload & pending, + uint32_t start_byte, + uint32_t size_bytes) +{ + const uint64_t end_byte64 = static_cast(start_byte) + size_bytes; + const uint32_t end_byte = end_byte64 > UINT32_MAX + ? UINT32_MAX + : static_cast(end_byte64); + if (!pending.valid) + { + pending.startByte = start_byte; + pending.endByte = end_byte; + pending.valid = true; + return; + } + if (start_byte < pending.startByte) + { + pending.startByte = start_byte; + } + if (end_byte > pending.endByte) + { + pending.endByte = end_byte; + } +} + +void FlushPendingVertexRangeUpload(const VertexBufferClass * vb) +{ + auto it = g_caches.pendingVbRangeUploads.find(vb); + if (it == g_caches.pendingVbRangeUploads.end() || !it->second.valid) + { + return; + } + + BgfxPendingRangeUpload pending = it->second; + g_caches.pendingVbRangeUploads.erase(it); + + const uint32_t stride = vb != nullptr ? vb->FVF_Info().Get_FVF_Size() : 0; + const uint32_t buffer_bytes = + (vb != nullptr) ? static_cast(vb->Get_Vertex_Count()) * stride : 0; + if (!g_device.initialized + || vb == nullptr + || stride == 0 + || buffer_bytes == 0 + || !vb->Has_CPU_Buffer_Data() + || vb->Get_CPU_Buffer_Size() < buffer_bytes) + { + return; + } + + uint32_t start_byte = pending.startByte - (pending.startByte % stride); + uint32_t end_byte = pending.endByte; + if (end_byte > buffer_bytes) + { + end_byte = buffer_bytes; + } + const uint32_t end_remainder = end_byte % stride; + if (end_remainder != 0) + { + end_byte += stride - end_remainder; + if (end_byte > buffer_bytes) + { + end_byte = buffer_bytes; + } + } + if (end_byte <= start_byte) + { + return; + } + + bgfx::DynamicVertexBufferHandle h = EnsureDynamicVertexBuffer(vb); + if (!bgfx::isValid(h)) + { + return; + } + + const uint32_t start_vertex = start_byte / stride; + const uint32_t size_bytes = end_byte - start_byte; + const unsigned char * src = vb->Peek_CPU_Buffer_Data() + start_byte; + const bgfx::Memory * mem = bgfx::copy(src, size_bytes); + LogBgfxBufferUpdate("vb-range-flush", vb, src, start_vertex, size_bytes, h.idx, mem); + bgfx::update(h, start_vertex, mem); +} + +void FlushPendingIndexRangeUpload(const IndexBufferClass * ib) +{ + auto it = g_caches.pendingIbRangeUploads.find(ib); + if (it == g_caches.pendingIbRangeUploads.end() || !it->second.valid) + { + return; + } + + BgfxPendingRangeUpload pending = it->second; + g_caches.pendingIbRangeUploads.erase(it); + + const uint32_t index_size = sizeof(uint16_t); + const uint32_t buffer_bytes = + (ib != nullptr) ? static_cast(ib->Get_Index_Count()) * index_size : 0; + if (!g_device.initialized + || ib == nullptr + || buffer_bytes == 0 + || !ib->Has_CPU_Buffer_Data() + || ib->Get_CPU_Buffer_Size() < buffer_bytes) + { + return; + } + + uint32_t start_byte = pending.startByte - (pending.startByte % index_size); + uint32_t end_byte = pending.endByte; + if (end_byte > buffer_bytes) + { + end_byte = buffer_bytes; + } + if ((end_byte % index_size) != 0) + { + end_byte += index_size - (end_byte % index_size); + if (end_byte > buffer_bytes) + { + end_byte = buffer_bytes; + } + } + if (end_byte <= start_byte) + { + return; + } + + bgfx::DynamicIndexBufferHandle h = EnsureDynamicIndexBuffer(ib); + if (!bgfx::isValid(h)) + { + return; + } + + const uint32_t start_index = start_byte / index_size; + const uint32_t size_bytes = end_byte - start_byte; + const unsigned char * src = ib->Peek_CPU_Buffer_Data() + start_byte; + const bgfx::Memory * mem = bgfx::copy(src, size_bytes); + LogBgfxBufferUpdate("ib-range-flush", ib, src, start_index, size_bytes, h.idx, mem); + bgfx::update(h, start_index, mem); +} +} + +// 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_caches.texture keyed by the +// engine's destination TextureClass so EnsureBgfxTexture finds it on lookup +// before reaching the POOL_DEFAULT early-out. + +void BgfxBackend::Upload_Vertex_Buffer_Data(const VertexBufferClass * vb, + const void * data, + unsigned int size_bytes) +{ + if (!g_device.initialized || vb == nullptr || data == nullptr || size_bytes == 0) + { + 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) + { + static bool s_loggedVbCaptureSkip = false; + if (!s_loggedVbCaptureSkip) + { + s_loggedVbCaptureSkip = true; + WWDEBUG_SAY(("[BgfxBackend] skip VB full-upload: " + "size_bytes=%u stride=%u total=%u", + size_bytes, stride, buffer_bytes)); + } + return; + } + g_caches.pendingVbRangeUploads.erase(vb); + if (TryCaptureStaticVertexBuffer(vb, data, size_bytes)) + { + return; + } + + bgfx::DynamicVertexBufferHandle h = EnsureDynamicVertexBuffer(vb); + if (!bgfx::isValid(h)) + { + return; + } + const bgfx::Memory * mem = bgfx::copy(data, size_bytes); + LogBgfxBufferUpdate("vb-full", vb, data, 0, size_bytes, h.idx, mem); + bgfx::update(h, 0, mem); +} + +void BgfxBackend::Upload_Index_Buffer_Data(const IndexBufferClass * ib, + const void * data, + unsigned int size_bytes) +{ + 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) + { + static bool s_loggedIbCaptureSkip = false; + if (!s_loggedIbCaptureSkip) + { + s_loggedIbCaptureSkip = true; + WWDEBUG_SAY(("[BgfxBackend] skip IB full-upload: " + "size_bytes=%u total=%u", size_bytes, buffer_bytes)); + } + return; + } + g_caches.pendingIbRangeUploads.erase(ib); + if (TryCaptureStaticIndexBuffer(ib, data, size_bytes)) + { + return; + } + bgfx::DynamicIndexBufferHandle h = EnsureDynamicIndexBuffer(ib); + if (!bgfx::isValid(h)) + { + return; + } + const bgfx::Memory * mem = bgfx::copy(data, size_bytes); + LogBgfxBufferUpdate("ib-full", ib, data, 0, size_bytes, h.idx, mem); + bgfx::update(h, 0, mem); +} + +void BgfxBackend::Upload_Vertex_Buffer_Sub_Range(const VertexBufferClass * vb, + const void * data, + unsigned int start_vertex, + unsigned int size_bytes) +{ + if (!g_device.initialized || vb == nullptr || data == nullptr || size_bytes == 0) + { + return; + } + // Guard: total update must fit in the dynamic VB allocation. + const uint32_t stride = vb->FVF_Info().Get_FVF_Size(); + if (stride == 0) + { + static bool s_loggedVbStrideZero = false; + if (!s_loggedVbStrideZero) + { + s_loggedVbStrideZero = true; + WWDEBUG_SAY(("[BgfxBackend] skip VB upload: 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) + { + static bool s_loggedVbSubRangeOor = false; + if (!s_loggedVbSubRangeOor) + { + s_loggedVbSubRangeOor = true; + WWDEBUG_SAY(("[BgfxBackend] skip VB upload: out-of-range " + "start_vert=%u size_bytes=%u stride=%u total=%u", + start_vertex, size_bytes, stride, buffer_bytes)); + } + return; + } + if (BgfxResourceEntry * entry = FindVertexBufferResourceEntry(vb)) + { + // TheSuperHackers @bugfix bobtista 06/06/2026 Defer the static-buffer destroy (matching + // TryCaptureStaticVertexBuffer) so a buffer still referenced by an earlier draw this frame + // is not freed before bgfx::frame() - the RefCount-leak / one-frame use-after-free case. + DeferDestroyStaticVertexResource(*entry); + } + bgfx::DynamicVertexBufferHandle h = EnsureDynamicVertexBuffer(vb); + if (!bgfx::isValid(h)) + { + return; + } + const uint32_t byte_offset = start_vertex * stride; + if (CoalesceDynamicRangeUploadsEnabled() + && vb->Has_CPU_Buffer_Data() + && vb->Get_CPU_Buffer_Size() >= buffer_bytes) + { + MarkPendingRangeUpload(g_caches.pendingVbRangeUploads[vb], + byte_offset, + size_bytes); + LogBgfxBufferUpdate("vb-range-defer", vb, data, start_vertex, size_bytes, h.idx, nullptr); + return; + } + const bgfx::Memory * mem = bgfx::copy(data, size_bytes); + LogBgfxBufferUpdate("vb-range", vb, data, start_vertex, size_bytes, h.idx, mem); + bgfx::update(h, start_vertex, mem); + +} + +void BgfxBackend::Upload_Index_Buffer_Sub_Range(const IndexBufferClass * ib, + const void * data, + unsigned int start_index, + unsigned int size_bytes) +{ + 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); + const uint64_t end_byte = static_cast(start_index) * sizeof(uint16_t) + size_bytes; + if (end_byte > buffer_bytes) + { + static bool s_loggedIbSubRangeOor = false; + if (!s_loggedIbSubRangeOor) + { + s_loggedIbSubRangeOor = true; + WWDEBUG_SAY(("[BgfxBackend] skip IB upload: out-of-range " + "start_idx=%u size_bytes=%u total=%u", + start_index, size_bytes, buffer_bytes)); + } + return; + } + if (BgfxResourceEntry * entry = FindIndexBufferResourceEntry(ib)) + { + // TheSuperHackers @bugfix bobtista 06/06/2026 Defer the static-buffer destroy (matching + // TryCaptureStaticIndexBuffer) so a buffer still referenced by an earlier draw this frame + // is not freed before bgfx::frame(). + DeferDestroyStaticIndexResource(*entry); + } + bgfx::DynamicIndexBufferHandle h = EnsureDynamicIndexBuffer(ib); + if (!bgfx::isValid(h)) + { + return; + } + const uint32_t byte_offset = start_index * sizeof(uint16_t); + if (CoalesceDynamicRangeUploadsEnabled() + && ib->Has_CPU_Buffer_Data() + && ib->Get_CPU_Buffer_Size() >= buffer_bytes) + { + MarkPendingRangeUpload(g_caches.pendingIbRangeUploads[ib], + byte_offset, + size_bytes); + LogBgfxBufferUpdate("ib-range-defer", ib, data, start_index, size_bytes, h.idx, nullptr); + return; + } + const bgfx::Memory * mem = bgfx::copy(data, size_bytes); + LogBgfxBufferUpdate("ib-range", ib, data, start_index, size_bytes, h.idx, mem); + bgfx::update(h, start_index, mem); + +} + +// TheSuperHackers @refactor bobtista 11/04/2026 Sorted draw pass routing. Begin/End flip +// the flag that routes SubmitEngineDraw to kBgfxEngineSortView; Capture stores the +// per-batch world as sortView^T * sortWorld^T directly in bgfx column-major layout. + +static bool s_sortedTransformCacheValid = false; +static unsigned char s_sortedTransformWorldCache[sizeof(Matrix4x4)]; +static unsigned char s_sortedTransformViewCache[sizeof(Matrix4x4)]; + +static void InvalidateSortedTransformCache() +{ + s_sortedTransformCacheValid = false; +} + +static bool DisableSortedMaterialRecaptureSkip() +{ + static const bool s_disabled = + GgcFlags::Enabled(GgcFlag_BgfxDisableSortedMaterialRecaptureSkip); + return s_disabled; +} + +static bool DisableSortedMaterialSnapshot() +{ + static const bool s_disabled = + GgcFlags::Enabled(GgcFlag_BgfxDisableSortedMaterialSnapshot); + return s_disabled; +} + +static void ApplySortedMaterialSnapshotForBgfx(const RenderBackendSortedMaterialSnapshot & material, + const VertexMaterialClass * sourceMaterial) +{ + g_draw.sourceMaterial = sourceMaterial; + g_draw.explicitMaterialState = false; + for (int i = 0; i < 4; ++i) + { + g_draw.matDiffuse[i] = material.diffuse[i]; + g_draw.matAmbient[i] = material.ambient[i]; + g_draw.matEmissive[i] = material.emissive[i]; + g_draw.matSpecular[i] = material.specular[i]; + } + // TheSuperHackers @bugfix bobtista 02/07/2026 Mirror Set_Material exactly: it + // writes only vertexColorFlags[1..3] (the material color sources) and + // lightingEnabled[0]. vertexColorFlags[0] is owned by Set_Vertex_Buffer (the + // "bound FVF has a per-vertex diffuse" flag) and must not be touched here. The + // snapshot never populated index [0], so copying all four stomped it to 0, + // making the shader substitute the fixed-function white default and rendering + // every per-vertex-colored sorted particle white (propaganda, smoke, EMP rings). + g_draw.vertexColorFlags[1] = material.vertex_color_flags[1]; + g_draw.vertexColorFlags[2] = material.vertex_color_flags[2]; + g_draw.vertexColorFlags[3] = material.vertex_color_flags[3]; + g_draw.lightingEnabled[0] = material.lighting_enabled[0]; +} + +void BgfxBackend::Begin_Sorted_Batch_Pass() +{ + g_views.inSortFlush = true; + g_views.sortedBatchMaterialCaptured = false; + InvalidateSortedTransformCache(); + if (!g_frame.sortProjCaptured) + { + std::memcpy(g_frame.sortProj, g_frame.proj, sizeof(g_frame.sortProj)); + g_frame.sortProjCaptured = true; + } +} + +void BgfxBackend::End_Sorted_Batch_Pass() +{ + g_views.inSortFlush = false; + g_views.sortedBatchDrawFlags = RB_SORTED_DRAW_NONE; + g_views.sortedBatchMaterialCaptured = false; + InvalidateSortedTransformCache(); +} + +static void CaptureSortedBatchTransformsForBgfx(const Matrix4x4 & sortWorld, + const Matrix4x4 & sortView) +{ + // Compute the legacy row-major product sortWorld * sortView, then store + // it as row-major float[16] (r*4+c). bgfx native backends interpret 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_frame.sortWorld[r * 4 + c] = s; + // Store raw model (no camera view baked in) + // for shadow caster submissions. + } + } + // Store raw sortWorld (model-to-world only). sortWorld is captured from + // RenderStateStruct's legacy D3DMATRIX storage, reinterpreted as Matrix4x4 + // by the sorting renderer; do not run it through the normal W3D matrix + // conversion helper or it gets transposed a second time. Sorted local-model + // cards such as the Comanche rotor blur render through the normal camera + // view and need this raw per-mesh world matrix. + for (int r = 0; r < 4; ++r) + { + for (int c = 0; c < 4; ++c) + { + g_frame.sortWorldRaw[r * 4 + c] = sortWorld[r][c]; + // View with identity world, for runs whose vertices were baked to + // world space at sorted-pool fill time. + g_frame.sortViewOnly[r * 4 + c] = sortView[r][c]; + } + } +} + +static void ApplyMaterialMappersForBgfx(BgfxBackend * backend, const VertexMaterialClass * material); + +void BgfxBackend::Apply_Sorted_Batch_State(const RenderBackendSortedBatchState & state) +{ + static const bool s_trackSortedReplayPhases = IsBgfxStatsLoggingEnabled(); + LARGE_INTEGER replayStart; + LARGE_INTEGER segmentStart; + LARGE_INTEGER segmentEnd; + if (s_trackSortedReplayPhases) + { + QueryPerformanceCounter(&replayStart); + g_stats.sortedReplayCalls++; + } + + g_views.sortedBatchDrawFlags = state.draw_flags; + if (state.shader != nullptr) + { + if (s_trackSortedReplayPhases) + { + QueryPerformanceCounter(&segmentStart); + } + Set_Shader(*state.shader); + // TheSuperHackers @bugfix bobtista 10/06/2026 The sorted replay restores the captured shader's + // depth state through Set_Shader, but the cull face is read from the separate semantic cull + // (FixedFunctionState::Cull_Mode), which Set_Shader does not update. A captured CULL_MODE_DISABLE + // - used by two-sided segmented-line ribbons such as rally/waypoint lines - was therefore lost, + // and whatever cull was last set (typically CW) culled the backward-wound ribbon segments, + // making parts of the line vanish. Restore the captured shader's cull mode so two-sided sorted + // geometry draws both faces, matching the DX8 path where applying a shader set the cull state. + Set_Cull_Mode(state.shader->Get_Cull_Mode() == ShaderClass::CULL_MODE_ENABLE ? RB_CULL_CW : RB_CULL_NONE); + if (s_trackSortedReplayPhases) + { + QueryPerformanceCounter(&segmentEnd); + g_stats.sortedReplayShaderTicks += segmentEnd.QuadPart - segmentStart.QuadPart; + } + } + if (s_trackSortedReplayPhases) + { + QueryPerformanceCounter(&segmentStart); + } + const bool sortedMaterialSnapshotEnabled = + state.material_snapshot.valid + && !DisableSortedMaterialSnapshot() + && !DisableSortedMaterialRecaptureSkip(); + if (sortedMaterialSnapshotEnabled) + { + ApplySortedMaterialSnapshotForBgfx(state.material_snapshot, state.material); + // The snapshot bypasses Set_Material, so run the mappers explicitly or + // sorted meshes with animated UV mappers replay stale texture transforms. + ApplyMaterialMappersForBgfx(this, state.material); + g_views.sortedBatchMaterialCaptured = true; + } + else + { + Set_Material(state.material); + g_views.sortedBatchMaterialCaptured = !DisableSortedMaterialRecaptureSkip(); + } + if (s_trackSortedReplayPhases) + { + QueryPerformanceCounter(&segmentEnd); + g_stats.sortedReplayMaterialTicks += segmentEnd.QuadPart - segmentStart.QuadPart; + } + + if (s_trackSortedReplayPhases) + { + QueryPerformanceCounter(&segmentStart); + } + for (unsigned i = 0; i < RB_MAX_TEXTURE_STAGES; ++i) + { + Set_Texture(i, state.textures[i]); + } + if (s_trackSortedReplayPhases) + { + QueryPerformanceCounter(&segmentEnd); + g_stats.sortedReplayTextureTicks += segmentEnd.QuadPart - segmentStart.QuadPart; + } + + if (s_trackSortedReplayPhases) + { + QueryPerformanceCounter(&segmentStart); + } + if (state.world != nullptr && state.view != nullptr) + { + static const bool s_disableSortedTransformRestoreSkip = + GgcFlags::Enabled(GgcFlag_BgfxDisableSortedTransformRestoreSkip); + const bool sameTransform = + !s_disableSortedTransformRestoreSkip + && s_sortedTransformCacheValid + && std::memcmp(s_sortedTransformWorldCache, state.world, sizeof(Matrix4x4)) == 0 + && std::memcmp(s_sortedTransformViewCache, state.view, sizeof(Matrix4x4)) == 0; + if (!sameTransform) + { + CaptureSortedBatchTransformsForBgfx(*state.world, *state.view); + std::memcpy(s_sortedTransformWorldCache, state.world, sizeof(Matrix4x4)); + std::memcpy(s_sortedTransformViewCache, state.view, sizeof(Matrix4x4)); + s_sortedTransformCacheValid = true; + } + } + if (s_trackSortedReplayPhases) + { + QueryPerformanceCounter(&segmentEnd); + g_stats.sortedReplayTransformTicks += segmentEnd.QuadPart - segmentStart.QuadPart; + } + + if (s_trackSortedReplayPhases) + { + QueryPerformanceCounter(&segmentStart); + } + for (int i = 0; i < 4; ++i) + { + const RenderBackendLight & light = state.lights.lights[i]; + if (state.lights.enabled[i]) + { + g_draw.lightDirs[i][0] = -light.direction[0]; + g_draw.lightDirs[i][1] = -light.direction[1]; + g_draw.lightDirs[i][2] = -light.direction[2]; + g_draw.lightDirs[i][3] = 1.0f; + g_draw.lightColors[i][0] = light.diffuse[0]; + g_draw.lightColors[i][1] = light.diffuse[1]; + g_draw.lightColors[i][2] = light.diffuse[2]; + g_draw.lightColors[i][3] = 1.0f; + g_draw.lightAmbients[i][0] = light.ambient[0]; + g_draw.lightAmbients[i][1] = light.ambient[1]; + g_draw.lightAmbients[i][2] = light.ambient[2]; + g_draw.lightAmbients[i][3] = 1.0f; + // TheSuperHackers @bugfix bobtista 17/07/2026 Reconstruct point lights from the + // captured legacy encoding (range = orad, attenuation1 = 0.1/irad) instead of + // flattening every light to directional; a zero direction would otherwise hit the + // NaN guard and light the node from straight above. + if (light.type == 1 /* legacy D3DLIGHT_POINT */) + { + g_draw.lightPositions[i][0] = light.position[0]; + g_draw.lightPositions[i][1] = light.position[1]; + g_draw.lightPositions[i][2] = light.position[2]; + g_draw.lightPositions[i][3] = 1.0f; + g_draw.lightParams[i][0] = (light.attenuation[1] > 1e-8f) + ? 0.1f / light.attenuation[1] : light.range; + g_draw.lightParams[i][1] = light.range; + g_draw.lightParams[i][2] = 1.0f; + } + else + { + g_draw.lightParams[i][0] = 0.0f; + g_draw.lightParams[i][1] = 0.0f; + g_draw.lightParams[i][2] = 0.0f; + } + g_draw.lightParams[i][3] = 1.0f; + } + else + { + g_draw.lightDirs[i][3] = 0.0f; + g_draw.lightParams[i][3] = 0.0f; + } + } + if (s_trackSortedReplayPhases) + { + QueryPerformanceCounter(&segmentEnd); + g_stats.sortedReplayLightTicks += segmentEnd.QuadPart - segmentStart.QuadPart; + g_stats.sortedReplayTotalTicks += segmentEnd.QuadPart - replayStart.QuadPart; + } +} + +void BgfxBackend::Set_Point_Group_Render_Active(bool active) +{ + g_views.pointGroupRenderActive = active; +} + +void BgfxBackend::Set_Streak_Render_Active(bool active) +{ + g_views.streakRenderActive = active; +} + +void BgfxBackend::Set_Mesh_Render_Active(bool active) +{ + g_views.meshRenderActive = active; +} + +// TheSuperHackers @feature bobtista 07/07/2026 GGC_BGFX_DISABLE_SORTED_MESH_ROUTING +// restores the texture-name-only routing of model-space sorted draws for A/B +// comparison and as a fallback if the mesh-origin routing ever regresses. +static bool SortedMeshRoutingDisabled() +{ + static const bool s_disabled = + GgcFlags::Enabled(GgcFlag_BgfxDisableSortedMeshRouting); + return s_disabled; +} + +static const char * TextureDebugName(TextureBaseClass * texture); +static bool IsCommandCenterEmblemTextureName(const char *name); +static bool IsRotorBlurTextureName(const char *name); + +// Logs (once per texture, under GGC_TRACE) mesh-origin sorted draws that no +// texture-name predicate covers - the cases the name-keyed routing used to +// silently misplace, and the first place to look when a model-space sorted +// effect misbehaves on a new faction variant. +static void LogSortedMeshDrawWithoutNamePredicate(TextureBaseClass * texture, const char * texName) +{ + static const bool s_trace = (GgcFlags::Enabled(GgcFlag_Trace)); + if (!s_trace) + { + return; + } + static TextureBaseClass * s_seen[16]; + static int s_seenCount = 0; + for (int i = 0; i < s_seenCount; ++i) + { + if (s_seen[i] == texture) + { + return; + } + } + if (s_seenCount < 16) + { + s_seen[s_seenCount++] = texture; + } + std::fprintf(stderr, + "[ggc] mesh-origin sorted draw handled by origin flag (no name predicate): tex=%s\n", + texName != nullptr ? texName : "(null)"); +} + +static void ComputeSortedTextureArraySlot(RenderStateStruct & state); +static uint64_t ComputeFinalDrawState(bool triangle_strip); + +void BgfxBackend::Capture_Legacy_Render_State_For_Sorted_Draw(RenderStateStruct & state) +{ + GGC_RPROFILE(SORTED_CAPTURE); + // Transitional boundary for sorted replay. SortingRenderer snapshots a + // full fixed-function state so it can replay translucent geometry later. + // bgfx still mirrors draw state into FixedFunctionState for that snapshot + // today; future phases should make that state shape backend-neutral too. + FixedFunctionState::Capture_Render_State(state); + state.sorted_array_page = -1; + state.sorted_array_layer = -1; + state.sorted_array_scale_u = 1.0f; + state.sorted_array_scale_v = 1.0f; + if (g_views.pointGroupRenderActive) + { + state.sorted_draw_flags |= RB_SORTED_DRAW_POINT_GROUP; + } + if (g_views.streakRenderActive) + { + state.sorted_draw_flags |= RB_SORTED_DRAW_STREAK; + } + const bool meshLocalModelDraw = g_views.meshRenderActive && !SortedMeshRoutingDisabled(); + if (meshLocalModelDraw) + { + state.sorted_draw_flags |= RB_SORTED_DRAW_MESH; + } + + // TheSuperHackers @bugfix bobtista 17/05/2026 These sorted meshes are authored in local + // model space, but FixedFunctionState's world is hard-wired to identity on bgfx; capture + // the live per-mesh world so the replay places and rotates them correctly. + // TheSuperHackers @feature bobtista 07/07/2026 Mesh-origin sorted draws always capture + // their world via the RB_SORTED_DRAW_MESH flag; the texture-name list remains for + // non-mesh special cases and as the name-only fallback behind + // GGC_BGFX_DISABLE_SORTED_MESH_ROUTING. + const char *texName = TextureDebugName(g_draw.sourceTextures[0]); + const bool namedLocalModelDraw = texName != nullptr + && (IsCommandCenterEmblemTextureName(texName) + || IsRotorBlurTextureName(texName) + || ContainsCaseInsensitive(texName, "ubsnkatak_01") + || ContainsCaseInsensitive(texName, "coplight")); + if (meshLocalModelDraw && !namedLocalModelDraw) + { + LogSortedMeshDrawWithoutNamePredicate(g_draw.sourceTextures[0], texName); + } + if (namedLocalModelDraw || meshLocalModelDraw) + { + FixedFunctionState::Transform_Matrix( + static_cast(RB_TRANSFORM_WORLD), state.world); + } + else + { + ComputeSortedTextureArraySlot(state); + } + // TheSuperHackers @performance bobtista 10/07/2026 Resolve the final + // pipeline-state word now, while g_draw holds the exact translated state + // this node's replayed submit will apply. The packet submit consumes it + // instead of re-deriving per draw when the resolved-pipeline flag is on. + // The replay context differs from the capture context in three ways that + // feed the state word: the sorted-draw flag predicates read + // g_views.sortedBatchDrawFlags, which Apply_Sorted_Batch_State populates + // from this node's flags; the replay derives the cull mode from the + // captured shader (two-sided sorted geometry keeps CULL_MODE_DISABLE) + // rather than from the inserting renderer's live cull; and the flush-only + // predicates (rotor-blur cull exemption and friends) require + // g_views.inSortFlush. Mirror all three around the resolve. + { + const unsigned int savedBatchDrawFlags = g_views.sortedBatchDrawFlags; + const CullMode savedCullMode = Get_Cull_Mode(); + const bool savedInSortFlush = g_views.inSortFlush; + g_views.sortedBatchDrawFlags = state.sorted_draw_flags; + g_views.inSortFlush = true; + Set_Cull_Mode(state.shader.Get_Cull_Mode() == ShaderClass::CULL_MODE_ENABLE + ? RB_CULL_CW + : RB_CULL_NONE); + const uint64_t resolved = ComputeFinalDrawState(false); + Set_Cull_Mode(savedCullMode); + g_views.inSortFlush = savedInSortFlush; + g_views.sortedBatchDrawFlags = savedBatchDrawFlags; + state.resolved_state_lo = static_cast(resolved & 0xffffffffu); + state.resolved_state_hi = static_cast(resolved >> 32); + state.resolved_state_valid = true; + } +} + +void BgfxBackend::Restore_Legacy_Render_State_For_Sorted_Draw(const RenderStateStruct & state) +{ + (void)state; +} + +void BgfxBackend::Release_Legacy_Render_State_For_Sorted_Draw() +{ + FixedFunctionState::Release_Render_State(); +} + +// TheSuperHackers @refactor bobtista 26/04/2026 Shared submit helpers used by +// both Submit_Sorted_Draw and SubmitEngineDraw to avoid duplicated blocks. +static uint64_t ApplyCullModeOverride(uint64_t state) +{ + CullMode cullMode = static_cast(FixedFunctionState::Cull_Mode(RB_CULL_NONE)); + state &= ~(BGFX_STATE_CULL_CW | BGFX_STATE_CULL_CCW); + if (cullMode == RB_CULL_CW) + { + state |= BGFX_STATE_CULL_CW; + } + else if (cullMode == RB_CULL_CCW) + { + state |= BGFX_STATE_CULL_CCW; + } + return state; +} + +static uint64_t ApplyColorWriteOverride(uint64_t state) +{ + if (g_overrides.colorWriteOverride >= 0) + { + state &= ~(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); + state |= static_cast(g_overrides.colorWriteOverride); + } + return state; +} + +static uint64_t ApplyBlendEquation(uint64_t state) +{ + state &= ~BGFX_STATE_BLEND_EQUATION_MASK; + if ((state & BGFX_STATE_BLEND_MASK) != 0) + { + state |= g_draw.blendEquationBits; + } + return state; +} + +static bool IsOpaqueBlend(BlendFactor src, BlendFactor dest) +{ + return src == RB_BLEND_ONE && dest == RB_BLEND_ZERO; +} + +static uint64_t ApplyBlendState(uint64_t state) +{ + state &= ~(BGFX_STATE_BLEND_MASK | BGFX_STATE_BLEND_EQUATION_MASK); + const bool blendEnabled = g_overrides.blendEnableActive + ? g_overrides.blendEnableValue + : g_draw.alphaBlendEnabled; + if (blendEnabled) + { + const uint64_t blendBits = g_overrides.blendActive + ? g_overrides.blendBits + : g_draw.blendFuncBits; + state |= blendBits; + state |= g_draw.blendEquationBits; + } + return state; +} + +static uint64_t ApplyDepthState(uint64_t state) +{ + state &= ~(BGFX_STATE_DEPTH_TEST_MASK | BGFX_STATE_WRITE_Z); + if (g_draw.depthTestEnabled) + { + state |= g_draw.depthFuncBits; + if (g_draw.depthWriteEnabled) + { + state |= BGFX_STATE_WRITE_Z; + } + } + return state; +} + +static uint64_t GetEffectiveDrawState() +{ + uint64_t state = (g_draw.state != 0) + ? g_draw.state + : (BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); + state = ApplyDepthState(state); + state = ApplyBlendState(state); + return state; +} + +static bool LegacyStencilShadowsEnabled() +{ + return BgfxStencilShadowsEnabled() + || GgcFlags::Enabled(GgcFlag_EnableLegacyStencilShadows); +} + +static bool ShouldLogBgfxStencilShadows() +{ + return GgcFlags::Enabled(GgcFlag_StencilShadowDiag) + || GgcFlags::Enabled(GgcFlag_ShadowPathDiag); +} + +static bool BgfxPreMeshStencilShadows() +{ + // bgfx view order is global, not call-order based. Submitting the legacy + // stencil volumes into the engine view preserves the W3D call order used + // by the bgfx path: terrain/projected decals first, stencil darken next, + // opaque meshes later. That keeps volume shadows on terrain without + // multiplying the lighting on units/buildings/effects. + return !GgcFlags::Enabled(GgcFlag_BgfxLegacyPostMeshStencilShadows); +} + +static bgfx::ViewId BgfxShadowVolumeSubmitView() +{ + return BgfxPreMeshStencilShadows() ? kBgfxEngineView : kBgfxShadowVolumeView; +} + +static uint64_t BgfxShadowVolumeDepthState() +{ + const char *depth = GgcFlags::StringValue(GgcFlag_BgfxStencilDepth); + if (depth != nullptr && std::strcmp(depth, "less") == 0) + { + return BGFX_STATE_DEPTH_TEST_LESS; + } + if (depth != nullptr && std::strcmp(depth, "always") == 0) + { + return BGFX_STATE_DEPTH_TEST_ALWAYS; + } + return BGFX_STATE_DEPTH_TEST_LEQUAL; +} + +static bool BgfxTwoSidedStencilVolumes() +{ + // TheSuperHackers @bugfix bobtista 04/06/2026 Keep two-sided stencil + // shadow volumes opt-in. Defaulting it on (the attempt to halve + // shadow-volume submissions) miscounts the stencil for elevated casters: + // aircraft, helicopters and tall/stilted buildings draw a dark band or + // fan instead of a ground shadow. The legacy two-pass submit is correct; + // opt in with GGC_BGFX_STENCIL_TWO_SIDED only for A/B testing. + static const bool cached = GgcFlags::Enabled(GgcFlag_BgfxStencilTwoSided); + return cached; +} + +static unsigned BgfxShadowCullModeBits() +{ + // Use the same face selection as the W3D/DX8 shadow-volume pass. The + // earlier bgfx-only inversion made the default z-pass counts cancel out + // on useful receivers, leaving vehicles and aircraft without shadows. + if (!GgcFlags::Enabled(GgcFlag_BgfxStencilInvertCull)) + { + return g_draw.cullModeBits; + } + if (g_draw.cullModeBits == 1) + { + return 2; + } + if (g_draw.cullModeBits == 2) + { + return 1; + } + return g_draw.cullModeBits; +} + +static void BindShadowVolumeBiasUniform() +{ + if (!bgfx::isValid(g_uniforms.uShadowBias)) + { + return; + } + + float bias[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + if (GgcFlags::Enabled(GgcFlag_BgfxStencilClampClip)) + { + bias[1] = 1.0f; + } + bgfx::setUniform(g_uniforms.uShadowBias, bias); +} + +static bool BgfxSwapTwoSidedStencilVolumeOps() +{ + const char *algo = GgcFlags::StringValue(GgcFlag_BgfxStencilAlgo); + return algo != nullptr && std::strcmp(algo, "zpass-swap") == 0; +} + +static void LogBgfxStencilShadowEvent(const char *event, const char *reason, + unsigned countA, unsigned countB) +{ + if (!ShouldLogBgfxStencilShadows()) + { + return; + } + + if (FILE *diag = std::fopen("ggc_stencil_shadow_diag.txt", "a")) + { + std::fprintf(diag, + "%s frame=%u enabled=%d mode=%d active=%d submits=%u a=%u b=%u reason=%s\n", + event, + g_stats.frameIndex, + LegacyStencilShadowsEnabled() ? 1 : 0, + static_cast(GetBgfxShadowMode()), + g_views.shadowVolumeActive ? 1 : 0, + g_stats.shadowVolumeSubmits, + countA, + countB, + reason != nullptr ? reason : ""); + std::fclose(diag); + } +} + +static bool IsStandardAlphaBlend(uint64_t state) +{ + const uint64_t kAlphaSA_ISA = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, + BGFX_STATE_BLEND_INV_SRC_ALPHA); + return (state & BGFX_STATE_BLEND_MASK) == kAlphaSA_ISA; +} + +static bool IsOneOneAdditiveBlend(uint64_t state) +{ + const uint64_t kAddOneOne = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ONE, + BGFX_STATE_BLEND_ONE); + return (state & BGFX_STATE_BLEND_MASK) == kAddOneOne; +} + +static bool IsAnyAdditiveBlend(uint64_t state) +{ + const uint64_t blend = state & BGFX_STATE_BLEND_MASK; + const uint64_t kOneOne = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ONE, + BGFX_STATE_BLEND_ONE); + const uint64_t kSaOne = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, + BGFX_STATE_BLEND_ONE); + return blend == kOneOne || blend == kSaOne; +} + +static bool IsMultiplicativeBlend(uint64_t state) +{ + const uint64_t blend = state & BGFX_STATE_BLEND_MASK; + const uint64_t kZeroSrcColor = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ZERO, + BGFX_STATE_BLEND_SRC_COLOR); + const uint64_t kDstColorZero = BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_DST_COLOR, + BGFX_STATE_BLEND_ZERO); + return blend == kZeroSrcColor || blend == kDstColorZero; +} + +static bool IsSoftParticleCandidate(uint64_t state) +{ + // Soft depth fading is only appropriate for particle-style sorted quads. + // Some sorted alpha draws are world decals/material passes (for example + // command-center floor emblems) that sit directly on opaque geometry and + // must not be faded out against the scene depth. + return IsStandardAlphaBlend(state) && g_draw.tssOps0[0] < 1.5f; +} + +static bool IsSortedMaterialDecal(uint64_t state) +{ + return IsStandardAlphaBlend(state) + && !IsSoftParticleCandidate(state) + && g_draw.tssOps0[0] > 2.5f && g_draw.tssOps0[0] < 3.5f + && g_draw.tssOps0[3] > 0.5f; +} + +static const char * TextureDebugName(TextureBaseClass * texture); +static bool IsCommandCenterEmblemTextureName(const char *name); + +// TheSuperHackers @performance bobtista 15/06/2026 The sorted-draw routing predicates +// scan the stage-0 texture name for special-case effect markers. During the sort flush +// these run for ~1k particle draws/frame. Get_Full_Path returns a stable member ref (no +// alloc), but the repeated case-insensitive scans add up. Cache the marker results +// keyed by the bound texture pointer so consecutive draws of one texture reuse them. +struct SortedTexNameFlags { bool snk01; bool snk0; bool rotor; bool coplight; bool commandCenterEmblem; bool lightbeam; }; +static const SortedTexNameFlags & GetSortedTexNameFlags(); + +static bool IsSortedAlphaDepthDecal(uint64_t state) +{ + const unsigned particleFlags = RB_SORTED_DRAW_POINT_GROUP | RB_SORTED_DRAW_STREAK; + return g_views.inSortFlush + && (g_views.sortedBatchDrawFlags & particleFlags) == 0 + && IsStandardAlphaBlend(state) + && !IsSoftParticleCandidate(state) + && (state & BGFX_STATE_WRITE_Z) == 0 + && g_draw.tssOps0[0] > 2.5f && g_draw.tssOps0[0] < 3.5f + && g_draw.tssOps0[1] > 2.5f && g_draw.tssOps0[1] < 3.5f + && g_draw.tssOps0[2] < 0.5f + && g_draw.tssOps0[3] < 0.5f + && (g_draw.texcoordSelect2[0] < 0.5f + || GetSortedTexNameFlags().snk01); +} + +static bool IsSortedParticleEffect(uint64_t state) +{ + const unsigned particleFlags = RB_SORTED_DRAW_POINT_GROUP | RB_SORTED_DRAW_STREAK; + return g_views.inSortFlush + && (g_views.sortedBatchDrawFlags & particleFlags) != 0 + && (state & BGFX_STATE_BLEND_MASK) != 0; +} + +static bool IsSneakAttackAlphaDepthDecal(uint64_t state) +{ + return IsSortedAlphaDepthDecal(state) + && GetSortedTexNameFlags().snk01; +} + +static bool IsSneakAttackCoplanarSurface() +{ + const SortedTexNameFlags & f = GetSortedTexNameFlags(); + return f.snk0 && !f.snk01; +} + +static bool ShouldApplySubmittedNormalBias(uint64_t state) +{ + return IsSortedMaterialDecal(state) + || IsSortedAlphaDepthDecal(state) + || IsSneakAttackCoplanarSurface(); +} + +static bool CoplanarBiasGateEnabled() +{ + // TheSuperHackers @performance bobtista 04/06/2026 GGC_NO_COPLANAR_BIAS_GATE + // restores the unconditional O(n^2) coplanar-pair scan on every dynamic vertex + // write (the pre-gate behavior) for A/B measurement and as a fallback if a + // sorted-decal depth-bias regression is ever observed. + static const bool s_enabled = (!GgcFlags::Enabled(GgcFlag_NoCoplanarBiasGate)); + return s_enabled; +} + +static bool ShouldScanSubmittedNormalBiasForCurrentDynamicWrite() +{ + if (!CoplanarBiasGateEnabled()) + { + return true; + } + + if (g_views.pointGroupRenderActive || g_views.streakRenderActive) + { + return false; + } + + return ShouldApplySubmittedNormalBias(GetEffectiveDrawState()); +} + +// TheSuperHackers @feature bobtista 07/07/2026 A replayed sorted batch flagged as +// mesh-origin carries its live per-mesh world (captured above) and is authored in +// model space, so it must render through the engine view with that raw world. This +// covers every model mesh generically; the name predicates below remain for the +// non-mesh special cases and their extra per-effect handling. +static bool IsSortedMeshModelDraw(uint64_t /*state*/) +{ + return g_views.inSortFlush + && (g_views.sortedBatchDrawFlags & RB_SORTED_DRAW_MESH) != 0; +} + +static bool IsSortedRotorBlur(uint64_t state) +{ + return g_views.inSortFlush + && IsStandardAlphaBlend(state) + && ((g_draw.tssOps0[0] > 0.5f && g_draw.tssOps0[0] < 1.5f) + || (g_draw.tssOps0[0] > 2.5f && g_draw.tssOps0[0] < 3.5f)) + && ((g_draw.tssOps0[1] > 0.5f && g_draw.tssOps0[1] < 1.5f) + || (g_draw.tssOps0[1] > 2.5f && g_draw.tssOps0[1] < 3.5f)) + && g_draw.tssOps0[2] < 0.5f + && g_draw.tssOps0[3] < 0.5f + && GetSortedTexNameFlags().rotor; +} + +// TheSuperHackers @bugfix bobtista 25/05/2026 Police-car lightbar glow meshes +// (CopLight*.tga) live in model space and rely on the per-mesh world transform +// the same way the Chinook rotor blur and Sneak Attack dirt plane do. Routing +// them through the sort view, whose pre-view-multiplied matrix and Z-biased +// projection are tuned for camera-facing particles, washes them out and leaves +// the glow dim/invisible. Route through the engine view with the raw model +// world so each animated coplight quad lands at the lightbar with normal +// brightness, exactly like the other model-space sorted meshes above. +static bool IsSortedCopLightSprite(uint64_t /*state*/) +{ + return g_views.inSortFlush + && GetSortedTexNameFlags().coplight; +} + +// TheSuperHackers @bugfix bobtista 01/07/2026 Structure searchlight/beacon beams (lightbeam*.tga, +// e.g. the China power-plant beacons) are model-space sorted quads. Left on the sort view they fold +// through its pre-view-multiplied matrix and land in screen space, drawing a fixed diagonal light +// streak across the map. Route them like the coplight/rotor sprites: engine view + raw model world. +static bool IsSortedLightBeam(uint64_t /*state*/) +{ + return g_views.inSortFlush + && GetSortedTexNameFlags().lightbeam; +} + +static bool IsSortedLocalModelEffectDraw(uint64_t state) +{ + return IsSortedMeshModelDraw(state) + || IsSortedRotorBlur(state) + || IsSneakAttackAlphaDepthDecal(state) + || IsSortedCopLightSprite(state) + || IsSortedLightBeam(state) + || (g_views.inSortFlush + && GetSortedTexNameFlags().commandCenterEmblem); +} + +// TheSuperHackers @bugfix bobtista 30/06/2026 A draw whose material carries an +// explicit emissive color is deliberately self-illuminated and depends on the +// uber shader's lit branch to add that emissive. The force-unlit-for-baked-color +// heuristic below targets particles/decals/additive sprites that bake intensity +// into vertex color (no material emissive), so emissive-bearing passes must be +// exempt. The detected-stealth "heat vision" pass is the case in point: it is an +// additive (ONE/ONE) textureless pass whose only visible output is the orange +// material emissive. Forcing it unlit drops the emissive and the additive pass +// renders black, leaving detected stealth objects invisible. +static bool DrawHasSelfIllumEmissive() +{ + return g_draw.matEmissive[0] > 0.001f + || g_draw.matEmissive[1] > 0.001f + || g_draw.matEmissive[2] > 0.001f; +} + +static bool ShouldForceUnlitForBakedColorDraw(uint64_t state) +{ + // TheSuperHackers @bugfix bobtista 02/07/2026 The force-unlit exists to keep + // additive particles/dazzles that bake their intensity into vertex diffuse + // from being overwritten by the shader's computed lighting. A real geometry + // mesh with material lighting enabled but NO baked vertex color (e.g. the + // additive night-detail pass on building roofs, atcemwall04) has no baked + // color to preserve — forcing it unlit dumps the raw full-bright texture, + // so it glows tan at night. Keep such draws lit. + if (g_draw.lightingEnabled[0] > 0.5f + && g_draw.vertexColorFlags[0] <= 0.5f + && g_draw.fvfHasNormal) + { + return false; + } + if (DrawHasSelfIllumEmissive()) + { + static const bool s_trace = (GgcFlags::Enabled(GgcFlag_Trace)); + static bool s_loggedSelfIllumExempt = false; + if (s_trace && !s_loggedSelfIllumExempt && IsAnyAdditiveBlend(state)) + { + std::fprintf(stderr, + "[ggc] heat-vision/self-illum additive pass kept lit: emissive=(%.2f,%.2f,%.2f) priColorOp=%.0f lightingEnabled=%.0f\n", + g_draw.matEmissive[0], g_draw.matEmissive[1], g_draw.matEmissive[2], + g_draw.tssOps0[0], g_draw.lightingEnabled[0]); + s_loggedSelfIllumExempt = true; + } + return false; + } + return IsAnyAdditiveBlend(state) + || IsSortedParticleEffect(state) + || IsSoftParticleCandidate(state) + || IsSortedMaterialDecal(state) + || IsSortedAlphaDepthDecal(state) + || IsSortedRotorBlur(state); +} + +static uint64_t ApplySortedMaterialDecalDepthState(uint64_t state) +{ + if (!IsSortedMaterialDecal(state) && !IsSortedAlphaDepthDecal(state)) + { + return state; + } + + // W3DBibBuffer's legacy shader is PASS_ALWAYS because the DX8 path relied + // on draw order plus fixed-function z-bias for driveway bibs/faction + // emblems. In bgfx these draws are routed through the sorted view after + // units, so PASS_ALWAYS makes the ground decal alpha-blend over bulldozers + // as they exit the command center. Keep the decal z-bias that prevents + // coplanar ground fighting, but still test against scene depth so vehicles + // and other opaque meshes occlude the decal. + state &= ~BGFX_STATE_DEPTH_TEST_MASK; + state |= BGFX_STATE_DEPTH_TEST_LEQUAL; + state &= ~BGFX_STATE_WRITE_Z; + return state; +} + +static uint64_t ApplyDelayedObjectShroudDepthState(uint64_t state) +{ + // The object shroud overlay is submitted in a later bgfx view so building + // detail passes cannot draw over it. Keep DX8's depth-equal behavior: + // alpha-tested base pixels write object depth, while transparent card + // pixels leave terrain depth behind and must not receive object shroud. + state &= ~BGFX_STATE_DEPTH_TEST_MASK; + state |= BGFX_STATE_DEPTH_TEST_EQUAL; + state &= ~BGFX_STATE_WRITE_Z; + return state; +} + +static bool ShouldHideMissingTextureForCurrentDraw(uint64_t state) +{ + // Keep missing textures visible on opaque geometry so bad assets are still + // diagnosable. In blended/sorted/effect passes, the checker texture becomes + // the artifact itself, e.g. missing spy-satellite smoke particles drawing + // black radiating blocks. + return (state & BGFX_STATE_BLEND_MASK) != 0 + || g_views.inSortFlush + || g_views.effectOverlayActive; +} + +static bool ShouldSkipHiddenMissingTextureDraw(uint64_t state) +{ + if (!ShouldHideMissingTextureForCurrentDraw(state)) + { + return false; + } + + const bool usesStage1 = g_draw.tssOps0[2] > 0.5f || g_draw.tssOps0[3] > 0.5f; + const bool usesLateStages = usesStage1 && g_draw.texcoordSelect[1] > 0.5f; + if (g_draw.textureIsMissing[0] + || (usesStage1 && g_draw.textureIsMissing[1]) + || (usesLateStages && (g_draw.textureIsMissing[2] || g_draw.textureIsMissing[3]))) + { + return true; + } + return false; +} + +static bool IsMissingOrUnavailableTexture(TextureBaseClass * texture, bgfx::TextureHandle handle) +{ + if (texture == nullptr) + { + return false; + } + if (texture->Is_Missing_Texture()) + { + return true; + } + return !bgfx::isValid(handle); +} + +static bool ShouldLogBgfxShroudPass() +{ + static const bool cached = GgcFlags::Enabled(GgcFlag_BgfxShroudPassDiag); + return cached; +} + +static bool ShouldLogBgfxSortedDecals() +{ + static const bool cached = GgcFlags::Enabled(GgcFlag_BgfxSortedDecalDiag); + return cached; +} + +static bool ShouldLogBgfxRevealDiag() +{ + static const bool cached = GgcFlags::Enabled(GgcFlag_BgfxRevealDiag); + return cached; +} + +static bool ShouldLogBgfxRevealDiagVerbose() +{ + static const bool cached = GgcFlags::Enabled(GgcFlag_BgfxRevealDiagVerbose); + return cached; +} + +static bool ShouldLogBgfxEffectSubmitDiag() +{ + static const bool cached = GgcFlags::Enabled(GgcFlag_BgfxEffectSubmitDiag); + return cached; +} + +static uint32_t GetCurrentStageSamplerFlags(unsigned stage); + +static bool ShouldAllowBgfxDiagnosticDrawOverrides() +{ + static const bool cached = GgcFlags::Enabled(GgcFlag_BgfxEnableDiagnosticOverrides); + return cached; +} + +static const char * TextureDebugName(TextureBaseClass * texture) +{ + TextureClass * tex2d = texture ? texture->As_TextureClass() : nullptr; + return tex2d ? tex2d->Get_Full_Path().str() : "(null)"; +} + +static bool IsCommandCenterEmblemTextureName(const char *name) +{ + // The command-center driveway/bib emblems use this small faction texture + // family. Do not match every ZHCA_* texture; that prefix also appears on + // infantry, hero, and UI materials in W3DZH.big. + return ContainsCaseInsensitive(name, "zhca_ab") + || ContainsCaseInsensitive(name, "zhca_atlaser") + || ContainsCaseInsensitive(name, "zhca_ch") + || ContainsCaseInsensitive(name, "zhca_nb") + || ContainsCaseInsensitive(name, "zhca_gl") + || ContainsCaseInsensitive(name, "zhca_gdemo") + || ContainsCaseInsensitive(name, "zhca_gstlth") + || ContainsCaseInsensitive(name, "zhca_gtoxin"); +} + +// TheSuperHackers @bugfix bobtista 07/07/2026 The rotor-blur routing matched only +// avcomanche_p (base Comanche and Chinook), so the Air Force General Comanche's +// dedicated avcomancheag_p blur texture missed both the per-mesh world capture and +// the engine-view routing, leaving its main rotor invisible. +static bool IsRotorBlurTextureName(const char *name) +{ + return ContainsCaseInsensitive(name, "avcomanche_p") + || ContainsCaseInsensitive(name, "avcomancheag_p"); +} + +static const SortedTexNameFlags & GetSortedTexNameFlags() +{ + static const TextureBaseClass * s_cachedTex = nullptr; + static bool s_valid = false; + static SortedTexNameFlags s_flags = { false, false, false, false, false, false }; + TextureBaseClass * tex = g_draw.sourceTextures[0]; + if (!s_valid || tex != s_cachedTex) + { + const char * n = TextureDebugName(tex); + s_flags.snk01 = ContainsCaseInsensitive(n, "ubsnkatak_01"); + s_flags.snk0 = ContainsCaseInsensitive(n, "ubsnkatak_0"); + s_flags.rotor = IsRotorBlurTextureName(n); + s_flags.coplight = ContainsCaseInsensitive(n, "coplight"); + s_flags.commandCenterEmblem = IsCommandCenterEmblemTextureName(n); + s_flags.lightbeam = ContainsCaseInsensitive(n, "lightbeam"); + s_cachedTex = tex; + s_valid = true; + } + return s_flags; +} + +static bool IsCommandCenterEmblemTexture(TextureBaseClass *texture) +{ + if (texture == g_draw.sourceTextures[0]) + { + return GetSortedTexNameFlags().commandCenterEmblem; + } + + const char *name = TextureDebugName(texture); + return IsCommandCenterEmblemTextureName(name); +} + +static bool IsRevealRelevantTextureName(const char *name) +{ + return ContainsCaseInsensitive(name, "exgrid") + || ContainsCaseInsensitive(name, "exredsmokepuff") + || ContainsCaseInsensitive(name, "smoke") + || ContainsCaseInsensitive(name, "shadow"); +} + +static bool IsRevealGridTexture(TextureBaseClass *texture) +{ + return ContainsCaseInsensitive(TextureDebugName(texture), "exgrid"); +} + +static bool IsEffectTextureName(const char *name) +{ + return ContainsCaseInsensitive(name, "ex") + || ContainsCaseInsensitive(name, "fire") + || ContainsCaseInsensitive(name, "missile") + || ContainsCaseInsensitive(name, "flame") + || ContainsCaseInsensitive(name, "smoke") + || ContainsCaseInsensitive(name, "noise"); +} + +static void LogBgfxEffectSubmit(const char *event, + bgfx::ViewId view, + unsigned short polygonCount, + unsigned short vertexCount, + uint64_t state, + const char *decision) +{ + if (!ShouldLogBgfxEffectSubmitDiag()) + { + return; + } + + const char *tex0 = TextureDebugName(g_draw.sourceTextures[0]); + const char *tex1 = TextureDebugName(g_draw.sourceTextures[1]); + const char *tex2 = TextureDebugName(g_draw.sourceTextures[2]); + const char *tex3 = TextureDebugName(g_draw.sourceTextures[3]); + const bool effectTex = IsEffectTextureName(tex0) + || IsEffectTextureName(tex1) + || IsEffectTextureName(tex2) + || IsEffectTextureName(tex3); + const bool interestingState = IsAnyAdditiveBlend(state) + || IsStandardAlphaBlend(state) + || view == kBgfxEngineSortView + || view == kBgfxEffectOverlayView; + if (!effectTex && !interestingState) + { + return; + } + + if (FILE *diag = std::fopen("ggc_bgfx_effect_submit_diag.txt", "a")) + { + std::fprintf(diag, + "%s frame=%u view=%u polys=%u verts=%u decision=%s raw=0x%llx state=0x%llx blend=0x%llx depth=0x%llx wz=%d inSort=%d effect=%d programValid=%d texValid=(%d,%d,%d,%d) missing=(%d,%d,%d,%d) tex=(%s|%s|%s|%s) tss0=(%.1f,%.1f,%.1f,%.1f) tss1=(%.1f,%.1f,%.1f,%.1f) texSel=(%.1f,%.1f,%.1f,%.1f) texSel2=(%.1f,%.1f,%.1f,%.1f) lighting=%.1f\n", + event, + g_stats.frameIndex, + static_cast(view), + static_cast(polygonCount), + static_cast(vertexCount), + decision ? decision : "", + static_cast(g_draw.state), + static_cast(state), + static_cast(state & BGFX_STATE_BLEND_MASK), + static_cast(state & BGFX_STATE_DEPTH_TEST_MASK), + (state & BGFX_STATE_WRITE_Z) != 0 ? 1 : 0, + g_views.inSortFlush ? 1 : 0, + g_views.effectOverlayActive ? 1 : 0, + bgfx::isValid(g_draw.program) ? 1 : 0, + bgfx::isValid(g_draw.tex[0]) ? 1 : 0, + bgfx::isValid(g_draw.tex[1]) ? 1 : 0, + bgfx::isValid(g_draw.tex[2]) ? 1 : 0, + bgfx::isValid(g_draw.tex[3]) ? 1 : 0, + g_draw.textureIsMissing[0] ? 1 : 0, + g_draw.textureIsMissing[1] ? 1 : 0, + g_draw.textureIsMissing[2] ? 1 : 0, + g_draw.textureIsMissing[3] ? 1 : 0, + tex0, tex1, tex2, tex3, + g_draw.tssOps0[0], g_draw.tssOps0[1], + g_draw.tssOps0[2], g_draw.tssOps0[3], + g_draw.tssOps1[0], g_draw.tssOps1[1], + g_draw.tssOps1[2], g_draw.tssOps1[3], + g_draw.texcoordSelect[0], g_draw.texcoordSelect[1], + g_draw.texcoordSelect[2], g_draw.texcoordSelect[3], + g_draw.texcoordSelect2[0], g_draw.texcoordSelect2[1], + g_draw.texcoordSelect2[2], g_draw.texcoordSelect2[3], + g_draw.lightingEnabled[0]); + std::fclose(diag); + } +} + +static void LogBgfxRevealDraw(const char *event, + bgfx::ViewId view, + unsigned short polygonCount, + unsigned short vertexCount, + uint64_t state, + const char *decision) +{ + if (!ShouldLogBgfxRevealDiag()) + { + return; + } + + const char *tex0 = TextureDebugName(g_draw.sourceTextures[0]); + const char *tex1 = TextureDebugName(g_draw.sourceTextures[1]); + const char *tex2 = TextureDebugName(g_draw.sourceTextures[2]); + const char *tex3 = TextureDebugName(g_draw.sourceTextures[3]); + const bool relevantTexture = + IsRevealRelevantTextureName(tex0) + || IsRevealRelevantTextureName(tex1) + || IsRevealRelevantTextureName(tex2) + || IsRevealRelevantTextureName(tex3); + const bool relevantState = + g_views.projectedShadowDecalActive + || g_views.projectedDecalMode != RB_PROJECTED_DECAL_NONE + || g_draw.textureIsMissing[0] + || g_draw.textureIsMissing[1] + || g_draw.textureIsMissing[2] + || g_draw.textureIsMissing[3] + || (ShouldLogBgfxRevealDiagVerbose() && IsAnyAdditiveBlend(state)) + || IsMultiplicativeBlend(state); + if (!relevantTexture && !relevantState) + { + return; + } + + if (FILE *diag = std::fopen("ggc_bgfx_reveal_diag.txt", "a")) + { + std::fprintf(diag, + "%s frame=%u view=%u polys=%u verts=%u decision=%s raw=0x%llx state=0x%llx blend=0x%llx depth=0x%llx wz=%d sort=%d effect=%d decalMode=%u missing=(%d,%d,%d,%d) tex=(%s|%s|%s|%s) tss0=(%.1f,%.1f,%.1f,%.1f) tss1=(%.1f,%.1f,%.1f,%.1f) texSel=(%.1f,%.1f,%.1f,%.1f) texSel2=(%.1f,%.1f,%.1f,%.1f)\n", + event, + g_stats.frameIndex, + static_cast(view), + static_cast(polygonCount), + static_cast(vertexCount), + decision ? decision : "", + static_cast(g_draw.state), + static_cast(state), + static_cast(state & BGFX_STATE_BLEND_MASK), + static_cast(state & BGFX_STATE_DEPTH_TEST_MASK), + (state & BGFX_STATE_WRITE_Z) != 0 ? 1 : 0, + g_views.inSortFlush ? 1 : 0, + g_views.effectOverlayActive ? 1 : 0, + g_views.projectedDecalMode, + g_draw.textureIsMissing[0] ? 1 : 0, + g_draw.textureIsMissing[1] ? 1 : 0, + g_draw.textureIsMissing[2] ? 1 : 0, + g_draw.textureIsMissing[3] ? 1 : 0, + tex0, tex1, tex2, tex3, + g_draw.tssOps0[0], g_draw.tssOps0[1], + g_draw.tssOps0[2], g_draw.tssOps0[3], + g_draw.tssOps1[0], g_draw.tssOps1[1], + g_draw.tssOps1[2], g_draw.tssOps1[3], + g_draw.texcoordSelect[0], g_draw.texcoordSelect[1], + g_draw.texcoordSelect[2], g_draw.texcoordSelect[3], + g_draw.texcoordSelect2[0], g_draw.texcoordSelect2[1], + g_draw.texcoordSelect2[2], g_draw.texcoordSelect2[3]); + std::fclose(diag); + } +} + +static void LogBgfxSortedMaterialDecal(const char *event, + bgfx::ViewId view, + unsigned short polygonCount, + unsigned short vertexCount, + uint64_t state) +{ + if (!ShouldLogBgfxSortedDecals() || !IsSortedMaterialDecal(GetEffectiveDrawState())) + { + return; + } + + if (FILE *diag = std::fopen("ggc_bgfx_sorted_decal_diag.txt", "a")) + { + std::fprintf(diag, + "%s frame=%u view=%u polys=%u verts=%u state=0x%llx final=0x%llx depth=0x%llx zbias=%.6f rawZBias=%u tex0=%s tex1=%s tss0=(%.1f,%.1f,%.1f,%.1f) texSel=(%.1f,%.1f,%.1f,%.1f)\n", + event, + g_stats.frameIndex, + static_cast(view), + static_cast(polygonCount), + static_cast(vertexCount), + static_cast(g_draw.state), + static_cast(state), + static_cast(state & BGFX_STATE_DEPTH_TEST_MASK), + g_draw.zBias[0], + g_draw.zBiasUnits, + TextureDebugName(g_draw.sourceTextures[0]), + TextureDebugName(g_draw.sourceTextures[1]), + g_draw.tssOps0[0], g_draw.tssOps0[1], + g_draw.tssOps0[2], g_draw.tssOps0[3], + g_draw.texcoordSelect[0], g_draw.texcoordSelect[1], + g_draw.texcoordSelect[2], g_draw.texcoordSelect[3]); + std::fclose(diag); + } +} + +static bool IsDefaultInfantryBlobShadowTexture(TextureBaseClass * texture) +{ + TextureClass * tex2d = texture ? texture->As_TextureClass() : nullptr; + if (tex2d == nullptr) + { + return false; + } + + const char *name = tex2d->Get_Full_Path().str(); + const char *base = name; + for (const char *p = name; *p != '\0'; ++p) + { + if (*p == '\\' || *p == '/') + { + base = p + 1; + } + } + + return stricmp(base, "shadowi.tga") == 0 + || stricmp(base, "shadowi.dds") == 0; +} + +static void UpdateAlphaMaskedShadowDecalMode() +{ + const uint64_t multiplicativeBlend = + BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ZERO, BGFX_STATE_BLEND_SRC_COLOR); + uint64_t state = GetEffectiveDrawState(); + const bool isAlphaMaskedShadow = + IsDefaultInfantryBlobShadowTexture(g_draw.sourceTextures[0]) + && ((state & BGFX_STATE_BLEND_MASK) == multiplicativeBlend); + g_draw.texcoordSelect2[2] = isAlphaMaskedShadow ? 1.0f : 0.0f; +} + +static void UpdateAlphaMaskAndSortedModes(uint64_t state) +{ + UpdateAlphaMaskedShadowDecalMode(); + if (IsSortedRotorBlur(state)) + { + // avcomanche_p stores the rotor blur as a sorted mask. The legacy + // fixed-function path keeps it independent from the vehicle material + // opacity; the shader's normal MODULATE path can otherwise multiply it + // away after replaying the sorted pool. + g_draw.texcoordSelect2[2] = 2.0f; + } + else if (IsSneakAttackAlphaDepthDecal(state)) + { + // UBSnkAtak_01 is the Sneak Attack's authored dirt/mound alpha quad. + // Its sorted replay can inherit a zero-opacity material from the W3D + // pass, which makes the wide dirt stain disappear while the opaque + // mound geometry remains. Keep this path texture-alpha driven. + g_draw.texcoordSelect2[2] = 3.0f; + } + else if (IsCommandCenterEmblemTexture(g_draw.sourceTextures[0])) + { + // ZH command-center driveway emblems are sorted local-model decals + // with player color baked into tex0 and black RGB used as the matte. + // Keep them out of the generic material-opacity path, which can + // inherit zero alpha from surrounding water/shroud submits. + g_draw.texcoordSelect2[2] = 4.0f; + } +} + +static RenderBackendProjectedDecalMode GetEffectiveProjectedDecalModeForCurrentDraw() +{ + RenderBackendProjectedDecalMode mode = + static_cast(g_views.projectedDecalMode); + if (mode != RB_PROJECTED_DECAL_BLOB_SHADOW) + { + return mode; + } + + const uint64_t multiplicativeBlend = + BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ZERO, BGFX_STATE_BLEND_SRC_COLOR); + uint64_t state = GetEffectiveDrawState(); + const bool validBlobShadow = + IsDefaultInfantryBlobShadowTexture(g_draw.sourceTextures[0]) + && ((state & BGFX_STATE_BLEND_MASK) == multiplicativeBlend); + + return validBlobShadow ? RB_PROJECTED_DECAL_BLOB_SHADOW : RB_PROJECTED_DECAL_MULTIPLY; +} + +static void UpdateProjectedDecalModeForCurrentDraw() +{ + const RenderBackendProjectedDecalMode mode = GetEffectiveProjectedDecalModeForCurrentDraw(); + g_draw.projectedDecalMode[0] = static_cast(mode); + g_draw.projectedDecalMode[1] = 0.0f; + g_draw.projectedDecalMode[2] = 0.0f; + g_draw.projectedDecalMode[3] = 0.0f; +} + +static bool IsEffectiveProjectedBlobShadowDraw() +{ + return GetEffectiveProjectedDecalModeForCurrentDraw() == RB_PROJECTED_DECAL_BLOB_SHADOW; +} + +static bool IsEffectiveProjectedShadowDraw() +{ + const RenderBackendProjectedDecalMode mode = GetEffectiveProjectedDecalModeForCurrentDraw(); + return mode == RB_PROJECTED_DECAL_BLOB_SHADOW + || mode == RB_PROJECTED_DECAL_MULTIPLY; +} + +static bool IsProjectedAdditiveDecalDraw() +{ + return g_views.projectedDecalMode == RB_PROJECTED_DECAL_ADDITIVE; +} + +static uint64_t ApplyProjectedAdditiveDecalDrawState(uint64_t state) +{ + if (!IsProjectedAdditiveDecalDraw()) + { + return state; + } + + // DX8 projected additive decals write visual RGB into a backbuffer whose + // alpha is not sampled later. The bgfx path renders world passes through an + // intermediate scene target, so keep additive decal alpha isolated while + // preserving the original RGB ONE/ONE blend. + return state & ~BGFX_STATE_WRITE_A; +} + +// TheSuperHackers @refactor bobtista 10/07/2026 The one derivation of the +// final bgfx pipeline-state word from the live translated draw state. Shared +// by the per-draw engine submit and by the sorted capture (which resolves the +// word once at insertion), so the two can never drift apart. +static uint64_t ComputeFinalDrawState(bool triangle_strip) +{ + uint64_t state = GetEffectiveDrawState(); + state |= BGFX_STATE_MSAA; + + state = ApplyCullModeOverride(state); + if (IsSortedRotorBlur(state)) + { + // The rotor blur is built from camera-facing sorted cards. Once the + // cards are replayed through the normal engine view, culling can drop + // one side of the blur depending on the camera angle. + state &= ~(BGFX_STATE_CULL_CW | BGFX_STATE_CULL_CCW); + } + state = ApplyBlendEquation(state); + + state = ApplyColorWriteOverride(state); + if (triangle_strip) + { + state &= ~BGFX_STATE_PT_MASK; + state |= BGFX_STATE_PT_TRISTRIP; + } + state = ApplyProjectedAdditiveDecalDrawState(state); + state = ApplySortedMaterialDecalDepthState(state); + if (g_draw.delayedObjectShroudPass) + { + state = ApplyDelayedObjectShroudDepthState(state); + } + return state; +} + +static void LogBgfxShroudPass(const char *event, + bgfx::ViewId view, + unsigned short polygonCount, + unsigned depthFunc, + unsigned tci0, + bool shroudDetected, + const float *shroudParams) +{ + if (!ShouldLogBgfxShroudPass()) + { + return; + } + + if (FILE *diag = std::fopen("ggc_bgfx_shroud_pass_diag.txt", "a")) + { + std::fprintf(diag, + "%s bgfxFrame=%u view=%u polys=%u depthFunc=%u stencil=%d active=%d objectActive=%d objectDim=%.3f activeStage=%u state=0x%llx tex0=%u tex1=%u tex2=%u tex3=%u sampler0=0x%x tci0=0x%x tss0=(%.1f,%.1f,%.1f,%.1f) texSel=(%.1f,%.1f,%.1f,%.1f) shroud=%d params=(%.6f,%.6f,%.6f,%.6f) names=(%s|%s|%s|%s)\n", + event, + g_stats.frameIndex, + static_cast(view), + static_cast(polygonCount), + depthFunc, + g_draw.stencilEnabled ? 1 : 0, + g_views.shroudTexturePassActive ? 1 : 0, + g_views.objectShroudTexturePassActive ? 1 : 0, + g_draw.objectShroudDim[0], + g_views.shroudTexturePassStage, + static_cast(g_draw.state), + bgfx::isValid(g_draw.tex[0]) ? g_draw.tex[0].idx : 0xffff, + bgfx::isValid(g_draw.tex[1]) ? g_draw.tex[1].idx : 0xffff, + bgfx::isValid(g_draw.tex[2]) ? g_draw.tex[2].idx : 0xffff, + bgfx::isValid(g_draw.tex[3]) ? g_draw.tex[3].idx : 0xffff, + g_draw.samplerFlags[0], + tci0, + g_draw.tssOps0[0], g_draw.tssOps0[1], g_draw.tssOps0[2], g_draw.tssOps0[3], + g_draw.texcoordSelect[0], g_draw.texcoordSelect[1], g_draw.texcoordSelect[2], g_draw.texcoordSelect[3], + shroudDetected ? 1 : 0, + shroudParams[0], shroudParams[1], shroudParams[2], shroudParams[3], + TextureDebugName(g_draw.sourceTextures[0]), + TextureDebugName(g_draw.sourceTextures[1]), + TextureDebugName(g_draw.sourceTextures[2]), + TextureDebugName(g_draw.sourceTextures[3])); + std::fclose(diag); + } +} + +// TheSuperHackers @bugfix bobtista 09/07/2026 Legacy D3DRS_ZBIAS shifted depth +// by a few depth-buffer ULPs per unit - just enough to break coplanar ties. +// The previous emulation of 0.001 NDC per unit was ~2000x stronger and pulled +// biased draws through solid terrain at RTS camera distances: Fortress +// Avalanche's stale vanilla shore surf (authored below the ZH map's raised +// hill) rendered on top of the grass instead of being depth-rejected as on +// DX8. Two ULPs of a 24-bit depth buffer per unit keeps hundreds of ULPs of +// tie-break margin at the legacy maximum of 8 while lifting geometry well +// under a world unit at gameplay depths. Sorted material decals are +// unaffected: they clamp to their own tuned floor below. +static const float kZBiasPerUnit = 0.000002f; + +static void TraceLegacyZBiasTranslation() +{ + static const bool s_trace = GgcFlags::Enabled(GgcFlag_Trace); + static bool s_logged = false; + if (s_trace && !s_logged && g_draw.zBiasUnits != 0) + { + s_logged = true; + std::fprintf(stderr, "[ggc] legacy z-bias %d translates to %.3g ndc\n", + static_cast(g_draw.zBiasUnits), + static_cast(g_draw.zBiasUnits) * kZBiasPerUnit); + } +} + +// NDC z-pull applied to coplanar sorted decals so they win the LEQUAL test +// against the opaque sub-mesh they sit on. Keep this much smaller than the +// generic legacy z-bias conversion: a large clip-space pull makes command-center +// floor emblems render in front of bulldozers as they leave the building. +static const float kSortedDecalMinZBias = 0.00025f; +static const float kSortedDecalMaxZBias = 0.00075f; +// UBSnkAtak_01 sits under the entrance mesh as part of the model art. Do not +// pull it toward the camera or the dirt quad blends over the tunnel shell. +static const float kSneakAttackDecalMinZBias = 0.0f; +static const float kSneakAttackDecalMaxZBias = 0.0f; + +static void ClampSortedMaterialDecalZBias() +{ + const uint64_t state = GetEffectiveDrawState(); + if (!IsSortedMaterialDecal(state) && !IsSortedAlphaDepthDecal(state)) + { + return; + } + + const float minZBias = IsSneakAttackAlphaDepthDecal(state) + ? kSneakAttackDecalMinZBias + : kSortedDecalMinZBias; + const float maxZBias = IsSneakAttackAlphaDepthDecal(state) + ? kSneakAttackDecalMaxZBias + : kSortedDecalMaxZBias; + + if (g_draw.zBias[0] < minZBias) + { + g_draw.zBias[0] = minZBias; + } + else if (g_draw.zBias[0] > maxZBias) + { + g_draw.zBias[0] = maxZBias; + } +} + +static void BindSoftParticleDepth(bool enable) +{ + float params[4]; + GetSoftParticleParams(params); + + if (!enable || params[1] <= 0.0f) + { + params[0] = 0.0f; + } + + if (params[0] > 0.5f + && g_device.width > 0 + && g_device.height > 0 + && bgfx::isValid(g_device.sceneReadableDepth) + && bgfx::isValid(g_uniforms.sSceneDepth)) + { + params[0] = 1.0f; + params[2] = 1.0f / static_cast(g_device.width); + params[3] = 1.0f / static_cast(g_device.height); + bgfx::setTexture(kBgfxSceneDepthSamplerStage, g_uniforms.sSceneDepth, + g_device.sceneReadableDepth, + BGFX_SAMPLER_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + g_stats.textureBinds++; + } + else + { + params[0] = 0.0f; + // TheSuperHackers @bugfix bobtista 30/04/2026 fs_uber declares + // SAMPLER2D(s_sceneDepth, 6); Metal validation requires slot 6 + // to be bound on every draw even though u_softParticleParams.x + // = 0 makes the shader skip the sample. defaultWhiteTexture is + // a 1x1 RGBA8 placeholder that satisfies the validator without + // creating a read/write hazard against the real depth target. + if (bgfx::isValid(g_uniforms.sSceneDepth) && bgfx::isValid(g_device.defaultWhiteTexture)) + { + bgfx::setTexture(kBgfxSceneDepthSamplerStage, g_uniforms.sSceneDepth, + g_device.defaultWhiteTexture, + BGFX_SAMPLER_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + } + } + + if (bgfx::isValid(g_uniforms.uSoftParticleParams)) + { + // TheSuperHackers @feature bobtista 27/04/2026 Use the readable + // opaque scene-depth target for conservative soft particles. This + // starts with standard alpha-blended sorted draws only so additive + // lasers, fire, and scanner effects keep their legacy intensity. + bgfx::setUniform(g_uniforms.uSoftParticleParams, params); + } +} + +// TheSuperHackers @feature bobtista 16/06/2026 Debug toggle to force nearest/point +// texture filtering on the game texture stages, so the smooth linear/trilinear +// renderer baseline can be A/B'd against the old blocky look. Cached per frame so the +// global is read once rather than on every texture bind. +static bool ForcePointFilterEnabled() +{ + static uint32_t s_frame = 0xFFFFFFFFu; + static bool s_value = false; + if (s_frame != g_stats.frameIndex) + { + s_frame = g_stats.frameIndex; + s_value = GgcFlags::Enabled(GgcFlag_BgfxPointFilter); +#ifdef RTS_ZEROHOUR + if (!s_value && GGC_GetBgfxPointFilter() != 0) + { + s_value = true; + } +#endif + } + return s_value; +} + +static bool IsStrategyCenterSlabTexture(TextureBaseClass *texture); + +static uint32_t GetCurrentStageSamplerFlags(unsigned stage) +{ + uint32_t flags = (stage < 4) ? g_draw.samplerFlags[stage] : 0; + if (stage == 0 && IsStrategyCenterSlabTexture(g_draw.sourceTextures[stage])) + { + flags &= ~(BGFX_SAMPLER_MIN_ANISOTROPIC | BGFX_SAMPLER_MAG_ANISOTROPIC); + flags |= BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_MIP_POINT; + } + if (ForcePointFilterEnabled()) + { + flags |= BGFX_SAMPLER_POINT; + } + return flags; +} + +static bool IsCurrentStageMipFilterDisabled(unsigned stage) +{ + return stage < 4 && g_draw.mipFilterDisabled[stage]; +} + +static bool ShouldBindSortedParticleBaseMip(unsigned stage) +{ + // Point-group and streak renderers generate camera-facing particle quads + // in the sorted pass. Under bgfx/Metal the authored lower effect mips can + // erase thin smoke/contrail sprites at normal gameplay zoom, while the + // legacy particle path keeps these sprites legible. Bind stage 0 through + // the existing one-mip sibling for those dynamic particle draws only; do + // not apply this to sorted decals or their detail stages. + // TheSuperHackers @bugfix bobtista 25/05/2026 The police-car lightbar glow + // sprites (CopLight*.tga) are tiny sorted additive meshes whose authored + // lower mips carry the red/blue/yellow color. Binding the one-mip + // compatibility texture collapses them back to a dull level-0 hotspot, so + // exclude them from this remap and let them sample the full mip chain. + if (stage == 0 + && ContainsCaseInsensitive(TextureDebugName(g_draw.sourceTextures[0]), "coplight")) + { + return false; + } + return stage == 0 + && IsSortedParticleEffect(GetEffectiveDrawState()); +} + +static bool IsStrategyCenterSlabTexture(TextureBaseClass *texture) +{ + static TextureBaseClass *s_lastTexture = nullptr; + static bool s_lastResult = false; + if (texture == s_lastTexture) + { + return s_lastResult; + } + + s_lastTexture = texture; + const char *name = TextureDebugName(texture); + s_lastResult = ContainsCaseInsensitive(name, "atstratslab"); + return s_lastResult; +} + +static bgfx::TextureHandle GetCurrentStageTextureHandle(unsigned stage) +{ + if (stage >= 4) + { + return BGFX_INVALID_HANDLE; + } + + TextureBaseClass *texture = g_draw.sourceTextures[stage]; + if (stage == 0 && texture != nullptr && IsStrategyCenterSlabTexture(texture)) + { + // The Strategy Center side ledge maps ATStratSlab across a very short + // vertical face. Its authored lower mips average the black atlas inset + // into the entire slab, which appears as a zoom-dependent black band. + return EnsureBgfxTexture(texture, true); + } + if (texture != nullptr && ShouldBindSortedParticleBaseMip(stage)) + { + return EnsureBgfxTexture(texture, true); + } + if (texture != nullptr && IsCurrentStageMipFilterDisabled(stage)) + { + // bgfx has point/linear mip selection flags but no sampler flag for + // disabled mip filtering. Bind a one-mip sibling texture to preserve + // the legacy "sample level 0 only" behavior. + return EnsureBgfxTexture(texture, true); + } + return g_draw.tex[stage]; +} + +static bool DisableUnlitLightInputSkip() +{ + static const bool s_disabled = + GgcFlags::Enabled(GgcFlag_BgfxDisableUnlitLightInputSkip); + return s_disabled; +} + +static bool DisableInactiveShadowUniformSkip() +{ + static const bool s_disabled = + GgcFlags::Enabled(GgcFlag_BgfxDisableInactiveShadowUniformSkip); + return s_disabled; +} + +static bool UniformCommandCountersEnabled() +{ + return true; +} + +static void CountUniformCommand(uint32_t & bucket) +{ + if (!UniformCommandCountersEnabled()) + { + return; + } + g_stats.uniformCommands++; + bucket++; +} + +// TheSuperHackers @performance bobtista 11/07/2026 Frame-constant uniform +// elision. bgfx uniform values persist across draws in PLAYBACK order, and +// playback is sorted by view id, not submission order — so skipping a +// redundant upload is only sound when the previously uploaded identical value +// is what playback will have live at this draw. Guarding per (frame, view) +// with a forced first upload per view makes that hold: within one view, +// playback order equals submission order, and the first draw of each view +// never inherits from another view. All upload sites of a guarded handle must +// route through its guard. +static bool UniformFrequencySplitDisabled() +{ + static const bool s_disabled = GgcFlags::Enabled(GgcFlag_BgfxDisableUniformFrequencySplit); + return s_disabled; +} + +struct FrameConstUniformGuard +{ + uint32_t frameIndex = 0xffffffffu; + uint16_t viewId = 0xffffu; + uint16_t sizeBytes = 0; + uint8_t value[512]; // fits the packed material block (MU_COUNT vec4s) and the cascade matrices +}; + +static bool ShouldUploadFrameConstUniform(FrameConstUniformGuard & guard, + bgfx::ViewId view, + const void * data, + unsigned sizeBytes) +{ + if (UniformFrequencySplitDisabled() || sizeBytes > sizeof(guard.value)) + { + return true; + } + if (guard.frameIndex == g_stats.frameIndex + && guard.viewId == static_cast(view) + && guard.sizeBytes == sizeBytes + && std::memcmp(guard.value, data, sizeBytes) == 0) + { + return false; + } + guard.frameIndex = g_stats.frameIndex; + guard.viewId = static_cast(view); + guard.sizeBytes = static_cast(sizeBytes); + std::memcpy(guard.value, data, sizeBytes); + return true; +} + +static void UploadLightUniforms(bool fixedFunctionLightInputsNeeded, bgfx::ViewId submitView) +{ + PERF_TIME(PERF_SECT_UPLOAD_LIGHTS); + g_stats.lightUniformUploads++; + if (BgfxProbeFlag("GGC_PROBE_FREEZE_STATE") + || BgfxProbeFlag("GGC_PROBE_NO_LIGHTUNIFORM")) + { + return; + } + const bool uploadFixedFunctionLightInputs = + fixedFunctionLightInputsNeeded || DisableUnlitLightInputSkip(); + if (uploadFixedFunctionLightInputs) + { + if (bgfx::isValid(g_uniforms.uLightDirs)) + { + static FrameConstUniformGuard s_lightDirsGuard; + if (ShouldUploadFrameConstUniform(s_lightDirsGuard, submitView, + g_draw.lightDirs, sizeof(g_draw.lightDirs))) + { + bgfx::setUniform(g_uniforms.uLightDirs, g_draw.lightDirs, 4); + CountUniformCommand(g_stats.lightUniformCommands); + } + } + if (bgfx::isValid(g_uniforms.uLightColors)) + { + static FrameConstUniformGuard s_lightColorsGuard; + if (ShouldUploadFrameConstUniform(s_lightColorsGuard, submitView, + g_draw.lightColors, sizeof(g_draw.lightColors))) + { + bgfx::setUniform(g_uniforms.uLightColors, g_draw.lightColors, 4); + CountUniformCommand(g_stats.lightUniformCommands); + } + } + if (bgfx::isValid(g_uniforms.uLightAmbients)) + { + static FrameConstUniformGuard s_lightAmbientsGuard; + if (ShouldUploadFrameConstUniform(s_lightAmbientsGuard, submitView, + g_draw.lightAmbients, sizeof(g_draw.lightAmbients))) + { + bgfx::setUniform(g_uniforms.uLightAmbients, g_draw.lightAmbients, 4); + CountUniformCommand(g_stats.lightUniformCommands); + } + } + if (bgfx::isValid(g_uniforms.uLightPositions)) + { + static FrameConstUniformGuard s_lightPositionsGuard; + if (ShouldUploadFrameConstUniform(s_lightPositionsGuard, submitView, + g_draw.lightPositions, sizeof(g_draw.lightPositions))) + { + bgfx::setUniform(g_uniforms.uLightPositions, g_draw.lightPositions, 4); + CountUniformCommand(g_stats.lightUniformCommands); + } + } + if (bgfx::isValid(g_uniforms.uLightParams)) + { + static FrameConstUniformGuard s_lightParamsGuard; + if (ShouldUploadFrameConstUniform(s_lightParamsGuard, submitView, + g_draw.lightParams, sizeof(g_draw.lightParams))) + { + bgfx::setUniform(g_uniforms.uLightParams, g_draw.lightParams, 4); + CountUniformCommand(g_stats.lightUniformCommands); + } + } + } + const bool uploadEyePos = + uploadFixedFunctionLightInputs || g_draw.pointShadowParams[0] >= 0.0f; + if (uploadEyePos && bgfx::isValid(g_uniforms.uEyePos)) + { + // World-space camera position recovered from the rigid view matrix: + // eye = -transpose(R) * T (column-major bgfx layout). + const float * v = g_frame.view; + float eye[4]; + eye[0] = -(v[0] * v[12] + v[1] * v[13] + v[2] * v[14]); + eye[1] = -(v[4] * v[12] + v[5] * v[13] + v[6] * v[14]); + eye[2] = -(v[8] * v[12] + v[9] * v[13] + v[10] * v[14]); + eye[3] = 0.0f; + static FrameConstUniformGuard s_eyePosGuard; + if (ShouldUploadFrameConstUniform(s_eyePosGuard, submitView, eye, sizeof(eye))) + { + bgfx::setUniform(g_uniforms.uEyePos, eye); + CountUniformCommand(g_stats.lightUniformCommands); + } + } + const bool shadowMapActive = g_frame.shadowActive && bgfx::isValid(g_device.shadowMapTex) + && g_device.shadowMapSize > 0; + const bool uploadInactiveShadowInputs = + shadowMapActive || DisableInactiveShadowUniformSkip(); + if (uploadInactiveShadowInputs && bgfx::isValid(g_uniforms.uShadowMatrices)) + { + static FrameConstUniformGuard s_shadowMatricesGuard; + if (ShouldUploadFrameConstUniform(s_shadowMatricesGuard, submitView, + g_frame.shadowMatrices, + 16 * sizeof(float))) + { + bgfx::setUniform(g_uniforms.uShadowMatrices, g_frame.shadowMatrices, 1); + CountUniformCommand(g_stats.shadowUniformCommands); + } + } + if (bgfx::isValid(g_uniforms.uShadowParams)) + { + float shadowParams[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + if (shadowMapActive) + { + float p[4] = { 0.0015f, 1.0f, 0.0f, 0.0f }; + GGC_GetBgfxShadowMapParams(p); + shadowParams[0] = 1.0f / static_cast(g_device.shadowMapSize); // texel size + shadowParams[1] = p[0]; // depth bias + shadowParams[2] = p[1]; // shadow strength + shadowParams[3] = 1.0f; // enabled + } + static int s_lastRecvState = -1; + const int recvState = (shadowParams[3] > 0.5f) ? 1 : 0; + if (BgfxDiagVerbose() && recvState != s_lastRecvState) + { + std::fprintf(stderr, + "[ggc] shadow receiver enabled=%d (active=%d tex=%d size=%u) at content %dx%d swap %dx%d\n", + recvState, g_frame.shadowActive ? 1 : 0, + bgfx::isValid(g_device.shadowMapTex) ? 1 : 0, g_device.shadowMapSize, + g_device.width, g_device.height, g_device.swapWidth, g_device.swapHeight); + s_lastRecvState = recvState; + } + static FrameConstUniformGuard s_shadowParamsGuard; + if (ShouldUploadFrameConstUniform(s_shadowParamsGuard, submitView, + shadowParams, sizeof(shadowParams))) + { + bgfx::setUniform(g_uniforms.uShadowParams, shadowParams); + CountUniformCommand(g_stats.shadowUniformCommands); + } + } + if (uploadInactiveShadowInputs && bgfx::isValid(g_uniforms.uShadowQuality)) + { + // TheSuperHackers @performance bobtista 28/06/2026 Default reduced 9-fetch PCF; + // GGC_BGFX_SHADOW_FULL_PCF=1 restores the original 36-fetch path for a quality/perf A/B. + static const float s_fullPcf = (GgcFlags::Enabled(GgcFlag_BgfxShadowFullPcf) || GGC_GetBgfxShadowFullPcf() != 0) ? 1.0f : 0.0f; + const float shadowQuality[4] = { s_fullPcf, 0.0f, 0.0f, 0.0f }; + static FrameConstUniformGuard s_shadowQualityGuard; + if (ShouldUploadFrameConstUniform(s_shadowQualityGuard, submitView, + shadowQuality, sizeof(shadowQuality))) + { + bgfx::setUniform(g_uniforms.uShadowQuality, shadowQuality); + CountUniformCommand(g_stats.shadowUniformCommands); + } + } + if (uploadInactiveShadowInputs && bgfx::isValid(g_uniforms.sShadowMap)) + { + const bgfx::TextureHandle shadowTex = + shadowMapActive + ? g_device.shadowMapTex + : g_device.defaultWhiteTexture; + if (bgfx::isValid(shadowTex)) + { + bgfx::setTexture(kBgfxShadowMapSamplerStage, g_uniforms.sShadowMap, shadowTex, + BGFX_SAMPLER_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + } + } + // TheSuperHackers @feature bobtista 23/06/2026 Point-light shadow uniforms + sampler. The shader + // does not sample these yet (Task 5); pointShadowParams[0] = -1 keeps the term inert until then. + const bool pointShadowLightActive = g_draw.pointShadowParams[0] >= 0.0f; + const bool pointShadowMapActive = g_draw.pointShadowParams[0] >= 0.5f; + if (bgfx::isValid(g_uniforms.uPointShadowMatrix)) + { + static FrameConstUniformGuard s_pointShadowParamsGuard; + if (ShouldUploadFrameConstUniform(s_pointShadowParamsGuard, submitView, + g_draw.pointShadowParams, + sizeof(g_draw.pointShadowParams))) + { + bgfx::setUniform(g_uniforms.uPointShadowParams, g_draw.pointShadowParams); + CountUniformCommand(g_stats.pointShadowUniformCommands); + } + if (pointShadowLightActive) + { + static FrameConstUniformGuard s_pointShadowMatrixGuard; + if (ShouldUploadFrameConstUniform(s_pointShadowMatrixGuard, submitView, + g_draw.pointShadowMatrix, + sizeof(g_draw.pointShadowMatrix))) + { + bgfx::setUniform(g_uniforms.uPointShadowMatrix, g_draw.pointShadowMatrix); + CountUniformCommand(g_stats.pointShadowUniformCommands); + } + static FrameConstUniformGuard s_pointShadowLightPosGuard; + if (ShouldUploadFrameConstUniform(s_pointShadowLightPosGuard, submitView, + g_draw.pointShadowLightPos, + sizeof(g_draw.pointShadowLightPos))) + { + bgfx::setUniform(g_uniforms.uPointShadowLightPos, g_draw.pointShadowLightPos); + CountUniformCommand(g_stats.pointShadowUniformCommands); + } + static FrameConstUniformGuard s_pointShadowLightColorGuard; + if (ShouldUploadFrameConstUniform(s_pointShadowLightColorGuard, submitView, + g_draw.pointShadowLightColor, + sizeof(g_draw.pointShadowLightColor))) + { + bgfx::setUniform(g_uniforms.uPointShadowLightColor, g_draw.pointShadowLightColor); + CountUniformCommand(g_stats.pointShadowUniformCommands); + } + } + } + if ((pointShadowMapActive || DisableInactiveShadowUniformSkip()) + && bgfx::isValid(g_uniforms.sPointShadowMap)) + { + const bgfx::TextureHandle pointShadowTex = + pointShadowMapActive && bgfx::isValid(g_device.pointShadowTex) + ? g_device.pointShadowTex + : g_device.defaultWhiteTexture; + if (bgfx::isValid(pointShadowTex)) + { + bgfx::setTexture(kBgfxPointShadowMapSamplerStage, g_uniforms.sPointShadowMap, pointShadowTex, + BGFX_SAMPLER_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + } + } + // TheSuperHackers @feature bobtista 14/07/2026 Second point-shadow slot uniforms + sampler + // (transient lightning-flash lights). Mirrors the slot-1 upload above. + const bool pointShadow2Active = g_draw.pointShadow2Params[0] >= 0.5f; + if (bgfx::isValid(g_uniforms.uPointShadow2Matrix)) + { + static FrameConstUniformGuard s_pointShadow2ParamsGuard; + if (ShouldUploadFrameConstUniform(s_pointShadow2ParamsGuard, submitView, + g_draw.pointShadow2Params, + sizeof(g_draw.pointShadow2Params))) + { + bgfx::setUniform(g_uniforms.uPointShadow2Params, g_draw.pointShadow2Params); + CountUniformCommand(g_stats.pointShadowUniformCommands); + } + if (pointShadow2Active) + { + static FrameConstUniformGuard s_pointShadow2MatrixGuard; + if (ShouldUploadFrameConstUniform(s_pointShadow2MatrixGuard, submitView, + g_draw.pointShadow2Matrix, + sizeof(g_draw.pointShadow2Matrix))) + { + bgfx::setUniform(g_uniforms.uPointShadow2Matrix, g_draw.pointShadow2Matrix); + CountUniformCommand(g_stats.pointShadowUniformCommands); + } + static FrameConstUniformGuard s_pointShadow2LightPosGuard; + if (ShouldUploadFrameConstUniform(s_pointShadow2LightPosGuard, submitView, + g_draw.pointShadow2LightPos, + sizeof(g_draw.pointShadow2LightPos))) + { + bgfx::setUniform(g_uniforms.uPointShadow2LightPos, g_draw.pointShadow2LightPos); + CountUniformCommand(g_stats.pointShadowUniformCommands); + } + static FrameConstUniformGuard s_pointShadow2LightColorGuard; + if (ShouldUploadFrameConstUniform(s_pointShadow2LightColorGuard, submitView, + g_draw.pointShadow2LightColor, + sizeof(g_draw.pointShadow2LightColor))) + { + bgfx::setUniform(g_uniforms.uPointShadow2LightColor, g_draw.pointShadow2LightColor); + CountUniformCommand(g_stats.pointShadowUniformCommands); + } + } + } + if ((pointShadow2Active || DisableInactiveShadowUniformSkip()) + && bgfx::isValid(g_uniforms.sPointShadowMap2)) + { + const bgfx::TextureHandle pointShadow2Tex = + pointShadow2Active && bgfx::isValid(g_device.pointShadow2Tex) + ? g_device.pointShadow2Tex + : g_device.defaultWhiteTexture; + if (bgfx::isValid(pointShadow2Tex)) + { + bgfx::setTexture(kBgfxPointShadow2MapSamplerStage, g_uniforms.sPointShadowMap2, pointShadow2Tex, + BGFX_SAMPLER_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + } + } + if (bgfx::isValid(g_uniforms.uDramaDim)) + { + // TheSuperHackers @bugfix bobtista 15/07/2026 Drama lighting is world-space only. + // 2D/UI draws (control bar) run through the same uber shader with screen coordinates + // in v_worldPos; without this gate the radial dim read them as "far from the beam" + // and visibly darkened the HUD while the cannon fired. w = -1 tells the shader to + // skip the dedicated point lights and the dim entirely for this view. + const bool worldView = (submitView == kBgfxEngineView + || submitView == kBgfxEngineSortView + || submitView == kBgfxWaterView + || submitView == kBgfxEffectOverlayView); + float dramaDim[4] = { 0.0f, 0.0f, 0.0f, -1.0f }; + if (worldView) + { + std::memcpy(dramaDim, g_draw.dramaDim, sizeof(dramaDim)); + // Blended translucents (foliage passes, effects) receive the scene dim but NOT the + // drama point lights: leaf passes carry no alpha-test flag - whether they arrive + // through the sorted flush or drawn directly blended into the engine view (tree + // buffer) - so the flash speckled canopies/fences with bright blotches through + // every cutout gate. A negative falloff scale flags "dim only" to the shader. + const bool drawIsBlended = + (GetEffectiveDrawState() & BGFX_STATE_BLEND_MASK) != 0; + if (submitView == kBgfxEngineSortView || drawIsBlended) + { + dramaDim[2] = -dramaDim[2]; + } + } + static FrameConstUniformGuard s_dramaDimGuard; + if (ShouldUploadFrameConstUniform(s_dramaDimGuard, submitView, + dramaDim, sizeof(dramaDim))) + { + static int s_upLog = 0; + if (BgfxDiagVerbose() && (++s_upLog % 500) == 0) + { + std::fprintf(stderr, "[ggc] dramaDim upload view=%u {%.1f,%.1f,%.5f,%.3f}\n", + (unsigned)submitView, dramaDim[0], dramaDim[1], dramaDim[2], dramaDim[3]); + } + bgfx::setUniform(g_uniforms.uDramaDim, dramaDim); + CountUniformCommand(g_stats.pointShadowUniformCommands); + } + } + if (bgfx::isValid(g_uniforms.uMatFx)) + { + float matFx[4]; + GGC_GetBgfxMaterialFxParams(matFx); + bgfx::setUniform(g_uniforms.uMatFx, matFx); + CountUniformCommand(g_stats.materialUniformCommands); + static bool s_loggedMatFx = false; + if (BgfxDiagVerbose() && !s_loggedMatFx && (matFx[0] > 0.0f || matFx[1] > 0.0f || matFx[3] != 1.0f)) + { + std::fprintf(stderr, + "[ggc] material FX active: spec=%.2f rim=%.2f rimPow=%.2f emissive=%.2f" + " (sample matSpecular rgb=%.2f,%.2f,%.2f shin=%.1f)\n", + matFx[0], matFx[1], matFx[2], matFx[3], + g_draw.matSpecular[0], g_draw.matSpecular[1], g_draw.matSpecular[2], + g_draw.matSpecular[3]); + s_loggedMatFx = true; + } + } +} + +// TheSuperHackers @performance bobtista Kill switch for the frame-constant texture path. +// Default on; set GGC_BGFX_NO_UNIFORM_FRAME_TEXTURE to fall back to the byte-identical plain +// uber program (uniform reads) for A/B verification or bisection. +static bool UniformFrameTextureEnabled() +{ + static const bool s_disabled = (getenv("GGC_BGFX_NO_UNIFORM_FRAME_TEXTURE") != nullptr); + return !s_disabled; +} + +// TheSuperHackers @performance bobtista Pack the global per-frame constants (sun/point shadow, +// scene ambient) into frameConstTexture. These are identical for every fs_uber draw in a frame, +// so uploading them once per frame and having the fs_uber_frameconst variant sample them removes +// ~352B/draw from the per-draw constant buffer. Texel layout MUST match fs_uber.sc. +static void PackAndUploadFrameConstTexture() +{ + if (!bgfx::isValid(g_device.frameConstTexture)) + { + return; + } + float texels[kFrameConstTexels * 4]; + std::memset(texels, 0, sizeof(texels)); + + // texel 0: scene ambient + texels[0] = g_draw.sceneAmbient[0]; + texels[1] = g_draw.sceneAmbient[1]; + texels[2] = g_draw.sceneAmbient[2]; + texels[3] = 1.0f; + + // texel 1: shadow params (must match UploadLightUniforms) + const bool shadowMapActive = g_frame.shadowActive && bgfx::isValid(g_device.shadowMapTex) + && g_device.shadowMapSize > 0; + if (shadowMapActive) + { + float p[4] = { 0.0015f, 1.0f, 0.0f, 0.0f }; + GGC_GetBgfxShadowMapParams(p); + texels[4] = 1.0f / static_cast(g_device.shadowMapSize); // texel size + texels[5] = p[0]; // depth bias + texels[6] = p[1]; // shadow strength + texels[7] = 1.0f; // enabled + } + + // texel 2: shadow quality + static const float s_fullPcf = + (GgcFlags::Enabled(GgcFlag_BgfxShadowFullPcf) || GGC_GetBgfxShadowFullPcf() != 0) ? 1.0f : 0.0f; + texels[8] = s_fullPcf; + + // texels 3-6: sun shadow matrix (bgfx column-major float[16]: texel = one column; + // the shader rebuilds with mtxFromCols, the same convention as the instanced i_data path) + std::memcpy(&texels[12], g_frame.shadowMatrices, 16 * sizeof(float)); + + // texel 7: point shadow params, 8: light pos, 9: light color + std::memcpy(&texels[28], g_draw.pointShadowParams, sizeof(g_draw.pointShadowParams)); + std::memcpy(&texels[32], g_draw.pointShadowLightPos, sizeof(g_draw.pointShadowLightPos)); + std::memcpy(&texels[36], g_draw.pointShadowLightColor, sizeof(g_draw.pointShadowLightColor)); + + // texels 10-13: point shadow matrix + std::memcpy(&texels[40], g_draw.pointShadowMatrix, sizeof(g_draw.pointShadowMatrix)); + + // texel 14: point shadow 2 params, 15: light pos, 16: light color, 17-20: matrix + std::memcpy(&texels[56], g_draw.pointShadow2Params, sizeof(g_draw.pointShadow2Params)); + std::memcpy(&texels[60], g_draw.pointShadow2LightPos, sizeof(g_draw.pointShadow2LightPos)); + std::memcpy(&texels[64], g_draw.pointShadow2LightColor, sizeof(g_draw.pointShadow2LightColor)); + std::memcpy(&texels[68], g_draw.pointShadow2Matrix, sizeof(g_draw.pointShadow2Matrix)); + + bgfx::updateTexture2D(g_device.frameConstTexture, 0, 0, 0, 0, + kFrameConstTexels, 1, bgfx::copy(texels, sizeof(texels))); +} + +static void BindTextureStages() +{ + PERF_TIME(PERF_SECT_APPLY_TEX); + if (BgfxProbeFlag("GGC_PROBE_FREEZE_STATE") + || BgfxProbeFlag("GGC_PROBE_NO_TEXBIND")) + { + return; + } + const uint64_t state = GetEffectiveDrawState(); + if (bgfx::isValid(g_uniforms.sTex0)) + { + const bgfx::TextureHandle stageTexture = GetCurrentStageTextureHandle(0); + const bgfx::TextureHandle bound = + g_draw.textureIsMissing[0] && ShouldHideMissingTextureForCurrentDraw(state) + ? g_device.defaultTransparentTexture + : (bgfx::isValid(stageTexture) ? stageTexture : g_device.defaultWhiteTexture); + if (bgfx::isValid(bound)) + { + bgfx::setTexture(0, g_uniforms.sTex0, bound, GetCurrentStageSamplerFlags(0)); + g_stats.textureBinds++; + } + } + if (bgfx::isValid(g_uniforms.sTex1)) + { + const bgfx::TextureHandle stageTexture = GetCurrentStageTextureHandle(1); + const bgfx::TextureHandle bound = + g_draw.textureIsMissing[1] && ShouldHideMissingTextureForCurrentDraw(state) + ? g_device.defaultTransparentTexture + : (bgfx::isValid(stageTexture) ? stageTexture : g_device.defaultWhiteTexture); + if (bgfx::isValid(bound)) + { + bgfx::setTexture(1, g_uniforms.sTex1, bound, GetCurrentStageSamplerFlags(1)); + g_stats.textureBinds++; + } + } + if (bgfx::isValid(g_uniforms.sTex2)) + { + const bgfx::TextureHandle stageTexture = GetCurrentStageTextureHandle(2); + const bgfx::TextureHandle bound = + g_draw.textureIsMissing[2] && ShouldHideMissingTextureForCurrentDraw(state) + ? g_device.defaultTransparentTexture + : (bgfx::isValid(stageTexture) ? stageTexture : g_device.defaultWhiteTexture); + if (bgfx::isValid(bound)) + { + bgfx::setTexture(2, g_uniforms.sTex2, bound, GetCurrentStageSamplerFlags(2)); + g_stats.textureBinds++; + } + } + if (bgfx::isValid(g_uniforms.sTex3)) + { + const bgfx::TextureHandle stageTexture = GetCurrentStageTextureHandle(3); + const bgfx::TextureHandle bound = + g_draw.textureIsMissing[3] && ShouldHideMissingTextureForCurrentDraw(state) + ? g_device.defaultTransparentTexture + : (bgfx::isValid(stageTexture) ? stageTexture : g_device.defaultWhiteTexture); + if (bgfx::isValid(bound)) + { + bgfx::setTexture(3, g_uniforms.sTex3, bound, GetCurrentStageSamplerFlags(3)); + g_stats.textureBinds++; + } + } +} + +static float GetTexcoordSource(unsigned texcoordGen) +{ + if (texcoordGen == kTexcoordGenCameraNormal) + { + return 1.0f; + } + if (texcoordGen == kTexcoordGenCameraReflection) + { + return 2.0f; + } + if (texcoordGen == kTexcoordGenCameraPosition) + { + return 3.0f; + } + return 0.0f; +} + +static void SetIdentityTextureTransform(float * row0, float * row1) +{ + row0[0] = 1.0f; + row0[1] = 0.0f; + row0[2] = 0.0f; + row0[3] = 0.0f; + row1[0] = 0.0f; + row1[1] = 1.0f; + row1[2] = 0.0f; + row1[3] = 0.0f; +} + +static void ReadTextureTransform(unsigned stage, float * row0, float * row1) +{ + auto texMtx = MakeIdentityLegacyCacheMatrix(); + FixedFunctionState::Transform_Matrix(kTextureTransformStage0 + stage, texMtx); + row0[0] = texMtx.m[0][0]; + row0[1] = texMtx.m[1][0]; + row0[2] = texMtx.m[2][0]; + row0[3] = texMtx.m[3][0]; + row1[0] = texMtx.m[0][1]; + row1[1] = texMtx.m[1][1]; + row1[2] = texMtx.m[2][1]; + row1[3] = texMtx.m[3][1]; +} + +// TheSuperHackers @feature bobtista 30/04/2026 Read column 2 of the texture +// matrix for projected 3-component stages - TexProjectClass uses +// this column (= ViewToPixel row 3 in MatrixMapperClass::Apply) as the +// projected W. The shader divides UV.xy by this value at vertex time. +static void ReadTextureTransformZ(unsigned stage, float * rowZ) +{ + auto texMtx = MakeIdentityLegacyCacheMatrix(); + FixedFunctionState::Transform_Matrix(kTextureTransformStage0 + stage, texMtx); + rowZ[0] = texMtx.m[0][2]; + rowZ[1] = texMtx.m[1][2]; + rowZ[2] = texMtx.m[2][2]; + rowZ[3] = texMtx.m[3][2]; +} + +static void UpdateTextureTransforms() +{ + g_stats.textureTransformUpdates++; + // TheSuperHackers @bugfix bobtista 25/04/2026 Honor material-stage texture + // matrices in the bgfx uber shader. W3D atlas mappers animate tank + // treads, bike wheels, and other sub-materials by setting + // cached texture transform plus two-coordinate transform mode. Passing raw UVs sampled the + // unused black padding in those atlases; stage 1 matters for detail + // and environment-mapped sub-materials. + const unsigned texcoordIndex = g_draw.texcoordIndex[0]; + const unsigned uvIndex = texcoordIndex & 0xFFFF; + const unsigned texcoordGen = texcoordIndex & 0xFFFF0000; + // TheSuperHackers @info bobtista 26/04/2026 Only UV sets 0 and 1 are + // supported. The legacy fixed-function path allows up to 8 UV sets but + // the uber shader only has v_texcoord0/v_texcoord1. Extend if any + // material is found using UV set 2+. + if (uvIndex > 1) + { + static bool s_loggedUV2 = false; + if (!s_loggedUV2) + { + s_loggedUV2 = true; + WWDEBUG_SAY(("[BgfxBackend] Stage 0 TEXCOORDINDEX uses UV set %u (only 0/1 supported)", uvIndex)); + } + } + g_draw.texcoordSelect[0] = (uvIndex == 1) ? 1.0f : 0.0f; + g_draw.texcoordSource[0] = GetTexcoordSource(texcoordGen); + + const unsigned texFlags = g_draw.textureTransformFlags[0]; + const unsigned texCount = texFlags & 0xFFu; + const bool texProjected0 = (texFlags & kTextureTransformProjected) != 0 + && texCount >= kTextureTransformCount3; + if (texCount >= kTextureTransformCount2) + { + g_draw.texcoordSelect[3] = 1.0f; + ReadTextureTransform(0, g_draw.texTransform0, g_draw.texTransform1); + if (texProjected0) + { + ReadTextureTransformZ(0, g_draw.texTransform0Z); + } + } + else + { + g_draw.texcoordSelect[3] = 0.0f; + SetIdentityTextureTransform(g_draw.texTransform0, g_draw.texTransform1); + } + g_draw.texProjected[0] = texProjected0 ? 1.0f : 0.0f; + + const unsigned texcoordIndex1 = g_draw.texcoordIndex[1]; + const unsigned uvIndex1 = texcoordIndex1 & 0xFFFF; + const unsigned texcoordGen1 = texcoordIndex1 & 0xFFFF0000; + if (uvIndex1 > 1) + { + static bool s_loggedUV2_s1 = false; + if (!s_loggedUV2_s1) + { + s_loggedUV2_s1 = true; + WWDEBUG_SAY(("[BgfxBackend] Stage 1 TEXCOORDINDEX uses UV set %u (only 0/1 supported)", uvIndex1)); + } + } + g_draw.texcoordSelect2[0] = (uvIndex1 == 1) ? 1.0f : 0.0f; + g_draw.texcoordSource[1] = GetTexcoordSource(texcoordGen1); + + const unsigned texFlags1 = g_draw.textureTransformFlags[1]; + const unsigned texCount1 = texFlags1 & 0xFFu; + const bool texProjected1 = (texFlags1 & kTextureTransformProjected) != 0 + && texCount1 >= kTextureTransformCount3; + if (texCount1 >= kTextureTransformCount2) + { + g_draw.texcoordSelect2[1] = 1.0f; + ReadTextureTransform(1, g_draw.tex1Transform0, g_draw.tex1Transform1); + if (texProjected1) + { + ReadTextureTransformZ(1, g_draw.tex1TransformZ); + } + } + else + { + g_draw.texcoordSelect2[1] = 0.0f; + SetIdentityTextureTransform(g_draw.tex1Transform0, g_draw.tex1Transform1); + } + g_draw.texProjected[1] = texProjected1 ? 1.0f : 0.0f; + + const unsigned texcoordIndex2 = g_draw.texcoordIndex[2]; + const unsigned texcoordGen2 = texcoordIndex2 & 0xFFFF0000; + g_draw.texcoordSource[2] = GetTexcoordSource(texcoordGen2); + + const unsigned texFlags2 = g_draw.textureTransformFlags[2]; + const unsigned texCount2 = texFlags2 & 0xFFu; + if (texCount2 >= kTextureTransformCount2) + { + ReadTextureTransform(2, g_draw.tex2Transform0, g_draw.tex2Transform1); + } + else + { + SetIdentityTextureTransform(g_draw.tex2Transform0, g_draw.tex2Transform1); + } +} + +static void UploadMaterialUniforms_Body(bgfx::ViewId submitView); +static void UploadMaterialUniforms(bgfx::ViewId submitView) +{ + PERF_TIME(PERF_SECT_UPLOAD_UNIFORMS); + if (BgfxProbeFlag("GGC_PROBE_FREEZE_STATE") + || BgfxProbeFlag("GGC_PROBE_NO_MATUNIFORM")) + { + return; + } + UploadMaterialUniforms_Body(submitView); +} +static void UploadMaterialUniforms_Body(bgfx::ViewId submitView) +{ + g_stats.materialUniformUploads++; + if (bgfx::isValid(g_uniforms.uMaterial)) + { + // Pack the per-draw material block and upload it in a single setUniform. + // Slot order MUST match MaterialUniformSlot and the shader #define block. + float m[MU_COUNT][4]; + std::memcpy(m[MU_MatDiffuse], g_draw.matDiffuse, sizeof(float) * 4); + std::memcpy(m[MU_MatAmbient], g_draw.matAmbient, sizeof(float) * 4); + std::memcpy(m[MU_MatEmissive], g_draw.matEmissive, sizeof(float) * 4); + m[MU_TssOps0][0] = g_draw.tssOps0[0]; + m[MU_TssOps0][1] = g_draw.tssOps0[1]; + m[MU_TssOps0][2] = g_draw.shaderTssOps0[2]; + m[MU_TssOps0][3] = g_draw.shaderTssOps0[3]; + std::memcpy(m[MU_TssOps1], g_draw.tssOps1, sizeof(float) * 4); + { + const float effectiveAtestRef = g_overrides.atestActive ? g_overrides.atestRef : g_draw.atestRef; + const float effectiveAtestFunc = g_overrides.atestActive ? g_overrides.atestFunc : (g_draw.atestEnabled ? g_draw.atestFunc : 0.0f); + m[MU_AtestParams][0] = effectiveAtestRef; + m[MU_AtestParams][1] = effectiveAtestFunc; + m[MU_AtestParams][2] = 0.0f; + m[MU_AtestParams][3] = 0.0f; + } + std::memcpy(m[MU_TexcoordSource], g_draw.texcoordSource, sizeof(float) * 4); + std::memcpy(m[MU_VertexColorFlags], g_draw.vertexColorFlags, sizeof(float) * 4); + std::memcpy(m[MU_TexcoordSelect2], g_draw.texcoordSelect2, sizeof(float) * 4); + std::memcpy(m[MU_ProjectedDecalMode], g_draw.projectedDecalMode, sizeof(float) * 4); + std::memcpy(m[MU_GrayscaleEnable], g_draw.grayscaleEnable, sizeof(float) * 4); + m[MU_ObjectShroudDim][0] = g_views.objectShroudTexturePassActive ? g_draw.objectShroudDim[0] : 1.0f; + m[MU_ObjectShroudDim][1] = g_views.objectShroudTexturePassActive ? g_draw.objectShroudDim[1] : 0.0f; + m[MU_ObjectShroudDim][2] = g_views.objectShroudTexturePassActive ? g_draw.objectShroudDim[2] : 0.0f; + m[MU_ObjectShroudDim][3] = g_draw.objectShroudDim[3]; + std::memcpy(m[MU_CloudParams], g_draw.cloudParams, sizeof(float) * 4); + std::memcpy(m[MU_TexTransform0], g_draw.texTransform0, sizeof(float) * 4); + std::memcpy(m[MU_TexTransform1], g_draw.texTransform1, sizeof(float) * 4); + std::memcpy(m[MU_TexTransform0Z], g_draw.texTransform0Z, sizeof(float) * 4); + std::memcpy(m[MU_Tex1Transform0], g_draw.tex1Transform0, sizeof(float) * 4); + std::memcpy(m[MU_Tex1Transform1], g_draw.tex1Transform1, sizeof(float) * 4); + std::memcpy(m[MU_Tex1TransformZ], g_draw.tex1TransformZ, sizeof(float) * 4); + std::memcpy(m[MU_Tex2Transform0], g_draw.tex2Transform0, sizeof(float) * 4); + std::memcpy(m[MU_Tex2Transform1], g_draw.tex2Transform1, sizeof(float) * 4); + std::memcpy(m[MU_TexProjected], g_draw.texProjected, sizeof(float) * 4); + std::memcpy(m[MU_LegacyPixelShaderMode], g_draw.legacyPixelShaderMode, sizeof(float) * 4); + std::memcpy(m[MU_ZBias], g_draw.zBias, sizeof(float) * 4); + std::memcpy(m[MU_LightMapParams], g_draw.lightMapParams, sizeof(float) * 4); + // TheSuperHackers @performance bobtista 11/07/2026 Consecutive draws in + // one view frequently share the whole packed block (rigid category + // meshes, grouped sorted runs) — elide the re-upload when unchanged. + static FrameConstUniformGuard s_materialBlockGuard; + if (ShouldUploadFrameConstUniform(s_materialBlockGuard, submitView, m, sizeof(m))) + { + bgfx::setUniform(g_uniforms.uMaterial, m, MU_COUNT); + CountUniformCommand(g_stats.materialUniformCommands); + } + } + // u_matSpecular is not part of the packed material array, so upload it individually. + if (bgfx::isValid(g_uniforms.uMatSpecular)) + { + static FrameConstUniformGuard s_matSpecularGuard; + if (ShouldUploadFrameConstUniform(s_matSpecularGuard, submitView, + g_draw.matSpecular, sizeof(g_draw.matSpecular))) + { + bgfx::setUniform(g_uniforms.uMatSpecular, g_draw.matSpecular); + CountUniformCommand(g_stats.materialUniformCommands); + } + } + if (bgfx::isValid(g_uniforms.uSunShadowReceive)) + { + static FrameConstUniformGuard s_sunShadowReceiveGuard; + if (ShouldUploadFrameConstUniform(s_sunShadowReceiveGuard, submitView, + g_draw.sunShadowReceive, sizeof(g_draw.sunShadowReceive))) + { + bgfx::setUniform(g_uniforms.uSunShadowReceive, g_draw.sunShadowReceive); + CountUniformCommand(g_stats.materialUniformCommands); + } + } + if (bgfx::isValid(g_uniforms.sCloudMap)) + { + // WRAP addressing matches the DX8 cloud pass at W3DShaderManager.cpp:1742. + // TheSuperHackers @bugfix bobtista 30/04/2026 fs_uber declares + // SAMPLER2D(s_cloudMap, 5); Metal validation requires slot 5 + // to be bound on every draw even when u_cloudParams.w = 0 + // disables the cloud blend. Fall back to defaultWhiteTexture + // when no cloud texture has been set yet (early frames, UI). + bgfx::TextureHandle h = bgfx::isValid(g_draw.cloudTex) + ? g_draw.cloudTex + : g_device.defaultWhiteTexture; + if (bgfx::isValid(h)) + { + bgfx::setTexture(5, g_uniforms.sCloudMap, h, BGFX_SAMPLER_NONE); + g_stats.textureBinds++; + } + } + if (bgfx::isValid(g_uniforms.sLightMap)) + { + // White fallback = multiply identity when the lightmap is disabled or unset. + bgfx::TextureHandle lm = bgfx::isValid(g_draw.lightMapTex) + ? g_draw.lightMapTex + : g_device.defaultWhiteTexture; + if (bgfx::isValid(lm)) + { + bgfx::setTexture(11, g_uniforms.sLightMap, lm, BGFX_SAMPLER_NONE); + g_stats.textureBinds++; + } + } +} + +// TheSuperHackers @bugfix bobtista 16/07/2026 Run the material's UV mappers at +// material bind. The DX8 path applies them in Commit_Deferred_Render_State_Changes +// via VertexMaterialClass::Apply, but that block is compiled out of the bgfx build +// and nothing replaced it, so animated mappers (Grid flipbooks, Linear Offset +// scrolls, Rotate) never rebuilt their texture matrices or texcoord routing and +// rendered frozen at their initial frame. Mirrors the mapper loop of +// VertexMaterialClass::Apply for the two stages a W3D vertex material carries; +// stages 2+ hold terrain cloud/noise state owned by other passes and stay untouched. +static void ApplyMaterialMappersForBgfx(BgfxBackend * backend, const VertexMaterialClass * material) +{ + static const bool s_disabled = GgcFlags::Enabled(GgcFlag_BgfxNoMapperApply); + if (s_disabled) + { + return; + } + for (int stage = 0; stage < 2; ++stage) + { + if (material != nullptr) + { + VertexMaterialClass * mutableMaterial = const_cast(material); + TextureMapperClass * mapper = mutableMaterial->Peek_Mapper(stage); + if (mapper != nullptr) + { + mapper->Apply(mutableMaterial->Get_UV_Source(stage)); + continue; + } + backend->Set_Texture_Coord_Source(stage, RB_TEXCOORD_MESH_UV, mutableMaterial->Get_UV_Source(stage)); + } + else + { + backend->Set_Texture_Coord_Source(stage, RB_TEXCOORD_MESH_UV, stage); + } + backend->Set_Texture_Transform_Mode(stage, 0, false); + } +} + +// Mirror the material fields used by fs_uber without applying DX8 state. +// This is called from Set_Material and again at submit time because +// DX8Wrapper::Draw can apply pending material state directly through +// VertexMaterialClass::Apply after the bgfx backend saw the original setter. +static void CaptureMaterialStateForBgfx(const VertexMaterialClass * material) +{ + if (material != nullptr) + { + Vector3 diffuse(1.0f, 1.0f, 1.0f); + Vector3 ambient(1.0f, 1.0f, 1.0f); + const VertexMaterialClass::ColorSourceType diffuseSource = + const_cast(material)->Get_Diffuse_Color_Source(); + const VertexMaterialClass::ColorSourceType ambientSource = + const_cast(material)->Get_Ambient_Color_Source(); + const VertexMaterialClass::ColorSourceType emissiveSource = + const_cast(material)->Get_Emissive_Color_Source(); + if (diffuseSource == VertexMaterialClass::MATERIAL) + { + material->Get_Diffuse(&diffuse); + } + if (ambientSource == VertexMaterialClass::MATERIAL) + { + material->Get_Ambient(&ambient); + } + 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(); + g_draw.matAmbient[0] = ambient.X; + g_draw.matAmbient[1] = ambient.Y; + g_draw.matAmbient[2] = ambient.Z; + g_draw.matAmbient[3] = 1.0f; + g_draw.vertexColorFlags[1] = + (diffuseSource == VertexMaterialClass::COLOR1) ? 1.0f : 0.0f; + g_draw.vertexColorFlags[2] = + (ambientSource == VertexMaterialClass::COLOR1) ? 1.0f : 0.0f; + g_draw.vertexColorFlags[3] = + (emissiveSource == VertexMaterialClass::COLOR1) ? 1.0f : 0.0f; + g_draw.lightingEnabled[0] = + (material->Get_Lighting() + && FixedFunctionState::Lighting_Enabled(false) + && !WW3D::Is_Coloring_Enabled()) ? 1.0f : 0.0f; + + Vector3 emissive(0.0f, 0.0f, 0.0f); + material->Get_Emissive(&emissive); + g_draw.matEmissive[0] = emissive.X; + g_draw.matEmissive[1] = emissive.Y; + g_draw.matEmissive[2] = emissive.Z; + g_draw.matEmissive[3] = 0.0f; + + Vector3 specular(0.0f, 0.0f, 0.0f); + material->Get_Specular(&specular); + g_draw.matSpecular[0] = specular.X; + g_draw.matSpecular[1] = specular.Y; + g_draw.matSpecular[2] = specular.Z; + g_draw.matSpecular[3] = material->Get_Shininess(); + } + else + { + 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.matAmbient[0] = 1.0f; + g_draw.matAmbient[1] = 1.0f; + g_draw.matAmbient[2] = 1.0f; + g_draw.matAmbient[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; + g_draw.matSpecular[0] = 0.0f; + g_draw.matSpecular[1] = 0.0f; + g_draw.matSpecular[2] = 0.0f; + g_draw.matSpecular[3] = 1.0f; + g_draw.vertexColorFlags[1] = 0.0f; + g_draw.vertexColorFlags[2] = 0.0f; + g_draw.vertexColorFlags[3] = 0.0f; + g_draw.lightingEnabled[0] = 0.0f; + } +} + +// TheSuperHackers @refactor bobtista 11/04/2026 Sorted VB direct-draw submit: claims the +// transients Capture_Dynamic_* stashed for Draw_Sorting_IB_VB's inner buffers, submits to +// the sorted view with remapped args, and skips the outer Draw_Triangles submit. + +// TheSuperHackers @performance bobtista 08/07/2026 Sorted texture-array merge. +// A sorted node is page-eligible when its draw samples exactly one stage-0 +// texture through UV channel 0; such nodes can share one merged draw with +// neighbors that differ only by that texture, selecting the layer per vertex. +// Eligibility is decided here at capture time, when g_draw holds the exact +// translated state the replayed submit will apply: interrogating the material +// object at pool-fill time gave divergent answers (prelit particle materials +// carry a nonzero object-level emissive that their unlit draws never read). +static void ComputeSortedTextureArraySlot(RenderStateStruct & state) +{ + // Default ON since the 2026-07-11 Windows benchmark verdict; see + // Sorted_Texture_Array_Merge_Enabled in sortingrenderer.cpp. + static const bool s_enabled = !GgcFlags::Enabled(GgcFlag_BgfxNoSortedTextureArray); + if (!s_enabled || !g_device.initialized || !bgfx::isValid(g_device.sortedArrayProgram)) + { + return; + } + // Point groups author their vertices in world space with an identity or + // camera-only world, so the fill loop's world bake is exact for them. + // TheSuperHackers @bugfix bobtista 10/07/2026 Streaks are excluded: + // StreakRendererClass::RenderStreak transforms its points into eye space + // on the CPU (modelview = view * transform) and inserts with identity + // world AND view, so the bake's world multiply is a no-op and the merged + // submit's view-only transform would re-apply the camera view to + // eye-space vertices, throwing the geometry off screen. Other sorted + // classes (local-model mesh sprites, seglines, decals, reveal grids) + // also stay on the classic per-run replay until each earns its way in + // with a verified bake contract. + if ((state.sorted_draw_flags & RB_SORTED_DRAW_POINT_GROUP) == 0) + { + return; + } + if (state.shader.Get_Texturing() != ShaderClass::TEXTURING_ENABLE) + { + return; + } + if (state.Textures[0] == nullptr || state.Textures[0]->As_TextureClass() == nullptr) + { + return; + } + for (int stage = 1; stage < MAX_TEXTURE_STAGES; ++stage) + { + if (state.Textures[stage] != nullptr) + { + return; + } + } + // The merged path carries the layer in the vertex normal's z and the + // page-scaled UVs in the second UV channel, so it is restricted to draws + // whose normals are guaranteed unused and whose UVs are plain channel 0. + const ShaderClass & shader = state.shader; + if (shader.Get_Depth_Mask() != ShaderClass::DEPTH_WRITE_DISABLE + || shader.Get_Alpha_Test() != ShaderClass::ALPHATEST_DISABLE) + { + return; + } + const ShaderClass::SrcBlendFuncType srcBlend = shader.Get_Src_Blend_Func(); + const ShaderClass::DstBlendFuncType dstBlend = shader.Get_Dst_Blend_Func(); + const bool particleBlend = + (srcBlend == ShaderClass::SRCBLEND_ONE && dstBlend == ShaderClass::DSTBLEND_ONE) + || (srcBlend == ShaderClass::SRCBLEND_SRC_ALPHA && dstBlend == ShaderClass::DSTBLEND_ONE) + || (srcBlend == ShaderClass::SRCBLEND_SRC_ALPHA && dstBlend == ShaderClass::DSTBLEND_ONE_MINUS_SRC_ALPHA); + if (!particleBlend) + { + return; + } + // TheSuperHackers @bugfix bobtista 17/07/2026 Peek, don't Get: Get_Mapper Add_Refs the + // mapper and the reference was never released, leaking one ref per captured point-group + // node per frame. + if (state.material == nullptr + || state.material->Get_UV_Source(0) != 0 + || state.material->Peek_Mapper(0) != nullptr) + { + return; + } + // The vertex normal is only safe to repurpose when the shader never reads + // it: either the draw is unlit outright (prelit particle materials), or + // the baked-color force-unlit will fire at submit. Both sides of this + // predicate read the same translated g_draw state the submit reads, so + // they cannot drift apart. + const bool unlit = g_draw.lightingEnabled[0] <= 0.5f; + if (!unlit && !ShouldForceUnlitForBakedColorDraw(GetEffectiveDrawState())) + { + return; + } + int layer = -1; + float scaleU = 1.0f; + float scaleV = 1.0f; + const int page = BgfxSortedTextureArrayGetSlot(state.Textures[0], &layer, &scaleU, &scaleV); + if (page < 0) + { + return; + } + state.sorted_array_page = page; + state.sorted_array_layer = layer; + state.sorted_array_scale_u = scaleU; + state.sorted_array_scale_v = scaleV; +} + +void BgfxBackend::Set_Sorted_Texture_Array_Page(int page) +{ + g_draw.sortedArrayPage = page; +} + +void BgfxBackend::Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, + const DynamicIBAccessClass & dyn_ib, + unsigned short polygon_count, + unsigned short vertex_count) +{ + if (!g_device.initialized) + { + return; + } + g_stats.sortedDraws++; + if (BgfxProbeFlag("GGC_PROBE_NO_SORTED") + || BgfxProbeFlag("GGC_PROBE_NULL_SUBMIT")) + { + g_views.skipNextSubmitEngineDraw = true; + g_stats.skippedDraws++; + bgfx::discard(BGFX_DISCARD_ALL); + 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. + // TheSuperHackers @bugfix bobtista 08/07/2026 A texture-array run's pool + // vertices were baked into world space at fill time, so the classic + // fallback submit (which applies the captured world*view again) would + // double-transform them; drop the run for this frame instead. + if (!g_draw.pendingVB.valid || g_draw.pendingVB.owner != &dyn_vb) + { + static bool s_loggedSkipVB = false; + if (!s_loggedSkipVB) + { + s_loggedSkipVB = true; + WWDEBUG_SAY(("[BgfxBackend] Submit_Sorted_Draw SKIP: pendingDynVB not " + "claimable (valid=%d ownerMatch=%d)", + int(g_draw.pendingVB.valid), + int(g_draw.pendingVB.owner == &dyn_vb))); + } + if (g_draw.sortedArrayPage >= 0) + { + g_views.skipNextSubmitEngineDraw = true; + } + g_stats.skippedDraws++; + return; + } + if (!g_draw.pendingIB.valid || g_draw.pendingIB.owner != &dyn_ib) + { + static bool s_loggedSkipIB = false; + if (!s_loggedSkipIB) + { + s_loggedSkipIB = true; + WWDEBUG_SAY(("[BgfxBackend] Submit_Sorted_Draw SKIP: pendingDynIB not " + "claimable (valid=%d ownerMatch=%d)", + int(g_draw.pendingIB.valid), + int(g_draw.pendingIB.owner == &dyn_ib))); + } + if (g_draw.sortedArrayPage >= 0) + { + g_views.skipNextSubmitEngineDraw = true; + } + g_stats.skippedDraws++; + return; + } + + const bgfx::TransientVertexBuffer vb = g_draw.pendingVB.tvb; + const bgfx::TransientIndexBuffer ib = g_draw.pendingIB.tib; + const FVFInfoClass & traceFvf = dyn_vb.FVF_Info(); + const unsigned traceStride = traceFvf.Get_FVF_Size(); + g_draw.activeVertexNormalBias = g_draw.pendingVB.coplanarNormalBias; + g_draw.pendingVB.valid = false; + g_draw.pendingIB.valid = false; + + if (!bgfx::isValid(g_draw.program)) + { + g_views.skipNextSubmitEngineDraw = true; + g_stats.skippedDraws++; + 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_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 uint64_t earlyState = GetEffectiveDrawState(); + const bool localModelSortedDraw = IsSortedLocalModelEffectDraw(earlyState); + const bgfx::ViewId submitView = localModelSortedDraw ? kBgfxEngineView : kBgfxEngineSortView; + const float * worldMtx = g_views.inSortFlush ? g_frame.sortWorld : g_frame.world; + if (localModelSortedDraw) + { + // These sorted quads are authored in model space. Use the raw per-mesh + // world so animated local geometry lands on the object instead of + // folding through the sort view's pre-multiplied matrix. + worldMtx = g_frame.sortWorldRaw; + } + if (g_draw.sortedArrayPage >= 0) + { + // World-baked run: positions were transformed to world space when the + // sorted pool was filled, so apply the view only (identity for the + // engine-view local-model route, which carries the real camera view). + static const float s_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 }; + worldMtx = localModelSortedDraw ? s_identity : g_frame.sortViewOnly; + } + bgfx::setTransform(worldMtx); + + bgfx::setVertexBuffer(0, &vb, 0, vertex_count); + bgfx::setIndexBuffer(&ib, 0, static_cast(polygon_count) * 3); + + BindTextureStages(); + UpdateTextureTransforms(); + if (IsSortedMaterialDecal(GetEffectiveDrawState())) + { + // Terrain rendering leaves this flag set until reset by the + // shader manager. Sorted material decals, including command-center + // driveway emblems, use the fixed-function TSS path and must not + // inherit the terrain pixel-shader branch. Submit_Sorted_Draw can + // be reached by rigid sorting draws even when g_views.inSortFlush + // is false, so key this reset to the draw signature itself. + g_draw.texcoordSelect[1] = 0.0f; + } + { + g_draw.zBias[0] = static_cast(g_draw.zBiasUnits) * kZBiasPerUnit; + TraceLegacyZBiasTranslation(); + const bool applySubmittedNormalBias = ShouldApplySubmittedNormalBias(GetEffectiveDrawState()); + const bool normalBiasFromGeometry = + g_draw.normalBias[0] != 0.0f + || (g_draw.activeVertexNormalBias && applySubmittedNormalBias) + || IsSneakAttackCoplanarSurface(); + g_draw.zBias[1] = normalBiasFromGeometry + ? ((g_draw.normalBias[0] < 0.02f) ? 0.02f : g_draw.normalBias[0]) + : 0.0f; + ClampSortedMaterialDecalZBias(); + } + UpdateProjectedDecalModeForCurrentDraw(); + uint64_t state = GetEffectiveDrawState(); + { + g_draw.texcoordSelect2[3] = IsAnyAdditiveBlend(state) + ? 1.0f + : 0.0f; + } + UpdateAlphaMaskAndSortedModes(state); + UploadMaterialUniforms(submitView); + if (bgfx::isValid(g_uniforms.uTexcoordSelect)) + { + bgfx::setUniform(g_uniforms.uTexcoordSelect, g_draw.texcoordSelect); + } + const bool forceUnlitLighting = ShouldForceUnlitForBakedColorDraw(state); + const bool fixedFunctionLightInputsNeeded = + g_draw.lightingEnabled[0] > 0.5f + && !forceUnlitLighting; + UploadLightUniforms(fixedFunctionLightInputsNeeded, submitView); + // Match SubmitEngineDraw for sorted dynamic particles/effects. Additive + // sprites, soft alpha particles, and material decals bake intensity in + // vertex diffuse or the source texture; the shader's lit branch would + // ignore that baked color and multiply by scene light instead. + if (bgfx::isValid(g_uniforms.uLightingEnabled)) + { + float lit[4] = { g_draw.lightingEnabled[0], 0.0f, 0.0f, 0.0f }; + if (forceUnlitLighting) + { + lit[0] = 0.0f; + } + bgfx::setUniform(g_uniforms.uLightingEnabled, lit); + } + + state = ApplyCullModeOverride(state); + state = ApplyBlendEquation(state); + state = ApplyProjectedAdditiveDecalDrawState(state); + state = ApplyColorWriteOverride(state); + state = ApplySortedMaterialDecalDepthState(state); + // TheSuperHackers @bugfix bobtista 12/06/2026 Match SubmitEngineDraw: enable MSAA so sorted + // translucent effects get the same edge antialiasing as opaque geometry on a multisampled target. + state |= BGFX_STATE_MSAA; + LogBgfxSortedMaterialDecal("submit-sorted", submitView, + polygon_count, vertex_count, state); + LogBgfxEffectSubmit("submit-sorted", submitView, + polygon_count, vertex_count, state, "pre-skip"); + LogBgfxRevealDraw("submit-sorted", submitView, + polygon_count, vertex_count, state, "pre-skip"); + + if (ShouldAllowBgfxDiagnosticDrawOverrides() + && GgcFlags::Enabled(GgcFlag_BgfxSkipRevealGrid) + && IsRevealGridTexture(g_draw.sourceTextures[0])) + { + g_stats.skippedDraws++; + bgfx::discard(BGFX_DISCARD_ALL); + // A skipped sorted draw must also suppress the fallback + // SubmitEngineDraw, matching the skip-missing path below. + g_views.skipNextSubmitEngineDraw = true; + return; + } + + if (ShouldSkipHiddenMissingTextureDraw(state)) + { + LogBgfxRevealDraw("submit-sorted", submitView, + polygon_count, vertex_count, state, "skip-missing"); + g_stats.skippedDraws++; + // TheSuperHackers @bugfix bobtista 06/06/2026 Discard the queued encoder state (transform/ + // VB/IB/textures/uniforms) and suppress the fallback SubmitEngineDraw, matching the success + // path below. Without this the queued state leaks into the next draw and the outer + // Draw_Triangles re-submits with stale sorting-VB args. + bgfx::discard(BGFX_DISCARD_ALL); + g_views.skipNextSubmitEngineDraw = true; + return; + } + + bgfx::setState(state); + // TheSuperHackers @bugfix bobtista 12/06/2026 Sorted translucent effects never use the stencil; + // clear it explicitly so a preceding stencil pass (shadow volumes / shroud) can't leak its + // stencil test into this submit. SubmitEngineDraw achieves the same via its applyStencil gate. + bgfx::setStencil(BGFX_STENCIL_NONE); + BindSoftParticleDepth(submitView == kBgfxEngineSortView + && IsSoftParticleCandidate(state)); + bgfx::ProgramHandle program = g_draw.program; + if (g_draw.sortedArrayPage >= 0) + { + const bgfx::TextureHandle pageTex = BgfxSortedTextureArrayPageHandle(g_draw.sortedArrayPage); + if (bgfx::isValid(pageTex) && bgfx::isValid(g_device.sortedArrayProgram)) + { + bgfx::setTexture(kBgfxSortedArraySamplerStage, g_uniforms.sTexArray, + pageTex, + g_draw.samplerFlags[0] | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + program = g_device.sortedArrayProgram; + } + } + bgfx::submit(submitView, program); + LogBgfxEffectSubmit("submit-sorted", submitView, + polygon_count, vertex_count, state, "submit"); + LogBgfxRevealDraw("submit-sorted", submitView, + polygon_count, vertex_count, state, "submit"); + g_stats.baseSubmits++; + g_stats.transientVbDraws++; + g_stats.transientIbDraws++; + g_views.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, +// 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_draw.pendingVB and claims the transient for the draw. + +static bool SameSubmittedPosition(const float * a, const float * b) +{ + const float dx = a[0] - b[0]; + const float dy = a[1] - b[1]; + const float dz = a[2] - b[2]; + return dx * dx + dy * dy + dz * dz < 0.000001f; +} + +static bool HasSubmittedOppositeNormalPairs(const FVFInfoClass & fvf, + const void * data, + uint32_t numVerts) +{ + if (!fvf.Has_Normal() || data == nullptr || numVerts < 2 || numVerts > 4096) + { + return false; + } + + const uint8_t * bytes = static_cast(data); + const unsigned stride = fvf.Get_FVF_Size(); + const unsigned positionOffset = fvf.Get_Location_Offset(); + const unsigned normalOffset = fvf.Get_Normal_Offset(); + for (uint32_t i = 0; i < numVerts; ++i) + { + const float * pi = reinterpret_cast(bytes + i * stride + positionOffset); + const float * ni = reinterpret_cast(bytes + i * stride + normalOffset); + for (uint32_t j = i + 1; j < numVerts; ++j) + { + const float * pj = reinterpret_cast(bytes + j * stride + positionOffset); + if (!SameSubmittedPosition(pi, pj)) + { + continue; + } + const float * nj = reinterpret_cast(bytes + j * stride + normalOffset); + const float dot = ni[0] * nj[0] + ni[1] * nj[1] + ni[2] * nj[2]; + if (dot < -0.9f) + { + return true; + } + } + } + return false; +} + +void BgfxBackend::Capture_Dynamic_Vertex_Data(const DynamicVBAccessClass * vba, + const void * data, + unsigned int size_bytes) +{ + if (!g_device.initialized || 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) + { + LogBgfxTransientDiag("capture", "vb", vba, num_verts, + g_draw.pendingVB.valid, + g_draw.pendingVB.owner == vba, + g_draw.useTransientVB, + g_draw.activeTransientVBOwner == vba, + "no-avail"); + g_draw.pendingVB.valid = false; + g_draw.pendingVB.coplanarNormalBias = false; + return; + } + + bgfx::allocTransientVertexBuffer(&g_draw.pendingVB.tvb, num_verts, layout); + g_stats.transientVbAllocations++; + const uint32_t copy_bytes = num_verts * layout.getStride(); + const uint32_t bytes = (size_bytes < copy_bytes) ? size_bytes : copy_bytes; + std::memcpy(g_draw.pendingVB.tvb.data, data, bytes); + g_draw.pendingVB.owner = vba; + g_draw.pendingVB.valid = true; + // TheSuperHackers @performance bobtista 04/06/2026 Gate the O(n^2) coplanar scan + // on ShouldApplySubmittedNormalBias (see End_Dynamic_Vertex_Write) so it only runs + // for the rare sorted decals that consume the result, not every particle batch. + g_draw.pendingVB.coplanarNormalBias = + ShouldScanSubmittedNormalBiasForCurrentDynamicWrite() + && HasSubmittedOppositeNormalPairs(vba->FVF_Info(), data, num_verts); + LogBgfxTransientDiag("capture", "vb", vba, num_verts, + true, + true, + g_draw.useTransientVB, + g_draw.activeTransientVBOwner == vba, + "ok"); + + // TheSuperHackers @bugfix bobtista 30/04/2026 Track FVF normal presence + // for transient-VB submits so the engine-view targeted lit-on override + // in SubmitEngineDraw works for translucent meshes and other paths that + // never go through Set_Vertex_Buffer. + g_draw.fvfHasNormal = vba->FVF_Info().Has_Normal(); +} + +void BgfxBackend::Capture_Dynamic_Index_Data(const DynamicIBAccessClass * iba, + const void * data, + unsigned int size_bytes) +{ + if (!g_device.initialized || 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) + { + LogBgfxTransientDiag("capture", "ib", iba, num_indices, + g_draw.pendingIB.valid, + g_draw.pendingIB.owner == iba, + g_draw.useTransientIB, + g_draw.activeTransientIBOwner == iba, + "no-avail"); + g_draw.pendingIB.valid = false; + return; + } + + bgfx::allocTransientIndexBuffer(&g_draw.pendingIB.tib, num_indices); + g_stats.transientIbAllocations++; + 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_draw.pendingIB.tib.data, data, bytes); + g_draw.pendingIB.owner = iba; + g_draw.pendingIB.valid = true; + LogBgfxTransientDiag("capture", "ib", iba, num_indices, + true, + true, + g_draw.useTransientIB, + g_draw.activeTransientIBOwner == iba, + "ok"); +} + +void * BgfxBackend::Begin_Dynamic_Vertex_Write(const DynamicVBAccessClass * vba, + unsigned int size_bytes) +{ + // TheSuperHackers @bugfix bobtista 05/06/2026 Reset valid up front so any early + // return below leaves a previous draw's transient buffer marked invalid; only a + // successful allocation re-validates it (End must not blindly mark it valid). + g_draw.pendingVB.valid = false; + if (!g_device.initialized || vba == nullptr || size_bytes == 0) { + return nullptr; + } + bgfx::VertexLayout layout; + if (!BuildBgfxLayoutForFVF(vba->FVF_Info(), layout)) { + return nullptr; + } + const uint32_t num_verts = static_cast(vba->Get_Vertex_Count()); + if (num_verts == 0 || bgfx::getAvailTransientVertexBuffer(num_verts, layout) < num_verts) { + return nullptr; + } + { + PERF_TIME(PERF_SECT_DVW_ALLOC); + bgfx::allocTransientVertexBuffer(&g_draw.pendingVB.tvb, num_verts, layout); + } + g_draw.pendingVB.valid = true; + g_stats.transientVbAllocations++; + return g_draw.pendingVB.tvb.data; +} + +void BgfxBackend::End_Dynamic_Vertex_Write(const DynamicVBAccessClass * vba, + const void * data, + unsigned int size_bytes) +{ + if (!g_device.initialized || vba == nullptr) { + return; + } + // TheSuperHackers @bugfix bobtista 05/06/2026 If Begin failed to allocate a + // transient buffer (pendingVB.valid stayed false), do not finalize a stale buffer. + if (!g_draw.pendingVB.valid) { + return; + } + PERF_TIME(PERF_SECT_DVW_END); + g_draw.pendingVB.owner = vba; + g_dvwVerts += static_cast(vba->Get_Vertex_Count()); + // TheSuperHackers @performance bobtista 04/06/2026 HasSubmittedOppositeNormalPairs + // is an O(n^2) coplanar-pair scan and dynamic_fvf_type carries a normal, so it + // ran on every sorted-translucent batch (particles/smoke/water) — ~40% of render + // CPU on heavy scenes. The result only matters when ShouldApplySubmittedNormalBias + // is true (a handful of sorted decals / sneak-attack surfaces per frame), which is + // re-checked identically at draw time, so gate the scan on it. Particles short-circuit. + // (Split for measurement; semantics identical to the original (A||B)&&C short-circuit.) + bool coplanarBias = false; + if (ShouldScanSubmittedNormalBiasForCurrentDynamicWrite()) + { + PERF_TIME(PERF_SECT_DVW_SCAN); + coplanarBias = HasSubmittedOppositeNormalPairs( + vba->FVF_Info(), data, vba->Get_Vertex_Count()); + } + g_draw.pendingVB.coplanarNormalBias = coplanarBias; + g_draw.fvfHasNormal = vba->FVF_Info().Has_Normal(); +} + +void * BgfxBackend::Begin_Dynamic_Index_Write(const DynamicIBAccessClass * iba, + unsigned int size_bytes) +{ + // TheSuperHackers @bugfix bobtista 06/06/2026 Mirror Begin_Dynamic_Vertex_Write: reset valid up + // front so any early return leaves a previous draw's transient buffer marked invalid; only a + // successful allocation re-validates it (End must not blindly mark it valid). + g_draw.pendingIB.valid = false; + if (!g_device.initialized || iba == nullptr || size_bytes == 0) { + return nullptr; + } + const uint32_t num_indices = static_cast(iba->Get_Index_Count()); + if (num_indices == 0 || bgfx::getAvailTransientIndexBuffer(num_indices) < num_indices) { + return nullptr; + } + bgfx::allocTransientIndexBuffer(&g_draw.pendingIB.tib, num_indices); + g_draw.pendingIB.valid = true; + g_stats.transientIbAllocations++; + return g_draw.pendingIB.tib.data; +} + +void BgfxBackend::End_Dynamic_Index_Write(const DynamicIBAccessClass * iba, + const void * data, + unsigned int size_bytes) +{ + if (!g_device.initialized || iba == nullptr) { + return; + } + // TheSuperHackers @bugfix bobtista 06/06/2026 If Begin failed to allocate a transient buffer + // (pendingIB.valid stayed false), do not finalize a stale buffer. + if (!g_draw.pendingIB.valid) { + return; + } + g_draw.pendingIB.owner = iba; +} + +// -- Instancing ------------------------------------------------------------- + +bool BgfxBackend::Supports_Instancing() const +{ + return g_device.initialized + && (bgfx::getCaps()->supported & BGFX_CAPS_INSTANCING) != 0; +} + +bool BgfxBackend::Begin_Instanced_Batch(unsigned max_instances) +{ + if (!Supports_Instancing() || max_instances == 0) { + return false; + } + + // TheSuperHackers @bugfix bobtista 12/06/2026 Clamp to what the per-frame transient instance pool + // can actually provide. bgfx::allocInstanceDataBuffer may hand back fewer instances than requested + // when the pool is low; without this, instanceMax stayed at the (larger) request and Add_Instance + // would memcpy past the end of the allocated buffer. + const unsigned avail = bgfx::getAvailInstanceDataBuffer(max_instances, 64); + // TheSuperHackers @bugfix bobtista 17/07/2026 Refuse a partial grant instead of clamping. + // The mesh renderer consumes and deletes all batched render tasks once this returns true, + // and Add_Instance silently ignores instances past the clamp, so a partial grant made the + // tail of the batch vanish for the frame. Returning false keeps every mesh on the + // per-draw fallback path, which renders correctly under instance-pool pressure. + if (avail < max_instances) { + return false; + } + + bgfx::allocInstanceDataBuffer(&g_draw.instanceBatch, max_instances, 64); + if (g_draw.instanceBatch.data == nullptr) { + return false; + } + + g_draw.instanceCount = 0; + g_draw.instanceMax = max_instances; + g_draw.instanceBatchActive = true; + return true; +} + +void BgfxBackend::Add_Instance(const float * world_matrix_4x4) +{ + if (!g_draw.instanceBatchActive || g_draw.instanceCount >= g_draw.instanceMax) { + return; + } + static const bool s_probeIdentityInstances = GgcFlags::Enabled(GgcFlag_ProbeIdentityInstances); + if (s_probeIdentityInstances) + { + IdentityMatrix(reinterpret_cast(g_draw.instanceBatch.data + g_draw.instanceCount * 64)); + g_draw.instanceCount++; + return; + } + // TheSuperHackers @bugfix bobtista 28/06/2026 The engine passes a W3D Matrix3D (3x4 row-major, + // 48 bytes); the instanced VS reads a column-major 4x4 via mtxFromCols, exactly like the + // non-instanced setTransform path (Set_Transform -> W3DMatrix3DToBgfx). The old raw 64-byte + // memcpy used the wrong layout AND over-read 16 bytes past the 48-byte matrix, so instanced + // opaque meshes (vehicles/ships) got garbage transforms and vanished. Convert properly. + float converted[16]; + W3DMatrix3DToBgfx(*reinterpret_cast(world_matrix_4x4), converted); + float * dst = reinterpret_cast(g_draw.instanceBatch.data + g_draw.instanceCount * 64); + static const bool s_probeTransposeInstances = GgcFlags::Enabled(GgcFlag_ProbeTransposeInstances); + if (s_probeTransposeInstances) + { + for (unsigned r = 0; r < 4; ++r) + { + for (unsigned c = 0; c < 4; ++c) + { + dst[r * 4 + c] = converted[c * 4 + r]; + } + } + } + else + { + std::memcpy(dst, converted, sizeof(converted)); + } + g_draw.instanceCount++; +} + +void BgfxBackend::Submit_Instanced_Batch(unsigned index_offset, + unsigned triangle_count, + unsigned min_vertex_index, + unsigned vertex_count) +{ + if (DrawCallLog_Is_Active()) { + const TextureBaseClass * tex0 = FixedFunctionState::Render_State().Textures[0]; + const char * tex_name = (tex0 != nullptr) ? tex0->Get_Texture_Name().str() : ""; + DrawCallLog_Record( + 4, triangle_count, vertex_count, + FixedFunctionState::Render_State().vertex_buffer_types[0], + FixedFunctionState::Render_State().index_buffer_type, + FixedFunctionState::Render_State().shader.Get_Bits(), + FixedFunctionState::Render_State().sorted_draw_flags, + tex_name); + } + if (!g_draw.instanceBatchActive || g_draw.instanceCount == 0) { + g_draw.instanceBatchActive = false; + return; + } + g_draw.instanceBatchActive = false; + + bgfx::setInstanceDataBuffer(&g_draw.instanceBatch, 0, g_draw.instanceCount); + + 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::setTransform(identity); + + bgfx::ProgramHandle savedProgram = g_draw.program; + g_draw.program = g_device.uberInstancedProgram; + g_draw.drawIsInstanced = true; + + Draw_Triangles( + static_cast(index_offset), + static_cast(triangle_count), + static_cast(min_vertex_index), + static_cast(vertex_count)); + + g_draw.drawIsInstanced = false; + g_draw.program = savedProgram; + g_stats.instancedSavedDrawCalls += g_draw.instanceCount - 1; +} + +// -- State: shaders, materials, textures ------------------------------------ + +void BgfxBackend::Set_Shader(const ShaderClass & shader) +{ + FixedFunctionState::Set_Shader(shader); + // DX8 applies the shader's cull mode as part of the fixed-function state. bgfx keeps + // that semantic cull state separately for legacy overrides, so keep it synchronized + // here; otherwise a two-sided effect shader can leak RB_CULL_NONE into later opaque + // draws and make their sun-shadow caster duplicate render both sides. + Set_Cull_Mode(shader.Get_Cull_Mode() == ShaderClass::CULL_MODE_ENABLE ? RB_CULL_CW : RB_CULL_NONE); + g_draw.program = g_device.uberProgram; + g_draw.state = BuildBgfxStateForShader(shader); + const uint64_t srcBits = TranslateBlendFactor(shader.Get_Src_Blend_Func()); + const uint64_t dstBits = TranslateBlendFactor(shader.Get_Dst_Blend_Func()); + g_draw.blendFuncBits = BGFX_STATE_BLEND_FUNC(srcBits, dstBits); + g_draw.shaderBlendFuncBits = g_draw.blendFuncBits; + g_draw.blendEquationBits = BGFX_STATE_BLEND_EQUATION(BGFX_STATE_BLEND_EQUATION_ADD); + { + bool newBlend = !(srcBits == BGFX_STATE_BLEND_ONE && dstBits == BGFX_STATE_BLEND_ZERO); + g_draw.alphaBlendEnabled = newBlend; + g_draw.shaderAlphaBlendEnabled = newBlend; + } + g_draw.alphaBlendExplicitlySet = false; + g_draw.alphaTestExplicitlySet = false; + g_draw.depthTestEnabled = true; + g_draw.depthWriteEnabled = shader.Get_Depth_Mask() == ShaderClass::DEPTH_WRITE_ENABLE; + g_draw.depthFuncBits = TranslateDepthCompare(shader.Get_Depth_Compare()); + g_draw.depthFunc = static_cast(MapShaderDepthCompareToBackendCompare(shader.Get_Depth_Compare())); + BuildTssOpsForShader(shader, g_draw.tssOps0, g_draw.tssOps1, &g_draw.atestRef, &g_draw.atestFunc); + BuildTssOpsForShader(shader, g_draw.shaderTssOps0, g_draw.shaderTssOps1, &g_draw.shaderAtestRef, &g_draw.shaderAtestFunc); + g_draw.atestEnabled = g_draw.atestFunc > 0.0f; + g_draw.legacyPixelShaderMode[0] = static_cast(RB_LEGACY_PIXEL_SHADER_NONE); + Clear_State_Overrides(); +} + +void BgfxBackend::Set_Material(const VertexMaterialClass * material) +{ + g_draw.sourceMaterial = material; + FixedFunctionState::Set_Material(material); + const bool lightingEnabled = + material != nullptr + && material->Get_Lighting() + && !WW3D::Is_Coloring_Enabled(); + FixedFunctionState::Set_Lighting_Enabled(lightingEnabled); + g_draw.explicitMaterialState = false; + CaptureMaterialStateForBgfx(material); + ApplyMaterialMappersForBgfx(this, material); +} + +void BgfxBackend::Apply_Material_State(const RenderBackendMaterialState & material) +{ + g_draw.explicitMaterialState = true; + for (int i = 0; i < 4; ++i) + { + g_draw.matDiffuse[i] = material.diffuse[i]; + g_draw.matAmbient[i] = material.ambient[i]; + g_draw.matEmissive[i] = material.emissive[i]; + } + g_draw.matSpecular[0] = 0.0f; + g_draw.matSpecular[1] = 0.0f; + g_draw.matSpecular[2] = 0.0f; + g_draw.matSpecular[3] = 1.0f; +} + +void BgfxBackend::Set_Material_Color_Source(RenderBackendMaterialColorSource ambient_source, + RenderBackendMaterialColorSource diffuse_source, + RenderBackendMaterialColorSource emissive_source) +{ + FixedFunctionState::Set_Material_Color_Sources( + static_cast(ambient_source), + static_cast(diffuse_source), + static_cast(emissive_source)); + g_draw.vertexColorFlags[1] = (diffuse_source == RB_MATERIAL_COLOR_SOURCE_COLOR1) ? 1.0f : 0.0f; + g_draw.vertexColorFlags[2] = (ambient_source == RB_MATERIAL_COLOR_SOURCE_COLOR1) ? 1.0f : 0.0f; + g_draw.vertexColorFlags[3] = (emissive_source == RB_MATERIAL_COLOR_SOURCE_COLOR1) ? 1.0f : 0.0f; +} + +void BgfxBackend::Set_Texture(unsigned int stage, TextureBaseClass * texture) +{ + PERF_TIME(PERF_SECT_SET_TEXTURE); + FixedFunctionState::Set_Texture(stage, texture); + // 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. + { + TextureClass * t2d_name = texture ? texture->As_TextureClass() : nullptr; + if (t2d_name != nullptr) + { + // DX8 applies the texture object's filter/address state when the + // deferred texture bind is flushed. Apply it before later bind + // logic interprets the current stage sampler state. + t2d_name->Get_Filter().Apply(stage); + + // TheSuperHackers @bugfix bobtista 16/07/2026 Retail kicks texture loading from + // TextureClass::Apply at draw commit, which never runs on this backend; a file + // texture constructed before thumbnails were disabled (or off the render thread) + // stayed uninitialized and bound the white fallback forever. Kick the loader at + // bind time instead; this runs on the main render thread so the foreground-load + // path behaves exactly as the retail Apply did. Must happen before + // EnsureBgfxTexture so the refreshed snapshot revision is picked up in this bind. + if (!t2d_name->Is_Initialized() + && t2d_name->Get_Asset_Type() == TextureBaseClass::TEX_REGULAR) + { + t2d_name->Init(); + } + } + + bgfx::TextureHandle h = EnsureBgfxTexture(texture); + const bool missingOrUnavailable = IsMissingOrUnavailableTexture(texture, h); + if (!bgfx::isValid(h) && texture != nullptr && + g_caches.renderTarget.count(texture) == 0) + { + static bool s_loggedWhiteFallback = false; + if (!s_loggedWhiteFallback) + { + s_loggedWhiteFallback = true; + 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())); + } + } + // TextureFilterClass::Apply() updates the semantic sampler state above. + // Preserve those bits when recording the bind; bridge atlases and thin + // particles depend on mip filtering staying disabled after Set_Texture. + const uint32_t samplerFlags = stage < 4 ? g_draw.samplerFlags[stage] : 0; + const bool mipFilterDisabled = stage < 4 && g_draw.mipFilterDisabled[stage]; + switch (stage) + { + case 0: g_draw.tex[0] = h; + g_draw.sourceTextures[0] = texture; + g_draw.samplerFlags[0] = samplerFlags; + g_draw.mipFilterDisabled[0] = mipFilterDisabled; + g_draw.textureIsMissing[0] = missingOrUnavailable; break; + case 1: g_draw.tex[1] = h; + g_draw.sourceTextures[1] = texture; + g_draw.samplerFlags[1] = samplerFlags; + g_draw.mipFilterDisabled[1] = mipFilterDisabled; + g_draw.textureIsMissing[1] = missingOrUnavailable; break; + case 2: g_draw.tex[2] = h; + g_draw.sourceTextures[2] = texture; + g_draw.samplerFlags[2] = samplerFlags; + g_draw.mipFilterDisabled[2] = mipFilterDisabled; + g_draw.textureIsMissing[2] = missingOrUnavailable; break; + case 3: g_draw.tex[3] = h; + g_draw.sourceTextures[3] = texture; + g_draw.samplerFlags[3] = samplerFlags; + g_draw.mipFilterDisabled[3] = mipFilterDisabled; + g_draw.textureIsMissing[3] = missingOrUnavailable; break; + default: break; + } + } +} + +void BgfxBackend::Bind_Texture_Immediate(unsigned int stage, TextureBaseClass * texture) +{ + Set_Texture(stage, texture); +} + +void BgfxBackend::Set_Ambient(const Vector3 & color) +{ + FixedFunctionState::Set_Ambient_Color(MakeLegacyARGBColor(color, 0.0f)); + g_draw.sceneAmbient[0] = color.X; + g_draw.sceneAmbient[1] = color.Y; + g_draw.sceneAmbient[2] = color.Z; +} + +const Vector3 & BgfxBackend::Get_Ambient() const +{ + m_ambient.Set(g_draw.sceneAmbient[0], g_draw.sceneAmbient[1], g_draw.sceneAmbient[2]); + return m_ambient; +} + +void BgfxBackend::Set_Fog(bool enable, const Vector3 & color, float start, float end) +{ + (void)enable; + (void)color; + (void)start; + (void)end; +} + +void BgfxBackend::Set_Fog_Enable(bool enable) +{ + FixedFunctionState::Set_Fog_Enabled(enable); +} + +void BgfxBackend::Set_Fog_Color(unsigned argb) +{ + FixedFunctionState::Set_Fog_Color(argb); +} + +unsigned BgfxBackend::Get_Fog_Color() const +{ + return FixedFunctionState::Fog_Color(0); +} + +void BgfxBackend::Set_Specular_Enable(bool enable) +{ + FixedFunctionState::Set_Specular_Enabled(enable); +} + +void BgfxBackend::Set_Patch_Segments(float level) +{ + FixedFunctionState::Set_Patch_Segments_Bits(FloatAsDword(level)); +} + +void BgfxBackend::Set_Light(unsigned int index, const LightClass & light) +{ + if (index >= 4) + { + return; + } + + Vector3 color; + light.Get_Diffuse(&color); + color *= light.Get_Intensity(); + g_draw.lightColors[index][0] = color.X; + g_draw.lightColors[index][1] = color.Y; + g_draw.lightColors[index][2] = color.Z; + g_draw.lightColors[index][3] = 1.0f; + + light.Get_Ambient(&color); + color *= light.Get_Intensity(); + g_draw.lightAmbients[index][0] = color.X; + g_draw.lightAmbients[index][1] = color.Y; + g_draw.lightAmbients[index][2] = color.Z; + g_draw.lightAmbients[index][3] = 1.0f; + + Vector3 position = light.Get_Position(); + g_draw.lightPositions[index][0] = position.X; + g_draw.lightPositions[index][1] = position.Y; + g_draw.lightPositions[index][2] = position.Z; + g_draw.lightPositions[index][3] = 1.0f; + + Vector3 direction; + light.Get_Spot_Direction(direction); + g_draw.lightDirs[index][0] = -direction.X; + g_draw.lightDirs[index][1] = -direction.Y; + g_draw.lightDirs[index][2] = -direction.Z; + g_draw.lightDirs[index][3] = 1.0f; + + g_draw.lightParams[index][0] = 0.0f; + g_draw.lightParams[index][1] = light.Get_Attenuation_Range(); + // TheSuperHackers @info bobtista 16/07/2026 SPOT is approximated as a point light with no + // cone falloff. This is latent: no spot LightClass is ever constructed in this game, and + // this setter's only game caller sits in dead code (SimpleSceneClass::Customized_Render); + // real lighting arrives through Set_Light_Environment, which folds spots CPU-side. + g_draw.lightParams[index][2] = + (light.Get_Type() == LightClass::POINT || light.Get_Type() == LightClass::SPOT) ? 1.0f : 0.0f; + g_draw.lightParams[index][3] = 1.0f; +} + +void BgfxBackend::Clear_Light(unsigned int index) +{ + if (index >= 4) + { + return; + } + + g_draw.lightDirs[index][3] = 0.0f; + g_draw.lightColors[index][3] = 0.0f; + g_draw.lightAmbients[index][3] = 0.0f; + g_draw.lightParams[index][3] = 0.0f; +} + +// Maps WW3D BlendFactor enum (1..11) to bgfx blend-factor bits. Index 0 unused. +static const uint64_t kBgfxBlendMap[12] = { + 0, + BGFX_STATE_BLEND_ZERO, // 1 = RB_BLEND_ZERO + BGFX_STATE_BLEND_ONE, // 2 = RB_BLEND_ONE + BGFX_STATE_BLEND_SRC_COLOR, // 3 = RB_BLEND_SRC_COLOR + BGFX_STATE_BLEND_INV_SRC_COLOR, // 4 = RB_BLEND_INV_SRC_COLOR + 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 + BGFX_STATE_BLEND_DST_COLOR, // 9 = RB_BLEND_DEST_COLOR + BGFX_STATE_BLEND_INV_DST_COLOR, // 10 = RB_BLEND_INV_DEST_COLOR + BGFX_STATE_BLEND_SRC_ALPHA_SAT // 11 = RB_BLEND_SRC_ALPHA_SAT +}; + +static uint64_t TranslateBlendOp(BlendOp op) +{ + switch (op) + { + case RB_BLEND_OP_SUBTRACT: + return BGFX_STATE_BLEND_EQUATION(BGFX_STATE_BLEND_EQUATION_SUB); + case RB_BLEND_OP_REV_SUBTRACT: + return BGFX_STATE_BLEND_EQUATION(BGFX_STATE_BLEND_EQUATION_REVSUB); + case RB_BLEND_OP_MIN: + return BGFX_STATE_BLEND_EQUATION(BGFX_STATE_BLEND_EQUATION_MIN); + case RB_BLEND_OP_MAX: + return BGFX_STATE_BLEND_EQUATION(BGFX_STATE_BLEND_EQUATION_MAX); + case RB_BLEND_OP_ADD: + default: + return BGFX_STATE_BLEND_EQUATION(BGFX_STATE_BLEND_EQUATION_ADD); + } +} + +// TheSuperHackers @fix bobtista 20/04/2026 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) +{ + const unsigned s = static_cast(src); + const unsigned d = static_cast(dest); + FixedFunctionState::Set_Blend_Factors(s, d); + if (s >= 1 && s <= 11 && d >= 1 && d <= 11) + { + g_draw.blendFuncBits = BGFX_STATE_BLEND_FUNC(kBgfxBlendMap[s], kBgfxBlendMap[d]); + // TheSuperHackers @bugfix bobtista 26/05/2026 If a shader override + // (Override_Alpha_Blend_Enable / Override_Blend) is already active, + // propagate the explicit Set_Blend_Factors into the override so the + // engine's intent wins. ApplyBlendState prefers g_overrides.blendBits + // over g_draw.blendFuncBits when an override is active, which silently + // dropped the water surface's DEST_ALPHA / INV_DEST_ALPHA shoreline + // feather (Override_Alpha_Blend_Enable had stamped the override at + // SRC_ALPHA / INV_SRC_ALPHA just above the explicit Set_Blend_Factors). + if (g_overrides.blendActive) + { + g_overrides.SetBlend(g_draw.blendFuncBits); + } + if (!g_draw.alphaBlendExplicitlySet && !IsOpaqueBlend(src, dest)) + { + g_draw.alphaBlendEnabled = true; + } + // TheSuperHackers @bugfix bobtista 06/06/2026 Mark blend as explicitly set (like + // Set_Alpha_Blend_Enable) so SubmitEngineDraw does not reset blendFuncBits back to the + // shader's default, which silently dropped explicit factors (e.g. the m_gForceMultiply + // DEST_COLOR/SRC_COLOR multiply) when no blend override was active. + g_draw.alphaBlendExplicitlySet = true; + } +} + +void BgfxBackend::Set_Blend_Op(BlendOp op) +{ + FixedFunctionState::Set_Blend_Op(static_cast(op)); + g_draw.blendEquationBits = TranslateBlendOp(op); +} + +void BgfxBackend::Set_Alpha_Blend_Enable(bool enable) +{ + FixedFunctionState::Set_Alpha_Blend_Enabled(enable); + g_draw.alphaBlendEnabled = enable; + g_draw.alphaBlendExplicitlySet = true; +} + +// TheSuperHackers @bugfix bobtista 28/05/2026 Each single-knob alpha-test setter now writes only its own bucket through Set_Cached_Render_State; the prior code routed through Set_Alpha_Test_State and clobbered the other two buckets with zeros whenever their semantic cache flags were still false. +// TheSuperHackers @bugfix bobtista 16/07/2026 Mark alpha-test state as explicitly set (like +// Set_Blend_Factors does for blend) so SubmitEngineDraw does not reset it back to the shader's +// snapshot. The mesh renderer's Alpha_Override fade scales the alpha-test reference after +// Set_Shader (dx8renderer.cpp:1919); without the flag the scaled reference was silently +// clobbered back to the default 0x60 and fading alpha-tested meshes popped instead of thinning. +void BgfxBackend::Set_Alpha_Test_Enable(bool enable) +{ + FixedFunctionState::Set_Cached_Render_State(RS::ALPHATESTENABLE, enable ? 1U : 0U); + g_draw.atestEnabled = enable; + g_draw.alphaTestExplicitlySet = true; +} + +void BgfxBackend::Set_Alpha_Test_Reference(unsigned ref) +{ + FixedFunctionState::Set_Cached_Render_State(RS::ALPHAREF, ref); + g_draw.atestRef = ref / 255.0f; + g_draw.alphaTestExplicitlySet = true; +} + +void BgfxBackend::Set_Alpha_Test_Function(CompareFunc func) +{ + FixedFunctionState::Set_Cached_Render_State(RS::ALPHAFUNC, static_cast(func)); + g_draw.atestFunc = static_cast(func); + g_draw.alphaTestExplicitlySet = true; +} + +void BgfxBackend::Set_Normalize_Normals(bool enable) +{ + FixedFunctionState::Set_Normalize_Normals_Enabled(enable); +} + +void BgfxBackend::Override_Blend(BlendFactor srcBlend, BlendFactor dstBlend) +{ + const unsigned srcIdx = static_cast(srcBlend); + const unsigned dstIdx = static_cast(dstBlend); + if (srcIdx >= 1 && srcIdx <= 11 && dstIdx >= 1 && dstIdx <= 11) + { + g_overrides.SetBlend(BGFX_STATE_BLEND_FUNC(kBgfxBlendMap[srcIdx], kBgfxBlendMap[dstIdx])); + g_overrides.SetBlendEnable(true); + } + 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-11)", + srcIdx, dstIdx)); + } + } + FixedFunctionState::Set_Blend_Factors(srcIdx, dstIdx); +} + +void BgfxBackend::Override_Alpha_Test(bool enable, unsigned ref, CompareFunc func) +{ + g_overrides.atestActive = enable; + g_overrides.atestRef = enable ? (ref / 255.0f) : 0.0f; + g_overrides.atestFunc = enable ? static_cast(func) : 0.0f; + // TheSuperHackers @bugfix bobtista 28/05/2026 When disabling the override, only clear ALPHATESTENABLE; preserve the existing ALPHAFUNC/ALPHAREF so downstream code observes the unmodified shader state. + if (enable) + { + FixedFunctionState::Set_Alpha_Test_State(enable, ref, static_cast(func)); + } + else + { + FixedFunctionState::Set_Cached_Render_State(RS::ALPHATESTENABLE, 0U); + } +} + +void BgfxBackend::Override_Alpha_Blend_Enable(bool enable) +{ + g_overrides.SetBlendEnable(enable); + if (enable) + { + g_overrides.SetBlend(BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, + BGFX_STATE_BLEND_INV_SRC_ALPHA)); + } + FixedFunctionState::Set_Alpha_Blend_Enabled(enable); +} + +void BgfxBackend::Override_Texcoord_Index(unsigned stage, unsigned uvIndex) +{ + if (stage < 4) + { + g_draw.texcoordIndex[stage] = uvIndex; + } + if (stage == 0) + { + g_draw.texcoordSelect[0] = (uvIndex == 1) ? 1.0f : 0.0f; + } + FixedFunctionState::Set_Texture_Stage_State(stage, TSS::TEXCOORDINDEX, uvIndex); +} + +void BgfxBackend::Set_Texture_Transform(unsigned stage, const Matrix4x4 & matrix) +{ + auto cacheMatrix = MakeLegacyCacheMatrix(matrix); + FixedFunctionState::Set_Transform_Matrix(kTextureTransformStage0 + stage, cacheMatrix); + + if (stage == 0) + { + ReadTextureTransform(0, g_draw.texTransform0, g_draw.texTransform1); + ReadTextureTransformZ(0, g_draw.texTransform0Z); + g_draw.texcoordSelect[3] = 1.0f; + } + else if (stage == 1) + { + ReadTextureTransform(1, g_draw.tex1Transform0, g_draw.tex1Transform1); + ReadTextureTransformZ(1, g_draw.tex1TransformZ); + g_draw.texcoordSelect2[1] = 1.0f; + } +} + +void BgfxBackend::Clear_Texture_Transform(unsigned stage) +{ + FixedFunctionState::Set_Texture_Stage_State(stage, TSS::TEXCOORDINDEX, stage); + FixedFunctionState::Set_Texture_Stage_State(stage, TSS::TEXTURETRANSFORMFLAGS, kTextureTransformDisable); + + if (stage < 4) + { + g_draw.texcoordIndex[stage] = stage; + g_draw.textureTransformFlags[stage] = kTextureTransformDisable; + g_draw.texcoordSource[stage] = 0.0f; + g_draw.texProjected[stage] = 0.0f; + } + + if (stage == 0) + { + g_draw.texcoordSelect[3] = 0.0f; + SetIdentityTextureTransform(g_draw.texTransform0, g_draw.texTransform1); + g_draw.texTransform0Z[0] = 0.0f; + g_draw.texTransform0Z[1] = 0.0f; + g_draw.texTransform0Z[2] = 1.0f; + g_draw.texTransform0Z[3] = 0.0f; + } + else if (stage == 1) + { + g_draw.texcoordSelect2[1] = 0.0f; + SetIdentityTextureTransform(g_draw.tex1Transform0, g_draw.tex1Transform1); + g_draw.tex1TransformZ[0] = 0.0f; + g_draw.tex1TransformZ[1] = 0.0f; + g_draw.tex1TransformZ[2] = 1.0f; + g_draw.tex1TransformZ[3] = 0.0f; + } +} + +void BgfxBackend::Set_Texture_Coord_Source(unsigned stage, + RenderBackendTexcoordSource source, + unsigned uv_array_index) +{ + unsigned tci = uv_array_index; + switch (source) + { + case RB_TEXCOORD_MESH_UV: + tci = kTexcoordGenPassthru | uv_array_index; + break; + case RB_TEXCOORD_CAMERA_SPACE_NORMAL: + tci = kTexcoordGenCameraNormal | uv_array_index; + break; + case RB_TEXCOORD_CAMERA_SPACE_REFLECTION: + tci = kTexcoordGenCameraReflection | uv_array_index; + break; + case RB_TEXCOORD_CAMERA_SPACE_POSITION: + tci = kTexcoordGenCameraPosition | uv_array_index; + break; + } + + FixedFunctionState::Set_Texture_Stage_State(stage, TSS::TEXCOORDINDEX, tci); + if (stage < 4) + { + g_draw.texcoordIndex[stage] = tci; + g_draw.texcoordSource[stage] = static_cast(source); + } + if (stage == 0) + { + g_draw.texcoordSelect[0] = (uv_array_index == 1) ? 1.0f : 0.0f; + } + else if (stage == 1) + { + g_draw.texcoordSelect2[0] = (uv_array_index == 1) ? 1.0f : 0.0f; + } +} + +void BgfxBackend::Set_Texture_Transform_Mode(unsigned stage, unsigned coord_count, bool projected) +{ + const unsigned flags = (coord_count == 0 ? kTextureTransformDisable : coord_count) + | (projected ? kTextureTransformProjected : 0); + + FixedFunctionState::Set_Texture_Stage_State(stage, TSS::TEXTURETRANSFORMFLAGS, flags); + if (stage < 4) + { + g_draw.textureTransformFlags[stage] = flags; + g_draw.texProjected[stage] = projected && coord_count >= 3 ? 1.0f : 0.0f; + } +} + +void BgfxBackend::Set_Texture_Bump_Env_Matrix(unsigned stage, + float m00, + float m01, + float m10, + float m11) +{ + FixedFunctionState::Set_Texture_Stage_State(stage, TSS::BUMPENVMAT00, FloatAsDword(m00)); + FixedFunctionState::Set_Texture_Stage_State(stage, TSS::BUMPENVMAT01, FloatAsDword(m01)); + FixedFunctionState::Set_Texture_Stage_State(stage, TSS::BUMPENVMAT10, FloatAsDword(m10)); + FixedFunctionState::Set_Texture_Stage_State(stage, TSS::BUMPENVMAT11, FloatAsDword(m11)); +} + +void BgfxBackend::Set_Texture_Bump_Env_Luminance(unsigned stage, + float scale, + float offset) +{ + FixedFunctionState::Set_Texture_Stage_State(stage, TSS::BUMPENVLSCALE, FloatAsDword(scale)); + FixedFunctionState::Set_Texture_Stage_State(stage, TSS::BUMPENVLOFFSET, FloatAsDword(offset)); +} + +void BgfxBackend::Set_Texture_Color_Operation(unsigned stage, RenderBackendTextureOperation op) +{ + Set_Texture_Stage_State(stage, TSS::COLOROP, static_cast(op)); +} + +void BgfxBackend::Set_Texture_Alpha_Operation(unsigned stage, RenderBackendTextureOperation op) +{ + Set_Texture_Stage_State(stage, TSS::ALPHAOP, static_cast(op)); +} + +void BgfxBackend::Set_Texture_Color_Argument(unsigned stage, + unsigned argument_index, + RenderBackendTextureArgument arg) +{ + static const unsigned states[] = { + TSS::COLORARG0, + TSS::COLORARG1, + TSS::COLORARG2, + }; + if (argument_index >= sizeof(states) / sizeof(states[0])) + return; + + Set_Texture_Stage_State(stage, states[argument_index], static_cast(arg)); +} + +void BgfxBackend::Set_Texture_Alpha_Argument(unsigned stage, + unsigned argument_index, + RenderBackendTextureArgument arg) +{ + static const unsigned states[] = { + TSS::ALPHAARG0, + TSS::ALPHAARG1, + TSS::ALPHAARG2, + }; + if (argument_index >= sizeof(states) / sizeof(states[0])) + return; + + Set_Texture_Stage_State(stage, states[argument_index], static_cast(arg)); +} + +void BgfxBackend::Set_Texture_Coord_Generation(unsigned stage, bool cameraPosEnabled) +{ + Set_Texture_Coord_Source(stage, + cameraPosEnabled ? RB_TEXCOORD_CAMERA_SPACE_POSITION : RB_TEXCOORD_MESH_UV, + stage); +} + +void BgfxBackend::Set_Texture_UV_Wrap(unsigned stage, bool enable) +{ + if (stage == 0) + { + g_draw.objectShroudDim[3] = enable ? 1.0f : 0.0f; + } +} + +static unsigned TextureAddressModeToLegacyStageState(RenderBackendTextureAddressMode mode) +{ + switch (mode) + { + case RB_TEXTURE_ADDRESS_CLAMP: + return kTextureAddressClamp; + case RB_TEXTURE_ADDRESS_BORDER: + return kTextureAddressBorder; + case RB_TEXTURE_ADDRESS_WRAP: + default: + return kTextureAddressWrap; + } +} + +void BgfxBackend::Set_Texture_Address_Mode(unsigned stage, + RenderBackendTextureAddressMode u, + RenderBackendTextureAddressMode v, + RenderBackendTextureAddressMode w) +{ + Set_Texture_Stage_State(stage, TSS::ADDRESSU, TextureAddressModeToLegacyStageState(u)); + Set_Texture_Stage_State(stage, TSS::ADDRESSV, TextureAddressModeToLegacyStageState(v)); + Set_Texture_Stage_State(stage, TSS::ADDRESSW, TextureAddressModeToLegacyStageState(w)); +} + +static unsigned TextureSampleFilterToLegacyStageState(RenderBackendTextureSampleFilter filter) +{ + switch (filter) + { + case RB_TEXTURE_SAMPLE_NONE: + return kTextureSampleNone; + case RB_TEXTURE_SAMPLE_POINT: + return kTextureSamplePoint; + case RB_TEXTURE_SAMPLE_ANISOTROPIC: + return kTextureSampleAnisotropic; + case RB_TEXTURE_SAMPLE_LINEAR: + default: + return kTextureSampleLinear; + } +} + +void BgfxBackend::Set_Texture_Sample_Filter(unsigned stage, + RenderBackendTextureSampleFilter min_filter, + RenderBackendTextureSampleFilter mag_filter, + RenderBackendTextureSampleFilter mip_filter) +{ + Set_Texture_Stage_State(stage, TSS::MINFILTER, TextureSampleFilterToLegacyStageState(min_filter)); + Set_Texture_Stage_State(stage, TSS::MAGFILTER, TextureSampleFilterToLegacyStageState(mag_filter)); + Set_Texture_Stage_State(stage, TSS::MIPFILTER, TextureSampleFilterToLegacyStageState(mip_filter)); +} + +void BgfxBackend::Set_Texture_Min_Mag_Filter(unsigned stage, + RenderBackendTextureSampleFilter min_filter, + RenderBackendTextureSampleFilter mag_filter) +{ + Set_Texture_Stage_State(stage, TSS::MINFILTER, TextureSampleFilterToLegacyStageState(min_filter)); + Set_Texture_Stage_State(stage, TSS::MAGFILTER, TextureSampleFilterToLegacyStageState(mag_filter)); +} + +void BgfxBackend::Set_Texture_Mip_Filter(unsigned stage, RenderBackendTextureSampleFilter mip_filter) +{ + Set_Texture_Stage_State(stage, TSS::MIPFILTER, TextureSampleFilterToLegacyStageState(mip_filter)); +} + +void BgfxBackend::Set_Texture_Max_Anisotropy(unsigned stage, unsigned max_anisotropy) +{ + Set_Texture_Stage_State(stage, TSS::MAXANISOTROPY, max_anisotropy); +} + +void BgfxBackend::Set_Texture_Clamp_Mode(unsigned stage, bool clampU, bool clampV) +{ + FixedFunctionState::Set_Texture_Stage_State(stage, TSS::ADDRESSU, + clampU ? kTextureAddressClamp : kTextureAddressWrap); + FixedFunctionState::Set_Texture_Stage_State(stage, TSS::ADDRESSV, + clampV ? kTextureAddressClamp : kTextureAddressWrap); + + if (stage < 4) + { + g_draw.samplerFlags[stage] &= ~(BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + if (clampU) + { + g_draw.samplerFlags[stage] |= BGFX_SAMPLER_U_CLAMP; + } + if (clampV) + { + g_draw.samplerFlags[stage] |= BGFX_SAMPLER_V_CLAMP; + } + } +} + +void BgfxBackend::Set_Texture_Stage_State(unsigned stage, unsigned state, unsigned value) +{ + FixedFunctionState::Set_Texture_Stage_State(stage, state, value); + if (stage >= 4) + { + return; + } + + if (stage == 0) + { + if (state == TSS::COLOROP) + { + g_draw.tssOps0[0] = TextureOpToTssOp(value); + } + else if (state == TSS::ALPHAOP) + { + g_draw.tssOps0[1] = TextureOpToTssOp(value); + } + else if (state == TSS::COLORARG1) + { + g_draw.tssOps1[0] = TextureArgToTssArg(value); + } + else if (state == TSS::ALPHAARG1) + { + g_draw.tssOps1[1] = TextureArgToTssArg(value); + } + } + else if (stage == 1) + { + if (state == TSS::COLOROP) + { + g_draw.tssOps0[2] = TextureOpToTssOp(value); + } + else if (state == TSS::ALPHAOP) + { + g_draw.tssOps0[3] = TextureOpToTssOp(value); + } + else if (state == TSS::COLORARG1) + { + g_draw.tssOps1[2] = TextureArgToTssArg(value); + } + else if (state == TSS::ALPHAARG1) + { + g_draw.tssOps1[3] = TextureArgToTssArg(value); + } + } + + if (state == TSS::ADDRESSU) + { + g_draw.samplerFlags[stage] &= ~BGFX_SAMPLER_U_CLAMP; + if (value == kTextureAddressClamp) + { + g_draw.samplerFlags[stage] |= BGFX_SAMPLER_U_CLAMP; + } + } + else if (state == TSS::ADDRESSV) + { + g_draw.samplerFlags[stage] &= ~BGFX_SAMPLER_V_CLAMP; + if (value == kTextureAddressClamp) + { + g_draw.samplerFlags[stage] |= BGFX_SAMPLER_V_CLAMP; + } + } + else if (state == TSS::MINFILTER) + { + g_draw.samplerFlags[stage] &= ~(BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MIN_ANISOTROPIC); + if (value == kTextureSamplePoint) + { + g_draw.samplerFlags[stage] |= BGFX_SAMPLER_MIN_POINT; + } + else if (value == kTextureSampleAnisotropic) + { + g_draw.samplerFlags[stage] |= BGFX_SAMPLER_MIN_ANISOTROPIC; + } + } + else if (state == TSS::MAGFILTER) + { + g_draw.samplerFlags[stage] &= ~(BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_MAG_ANISOTROPIC); + if (value == kTextureSamplePoint) + { + g_draw.samplerFlags[stage] |= BGFX_SAMPLER_MAG_POINT; + } + else if (value == kTextureSampleAnisotropic) + { + g_draw.samplerFlags[stage] |= BGFX_SAMPLER_MAG_ANISOTROPIC; + } + } + else if (state == TSS::MIPFILTER) + { + g_draw.samplerFlags[stage] &= ~BGFX_SAMPLER_MIP_POINT; + g_draw.mipFilterDisabled[stage] = value == kTextureSampleNone; + if (value == kTextureSamplePoint) + { + g_draw.samplerFlags[stage] |= BGFX_SAMPLER_MIP_POINT; + } + } + else if (state == TSS::TEXCOORDINDEX) + { + g_draw.texcoordIndex[stage] = value; + const unsigned uvIndex = value & 0xFFFFu; + const unsigned texcoordGen = value & 0xFFFF0000u; + if (stage == 3 && texcoordGen == kTexcoordGenCameraPosition) + { + g_draw.texcoordSource[3] = 3.0f; + } + else if (stage == 3) + { + g_draw.texcoordSource[3] = (uvIndex == 1) ? 1.0f : 0.0f; + } + } + else if (state == TSS::TEXTURETRANSFORMFLAGS) + { + g_draw.textureTransformFlags[stage] = value; + } +} + +void BgfxBackend::Configure_Custom_Edging_Cloud_Texture_Stages() +{ + Set_Texture_Stage_State(0, TSS::ALPHAARG1, kTextureArgCurrent); + Set_Texture_Stage_State(0, TSS::ALPHAOP, kTextureOpSelectArg1); + + Set_Texture_Stage_State(1, TSS::COLORARG1, kTextureArgCurrent); + Set_Texture_Stage_State(1, TSS::COLORARG2, kTextureArgTexture); + Set_Texture_Stage_State(1, TSS::COLOROP, kTextureOpSelectArg1); + Set_Texture_Stage_State(1, TSS::ALPHAARG1, kTextureArgCurrent); + Set_Texture_Stage_State(1, TSS::ALPHAARG2, kTextureArgTexture); + Set_Texture_Stage_State(1, TSS::ALPHAOP, kTextureOpSelectArg2); + Set_Texture_Stage_State(1, TSS::TEXCOORDINDEX, 1); +} + +void BgfxBackend::Configure_Shadow_Volume_Fill_Texture_Stages() +{ + Set_Texture_Stage_State(0, TSS::COLORARG1, kTextureArgTexture); + Set_Texture_Stage_State(0, TSS::COLORARG2, kTextureArgDiffuse); + Set_Texture_Stage_State(0, TSS::COLOROP, kTextureOpSelectArg2); + Set_Texture_Stage_State(0, TSS::ALPHAOP, kTextureOpDisable); + Set_Texture_Stage_State(0, TSS::TEXCOORDINDEX, 0); + + Set_Texture_Stage_State(1, TSS::COLOROP, kTextureOpDisable); + Set_Texture_Stage_State(1, TSS::ALPHAOP, kTextureOpDisable); + Set_Texture_Stage_State(1, TSS::TEXCOORDINDEX, 1); +} + +void BgfxBackend::Set_Shroud_Texture_Pass_Active(bool active, unsigned stage) +{ + g_views.shroudTexturePassActive = active; + g_views.shroudTexturePassStage = stage; + if (!active) + { + g_draw.shroudTextureParamsValid = false; + g_views.objectShroudTexturePassActive = false; + } + if (!active || stage != 0) + { + g_draw.texcoordSelect[2] = 0.0f; + } +} + +void BgfxBackend::Set_Object_Shroud_Texture_Pass_Active(bool active) +{ + g_views.objectShroudTexturePassActive = active; +} + +void BgfxBackend::Set_Object_Shroud_Alpha_Mask_Texture(TextureBaseClass * texture) +{ + g_draw.objectShroudDim[1] = texture != nullptr ? 1.0f : 0.0f; + // The delayed object-shroud shader uses the object's base texture as an + // alpha mask, but it renders under the shroud shader rather than the + // object's original shader. Preserve WW3D's cutout coverage by applying + // the same default alpha-test cutoff used by alpha-tested meshes. + g_draw.objectShroudDim[2] = texture != nullptr ? kDefaultAlphaTestRef : 0.0f; + if (texture == nullptr) + { + return; + } + + bgfx::TextureHandle h = EnsureBgfxTexture(texture); + g_draw.tex[1] = h; + g_draw.sourceTextures[1] = texture; + g_draw.textureIsMissing[1] = IsMissingOrUnavailableTexture(texture, h); + + g_draw.samplerFlags[1] = 0; + if (TextureClass * t2d = texture->As_TextureClass()) + { + const TextureFilterClass & flt = t2d->Get_Filter(); + if (flt.Get_U_Addr_Mode() == TextureFilterClass::TEXTURE_ADDRESS_CLAMP) + { + g_draw.samplerFlags[1] |= BGFX_SAMPLER_U_CLAMP; + } + if (flt.Get_V_Addr_Mode() == TextureFilterClass::TEXTURE_ADDRESS_CLAMP) + { + g_draw.samplerFlags[1] |= BGFX_SAMPLER_V_CLAMP; + } + } +} + +void BgfxBackend::Set_Shroud_Texture_Params(float offset_x, float offset_y, + float scale_x, float scale_y) +{ + g_draw.shroudTextureParams[0] = offset_x; + g_draw.shroudTextureParams[1] = offset_y; + g_draw.shroudTextureParams[2] = scale_x; + g_draw.shroudTextureParams[3] = scale_y; + g_draw.shroudTextureParamsValid = true; +} + +void BgfxBackend::Override_Terrain_Blend(bool enable) +{ + g_draw.texcoordSelect[1] = enable ? 1.0f : 0.0f; +} + +void BgfxBackend::Override_Material_Opacity(float opacity) +{ + // TheSuperHackers @fix bobtista 20/04/2026 Only override the opacity uniform; the water + // code sets DESTALPHA explicitly via Set_Blend_Factors when soft water edge is enabled. + g_draw.matDiffuse[3] = opacity; + g_views.waterOverrideActive = true; +} + +void BgfxBackend::Begin_Water_Overlay() +{ + g_views.waterOverlayActive = true; +} + +void BgfxBackend::End_Water_Overlay() +{ + g_views.waterOverlayActive = false; +} + +void BgfxBackend::Begin_Effect_Overlay() +{ + if (GgcFlags::Enabled(GgcFlag_NoEffectOverlay)) + { + return; + } + g_views.effectOverlayActive = true; +} + +void BgfxBackend::End_Effect_Overlay() +{ + g_views.effectOverlayActive = false; +} + +bool BgfxBackend::Begin_Smudge_Distortion(float tactical_width_fraction, + float tactical_height_fraction) +{ + const BgfxDiagnosticFlags diagnostics = GetBgfxDiagnosticFlags(); + if (diagnostics.noSceneFramebuffer + || diagnostics.noPostFx + || !bgfx::isValid(g_device.sceneColor) + || !bgfx::isValid(g_device.sceneSmudgeCopy) + || !bgfx::isValid(g_device.sceneSmudgeCopyFB) + || !bgfx::isValid(g_device.copyProgram) + || !bgfx::isValid(g_device.fullscreenClearVB) + || !bgfx::isValid(g_uniforms.sTex0) + || !bgfx::isValid(g_device.smudgeProgram)) + { + return false; + } + + const float sceneWidth = static_cast(g_device.sceneRenderWidth != 0 ? g_device.sceneRenderWidth : g_device.width); + const float sceneHeight = static_cast(g_device.sceneRenderHeight != 0 ? g_device.sceneRenderHeight : g_device.height); + uint16_t viewportX = g_views.sceneViewportW != 0 ? g_views.sceneViewportX : 0; + uint16_t viewportY = g_views.sceneViewportH != 0 ? g_views.sceneViewportY : 0; + uint16_t viewportW = g_views.sceneViewportW != 0 ? g_views.sceneViewportW : static_cast(sceneWidth); + uint16_t viewportH = g_views.sceneViewportH != 0 ? g_views.sceneViewportH : static_cast(sceneHeight); + const float passedClipX = WWMath::Clamp(tactical_width_fraction, 0.0f, 1.0f); + const float passedClipY = WWMath::Clamp(tactical_height_fraction, 0.0f, 1.0f); + if (sceneWidth > 0.0f && sceneHeight > 0.0f + && (passedClipX < 0.999f || passedClipY < 0.999f)) + { + viewportX = 0; + viewportY = 0; + viewportW = static_cast(sceneWidth * passedClipX + 0.5f); + viewportH = static_cast(sceneHeight * passedClipY + 0.5f); + } + bgfx::setViewRect(kBgfxSmudgeCopyView, 0, 0, + static_cast(sceneWidth), + static_cast(sceneHeight)); + bgfx::setViewRect(kBgfxSmudgeView, viewportX, viewportY, viewportW, viewportH); + g_views.smudgeClip[0] = sceneWidth > 0.0f ? 1.0f / sceneWidth : 1.0f; + g_views.smudgeClip[1] = sceneHeight > 0.0f ? 1.0f / sceneHeight : 1.0f; + g_views.smudgeClip[2] = sceneWidth > 0.0f + ? WWMath::Clamp(static_cast(viewportX + viewportW) / sceneWidth, 0.0f, 1.0f) + : passedClipX; + g_views.smudgeClip[3] = sceneHeight > 0.0f + ? WWMath::Clamp(static_cast(viewportY + viewportH) / sceneHeight, 0.0f, 1.0f) + : passedClipY; + + bgfx::setTexture(0, g_uniforms.sTex0, g_device.sceneColor, + BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + bgfx::setVertexBuffer(0, g_device.fullscreenClearVB); + bgfx::setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_DEPTH_TEST_ALWAYS); + bgfx::submit(kBgfxSmudgeCopyView, g_device.copyProgram); + g_views.smudgeActive = true; + return true; +} + +void BgfxBackend::End_Smudge_Distortion() +{ + g_views.smudgeActive = false; +} + +void BgfxBackend::Set_Tree_Shader_Constants(const float swayTable[11][4], + const float shroudOffset[4], + const float shroudScale[4]) +{ + 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_views.treeShaderActive = active; +} + +void BgfxBackend::Set_Grayscale_Mode(bool enable) +{ + 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) +{ + if (GgcFlags::Enabled(GgcFlag_NoCloudShadows)) + { + enable = false; + cloud_tex = nullptr; + } + + 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_draw.cloudTex = EnsureBgfxTexture(cloud_tex); + } + else + { + g_draw.cloudTex = BGFX_INVALID_HANDLE; + } +} + +// TheSuperHackers @feature bobtista 17/07/2026 Static noise/lightmap layer (the DX8 +// ST_TERRAIN_BASE_NOISE2 pass): world-XY projected multiplicative texture, applied by +// fs_uber to terrain and other ground draws alongside the cloud shadow. Kill switch +// GGC_NO_LIGHTMAP restores the previous no-lightmap rendering. +void BgfxBackend::Set_Light_Map_Params(bool enable, float stretch, TextureClass * noise_tex) +{ + if (GgcFlags::Enabled(GgcFlag_NoLightMap)) + { + enable = false; + noise_tex = nullptr; + } + + g_draw.lightMapParams[0] = stretch; + g_draw.lightMapParams[1] = 0.0f; + g_draw.lightMapParams[2] = 0.0f; + g_draw.lightMapParams[3] = enable ? 1.0f : 0.0f; + + if (enable && noise_tex != nullptr) + { + g_draw.lightMapTex = EnsureBgfxTexture(noise_tex); + } + else + { + g_draw.lightMapTex = BGFX_INVALID_HANDLE; + } +} + +void BgfxBackend::Set_Color_Write_Enable(bool red, bool green, bool blue, bool 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; + } + unsigned d3dMask = 0; + if (red) + { + d3dMask |= RB_COLOR_RED; + } + if (green) + { + d3dMask |= RB_COLOR_GREEN; + } + if (blue) + { + d3dMask |= RB_COLOR_BLUE; + } + if (alpha) + { + d3dMask |= RB_COLOR_ALPHA; + } + FixedFunctionState::Set_Color_Write_Mask(d3dMask); + g_overrides.colorWriteOverride = static_cast(mask); + g_overrides.suppressDraw = false; +} + +// TheSuperHackers @refactor bobtista 15/04/2026 Mirror the +// DWORD variant into g_overrides.colorWriteOverride so stencil shadow volume +// passes that call Set_Color_Write_Mask(0) actually disable bgfx color writes. +unsigned BgfxBackend::Get_Color_Write_Mask() const +{ + return FixedFunctionState::Color_Write_Mask( + RB_COLOR_RED | RB_COLOR_GREEN | RB_COLOR_BLUE | RB_COLOR_ALPHA); +} + +void BgfxBackend::Set_Color_Write_Mask(unsigned mask) +{ + FixedFunctionState::Set_Color_Write_Mask(mask); + uint64_t bgfxMask = 0; + if (mask & RB_COLOR_RED) + { + bgfxMask |= BGFX_STATE_WRITE_R; + } + if (mask & RB_COLOR_GREEN) + { + bgfxMask |= BGFX_STATE_WRITE_G; + } + if (mask & RB_COLOR_BLUE) + { + bgfxMask |= BGFX_STATE_WRITE_B; + } + if (mask & RB_COLOR_ALPHA) + { + bgfxMask |= BGFX_STATE_WRITE_A; + } + g_overrides.colorWriteOverride = static_cast(bgfxMask); + g_overrides.suppressDraw = false; +} + +void BgfxBackend::Set_Lighting_Enable(bool enable) +{ + FixedFunctionState::Set_Lighting_Enabled(enable); + g_draw.lightingEnabled[0] = enable ? 1.0f : 0.0f; +} + +void BgfxBackend::Set_Point_Sprite_Enable(bool enable) +{ + FixedFunctionState::Set_Point_Sprite_Enabled(enable); +} + +void BgfxBackend::Set_Point_Scale_Enable(bool enable) +{ + FixedFunctionState::Set_Point_Scale_Enabled(enable); +} + +void BgfxBackend::Set_Point_Size(float size, float min_size, float max_size) +{ + FixedFunctionState::Set_Point_Size_Bits( + FloatAsDword(size), + FloatAsDword(min_size), + FloatAsDword(max_size)); +} + +void BgfxBackend::Set_Point_Scale(float a, float b, float c) +{ + FixedFunctionState::Set_Point_Scale_Bits( + FloatAsDword(a), + FloatAsDword(b), + FloatAsDword(c)); +} + +void BgfxBackend::Skip_Next_Bgfx_Submit() +{ + g_views.skipNextSubmitEngineDraw = true; +} + +void BgfxBackend::Set_Projected_Shadow_Decal_Active(bool active) +{ + Set_Projected_Decal_Mode(active ? RB_PROJECTED_DECAL_BLOB_SHADOW : RB_PROJECTED_DECAL_NONE); +} + +void BgfxBackend::Set_Projected_Decal_Mode(RenderBackendProjectedDecalMode mode) +{ + g_views.projectedDecalMode = static_cast(mode); + g_views.projectedShadowDecalActive = mode != RB_PROJECTED_DECAL_NONE; +} + +// 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) +{ + FixedFunctionState::Set_Texture_Factor(argb); +} + +void BgfxBackend::Set_Shadow_Volume_Shader_Active(bool active) +{ + g_views.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 (!LegacyStencilShadowsEnabled() + || !g_device.initialized + || !g_views.shadowVolumeActive + || !bgfx::isValid(g_device.shadowVolumeProgram) + || !bgfx::isValid(g_draw.vb) + || num_silhouette_verts < 3) + { + LogBgfxStencilShadowEvent("caps-skip", "disabled-or-invalid", + num_silhouette_verts, 0); + 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) + { + LogBgfxStencilShadowEvent("caps-skip", "no-transient-index-buffer", + num_silhouette_verts, total_indices); + return; + } + + bgfx::TransientIndexBuffer tib; + bgfx::allocTransientIndexBuffer(&tib, total_indices); + g_stats.transientIbAllocations++; + 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_draw.stencilPassOpBits == BGFX_STENCIL_OP_PASS_Z_DECR + || g_draw.stencilPassOpBits == BGFX_STENCIL_OP_PASS_Z_DECRSAT) + { + LogBgfxStencilShadowEvent("caps-skip", "second-pass-two-sided", + num_silhouette_verts, total_indices); + return; + } + + // No face culling, two-sided stencil matching the side-wall submit. + uint64_t state = BgfxShadowVolumeDepthState(); + bgfx::setState(state); + 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_draw.vb); + bgfx::setIndexBuffer(&tib); + bgfx::setTransform(g_frame.world); + BindShadowVolumeBiasUniform(); + + bgfx::submit(BgfxShadowVolumeSubmitView(), g_device.shadowVolumeProgram); + g_stats.shadowVolumeSubmits++; + LogBgfxStencilShadowEvent("caps-submit", nullptr, + num_silhouette_verts, total_indices); +} + +void BgfxBackend::Submit_Shadow_Volume_Triangulated_Caps( + unsigned strip_start_vertex, + const short * local_cap_indices, + unsigned cap_index_count) +{ + if (!LegacyStencilShadowsEnabled() + || !g_device.initialized + || !g_views.shadowVolumeActive + || !bgfx::isValid(g_device.shadowVolumeProgram) + || !bgfx::isValid(g_draw.vb) + || local_cap_indices == nullptr + || cap_index_count < 3) + { + LogBgfxStencilShadowEvent("tri-caps-skip", "disabled-or-invalid", + cap_index_count, 0); + return; + } + + // front cap + back cap (reversed winding) + const unsigned total_indices = cap_index_count * 2; + + if (bgfx::getAvailTransientIndexBuffer(total_indices) < total_indices) + { + LogBgfxStencilShadowEvent("tri-caps-skip", "no-transient-index-buffer", + cap_index_count, total_indices); + return; + } + + bgfx::TransientIndexBuffer tib; + bgfx::allocTransientIndexBuffer(&tib, total_indices); + g_stats.transientIbAllocations++; + 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 = BgfxShadowVolumeDepthState(); + 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_draw.shadowStencilFront, g_draw.shadowStencilBack); + + bgfx::setVertexBuffer(0, g_draw.vb); + bgfx::setIndexBuffer(&tib); + bgfx::setTransform(g_frame.world); + BindShadowVolumeBiasUniform(); + + bgfx::submit(BgfxShadowVolumeSubmitView(), g_device.shadowVolumeProgram); + g_stats.shadowVolumeSubmits++; + LogBgfxStencilShadowEvent("tri-caps-submit", nullptr, + cap_index_count, total_indices); +} + +bool BgfxBackend::Needs_Closed_Shadow_Volumes() const +{ + return LegacyStencilShadowsEnabled() + && GgcFlags::Enabled(GgcFlag_BgfxClosedShadowVolumes); +} + +void BgfxBackend::Apply_Stencil_Shadow_Darken(unsigned shadow_color, + unsigned stencil_read_mask, + unsigned stencil_ref, + int /*x*/, + int /*y*/, + int /*width*/, + int /*height*/) +{ + if (!LegacyStencilShadowsEnabled() + || GgcFlags::Enabled(GgcFlag_BgfxStencilNoApply) + || !g_device.initialized + || !bgfx::isValid(g_device.shadowApplyProgram)) + { + LogBgfxStencilShadowEvent("darken-skip", "disabled-or-invalid", + stencil_read_mask, stencil_ref); + return; + } + // The legacy DX8 shadow manager draws its volume geometry through raw D3D + // state in some paths. bgfx only sees stencil writes that are explicitly + // submitted through this backend. If no bgfx shadow volume touched stencil + // this frame, the fullscreen darken quad would read stale stencil contents + // left by unrelated passes and darken buildings/terrain based on camera + // position. Only apply the darken pass when bgfx populated the matching + // shadow stencil first. + if (g_stats.shadowVolumeSubmits == 0) + { + LogBgfxStencilShadowEvent("darken-skip", "no-volume-submits", + stencil_read_mask, stencil_ref); + 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)) + { + LogBgfxStencilShadowEvent("darken-skip", "no-transient-buffers", + stencil_read_mask, stencil_ref); + return; + } + g_stats.transientVbAllocations++; + g_stats.transientIbAllocations++; + + 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_uniforms.uShadowColor)) + { + bgfx::setUniform(g_uniforms.uShadowColor, color); + } + + uint64_t state = BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A + | BGFX_STATE_DEPTH_TEST_ALWAYS + | BGFX_STATE_MSAA + | BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_DST_COLOR, BGFX_STATE_BLEND_ZERO); + bgfx::setState(state); + + // Shadow volumes leave a non-zero count in stencil where the fullscreen + // darken quad should apply. Use an explicit front/back state because the + // clip-space quad winding differs across backends. + uint32_t stencil = BGFX_STENCIL_TEST_NOTEQUAL + | BGFX_STENCIL_FUNC_REF(0) + | 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, stencil); + + bgfx::submit(BgfxShadowVolumeSubmitView(), g_device.shadowApplyProgram); + g_stats.shadowApplySubmits++; + LogBgfxStencilShadowEvent("darken-submit", nullptr, + stencil_read_mask, stencil_ref); +} + +void BgfxBackend::Set_Stencil_Enable(bool enable) +{ + FixedFunctionState::Set_Stencil_Enabled(enable); + g_draw.stencilEnabled = enable; + UpdateShadowStencilState(); +} + +void BgfxBackend::Set_Stencil_Func(CompareFunc f) +{ + FixedFunctionState::Set_Stencil_Function(static_cast(f)); + g_draw.stencilFuncBits = MapCmpFuncToBgfxStencilTest(f); + UpdateShadowStencilState(); +} + +void BgfxBackend::Set_Stencil_Ref(unsigned ref) +{ + FixedFunctionState::Set_Stencil_Reference(ref); + g_draw.stencilRef = ref; + UpdateShadowStencilState(); +} + +void BgfxBackend::Set_Stencil_Mask(unsigned mask) +{ + FixedFunctionState::Set_Stencil_Read_Mask(mask); + g_draw.stencilReadMask = mask; + UpdateShadowStencilState(); +} + +void BgfxBackend::Set_Stencil_Write_Mask(unsigned mask) +{ + FixedFunctionState::Set_Stencil_Write_Mask(mask); + UpdateShadowStencilState(); +} + +void BgfxBackend::Set_Stencil_Pass_Op(StencilOp op) +{ + FixedFunctionState::Set_Stencil_Pass_Op(static_cast(op)); + g_draw.stencilPassOpBits = MapStencilOpToBgfx(op, BGFX_STENCIL_OP_PASS_Z_SHIFT); + UpdateShadowStencilState(); +} + +void BgfxBackend::Set_Stencil_Fail_Op(StencilOp op) +{ + FixedFunctionState::Set_Stencil_Fail_Op(static_cast(op)); + g_draw.stencilFailOpBits = MapStencilOpToBgfx(op, BGFX_STENCIL_OP_FAIL_S_SHIFT); + UpdateShadowStencilState(); +} + +void BgfxBackend::Set_Stencil_ZFail_Op(StencilOp op) +{ + FixedFunctionState::Set_Stencil_ZFail_Op(static_cast(op)); + g_draw.stencilZFailOpBits = MapStencilOpToBgfx(op, BGFX_STENCIL_OP_FAIL_Z_SHIFT); + UpdateShadowStencilState(); +} + +CullMode BgfxBackend::Get_Cull_Mode() const +{ + return static_cast(FixedFunctionState::Cull_Mode(RB_CULL_NONE)); +} + +void BgfxBackend::Set_Cull_Mode(CullMode mode) +{ + FixedFunctionState::Set_Cull_Mode(static_cast(mode)); + switch (mode) + { + case RB_CULL_CW: g_draw.cullModeBits = 1; break; + case RB_CULL_CCW: g_draw.cullModeBits = 2; break; + case RB_CULL_NONE: + default: g_draw.cullModeBits = 0; break; + } +} + +void BgfxBackend::Set_Z_Bias(int bias) +{ + FixedFunctionState::Set_Z_Bias(bias); + g_draw.zBiasUnits = static_cast(bias) & 0xFFu; +} + +void BgfxBackend::Set_Normal_Bias(float bias) +{ + g_draw.normalBias[0] = bias; +} + +void BgfxBackend::Set_Fill_Mode(FillMode mode) +{ + FixedFunctionState::Set_Fill_Mode(static_cast(mode)); +} + +void BgfxBackend::Set_Shade_Mode(ShadeMode mode) +{ + FixedFunctionState::Set_Shade_Mode(static_cast(mode)); +} + +void BgfxBackend::Set_Depth_Test_Enable(bool enable) +{ + FixedFunctionState::Set_Depth_Test_Enabled(enable); + g_draw.depthTestEnabled = enable; +} + +void BgfxBackend::Set_Depth_Write_Enable(bool enable) +{ + FixedFunctionState::Set_Depth_Write_Enabled(enable); + g_draw.depthWriteEnabled = enable; +} + +void BgfxBackend::Set_Depth_Func(CompareFunc func) +{ + const unsigned idx = static_cast(func); + FixedFunctionState::Set_Depth_Function(idx); + g_draw.depthFunc = idx; + 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 + }; + if (idx < 9) + { + g_draw.depthFuncBits = kDepthMap[idx]; + } +} + +static bgfx::TextureFormat::Enum Resolve_Render_Target_Color_Format(WW3DFormat format) +{ + bgfx::TextureFormat::Enum bgfxFormat = TranslateWW3DFormat(format); + const bgfx::Caps *caps = bgfx::getCaps(); + if (caps != nullptr + && bgfxFormat != bgfx::TextureFormat::Unknown + && (caps->formats[bgfxFormat] & BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER) != 0) + { + return bgfxFormat; + } + + if (caps != nullptr + && (caps->formats[bgfx::TextureFormat::BGRA8] & BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER) != 0) + { + return bgfx::TextureFormat::BGRA8; + } + return bgfx::TextureFormat::RGBA8; +} + +static const BgfxFramebufferEntry *Ensure_Render_Target_Framebuffer(TextureClass *texture) +{ + if (texture == nullptr || !g_device.initialized) + { + return nullptr; + } + + auto it = g_caches.framebuffer.find(texture); + if (it != g_caches.framebuffer.end()) + { + // TheSuperHackers @bugfix bobtista 12/06/2026 Revalidate the cached framebuffer against the + // texture's current size. A render-target texture can be resized (e.g. on a resolution + // change), which would otherwise return a stale framebuffer at the old dimensions and a + // mismatched setViewRect. Defer-destroy the stale FB so the in-flight frame retires cleanly, + // then fall through to rebuild it at the new size. + const uint16_t curW = static_cast(texture->Get_Width()); + const uint16_t curH = static_cast(texture->Get_Height()); + if (it->second.width != curW || it->second.height != curH) + { + if (bgfx::isValid(it->second.fb)) + { + g_caches.deferredDestroyFB.push_back(it->second.fb); + } + g_caches.framebuffer.erase(it); + it = g_caches.framebuffer.end(); + } + } + if (it == g_caches.framebuffer.end()) + { + const uint16_t w = static_cast(texture->Get_Width()); + const uint16_t h = static_cast(texture->Get_Height()); + const bgfx::TextureFormat::Enum colorFormat = + Resolve_Render_Target_Color_Format(texture->Get_Texture_Format()); + + bgfx::TextureHandle colorTex = bgfx::createTexture2D( + w, h, false, 1, colorFormat, + 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); + if (!bgfx::isValid(fb)) + { + if (bgfx::isValid(colorTex)) { + bgfx::destroy(colorTex); + } + if (bgfx::isValid(depthTex)) { + bgfx::destroy(depthTex); + } + return nullptr; + } + + BgfxFramebufferEntry entry = { fb, colorTex, w, h }; + 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)); + } + + return &it->second; +} + +void BgfxBackend::Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture) +{ + // The engine-supplied depth target is intentionally unused; the bgfx framebuffer allocates its own D24S8 attachment. + (void)ztexture; + if (texture == nullptr || !g_device.initialized) + { + g_views.renderToTexture = false; + g_views.renderTargetTexture = nullptr; + return; + } + + const BgfxFramebufferEntry *entry = Ensure_Render_Target_Framebuffer(texture); + if (entry == nullptr) { + g_views.renderToTexture = false; + g_views.renderTargetTexture = nullptr; + return; + } + + 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_views.renderToTexture = true; + g_views.renderTargetTexture = texture; +} + +void BgfxBackend::Clear_State_Overrides() +{ + g_overrides.Reset(); + g_draw.texcoordSelect[0] = 0.0f; + g_draw.objectShroudDim[3] = 0.0f; + g_draw.delayedObjectShroudPass = false; + // 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 + // at Begin_Scene (above) and by Override_Terrain_Blend(false). +} + +static LightEnvironmentClass * g_lastLightEnv = nullptr; + +void BgfxBackend::Set_Light_Environment(LightEnvironmentClass * light_env) +{ + if (light_env != nullptr) + { + g_lastLightEnv = light_env; + const Vector3 & ambient = light_env->Get_Equivalent_Ambient(); + FixedFunctionState::Set_Ambient_Color(MakeLegacyARGBColor(ambient, 0.0f)); + g_draw.sceneAmbient[0] = ambient.X; + g_draw.sceneAmbient[1] = ambient.Y; + g_draw.sceneAmbient[2] = ambient.Z; + + // TheSuperHackers @bugfix bobtista 17/07/2026 Mirror the lights into + // FixedFunctionState like the retail Set_Light chain did. The sorting + // renderer snapshots FixedFunctionState per node and replays it at flush; + // with the mirror missing every sorted node captured "no lights" and lit + // translucent SORT meshes replayed with scene ambient only, darker than + // the DX8 build. Encoding matches retail Set_Light_Environment (D3D + // direction = -toward-light; point attenuation 0.1/irad and 8/orad^2, + // range = orad) so the replay's inverse mapping recovers the same values. + RenderStateStruct & rsLights = FixedFunctionState::Render_State(); + const int count = light_env->Get_Light_Count(); + for (int i = 0; i < 4; ++i) + { + LegacyFixedFunctionLight & mirrored = rsLights.Lights[i]; + std::memset(&mirrored, 0, sizeof(mirrored)); + rsLights.LightEnable[i] = (i < count); + if (i < count) + { + const Vector3 & dir = light_env->Get_Light_Direction(i); + 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 + mirrored.Direction.x = -dir.X; + mirrored.Direction.y = -dir.Y; + mirrored.Direction.z = -dir.Z; + if (light_env->isPointLight(i)) + { + const Vector3 & dif = light_env->getPointDiffuse(i); + const Vector3 & amb = light_env->getPointAmbient(i); + const Vector3 & pos = light_env->getPointCenter(i); + g_draw.lightColors[i][0] = dif.X; + g_draw.lightColors[i][1] = dif.Y; + g_draw.lightColors[i][2] = dif.Z; + g_draw.lightAmbients[i][0] = amb.X; + g_draw.lightAmbients[i][1] = amb.Y; + g_draw.lightAmbients[i][2] = amb.Z; + g_draw.lightPositions[i][0] = pos.X; + g_draw.lightPositions[i][1] = pos.Y; + g_draw.lightPositions[i][2] = pos.Z; + g_draw.lightParams[i][0] = light_env->getPointIrad(i); + g_draw.lightParams[i][1] = light_env->getPointOrad(i); + g_draw.lightParams[i][2] = 1.0f; + g_draw.lightParams[i][3] = 1.0f; + const float irad = light_env->getPointIrad(i); + const float orad = light_env->getPointOrad(i); + mirrored.Type = 1; // legacy D3DLIGHT_POINT + mirrored.Position.x = pos.X; + mirrored.Position.y = pos.Y; + mirrored.Position.z = pos.Z; + mirrored.Diffuse.r = dif.X; + mirrored.Diffuse.g = dif.Y; + mirrored.Diffuse.b = dif.Z; + mirrored.Ambient.r = amb.X; + mirrored.Ambient.g = amb.Y; + mirrored.Ambient.b = amb.Z; + mirrored.Range = orad; + mirrored.Attenuation0 = 1.0f; + mirrored.Attenuation1 = (WWMath::Fabs(irad - orad) < 1e-5f || irad <= 0.0f) + ? 0.0f : 0.1f / irad; + mirrored.Attenuation2 = (orad > 0.0f) ? 8.0f / (orad * orad) : 0.0f; + } + else + { + const Vector3 & dif = light_env->Get_Light_Diffuse(i); + g_draw.lightColors[i][0] = dif.X; + g_draw.lightColors[i][1] = dif.Y; + g_draw.lightColors[i][2] = dif.Z; + g_draw.lightAmbients[i][0] = 0.0f; + g_draw.lightAmbients[i][1] = 0.0f; + g_draw.lightAmbients[i][2] = 0.0f; + g_draw.lightPositions[i][0] = 0.0f; + g_draw.lightPositions[i][1] = 0.0f; + g_draw.lightPositions[i][2] = 0.0f; + g_draw.lightParams[i][0] = 0.0f; + g_draw.lightParams[i][1] = 0.0f; + g_draw.lightParams[i][2] = 0.0f; + g_draw.lightParams[i][3] = 1.0f; + mirrored.Type = 3; // legacy D3DLIGHT_DIRECTIONAL + mirrored.Diffuse.r = dif.X; + mirrored.Diffuse.g = dif.Y; + mirrored.Diffuse.b = dif.Z; + } + g_draw.lightColors[i][3] = 1.0f; + } + else + { + g_draw.lightDirs[i][3] = 0.0f; // disabled + g_draw.lightColors[i][3] = 0.0f; + g_draw.lightAmbients[i][3] = 0.0f; + g_draw.lightParams[i][3] = 0.0f; + } + } + } +} + +// -- Transforms -------------------------------------------------------------- + +void BgfxBackend::Set_Transform(TransformKind transform, const Matrix4x4 & m) +{ + CacheTransform(transform, m); + switch (transform) + { + case RB_TRANSFORM_WORLD: + W3DMatrix4ToBgfx(m, g_frame.world); + break; + case RB_TRANSFORM_VIEW: + W3DMatrix4ToBgfx(m, g_frame.view); + g_frame.cameraProjDirty = true; + g_views.overlay2DActive = false; + break; + case RB_TRANSFORM_PROJECTION: + W3DMatrix4ToBgfx(m, g_frame.proj); + g_frame.cameraProjDirty = true; + break; + default: + break; + } +} + +void BgfxBackend::Set_Transform(TransformKind transform, const Matrix3D & m) +{ + CacheTransform(transform, m); + switch (transform) + { + case RB_TRANSFORM_WORLD: + W3DMatrix3DToBgfx(m, g_frame.world); + break; + case RB_TRANSFORM_VIEW: + W3DMatrix3DToBgfx(m, g_frame.view); + g_frame.cameraProjDirty = true; + g_views.overlay2DActive = false; + break; + default: + break; + } +} + +void BgfxBackend::Get_Transform(TransformKind transform, Matrix4x4 & m) const +{ + auto matrix = MakeIdentityLegacyCacheMatrix(); + FixedFunctionState::Transform_Matrix(static_cast(transform), matrix); + m = To_Matrix4x4(matrix); +} + +void BgfxBackend::Set_World_Identity() +{ + CacheIdentityTransform(RB_TRANSFORM_WORLD); + FixedFunctionState::Set_World_Identity(); + IdentityMatrix(g_frame.world); +} + +void BgfxBackend::Set_View_Identity() +{ + CacheIdentityTransform(RB_TRANSFORM_VIEW); + FixedFunctionState::Set_View_Identity(); + IdentityMatrix(g_frame.view); + g_frame.cameraProjDirty = true; + g_views.overlay2DActive = true; +} + +bool BgfxBackend::Is_World_Identity() const +{ + return IsCachedTransformIdentity(RB_TRANSFORM_WORLD); +} + +bool BgfxBackend::Is_View_Identity() const +{ + return IsCachedTransformIdentity(RB_TRANSFORM_VIEW); +} + +void BgfxBackend::Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, + float znear, float zfar) +{ + (void)znear; + (void)zfar; + CacheTransform(RB_TRANSFORM_PROJECTION, matrix); + W3DMatrix4ToBgfx(matrix, g_frame.proj); + g_frame.cameraProjDirty = true; + +} + +void BgfxBackend::Draw_Screen_Multiply_Quad(unsigned color, int x, int y, int width, int height) +{ + if (!g_device.initialized || !bgfx::isValid(g_device.passthroughProgram) + || !Is_Triangle_Draw_Enabled()) + { + return; + } + + const float left = (2.0f * static_cast(x) / g_device.width) - 1.0f; + const float right = (2.0f * static_cast(x + width) / g_device.width) - 1.0f; + const float top = 1.0f - (2.0f * static_cast(y) / g_device.height); + const float bottom = 1.0f - (2.0f * static_cast(y + height) / g_device.height); + + struct ScreenVert { float x, y, z; uint32_t rgba; }; + bgfx::TransientVertexBuffer vb; + if (bgfx::getAvailTransientVertexBuffer(6, g_device.triangleLayout) < 6) + { + return; + } + bgfx::allocTransientVertexBuffer(&vb, 6, g_device.triangleLayout); + const ScreenVert verts[6] = { + { left, bottom, 0.0f, color }, { left, top, 0.0f, color }, { right, top, 0.0f, color }, + { left, bottom, 0.0f, color }, { right, top, 0.0f, color }, { right, bottom, 0.0f, color }, + }; + std::memcpy(vb.data, verts, sizeof(verts)); + + float identity[16]; + IdentityMatrix(identity); + bgfx::setViewTransform(kBgfxUIView, identity, identity); + bgfx::setVertexBuffer(0, &vb); + bgfx::setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A + | BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ZERO, BGFX_STATE_BLEND_SRC_COLOR)); + bgfx::submit(kBgfxUIView, g_device.passthroughProgram); +} + +// -- Draw calls -------------------------------------------------------------- + +namespace +{ +// TheSuperHackers @refactor bobtista 11/04/2026 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. +void FlushPendingBoundDynamicRangeUploads() +{ + if (!g_draw.useTransientVB && !g_draw.useStaticVB && g_draw.vbOwner != nullptr) + { + const VertexBufferClass * owner = g_draw.vbOwner; + FlushPendingVertexRangeUpload(owner); + bgfx::DynamicVertexBufferHandle h = FindResourceVertexBufferHandle(owner); + if (!bgfx::isValid(h)) + { + auto it = g_caches.vb.find(owner); + if (it != g_caches.vb.end()) + { + h = it->second.handle; + } + } + if (bgfx::isValid(h)) + { + g_draw.vb = h; + } + } + + if (!g_draw.useTransientIB && !g_draw.useStaticIB && g_draw.ibOwner != nullptr) + { + const IndexBufferClass * owner = g_draw.ibOwner; + FlushPendingIndexRangeUpload(owner); + bgfx::DynamicIndexBufferHandle h = FindResourceIndexBufferHandle(owner); + if (!bgfx::isValid(h)) + { + auto it = g_caches.ib.find(owner); + if (it != g_caches.ib.end()) + { + h = it->second.handle; + } + } + if (bgfx::isValid(h)) + { + g_draw.ib = h; + } + } +} + +void SubmitEngineDraw(unsigned short start_index, + unsigned short polygon_count, + unsigned short min_vertex_index, + unsigned short vertex_count, + bool triangle_strip = false) +{ + PERF_TIME(PERF_SECT_SUBMIT_DRAW); + if (g_overrides.suppressDraw) + { + g_stats.skippedDraws++; + bgfx::discard(BGFX_DISCARD_ALL); + return; + } + if (!g_device.initialized) + { + return; + } + g_stats.drawCalls++; + // Lock in the scene sun for the shadow CSM from the first sun-like draw of the level (before + // SetupSunShadowView runs below), so dynamic lights cannot later swing the cast shadows. + MaybeCaptureSceneSun(); + if (!g_draw.alphaBlendExplicitlySet) + { + g_draw.alphaBlendEnabled = g_draw.shaderAlphaBlendEnabled; + if (g_draw.alphaBlendEnabled) + { + g_draw.blendFuncBits = g_draw.shaderBlendFuncBits; + } + } + if (!g_draw.alphaTestExplicitlySet) + { + g_draw.atestRef = g_draw.shaderAtestRef; + g_draw.atestFunc = g_draw.shaderAtestFunc; + g_draw.atestEnabled = g_draw.atestFunc > 0.0f; + } + if (!bgfx::isValid(g_draw.program)) + { + LogBgfxEffectSubmit("submit-engine", + g_views.inSortFlush ? kBgfxEngineSortView : kBgfxEngineView, + polygon_count, + vertex_count, + g_draw.state, + "skip-no-program"); + g_stats.skippedDraws++; + // TheSuperHackers @bugfix bobtista 17/07/2026 Discard pending encoder state like every + // other skip path in this function. An instanced batch has already bound its instance + // buffer by the time this runs; without the discard that state leaks into the next + // submit, which then draws instanceCount overlapping copies. + bgfx::discard(BGFX_DISCARD_ALL); + return; + } + FlushPendingBoundDynamicRangeUploads(); + const bool have_vb = g_draw.useTransientVB + || (g_draw.useStaticVB && bgfx::isValid(g_draw.staticVB)) + || bgfx::isValid(g_draw.vb); + const bool have_ib = g_draw.useTransientIB + || (g_draw.useStaticIB && bgfx::isValid(g_draw.staticIB)) + || bgfx::isValid(g_draw.ib); + if (!have_vb || !have_ib) + { + LogBgfxEffectSubmit("submit-engine", + g_views.inSortFlush ? kBgfxEngineSortView : kBgfxEngineView, + polygon_count, + vertex_count, + g_draw.state, + !have_vb && !have_ib ? "skip-no-vb-ib" : (!have_vb ? "skip-no-vb" : "skip-no-ib")); + g_stats.skippedDraws++; + bgfx::discard(BGFX_DISCARD_ALL); + return; + } + if (g_draw.useTransientVB) + { + g_stats.transientVbDraws++; + } + if (g_draw.useTransientIB) + { + g_stats.transientIbDraws++; + } + + // 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. + // 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_views.overlay2DActive between the + // Set_View_Identity call and the actual Draw_Triangles. + bool is2D = g_views.overlay2DActive; + if (!is2D && !g_views.renderToTexture && !g_views.waterOverrideActive + && !g_views.waterOverlayActive + && !g_views.effectOverlayActive && !g_views.inSortFlush) + { + // Camera-space particles and smudges also draw with an identity view, + // but keep the camera perspective projection. Only infer 2D when both + // the view and projection match screen-space drawing. + if (IsIdentityViewMatrix(g_frame.view) && IsNonPerspectiveProjection(g_frame.proj)) + { + is2D = true; + } + } + + bgfx::ViewId submitView; + if (g_views.smudgeActive) + { + submitView = kBgfxSmudgeView; + } + else if (g_views.effectOverlayActive) + { + submitView = kBgfxEffectOverlayView; + } + else if (is2D) + { + submitView = kBgfxUIView; + } + else if (g_views.renderToTexture) + { + submitView = kBgfxRTTView; + } + else if (g_views.waterOverrideActive || g_views.waterOverlayActive) + { + submitView = kBgfxWaterView; + } + else if (g_views.inSortFlush) + { + submitView = kBgfxEngineSortView; + } + else + { + submitView = kBgfxEngineView; + } + const uint64_t routeState = GetEffectiveDrawState(); + if (IsSortedLocalModelEffectDraw(routeState)) + { + // These sorted meshes need their raw model world matrix and the normal + // camera view. The pre-view-multiplied sort matrix lands local W3D + // model quads away from the object. + submitView = kBgfxEngineView; + } + const BgfxDiagnosticFlags diagnostics = GetBgfxDiagnosticFlags(); + switch (submitView) + { + case kBgfxUIView: g_stats.uiDraws++; break; + case kBgfxWaterView: g_stats.waterDraws++; break; + case kBgfxEngineSortView: g_stats.sortedDraws++; break; + case kBgfxEffectOverlayView: g_stats.effectDraws++; break; + case kBgfxRTTView: g_stats.rttDraws++; break; + case kBgfxSmudgeView: g_stats.smudgeDraws++; break; + default: g_stats.worldDraws++; break; + } + if (BgfxProbeFlag("GGC_PROBE_NULL_SUBMIT") + || (BgfxProbeFlag("GGC_PROBE_NO_SORTED") && submitView == kBgfxEngineSortView)) + { + g_stats.skippedDraws++; + bgfx::discard(BGFX_DISCARD_ALL); + return; + } + if (ShouldAllowBgfxDiagnosticDrawOverrides() + && GgcFlags::Enabled(GgcFlag_BgfxSkipEffectOverlayDraws) + && submitView == kBgfxEffectOverlayView) + { + g_stats.skippedDraws++; + bgfx::discard(BGFX_DISCARD_ALL); + return; + } + if (ShouldAllowBgfxDiagnosticDrawOverrides() + && GgcFlags::Enabled(GgcFlag_BgfxSkipSortedDraws) + && submitView == kBgfxEngineSortView) + { + g_stats.skippedDraws++; + bgfx::discard(BGFX_DISCARD_ALL); + 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. Sort view + // 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_frame.proj with a different frustum. We re-apply + // the camera projection to view 1 at End_Scene time. + if (!g_frame.cameraCaptured) + { + 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; + // The sun shadow view is fit to the camera, so arm it once the camera + // matrices are known (before any caster is submitted into view 20). + SetupSunShadowView(); + // TheSuperHackers @feature bobtista 23/06/2026 Arm the perspective point-shadow view for + // the strongest caster dynamic light alongside the sun map (before any caster submit). + SetupPointShadowView(); + SetupPointShadowView2(); + UpdateDramaLighting(); + } + 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_frame.view, g_frame.proj); + bgfx::setViewTransform(kBgfxShroudOverlayView, g_frame.view, g_frame.proj); + bgfx::setViewTransform(kBgfxSceneDepthView, 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_views.renderToTexture && g_frame.cameraProjDirty) + { + bgfx::setViewTransform(kBgfxRTTView, g_frame.view, g_frame.proj); + g_frame.cameraProjDirty = false; + } + if (g_views.smudgeActive) + { + float identityView[16]; + IdentityMatrix(identityView); + bgfx::setViewTransform(kBgfxSmudgeView, identityView, g_frame.proj); + } + + float identityWorld[16]; + const float * worldMtx = g_views.inSortFlush + ? g_frame.sortWorld + : g_frame.world; + if (IsSortedLocalModelEffectDraw(routeState)) + { + worldMtx = g_frame.sortWorldRaw; + } + // TheSuperHackers @bugfix bobtista 10/07/2026 World-baked texture-array + // run: the sorted fill transformed these positions into world space, so + // apply the captured view only. The equivalent handling in + // Submit_Sorted_Draw is unreachable on bgfx builds (its only caller lives + // in dx8wrapper.cpp, which the bgfx build excludes); the live sorted-pool + // path is this function. + if (g_views.inSortFlush && g_draw.sortedArrayPage >= 0) + { + worldMtx = g_frame.sortViewOnly; + } + if (is2D) + { + // TheSuperHackers @bugfix bobtista 30/04/2026 2D UI vertices are + // authored in screen/clip space for the UI view. Do not let a stale + // world matrix from the preceding 3D draw offset textured control-bar + // quads away from their text/widgets. + IdentityMatrix(identityWorld); + worldMtx = identityWorld; + } + + // TheSuperHackers @bugfix bobtista 08/07/2026 Camera-space draws (identity view with the + // camera perspective projection, e.g. ground-aligned particles that no longer sort) land in + // the engine view, but that view's transform is retroactively the camera view because + // End_Scene re-applies it for the whole frame. Fold the inverse camera view into the model + // matrix so the pre-transformed vertices are not view-transformed twice. + float cameraSpaceWorld[16]; + static const bool cameraSpaceWorldFixDisabled = GgcFlags::Enabled(GgcFlag_BgfxNoCameraSpaceWorldFix); + if (!cameraSpaceWorldFixDisabled + && !is2D + && submitView == kBgfxEngineView + && !g_views.inSortFlush + && !IsSortedLocalModelEffectDraw(routeState) + && g_frame.cameraCaptured + && IsIdentityViewMatrix(g_frame.view) + && !IsNonPerspectiveProjection(g_frame.proj)) + { + float invCameraView[16]; + bx::mtxInverse(invCameraView, g_frame.cameraView); + bx::mtxMul(cameraSpaceWorld, worldMtx, invCameraView); + worldMtx = cameraSpaceWorld; + LogBgfxEffectSubmit("submit-engine", submitView, + polygon_count, vertex_count, g_draw.state, "camera-space-world"); + } + + bgfx::setTransform(worldMtx); + + // TheSuperHackers @refactor bobtista 11/04/2026 d3d8's BaseVertexIndex maps to bgfx's + // setVertexBuffer _startVertex, not an IB start offset: it biases which vertex an index + // resolves to, not which indices are read. + const uint32_t base_vertex = static_cast(g_draw.ibOffset); + const uint32_t bindVertexCount = + static_cast(min_vertex_index) + static_cast(vertex_count); + + if (g_draw.useTransientVB) + { + 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_draw.transientVB); + } + 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_draw.transientVB, + base_vertex, bindVertexCount); + } + } + else + { + if (g_draw.useStaticVB) + { + bgfx::setVertexBuffer(0, g_draw.staticVB, base_vertex, bindVertexCount); + } + else + { + bgfx::setVertexBuffer(0, g_draw.vb, base_vertex, bindVertexCount); + } + } + + const uint32_t indexCount = triangle_strip + ? static_cast(polygon_count) + 2 + : static_cast(polygon_count) * 3; + uint32_t indexStart = start_index; + + if (g_draw.useTransientIB) + { + bgfx::setIndexBuffer(&g_draw.transientIB, + indexStart, + indexCount); + } + else + { + if (g_draw.useStaticIB) + { + bgfx::setIndexBuffer(g_draw.staticIB, + indexStart, + indexCount); + } + else + { + bgfx::setIndexBuffer(g_draw.ib, + indexStart, + indexCount); + } + } + + if (g_views.smudgeActive) + { + if (bgfx::isValid(g_device.smudgeProgram)) + { + bgfx::setTexture(0, g_uniforms.sTex0, g_device.sceneSmudgeCopy, + BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + if (bgfx::isValid(g_uniforms.uSmudgeClip)) + { + bgfx::setUniform(g_uniforms.uSmudgeClip, g_views.smudgeClip); + } + bgfx::setState(BGFX_STATE_WRITE_RGB + | BGFX_STATE_DEPTH_TEST_ALWAYS + | BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, + BGFX_STATE_BLEND_INV_SRC_ALPHA) + | BGFX_STATE_MSAA); + bgfx::submit(kBgfxSmudgeView, g_device.smudgeProgram); + g_stats.smudgeSubmits++; + } + else + { + g_stats.skippedDraws++; + bgfx::discard(BGFX_DISCARD_ALL); + } + return; + } + + // Bind engine textures with the 1x1 white fallback (identity for vertex color and for the + // stage-1 multiply). Sampler flags 0 = the sampler's creation-time defaults, i.e. trilinear + // when the texture has mips. + + BindTextureStages(); + UpdateTextureTransforms(); + + // TheSuperHackers @bugfix bobtista 12/06/2026 The 2D override below overwrites persistent g_draw + // texcoord-routing fields. Those are only rewritten when texcoord generation is reconfigured, so a + // later 3D draw that reuses the prior routing would inherit the 2D values and sample black atlas + // padding. Restore them on every exit path of this function (texTransform* are rebuilt by + // UpdateTextureTransforms each draw and so do not leak). + struct TexcoordRoutingGuard + { + bool active = false; + float select0 = 0.0f; + float select3 = 0.0f; + float select2_0 = 0.0f; + float select2_1 = 0.0f; + float source[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float projected0 = 0.0f; + float projected1 = 0.0f; + ~TexcoordRoutingGuard() + { + if (!active) + { + return; + } + g_draw.texcoordSelect[0] = select0; + g_draw.texcoordSelect[3] = select3; + g_draw.texcoordSelect2[0] = select2_0; + g_draw.texcoordSelect2[1] = select2_1; + for (unsigned s = 0; s < 4; ++s) + { + g_draw.texcoordSource[s] = source[s]; + } + g_draw.texProjected[0] = projected0; + g_draw.texProjected[1] = projected1; + } + } texcoordRoutingGuard; + + if (is2D) + { + texcoordRoutingGuard.active = true; + texcoordRoutingGuard.select0 = g_draw.texcoordSelect[0]; + texcoordRoutingGuard.select3 = g_draw.texcoordSelect[3]; + texcoordRoutingGuard.select2_0 = g_draw.texcoordSelect2[0]; + texcoordRoutingGuard.select2_1 = g_draw.texcoordSelect2[1]; + for (unsigned s = 0; s < 4; ++s) + { + texcoordRoutingGuard.source[s] = g_draw.texcoordSource[s]; + } + texcoordRoutingGuard.projected0 = g_draw.texProjected[0]; + texcoordRoutingGuard.projected1 = g_draw.texProjected[1]; + + // Render2DClass-authored quads only carry UV0 in screen space. + // Stale world texture coordinate routing/transform state can + // otherwise redirect video/UI samples into black atlas padding. + g_draw.texcoordSelect[0] = 0.0f; + g_draw.texcoordSelect[3] = 0.0f; + g_draw.texcoordSelect2[0] = 0.0f; + g_draw.texcoordSelect2[1] = 0.0f; + for (unsigned stage = 0; stage < 4; ++stage) + { + g_draw.texcoordSource[stage] = 0.0f; + } + g_draw.texProjected[0] = 0.0f; + g_draw.texProjected[1] = 0.0f; + SetIdentityTextureTransform(g_draw.texTransform0, g_draw.texTransform1); + SetIdentityTextureTransform(g_draw.tex1Transform0, g_draw.tex1Transform1); + SetIdentityTextureTransform(g_draw.tex2Transform0, g_draw.tex2Transform1); + g_draw.texTransform0Z[0] = 0.0f; + g_draw.texTransform0Z[1] = 0.0f; + g_draw.texTransform0Z[2] = 1.0f; + g_draw.texTransform0Z[3] = 0.0f; + g_draw.tex1TransformZ[0] = 0.0f; + g_draw.tex1TransformZ[1] = 0.0f; + g_draw.tex1TransformZ[2] = 1.0f; + g_draw.tex1TransformZ[3] = 0.0f; + } + const bool skipSortedMaterialRecapture = + g_views.inSortFlush + && g_views.sortedBatchMaterialCaptured + && !DisableSortedMaterialRecaptureSkip(); + if (!g_draw.explicitMaterialState && !skipSortedMaterialRecapture) + { + CaptureMaterialStateForBgfx(g_draw.sourceMaterial); + } + g_views.sortedBatchMaterialCaptured = false; + if (IsSortedMaterialDecal(GetEffectiveDrawState())) + { + // Terrain rendering leaves this flag set until reset by the shader + // manager. Sorted material decals use the fixed-function TSS path and + // must not inherit the terrain pixel-shader branch. + g_draw.texcoordSelect[1] = 0.0f; + } + // TheSuperHackers @bugfix bobtista 30/04/2026 Z-bias must be picked + // BEFORE UploadMaterialUniforms — that helper is what actually pushes + // u_zBias to the GPU. Setting g_draw.zBias afterward leaves the uniform + // at the previous (or default) value and defeats the whole fix. + { + g_draw.zBias[0] = static_cast(g_draw.zBiasUnits) * kZBiasPerUnit; + TraceLegacyZBiasTranslation(); + const bool applySubmittedNormalBias = ShouldApplySubmittedNormalBias(routeState); + const bool normalBiasFromGeometry = + !is2D + && (g_draw.normalBias[0] != 0.0f + || (g_draw.activeVertexNormalBias && applySubmittedNormalBias) + || IsSneakAttackCoplanarSurface()); + g_draw.zBias[1] = normalBiasFromGeometry + ? ((g_draw.normalBias[0] < 0.02f) ? 0.02f : g_draw.normalBias[0]) + : 0.0f; + // TheSuperHackers @feature bobtista 17/06/2026 Mark opaque/alpha-tested objects (units, + // structures, props) as sun-shadow RECEIVERS via u_zBias.z so the fragment shader darkens + // them when they stand in a cast shadow. Mirrors the caster set (engine view, writes depth, + // not blended), so blended effects/particles are excluded and cannot be wrongly darkened. + // Terrain and ground decals already receive via their own shader paths; this adds the + // object path. Self-shadowing is avoided by the normal-offset bias in sampleSunShadow, + // which uses the object's real vertex normal. + static const bool s_noPropShadows = (GgcFlags::Enabled(GgcFlag_NoPropShadows)); + const bool sunShadowReceiver = + !s_noPropShadows + && g_frame.shadowActive + && submitView == kBgfxEngineView + && !is2D + && (routeState & BGFX_STATE_WRITE_Z) != 0 + && (routeState & BGFX_STATE_BLEND_MASK) == 0; + g_draw.sunShadowReceive[0] = sunShadowReceiver ? 1.0f : 0.0f; + // TheSuperHackers @bugfix bobtista 02/05/2026 Sorted material decals + // (alpha-blend + DEPTH_WRITE_OFF + postdetail-alpha) such as the USA + // strategy center floor emblem (ABBTCMDHQS.SWORD) sit coplanar with + // an opaque sub-mesh. DX8 LEQUAL wins ties; bgfx's projection rounds + // slightly so the decal ends up just behind the slab and is occluded. + // Pull the NDC z slightly toward the camera so coplanar decals beat + // the surface they sit on, but clamp the pull tightly so the ground + // decal still loses to vehicles sitting above it. + ClampSortedMaterialDecalZBias(); + } + UpdateProjectedDecalModeForCurrentDraw(); + const uint64_t earlyState = GetEffectiveDrawState(); + if (IsMultiplicativeBlend(earlyState) + && (earlyState & BGFX_STATE_WRITE_Z) == 0 + && (earlyState & BGFX_STATE_DEPTH_TEST_MASK) != BGFX_STATE_DEPTH_TEST_EQUAL + && !IsEffectiveProjectedShadowDraw()) + { + LogBgfxRevealDraw("submit-engine", submitView, + polygon_count, vertex_count, earlyState, + "skip-multiply"); + g_stats.skippedDraws++; + bgfx::discard(BGFX_DISCARD_ALL); + return; + } + // TheSuperHackers @bugfix bobtista 17/07/2026 Shroud detection must run BEFORE the + // guarded uniform uploads below: it can reassign submitView to the shroud overlay + // view, and FrameConstUniformGuard keys on the view. Keying the uploads to the + // pre-reassignment engine view let elision skip uploads the shroud view never saw + // (shroud overlay inheriting lit state) and let later engine draws reuse guard + // entries whose values actually played in the shroud view. + // Detect shroud pass: legacy setup 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 = g_draw.depthFunc; + const unsigned stg = 0; + unsigned tci = g_draw.texcoordIndex[stg]; + float shroudParams[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + // Projected terrain receivers and cloud/noise stages also use + // TCI_CAMERASPACEPOSITION. Only the actual shroud overlay uses the + // stage-0 multiplicative sprite path that needs this world-space + // shortcut; projector receivers must keep the normal texture matrix. + const bool explicitShroudPass = + g_views.shroudTexturePassActive && g_views.shroudTexturePassStage == stg; + const bool legacyShroudSignature = + g_draw.tssOps0[0] > 2.5f && g_draw.tssOps0[0] < 3.5f; + if (explicitShroudPass) + { + // Shroud setup is explicit backend state. It must not inherit a + // stale stencil test from previous shadow/player-color passes. + g_draw.stencilEnabled = false; + } + if (depthFunc == static_cast(RB_CMP_EQUAL) + && (explicitShroudPass || (!g_draw.stencilEnabled && legacyShroudSignature))) + { + if (tci & kTexcoordGenCameraPosition) + { + shroudDetected = true; + g_draw.texcoordSelect[2] = 1.0f; + if (g_draw.shroudTextureParamsValid) + { + std::memcpy(shroudParams, g_draw.shroudTextureParams, sizeof(shroudParams)); + } + else + { + // Legacy fallback for call sites that only expose the cached + // texture matrix. Dedicated shroud setup paths provide + // direct world-space params; decomposing camera-space + // matrices is fragile across compatibility layers. + auto texMtx = MakeIdentityLegacyCacheMatrix(); + FixedFunctionState::Transform_Matrix(kTextureTransformStage0 + stg, texMtx); + auto viewMtx = MakeIdentityLegacyCacheMatrix(); + FixedFunctionState::Transform_Matrix(kTransformView, viewMtx); + auto ts = MakeIdentityLegacyCacheMatrix(); + 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]; + } + } + } + shroudParams[0] = (ts.m[0][0] != 0.0f) ? ts.m[3][0] / ts.m[0][0] : 0.0f; + shroudParams[1] = (ts.m[1][1] != 0.0f) ? ts.m[3][1] / ts.m[1][1] : 0.0f; + shroudParams[2] = ts.m[0][0]; + shroudParams[3] = ts.m[1][1]; + } + if (bgfx::isValid(g_uniforms.uShroudParams)) + { + bgfx::setUniform(g_uniforms.uShroudParams, shroudParams); + } + } + } + if (!shroudDetected) + { + g_draw.texcoordSelect[2] = 0.0f; + } + else if (ShouldAllowBgfxDiagnosticDrawOverrides() + && GgcFlags::Enabled(GgcFlag_BgfxSkipShroudOverlay)) + { + bgfx::discard(BGFX_DISCARD_ALL); + return; + } + const bool delayedObjectShroudPass = + shroudDetected + && g_views.objectShroudTexturePassActive + && submitView == kBgfxEngineView; + if (delayedObjectShroudPass) + { + submitView = kBgfxShroudOverlayView; + } + g_draw.delayedObjectShroudPass = delayedObjectShroudPass; + if ((tci & kTexcoordGenCameraPosition) + || depthFunc == static_cast(RB_CMP_EQUAL) + || shroudDetected) + { + LogBgfxShroudPass("shroud-candidate", submitView, polygon_count, depthFunc, tci, shroudDetected, shroudParams); + } + } + + { + uint64_t blendState = earlyState; + g_draw.texcoordSelect2[3] = IsAnyAdditiveBlend(blendState) + ? 1.0f + : 0.0f; + UpdateAlphaMaskAndSortedModes(blendState); + } + UploadMaterialUniforms(submitView); + // TheSuperHackers @bugfix bobtista 17/07/2026 Skip the fixed-function light fallback + // during the sorted flush: the replay has authoritatively restored the captured light + // state, and a node captured under a zero-light environment must stay unlit instead of + // inheriting the current frame's mirrored lights. Point lights (Type 1) are also skipped + // here since this directional fallback would leave stale point parameters in lightParams. + if (g_draw.lightDirs[0][3] < 0.5f && !g_views.inSortFlush) + { + const auto &rs = FixedFunctionState::Render_State(); + for (int i = 0; i < 4; ++i) + { + if (rs.LightEnable[i] && rs.Lights[i].Type != 1) + { + const auto &dl = rs.Lights[i]; + g_draw.lightDirs[i][0] = -dl.Direction.x; + g_draw.lightDirs[i][1] = -dl.Direction.y; + g_draw.lightDirs[i][2] = -dl.Direction.z; + g_draw.lightDirs[i][3] = 1.0f; + g_draw.lightColors[i][0] = dl.Diffuse.r; + g_draw.lightColors[i][1] = dl.Diffuse.g; + g_draw.lightColors[i][2] = dl.Diffuse.b; + g_draw.lightColors[i][3] = 1.0f; + g_draw.lightAmbients[i][0] = dl.Ambient.r; + g_draw.lightAmbients[i][1] = dl.Ambient.g; + g_draw.lightAmbients[i][2] = dl.Ambient.b; + g_draw.lightAmbients[i][3] = 1.0f; + g_draw.lightParams[i][0] = 0.0f; + g_draw.lightParams[i][1] = 0.0f; + g_draw.lightParams[i][2] = 0.0f; + g_draw.lightParams[i][3] = 1.0f; + } + } + } + const bool forceUnlitLighting = ShouldForceUnlitForBakedColorDraw(earlyState); + const bool fixedFunctionLightInputsNeeded = + g_draw.lightingEnabled[0] > 0.5f + && !forceUnlitLighting; + UploadLightUniforms(fixedFunctionLightInputsNeeded, submitView); + if (bgfx::isValid(g_uniforms.uSceneAmbient)) + { + static FrameConstUniformGuard s_sceneAmbientGuard; + if (ShouldUploadFrameConstUniform(s_sceneAmbientGuard, submitView, + g_draw.sceneAmbient, + sizeof(g_draw.sceneAmbient))) + { + 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/alpha + // particle and sorted-decal draws that bake intensity or final color into vertex diffuse + // or recolored tex0; the lit branch would otherwise ignore that baking. + const float forced[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + const float * lightingEnabledValue = forceUnlitLighting ? forced : g_draw.lightingEnabled; + static FrameConstUniformGuard s_lightingEnabledGuard; + if (ShouldUploadFrameConstUniform(s_lightingEnabledGuard, submitView, + lightingEnabledValue, sizeof(forced))) + { + bgfx::setUniform(g_uniforms.uLightingEnabled, lightingEnabledValue); + } + } + + if (bgfx::isValid(g_uniforms.uTexcoordSelect)) + { + static FrameConstUniformGuard s_texcoordSelectGuard; + if (ShouldUploadFrameConstUniform(s_texcoordSelectGuard, submitView, + g_draw.texcoordSelect, sizeof(g_draw.texcoordSelect))) + { + bgfx::setUniform(g_uniforms.uTexcoordSelect, g_draw.texcoordSelect); + } + } + + // TheSuperHackers @performance bobtista 10/07/2026 Sorted packet submits + // carry a pipeline-state word resolved at capture time. With the + // resolved-pipeline flag on (and no trace comparison requested) the live + // per-draw derivation is skipped entirely; under trace both are computed + // and divergences are reported once per differing bit pattern. + uint64_t state; + { + static const bool s_useResolved = GgcFlags::Enabled(GgcFlag_BgfxSortedResolvedPipeline); + static const bool s_traceResolved = GgcFlags::Enabled(GgcFlag_Trace); + const bool haveResolved = g_draw.sortedResolvedStateValid; + if (haveResolved && s_useResolved && !s_traceResolved) + { + state = g_draw.sortedResolvedState; + } + else + { + state = ComputeFinalDrawState(triangle_strip); + if (haveResolved && s_traceResolved && state != g_draw.sortedResolvedState) + { + static uint64_t s_reportedDiffMasks[8] = {}; + static int s_reportedDiffCount = 0; + const uint64_t diff = state ^ g_draw.sortedResolvedState; + bool seen = false; + for (int i = 0; i < s_reportedDiffCount; ++i) + { + if (s_reportedDiffMasks[i] == diff) + { + seen = true; + break; + } + } + if (!seen && s_reportedDiffCount < 8) + { + s_reportedDiffMasks[s_reportedDiffCount++] = diff; + std::fprintf(stderr, + "[ggc] resolved-pipeline mismatch: live=0x%llx resolved=0x%llx diff=0x%llx tex=%s\n", + static_cast(state), + static_cast(g_draw.sortedResolvedState), + static_cast(diff), + TextureDebugName(g_draw.sourceTextures[0])); + std::fflush(stderr); + } + } + if (haveResolved && s_useResolved) + { + state = g_draw.sortedResolvedState; + } + } + } + if (g_views.waterOverrideActive) + { + g_views.waterOverrideActive = false; + } + LogBgfxSortedMaterialDecal("submit-engine", submitView, + polygon_count, vertex_count, state); + LogBgfxEffectSubmit("submit-engine", submitView, + polygon_count, vertex_count, state, "pre-skip"); + LogBgfxRevealDraw("submit-engine", submitView, + polygon_count, vertex_count, state, "pre-skip"); + if (ShouldAllowBgfxDiagnosticDrawOverrides() + && GgcFlags::Enabled(GgcFlag_BgfxSkipRevealGrid) + && IsRevealGridTexture(g_draw.sourceTextures[0])) + { + g_stats.skippedDraws++; + bgfx::discard(BGFX_DISCARD_ALL); + return; + } + + if (ShouldSkipHiddenMissingTextureDraw(state)) + { + LogBgfxRevealDraw("submit-engine", submitView, + polygon_count, vertex_count, state, "skip-missing"); + g_stats.skippedDraws++; + bgfx::discard(BGFX_DISCARD_ALL); + return; + } + + if (g_views.shadowVolumeActive && bgfx::isValid(g_device.shadowVolumeProgram)) + { + if (LegacyStencilShadowsEnabled()) + { + if (BgfxTwoSidedStencilVolumes() + && (g_draw.stencilPassOpBits == BGFX_STENCIL_OP_PASS_Z_DECR + || g_draw.stencilPassOpBits == BGFX_STENCIL_OP_PASS_Z_DECRSAT)) + { + g_stats.skippedDraws++; + bgfx::discard(BGFX_DISCARD_ALL); + return; + } + uint64_t volumeState = BgfxShadowVolumeDepthState() | (state & BGFX_STATE_PT_MASK); + const unsigned shadowCullModeBits = BgfxShadowCullModeBits(); + if (!BgfxTwoSidedStencilVolumes() && shadowCullModeBits == 1) + { + volumeState |= BGFX_STATE_CULL_CW; + } + else if (!BgfxTwoSidedStencilVolumes() && shadowCullModeBits == 2) + { + volumeState |= BGFX_STATE_CULL_CCW; + } + bgfx::setState(volumeState); + if (BgfxTwoSidedStencilVolumes()) + { + const uint32_t common = 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; + if (BgfxSwapTwoSidedStencilVolumeOps()) + { + bgfx::setStencil(common | BGFX_STENCIL_OP_PASS_Z_DECRSAT, + common | BGFX_STENCIL_OP_PASS_Z_INCR); + } + else + { + bgfx::setStencil(common | BGFX_STENCIL_OP_PASS_Z_INCR, + common | BGFX_STENCIL_OP_PASS_Z_DECRSAT); + } + } + else + { + bgfx::setStencil(BuildCurrentStencilState()); + } + BindShadowVolumeBiasUniform(); + bgfx::submit(BgfxShadowVolumeSubmitView(), g_device.shadowVolumeProgram); + g_stats.shadowVolumeSubmits++; + LogBgfxStencilShadowEvent("side-wall-submit", nullptr, + polygon_count, vertex_count); + return; + } + LogBgfxStencilShadowEvent("side-wall-skip", "legacy-stencil-disabled", + polygon_count, vertex_count); + g_stats.skippedDraws++; + bgfx::discard(BGFX_DISCARD_ALL); + return; + } + + const bool writesDepth = (state & BGFX_STATE_WRITE_Z) != 0; + const bool isBlended = (state & BGFX_STATE_BLEND_MASK) != 0; + const bool isAlphaTested = g_overrides.atestActive ? (g_overrides.atestFunc > 0.0f) : g_draw.atestEnabled; + // Projected decals are receiver-space overlays, not physical casters. + const bool isProjectedDecal = + GetEffectiveProjectedDecalModeForCurrentDraw() != RB_PROJECTED_DECAL_NONE; + const bool isSceneDepthCaster = + submitView == kBgfxEngineView + && !g_views.overlay2DActive + && writesDepth + && !isBlended + && !isAlphaTested; + // TheSuperHackers @feature bobtista 16/06/2026 The sun shadow map also accepts alpha-tested + // cutout geometry (infantry, foliage) so they cast a real silhouette; the caster shader + // re-applies the alpha test. Blended effects/particles and projected decals stay out. + // Off-camera casters reach this path because MeshClass::Render keeps meshes inside the + // sun-shadow cull box (see mesh.cpp), so a caster that has scrolled off the screen still casts + // into the visible ground. + // Projected decals are visual receiver-space overlays, not physical casters. Mirroring them + // into the sun shadow map would turn effects/ground marks into real occluders. + const bool isShadowCaster = + submitView == kBgfxEngineView + && !g_views.overlay2DActive + && writesDepth + && !isBlended + && !isProjectedDecal; + + // Sorted translucent/effect draws (particles, lasers, material decals) + // are submitted after the world pass and should not inherit stale stencil + // state from shroud/player-color/shadow passes. Keeping stencil active + // here clips effects such as the particle-cannon beam against buildings. + const bool sortedTranslucentEffect = submitView == kBgfxEngineSortView + && isBlended; + const bool sortedMaterialDecal = submitView == kBgfxEngineSortView + && (IsSortedMaterialDecal(state) + || IsSortedAlphaDepthDecal(state) + || IsSortedRotorBlur(state)); + const bool localModelMaterialDecal = + g_views.inSortFlush + && IsCommandCenterEmblemTexture(g_draw.sourceTextures[0]) + && IsSortedMaterialDecal(state); + // Sort-flushed material decals (command-center driveway emblems, upgrade + // floor marks) are ordinary alpha decals. The wrapper can still have + // stencil state cached from shroud/player-color passes; applying it here + // clips revealed decals out completely. + const bool applyStencil = g_draw.stencilEnabled + && submitView != kBgfxUIView + && !sortedTranslucentEffect + && !sortedMaterialDecal + && !localModelMaterialDecal; + + bgfx::setState(state); + if (applyStencil) + { + bgfx::setStencil(BuildCurrentStencilState()); + } + + BindSoftParticleDepth(submitView == kBgfxEngineSortView + && isBlended + && IsSoftParticleCandidate(state)); + + // Tree / grass sway shader takes over the program slot + // and uploads its own constants when active. Otherwise fall back + // 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); + } + } + // TheSuperHackers @bugfix bobtista 10/07/2026 Bind the sorted texture-array + // page on the live sorted-pool submit path. Without this, merged runs were + // submitted with the uber program and the representative node's stage-0 + // texture, collapsing every absorbed node's texture onto one. + if (g_views.inSortFlush && g_draw.sortedArrayPage >= 0) + { + const bgfx::TextureHandle pageTex = BgfxSortedTextureArrayPageHandle(g_draw.sortedArrayPage); + if (bgfx::isValid(pageTex) && bgfx::isValid(g_device.sortedArrayProgram)) + { + bgfx::setTexture(kBgfxSortedArraySamplerStage, g_uniforms.sTexArray, + pageTex, + g_draw.samplerFlags[0] | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + program = g_device.sortedArrayProgram; + } + } + if (program.idx == g_device.uberProgram.idx) + { + // TheSuperHackers @performance bobtista Route plain uber draws to the frame-constant + // variant: it reads the global sun/point-shadow + scene-ambient constants from a data + // texture (updated once per frame) instead of per-draw uniforms, shrinking the per-draw + // constant buffer so heavy scenes stay under bgfx's fixed 8MB Metal uniform arena. + if (UniformFrameTextureEnabled() + && bgfx::isValid(g_device.uberFrameConstProgram) + && bgfx::isValid(g_device.frameConstTexture)) + { + static uint32_t s_frameConstUpdatedFrame = 0xffffffffu; + if (s_frameConstUpdatedFrame != g_stats.frameIndex) + { + s_frameConstUpdatedFrame = g_stats.frameIndex; + PackAndUploadFrameConstTexture(); + } + bgfx::setTexture(kBgfxFrameConstSamplerStage, g_uniforms.sFrameConst, + g_device.frameConstTexture, + BGFX_SAMPLER_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + program = g_device.uberFrameConstProgram; + } + } + bgfx::submit(submitView, program); + LogBgfxEffectSubmit("submit-engine", submitView, + polygon_count, vertex_count, state, "submit"); + LogBgfxRevealDraw("submit-engine", submitView, + polygon_count, vertex_count, state, "submit"); + g_stats.baseSubmits++; + + const bool hasVB = g_draw.useTransientVB + || (g_draw.useStaticVB && bgfx::isValid(g_draw.staticVB)) + || bgfx::isValid(g_draw.vb); + const bool casterToDepth = isSceneDepthCaster + && bgfx::isValid(g_draw.drawIsInstanced ? g_device.sceneDepthInstancedProgram : g_device.sceneDepthProgram) + && bgfx::isValid(g_device.sceneReadableDepthFB); + bool casterToShadow = isShadowCaster + && g_frame.shadowActive + && bgfx::isValid(g_device.shadowMapFB) + && bgfx::isValid(g_draw.drawIsInstanced ? g_device.shadowCasterInstancedProgram : g_device.shadowCasterProgram); + // TheSuperHackers @feature bobtista 18/06/2026 The spinning Chinook rotor renders as an + // alpha-blended "rotor blur" disc in the sorted pass, so it is excluded from the normal caster + // set (opaque, engine view). Cast it into the sun shadow map anyway, as an alpha-tested + // silhouette of the blur texture, so the rotor throws a disc shadow on the ground like retail. + static const bool s_noRotorShadow = (GgcFlags::Enabled(GgcFlag_NoRotorShadow)); + const bool rotorShadowCaster = !s_noRotorShadow + && IsSortedRotorBlur(state) + && g_frame.shadowActive + && bgfx::isValid(g_device.shadowMapFB) + && bgfx::isValid(g_device.shadowCasterProgram); + if (rotorShadowCaster) + { + casterToShadow = true; + } + const bool pointShadowArmed = g_draw.pointShadowParams[0] >= 0.5f + && bgfx::isValid(g_device.pointShadowFB) + && bgfx::isValid(g_draw.drawIsInstanced ? g_device.shadowCasterInstancedProgram : g_device.shadowCasterProgram); + // TheSuperHackers @bugfix bobtista 15/07/2026 Terrain must not cast into either point-shadow + // map: these lights sit close to the ground, so its self-comparison fails at grazing angles + // across the whole frustum and prints the map footprint as a large dark patch. Alpha-tested + // cutouts (foliage, infantry) DO cast into the primary map - the beam's moving unit/tree + // shadows are the heart of the effect, and the primary light hovers over the beam impact, + // far enough that canopy self-shadow speckle stays invisible. They stay excluded from the + // transient flash map (slot 2): flash pulses spawn right beside trees, where per-texel leaf + // self-shadowing speckled canopies with isolated lit pixels. + const bool drawIsTerrain = (g_draw.texcoordSelect[1] > 0.5f); + const bool casterToPointShadow = pointShadowArmed && !drawIsTerrain + && (isSceneDepthCaster || isShadowCaster || rotorShadowCaster); + const bool pointShadow2Armed = g_draw.pointShadow2Params[0] >= 0.5f + && bgfx::isValid(g_device.pointShadow2FB) + && bgfx::isValid(g_draw.drawIsInstanced ? g_device.shadowCasterInstancedProgram : g_device.shadowCasterProgram); + // Cutouts stay OUT of the flash map: re-submitting them as casters there corrupts their + // main-view draw with flickering bright edge texels (blotches), and their per-texel + // self-shadow made receiving speckle historically. Solid objects carry the flash shadows. + // TheSuperHackers @bugfix bobtista 16/07/2026 Slot 2 casts real shadows again now that its + // view (25) is in the per-frame view order. Without that, view 25 was deferred to a 1x1 + // viewport so its map stayed black (read as fully occluded = a dark circle) and stray + // caster pixels leaked to the backbuffer as white silhouettes. Cutouts stay excluded (their + // per-texel self-shadowing speckles canopies); terrain never casts (grazing-angle acne). + const bool casterToPointShadow2 = pointShadow2Armed && !drawIsTerrain && !isAlphaTested + && (isSceneDepthCaster || isShadowCaster || rotorShadowCaster); + // Diagnostic: GGC_LOG_SHADOW_CASTER_AUDIT=1 dumps each world draw that could feed the + // sun shadow map, including excluded candidates. Use GGC_LOG_SHADOW_CASTER_START/END + // to bracket the particle-cannon firing window without filling stderr for the whole run. + static const bool s_logShadowCasterAudit = (GgcFlags::Enabled(GgcFlag_LogShadowCasterAudit)); + if (s_logShadowCasterAudit) + { + static const int s_logShadowCasterStart = + (GgcFlags::Enabled(GgcFlag_LogShadowCasterStart)) + ? std::atoi(GgcFlags::StringValue(GgcFlag_LogShadowCasterStart)) : 0; + static const int s_logShadowCasterEnd = + (GgcFlags::Enabled(GgcFlag_LogShadowCasterEnd)) + ? std::atoi(GgcFlags::StringValue(GgcFlag_LogShadowCasterEnd)) : 0x7fffffff; + const int frame = static_cast(g_stats.frameIndex); + const bool inAuditWindow = frame >= s_logShadowCasterStart && frame <= s_logShadowCasterEnd; + const bool auditCandidate = + ((submitView == kBgfxEngineView + && !g_views.overlay2DActive + && writesDepth + && !isBlended + && hasVB) + || (rotorShadowCaster && hasVB)); + if (inAuditWindow && auditCandidate) + { + const TextureBaseClass * at = FixedFunctionState::Render_State().Textures[0]; + const char * an = (at != nullptr && at->Get_Texture_Name().str() != nullptr + && at->Get_Texture_Name().str()[0] != '\0') + ? at->Get_Texture_Name().str() : ""; + const unsigned texW = (at != nullptr) ? at->Get_Width() : 0; + const unsigned texH = (at != nullptr) ? at->Get_Height() : 0; + std::fprintf(stderr, + "[ggc-shadowcaster] f=%u cast=%d view=%u polys=%u verts=%u idx=%u " + "world=(%.1f,%.1f,%.1f) tex=%s texSize=%ux%u atest=%d blend=%d " + "proj=%d terrain=%d high=%d untex=%d rotor=%d transientVB=%d transientIB=%d " + "tcsel=(%.1f,%.1f) state=0x%llx\n", + g_stats.frameIndex, casterToShadow ? 1 : 0, submitView, + polygon_count, vertex_count, indexCount, + worldMtx[12], worldMtx[13], worldMtx[14], + an, texW, texH, isAlphaTested ? 1 : 0, isBlended ? 1 : 0, + static_cast(GetEffectiveProjectedDecalModeForCurrentDraw()), + (g_draw.texcoordSelect[1] > 0.5f) ? 1 : 0, + (worldMtx[14] > 100.0f) ? 1 : 0, + (at == nullptr) ? 1 : 0, + rotorShadowCaster ? 1 : 0, g_draw.useTransientVB ? 1 : 0, + g_draw.useTransientIB ? 1 : 0, + g_draw.texcoordSelect[0], g_draw.texcoordSelect[1], + static_cast(state)); + } + } + if ((casterToDepth || casterToShadow || casterToPointShadow || casterToPointShadow2) && hasVB) + { + // TheSuperHackers @feature bobtista 27/04/2026 Duplicate opaque + // non-alpha-tested world geometry into a sampleable R32F scene-depth target. + // TheSuperHackers @feature bobtista 16/06/2026 The same geometry feeds the sun + // shadow cascades from the light's POV. Alpha-tested cutouts (infantry, foliage) + // are excluded from the scene-depth target but still cast into the shadow map via + // the alpha-aware caster shader. + if (g_draw.useTransientVB) + { + bgfx::setVertexBuffer(0, &g_draw.transientVB, + static_cast(g_draw.ibOffset), + bindVertexCount); + } + else + { + if (g_draw.useStaticVB) + { + bgfx::setVertexBuffer(0, g_draw.staticVB, + static_cast(g_draw.ibOffset), + bindVertexCount); + } + else + { + bgfx::setVertexBuffer(0, g_draw.vb, + static_cast(g_draw.ibOffset), + bindVertexCount); + } + } + if (g_draw.useTransientIB) + { + bgfx::setIndexBuffer(&g_draw.transientIB, + start_index, + indexCount); + } + else + { + if (g_draw.useStaticIB) + { + bgfx::setIndexBuffer(g_draw.staticIB, + start_index, + indexCount); + } + else + { + bgfx::setIndexBuffer(g_draw.ib, + start_index, + indexCount); + } + } + bgfx::setTransform(worldMtx); + const uint64_t depthState = + BGFX_STATE_WRITE_RGB + | BGFX_STATE_WRITE_Z + | BGFX_STATE_DEPTH_TEST_LESS + | (state & BGFX_STATE_CULL_MASK) + | (state & BGFX_STATE_PT_MASK); + bgfx::setState(depthState); + // The alpha-aware shadow caster shader samples the base texture and re-applies + // the draw's alpha test; bind both so cutout geometry casts its real silhouette. + // Opaque draws pass an inactive test (y = 0) and skip the discard. Preserved + // across the cascade submits via BGFX_DISCARD_NONE. + if (casterToShadow || casterToPointShadow || casterToPointShadow2) + { + bgfx::TextureHandle baseTex = g_draw.tex[0]; + if (!bgfx::isValid(baseTex)) + { + baseTex = g_device.defaultWhiteTexture; + } + if (bgfx::isValid(baseTex) && bgfx::isValid(g_uniforms.sTex0)) + { + bgfx::setTexture(0, g_uniforms.sTex0, baseTex, GetCurrentStageSamplerFlags(0)); + } + if (bgfx::isValid(g_uniforms.uAtestParams)) + { + float shadowAtest[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + if (isAlphaTested) + { + shadowAtest[0] = g_overrides.atestActive ? g_overrides.atestRef : g_draw.atestRef; + shadowAtest[1] = 1.0f; + } + else if (rotorShadowCaster) + { + // The rotor blur has no alpha test of its own; force a threshold so the caster + // shader discards the transparent corners and casts only the blur disc. The + // disc is intentionally faint - a spinning rotor is mostly air. + shadowAtest[0] = 0.12f; + shadowAtest[1] = 1.0f; + } + bgfx::setUniform(g_uniforms.uAtestParams, shadowAtest); + } + } + if (casterToDepth) + { + const uint8_t discard = (casterToShadow || casterToPointShadow || casterToPointShadow2) ? BGFX_DISCARD_NONE : BGFX_DISCARD_ALL; + // Instanced batches cast every instance in one submit via the instanced caster program; + // the instance buffer is consumed per submit, so re-bind it before each recast submit. + if (g_draw.drawIsInstanced) + { + bgfx::setInstanceDataBuffer(&g_draw.instanceBatch, 0, g_draw.instanceCount); + } + const bgfx::ProgramHandle depthProg = g_draw.drawIsInstanced + ? g_device.sceneDepthInstancedProgram : g_device.sceneDepthProgram; + bgfx::submit(kBgfxSceneDepthView, depthProg, 0, discard); + g_stats.sceneDepthSubmits++; + } + if (casterToPointShadow) + { + if (g_draw.drawIsInstanced) + { + bgfx::setInstanceDataBuffer(&g_draw.instanceBatch, 0, g_draw.instanceCount); + } + const bgfx::ProgramHandle pointProg = g_draw.drawIsInstanced + ? g_device.shadowCasterInstancedProgram : g_device.shadowCasterProgram; + // TheSuperHackers @feature bobtista 23/06/2026 Cast eligible world geometry into the + // perspective point-shadow map when a caster dynamic light is armed this frame. This is not + // limited to the sun-shadow caster set: solid buildings/vehicles can be ordinary depth + // casters even when they are not otherwise being submitted to the sun map, and the particle + // cannon beam still needs their silhouettes. + bgfx::submit(kBgfxPointShadowView, pointProg, 0, + (casterToShadow || casterToPointShadow2) ? BGFX_DISCARD_NONE : BGFX_DISCARD_ALL); + } + if (casterToPointShadow2) + { + if (g_draw.drawIsInstanced) + { + bgfx::setInstanceDataBuffer(&g_draw.instanceBatch, 0, g_draw.instanceCount); + } + const bgfx::ProgramHandle point2Prog = g_draw.drawIsInstanced + ? g_device.shadowCasterInstancedProgram : g_device.shadowCasterProgram; + bgfx::submit(kBgfxPointShadow2View, point2Prog, 0, + casterToShadow ? BGFX_DISCARD_NONE : BGFX_DISCARD_ALL); + } + if (casterToShadow) + { + // TheSuperHackers @refactor bobtista 18/06/2026 Submit the caster into the single + // camera-fit shadow view (the cascades were retired - see SetupSunShadowView). + if (g_draw.drawIsInstanced) + { + bgfx::setInstanceDataBuffer(&g_draw.instanceBatch, 0, g_draw.instanceCount); + } + const bgfx::ProgramHandle sunProg = g_draw.drawIsInstanced + ? g_device.shadowCasterInstancedProgram : g_device.shadowCasterProgram; + bgfx::submit(kBgfxShadowMapView, sunProg, 0, BGFX_DISCARD_ALL); + ++s_shadowCasterSubmitCount; + static int s_loggedAlphaCaster = 0; + if (BgfxDiagVerbose() && isAlphaTested && s_loggedAlphaCaster < 1) + { + std::fprintf(stderr, "[ggc] alpha-tested shadow caster submitted (atestRef=%.2f) " + "- infantry/foliage now cast real silhouettes\n", + g_overrides.atestActive ? g_overrides.atestRef : g_draw.atestRef); + s_loggedAlphaCaster = 1; + } + } + } +} +} + +// TheSuperHackers @refactor bobtista 10/07/2026 Single-call sorted-pool run +// submit. This is the packet-shaped entry the sorted flush uses on the shader +// pipeline; it applies the captured batch packet and hands the draw to the +// shared engine-submit core directly, without the Draw_Triangles wrapper and +// its skip-next handshake (only the retired dx8wrapper sorted path ever armed +// that flag for pool runs). The kill switch reverts the flush to the legacy +// call sequence. +bool BgfxBackend::Submit_Sorted_Packet(const RenderBackendSortedBatchState & packet, + unsigned int start_index, + unsigned int polygon_count, + unsigned int vertex_count, + int array_page) +{ + static const bool s_disabled = GgcFlags::Enabled(GgcFlag_BgfxDisableSortedPacketSubmit); + if (s_disabled || !g_device.initialized) + { + return false; + } + if (array_page >= 0) + { + Set_Sorted_Texture_Array_Page(array_page); + } + Apply_Sorted_Batch_State(packet); + g_draw.sortedResolvedState = packet.resolved_state; + g_draw.sortedResolvedStateValid = packet.resolved_state_valid; + { + PERF_TIME(PERF_SECT_DRAW_TRIANGLES); + if (DrawCallLog_Is_Active()) + { + const TextureBaseClass * tex0 = FixedFunctionState::Render_State().Textures[0]; + const char * tex_name = (tex0 != nullptr) ? tex0->Get_Texture_Name().str() : ""; + DrawCallLog_Record( + 4, polygon_count, vertex_count, + FixedFunctionState::Render_State().vertex_buffer_types[0], + FixedFunctionState::Render_State().index_buffer_type, + FixedFunctionState::Render_State().shader.Get_Bits(), + FixedFunctionState::Render_State().sorted_draw_flags, + tex_name); + } + if (g_triangleDrawEnabled) + { + SubmitEngineDraw(static_cast(start_index), + static_cast(polygon_count), + 0, + static_cast(vertex_count)); + } + } + g_draw.sortedResolvedStateValid = false; + if (array_page >= 0) + { + Set_Sorted_Texture_Array_Page(-1); + } + return true; +} + +// TheSuperHackers @refactor bobtista 11/07/2026 Single-call rigid mesh draw. +// Same operations the polygon renderer used to issue as two backend calls +// (index-base offset, then the indexed draw); one entry keeps the whole draw +// visible to the backend. The kill switch reverts to the legacy pair. +bool BgfxBackend::Submit_Rigid_Packet(int ib_base_offset, + unsigned int start_index, + unsigned int primitive_count, + unsigned int min_vertex_index, + unsigned int vertex_count, + bool triangle_strip) +{ + static const bool s_disabled = GgcFlags::Enabled(GgcFlag_BgfxDisableRigidPacketSubmit); + if (s_disabled || !g_device.initialized) + { + return false; + } + Set_Index_Buffer_Index_Offset(static_cast(ib_base_offset)); + if (triangle_strip) + { + Draw_Strip(static_cast(start_index), + static_cast(primitive_count), + static_cast(min_vertex_index), + static_cast(vertex_count)); + } + else + { + Draw_Triangles(static_cast(start_index), + static_cast(primitive_count), + static_cast(min_vertex_index), + static_cast(vertex_count)); + } + return true; +} + +void BgfxBackend::Draw_Triangles(unsigned short start_index, + unsigned short polygon_count, + unsigned short min_vertex_index, + unsigned short vertex_count) +{ + PERF_TIME(PERF_SECT_DRAW_TRIANGLES); + if (DrawCallLog_Is_Active()) { + const TextureBaseClass * tex0 = FixedFunctionState::Render_State().Textures[0]; + const char * tex_name = (tex0 != nullptr) ? tex0->Get_Texture_Name().str() : ""; + DrawCallLog_Record( + 4, polygon_count, vertex_count, + FixedFunctionState::Render_State().vertex_buffer_types[0], + FixedFunctionState::Render_State().index_buffer_type, + FixedFunctionState::Render_State().shader.Get_Bits(), + FixedFunctionState::Render_State().sorted_draw_flags, + tex_name); + } + // 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_views.skipNextSubmitEngineDraw) + { + g_views.skipNextSubmitEngineDraw = false; + return; + } + if (!g_triangleDrawEnabled) + { + if (g_device.initialized) + { + bgfx::discard(BGFX_DISCARD_ALL); + } + return; + } + SubmitEngineDraw(start_index, polygon_count, min_vertex_index, 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) +{ + PERF_TIME(PERF_SECT_DRAW_TRIANGLES); + (void)buffer_type; + if (g_views.skipNextSubmitEngineDraw) + { + g_views.skipNextSubmitEngineDraw = false; + return; + } + if (!g_triangleDrawEnabled) + { + if (g_device.initialized) + { + bgfx::discard(BGFX_DISCARD_ALL); + } + return; + } + SubmitEngineDraw(start_index, polygon_count, min_vertex_index, vertex_count); +} + +bool BgfxBackend::Is_Triangle_Draw_Enabled() const +{ + return g_triangleDrawEnabled; +} + +void BgfxBackend::Set_Triangle_Draw_Enabled(bool enable) +{ + g_triangleDrawEnabled = enable; +} + +// 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) +{ + if (g_views.skipNextSubmitEngineDraw) + { + g_views.skipNextSubmitEngineDraw = false; + return; + } + if (!g_triangleDrawEnabled) + { + return; + } + + SubmitEngineDraw(start_index, index_count, min_vertex_index, vertex_count, true); +} + +// -- Programmable pipeline compatibility ------------------------------------ + +bool BgfxBackend::Load_Legacy_Shader(const char * path, + const unsigned int * declaration, + unsigned int usage, + RenderBackendShaderKind kind, + unsigned long * handle) +{ + (void)path; + (void)declaration; + (void)usage; + (void)kind; + if (handle == nullptr) { + return false; + } + + *handle = AllocateLegacyShaderHandle(); + return true; +} + +bool BgfxBackend::Create_Vertex_Shader(const unsigned int * declaration, + const unsigned int * shader, + unsigned int usage, + unsigned long * handle) +{ + (void)declaration; + (void)shader; + (void)usage; + if (handle == nullptr) { + return false; + } + + *handle = AllocateLegacyShaderHandle(); + return true; +} + +bool BgfxBackend::Create_Pixel_Shader(const unsigned int * shader, + unsigned long * handle) +{ + (void)shader; + if (handle == nullptr) { + return false; + } + + *handle = AllocateLegacyShaderHandle(); + return true; +} + +bool BgfxBackend::Create_Legacy_Pixel_Shader(RenderBackendLegacyPixelShaderMode mode, + unsigned long * handle) +{ + if (handle == nullptr || mode == RB_LEGACY_PIXEL_SHADER_NONE) { + return false; + } + + *handle = AllocateLegacyShaderHandle(); + g_legacyPixelShaderModes[*handle] = mode; + return true; +} + +void BgfxBackend::Delete_Vertex_Shader(unsigned long vertex_shader) +{ + (void)vertex_shader; +} + +void BgfxBackend::Delete_Pixel_Shader(unsigned long pixel_shader) +{ + g_legacyPixelShaderModes.erase(pixel_shader); +} + +void BgfxBackend::Set_Vertex_Shader(unsigned long vertex_shader) +{ + (void)vertex_shader; +} + +void BgfxBackend::Set_Pixel_Shader(unsigned long pixel_shader) +{ + RenderBackendLegacyPixelShaderMode mode = RB_LEGACY_PIXEL_SHADER_NONE; + auto it = g_legacyPixelShaderModes.find(pixel_shader); + if (it != g_legacyPixelShaderModes.end()) + { + mode = it->second; + } + g_draw.legacyPixelShaderMode[0] = static_cast(mode); +} + +// =========================================================================== +// Asset-ingress resource creation +// =========================================================================== +// +// The returned RenderResource.id is a monotonically-increasing key into +// g_resourceRegistry.table; the entry holds the bgfx handle(s). Owner-backed resources +// still enter through the transitional *_Resource hooks and the older caches +// keyed by their owner objects. +// +namespace { + +unsigned __int64 AllocResourceId() +{ + const unsigned __int64 id = g_resourceRegistry.next_id++; + if (g_resourceRegistry.next_id == 0) { + // Roll-over guard — rarely hit; start back at 1 to avoid colliding + // with kInvalidRenderResource. + g_resourceRegistry.next_id = 1; + } + return id; +} + +bool IsCompressedTextureFormat(WW3DFormat format) +{ + return format == WW3D_FORMAT_DXT1 + || format == WW3D_FORMAT_DXT2 + || format == WW3D_FORMAT_DXT3 + || format == WW3D_FORMAT_DXT4 + || format == WW3D_FORMAT_DXT5; +} + +unsigned CompressedTextureBlockSize(WW3DFormat format) +{ + return format == WW3D_FORMAT_DXT1 ? 8 : 16; +} + +// Copy a MipSlice into tightly packed bgfx memory for updateTexture2D. +const bgfx::Memory * CopySliceToBgfxMemory(const TextureDesc & desc, const MipSlice & slice) +{ + if (slice.data == nullptr || slice.size_bytes == 0 || slice.width == 0 || slice.height == 0) { + return nullptr; + } + + const bool compressed = IsCompressedTextureFormat(desc.format); + const unsigned rows = compressed ? DXT_SurfaceRows(slice.height) : slice.height; + const unsigned expectedPitch = compressed + ? DXT_SurfacePitch(slice.width, CompressedTextureBlockSize(desc.format)) + : slice.width * Get_Bytes_Per_Pixel(desc.format); + if (rows == 0 || expectedPitch == 0) { + return nullptr; + } + + const unsigned sourcePitch = slice.pitch != 0 ? slice.pitch : expectedPitch; + const unsigned requiredSourceBytes = (rows - 1) * sourcePitch + expectedPitch; + if (slice.size_bytes < requiredSourceBytes) { + return nullptr; + } + + const unsigned uploadBytes = rows * expectedPitch; + const bgfx::Memory * mem = bgfx::alloc(uploadBytes); + const uint8_t * src = static_cast(slice.data); + uint8_t * dst = mem->data; + for (unsigned row = 0; row < rows; ++row) { + std::memcpy(dst, src, expectedPitch); + src += sourcePitch; + dst += expectedPitch; + } + return mem; +} + +RenderResource RegisterResourceEntry(const BgfxResourceEntry & entry) +{ + RenderResource rr; + rr.id = AllocResourceId(); + g_resourceRegistry.table[rr.id] = entry; + return rr; +} + +BgfxResourceEntry MakeVertexBufferResourceEntry(VertexBufferClass * owner) +{ + BgfxResourceEntry entry; + std::memset(&entry, 0, sizeof(entry)); + entry.kind = BGFX_RR_KIND_VB; + entry.vb = BGFX_INVALID_HANDLE; + entry.dvb = BGFX_INVALID_HANDLE; + entry.d3d_mirror = nullptr; + entry.owner = owner; + return entry; +} + +BgfxResourceEntry MakeIndexBufferResourceEntry(IndexBufferClass * owner) +{ + BgfxResourceEntry entry; + std::memset(&entry, 0, sizeof(entry)); + entry.kind = BGFX_RR_KIND_IB; + entry.ib = BGFX_INVALID_HANDLE; + entry.dib = BGFX_INVALID_HANDLE; + entry.d3d_mirror = nullptr; + entry.owner = owner; + return entry; +} + +bgfx::VertexBufferHandle CreateStaticVertexBufferFromInitialData(const BufferDesc & desc, + const void * initial_data) +{ + if (initial_data == nullptr + || desc.size_bytes == 0 + || desc.layout.fvf == 0 + || desc.layout.stride == 0 + || (desc.size_bytes % desc.layout.stride) != 0) + { + return BGFX_INVALID_HANDLE; + } + + FVFInfoClass fvf(desc.layout.fvf); + bgfx::VertexLayout layout; + if (!BuildBgfxLayoutForFVF(fvf, layout) || layout.getStride() != desc.layout.stride) + { + return BGFX_INVALID_HANDLE; + } + + bgfx::VertexBufferHandle h = bgfx::createVertexBuffer(bgfx::copy(initial_data, desc.size_bytes), layout); + return h; +} + +bgfx::IndexBufferHandle CreateStaticIndexBufferFromInitialData(const BufferDesc & desc, + const void * initial_data, + bool indices_are_32bit) +{ + const unsigned int indexSize = indices_are_32bit ? sizeof(uint32_t) : sizeof(uint16_t); + if (initial_data == nullptr + || desc.size_bytes == 0 + || (desc.size_bytes % indexSize) != 0) + { + return BGFX_INVALID_HANDLE; + } + + const uint64_t flags = indices_are_32bit ? BGFX_BUFFER_INDEX32 : BGFX_BUFFER_NONE; + bgfx::IndexBufferHandle h = bgfx::createIndexBuffer(bgfx::copy(initial_data, desc.size_bytes), flags); + return h; +} + +} // namespace + +bool BgfxBackend::Requires_Legacy_Buffer_Resources() const +{ + return false; +} + +RenderResource BgfxBackend::Create_Texture(const TextureDesc & desc) +{ + BgfxResourceEntry entry; + std::memset(&entry, 0, sizeof(entry)); + entry.kind = BGFX_RR_KIND_TEXTURE; + entry.d3d_mirror = nullptr; + entry.texture = BGFX_INVALID_HANDLE; + entry.fb = BGFX_INVALID_HANDLE; + entry.width = desc.width; + entry.height = desc.height; + + if (desc.is_render_target) { + const bgfx::TextureFormat::Enum colorFormat = + Resolve_Render_Target_Color_Format(desc.format); + bgfx::TextureHandle colorTex = bgfx::createTexture2D( + desc.width, desc.height, false, 1, colorFormat, + BGFX_TEXTURE_RT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + bgfx::TextureHandle depthTex = bgfx::createTexture2D( + desc.width, desc.height, false, 1, bgfx::TextureFormat::D24S8, + BGFX_TEXTURE_RT_WRITE_ONLY); + bgfx::TextureHandle attachments[2] = { colorTex, depthTex }; + entry.fb = bgfx::createFrameBuffer(2, attachments, true); + if (bgfx::isValid(entry.fb)) { + entry.texture = colorTex; + } else { + if (bgfx::isValid(colorTex)) { + bgfx::destroy(colorTex); + } + if (bgfx::isValid(depthTex)) { + bgfx::destroy(depthTex); + } + } + } else if (desc.mips != nullptr && desc.mip_count > 0) { + const bgfx::TextureFormat::Enum bgfxFmt = TranslateWW3DFormat(desc.format); + if (bgfxFmt != bgfx::TextureFormat::Unknown) { + const uint64_t texFlags = BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP; + entry.texture = bgfx::createTexture2D( + desc.width, desc.height, + desc.mip_count > 1, + 1, bgfxFmt, texFlags, nullptr); + if (bgfx::isValid(entry.texture)) { + bool uploadedAllLevels = true; + for (unsigned mip = 0; mip < desc.mip_count; ++mip) { + const MipSlice & slice = desc.mips[mip]; + const bgfx::Memory * mem = CopySliceToBgfxMemory(desc, slice); + if (mem == nullptr) { + uploadedAllLevels = false; + break; + } + bgfx::updateTexture2D( + entry.texture, + 0, + static_cast(mip), + 0, + 0, + slice.width, + slice.height, + mem); + } + if (!uploadedAllLevels) { + g_caches.deferredDestroys.push_back(entry.texture); + entry.texture = BGFX_INVALID_HANDLE; + } + } + } + } + + return RegisterResourceEntry(entry); +} + +RenderResource BgfxBackend::Create_Vertex_Buffer(const BufferDesc & desc, const void * initial_data) +{ + BgfxResourceEntry entry = MakeVertexBufferResourceEntry(nullptr); + entry.vb = CreateStaticVertexBufferFromInitialData(desc, initial_data); + return RegisterResourceEntry(entry); +} + +RenderResource BgfxBackend::Create_Index_Buffer(const BufferDesc & desc, const void * initial_data, bool indices_are_32bit) +{ + BgfxResourceEntry entry = MakeIndexBufferResourceEntry(nullptr); + entry.ib = CreateStaticIndexBufferFromInitialData(desc, initial_data, indices_are_32bit); + return RegisterResourceEntry(entry); +} + +void BgfxBackend::Destroy_Resource(RenderResource h) +{ + if (s_exitTeardownActive) + { + return; + } + auto it = g_resourceRegistry.table.find(h.id); + if (it == g_resourceRegistry.table.end()) { + return; + } + BgfxResourceEntry & entry = it->second; + + // Destroy bgfx side. + switch (entry.kind) { + case BGFX_RR_KIND_TEXTURE: + if (bgfx::isValid(entry.fb)) { + bgfx::destroy(entry.fb); + } else if (bgfx::isValid(entry.texture)) { + g_caches.deferredDestroys.push_back(entry.texture); + } + break; + case BGFX_RR_KIND_VB: + { + const VertexBufferClass *owner = static_cast(entry.owner); + g_caches.pendingVbRangeUploads.erase(owner); + if (g_draw.vbOwner == owner) + { + g_draw.vb = BGFX_INVALID_HANDLE; + g_draw.vbOwner = nullptr; + } + bool destroyedDynamic = false; + auto vbIt = g_caches.vb.find(owner); + if (vbIt != g_caches.vb.end()) + { + if (bgfx::isValid(vbIt->second.handle)) + { + if (bgfx::isValid(g_draw.vb) && g_draw.vb.idx == vbIt->second.handle.idx) + { + g_draw.vb = BGFX_INVALID_HANDLE; + } + // TheSuperHackers @bugfix bobtista 02/06/2026 Defer one frame: the + // engine frees this dynamic VB mid-frame, but a draw recorded earlier + // this frame may still reference the handle until bgfx::frame(). Immediate + // destroy here was the source of the per-frame "RefCount is 1 (expected 0)" + // warnings (the texture case above already defers for the same reason). + g_caches.deferredDestroyVB.push_back(vbIt->second.handle); + destroyedDynamic = bgfx::isValid(entry.dvb) && entry.dvb.idx == vbIt->second.handle.idx; + } + g_caches.vb.erase(vbIt); + } + if (bgfx::isValid(entry.dvb) && !destroyedDynamic) + { + if (bgfx::isValid(g_draw.vb) && g_draw.vb.idx == entry.dvb.idx) + { + g_draw.vb = BGFX_INVALID_HANDLE; + } + g_caches.deferredDestroyVB.push_back(entry.dvb); + } + DestroyStaticVertexResource(entry); + break; + } + case BGFX_RR_KIND_IB: + { + const IndexBufferClass *owner = static_cast(entry.owner); + g_caches.pendingIbRangeUploads.erase(owner); + if (g_draw.ibOwner == owner) + { + g_draw.ib = BGFX_INVALID_HANDLE; + g_draw.ibOwner = nullptr; + } + bool destroyedDynamic = false; + auto ibIt = g_caches.ib.find(owner); + if (ibIt != g_caches.ib.end()) + { + if (bgfx::isValid(ibIt->second.handle)) + { + if (bgfx::isValid(g_draw.ib) && g_draw.ib.idx == ibIt->second.handle.idx) + { + g_draw.ib = BGFX_INVALID_HANDLE; + } + // TheSuperHackers @bugfix bobtista 02/06/2026 Defer one frame; see the + // matching note in the VB case above. + g_caches.deferredDestroyIB.push_back(ibIt->second.handle); + destroyedDynamic = bgfx::isValid(entry.dib) && entry.dib.idx == ibIt->second.handle.idx; + } + g_caches.ib.erase(ibIt); + } + if (bgfx::isValid(entry.dib) && !destroyedDynamic) + { + if (bgfx::isValid(g_draw.ib) && g_draw.ib.idx == entry.dib.idx) + { + g_draw.ib = BGFX_INVALID_HANDLE; + } + g_caches.deferredDestroyIB.push_back(entry.dib); + } + DestroyStaticIndexResource(entry); + break; + } + case BGFX_RR_KIND_NONE: + default: + break; + } + + g_resourceRegistry.table.erase(it); +} + +// -- Transitional owner-backed resource hooks ------------------------------- + +RenderResource BgfxBackend::Register_Texture_Resource(TextureBaseClass * tex) +{ + if (tex == nullptr) { + return kInvalidRenderResource; + } + // Ensure the bgfx-side texture exists (peek+lock+upload from the legacy + // mirror that the legacy loader already created). The returned handle + // is owned by g_caches.texture (keyed on TextureBaseClass*), NOT by + // this registry entry — Release_Cached_Texture in the dtor queues it + // for deferred destroy. We leave entry.texture invalid so + // Destroy_Resource doesn't try to destroy the same handle twice. + if (tex->Is_Render_Target()) + { + Ensure_Render_Target_Framebuffer(tex->As_TextureClass()); + } + else + { + EnsureBgfxTexture(tex); + } + + BgfxResourceEntry entry; + std::memset(&entry, 0, sizeof(entry)); + entry.kind = BGFX_RR_KIND_TEXTURE; + entry.texture = BGFX_INVALID_HANDLE; + entry.fb = BGFX_INVALID_HANDLE; + entry.d3d_mirror = nullptr; + entry.owner = tex; + + return RegisterResourceEntry(entry); +} + +RenderResource BgfxBackend::Register_Vertex_Buffer_Resource(VertexBufferClass * vb) +{ + if (vb == nullptr) { + return kInvalidRenderResource; + } + // IMPORTANT: do NOT store the VertexBufferClass* as d3d_mirror — + // Destroy_Resource would cast it to IUnknown* and call Release(), which + // lands on whatever the third virtual of VertexBufferClass happens to + // be and crashes. The VB's legacy resource lifetime is owned by the + // render wrapper dtor; we have no cleanup to do on the reference side. + return RegisterResourceEntry(MakeVertexBufferResourceEntry(vb)); +} + +RenderResource BgfxBackend::Register_Index_Buffer_Resource(IndexBufferClass * ib) +{ + if (ib == nullptr) { + return kInvalidRenderResource; + } + // Same rationale as Register_Vertex_Buffer_Resource — leave d3d_mirror + // null so Destroy_Resource's reference-side Release does nothing. + return RegisterResourceEntry(MakeIndexBufferResourceEntry(ib)); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h new file mode 100644 index 00000000000..db30b03ea22 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackend.h @@ -0,0 +1,492 @@ +/* +** 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 11/04/2026 BgfxBackend. +// IRenderBackend implementation that drives bgfx directly. It owns the +// render-state, transform, buffer, and texture snapshots it needs. +// +// 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. + +#pragma once + +#include "IRenderBackend.h" +#include "rddesc.h" +#include "WWMath/vector3.h" + +class BgfxBackend : public IRenderBackend +{ +public: + BgfxBackend(); + virtual ~BgfxBackend(); + + virtual bool Has_Shader_Pipeline() const override { return true; } + virtual void Invalidate_Cached_Texture(TextureBaseClass * texture) override; + virtual void Copy_Render_Target_To_Texture(TextureClass * dst_texture, + TextureClass * src_render_target) override; + virtual void Release_Cached_Texture(TextureBaseClass * texture) override; + + virtual void Set_Present_Letterbox(bool enabled, float aspectW, float aspectH) override; + virtual bool Is_Present_Letterbox_Active() const override; + virtual int Get_Present_Content_Width() const override; + virtual int Get_Present_Content_Height() const override; + virtual int Get_Present_Offset_X() const override; + virtual int Get_Present_Offset_Y() const override; + + // -- Backend lifecycle ---------------------------------------------------- + // + // Initialize creates the bgfx popup window and calls bgfx::init. + // Shutdown tears down all bgfx resources before bgfx::shutdown. + // Both override IRenderBackend's empty stubs. + + virtual void Initialize(void * hwnd, int width, int height) override; + virtual void Shutdown() override; + + virtual bool Init_Render_System(void * hwnd, bool lite) override; + virtual void Shutdown_Render_System() override; + + // -- Device selection, windowing and display-mode control ----------------- + // + // bgfx has no D3D device-enumeration concept; these forward to the + // DX8Wrapper facade, which on bgfx builds stores the resolution list and + // window state so the existing options UI keeps working unchanged. + + virtual bool Set_Render_Device(const char * dev_name, int width, int height, int bits, int windowed, bool resize_window) override; + virtual bool Set_Render_Device(int dev, int width, int height, int bits, int windowed, bool resize_window, bool reset_device, bool restore_assets) override; + virtual bool Set_Any_Render_Device() override; + virtual bool Set_Next_Render_Device() override; + virtual bool Toggle_Windowed() override; + virtual bool Is_Windowed() const override; + virtual int Get_Render_Device() const override; + virtual const RenderDeviceDescClass & Get_Render_Device_Desc(int deviceidx) override; + virtual int Get_Render_Device_Count() const override; + virtual const char * Get_Render_Device_Name(int device_index) override; + virtual bool Set_Device_Resolution(int width, int height, int bits, int windowed, bool resize_window) override; + virtual void Get_Render_Target_Resolution(int & set_w, int & set_h, int & set_bits, bool & set_windowed) override; + virtual void Get_Device_Resolution(int & set_w, int & set_h, int & set_bits, bool & set_windowed) override; + virtual int Get_Device_Resolution_Width() const override; + virtual int Get_Device_Resolution_Height() const override; + // TheSuperHackers @refactor bobtista 08/06/2026 Registry_Save/Load_Render_Device is dead on bgfx + // (no GameEngine caller; macOS persistence is a no-op stub). Use the IRenderBackend base stubs + // instead of forwarding to DX8Wrapper. The DX8 reference build keeps DX8Backend's own forwards. + virtual void Set_Swap_Interval(int swap) override; + virtual int Get_Swap_Interval() const override; + + // -- Frame lifecycle ------------------------------------------------------ + // + // Begin_Scene touches the bgfx views; End_Scene calls bgfx::frame to advance the swap chain. + + virtual void Begin_Scene() override; + virtual void End_Scene(bool flip_frame) override; + virtual void Invalidate_Cached_Render_States() override; + virtual bool Has_Stencil() const override { return true; } + virtual WW3DFormat Get_Back_Buffer_Format() const override; + virtual bool Supports_Native_Screen_Shot() const override { return true; } + virtual bool Request_Native_Screen_Shot(const char * path) override; + virtual void Set_Texture_Bitdepth(int bitdepth) override; + virtual int Get_Texture_Bitdepth() const override; + virtual bool Supports_Texture_Format(WW3DFormat format) const override; + virtual bool Supports_Compressed_Textures() const override; + virtual bool Supports_Bump_Envmap() const override { return false; } + virtual bool Supports_Bump_Envmap_Luminance() const override { return false; } + virtual bool Supports_Texture_Filter(RenderBackendTextureFilterCapability /*capability*/) const override { return true; } + virtual bool Supports_Texture_Op(RenderBackendTextureOpCapability capability) const override; + // TheSuperHackers @info bobtista 16/07/2026 This backend does not implement distance fog: + // Set_Fog discards its parameters and the uber shader has no fog term. Advertising support + // made ShaderClass::Apply's per-material fog block believe fog could work (it then bailed + // on Get_Fog_Enable anyway). Report the truth; behavior is unchanged since stock ZH never + // enables scene fog. If fog is ever implemented, Set_Fog must also split the scene fog + // color from the per-draw munged color and call ShaderClass::Invalidate on change. + virtual bool Supports_Fog() const override { return false; } + virtual bool Is_Legacy_Voodoo3() const override { return false; } + virtual bool Supports_NPatches() const override { return false; } + virtual bool Supports_Hardware_Transform_And_Lighting() const override { return true; } + virtual bool Supports_Point_Sprites() const override { return false; } + virtual RenderBackendTextureLimits Get_Texture_Limits() const override; + virtual int Get_Max_Texture_Stages() const override; + virtual bool Supports_Z_Bias() const override { return true; } + virtual void Set_MSAA_Mode(RenderBackendMSAAMode mode) override; + virtual RenderBackendMSAAMode Get_MSAA_Mode() const override; + virtual bool Supports_Dot3() const override { return true; } + virtual bool Get_Device_Identity(RenderBackendDeviceIdentity & identity) const override; + virtual 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) override; + virtual void Set_Viewport(const RenderBackendViewport & viewport) override; + virtual bool Initialize_View_Capture(RenderBackendViewCaptureKind kind) override; + virtual void Release_View_Capture(RenderBackendViewCaptureKind kind) override; + virtual bool Supports_View_Capture(RenderBackendViewCaptureKind kind) const override; + virtual bool Begin_View_Capture(RenderBackendViewCaptureKind kind) override; + virtual bool End_View_Capture(RenderBackendViewCaptureKind kind) override; + virtual bool Is_View_Capture_Active(RenderBackendViewCaptureKind kind) const override; + virtual bool Has_View_Capture(RenderBackendViewCaptureKind kind) const override; + virtual bool Bind_View_Capture_Texture(RenderBackendViewCaptureKind kind, + unsigned int stage) override; + virtual bool Draw_View_Capture_Quad(RenderBackendViewCaptureKind kind, + const RenderBackendScreenVertex * vertices, + unsigned int vertex_count, + bool use_second_uv) override; + virtual bool Draw_Screen_Quad(const RenderBackendScreenVertex * vertices, + unsigned int vertex_count, + bool use_second_uv) override; + virtual bool Capture_Back_Buffer_RGBA(unsigned int display_width, + unsigned int display_height, + unsigned int image_size, + unsigned char * output_pixels, + unsigned int output_capacity, + unsigned int * output_width, + unsigned int * output_height) override; + + // -- Vertex / index buffers ----------------------------------------------- + // + // Record the bgfx cache hit (or miss) for the current draw. + + 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 upload hooks. BgfxBackend uploads the data into the cache + // for use by Set_Vertex_Buffer / Set_Index_Buffer. + // Adds the dynamic variants for DynamicVBAccessClass / + // DynamicIBAccessClass which get copied into bgfx transient buffers. + + virtual void Upload_Vertex_Buffer_Data(const VertexBufferClass * vb, + const void * data, + unsigned int size_bytes) override; + virtual void Upload_Index_Buffer_Data(const IndexBufferClass * ib, + const void * data, + unsigned int size_bytes) override; + virtual void Capture_Dynamic_Vertex_Data(const DynamicVBAccessClass * vba, + const void * data, + unsigned int size_bytes) override; + virtual void Capture_Dynamic_Index_Data(const DynamicIBAccessClass * iba, + const void * data, + unsigned int size_bytes) override; + virtual bool Supports_Instancing() const override; + virtual bool Begin_Instanced_Batch(unsigned max_instances) override; + virtual void Add_Instance(const float * world_matrix_4x4) override; + virtual void Submit_Instanced_Batch(unsigned index_offset, unsigned triangle_count, + unsigned min_vertex_index, unsigned vertex_count) override; + + virtual void * Begin_Dynamic_Vertex_Write(const DynamicVBAccessClass * vba, + unsigned int size_bytes) override; + virtual void End_Dynamic_Vertex_Write(const DynamicVBAccessClass * vba, + const void * data, + unsigned int size_bytes) override; + virtual void * Begin_Dynamic_Index_Write(const DynamicIBAccessClass * iba, + unsigned int size_bytes) override; + virtual void End_Dynamic_Index_Write(const DynamicIBAccessClass * iba, + const void * data, + unsigned int size_bytes) override; + virtual void Upload_Vertex_Buffer_Sub_Range(const VertexBufferClass * vb, + const void * data, + unsigned int start_vertex, + unsigned int size_bytes) override; + virtual void Upload_Index_Buffer_Sub_Range(const IndexBufferClass * ib, + const void * data, + unsigned int start_index, + unsigned int size_bytes) override; + virtual void Begin_Sorted_Batch_Pass() override; + virtual void End_Sorted_Batch_Pass() override; + virtual void Apply_Sorted_Batch_State(const RenderBackendSortedBatchState & state) override; + virtual void Set_Point_Group_Render_Active(bool active) override; + virtual void Set_Streak_Render_Active(bool active) override; + virtual void Set_Mesh_Render_Active(bool active) override; + virtual void Capture_Legacy_Render_State_For_Sorted_Draw(RenderStateStruct & state) override; + virtual void Restore_Legacy_Render_State_For_Sorted_Draw(const RenderStateStruct & state) override; + virtual void Release_Legacy_Render_State_For_Sorted_Draw() override; + virtual void Set_Sorted_Texture_Array_Page(int page) override; + virtual void Submit_Sorted_Draw(const DynamicVBAccessClass & dyn_vb, + const DynamicIBAccessClass & dyn_ib, + unsigned short polygon_count, + unsigned short vertex_count) override; + virtual bool Submit_Sorted_Packet(const RenderBackendSortedBatchState & packet, + unsigned int start_index, + unsigned int polygon_count, + unsigned int vertex_count, + int array_page) override; + virtual bool Submit_Rigid_Packet(int ib_base_offset, + unsigned int start_index, + unsigned int primitive_count, + unsigned int min_vertex_index, + unsigned int vertex_count, + bool triangle_strip) override; + + // -- State: shaders, materials, textures --------------------------------- + // + // Set_Shader picks a bgfx program and state mask from the preset bits. + // Set_Texture caches the texture handle for bgfx submission. + + virtual void Set_Shader(const ShaderClass & shader) override; + virtual void Set_Material(const VertexMaterialClass * material) override; + virtual void Apply_Material_State(const RenderBackendMaterialState & material) override; + virtual void Set_Material_Color_Source(RenderBackendMaterialColorSource ambient_source, + RenderBackendMaterialColorSource diffuse_source, + RenderBackendMaterialColorSource emissive_source) override; + virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture) override; + virtual void Bind_Texture_Immediate(unsigned int stage, TextureBaseClass * texture) override; + virtual void Set_Light(unsigned int index, const LightClass & light) override; + virtual void Clear_Light(unsigned int index) override; + virtual void Set_Light_Environment(LightEnvironmentClass * light_env) override; + virtual void Set_Ambient(const Vector3 & color) override; + virtual const Vector3 & Get_Ambient() const override; + virtual void Set_Fog(bool enable, const Vector3 & color, float start, float end) override; + virtual void Set_Fog_Enable(bool enable) override; + virtual void Set_Fog_Color(unsigned argb) override; + virtual unsigned Get_Fog_Color() const override; + virtual void Set_Specular_Enable(bool enable) override; + virtual void Set_Patch_Segments(float level) override; + virtual void Set_Blend_Factors(BlendFactor src, BlendFactor dest) override; + virtual void Set_Blend_Op(BlendOp op) override; + virtual void Set_Alpha_Blend_Enable(bool enable) override; + virtual void Set_Alpha_Test_Enable(bool enable) override; + virtual void Set_Alpha_Test_Reference(unsigned ref) override; + virtual void Set_Alpha_Test_Function(CompareFunc func) override; + virtual void Set_Normalize_Normals(bool enable) 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; + virtual void Override_Material_Opacity(float opacity) override; + virtual void Set_Texture_Transform(unsigned stage, const Matrix4x4& matrix) override; + virtual void Clear_Texture_Transform(unsigned stage) override; + virtual void Set_Texture_Coord_Source(unsigned stage, + RenderBackendTexcoordSource source, + unsigned uv_array_index = 0) override; + virtual void Set_Texture_Transform_Mode(unsigned stage, unsigned coord_count, bool projected) override; + virtual void Set_Texture_Bump_Env_Matrix(unsigned stage, + float m00, + float m01, + float m10, + float m11) override; + virtual void Set_Texture_Bump_Env_Luminance(unsigned stage, + float scale, + float offset) override; + virtual void Set_Texture_Color_Operation(unsigned stage, + RenderBackendTextureOperation op) override; + virtual void Set_Texture_Alpha_Operation(unsigned stage, + RenderBackendTextureOperation op) override; + virtual void Set_Texture_Color_Argument(unsigned stage, + unsigned argument_index, + RenderBackendTextureArgument arg) override; + virtual void Set_Texture_Alpha_Argument(unsigned stage, + unsigned argument_index, + RenderBackendTextureArgument arg) override; + virtual void Set_Texture_Coord_Generation(unsigned stage, bool cameraPosEnabled) override; + virtual void Set_Texture_UV_Wrap(unsigned stage, bool enable) override; + virtual void Set_Texture_Address_Mode(unsigned stage, + RenderBackendTextureAddressMode u, + RenderBackendTextureAddressMode v, + RenderBackendTextureAddressMode w) override; + virtual void Set_Texture_Sample_Filter(unsigned stage, + RenderBackendTextureSampleFilter min_filter, + RenderBackendTextureSampleFilter mag_filter, + RenderBackendTextureSampleFilter mip_filter) override; + virtual void Set_Texture_Min_Mag_Filter(unsigned stage, + RenderBackendTextureSampleFilter min_filter, + RenderBackendTextureSampleFilter mag_filter) override; + virtual void Set_Texture_Mip_Filter(unsigned stage, + RenderBackendTextureSampleFilter mip_filter) override; + virtual void Set_Texture_Max_Anisotropy(unsigned stage, unsigned max_anisotropy) override; + virtual void Set_Texture_Clamp_Mode(unsigned stage, bool clampU, bool clampV) override; + virtual void Set_Texture_Stage_State(unsigned stage, unsigned state, unsigned value) override; + virtual void Configure_Custom_Edging_Cloud_Texture_Stages() override; + virtual void Configure_Shadow_Volume_Fill_Texture_Stages() override; + virtual void Set_Shroud_Texture_Pass_Active(bool active, unsigned stage) override; + virtual void Set_Object_Shroud_Texture_Pass_Active(bool active) override; + virtual void Set_Object_Shroud_Alpha_Mask_Texture(TextureBaseClass * texture) override; + virtual void Set_Shroud_Texture_Params(float offset_x, float offset_y, + float scale_x, float scale_y) override; + virtual bool Requires_Delayed_Object_Shroud_Pass() const override { return true; } + virtual void Begin_Water_Overlay() override; + virtual void End_Water_Overlay() override; + virtual void Begin_Effect_Overlay() override; + virtual void End_Effect_Overlay() override; + virtual bool Begin_Smudge_Distortion(float tactical_width_fraction = 1.0f, + float tactical_height_fraction = 1.0f) override; + virtual void End_Smudge_Distortion() 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]) 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_Light_Map_Params(bool enable, float stretch, TextureClass * noise_tex) override; + virtual void Set_Color_Write_Enable(bool red, bool green, bool blue, bool alpha) override; + virtual bool Supports_Color_Write_Mask() const override { return true; } + virtual unsigned Get_Color_Write_Mask() const override; + virtual void Set_Color_Write_Mask(unsigned mask) override; + virtual void Set_Lighting_Enable(bool enable) override; + virtual void Skip_Next_Bgfx_Submit() override; + virtual void Set_Projected_Shadow_Decal_Active(bool active) override; + virtual void Set_Projected_Decal_Mode(RenderBackendProjectedDecalMode mode) 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, + int x, + int y, + int width, + int height) 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 bool Needs_Closed_Shadow_Volumes() const 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 src_x, + unsigned src_y, + unsigned dst_x, + unsigned dst_y, + unsigned pitch, + WW3DFormat format, + unsigned border_pixel) override; + virtual void Set_Texture_Factor(unsigned argb) override; + + virtual void Set_Z_Bias(int bias) override; + virtual void Set_Normal_Bias(float bias) override; + virtual void Set_Fill_Mode(FillMode mode) override; + virtual void Set_Shade_Mode(ShadeMode mode) override; + virtual void Set_Depth_Test_Enable(bool enable) override; + virtual void Set_Depth_Write_Enable(bool enable) override; + virtual void Set_Depth_Func(CompareFunc func) override; + virtual void Set_Point_Sprite_Enable(bool enable) override; + virtual void Set_Point_Scale_Enable(bool enable) override; + virtual void Set_Point_Size(float size, float min_size, float max_size) override; + virtual void Set_Point_Scale(float a, float b, float c) 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 CullMode Get_Cull_Mode() const 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 ----------------------------------------------------------- + // + // Captures the engine's view / projection / world matrices into bgfx + // column-major form. + + virtual void Set_Transform(TransformKind transform, const Matrix4x4 & m) override; + virtual void Set_Transform(TransformKind transform, const Matrix3D & m) override; + virtual void Get_Transform(TransformKind transform, Matrix4x4 & m) const override; + virtual void Set_World_Identity() override; + virtual void Set_View_Identity() override; + virtual bool Is_World_Identity() const override; + virtual bool Is_View_Identity() const override; + virtual void Set_Projection_Transform_With_Z_Bias(const Matrix4x4 & matrix, float znear, float zfar) override; + + // -- Draw calls ----------------------------------------------------------- + // + // Issues a real bgfx::submit if the cache lookup found a valid + // VB+IB+program for the current state. + + virtual void Draw_Triangles(unsigned short start_index, + unsigned short polygon_count, + unsigned short min_vertex_index, + 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) override; + virtual bool Is_Triangle_Draw_Enabled() const override; + virtual void Set_Triangle_Draw_Enabled(bool enable) override; + virtual void Draw_Screen_Multiply_Quad(unsigned color, + int x, + int y, + int width, + int height) override; + virtual void Draw_Strip(unsigned short start_index, + unsigned short index_count, + unsigned short min_vertex_index, + unsigned short vertex_count) override; + + // -- Programmable pipeline compatibility --------------------------------- + + virtual bool Load_Legacy_Shader(const char * path, + const unsigned int * declaration, + unsigned int usage, + RenderBackendShaderKind kind, + unsigned long * handle) override; + virtual bool Create_Vertex_Shader(const unsigned int * declaration, + const unsigned int * shader, + unsigned int usage, + unsigned long * handle) override; + virtual bool Create_Pixel_Shader(const unsigned int * shader, + unsigned long * handle) override; + virtual bool Create_Legacy_Pixel_Shader(RenderBackendLegacyPixelShaderMode mode, + unsigned long * handle) override; + virtual void Delete_Vertex_Shader(unsigned long vertex_shader) override; + virtual void Delete_Pixel_Shader(unsigned long pixel_shader) override; + virtual void Set_Vertex_Shader(unsigned long vertex_shader) override; + virtual void Set_Pixel_Shader(unsigned long pixel_shader) override; + + // -- Resource creation (asset ingress) --------------------------- + // + // Creates the corresponding bgfx resource. The returned RenderResource.id + // encodes an index into a backend-local side table. + + virtual bool Requires_Legacy_Buffer_Resources() const override; + virtual RenderResource Create_Texture(const TextureDesc & desc) override; + virtual RenderResource Create_Vertex_Buffer(const BufferDesc & desc, const void * initial_data) override; + virtual RenderResource Create_Index_Buffer(const BufferDesc & desc, const void * initial_data, bool indices_are_32bit) override; + virtual void Destroy_Resource(RenderResource h) override; + + // Transitional: populate m_backendHandle on owner-backed wrapper + // resources. See IRenderBackend.h for context. + virtual RenderResource Register_Texture_Resource(TextureBaseClass * tex) override; + virtual RenderResource Register_Vertex_Buffer_Resource(VertexBufferClass * vb) override; + virtual RenderResource Register_Index_Buffer_Resource(IndexBufferClass * ib) override; + +private: + int m_textureBitDepth; + RenderBackendMSAAMode m_msaaMode; + // TheSuperHackers @bugfix bobtista 28/05/2026 Persist the ambient color in a real member so Get_Ambient() returns a stable lvalue mirror of g_draw.sceneAmbient. + mutable Vector3 m_ambient; + + // TheSuperHackers @refactor bobtista 11/06/2026 Native device lifecycle so dx8wrapper.cpp is + // no longer compiled on bgfx builds. A single synthetic device entry feeds the options UI; + // the real bgfx device (Initialize) and WW3D subsystems come up on the first Set_Render_Device. + RenderDeviceDescClass m_renderDeviceDesc; + bool m_renderDeviceDescBuilt; + bool m_deviceCreated; + int m_curRenderDevice; + void Ensure_Render_Device_Desc(); + bool Reset_Bgfx_Device(bool reload_assets); +}; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackendState.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackendState.h new file mode 100644 index 00000000000..1a73c92188b --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackendState.h @@ -0,0 +1,784 @@ +/* +** 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). +#pragma once + +#include +#include +#include + +#include + +#include "ww3dformat.h" + +#include + +// Case-insensitive strstr shared by the backend translation units. +inline bool ContainsCaseInsensitive(const char *haystack, const char *needle) +{ + if (haystack == nullptr || needle == nullptr || *needle == '\0') + { + return false; + } + + const size_t needleLen = std::strlen(needle); + for (const char *p = haystack; *p != '\0'; ++p) + { + if (strnicmp(p, needle, needleLen) == 0) + { + return true; + } + } + return false; +} + +// Forward declarations — full headers are included by the .cpp files that need the method bodies. +class TextureBaseClass; +class TextureClass; +class VertexMaterialClass; +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 BgfxPendingRangeUpload +{ + uint32_t startByte = 0; + uint32_t endByte = 0; + bool valid = false; +}; + +struct TextureCacheInfo +{ + unsigned revision; + uint16_t w; + uint16_t h; + int sourceFormat = 0; + int createFormat = 0; + uint16_t mipCount = 0; + uint16_t uploadVariant = 0; +}; + +struct PendingTransientVB +{ + bool valid; + const DynamicVBAccessClass * owner; + bgfx::TransientVertexBuffer tvb; + bool coplanarNormalBias; +}; + +struct PendingTransientIB +{ + bool valid; + const DynamicIBAccessClass * owner; + bgfx::TransientIndexBuffer tib; +}; + +// --- The 8 render-state structs -------------------------------------------- + +// Device: created in Initialize(), released in Shutdown(). Never reset during frames. +struct BgfxDevice +{ + bool initialized = false; + HWND window = nullptr; + bool mainWindowShown = false; + // Set to 0 so any read before Initialize() trips obvious downstream + // sentinels (clip rects = 0x0, no allocation). The previous 800x600 + // placeholder masked uninitialized-use bugs. + int width = 0; + int height = 0; + // TheSuperHackers @feature bobtista 08/06/2026 Letterbox present. width/height are the CONTENT + // (render) size; when letterboxing, the swapchain is larger (swapWidth/swapHeight == window) and + // the content is drawn at presentOffsetX/Y with black bars filling the rest. When not + // letterboxing, swap == content and offset == 0 (identical to a direct present). + bool letterboxRequested = false; + float letterboxAspectW = 16.0f; + float letterboxAspectH = 9.0f; + bool letterboxActive = false; + int swapWidth = 0; + int swapHeight = 0; + int presentOffsetX = 0; + int presentOffsetY = 0; + uint32_t msaaResetFlags = 0; + bool srgbEnabled = false; + bool vsyncEnabled = false; + // TheSuperHackers @refactor bobtista 08/06/2026 Device windowed flag and color bit-depth are + // owned here on bgfx builds; DX8Wrapper mirrors its IsWindowed/BitDepth into these from its + // Set_Render_Device/Init. Defaults match DX8Wrapper (IsWindowed=false, BitDepth=DEFAULT_BIT_DEPTH=32). + bool windowed = false; + int bits = 32; + // 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 uberInstancedProgram = BGFX_INVALID_HANDLE; // vs_uber_instanced + fs_uber; per-instance world matrix from instance buffer. + 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 shadowVolumeProgram = BGFX_INVALID_HANDLE; + bgfx::ProgramHandle shadowApplyProgram = BGFX_INVALID_HANDLE; + bgfx::ProgramHandle sceneCompositeProgram = BGFX_INVALID_HANDLE; + bgfx::ProgramHandle bloomBrightProgram = BGFX_INVALID_HANDLE; + bgfx::ProgramHandle bloomBlurProgram = BGFX_INVALID_HANDLE; + bgfx::ProgramHandle ssaoProgram = BGFX_INVALID_HANDLE; + bgfx::ProgramHandle copyProgram = BGFX_INVALID_HANDLE; + bgfx::ProgramHandle sceneDepthProgram = BGFX_INVALID_HANDLE; + bgfx::ProgramHandle sceneDepthInstancedProgram = BGFX_INVALID_HANDLE; // per-instance world matrix + bgfx::ProgramHandle shadowCasterProgram = BGFX_INVALID_HANDLE; // alpha-aware shadow caster + bgfx::ProgramHandle shadowCasterInstancedProgram = BGFX_INVALID_HANDLE; // per-instance world matrix + bgfx::ProgramHandle smudgeProgram = BGFX_INVALID_HANDLE; + bgfx::ProgramHandle sortedArrayProgram = BGFX_INVALID_HANDLE; // vs_uber + fs_uber_array; stage 0 from a texture2DArray layer for merged sorted runs. + // TheSuperHackers @performance bobtista vs_uber + fs_uber_frameconst; reads the global + // per-frame constants (sun/point shadow, scene ambient) from frameConstTexture instead of + // per-draw uniforms to shrink the constant buffer. Selected in place of uberProgram unless + // GGC_BGFX_NO_UNIFORM_FRAME_TEXTURE is set. + bgfx::ProgramHandle uberFrameConstProgram = BGFX_INVALID_HANDLE; + bgfx::TextureHandle frameConstTexture = BGFX_INVALID_HANDLE; // 16x1 RGBA32F data texture, updated per frame. + + // Scene color/depth RT. World, water, sorted translucency, and effects + // render here, then a fullscreen composite pass copies the scene to the + // backbuffer before UI draws. + bgfx::FrameBufferHandle sceneFB = BGFX_INVALID_HANDLE; + bgfx::TextureHandle sceneColor = BGFX_INVALID_HANDLE; + bgfx::TextureHandle sceneDepth = BGFX_INVALID_HANDLE; + bgfx::TextureHandle sceneSmudgeCopy = BGFX_INVALID_HANDLE; + bgfx::FrameBufferHandle sceneSmudgeCopyFB = BGFX_INVALID_HANDLE; + bgfx::FrameBufferHandle sceneReadableDepthFB = BGFX_INVALID_HANDLE; + bgfx::TextureHandle sceneReadableDepth = BGFX_INVALID_HANDLE; + bgfx::TextureHandle sceneReadableDepthTest = BGFX_INVALID_HANDLE; + bgfx::TextureHandle bloomBrightTex = BGFX_INVALID_HANDLE; + bgfx::FrameBufferHandle bloomBrightFB = BGFX_INVALID_HANDLE; + bgfx::TextureHandle bloomBlurTex = BGFX_INVALID_HANDLE; + bgfx::FrameBufferHandle bloomBlurFB = BGFX_INVALID_HANDLE; + bgfx::TextureHandle ssaoTex = BGFX_INVALID_HANDLE; + bgfx::FrameBufferHandle ssaoFB = BGFX_INVALID_HANDLE; + bgfx::TextureHandle ssaoBlurTex = BGFX_INVALID_HANDLE; + bgfx::FrameBufferHandle ssaoBlurFB = BGFX_INVALID_HANDLE; + // Sun shadow map: light-POV depth (R32F) rendered with sceneDepthProgram. + bgfx::TextureHandle shadowMapTex = BGFX_INVALID_HANDLE; + bgfx::TextureHandle shadowMapDepth = BGFX_INVALID_HANDLE; + bgfx::FrameBufferHandle shadowMapFB = BGFX_INVALID_HANDLE; + uint16_t shadowMapSize = 0; + // TheSuperHackers @feature bobtista 23/06/2026 Point-light shadow map for one bright dynamic + // point light (e.g. the nuke fireball). 1024x1024 perspective depth target. + bgfx::FrameBufferHandle pointShadowFB = BGFX_INVALID_HANDLE; + bgfx::TextureHandle pointShadowTex = BGFX_INVALID_HANDLE; + uint16_t pointShadowMapSize = 0; + // TheSuperHackers @feature bobtista 14/07/2026 Second point-shadow slot so a transient light + // (particle-cannon lightning flash, or a nuke coinciding with a beam) can cast its own shadow + // without stealing the primary light's map. + bgfx::FrameBufferHandle pointShadow2FB = BGFX_INVALID_HANDLE; + bgfx::TextureHandle pointShadow2Tex = BGFX_INVALID_HANDLE; + uint16_t sceneWidth = 0; + uint16_t sceneHeight = 0; + // Supersampled scene render size = content size * render scale (1.0-2.0). + uint16_t sceneRenderWidth = 0; + uint16_t sceneRenderHeight = 0; + uint16_t bloomWidth = 0; + uint16_t bloomHeight = 0; + + // 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 sCloudMap = BGFX_INVALID_HANDLE; + bgfx::UniformHandle sLightMap = BGFX_INVALID_HANDLE; + bgfx::UniformHandle sSceneDepth = BGFX_INVALID_HANDLE; + bgfx::UniformHandle sTexArray = BGFX_INVALID_HANDLE; // texture2DArray for merged sorted runs (fs_uber_array stage 4) + bgfx::UniformHandle sFrameConst = BGFX_INVALID_HANDLE; // frame-constant data texture sampler (fs_uber_frameconst stage 9) + + // Material / TSS + // TheSuperHackers @performance bobtista 15/06/2026 Packed per-draw material block. + // The individual uMat*/uTss*/uTex*Transform*/uZBias etc. handles below are uploaded + // through this single array uniform (one setUniform/draw instead of ~24) to cut + // submit-thread CPU cost. Slot order is MaterialUniformSlot in BgfxBackend.cpp. + bgfx::UniformHandle uMaterial = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uMatDiffuse = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uMatAmbient = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uMatEmissive = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uMatSpecular = BGFX_INVALID_HANDLE; // rgb = specular color, w = shininess + bgfx::UniformHandle uMatFx = BGFX_INVALID_HANDLE; // x spec strength, y rim strength, z rim power, w emissive boost + bgfx::UniformHandle uEyePos = BGFX_INVALID_HANDLE; // xyz = world-space camera position + 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 uLightAmbients = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uLightPositions = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uLightParams = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uSceneAmbient = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uLightingEnabled = BGFX_INVALID_HANDLE; + + // Sun shadow map sampling. + bgfx::UniformHandle uShadowMatrices = BGFX_INVALID_HANDLE; // per-cascade light view-proj (world -> shadow clip) + bgfx::UniformHandle uShadowParams = BGFX_INVALID_HANDLE; // x texel size, y depth bias, z strength, w enabled + bgfx::UniformHandle uShadowQuality = BGFX_INVALID_HANDLE; // x: >0.5 = full 36-fetch PCF, else reduced 9-fetch + bgfx::UniformHandle uSunShadowReceive = BGFX_INVALID_HANDLE; // x>0.5 = this object draw receives the sun cast shadow + bgfx::UniformHandle sShadowMap = BGFX_INVALID_HANDLE; + // TheSuperHackers @feature bobtista 23/06/2026 Point-light shadow map uniforms. + // u_pointShadowParams: x=active(1)/none(-1), y=bias, z=texel, w=strength. + // The nuke caster is a dedicated light (not a LightEnvironment slot): u_pointShadowLightPos + // = world xyz + outer range, u_pointShadowLightColor = diffuse rgb. + bgfx::UniformHandle uPointShadowMatrix = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uPointShadowParams = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uPointShadowLightPos = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uPointShadowLightColor = BGFX_INVALID_HANDLE; + bgfx::UniformHandle sPointShadowMap = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uPointShadow2Matrix = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uPointShadow2Params = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uPointShadow2LightPos = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uPointShadow2LightColor = BGFX_INVALID_HANDLE; + bgfx::UniformHandle sPointShadowMap2 = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uDramaDim = BGFX_INVALID_HANDLE; + + // Misc per-draw flags / params + bgfx::UniformHandle uTexcoordSelect = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uTexcoordSelect2 = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uProjectedDecalMode = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uTexcoordSource = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uVertexColorFlags = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uGrayscaleEnable = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uObjectShroudDim = 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 uTexTransform0 = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uTexTransform1 = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uTexTransform0Z = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uTex1Transform0 = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uTex1Transform1 = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uTex1TransformZ = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uTex2Transform0 = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uTex2Transform1 = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uTexProjected = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uLegacyPixelShaderMode = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uZBias = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uShadowColor = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uPostParams = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uPostTexelSize = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uSmudgeClip = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uWipeParams = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uColorGradeParams = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uBloomParams = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uBloomBlurDir = BGFX_INVALID_HANDLE; + bgfx::UniformHandle sBloom = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uHdrParams = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uPostFx2Params = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uSsaoParams = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uSsaoInvProj = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uSsaoProj = BGFX_INVALID_HANDLE; + bgfx::UniformHandle sSsao = BGFX_INVALID_HANDLE; + bgfx::UniformHandle uSoftParticleParams = 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; + uint64_t blendFuncBits = 0; + bool alphaBlendEnabled = false; + bool alphaBlendExplicitlySet = false; + bool alphaTestExplicitlySet = false; + bool depthTestEnabled = true; + bool depthWriteEnabled = true; + uint64_t depthFuncBits = BGFX_STATE_DEPTH_TEST_LEQUAL; + unsigned depthFunc = 4; // == IRenderBackend RB_CMP_LESS_EQUAL + + // 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 }; + bool mipFilterDisabled[4] = { false, false, false, false }; + unsigned texcoordIndex[4] = { 0, 1, 2, 3 }; + unsigned textureTransformFlags[4] = { 0, 0, 0, 0 }; + bool textureIsMissing[4] = { false, false, false, false }; + TextureBaseClass * sourceTextures[4] = { nullptr, nullptr, nullptr, nullptr }; + const VertexMaterialClass * sourceMaterial = nullptr; + bool explicitMaterialState = false; + // >= 0 while the next sorted submit renders a texture-array merged run; + // selects sortedArrayProgram and binds the page at the s_texArray stage. + int sortedArrayPage = -1; + + // Buffers (static + transient variants) + bgfx::DynamicVertexBufferHandle vb = BGFX_INVALID_HANDLE; + bgfx::DynamicIndexBufferHandle ib = BGFX_INVALID_HANDLE; + bgfx::VertexBufferHandle staticVB = BGFX_INVALID_HANDLE; + bgfx::IndexBufferHandle staticIB = BGFX_INVALID_HANDLE; + const VertexBufferClass * vbOwner = nullptr; + const IndexBufferClass * ibOwner = nullptr; + unsigned short ibOffset = 0; + bool useStaticVB = false; + bool useStaticIB = false; + bool useTransientVB = false; + bgfx::TransientVertexBuffer transientVB = {}; + bool useTransientIB = false; + bgfx::TransientIndexBuffer transientIB = {}; + PendingTransientVB pendingVB = { false, nullptr, {}, false }; + PendingTransientIB pendingIB = { false, nullptr, {} }; + const DynamicVBAccessClass * activeTransientVBOwner = nullptr; + const DynamicIBAccessClass * activeTransientIBOwner = nullptr; + bool activeVertexNormalBias = false; + + // 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 + uint64_t blendEquationBits = 0; + float matDiffuse[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + float matAmbient[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + float matEmissive[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float matSpecular[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; // rgb = specular color, w = shininess + 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 shaderTssOps0[4] = { 3.0f, 3.0f, 0.0f, 0.0f }; + float shaderTssOps1[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + bool atestEnabled = false; + float atestRef = 0.0f; + float atestFunc = 0.0f; + bool shaderAlphaBlendEnabled = false; + uint64_t shaderBlendFuncBits = 0; + float shaderAtestRef = 0.0f; + float shaderAtestFunc = 0.0f; + float texcoordSelect[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + // .x/.y are used by vs_uber for stage-1 UV routing and transform state. + // .w tags additive blend draws for black-matte discard in fs_uber. + float texcoordSelect2[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float projectedDecalMode[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float texcoordSource[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float vertexColorFlags[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; + float texTransform0[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; + float texTransform1[4] = { 0.0f, 1.0f, 0.0f, 0.0f }; + float texTransform0Z[4] = { 0.0f, 0.0f, 1.0f, 0.0f }; + float tex1Transform0[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; + float tex1Transform1[4] = { 0.0f, 1.0f, 0.0f, 0.0f }; + float tex1TransformZ[4] = { 0.0f, 0.0f, 1.0f, 0.0f }; + float tex2Transform0[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; + float tex2Transform1[4] = { 0.0f, 1.0f, 0.0f, 0.0f }; + // .x > 0.5 = stage 0 uses projected 3-component texture coords - divide UV.xy + // by the third texcoord output produced from texTransform0Z. .y same for + // stage 1. Used by TexProjectClass perspective projection of building + // floor emblems / faction icons. + float texProjected[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float cloudParams[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float lightMapParams[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + bgfx::TextureHandle cloudTex = BGFX_INVALID_HANDLE; + bgfx::TextureHandle lightMapTex = 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 lightAmbients[4][4] = { + { 0.0f, 0.0f, 0.0f, 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 lightPositions[4][4] = { + { 0.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, 0.0f, 0.0f, 0.0f } + }; + float lightParams[4][4] = { + { 0.0f, 0.0f, 0.0f, 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 } + }; + // TheSuperHackers @feature bobtista 23/06/2026 Point-light shadow: world->shadowClip matrix + // and params (x=lightIndex(-1=none), y=bias, z=texel, w=strength) for the brightest point light. + float pointShadowMatrix[16] = { 0.0f }; + float pointShadowParams[4] = { -1.0f, 0.0f, 0.0f, 0.0f }; + // The dedicated nuke caster light published by SetupPointShadowView: world xyz + outer range + // in pointShadowLightPos, diffuse rgb in pointShadowLightColor. fs_uber applies this light and + // its shadow directly (dynamic lights never enter the per-object LightEnvironment). + float pointShadowLightPos[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float pointShadowLightColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + bool pointShadowLightValid = false; + // Second point-shadow slot (transient flash lights); same layout as slot 1. + float pointShadow2Matrix[16] = { 0.0f }; + float pointShadow2Params[4] = { -1.0f, 0.0f, 0.0f, 0.0f }; + float pointShadow2LightPos[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float pointShadow2LightColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + // TheSuperHackers @feature bobtista 14/07/2026 GGC_PCANNON_ENHANCED scene dip: eased dim + // level while a dramatic shadow-casting dynamic light is active. Published to the shader + // through dramaDim as a world-space radial falloff centred on the light, so the action + // stays vivid and the battlefield darkens with distance (vignette-like, but in world + // space). dramaDim = {center.x, center.y, 1/falloffWidth, dimFactor(1 = off)}. + float dramaAmbientDim = 1.0f; + float dramaDim[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + float sceneAmbient[4] = { 0.45f, 0.45f, 0.45f, 1.0f }; + float lightingEnabled[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; + bool fvfHasNormal = false; + // .x = post-projection clip-space Z offset (negative pushes toward camera), applied + // in vs_uber.sc as gl_Position.z -= u_zBias.x * gl_Position.w; sourced from the + // cached z-bias at submit time so decals keep their anti-z-fighting bias. + float zBias[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + // x>0.5 marks an opaque object draw (unit/structure/prop) that receives the sun cast shadow. + float sunShadowReceive[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + unsigned zBiasUnits = 0; + float normalBias[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float legacyPixelShaderMode[4] = { 0.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 }; + float shroudTextureParams[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; + float objectShroudDim[4] = { 1.0f, 0.0f, 0.0f, 0.0f }; + bool shroudTextureParamsValid = false; + bool delayedObjectShroudPass = false; + + // TheSuperHackers @performance bobtista 10/07/2026 Capture-time resolved + // pipeline-state word for the current sorted packet submit. Stashed by + // Submit_Sorted_Packet from the packet and cleared right after the draw; + // SubmitEngineDraw consumes it when the resolved-pipeline flag is on and + // compares it against the live derivation under trace. + uint64_t sortedResolvedState = 0; + bool sortedResolvedStateValid = false; + + // Instancing batch state + bgfx::InstanceDataBuffer instanceBatch; + unsigned instanceCount = 0; + unsigned instanceMax = 0; + bool instanceBatchActive = false; + // True only while the instanced batch submits, so the shadow/depth recast re-binds the instance + // buffer and uses the instanced caster programs instead of the single-copy path. + bool drawIsInstanced = false; +}; + +// Overrides: transient per-shader overrides. Reset by Clear_State_Overrides (called from Set_Shader). +struct BgfxOverrides +{ + bool blendActive = false; + uint64_t blendBits = 0; + bool blendEnableActive = false; + bool blendEnableValue = false; + bool atestActive = false; + float atestRef = 0.0f; + float atestFunc = 0.0f; + bool suppressDraw = false; + int colorWriteOverride = -1; + + void Reset() + { + blendActive = false; + blendBits = 0; + blendEnableActive = false; + blendEnableValue = false; + atestActive = false; + atestRef = 0.0f; + atestFunc = 0.0f; + suppressDraw = false; + colorWriteOverride = -1; + } + + void SetBlend(uint64_t bits) + { + blendActive = true; + blendBits = bits; + } + + void SetBlendEnable(bool enable) + { + blendEnableActive = true; + blendEnableValue = enable; + } +}; + +// Views: flags that control which bgfx view a submit routes to + ephemeral per-pass state. +struct BgfxViewFlags +{ + bool overlay2DActive = false; + bool renderToTexture = false; + TextureClass * renderTargetTexture = nullptr; + bool waterOverrideActive = false; + bool waterOverlayActive = false; + bool effectOverlayActive = false; + bool smudgeActive = false; + float smudgeClip[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + uint16_t sceneViewportX = 0; + uint16_t sceneViewportY = 0; + uint16_t sceneViewportW = 0; + uint16_t sceneViewportH = 0; + bool inSortFlush = false; + bool treeShaderActive = false; + bool shadowVolumeActive = false; + bool shroudTexturePassActive = false; + bool objectShroudTexturePassActive = false; + unsigned shroudTexturePassStage = 0; + bool projectedShadowDecalActive = false; + unsigned projectedDecalMode = 0; + bool skipNextSubmitEngineDraw = false; + bool pointGroupRenderActive = false; + bool streakRenderActive = false; + bool meshRenderActive = false; + unsigned sortedBatchDrawFlags = 0; + bool sortedBatchMaterialCaptured = 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; + + // Sun shadow map: per-cascade light view-proj for the current frame, and whether + // the shadow pass is active. 3 cascades; matrices are contiguous for setUniform. + float shadowMatrices[3 * 16] = {}; + bool shadowActive = false; + + float sortWorld[16] = {}; + float sortWorldRaw[16] = {}; + float sortViewOnly[16] = {}; // sortView alone (identity world) for world-baked merged sorted runs + float sortProj[16] = {}; + bool sortProjCaptured = false; + +}; + +// Stats: per-frame backend counters used by debug builds to profile draw/state churn. +struct BgfxStats +{ + uint32_t frameIndex = 0; + uint32_t drawCalls = 0; + uint32_t skippedDraws = 0; + + uint32_t baseSubmits = 0; + uint32_t sceneDepthSubmits = 0; + uint32_t shadowVolumeSubmits = 0; + uint32_t shadowApplySubmits = 0; + uint32_t smudgeSubmits = 0; + uint32_t sceneCompositeSubmits = 0; + uint32_t debugSubmits = 0; + + uint32_t uiDraws = 0; + uint32_t worldDraws = 0; + uint32_t waterDraws = 0; + uint32_t sortedDraws = 0; + uint32_t effectDraws = 0; + uint32_t rttDraws = 0; + uint32_t smudgeDraws = 0; + + uint32_t textureBinds = 0; + uint32_t textureCreates = 0; + uint32_t textureUploads = 0; + uint32_t textureCopies = 0; + uint32_t materialUniformUploads = 0; + uint32_t lightUniformUploads = 0; + uint32_t uniformCommands = 0; + uint32_t materialUniformCommands = 0; + uint32_t lightUniformCommands = 0; + uint32_t shadowUniformCommands = 0; + uint32_t pointShadowUniformCommands = 0; + uint32_t textureTransformUpdates = 0; + uint32_t renderStateCopies = 0; + + uint32_t transientVbAllocations = 0; + uint32_t transientIbAllocations = 0; + uint32_t transientVbDraws = 0; + uint32_t transientIbDraws = 0; + uint32_t dynamicVbAllocations = 0; + uint32_t dynamicIbAllocations = 0; + uint32_t instancedSavedDrawCalls = 0; + uint32_t sortedReplayCalls = 0; + long long sortedReplayTotalTicks = 0; + long long sortedReplayShaderTicks = 0; + long long sortedReplayMaterialTicks = 0; + long long sortedReplayTextureTicks = 0; + long long sortedReplayTransformTicks = 0; + long long sortedReplayLightTicks = 0; +}; + +// Caches: long-lived resource maps. +struct BgfxCaches +{ + std::unordered_map vb; + std::unordered_map ib; + std::unordered_map pendingVbRangeUploads; + std::unordered_map pendingIbRangeUploads; + std::unordered_map texture; + std::unordered_map textureInfo; + std::unordered_map textureBaseMip; + std::unordered_map textureBaseMipInfo; + std::unordered_map framebuffer; + std::unordered_map renderTarget; + std::vector deferredDestroys; // current frame + std::vector deferredDestroysPrev; // previous frame, safe to destroy + // TheSuperHackers @bugfix bobtista 07/06/2026 Render-target framebuffers (water RTTs, etc.) + // dropped by a resolution-change device reset must outlive the in-flight frame, same as the + // texture/buffer queues. Queue the framebuffer handle (which owns its attachment textures) + // here and destroy it one frame later. + std::vector deferredDestroyFB; + std::vector deferredDestroyFBPrev; + // TheSuperHackers @bugfix bobtista 02/06/2026 Dynamic VB/IB handles orphaned by a + // mid-frame resize must outlive the in-flight frame that may still reference them; + // destroyed one frame later, like the textures above. + std::vector deferredDestroyVB; + std::vector deferredDestroyVBPrev; + std::vector deferredDestroyIB; + std::vector deferredDestroyIBPrev; + // TheSuperHackers @bugfix bobtista 02/06/2026 Immutable static VB/IB handles dropped + // when a static-eligible buffer demotes to the dynamic path are still referenced by the + // draw recorded earlier this frame. Defer their destroy one frame, same as the dynamic + // queues above, to avoid the single in-gameplay "RefCount is 1 (expected 0)" warning. + std::vector deferredDestroyStaticVB; + std::vector deferredDestroyStaticVBPrev; + std::vector deferredDestroyStaticIB; + std::vector deferredDestroyStaticIBPrev; +}; + +// ---asset-ingress resource table ----------------------------------- +// +// Resources created via IRenderBackend::Create_Texture / Create_Vertex_Buffer +// etc. are tracked here. RenderResource.id is a monotonically-assigned index +// into BgfxResourceRegistry::table; table[id] holds the bgfx handle(s) plus +// an optional legacy mirror pointer for the ref-popup build. + +enum BgfxResourceKind +{ + BGFX_RR_KIND_NONE = 0, + BGFX_RR_KIND_TEXTURE = 1, + BGFX_RR_KIND_VB = 2, + BGFX_RR_KIND_IB = 3 +}; + +struct BgfxResourceEntry +{ + BgfxResourceKind kind; + bgfx::TextureHandle texture; + bgfx::FrameBufferHandle fb; + bgfx::VertexBufferHandle vb; + bgfx::IndexBufferHandle ib; + bgfx::DynamicVertexBufferHandle dvb; + bgfx::DynamicIndexBufferHandle dib; + uint16_t width; + uint16_t height; + void * d3d_mirror; // raw legacy mirror pointer, ref-popup only; nullptr in standalone + void * owner; // TextureBaseClass/VertexBufferClass/IndexBufferClass for loaded-resource caches + // TheSuperHackers @perf bobtista 02/06/2026 Content hash of the data last captured into + // the static vb/ib; when a re-upload hash-matches the GPU copy the recreate is skipped. + uint64_t vbContentHash; + uint64_t ibContentHash; +}; + +struct BgfxResourceRegistry +{ + // id 0 is reserved for kInvalidRenderResource. Allocate starting at 1. + std::unordered_map table; + uint64_t next_id; +}; + +extern BgfxResourceRegistry g_resourceRegistry; + +// --- 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 BgfxStats g_stats; +extern BgfxCaches g_caches; + +// --- Helpers shared across BgfxBackend*.cpp --------------------------------- +// Defined in BgfxBackend.cpp. True once process exit has begun (std::atexit +// handler registered at Initialize); resource/cache entry points reachable +// from static destructors must no-op past this point because this TU's +// static maps may already be destroyed. +bool BgfxExitTeardownActive(); + +// Defined in BgfxBackendTextures.cpp. +bgfx::TextureHandle EnsureBgfxTexture(TextureBaseClass * tex, bool baseMipOnly = false); + +// Persistent texture pages for the sorted texture-array merge path. +// GetSlot lazily copies an eligible texture's CPU mip snapshots into a +// texture2DArray layer once and returns its page id, or -1 when the texture +// cannot live in a page (unsupported format/dims, page budget exhausted). +int BgfxSortedTextureArrayGetSlot(TextureBaseClass * texture, int * outLayer, float * outScaleU, float * outScaleV); +void BgfxSortedTextureArrayReleaseTexture(TextureBaseClass * texture); +bgfx::TextureHandle BgfxSortedTextureArrayPageHandle(int page); +void BgfxSortedTextureArrayShutdown(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackendTextures.cpp b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackendTextures.cpp new file mode 100644 index 00000000000..e5b1166416a --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxBackendTextures.cpp @@ -0,0 +1,2555 @@ +/* +** 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 +#include +#include + +#include + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include + +#include "texture.h" +#include "surfaceclass.h" +#include "WWDebug/wwdebug.h" + +#include "BgfxBackend.h" +#include "BgfxBackendState.h" +#include "DXTUtils.h" +#include "GgcRuntimeFlags.h" + +namespace +{ +static const bgfx::ViewId kBgfxRTTTextureCopyView = 3; + +enum BgfxTextureUploadVariant +{ + kBgfxTextureUploadNormal = 0, + kBgfxTextureUploadDxt5Expanded = 1, + kBgfxTextureUploadTerrainAtlas = 2, + kBgfxTextureUploadPackedAtlas = 3, + kBgfxTextureUploadRenderTargetCopy = 4, + kBgfxTextureUploadBaseMipOnly = 5, +}; + +static int ParseLimitValue(const char *value, int fallback) +{ + if (value == nullptr || *value == '\0') + { + return fallback; + } + const int parsed = std::atoi(value); + return parsed > 0 ? parsed : fallback; +} + +static bool ShouldLogEffectTexture(TextureClass * tex2d) +{ + if (!GgcFlags::Enabled(GgcFlag_EffectTextureDiag) || tex2d == nullptr) + { + return false; + } + + const char *name = tex2d->Get_Full_Path().str(); + return ContainsCaseInsensitive(name, "ex") + || ContainsCaseInsensitive(name, "fire") + || ContainsCaseInsensitive(name, "missile"); +} + +static void LogEffectTextureUpload(TextureClass *tex2d, + bgfx::TextureFormat::Enum bgfxFmt, + const TextureBaseClass::TextureMipSnapshot &mip, + const bgfx::Memory *mem, + unsigned expectedPitch, + unsigned numRows) +{ + if (!ShouldLogEffectTexture(tex2d) || mem == nullptr) + { + return; + } + + unsigned minAlpha = 255; + unsigned maxAlpha = 0; + unsigned nonZeroAlpha = 0; + if (bgfxFmt == bgfx::TextureFormat::BGRA8) + { + const unsigned pixelCount = (expectedPitch / 4) * numRows; + for (unsigned i = 0; i < pixelCount; ++i) + { + const unsigned alpha = mem->data[i * 4 + 3]; + minAlpha = alpha < minAlpha ? alpha : minAlpha; + maxAlpha = alpha > maxAlpha ? alpha : maxAlpha; + nonZeroAlpha += alpha != 0 ? 1 : 0; + } + } + + if (FILE *diag = std::fopen("ggc_effect_texture_diag.txt", "a")) + { + std::fprintf(diag, + "texture name=%s srcFmt=%d bgfxFmt=%d size=%ux%u pitch=%u rows=%u alphaMin=%u alphaMax=%u alphaNonZero=%u compressed=%d\n", + tex2d->Get_Full_Path().str(), + static_cast(mip.Format), + static_cast(bgfxFmt), + mip.Width, + mip.Height, + expectedPitch, + numRows, + minAlpha, + maxAlpha, + nonZeroAlpha, + (bgfxFmt == bgfx::TextureFormat::BC1 + || bgfxFmt == bgfx::TextureFormat::BC2 + || bgfxFmt == bgfx::TextureFormat::BC3) ? 1 : 0); + std::fclose(diag); + } +} + +static void DumpShroudTextureForDiagnostics(const uint8_t *data, + unsigned width, + unsigned height, + unsigned bpp, + WW3DFormat format) +{ + const char *dir = GgcFlags::StringValue(GgcFlag_BgfxShroudDumpDir); + if (dir == nullptr || *dir == '\0' || data == nullptr || width == 0 || height == 0) + { + return; + } + + static int s_dumpCount = 0; + const int limit = ParseLimitValue(GgcFlags::StringValue(GgcFlag_BgfxShroudDumpLimit), 8); + if (s_dumpCount >= limit) + { + return; + } + + char path[512]; + std::snprintf(path, sizeof(path), "%s/shroud_%03d.ppm", dir, s_dumpCount++); + FILE *file = std::fopen(path, "wb"); + if (file == nullptr) + { + return; + } + + std::fprintf(file, "P6\n%u %u\n255\n", width, height); + for (unsigned y = 0; y < height; ++y) + { + for (unsigned x = 0; x < width; ++x) + { + const uint8_t *p = data + (y * width + x) * bpp; + uint8_t rgb[3] = { 255, 255, 255 }; + if (format == WW3D_FORMAT_R5G6B5 && bpp == 2) + { + const uint16_t v = static_cast(p[0] | (p[1] << 8)); + rgb[0] = static_cast(((v >> 11) & 0x1f) * 255 / 31); + rgb[1] = static_cast(((v >> 5) & 0x3f) * 255 / 63); + rgb[2] = static_cast((v & 0x1f) * 255 / 31); + } + else if (format == WW3D_FORMAT_A4R4G4B4 && bpp == 2) + { + const uint16_t v = static_cast(p[0] | (p[1] << 8)); + rgb[0] = static_cast(((v >> 8) & 0x0f) * 17); + rgb[1] = static_cast(((v >> 4) & 0x0f) * 17); + rgb[2] = static_cast((v & 0x0f) * 17); + } + else if (bpp == 4) + { + rgb[0] = p[2]; + rgb[1] = p[1]; + rgb[2] = p[0]; + } + else if (bpp == 1) + { + rgb[0] = p[0]; + rgb[1] = p[0]; + rgb[2] = p[0]; + } + std::fwrite(rgb, 1, sizeof(rgb), file); + } + } + std::fclose(file); +} +} + +// External linkage so BgfxBackend.cpp can reference this from its +// Create_Texture implementation. +bgfx::TextureFormat::Enum TranslateWW3DFormat(WW3DFormat fmt) +{ + switch (fmt) + { + case WW3D_FORMAT_A8R8G8B8: + case WW3D_FORMAT_X8R8G8B8: + // Legacy ARGB is stored 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; + } +} + +bool BgfxBackend::Supports_Texture_Format(WW3DFormat format) const +{ + const bgfx::TextureFormat::Enum bgfxFormat = TranslateWW3DFormat(format); + if (bgfxFormat == bgfx::TextureFormat::Unknown) + { + return false; + } + + const bgfx::Caps * caps = bgfx::getCaps(); + return caps != nullptr + && (caps->formats[bgfxFormat] & BGFX_CAPS_FORMAT_TEXTURE_2D) != 0; +} + +bool BgfxBackend::Supports_Compressed_Textures() const +{ + return Supports_Texture_Format(WW3D_FORMAT_DXT1) + || Supports_Texture_Format(WW3D_FORMAT_DXT2) + || Supports_Texture_Format(WW3D_FORMAT_DXT3) + || Supports_Texture_Format(WW3D_FORMAT_DXT4) + || Supports_Texture_Format(WW3D_FORMAT_DXT5); +} + +// TheSuperHackers @refactor bobtista 20/04/2026 The legacy renderer 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; + } +} + +static void ApplyTeamColorTextureKey(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; + } + + const char * texName = tex2d->Get_Full_Path().str(); + if (texName == nullptr || texName[0] != '#') + { + return; + } + + uint8_t * pix = mem->data; + const unsigned pixelCount = expectedPitch / 4 * numRows; + for (unsigned i = 0; i < pixelCount; ++i) + { + // TheSuperHackers @fix bobtista 29/04/2026 Team-colored textures + // use exact black RGB as a transparent matte in the legacy path. + if (pix[i * 4 + 0] == 0 && pix[i * 4 + 1] == 0 && pix[i * 4 + 2] == 0) + { + pix[i * 4 + 3] = 0; + } + } +} + +static bool IsCompressedBgfxFormat(bgfx::TextureFormat::Enum bgfxFmt) +{ + return bgfxFmt == bgfx::TextureFormat::BC1 + || bgfxFmt == bgfx::TextureFormat::BC2 + || bgfxFmt == bgfx::TextureFormat::BC3; +} + +static unsigned GetBytesPerPixel(bgfx::TextureFormat::Enum bgfxFmt) +{ + switch (bgfxFmt) + { + case bgfx::TextureFormat::BGRA4: + case bgfx::TextureFormat::R5G6B5: + case bgfx::TextureFormat::BGR5A1: + return 2; + case bgfx::TextureFormat::A8: + case bgfx::TextureFormat::R8: + return 1; + default: + return 4; + } +} + +static bgfx::TextureFormat::Enum GetBgfxTextureUploadFormat(WW3DFormat fmt) +{ + if (fmt == WW3D_FORMAT_A4R4G4B4) + { + // D3D A4R4G4B4 is packed as 0xARGB. bgfx::BGRA4 is not a reliable + // byte-for-byte substitute across renderers, so expand authored 4-bit + // alpha textures to BGRA8 before upload. This preserves transparent + // matte pixels used by projected decals such as spy satellite grids. + return bgfx::TextureFormat::BGRA8; + } +#if !defined(_WIN32) + if (fmt == WW3D_FORMAT_A1R5G5B5) + { + // TheSuperHackers @bugfix 30/07/2026 BGR5A1 is not a native GLES + // format. Emitting it leaves bgfx to convert on upload, which over-reads the + // source buffer, and what reaches the screen is the terrain atlas - the game's + // only large A1R5G5B5 surface - rendered as coloured speckle. Expand to BGRA8 + // up front instead, exactly like the A4R4G4B4 path above. Windows keeps the + // native 16-bit upload. + return bgfx::TextureFormat::BGRA8; + } +#endif + return TranslateWW3DFormat(fmt); +} + +static bgfx::TextureFormat::Enum GetBgfxTextureUploadFormat(TextureClass * tex2d) +{ + return GetBgfxTextureUploadFormat(tex2d != nullptr ? tex2d->Get_Texture_Format() : WW3D_FORMAT_UNKNOWN); +} + +static void BuildDXT5AlphaTable(const uint8_t *block, uint8_t *alpha) +{ + alpha[0] = block[0]; + alpha[1] = block[1]; + if (alpha[0] > alpha[1]) + { + for (unsigned i = 1; i < 7; ++i) + { + alpha[i + 1] = static_cast(((7 - i) * alpha[0] + i * alpha[1] + 3) / 7); + } + } + else + { + for (unsigned i = 1; i < 5; ++i) + { + alpha[i + 1] = static_cast(((5 - i) * alpha[0] + i * alpha[1] + 2) / 5); + } + alpha[6] = 0; + alpha[7] = 255; + } +} + +static bool DXT5UsesNonOpaqueAlpha(const TextureBaseClass::TextureMipSnapshot &mip) +{ + if (mip.Format != WW3D_FORMAT_DXT5 || mip.Data.empty() || mip.Width == 0 || mip.Height == 0) + { + return false; + } + + const unsigned blockRows = (mip.Height + 3) / 4; + const unsigned blockCols = (mip.Width + 3) / 4; + const unsigned minPitch = blockCols * 16; + if (mip.Pitch < minPitch || mip.Data.size() < (blockRows - 1) * mip.Pitch + minPitch) + { + return true; + } + + for (unsigned by = 0; by < blockRows; ++by) + { + const uint8_t *row = &mip.Data[0] + by * mip.Pitch; + for (unsigned bx = 0; bx < blockCols; ++bx) + { + const uint8_t *block = row + bx * 16; + uint8_t alpha[8]; + BuildDXT5AlphaTable(block, alpha); + + uint64_t alphaBits = 0; + for (unsigned i = 0; i < 6; ++i) + { + alphaBits |= static_cast(block[2 + i]) << (8 * i); + } + + for (unsigned pixel = 0; pixel < 16; ++pixel) + { + const unsigned alphaIndex = static_cast((alphaBits >> (3 * pixel)) & 0x7); + if (alpha[alphaIndex] < 255) + { + return true; + } + } + } + } + + return false; +} + +static bool DXT1UsesTransparentIndex(const TextureBaseClass::TextureMipSnapshot &mip) +{ + if (mip.Format != WW3D_FORMAT_DXT1 || mip.Data.empty() || mip.Width == 0 || mip.Height == 0) + { + return false; + } + + const unsigned blockRows = DXT_SurfaceRows(mip.Height); + const unsigned rowPitch = DXT_SurfacePitch(mip.Width, 8); + if (mip.Pitch < rowPitch) + { + return true; + } + const size_t requiredBytes = blockRows > 0 + ? (static_cast(blockRows - 1) * mip.Pitch + rowPitch) + : 0; + if (mip.Data.size() < requiredBytes) + { + return true; + } + + const unsigned blockCols = (mip.Width + 3) / 4; + for (unsigned by = 0; by < blockRows; ++by) + { + const uint8_t *row = &mip.Data[0] + by * mip.Pitch; + for (unsigned bx = 0; bx < blockCols; ++bx) + { + const uint8_t *block = row + bx * 8; + const uint16_t c0 = static_cast(block[0] | (block[1] << 8)); + const uint16_t c1 = static_cast(block[2] | (block[3] << 8)); + if (c0 > c1) + { + continue; + } + + const uint32_t bits = static_cast(block[4]) + | (static_cast(block[5]) << 8) + | (static_cast(block[6]) << 16) + | (static_cast(block[7]) << 24); + for (unsigned pixel = 0; pixel < 16; ++pixel) + { + if (((bits >> (2 * pixel)) & 0x3) == 3) + { + return true; + } + } + } + } + + return false; +} + +static bool ShouldUseBaseMipForThinDxt1Strip(const std::vector &mips) +{ + if (mips.size() <= 1 || mips[0].Format != WW3D_FORMAT_DXT1) + { + return false; + } + + const unsigned width = mips[0].Width; + const unsigned height = mips[0].Height; + const unsigned major = (width > height) ? width : height; + const unsigned minor = (width > height) ? height : width; + static const unsigned kMinStripAspectRatio = 8; + static const unsigned kMaxStripMinorExtent = 32; + if (minor == 0 || major / minor < kMinStripAspectRatio || minor > kMaxStripMinorExtent) + { + return false; + } + + // WW3D uses some high-aspect DXT1 mesh textures as compact strip atlases. + // Their authored lower mips can collapse thin dark details across the strip, + // while the base level stays stable. Keep this data-driven: only opaque DXT1 + // strips get the one-mip path, and true one-bit alpha DXT1 textures keep + // their authored chain. + return !DXT1UsesTransparentIndex(mips[0]); +} + +static void ExpandA4R4G4B4ToBGRA8(const uint8_t * srcRow, unsigned srcPitch, + unsigned width, unsigned height, const bgfx::Memory * mem) +{ + uint8_t * dst = mem->data; + for (unsigned y = 0; y < height; ++y) + { + const uint16_t * src = reinterpret_cast(srcRow); + for (unsigned x = 0; x < width; ++x) + { + const uint16_t p = src[x]; + const uint8_t a = static_cast(((p >> 12) & 0x0f) * 17); + const uint8_t r = static_cast(((p >> 8) & 0x0f) * 17); + const uint8_t g = static_cast(((p >> 4) & 0x0f) * 17); + const uint8_t b = static_cast((p & 0x0f) * 17); + dst[0] = b; + dst[1] = g; + dst[2] = r; + dst[3] = a; + dst += 4; + } + srcRow += srcPitch; + } +} + +// TheSuperHackers @bugfix 30/07/2026 Companion to the A1R5G5B5 case in +// GetBgfxTextureUploadFormat: widen each 5-bit channel to 8 bits (replicating the top +// bits so 0x1f maps to 0xff) and the 1-bit alpha to fully opaque or transparent. +static void ExpandA1R5G5B5ToBGRA8(const uint8_t * srcRow, unsigned srcPitch, + unsigned width, unsigned height, const bgfx::Memory * mem) +{ + uint8_t * dst = mem->data; + for (unsigned y = 0; y < height; ++y) + { + const uint16_t * src = reinterpret_cast(srcRow); + for (unsigned x = 0; x < width; ++x) + { + const uint16_t p = src[x]; + const uint8_t a = (p & 0x8000) ? 0xff : 0x00; + const uint8_t r5 = static_cast((p >> 10) & 0x1f); + const uint8_t g5 = static_cast((p >> 5) & 0x1f); + const uint8_t b5 = static_cast(p & 0x1f); + const uint8_t r8 = static_cast((r5 << 3) | (r5 >> 2)); + const uint8_t g8 = static_cast((g5 << 3) | (g5 >> 2)); + const uint8_t b8 = static_cast((b5 << 3) | (b5 >> 2)); + dst[0] = b8; + dst[1] = g8; + dst[2] = r8; + dst[3] = a; + dst += 4; + } + srcRow += srcPitch; + } +} + +static void DecodeRgb565(uint16_t value, uint8_t *rgb) +{ + rgb[0] = static_cast(((value >> 11) & 0x1F) * 255 / 31); + rgb[1] = static_cast(((value >> 5) & 0x3F) * 255 / 63); + rgb[2] = static_cast((value & 0x1F) * 255 / 31); +} + +static void ExpandDXT5ToBGRA8(const uint8_t *src, + unsigned srcPitch, + unsigned width, + unsigned height, + uint8_t *dst) +{ + const unsigned blockRows = (height + 3) / 4; + const unsigned blockCols = (width + 3) / 4; + + for (unsigned by = 0; by < blockRows; ++by) + { + const uint8_t *srcBlockRow = src + by * srcPitch; + for (unsigned bx = 0; bx < blockCols; ++bx) + { + const uint8_t *block = srcBlockRow + bx * 16; + + uint8_t alpha[8]; + BuildDXT5AlphaTable(block, alpha); + + uint64_t alphaBits = 0; + for (unsigned i = 0; i < 6; ++i) + { + alphaBits |= static_cast(block[2 + i]) << (8 * i); + } + + const uint16_t c0 = static_cast(block[8] | (block[9] << 8)); + const uint16_t c1 = static_cast(block[10] | (block[11] << 8)); + uint8_t color[4][3]; + DecodeRgb565(c0, color[0]); + DecodeRgb565(c1, color[1]); + for (unsigned c = 0; c < 3; ++c) + { + color[2][c] = static_cast((2 * color[0][c] + color[1][c] + 1) / 3); + color[3][c] = static_cast((color[0][c] + 2 * color[1][c] + 1) / 3); + } + + const uint32_t colorBits = static_cast(block[12]) + | (static_cast(block[13]) << 8) + | (static_cast(block[14]) << 16) + | (static_cast(block[15]) << 24); + + for (unsigned py = 0; py < 4; ++py) + { + const unsigned y = by * 4 + py; + if (y >= height) + { + continue; + } + for (unsigned px = 0; px < 4; ++px) + { + const unsigned x = bx * 4 + px; + if (x >= width) + { + continue; + } + const unsigned pixel = py * 4 + px; + const unsigned alphaIndex = static_cast((alphaBits >> (3 * pixel)) & 0x7); + const unsigned colorIndex = static_cast((colorBits >> (2 * pixel)) & 0x3); + uint8_t *out = dst + (y * width + x) * 4; + out[0] = color[colorIndex][2]; + out[1] = color[colorIndex][1]; + out[2] = color[colorIndex][0]; + out[3] = alpha[alphaIndex]; + } + } + } + } +} + +// TheSuperHackers @feature bobtista 08/07/2026 DXT1/DXT3 expanders for the +// sorted texture-array pages. Color-block layout matches DXT5's color half; +// DXT3 carries 16 explicit 4-bit alphas, DXT1 is opaque or 1-bit punch-through. +static void ExpandDXT3ToBGRA8(const uint8_t *src, + unsigned srcPitch, + unsigned width, + unsigned height, + uint8_t *dst) +{ + const unsigned blockRows = (height + 3) / 4; + const unsigned blockCols = (width + 3) / 4; + for (unsigned by = 0; by < blockRows; ++by) + { + const uint8_t *srcBlockRow = src + by * srcPitch; + for (unsigned bx = 0; bx < blockCols; ++bx) + { + const uint8_t *block = srcBlockRow + bx * 16; + + const uint16_t c0 = static_cast(block[8] | (block[9] << 8)); + const uint16_t c1 = static_cast(block[10] | (block[11] << 8)); + uint8_t color[4][3]; + DecodeRgb565(c0, color[0]); + DecodeRgb565(c1, color[1]); + for (unsigned c = 0; c < 3; ++c) + { + color[2][c] = static_cast((2 * color[0][c] + color[1][c] + 1) / 3); + color[3][c] = static_cast((color[0][c] + 2 * color[1][c] + 1) / 3); + } + + const uint32_t colorBits = static_cast(block[12]) + | (static_cast(block[13]) << 8) + | (static_cast(block[14]) << 16) + | (static_cast(block[15]) << 24); + + for (unsigned py = 0; py < 4; ++py) + { + const unsigned y = by * 4 + py; + if (y >= height) + { + continue; + } + for (unsigned px = 0; px < 4; ++px) + { + const unsigned x = bx * 4 + px; + if (x >= width) + { + continue; + } + const unsigned pixel = py * 4 + px; + const uint8_t alphaByte = block[pixel / 2]; + const uint8_t nibble = (pixel & 1) ? (alphaByte >> 4) : (alphaByte & 0x0F); + const unsigned colorIndex = static_cast((colorBits >> (2 * pixel)) & 0x3); + uint8_t *out = dst + (y * width + x) * 4; + out[0] = color[colorIndex][2]; + out[1] = color[colorIndex][1]; + out[2] = color[colorIndex][0]; + out[3] = static_cast(nibble * 17); + } + } + } + } +} + +static bool IsTerrainAtlasTexture(TextureClass * tex2d, + WW3DFormat sourceFmt, + bgfx::TextureFormat::Enum bgfxFmt) +{ + // TheSuperHackers @bugfix bobtista 28/04/2026 The terrain texture is a + // sparse tile atlas with black unused space between classes. The legacy + // terrain path relies on tile-aware source mips and authored borders, + // while bgfx creates a full mip chain whenever mips are enabled. Upload a + // complete atlas-safe chain only for textures explicitly tagged by the + // terrain atlas builder. + // TheSuperHackers @bugfix 30/07/2026 Also accept the format the + // atlas is expanded to off Windows (see GetBgfxTextureUploadFormat), or the + // terrain would silently lose its atlas-safe mip chain there. + return tex2d != nullptr + && sourceFmt == WW3D_FORMAT_A1R5G5B5 + && (bgfxFmt == bgfx::TextureFormat::BGR5A1 + || bgfxFmt == bgfx::TextureFormat::BGRA8) + && tex2d->Has_Atlas_Regions(); +} + +static unsigned GetFullMipCount(unsigned width, unsigned height) +{ + unsigned count = 1; + while (width > 1 || height > 1) + { + width = (width > 1) ? width >> 1 : 1; + height = (height > 1) ? height >> 1 : 1; + ++count; + } + return count; +} + +static bool IsTerrainAtlasRegionPixelValid(const std::vector & regions, + unsigned x, unsigned y, unsigned level) +{ + const unsigned scale = 1u << level; + for (unsigned i = 0; i < regions.size(); ++i) + { + const TextureClass::TextureAtlasRegion & region = regions[i]; + const unsigned x0 = region.X / scale; + const unsigned y0 = region.Y / scale; + const unsigned x1 = (region.X + region.Width + scale - 1) / scale; + const unsigned y1 = (region.Y + region.Height + scale - 1) / scale; + if (x >= x0 && x < x1 && y >= y0 && y < y1) + { + return true; + } + } + + return false; +} + +static uint16_t FilterTerrainAtlasA1R5G5B5(const uint16_t samples[4], const bool validSamples[4], bool & valid) +{ + unsigned count = 0; + unsigned red = 0; + unsigned green = 0; + unsigned blue = 0; + for (unsigned i = 0; i < 4; ++i) + { + if (validSamples[i]) + { + count++; + red += (samples[i] >> 10) & 0x1f; + green += (samples[i] >> 5) & 0x1f; + blue += samples[i] & 0x1f; + } + } + if (count == 0) + { + valid = false; + return 0; + } + valid = true; + red = (red + count / 2) / count; + green = (green + count / 2) / count; + blue = (blue + count / 2) / count; + return static_cast(0x8000 | (red << 10) | (green << 5) | blue); +} + +static void BuildTerrainAtlasMip(const std::vector & src, + unsigned srcWidth, unsigned srcHeight, std::vector & dst, + std::vector & validMask, unsigned dstWidth, + unsigned dstHeight, const std::vector & regions, + unsigned sourceLevel) +{ + for (unsigned y = 0; y < dstHeight; ++y) + { + const unsigned y0 = y * 2; + const unsigned y1 = (y0 + 1 < srcHeight) ? y0 + 1 : y0; + for (unsigned x = 0; x < dstWidth; ++x) + { + const unsigned x0 = x * 2; + const unsigned x1 = (x0 + 1 < srcWidth) ? x0 + 1 : x0; + const uint16_t samples[4] = { + src[y0 * srcWidth + x0], + src[y0 * srcWidth + x1], + src[y1 * srcWidth + x0], + src[y1 * srcWidth + x1] + }; + const bool validSamples[4] = { + IsTerrainAtlasRegionPixelValid(regions, x0, y0, sourceLevel), + IsTerrainAtlasRegionPixelValid(regions, x1, y0, sourceLevel), + IsTerrainAtlasRegionPixelValid(regions, x0, y1, sourceLevel), + IsTerrainAtlasRegionPixelValid(regions, x1, y1, sourceLevel) + }; + bool valid = false; + dst[y * dstWidth + x] = FilterTerrainAtlasA1R5G5B5(samples, validSamples, valid); + validMask[y * dstWidth + x] = valid ? 1 : 0; + } + } +} + +static void BleedTerrainAtlasMipGaps(std::vector & pixels, + std::vector & validMask, unsigned width, unsigned height) +{ + if (width == 0 || height == 0) + { + return; + } + + bool anyValid = false; + for (unsigned i = 0; i < width * height; ++i) + { + if (validMask[i] != 0) + { + anyValid = true; + break; + } + } + if (!anyValid) + { + return; + } + + uint16_t fallback = 0; + for (unsigned i = 0; i < width * height; ++i) + { + if (validMask[i] != 0) + { + fallback = pixels[i]; + break; + } + } + + for (unsigned y = 0; y < height; ++y) + { + uint16_t last = 0; + for (unsigned x = 0; x < width; ++x) + { + const unsigned index = y * width + x; + if (validMask[index] != 0) + { + last = pixels[index]; + } + else if (last != 0) + { + pixels[index] = last; + validMask[index] = 1; + } + } + + last = 0; + for (unsigned x = width; x > 0; --x) + { + const unsigned index = y * width + x - 1; + if (validMask[index] != 0) + { + last = pixels[index]; + } + else if (last != 0) + { + pixels[index] = last; + validMask[index] = 1; + } + } + } + + for (unsigned x = 0; x < width; ++x) + { + uint16_t last = 0; + for (unsigned y = 0; y < height; ++y) + { + const unsigned index = y * width + x; + if (validMask[index] != 0) + { + last = pixels[index]; + } + else if (last != 0) + { + pixels[index] = last; + validMask[index] = 1; + } + } + + last = 0; + for (unsigned y = height; y > 0; --y) + { + const unsigned index = (y - 1) * width + x; + if (validMask[index] != 0) + { + last = pixels[index]; + } + else if (last != 0) + { + pixels[index] = last; + validMask[index] = 1; + } + } + } + + for (unsigned i = 0; i < width * height; ++i) + { + if (validMask[i] == 0) + { + pixels[i] = fallback; + } + } +} + +static bool CopyTextureLevel(TextureClass * tex2d, + bgfx::TextureFormat::Enum bgfxFmt, + const TextureBaseClass::TextureMipSnapshot & mip, + unsigned level, + bgfx::Memory const ** outMem, + uint16_t * outWidth, + uint16_t * outHeight) +{ + (void)level; + if (tex2d == nullptr || outMem == nullptr + || outWidth == nullptr || outHeight == nullptr) + { + return false; + } + + if (mip.Data.empty() || mip.Width == 0 || mip.Height == 0) + { + return false; + } + + const bool isCompressed = IsCompressedBgfxFormat(bgfxFmt); + unsigned expectedPitch = 0; + unsigned numRows = 0; + if (isCompressed) + { + const unsigned blockSize = (bgfxFmt == bgfx::TextureFormat::BC1) ? 8 : 16; + expectedPitch = DXT_SurfacePitch(mip.Width, blockSize); + numRows = DXT_SurfaceRows(mip.Height); + } + else + { + expectedPitch = mip.Width * GetBytesPerPixel(bgfxFmt); + numRows = mip.Height; + } + + const unsigned totalBytes = numRows * expectedPitch; + const unsigned srcPitch = mip.Pitch; + const bool expandDXT5ToBGRA8 = + mip.Format == WW3D_FORMAT_DXT5 + && bgfxFmt == bgfx::TextureFormat::BGRA8 + && !isCompressed; + const unsigned requiredSourcePitch = expandDXT5ToBGRA8 + ? DXT_SurfacePitch(mip.Width, 16) + : expectedPitch; + if ((isCompressed || expandDXT5ToBGRA8) && srcPitch < requiredSourcePitch) + { + return false; + } + const unsigned requiredSourceRows = + expandDXT5ToBGRA8 ? DXT_SurfaceRows(mip.Height) : numRows; + const unsigned lastRowBytes = (isCompressed || expandDXT5ToBGRA8) + ? requiredSourcePitch + : ((srcPitch < expectedPitch) ? srcPitch : expectedPitch); + const size_t requiredBytes = requiredSourceRows > 0 + ? (static_cast(requiredSourceRows - 1) * srcPitch + lastRowBytes) + : 0; + if (mip.Data.size() < requiredBytes) + { + return false; + } + const bgfx::Memory * mem = bgfx::alloc(totalBytes); + if (mip.Format == WW3D_FORMAT_A4R4G4B4 + && bgfxFmt == bgfx::TextureFormat::BGRA8 + && !isCompressed) + { + ExpandA4R4G4B4ToBGRA8(&mip.Data[0], srcPitch, mip.Width, mip.Height, mem); + } + else if (mip.Format == WW3D_FORMAT_A1R5G5B5 + && bgfxFmt == bgfx::TextureFormat::BGRA8 + && !isCompressed) + { + ExpandA1R5G5B5ToBGRA8(&mip.Data[0], srcPitch, mip.Width, mip.Height, mem); + } + else if (expandDXT5ToBGRA8) + { + ExpandDXT5ToBGRA8(&mip.Data[0], srcPitch, mip.Width, mip.Height, mem->data); + } + else if (srcPitch == expectedPitch) + { + std::memcpy(mem->data, &mip.Data[0], totalBytes); + } + else + { + const unsigned copyPitch = (srcPitch < expectedPitch) ? srcPitch : expectedPitch; + const uint8_t * src = &mip.Data[0]; + 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; + } + } + + if (!isCompressed) + { + ApplyTeamColorTextureKey(tex2d, bgfxFmt, mem, expectedPitch, numRows); + ForceOpaqueIfProceduralX8R8G8B8(tex2d, bgfxFmt, mem, expectedPitch, numRows); + } + LogEffectTextureUpload(tex2d, bgfxFmt, mip, mem, expectedPitch, numRows); + + *outMem = mem; + *outWidth = static_cast(mip.Width); + *outHeight = static_cast(mip.Height); + g_stats.textureCopies++; + return true; +} + +// TheSuperHackers @bugfix 30/07/2026 The atlas is filtered as 16-bit +// A1R5G5B5 throughout - that is what the tile-aware mip builder works on - but off +// Windows the texture itself is created as BGRA8 (see GetBgfxTextureUploadFormat), so +// every level has to be widened before it is handed to bgfx. Uploading the 16-bit +// buffer into a 32-bit texture is what left the terrain as coloured speckle. +static bool UploadTerrainAtlasMips(TextureClass * tex2d, + bgfx::TextureHandle h, const std::vector & mips, + bgfx::TextureFormat::Enum bgfxFmt) +{ + const bool expandToBGRA8 = (bgfxFmt == bgfx::TextureFormat::BGRA8); + std::vector prev; + unsigned prevWidth = 0; + unsigned prevHeight = 0; + unsigned fullMipCount = static_cast(mips.size()); + + for (unsigned mip = 0; mip < mips.size(); ++mip) + { + const bgfx::Memory * mem = nullptr; + uint16_t mipWidth = 0; + uint16_t mipHeight = 0; + // CopyTextureLevel expands A1R5G5B5 to the wide format itself, so ask it for + // whatever is actually being uploaded rather than converting a second buffer + // afterwards - bgfx has no way to hand an unused allocation back. + if (!CopyTextureLevel(tex2d, bgfxFmt, mips[mip], mip, + &mem, &mipWidth, &mipHeight)) + { + return false; + } + // The tile-aware mip builder below works on A1R5G5B5 whatever gets uploaded, + // so keep its 16-bit input from the source snapshot, honouring its pitch. + const TextureBaseClass::TextureMipSnapshot & srcMip = mips[mip]; + prev.resize(static_cast(mipWidth) * mipHeight); + for (unsigned y = 0; y < mipHeight; ++y) + { + std::memcpy(&prev[static_cast(y) * mipWidth], + &srcMip.Data[static_cast(y) * srcMip.Pitch], + static_cast(mipWidth) * sizeof(uint16_t)); + } + prevWidth = mipWidth; + prevHeight = mipHeight; + bgfx::updateTexture2D(h, 0, static_cast(mip), 0, 0, + mipWidth, mipHeight, mem); + g_stats.textureUploads++; + if (mip == 0) + { + fullMipCount = GetFullMipCount(mipWidth, mipHeight); + } + } + + for (unsigned mip = static_cast(mips.size()); mip < fullMipCount; ++mip) + { + const unsigned nextWidth = (prevWidth > 1) ? prevWidth >> 1 : 1; + const unsigned nextHeight = (prevHeight > 1) ? prevHeight >> 1 : 1; + std::vector next(nextWidth * nextHeight); + std::vector validMask(nextWidth * nextHeight); + BuildTerrainAtlasMip(prev, prevWidth, prevHeight, next, validMask, + nextWidth, nextHeight, tex2d->Get_Atlas_Regions(), mip - 1); + BleedTerrainAtlasMipGaps(next, validMask, nextWidth, nextHeight); + const bgfx::Memory * nextMem = nullptr; + unsigned nextBytesPerPixel = static_cast(sizeof(uint16_t)); + if (expandToBGRA8) + { + nextMem = bgfx::alloc(static_cast(nextWidth) * nextHeight * 4); + ExpandA1R5G5B5ToBGRA8(reinterpret_cast(&next[0]), + static_cast(nextWidth * sizeof(uint16_t)), + nextWidth, nextHeight, nextMem); + nextBytesPerPixel = 4; + } + else + { + nextMem = bgfx::copy(&next[0], static_cast(next.size() * sizeof(uint16_t))); + } + bgfx::updateTexture2D(h, 0, static_cast(mip), 0, 0, + static_cast(nextWidth), static_cast(nextHeight), + nextMem, static_cast(nextWidth * nextBytesPerPixel)); + g_stats.textureUploads++; + prev.swap(next); + prevWidth = nextWidth; + prevHeight = nextHeight; + } + return true; +} + +// TheSuperHackers @bugfix bobtista 18/05/2026 The ubsnkatak_0 mesh atlas is a +// tightly packed 2x2 grid (scorpion | sand / metal | wood). Pre-authored DXT1 +// mips average across quadrant boundaries, and hardware bilinear/aniso filtering +// at mip 0 samples across the boundary at v=0.5. This causes the wing (bottom- +// left, grey metal) to bleed red from the adjacent top-left (GLA scorpion). +// Fix: decompress BC1 to BGRA8, add a small gutter at quadrant boundaries, and +// generate per-quadrant-safe mipmaps (same principle as UploadTerrainAtlasMips). + +static bool IsPackedMeshAtlasTexture(TextureClass *tex2d, WW3DFormat sourceFmt) +{ + if (tex2d == nullptr || sourceFmt != WW3D_FORMAT_DXT1) + { + return false; + } + const char *name = tex2d->Get_Full_Path().str(); + if (ContainsCaseInsensitive(name, "ubsnkatak_0")) + { + return true; + } + return false; +} + +static void ExpandDXT1ToBGRA8(const uint8_t *src, unsigned srcPitch, + unsigned width, unsigned height, uint8_t *dst) +{ + const unsigned blockRows = (height + 3) / 4; + const unsigned blockCols = (width + 3) / 4; + + for (unsigned by = 0; by < blockRows; ++by) + { + const uint8_t *srcBlockRow = src + by * srcPitch; + for (unsigned bx = 0; bx < blockCols; ++bx) + { + const uint8_t *block = srcBlockRow + bx * 8; + + const uint16_t c0 = static_cast(block[0] | (block[1] << 8)); + const uint16_t c1 = static_cast(block[2] | (block[3] << 8)); + uint8_t color[4][3]; + uint8_t alpha[4]; + DecodeRgb565(c0, color[0]); + DecodeRgb565(c1, color[1]); + + if (c0 > c1) + { + for (unsigned c = 0; c < 3; ++c) + { + color[2][c] = static_cast((2 * color[0][c] + color[1][c] + 1) / 3); + color[3][c] = static_cast((color[0][c] + 2 * color[1][c] + 1) / 3); + } + alpha[0] = alpha[1] = alpha[2] = alpha[3] = 255; + } + else + { + for (unsigned c = 0; c < 3; ++c) + { + color[2][c] = static_cast((color[0][c] + color[1][c]) / 2); + color[3][c] = 0; + } + alpha[0] = alpha[1] = alpha[2] = 255; + alpha[3] = 0; + } + + const uint32_t bits = static_cast(block[4]) + | (static_cast(block[5]) << 8) + | (static_cast(block[6]) << 16) + | (static_cast(block[7]) << 24); + + for (unsigned py = 0; py < 4; ++py) + { + const unsigned y = by * 4 + py; + if (y >= height) + { + continue; + } + for (unsigned px = 0; px < 4; ++px) + { + const unsigned x = bx * 4 + px; + if (x >= width) + { + continue; + } + const unsigned idx = (bits >> (2 * (py * 4 + px))) & 0x3; + uint8_t *out = dst + (y * width + x) * 4; + out[0] = color[idx][2]; // B + out[1] = color[idx][1]; // G + out[2] = color[idx][0]; // R + out[3] = alpha[idx]; // A + } + } + } + } +} + +static void AddPackedAtlasGutter(uint8_t *pixels, unsigned width, unsigned height, + unsigned gutter) +{ + const unsigned halfW = width / 2; + const unsigned halfH = height / 2; + const unsigned bpp = 4; + const unsigned stride = width * bpp; + + // Protect bottom quadrants from top-quadrant bleed at y = halfH: + // copy row halfH+g into row halfH-1-g. + for (unsigned g = 0; g < gutter && g < halfH; ++g) + { + std::memcpy(pixels + (halfH - 1 - g) * stride, + pixels + (halfH + g) * stride, stride); + } + + // Protect top quadrants from bottom-quadrant bleed: + // copy row halfH-1-g into row halfH+g. + // (Must happen after the above so we read the already-overwritten copies + // which now hold bottom-quadrant colors — that is intentional: both sides + // of the seam now match.) + + // Protect right quadrants from left-quadrant bleed at x = halfW: + // copy column halfW-1-g into column halfW+g. + for (unsigned y = 0; y < height; ++y) + { + for (unsigned g = 0; g < gutter && g < halfW; ++g) + { + std::memcpy(pixels + (y * width + halfW + g) * bpp, + pixels + (y * width + halfW - 1 - g) * bpp, bpp); + } + } + + // Protect left quadrants from right-quadrant bleed: + // copy column halfW+g into column halfW-1-g. + for (unsigned y = 0; y < height; ++y) + { + for (unsigned g = 0; g < gutter && g < halfW; ++g) + { + std::memcpy(pixels + (y * width + halfW - 1 - g) * bpp, + pixels + (y * width + halfW + g) * bpp, bpp); + } + } +} + +static void BuildPackedAtlasMip(const uint8_t *src, unsigned srcW, unsigned srcH, + uint8_t *dst, unsigned dstW, unsigned dstH, + unsigned boundaryX, unsigned boundaryY) +{ + for (unsigned y = 0; y < dstH; ++y) + { + const unsigned sy0 = y * 2; + const unsigned sy1 = (sy0 + 1 < srcH) ? sy0 + 1 : sy0; + for (unsigned x = 0; x < dstW; ++x) + { + const unsigned sx0 = x * 2; + const unsigned sx1 = (sx0 + 1 < srcW) ? sx0 + 1 : sx0; + + const bool leftOfBoundary = (sx0 < boundaryX); + const bool aboveBoundary = (sy0 < boundaryY); + + const unsigned coords[4][2] = { + { sx0, sy0 }, { sx1, sy0 }, { sx0, sy1 }, { sx1, sy1 } + }; + + for (unsigned c = 0; c < 4; ++c) + { + unsigned sum = 0; + unsigned count = 0; + for (unsigned s = 0; s < 4; ++s) + { + const bool sameX = (leftOfBoundary == (coords[s][0] < boundaryX)) + || (boundaryX == 0); + const bool sameY = (aboveBoundary == (coords[s][1] < boundaryY)) + || (boundaryY == 0); + if (sameX && sameY) + { + sum += src[(coords[s][1] * srcW + coords[s][0]) * 4 + c]; + count++; + } + } + if (count == 0) + { + count = 1; + } + dst[(y * dstW + x) * 4 + c] = static_cast( + (sum + count / 2) / count); + } + } + } +} + +static bool UploadPackedAtlasMips(TextureClass *tex2d, bgfx::TextureHandle h, + const std::vector &mips) +{ + if (mips.empty() || mips[0].Data.empty()) + { + return false; + } + + const TextureBaseClass::TextureMipSnapshot &baseMip = mips[0]; + const unsigned width = baseMip.Width; + const unsigned height = baseMip.Height; + const unsigned bpp = 4; + const unsigned pixelBytes = width * height * bpp; + + const unsigned blockRows = (height + 3) / 4; + const unsigned blockCols = (width + 3) / 4; + const unsigned srcPitch = blockCols * 8; + if (baseMip.Pitch < srcPitch) + { + return false; + } + const size_t requiredBytes = blockRows > 0 + ? (static_cast(blockRows - 1) * baseMip.Pitch + srcPitch) + : 0; + if (baseMip.Data.size() < requiredBytes) + { + return false; + } + + std::vector prev(pixelBytes); + ExpandDXT1ToBGRA8(&baseMip.Data[0], baseMip.Pitch, width, height, &prev[0]); + + static const unsigned kGutter = 16; + AddPackedAtlasGutter(&prev[0], width, height, kGutter); + + const bgfx::Memory *mem = bgfx::copy(&prev[0], static_cast(pixelBytes)); + bgfx::updateTexture2D(h, 0, 0, 0, 0, + static_cast(width), static_cast(height), mem); + g_stats.textureUploads++; + + const unsigned fullMipCount = GetFullMipCount(width, height); + unsigned prevW = width; + unsigned prevH = height; + unsigned boundaryX = width / 2; + unsigned boundaryY = height / 2; + + for (unsigned mip = 1; mip < fullMipCount; ++mip) + { + const unsigned nextW = (prevW > 1) ? prevW >> 1 : 1; + const unsigned nextH = (prevH > 1) ? prevH >> 1 : 1; + std::vector next(nextW * nextH * bpp); + BuildPackedAtlasMip(&prev[0], prevW, prevH, &next[0], nextW, nextH, + boundaryX, boundaryY); + const bgfx::Memory *nextMem = bgfx::copy(&next[0], + static_cast(next.size())); + bgfx::updateTexture2D(h, 0, static_cast(mip), 0, 0, + static_cast(nextW), static_cast(nextH), nextMem); + g_stats.textureUploads++; + prev.swap(next); + prevW = nextW; + prevH = nextH; + boundaryX = (boundaryX > 1) ? boundaryX >> 1 : 0; + boundaryY = (boundaryY > 1) ? boundaryY >> 1 : 0; + } + + return true; +} + +static TextureCacheInfo MakeTextureCacheInfo(unsigned revision, + TextureClass *tex2d, + const TextureBaseClass::TextureMipSnapshot &baseMip, + const std::vector &mips, + bgfx::TextureFormat::Enum bgfxFmt, + bool baseMipOnly) +{ + const bool terrainAtlasSafeMips = tex2d != nullptr + && IsTerrainAtlasTexture(tex2d, baseMip.Format, bgfxFmt); + const bool packedMeshAtlas = tex2d != nullptr + && IsPackedMeshAtlasTexture(tex2d, baseMip.Format); + const bool dxt5Expanded = DXT5UsesNonOpaqueAlpha(baseMip) + && bgfxFmt == bgfx::TextureFormat::BGRA8; + const bgfx::TextureFormat::Enum createFmt = packedMeshAtlas && !baseMipOnly + ? bgfx::TextureFormat::BGRA8 + : bgfxFmt; + const unsigned createMipCount = baseMipOnly + ? 1 + : (packedMeshAtlas + ? GetFullMipCount(baseMip.Width, baseMip.Height) + : static_cast(mips.size())); + const unsigned uploadVariant = baseMipOnly + ? kBgfxTextureUploadBaseMipOnly + : (packedMeshAtlas + ? kBgfxTextureUploadPackedAtlas + : (terrainAtlasSafeMips + ? kBgfxTextureUploadTerrainAtlas + : (dxt5Expanded + ? kBgfxTextureUploadDxt5Expanded + : kBgfxTextureUploadNormal))); + + TextureCacheInfo info = { + revision, + static_cast(baseMip.Width), + static_cast(baseMip.Height), + static_cast(baseMip.Format), + static_cast(createFmt), + static_cast(createMipCount), + static_cast(uploadVariant) + }; + return info; +} + +static bool TextureCacheInfoMatches(const TextureCacheInfo &lhs, + const TextureCacheInfo &rhs) +{ + return lhs.revision == rhs.revision + && lhs.w == rhs.w + && lhs.h == rhs.h + && lhs.sourceFormat == rhs.sourceFormat + && lhs.createFormat == rhs.createFormat + && lhs.mipCount == rhs.mipCount + && lhs.uploadVariant == rhs.uploadVariant; +} + +struct TextureUploadPlan +{ + bgfx::TextureFormat::Enum uploadFormat; + bgfx::TextureFormat::Enum createFormat; + unsigned createMipCount; + unsigned uploadMipCount; + bool terrainAtlasSafeMips; + bool packedMeshAtlas; + TextureCacheInfo cacheInfo; +}; + +static bool BuildTextureUploadPlan(unsigned revision, + TextureClass *tex2d, + const std::vector &mips, + bool baseMipOnly, + TextureUploadPlan *outPlan) +{ + if (tex2d == nullptr || mips.empty() || outPlan == nullptr) + { + return false; + } + + const TextureBaseClass::TextureMipSnapshot &baseMip = mips[0]; + const bool effectiveBaseMipOnly = baseMipOnly || ShouldUseBaseMipForThinDxt1Strip(mips); + const bgfx::TextureFormat::Enum uploadFormat = + DXT5UsesNonOpaqueAlpha(baseMip) + ? bgfx::TextureFormat::BGRA8 + : GetBgfxTextureUploadFormat(baseMip.Format); + if (uploadFormat == bgfx::TextureFormat::Unknown) + { + return false; + } + + TextureUploadPlan plan; + plan.uploadFormat = uploadFormat; + plan.terrainAtlasSafeMips = !effectiveBaseMipOnly && IsTerrainAtlasTexture(tex2d, baseMip.Format, uploadFormat); + plan.packedMeshAtlas = !effectiveBaseMipOnly && IsPackedMeshAtlasTexture(tex2d, baseMip.Format); + plan.createFormat = plan.packedMeshAtlas + ? bgfx::TextureFormat::BGRA8 + : uploadFormat; + // Disabled mip filtering is represented by a separate one-mip bgfx texture + // because bgfx sampler flags cannot disable mip sampling for a mipped texture. + plan.createMipCount = effectiveBaseMipOnly + ? 1 + : (plan.packedMeshAtlas + ? GetFullMipCount(baseMip.Width, baseMip.Height) + : static_cast(mips.size())); + plan.uploadMipCount = plan.createMipCount; + plan.cacheInfo = MakeTextureCacheInfo(revision, tex2d, baseMip, mips, uploadFormat, effectiveBaseMipOnly); + *outPlan = plan; + return true; +} + +static bool UploadBgfxTextureMips(TextureClass *tex2d, + bgfx::TextureHandle handle, + const std::vector &mips, + const TextureUploadPlan &plan) +{ + if (plan.terrainAtlasSafeMips) + { + return UploadTerrainAtlasMips(tex2d, handle, mips, plan.uploadFormat); + } + if (plan.packedMeshAtlas) + { + return UploadPackedAtlasMips(tex2d, handle, mips); + } + + const unsigned uploadMipCount = (plan.uploadMipCount < mips.size()) + ? plan.uploadMipCount + : static_cast(mips.size()); + for (unsigned mip = 0; mip < uploadMipCount; ++mip) + { + const bgfx::Memory *mem = nullptr; + uint16_t mipWidth = 0; + uint16_t mipHeight = 0; + if (!CopyTextureLevel(tex2d, plan.uploadFormat, mips[mip], mip, + &mem, &mipWidth, &mipHeight)) + { + return false; + } + bgfx::updateTexture2D(handle, 0, static_cast(mip), 0, 0, + mipWidth, mipHeight, mem); + g_stats.textureUploads++; + } + return true; +} + +// TheSuperHackers @bugfix bobtista 02/07/2026 Mid-game textures reusing freed GPU memory +// (e.g. "_n" night textures) rendered stale on Metal via create-empty + updateTexture2D; +// create immutably with data up front. Atlas-rebuild variants keep the mutable path. +static bool TryCreateImmutableBaseMip(TextureClass *tex2d, + const std::vector &mips, + const TextureUploadPlan &plan, + uint64_t texFlags, + bgfx::TextureHandle *outHandle) +{ + if (plan.cacheInfo.uploadVariant != kBgfxTextureUploadNormal + && plan.cacheInfo.uploadVariant != kBgfxTextureUploadBaseMipOnly + && plan.cacheInfo.uploadVariant != kBgfxTextureUploadDxt5Expanded) + { + return false; + } + + const unsigned baseW = mips[0].Width; + const unsigned baseH = mips[0].Height; + unsigned levels = (plan.uploadMipCount < mips.size()) + ? plan.uploadMipCount + : static_cast(mips.size()); + if (levels == 0) + { + return false; + } + // createTexture2D with mem and _hasMips=true expects the complete mip chain. + // Only claim mips when we actually hold every level; otherwise upload the base + // level alone (still immutable, so the Metal upload lands). + const bool completeChain = (levels == GetFullMipCount(baseW, baseH)) && levels > 1; + const unsigned useLevels = completeChain ? levels : 1; + if (useLevels > 16) + { + return false; + } + + const bgfx::Memory *levelMem[16] = { nullptr }; + uint16_t levelW[16] = { 0 }; + uint16_t levelH[16] = { 0 }; + uint32_t totalBytes = 0; + for (unsigned mip = 0; mip < useLevels; ++mip) + { + if (!CopyTextureLevel(tex2d, plan.uploadFormat, mips[mip], mip, + &levelMem[mip], &levelW[mip], &levelH[mip]) + || levelMem[mip] == nullptr) + { + return false; + } + totalBytes += levelMem[mip]->size; + } + + const bgfx::Memory *packed = bgfx::alloc(totalBytes); + uint32_t offset = 0; + for (unsigned mip = 0; mip < useLevels; ++mip) + { + std::memcpy(packed->data + offset, levelMem[mip]->data, levelMem[mip]->size); + offset += levelMem[mip]->size; + } + + *outHandle = bgfx::createTexture2D(static_cast(baseW), + static_cast(baseH), completeChain, 1, + plan.createFormat, texFlags, packed); + return bgfx::isValid(*outHandle); +} + +static bgfx::TextureHandle CreateBgfxTextureFromSnapshots(TextureClass *tex2d, + const std::vector &mips, + const TextureUploadPlan &plan) +{ + const TextureBaseClass::TextureMipSnapshot &baseMip = mips[0]; + const uint64_t texFlags = g_device.srgbEnabled ? BGFX_TEXTURE_SRGB : BGFX_TEXTURE_NONE; + + bgfx::TextureHandle immutableHandle = BGFX_INVALID_HANDLE; + if (TryCreateImmutableBaseMip(tex2d, mips, plan, texFlags, &immutableHandle)) + { + g_stats.textureCreates++; + return immutableHandle; + } + + bgfx::TextureHandle handle = bgfx::createTexture2D( + static_cast(baseMip.Width), + static_cast(baseMip.Height), + plan.createMipCount > 1, 1, + plan.createFormat, + texFlags, + nullptr); + if (!bgfx::isValid(handle)) + { + return BGFX_INVALID_HANDLE; + } + + g_stats.textureCreates++; + if (!UploadBgfxTextureMips(tex2d, handle, mips, plan)) + { + g_caches.deferredDestroys.push_back(handle); + return BGFX_INVALID_HANDLE; + } + return handle; +} + +// External linkage: called from BgfxBackend.cpp's Set_Texture path. +bgfx::TextureHandle EnsureBgfxTexture(TextureBaseClass * tex, bool baseMipOnly) +{ + if (tex == nullptr) + { + return BGFX_INVALID_HANDLE; + } + + TextureClass * tex2d = tex->As_TextureClass(); + if (tex2d != nullptr && + tex->Get_Pool() != TextureBaseClass::POOL_DEFAULT && + tex->Get_CPU_Texture_Mips().empty() && + tex->Has_Compatibility_Texture()) + { + WWASSERT_PRINT( + false, + "EnsureBgfxTexture: BGFX texture ownership missing CPU mips; no compatibility texture recapture is allowed"); + } + + const unsigned textureRevision = tex->Get_CPU_Texture_Revision(); + const std::vector & mips = tex->Get_CPU_Texture_Mips(); + std::unordered_map &textureCache = + baseMipOnly ? g_caches.textureBaseMip : g_caches.texture; + std::unordered_map &textureInfo = + baseMipOnly ? g_caches.textureBaseMipInfo : g_caches.textureInfo; + + auto it = textureCache.find(tex); + if (it != textureCache.end()) + { + auto infoIt = textureInfo.find(tex); + bool cacheKeyMatch = infoIt != textureInfo.end() + && infoIt->second.revision == textureRevision; + // TheSuperHackers @performance bobtista 04/06/2026 On a revision match with a + // fully-populated entry, the upload plan is a pure function of the + // revision-tracked snapshot, so return the cached handle without re-deriving + // it. The re-derivation re-ran an O(pixels) DXT5 alpha scan on every one of + // the ~6000 texture binds per frame. The {revision,0,0} in-progress sentinel + // (w==0) falls through to a real rebuild; content changes bump the revision + // and atlas-region changes pair with Invalidate_Cached_Texture which clears it. + if (cacheKeyMatch && infoIt->second.w != 0 && infoIt->second.h != 0) + { + return it->second; + } + if (cacheKeyMatch && !mips.empty()) + { + TextureUploadPlan plan; + cacheKeyMatch = BuildTextureUploadPlan(textureRevision, tex2d, mips, baseMipOnly, &plan) + && TextureCacheInfoMatches(infoIt->second, plan.cacheInfo); + } + if (cacheKeyMatch) + { + return it->second; + } + + uint16_t cachedW = 0; + uint16_t cachedH = 0; + if (infoIt != textureInfo.end()) + { + cachedW = infoIt->second.w; + cachedH = infoIt->second.h; + } + textureInfo[tex] = { textureRevision, 0, 0 }; + + if (!mips.empty() && bgfx::isValid(it->second)) + { + if (tex2d != nullptr) + { + const TextureBaseClass::TextureMipSnapshot & baseMip = mips[0]; + TextureUploadPlan plan; + // TheSuperHackers @bugfix bobtista 02/07/2026 The in-place + // updateTexture2D re-upload does not land on Metal for the same + // textures the immutable-create path fixes (mid-game "_n" night + // textures reusing freed day-texture memory), leaving the stale + // contents. When the content-changed re-upload targets an + // immutable-eligible texture, destroy and recreate it via the + // immutable path instead of updating in place. + const bool immutableEligible = + BuildTextureUploadPlan(textureRevision, tex2d, mips, baseMipOnly, &plan) + && (plan.cacheInfo.uploadVariant == kBgfxTextureUploadNormal + || plan.cacheInfo.uploadVariant == kBgfxTextureUploadBaseMipOnly + || plan.cacheInfo.uploadVariant == kBgfxTextureUploadDxt5Expanded); + if (!immutableEligible + && baseMip.Width == cachedW + && baseMip.Height == cachedH) + { + if (UploadBgfxTextureMips(tex2d, it->second, mips, plan)) + { + textureInfo[tex] = plan.cacheInfo; + return it->second; + } + } + } + // Immutable-eligible, dimensions, or format changed — destroy and recreate + g_caches.deferredDestroys.push_back(it->second); + } + textureCache.erase(it); + } + else + { + textureInfo[tex] = { textureRevision, 0, 0 }; + } + + // Only handle regular 2D TextureClass resources here. Cube and volume + // textures are dormant in the GeneralsMD bgfx runtime, and would need + // separate snapshot/cache/upload plumbing if a real caller appears. + if (tex2d == nullptr || tex->Get_Asset_Type() != TextureBaseClass::TEX_REGULAR) + { + // TheSuperHackers @info bobtista 16/07/2026 Release-visible breadcrumb: in release + // builds an unsupported texture type renders as the silent white fallback with no + // assert, which is undiagnosable from a screenshot. Fires once per texture because + // the invalid handle is cached below. + fprintf(stderr, "[BgfxBackend] Unsupported texture type %d for %s; binding white fallback\n", + static_cast(tex->Get_Asset_Type()), + tex->Get_Texture_Name().str()); + textureCache[tex] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + if (tex->Is_Render_Target()) + { + g_caches.renderTarget[tex] = true; + } + + 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; + } + + if (tex->Is_Render_Target() || mips.empty()) + { + g_caches.renderTarget[tex] = true; + return BGFX_INVALID_HANDLE; + } + } + + if (mips.empty()) + { + static bool s_loggedNullBase = false; + if (!s_loggedNullBase) + { + s_loggedNullBase = true; + WWDEBUG_SAY(("[BgfxBackend] EnsureBgfxTexture: no CPU texture snapshot for %s", + tex2d->Get_Full_Path().str())); + } + return BGFX_INVALID_HANDLE; + } + + const TextureBaseClass::TextureMipSnapshot & baseMip = mips[0]; + TextureUploadPlan plan; + if (!BuildTextureUploadPlan(textureRevision, tex2d, mips, baseMipOnly, &plan)) + { + 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()))); + } + textureCache[tex] = BGFX_INVALID_HANDLE; + return BGFX_INVALID_HANDLE; + } + + // TheSuperHackers @fix bobtista 19/04/2026 Create mutable (createTexture2D with data is + // immutable and silently rejects later updates) and preserve the authored mip chain, + // which tiny additive effect textures rely on when minified. + bgfx::TextureHandle h = CreateBgfxTextureFromSnapshots(tex2d, mips, plan); + + textureCache[tex] = h; + // Record dimensions so future reuse can update in place + textureInfo[tex] = plan.cacheInfo; + return h; +} +void BgfxBackend::Invalidate_Cached_Texture(TextureBaseClass * texture) +{ + // TheSuperHackers @bugfix bobtista 08/07/2026 Static render objects release + // texture refs from destructors that run after std::exit has begun; the + // g_caches maps this function touches may already be destroyed by then. + // Same exit-order hazard as Destroy_Resource, guarded the same way. + if (BgfxExitTeardownActive()) + { + return; + } + // TheSuperHackers @bugfix bobtista 08/07/2026 Drop the sorted texture-array + // slot with the other per-texture caches so a later allocation reusing this + // TextureBaseClass* address cannot inherit a stale page layer, and so + // content re-uploads re-register with fresh pixels. + BgfxSortedTextureArrayReleaseTexture(texture); + + if (texture == nullptr) + { + return; + } + if (!texture->Has_CPU_Texture_Mips()) + { + WWASSERT_PRINT( + false, + "Invalidate_Cached_Texture: BGFX texture ownership missing CPU mips; no compatibility texture recapture is allowed"); + } + // Set the cached revision to 0 (sentinel) so the next EnsureBgfxTexture + // detects a change and re-uploads pixel data. Keep dimensions so the + // in-place update path can check if the bgfx handle is reusable. + auto infoIt = g_caches.textureInfo.find(texture); + if (infoIt != g_caches.textureInfo.end()) + { + infoIt->second.revision = 0; + } + auto baseMipInfoIt = g_caches.textureBaseMipInfo.find(texture); + if (baseMipInfoIt != g_caches.textureBaseMipInfo.end()) + { + baseMipInfoIt->second.revision = 0; + } +} + +void BgfxBackend::Copy_Render_Target_To_Texture(TextureClass * dst_texture, + TextureClass * src_render_target) +{ + if (!g_device.initialized || dst_texture == nullptr || src_render_target == nullptr) + { + return; + } + + auto srcIt = g_caches.framebuffer.find(src_render_target); + if (srcIt == g_caches.framebuffer.end() || !bgfx::isValid(srcIt->second.colorTex)) + { + return; + } + + const uint16_t width = srcIt->second.width; + const uint16_t height = srcIt->second.height; + auto dstIt = g_caches.texture.find(dst_texture); + bool createTexture = (dstIt == g_caches.texture.end() || !bgfx::isValid(dstIt->second)); + if (!createTexture) + { + auto dimIt = g_caches.textureInfo.find(dst_texture); + if (dimIt == g_caches.textureInfo.end() + || dimIt->second.w != width + || dimIt->second.h != height) + { + g_caches.deferredDestroys.push_back(dstIt->second); + g_caches.texture.erase(dstIt); + createTexture = true; + } + } + + if (createTexture) + { + bgfx::TextureHandle h = bgfx::createTexture2D( + width, height, false, 1, + bgfx::TextureFormat::RGBA8, + BGFX_TEXTURE_BLIT_DST | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + g_caches.texture[dst_texture] = h; + if (bgfx::isValid(h)) + { + g_stats.textureCreates++; + bgfx::setName(h, "projectedShadowCopy"); + } + } + + bgfx::TextureHandle dstHandle = g_caches.texture[dst_texture]; + if (!bgfx::isValid(dstHandle)) + { + return; + } + + bgfx::blit(kBgfxRTTTextureCopyView, dstHandle, 0, 0, srcIt->second.colorTex); + g_stats.textureCopies++; + g_caches.textureInfo[dst_texture] = { + dst_texture->Get_CPU_Texture_Revision(), + width, + height + }; + g_caches.renderTarget.erase(dst_texture); +} + +void BgfxBackend::Release_Cached_Texture(TextureBaseClass * texture) +{ + // TheSuperHackers @bugfix bobtista 08/07/2026 Static render objects release + // texture refs from destructors that run after std::exit has begun; the + // g_caches maps this function touches may already be destroyed by then. + // Same exit-order hazard as Destroy_Resource, guarded the same way. + if (BgfxExitTeardownActive()) + { + return; + } + // TheSuperHackers @bugfix bobtista 08/07/2026 Drop the sorted texture-array + // slot with the other per-texture caches so a later allocation reusing this + // TextureBaseClass* address cannot inherit a stale page layer, and so + // content re-uploads re-register with fresh pixels. + BgfxSortedTextureArrayReleaseTexture(texture); + + if (texture == nullptr) + { + return; + } + // Called from TextureBaseClass::~TextureBaseClass before the legacy + // 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); + bgfx::TextureHandle oldHandle = BGFX_INVALID_HANDLE; + if (it != g_caches.texture.end()) + { + oldHandle = it->second; + if (bgfx::isValid(it->second)) + { + g_caches.deferredDestroys.push_back(it->second); + } + g_caches.texture.erase(it); + } + g_caches.textureInfo.erase(texture); + + auto baseMipIt = g_caches.textureBaseMip.find(texture); + bgfx::TextureHandle oldBaseMipHandle = BGFX_INVALID_HANDLE; + if (baseMipIt != g_caches.textureBaseMip.end()) + { + oldBaseMipHandle = baseMipIt->second; + if (bgfx::isValid(baseMipIt->second)) + { + g_caches.deferredDestroys.push_back(baseMipIt->second); + } + g_caches.textureBaseMip.erase(baseMipIt); + } + g_caches.textureBaseMipInfo.erase(texture); + + g_caches.renderTarget.erase(texture); + for (unsigned i = 0; i < 4; ++i) + { + if (g_draw.sourceTextures[i] == texture + || (bgfx::isValid(oldHandle) && g_draw.tex[i].idx == oldHandle.idx) + || (bgfx::isValid(oldBaseMipHandle) && g_draw.tex[i].idx == oldBaseMipHandle.idx)) + { + g_draw.tex[i] = BGFX_INVALID_HANDLE; + g_draw.sourceTextures[i] = nullptr; + g_draw.textureIsMissing[i] = false; + } + } + + // 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 src_x, + unsigned src_y, + unsigned dst_x, + unsigned dst_y, + unsigned pitch, + WW3DFormat format, + unsigned border_pixel) +{ + 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 = Get_Bytes_Per_Pixel(format); + if (bpp == 0) + { + return; + } + + // TheSuperHackers @bugfix bobtista 17/04/2026 Invalidate stale cache entries when the + // shroud texture pointer changes (save/load recreates m_pDstTexture); destroy the old + // handle via the deferred path since prior frame submits may still be in flight. + static TextureClass * s_lastShroudDst = nullptr; + static unsigned s_lastShroudW = 0; + static unsigned s_lastShroudH = 0; + bool forceFullUpload = false; + if (dst_texture != s_lastShroudDst + || dst_width != s_lastShroudW + || dst_height != s_lastShroudH) + { + forceFullUpload = true; + if (s_lastShroudDst != nullptr && s_lastShroudDst != dst_texture) + { + auto oldIt = g_caches.texture.find(s_lastShroudDst); + if (oldIt != g_caches.texture.end()) + { + if (bgfx::isValid(oldIt->second)) + { + g_caches.deferredDestroys.push_back(oldIt->second); + } + g_caches.texture.erase(oldIt); + } + g_caches.textureInfo.erase(s_lastShroudDst); + g_caches.renderTarget.erase(s_lastShroudDst); + } + // TheSuperHackers @bugfix bobtista 29/04/2026 Also invalidate the + // new shroud destination pointer. TextureClass addresses can be reused + // by replay/save loads after unrelated textures were cached, and the + // old bgfx handle would then be mistaken for the shroud texture. + auto currentIt = g_caches.texture.find(dst_texture); + if (currentIt != g_caches.texture.end()) + { + if (bgfx::isValid(currentIt->second)) + { + g_caches.deferredDestroys.push_back(currentIt->second); + } + g_caches.texture.erase(currentIt); + } + g_caches.textureInfo.erase(dst_texture); + g_caches.renderTarget.erase(dst_texture); + + s_lastShroudDst = dst_texture; + s_lastShroudW = dst_width; + s_lastShroudH = dst_height; + } + + // TheSuperHackers @bugfix bobtista 03/07/2026 Refill and re-upload the + // whole mirror when the border shroud color changes (script-driven via + // setBorderShroudLevel), since the border ring lives only in the + // destination image and is untouched by the per-cell dirty tracking. + static unsigned s_lastBorderPixel = 0xFFFFu; + if (border_pixel != s_lastBorderPixel) + { + forceFullUpload = true; + s_lastBorderPixel = border_pixel; + } + + 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))); + } + forceFullUpload = true; + } + + 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; + } + static std::vector s_prevShroudData; + static std::vector s_fullShroudImage; + const unsigned srcBytes = src_height * pitch; + const unsigned rowBytes = src_width * bpp; + const uint8_t * srcBase = static_cast(pixel_data) + src_y * pitch; + + if (!forceFullUpload + && s_prevShroudData.size() == srcBytes + && std::memcmp(s_prevShroudData.data(), srcBase, srcBytes) == 0) + { + return; + } + + if (s_fullShroudImage.size() != fullSize || forceFullUpload) + { + // TheSuperHackers @bugfix bobtista 03/07/2026 Fill the destination + // mirror with the border shroud pixel instead of hardcoded white. + // Terrain beyond the map boundary clamp-samples the border ring of + // this texture; white left the map edge fully lit instead of fading + // to the border shroud color (black by default) like DX8's + // fillBorderShroudData does. + s_fullShroudImage.resize(fullSize); + if (bpp == 2) + { + uint16_t * fillPtr = reinterpret_cast(s_fullShroudImage.data()); + const uint16_t fillPixel = static_cast(border_pixel); + for (unsigned i = 0; i < fullSize / 2u; ++i) + { + fillPtr[i] = fillPixel; + } + } + else + { + std::memset(s_fullShroudImage.data(), 0xFF, fullSize); + } + } + + unsigned dirtyRowMin = src_height; + unsigned dirtyRowMax = 0; + const bool hasPrev = s_prevShroudData.size() == srcBytes; + for (unsigned row = 0; row < src_height; ++row) + { + const unsigned srcRowOff = row * pitch + src_x * bpp; + const unsigned cacheRowOff = row * pitch; + bool rowDirty = forceFullUpload + || !hasPrev + || std::memcmp(s_prevShroudData.data() + cacheRowOff, + srcBase + cacheRowOff, rowBytes) != 0; + const unsigned dstOffset = ((dst_y + row) * dst_width + dst_x) * bpp; + if (rowDirty && dstOffset + rowBytes <= fullSize) + { + std::memcpy(s_fullShroudImage.data() + dstOffset, + static_cast(pixel_data) + src_y * pitch + srcRowOff, + rowBytes); + if (row < dirtyRowMin) { dirtyRowMin = row; } + if (row >= dirtyRowMax) { dirtyRowMax = row + 1; } + } + } + + s_prevShroudData.resize(srcBytes); + std::memcpy(s_prevShroudData.data(), srcBase, srcBytes); + + SurfaceClass::SurfaceImageData shroudImage; + shroudImage.Width = dst_width; + shroudImage.Height = dst_height; + shroudImage.Pitch = dst_width * bpp; + shroudImage.Format = format; + shroudImage.Data.assign(s_fullShroudImage.begin(), s_fullShroudImage.end()); + dst_texture->Update_Surface_Level_From_Surface(0, shroudImage); + + if (forceFullUpload) + { + dirtyRowMin = 0; + dirtyRowMax = dst_height; + } + else if (dirtyRowMin >= dirtyRowMax) + { + dirtyRowMin = 0; + dirtyRowMax = src_height; + } + const unsigned uploadY = forceFullUpload ? 0 : dst_y + dirtyRowMin; + const unsigned uploadH = forceFullUpload ? dst_height : dirtyRowMax - dirtyRowMin; + const unsigned uploadBytes = uploadH * dst_width * bpp; + const bgfx::Memory * mem = bgfx::alloc(uploadBytes); + for (unsigned row = 0; row < uploadH; ++row) + { + const unsigned imgOff = ((uploadY + row) * dst_width) * bpp; + std::memcpy(mem->data + row * dst_width * bpp, + s_fullShroudImage.data() + imgOff, + dst_width * bpp); + } + bgfx::updateTexture2D(h, 0, 0, + 0, static_cast(uploadY), + static_cast(dst_width), + static_cast(uploadH), + mem, static_cast(dst_width * bpp)); + g_caches.textureInfo[dst_texture] = { + dst_texture->Get_CPU_Texture_Revision(), + static_cast(dst_width), + static_cast(dst_height), + static_cast(format), + static_cast(bgfxFmt), + 1, + kBgfxTextureUploadNormal + }; + DumpShroudTextureForDiagnostics(mem->data, dst_width, dst_height, bpp, format); +} + +// ---------------------------------------------------------------------------- +// TheSuperHackers @performance bobtista 08/07/2026 Persistent texture pages for +// the sorted texture-array merge path. Adjacent sorted runs that differ only by +// their stage-0 texture can render as one draw when every texture lives in the +// same texture2DArray page; the layer index rides in the vertex stream. Pages +// are keyed by (width, height, mip count) and filled once per texture from the +// CPU mip snapshots, so there is no per-frame array churn. + +namespace +{ + +constexpr uint16_t kSortedArrayPageCapacity = 64; +// All layers live in one canonical square so differently-sized effect textures +// share a page (texture2DArray layers must match dimensions). Each texture +// occupies the top-left (w, h) region of its layer; the sorted merge path +// pre-scales the vertex UVs by (w, h) / kSortedArrayPageDim. +constexpr unsigned kSortedArrayPageDim = 256; +constexpr unsigned kSortedArrayPageMips = 9; // 256..1 full chain + +struct SortedTexturePage +{ + bgfx::TextureHandle handle = BGFX_INVALID_HANDLE; + uint16_t used = 0; + std::vector freeLayers; // layers returned by destroyed/invalidated textures +}; + +std::vector s_sortedPages; +struct SortedTextureSlot +{ + int page = -1; + int layer = -1; + float scaleU = 1.0f; + float scaleV = 1.0f; +}; +// Cached slots, including negative results (page == -1) so ineligible +// textures are only inspected once. +std::unordered_map s_sortedSlots; + +bool SortedArrayMipDataValid(const TextureBaseClass::TextureMipSnapshot & mip) +{ + if (mip.Format == WW3D_FORMAT_DXT1 || mip.Format == WW3D_FORMAT_DXT3 + || mip.Format == WW3D_FORMAT_DXT5) + { + const size_t blockBytes = (mip.Format == WW3D_FORMAT_DXT1) ? 8 : 16; + const size_t blockRows = (mip.Height + 3) / 4; + const size_t blockCols = (mip.Width + 3) / 4; + return mip.Pitch >= blockCols * blockBytes + && mip.Data.size() >= blockRows * mip.Pitch; + } + return mip.Pitch >= mip.Width * 4 + && mip.Data.size() >= static_cast(mip.Pitch) * mip.Height; +} + +bool SortedArraySnapshotEligible(const std::vector & mips) +{ + static const bool s_trace = GgcFlags::Enabled(GgcFlag_Trace); + if (mips.empty()) + { + if (s_trace) + { + static bool s_logged = false; + if (!s_logged) + { + std::fprintf(stderr, "[ggc] sorted-array reject: empty snapshot\n"); + s_logged = true; + } + } + return false; + } + const TextureBaseClass::TextureMipSnapshot & base = mips[0]; + if (base.Format != WW3D_FORMAT_A8R8G8B8 && base.Format != WW3D_FORMAT_X8R8G8B8 + && base.Format != WW3D_FORMAT_DXT1 && base.Format != WW3D_FORMAT_DXT3 + && base.Format != WW3D_FORMAT_DXT5) + { + if (s_trace) + { + static bool s_logged = false; + if (!s_logged) + { + std::fprintf(stderr, "[ggc] sorted-array reject: format=%d\n", (int)base.Format); + s_logged = true; + } + } + return false; + } + if (base.Width == 0 || base.Height == 0 + || base.Width > kSortedArrayPageDim || base.Height > kSortedArrayPageDim + || (base.Width & (base.Width - 1)) != 0 + || (base.Height & (base.Height - 1)) != 0) + { + return false; + } + for (size_t m = 0; m < mips.size(); ++m) + { + const TextureBaseClass::TextureMipSnapshot & mip = mips[m]; + if (mip.Format != base.Format) + { + return false; + } + const unsigned expectedW = std::max(1u, base.Width >> m); + const unsigned expectedH = std::max(1u, base.Height >> m); + if (mip.Width != expectedW || mip.Height != expectedH) + { + return false; + } + if (!SortedArrayMipDataValid(mip)) + { + return false; + } + } + return true; +} + +void DecodeSortedArrayMip(std::vector & out, const TextureBaseClass::TextureMipSnapshot & mip) +{ + const uint32_t rowBytes = mip.Width * 4; + out.resize(static_cast(rowBytes) * mip.Height); + if (mip.Format == WW3D_FORMAT_DXT5) + { + // Decode once at registration; the decode is deterministic, so the + // page layer samples the same pixels the original texture would. + ExpandDXT5ToBGRA8(&mip.Data[0], mip.Pitch, mip.Width, mip.Height, out.data()); + } + else if (mip.Format == WW3D_FORMAT_DXT3) + { + ExpandDXT3ToBGRA8(&mip.Data[0], mip.Pitch, mip.Width, mip.Height, out.data()); + } + else if (mip.Format == WW3D_FORMAT_DXT1) + { + ExpandDXT1ToBGRA8(&mip.Data[0], mip.Pitch, mip.Width, mip.Height, out.data()); + } + else + { + for (unsigned row = 0; row < mip.Height; ++row) + { + std::memcpy(out.data() + static_cast(row) * rowBytes, + mip.Data.data() + static_cast(row) * mip.Pitch, + rowBytes); + } + if (mip.Format == WW3D_FORMAT_X8R8G8B8) + { + // X8R8G8B8 snapshots carry undefined alpha bytes; the merged draws + // modulate texture alpha with vertex alpha, so force opaque. + for (size_t px = 3; px < out.size(); px += 4) + { + out[px] = 0xFF; + } + } + } +} + +// Box-downsample one BGRA8 level to the next (used to extend a texture's mip +// chain down to the page's 1x1 so no page level is left undefined). +void DownsampleSortedArrayMip(std::vector & out, const std::vector & src, + unsigned srcW, unsigned srcH) +{ + const unsigned dstW = srcW > 1 ? srcW / 2 : 1; + const unsigned dstH = srcH > 1 ? srcH / 2 : 1; + out.resize(static_cast(dstW) * dstH * 4); + for (unsigned y = 0; y < dstH; ++y) + { + for (unsigned x = 0; x < dstW; ++x) + { + const unsigned sx0 = x * 2; + const unsigned sy0 = y * 2; + const unsigned sx1 = (sx0 + 1 < srcW) ? sx0 + 1 : sx0; + const unsigned sy1 = (sy0 + 1 < srcH) ? sy0 + 1 : sy0; + for (unsigned c = 0; c < 4; ++c) + { + const unsigned sum = + src[(static_cast(sy0) * srcW + sx0) * 4 + c] + src[(static_cast(sy0) * srcW + sx1) * 4 + c] + + src[(static_cast(sy1) * srcW + sx0) * 4 + c] + src[(static_cast(sy1) * srcW + sx1) * 4 + c]; + out[(static_cast(y) * dstW + x) * 4 + c] = static_cast(sum / 4); + } + } + } +} + +void UploadSortedArrayLayer(const SortedTexturePage & page, + uint16_t layer, + const std::vector & mips) +{ + // Every page level is uploaded at FULL page dimensions with the texture's + // region at the top left and transparent black elsewhere, so no texel of + // the layer is ever left undefined (region-edge filtering and deep-mip + // minification would otherwise read uninitialized GPU memory). Levels + // below the texture's own chain keep box-downsampling to the page's 1x1. + std::vector region; + std::vector next; + unsigned levelW = 0; + unsigned levelH = 0; + for (unsigned m = 0; m < kSortedArrayPageMips; ++m) + { + if (m < mips.size()) + { + DecodeSortedArrayMip(region, mips[m]); + levelW = mips[m].Width; + levelH = mips[m].Height; + } + else if (levelW > 1 || levelH > 1) + { + DownsampleSortedArrayMip(next, region, levelW, levelH); + region.swap(next); + levelW = levelW > 1 ? levelW / 2 : 1; + levelH = levelH > 1 ? levelH / 2 : 1; + } + // else: 1x1 content is replicated into the remaining deeper page mips. + + const unsigned pageW = kSortedArrayPageDim >> m > 1 ? kSortedArrayPageDim >> m : 1; + const unsigned pageH = pageW; + const bgfx::Memory * mem = bgfx::alloc(pageW * pageH * 4); + std::memset(mem->data, 0, mem->size); + for (unsigned row = 0; row < levelH && row < pageH; ++row) + { + const unsigned copyW = levelW < pageW ? levelW : pageW; + std::memcpy(mem->data + static_cast(row) * pageW * 4, + region.data() + static_cast(row) * levelW * 4, + static_cast(copyW) * 4); + } + bgfx::updateTexture2D(page.handle, layer, static_cast(m), + 0, 0, + static_cast(pageW), + static_cast(pageH), + mem, + static_cast(pageW * 4)); + } +} + +} // namespace + +int BgfxSortedTextureArrayGetSlot(TextureBaseClass * texture, int * outLayer, float * outScaleU, float * outScaleV) +{ + *outLayer = -1; + *outScaleU = 1.0f; + *outScaleV = 1.0f; + if (texture == nullptr || !g_device.initialized) + { + return -1; + } + auto cached = s_sortedSlots.find(texture); + if (cached != s_sortedSlots.end()) + { + *outLayer = cached->second.layer; + *outScaleU = cached->second.scaleU; + *outScaleV = cached->second.scaleV; + return cached->second.page; + } + + int page = -1; + int layer = -1; + const std::vector & mips = texture->Get_CPU_Texture_Mips(); + if (SortedArraySnapshotEligible(mips)) + { + for (size_t p = 0; p < s_sortedPages.size(); ++p) + { + if (s_sortedPages[p].used < kSortedArrayPageCapacity + || !s_sortedPages[p].freeLayers.empty()) + { + page = static_cast(p); + break; + } + } + if (page < 0) + { + SortedTexturePage fresh; + fresh.handle = bgfx::createTexture2D( + static_cast(kSortedArrayPageDim), + static_cast(kSortedArrayPageDim), + true, + kSortedArrayPageCapacity, + bgfx::TextureFormat::BGRA8, + BGFX_TEXTURE_NONE); + if (bgfx::isValid(fresh.handle)) + { + s_sortedPages.push_back(fresh); + page = static_cast(s_sortedPages.size()) - 1; + } + } + if (page >= 0) + { + SortedTexturePage & target = s_sortedPages[page]; + if (!target.freeLayers.empty()) + { + layer = target.freeLayers.back(); + target.freeLayers.pop_back(); + } + else + { + layer = target.used++; + } + UploadSortedArrayLayer(target, static_cast(layer), mips); + } + } + + SortedTextureSlot slot; + slot.page = page; + slot.layer = layer; + if (page >= 0) + { + slot.scaleU = static_cast(mips[0].Width) / static_cast(kSortedArrayPageDim); + slot.scaleV = static_cast(mips[0].Height) / static_cast(kSortedArrayPageDim); + } + s_sortedSlots[texture] = slot; + *outLayer = slot.layer; + *outScaleU = slot.scaleU; + *outScaleV = slot.scaleV; + return page; +} + +bgfx::TextureHandle BgfxSortedTextureArrayPageHandle(int page) +{ + if (page < 0 || page >= static_cast(s_sortedPages.size())) + { + return BGFX_INVALID_HANDLE; + } + return s_sortedPages[page].handle; +} + +void BgfxSortedTextureArrayReleaseTexture(TextureBaseClass * texture) +{ + auto slot = s_sortedSlots.find(texture); + if (slot == s_sortedSlots.end()) + { + return; + } + if (slot->second.page >= 0 && slot->second.page < static_cast(s_sortedPages.size()) + && slot->second.layer >= 0) + { + s_sortedPages[slot->second.page].freeLayers.push_back(static_cast(slot->second.layer)); + } + s_sortedSlots.erase(slot); +} + +void BgfxSortedTextureArrayShutdown() +{ + for (size_t p = 0; p < s_sortedPages.size(); ++p) + { + if (bgfx::isValid(s_sortedPages[p].handle)) + { + bgfx::destroy(s_sortedPages[p].handle); + } + } + s_sortedPages.clear(); + s_sortedSlots.clear(); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/BgfxRenderProfile.h b/Core/Libraries/Source/WWVegas/WW3D2/BgfxRenderProfile.h new file mode 100644 index 00000000000..1d36cf7a608 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/BgfxRenderProfile.h @@ -0,0 +1,84 @@ +/* +** 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 @diag bobtista 04/06/2026 Cross-TU render-frame attribution. +// Lightweight phase timers usable from any engine TU (W3DScene, particle system, +// sorting renderer, terrain) so we can attribute the per-frame render CPU top-down. +// Accumulators + the per-frame reset and CSV emit live in BgfxBackend.cpp; the +// numbers are written alongside the existing frame-timing CSV when it is active. +// Accumulation is a couple of QueryPerformanceCounter reads per scope (cheap) and +// always on; only the CSV emit is gated by GGC_BGFX_FRAME_TIMING_*. + +#pragma once + +namespace GGCRenderProfile +{ + enum Phase + { + // Top-level sequential buckets inside W3DDisplay::draw. These do NOT overlap and + // sum (plus the unattributed remainder) to FRAME_DRAW. + FRAME_DRAW = 0, // whole W3DDisplay::draw (the real per-frame envelope) + UPDATE_VIEWS, // updateViews(): per-view recompute + visible-terrain refresh + PARTICLE_UPDATE, // TheParticleSystemManager->update(): particle simulation/spawn + RTT, // water reflection + projected-shadow render-target passes + DRAW_VIEWS, // drawViews(): the on-screen scene draw (contains RENDER_TOTAL) + UI_DRAW, // TheInGameUI->DRAW() + mouse draw + END_RENDER, // WW3D::End_Render (device flush / present / bgfx::frame) + + // Nested detail inside DRAW_VIEWS (and partly RTT) — reported but NOT subtracted + // from the top-level remainder, since they are subsets of the buckets above. + RENDER_TOTAL, // RTS3DScene::Render (summed across on-screen + RTT passes) + TRAVERSAL, // Customized_Render: scene walk + per-object dispatch + MESH_FLUSH, // DX8MeshRenderer::Flush (opaque/rigid mesh submission) + SORT_FLUSH, // SortingRendererClass::Flush (sorted translucents) + PARTICLES, // DoParticles (particle render build, subset of RENDER_TOTAL) + TERRAIN, // terrain heightmap render (nested detail) + POINTGROUP_COMPRESS, // PointGroupClass::Render active-point compression + POINTGROUP_VIEW_XFORM, // point center world->view transform + POINTGROUP_UPDATE_ARRAYS, // particle sprite vertex/uv/color expansion + POINTGROUP_GROUND_FIXUP, // bgfx ground-aligned view-space fixup + POINTGROUP_VB_FILL, // dynamic VB lock/fill for point groups + SORT_POOL_BUILD, // SortingRenderer pool VB/index metadata build + SORT_POOL_SORT, // transparent triangle sort/coalesce + SORT_POOL_DRAW, // sorted dynamic IB build + draw-run replay + SORTED_INSERT, // SortingRenderer::Insert_Triangles (per-node pool insertion) + PARTICLE_TEX_FETCH, // per-system Get_Texture name lookup in doParticles + SORTED_CAPTURE, // Capture_Legacy_Render_State_For_Sorted_Draw (per-node state capture, subset of SORTED_INSERT) + PHASE_COUNT + }; + + void Begin(Phase phase); + void End(Phase phase); + long long SnapshotTicks(Phase phase); + // Snapshot+reset the accumulators at a frame boundary (call at the top of the + // per-frame draw, before any scope of the new frame opens). The CSV emit reads + // the snapshot, so scopes that enclose the emit still report complete times. + void EndFrame(); + + struct Scope + { + Phase m_phase; + explicit Scope(Phase phase) : m_phase(phase) { Begin(phase); } + ~Scope() { End(m_phase); } + }; +} + +#define GGC_RPROFILE_CONCAT_(a, b) a##b +#define GGC_RPROFILE_CONCAT(a, b) GGC_RPROFILE_CONCAT_(a, b) +#define GGC_RPROFILE(phase) \ + GGCRenderProfile::Scope GGC_RPROFILE_CONCAT(_ggc_rprof_, __LINE__)(GGCRenderProfile::phase) diff --git a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index ecebd564696..e441402870d 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -47,7 +47,7 @@ set(WW3D2_SRC dx8caps.h dx8fvf.cpp dx8fvf.h - dx8indexbuffer.cpp + indexbuffer.cpp dx8indexbuffer.h dx8list.h dx8polygonrenderer.cpp @@ -56,22 +56,22 @@ set(WW3D2_SRC dx8renderer.h dx8rendererdebugger.cpp dx8rendererdebugger.h - dx8texman.cpp - dx8texman.h - dx8vertexbuffer.cpp + vertexbuffer.cpp dx8vertexbuffer.h - dx8webbrowser.cpp - dx8webbrowser.h - dx8wrapper.cpp dx8wrapper.h + WW3DDeviceInit.cpp + WW3DDeviceInit.h + texturecompatibilityinterop.cpp dynamesh.cpp dynamesh.h font3d.cpp font3d.h - formconv.cpp + dx8formatconv.h + texturecompatibilityinterop.h formconv.h - FramGrab.cpp framgrab.h + FixedFunctionState.cpp + FixedFunctionState.h hanim.cpp hanim.h #hanimmgr.cpp @@ -90,6 +90,7 @@ set(WW3D2_SRC htree.h #htreemgr.cpp #htreemgr.h + IRenderBackend.h intersec.cpp intersec.h intersec.inl @@ -155,6 +156,8 @@ set(WW3D2_SRC proto.h proxy.h rddesc.h + RenderBackend.cpp + RenderBackend.h #render2d.cpp #render2d.h render2dsentence.cpp @@ -165,6 +168,8 @@ set(WW3D2_SRC rendobj.h #rinfo.cpp #rinfo.h + renderdebugstats.cpp + renderdebugstats.h ringobj.cpp ringobj.h robjlist.h @@ -184,8 +189,6 @@ set(WW3D2_SRC sortingrenderer.cpp sortingrenderer.h soundlibrarybridge.h - soundrobj.cpp - soundrobj.h sphereobj.cpp sphereobj.h static_sort_list.cpp @@ -204,8 +207,11 @@ set(WW3D2_SRC texproject.h #textdraw.cpp # unused textdraw.h + texturecompat.h texture.cpp texture.h + TextureResourceManager.cpp + TextureResourceManager.h texturefilter.cpp texturefilter.h textureloader.cpp @@ -233,10 +239,143 @@ set(WW3D2_SRC ww3dtrig.h ) +if(WIN32) + list(APPEND WW3D2_SRC + FramGrab.cpp + soundrobj.cpp + soundrobj.h + ) +endif() + add_library(corei_ww3d2 INTERFACE) target_sources(corei_ww3d2 INTERFACE ${WW3D2_SRC}) +if(UNIX AND NOT APPLE) + find_package(Freetype REQUIRED) + find_package(Fontconfig REQUIRED) + target_link_libraries(corei_ww3d2 INTERFACE + Freetype::Freetype + Fontconfig::Fontconfig + ) +endif() + +if(NOT GGC_RENDER_BACKEND STREQUAL "bgfx") + target_sources(corei_ww3d2 INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/formconv.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dx8texman.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dx8texman.h + ) +endif() + +if(GGC_RENDER_BACKEND STREQUAL "dx8") + target_sources(corei_ww3d2 INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/DX8Backend.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/DX8Backend.h + ${CMAKE_CURRENT_SOURCE_DIR}/dx8webbrowser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dx8webbrowser.h + ${CMAKE_CURRENT_SOURCE_DIR}/dx8wrapper.cpp + ) +endif() + +# TheSuperHackers @feature bobtista 01/06/2026 Backend-agnostic RenderDoc +# capture trigger, compiled for every backend so the env var works in both +# dx8 and bgfx builds. +target_sources(corei_ww3d2 INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/RenderDocTrigger.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/RenderDocTrigger.h +) + +# TheSuperHackers @feature bobtista 01/06/2026 Backend-agnostic draw-call +# logger. Writes CSV per frame when GGC_DRAWLOG_AFTER+GGC_DRAWLOG_PATH set. +target_sources(corei_ww3d2 INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/DrawCallLog.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/DrawCallLog.h +) + +# TheSuperHackers @refactor bobtista 10/04/2026 Conditionally include the +# active render backend's source files. See cmake/render-backend.cmake. +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 Compile bgfx shaders. + # 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) + # TheSuperHackers @refactor bobtista 12/04/2026 Uber shader pair. + # A 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/vs_uber_instanced.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_uber.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_uber.sc NAME fs_uber_array DEFINES "GGC_UBER_STAGE0_ARRAY=1") + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_uber.sc NAME vs_uber_array DEFINES "GGC_UBER_STAGE0_ARRAY=1") + # TheSuperHackers @performance bobtista fs_uber variant that reads the global per-frame + # constants (sun/point shadow, scene ambient) from a data texture instead of per-draw + # uniforms, shrinking the per-draw constant buffer so heavy scenes stay under bgfx's + # fixed 8MB Metal uniform arena. Pairs with the unmodified vs_uber. + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_uber.sc NAME fs_uber_frameconst DEFINES "GGC_UBER_FRAME_TEXTURE=1") + # 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) + # 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) + # TheSuperHackers @feature bobtista 27/04/2026 Scene-color composite + # pass. World/effects render to an offscreen framebuffer, then this + # identity pass copies it to the swapchain before UI draws. + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_scene_composite.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_scene_composite.sc) + # TheSuperHackers @feature bobtista 15/06/2026 Bloom bright-pass and separable + # blur. Reuse vs_scene_composite as the fullscreen vertex stage. + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_bloom_bright.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_bloom_blur.sc) + # TheSuperHackers @feature bobtista 15/06/2026 SSAO pass (reuses vs_scene_composite). + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_ssao.sc) + # TheSuperHackers @feature bobtista 15/06/2026 Fullscreen copy/resolve (MSAA smudge). + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_copy.sc) + # TheSuperHackers @feature bobtista 27/04/2026 Readable scene-depth + # pass. Opaque world draws populate an R32F texture for future post + # effects and soft particles. + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_scene_depth.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_scene_depth_instanced.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_scene_depth.sc) + # TheSuperHackers @feature bobtista 16/06/2026 Shadow-map caster pass with + # alpha-test, so cutout geometry (infantry, foliage) casts its real silhouette. + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_shadow_caster.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_shadow_caster_instanced.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_shadow_caster.sc) + # TheSuperHackers @feature bobtista 27/04/2026 Dedicated smudge / + # heat-haze pass. This samples the scene-color snapshot without going + # through the fixed-function uber shader. + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/vs_smudge.sc) + ggc_compile_bgfx_shader(${CMAKE_CURRENT_SOURCE_DIR}/shaders/fs_smudge.sc) + get_property(GGC_BGFX_GENERATED_SHADER_HEADERS GLOBAL PROPERTY GGC_BGFX_SHADER_HEADERS) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/BgfxBackend.cpp PROPERTIES + OBJECT_DEPENDS "${GGC_BGFX_GENERATED_SHADER_HEADERS}" + ) + target_link_libraries(corei_ww3d2 INTERFACE ggc_bgfx_shaders) +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 +387,34 @@ target_link_libraries(corei_ww3d2 INTERFACE core_wwlib core_wwmath ) + +if(APPLE) + target_link_libraries(corei_ww3d2 INTERFACE + "-framework CoreGraphics" + "-framework CoreText" + "-framework CoreFoundation" + ) +endif() + +# 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 @build bobtista 29/04/2026 BgfxBackend.cpp pulls SDL3 headers + # to query the native window pointer for bgfx::PlatformData on macOS/Linux. + if(SAGE_USE_SDL3) + target_link_libraries(corei_ww3d2 INTERFACE sdl3lib) + endif() + # 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 + 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..a9455124e72 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.cpp @@ -0,0 +1,2566 @@ +/* +** 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 "dx8fvf.h" +#include "texturecompatibilityinterop.h" +#include "dx8wrapper.h" +#include "DrawCallLog.h" +#include "RenderDocTrigger.h" +#include "dx8formatconv.h" +#include "FixedFunctionState.h" +#include "GgcRuntimeFlags.h" +#include "WWMath/vector3.h" +#include "WWMath/matrix4.h" +#include "WWMath/matrix3d.h" +#include "WW3D2/light.h" +#include "WW3D2/lightenvironment.h" +#include "surfaceclass.h" +#include "texture.h" +#include +#include +#include +#include +#include +#include "BgfxRenderProfile.h" + +// TheSuperHackers @build bobtista 15/06/2026 GGCRenderProfile is referenced by the +// always-compiled W3DDisplay / W3DScene / sorting renderer, but its accumulators are +// owned by the active backend TU. BgfxBackend.cpp owns the bgfx copy; this is the +// DX8/VC6 copy so the reference (and VC6) builds link again. VC6-safe: __int64 and +// static zero-init, no C++11 default member initializers. Timing is accumulated but +// only the bgfx backend emits it; the DX8 build relies on -logFrameTimes for fps. +namespace GGCRenderProfile +{ + struct DX8PhaseAcc { __int64 start; __int64 total_ticks; unsigned calls; }; + static DX8PhaseAcc g_dx8_phase_acc[PHASE_COUNT]; + + void Begin(Phase phase) + { + LARGE_INTEGER c; + QueryPerformanceCounter(&c); + g_dx8_phase_acc[phase].start = c.QuadPart; + } + void End(Phase phase) + { + LARGE_INTEGER c; + QueryPerformanceCounter(&c); + g_dx8_phase_acc[phase].total_ticks += c.QuadPart - g_dx8_phase_acc[phase].start; + g_dx8_phase_acc[phase].calls++; + } + void EndFrame() + { + for (int i = 0; i < PHASE_COUNT; ++i) + { + g_dx8_phase_acc[i].total_ticks = 0; + g_dx8_phase_acc[i].calls = 0; + } + } +} + +namespace +{ +struct DX8ViewCaptureState +{ + IDirect3DSurface8 * oldRenderSurface; + IDirect3DTexture8 * renderTexture; + IDirect3DSurface8 * newRenderSurface; + IDirect3DSurface8 * oldDepthSurface; + bool active; +}; + +static DX8ViewCaptureState g_tacticalViewCapture = { nullptr, nullptr, nullptr, nullptr, false }; +static DWORD g_profilerSwizzleShader = 0; +static const DWORD kGrayscaleLuminanceWeights = 0x80A5CA8E; +static const DWORD kGrayscaleFlatGray = 0x60606060; + +static DWORD FloatAsDword(float value) +{ + DWORD bits = 0; + memcpy(&bits, &value, sizeof(bits)); + return bits; +} + +static const DWORD * GetXYZNDUV1Declaration() +{ + static const DWORD declaration[] = + { + D3DVSD_STREAM(0), + D3DVSD_REG(0, D3DVSDT_FLOAT3), + D3DVSD_REG(1, D3DVSDT_FLOAT3), + D3DVSD_REG(2, D3DVSDT_D3DCOLOR), + D3DVSD_REG(7, D3DVSDT_FLOAT2), + D3DVSD_END() + }; + return declaration; +} + +static DX8ViewCaptureState * GetViewCaptureState(RenderBackendViewCaptureKind kind) +{ + if (kind != RB_VIEW_CAPTURE_TACTICAL) { + return nullptr; + } + return &g_tacticalViewCapture; +} + +static void ReleaseViewCaptureState(DX8ViewCaptureState & state) +{ + if (state.newRenderSurface != nullptr) { + state.newRenderSurface->Release(); + } + if (state.renderTexture != nullptr) { + state.renderTexture->Release(); + } + if (state.oldRenderSurface != nullptr) { + state.oldRenderSurface->Release(); + } + if (state.oldDepthSurface != nullptr) { + state.oldDepthSurface->Release(); + } + state.oldRenderSurface = nullptr; + state.renderTexture = nullptr; + state.newRenderSurface = nullptr; + state.oldDepthSurface = nullptr; + state.active = false; +} + +static DWORD GetScreenQuadFVF(bool use_second_uv) +{ + return D3DFVF_XYZRHW | D3DFVF_DIFFUSE | (use_second_uv ? D3DFVF_TEX2 : D3DFVF_TEX1); +} + +static bool EnsureProfilerSwizzleShader() +{ + if (g_profilerSwizzleShader != 0) { + return true; + } + + ID3DXBuffer * compiledShader = nullptr; + const char * shader = + "ps.1.4\n" + "texld r0, t0\n" + "mov r1.a, r0.r\n" + "mov r2.a, r0.g\n" + "mov r3.a, r0.b\n" + "mul r0.rgb, r3.a, c0\n" + "mad r0.rgb, r2.a, c1, r0\n" + "mad r0.rgb, r1.a, c2, r0\n"; + + HRESULT hr = D3DXAssembleShader(shader, strlen(shader), 0, nullptr, &compiledShader, nullptr); + if (FAILED(hr) || compiledShader == nullptr) { + return false; + } + + hr = DX8Wrapper::_Get_D3D_Device8()->CreatePixelShader( + reinterpret_cast(compiledShader->GetBufferPointer()), + &g_profilerSwizzleShader); + compiledShader->Release(); + + if (FAILED(hr)) { + g_profilerSwizzleShader = 0; + return false; + } + + return true; +} +} + +DX8Backend::DX8Backend() +{ +} + +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() +{ + Release_View_Capture(RB_VIEW_CAPTURE_TACTICAL); + if (g_profilerSwizzleShader != 0 && DX8Wrapper::_Get_D3D_Device8() != nullptr) { + DX8Wrapper::_Get_D3D_Device8()->DeletePixelShader(g_profilerSwizzleShader); + g_profilerSwizzleShader = 0; + } +} + +bool DX8Backend::Init_Render_System(void * hwnd, bool lite) +{ + return DX8Wrapper::Init(hwnd, lite); +} + +void DX8Backend::Shutdown_Render_System() +{ + DX8Wrapper::Shutdown(); +} + +// -- Device selection, windowing and display-mode control -------------------- + +bool DX8Backend::Set_Render_Device(const char * dev_name, int width, int height, int bits, int windowed, bool resize_window) +{ + return DX8Wrapper::Set_Render_Device(dev_name, width, height, bits, windowed, resize_window); +} + +bool DX8Backend::Set_Render_Device(int dev, int width, int height, int bits, int windowed, bool resize_window, bool reset_device, bool restore_assets) +{ + return DX8Wrapper::Set_Render_Device(dev, width, height, bits, windowed, resize_window, reset_device, restore_assets); +} + +bool DX8Backend::Set_Any_Render_Device() +{ + return DX8Wrapper::Set_Any_Render_Device(); +} + +bool DX8Backend::Set_Next_Render_Device() +{ + return DX8Wrapper::Set_Next_Render_Device(); +} + +bool DX8Backend::Toggle_Windowed() +{ + return DX8Wrapper::Toggle_Windowed(); +} + +bool DX8Backend::Is_Windowed() const +{ + return DX8Wrapper::Is_Windowed(); +} + +int DX8Backend::Get_Render_Device() const +{ + return DX8Wrapper::Get_Render_Device(); +} + +const RenderDeviceDescClass & DX8Backend::Get_Render_Device_Desc(int deviceidx) +{ + return DX8Wrapper::Get_Render_Device_Desc(deviceidx); +} + +int DX8Backend::Get_Render_Device_Count() const +{ + return DX8Wrapper::Get_Render_Device_Count(); +} + +const char * DX8Backend::Get_Render_Device_Name(int device_index) +{ + return DX8Wrapper::Get_Render_Device_Name(device_index); +} + +bool DX8Backend::Set_Device_Resolution(int width, int height, int bits, int windowed, bool resize_window) +{ + return DX8Wrapper::Set_Device_Resolution(width, height, bits, windowed, resize_window); +} + +void DX8Backend::Get_Render_Target_Resolution(int & set_w, int & set_h, int & set_bits, bool & set_windowed) +{ + DX8Wrapper::Get_Render_Target_Resolution(set_w, set_h, set_bits, set_windowed); +} + +void DX8Backend::Get_Device_Resolution(int & set_w, int & set_h, int & set_bits, bool & set_windowed) +{ + DX8Wrapper::Get_Device_Resolution(set_w, set_h, set_bits, set_windowed); +} + +int DX8Backend::Get_Device_Resolution_Width() const +{ + return DX8Wrapper::Get_Device_Resolution_Width(); +} + +int DX8Backend::Get_Device_Resolution_Height() const +{ + return DX8Wrapper::Get_Device_Resolution_Height(); +} + +bool DX8Backend::Registry_Save_Render_Device(const char * sub_key) +{ + return DX8Wrapper::Registry_Save_Render_Device(sub_key); +} + +bool DX8Backend::Registry_Save_Render_Device(const char * sub_key, int device, int width, int height, int depth, bool windowed, int texture_depth) +{ + return DX8Wrapper::Registry_Save_Render_Device(sub_key, device, width, height, depth, windowed, texture_depth); +} + +bool DX8Backend::Registry_Load_Render_Device(const char * sub_key, bool resize_window) +{ + return DX8Wrapper::Registry_Load_Render_Device(sub_key, resize_window); +} + +bool DX8Backend::Registry_Load_Render_Device(const char * sub_key, char * device, int device_len, int & width, int & height, int & depth, int & windowed, int & texture_depth) +{ + return DX8Wrapper::Registry_Load_Render_Device(sub_key, device, device_len, width, height, depth, windowed, texture_depth); +} + +void DX8Backend::Set_Swap_Interval(int swap) +{ + DX8Wrapper::Set_Swap_Interval(swap); +} + +int DX8Backend::Get_Swap_Interval() const +{ + return DX8Wrapper::Get_Swap_Interval(); +} + +// -- Device state queries ---------------------------------------------------- + +bool DX8Backend::Is_Device_Lost() const +{ + return DX8Wrapper::Is_Device_Lost(); +} + +RenderBackendDeviceStatus DX8Backend::Get_Device_Status() const +{ + IDirect3DDevice8 * device = DX8Wrapper::_Get_D3D_Device8(); + if (device == nullptr) { + return RB_DEVICE_OK; + } + + HRESULT hr = device->TestCooperativeLevel(); + if (hr == D3DERR_DEVICELOST) { + return RB_DEVICE_LOST; + } + if (hr == D3DERR_DEVICENOTRESET) { + return RB_DEVICE_NOT_RESET; + } + return RB_DEVICE_OK; +} + +void DX8Backend::Reset_Device() +{ + DX8Wrapper::Reset_Device(); +} + +void DX8Backend::Set_Device_Cleanup_Hook(RenderDeviceCleanupHook * hook) +{ + DX8Wrapper::SetCleanupHook(hook); +} + +void DX8Backend::Set_MSAA_Mode(RenderBackendMSAAMode mode) +{ + switch (mode) { + default: + case RB_MSAA_NONE: + DX8Wrapper::Set_MSAA_Mode(D3DMULTISAMPLE_NONE); + break; + + case RB_MSAA_2X: + DX8Wrapper::Set_MSAA_Mode(D3DMULTISAMPLE_2_SAMPLES); + break; + + case RB_MSAA_4X: + DX8Wrapper::Set_MSAA_Mode(D3DMULTISAMPLE_4_SAMPLES); + break; + + case RB_MSAA_8X: + DX8Wrapper::Set_MSAA_Mode(D3DMULTISAMPLE_8_SAMPLES); + break; + } +} + +RenderBackendMSAAMode DX8Backend::Get_MSAA_Mode() const +{ + switch (DX8Wrapper::Get_MSAA_Mode()) { + default: + case D3DMULTISAMPLE_NONE: + return RB_MSAA_NONE; + + case D3DMULTISAMPLE_2_SAMPLES: + return RB_MSAA_2X; + + case D3DMULTISAMPLE_4_SAMPLES: + return RB_MSAA_4X; + + case D3DMULTISAMPLE_8_SAMPLES: + return RB_MSAA_8X; + } +} + +bool DX8Backend::Supports_Dot3() const +{ + return DX8Wrapper::Get_Current_Caps() != nullptr + && DX8Wrapper::Get_Current_Caps()->Support_Dot3(); +} + +bool DX8Backend::Get_Device_Identity(RenderBackendDeviceIdentity & identity) const +{ + const DX8Caps * caps = DX8Wrapper::Get_Current_Caps(); + if (caps == nullptr) + { + return false; + } + + identity = {}; + identity.max_simultaneous_textures = caps->Get_Max_Simultaneous_Textures(); + identity.pixel_shader_major = caps->Get_Pixel_Shader_Major_Version(); + identity.pixel_shader_minor = caps->Get_Pixel_Shader_Minor_Version(); + + IDirect3D8 * d3d = DX8Wrapper::_Get_D3D8(); + if (d3d != nullptr) + { + D3DADAPTER_IDENTIFIER8 adapter; + ::ZeroMemory(&adapter, sizeof(adapter)); + if (SUCCEEDED(d3d->GetAdapterIdentifier(0, D3DENUM_NO_WHQL_LEVEL, &adapter))) + { + identity.vendor_id = adapter.VendorId; + identity.device_id = adapter.DeviceId; +#ifdef _WIN32 + identity.driver_version = static_cast(adapter.DriverVersion.QuadPart); +#else + identity.driver_version = + (static_cast(adapter.DriverVersionHighPart) << 32) | + adapter.DriverVersionLowPart; +#endif + } + } + + return true; +} + +bool DX8Backend::Has_Stencil() const +{ + return DX8Wrapper::Has_Stencil(); +} + +WW3DFormat DX8Backend::Get_Back_Buffer_Format() const +{ + return DX8Wrapper::getBackBufferFormat(); +} + +bool DX8Backend::Get_Back_Buffer_Description(unsigned int num, RenderBackendSurfaceDescription & desc) const +{ + desc = RenderBackendSurfaceDescription(); + + SurfaceClass * back_buffer = DX8Wrapper::_Get_DX8_Back_Buffer(num); + if (back_buffer == nullptr) + { + return false; + } + + SurfaceClass::SurfaceDescription surface_desc; + back_buffer->Get_Description(surface_desc); + REF_PTR_RELEASE(back_buffer); + + desc.Width = surface_desc.Width; + desc.Height = surface_desc.Height; + desc.Format = surface_desc.Format; + return desc.Is_Valid(); +} + +static SurfaceClass * Capture_Back_Buffer_Surface(unsigned int num) +{ + SurfaceClass * back_buffer = DX8Wrapper::_Get_DX8_Back_Buffer(num); + if (back_buffer == nullptr) + { + return nullptr; + } + + SurfaceClass::SurfaceDescription desc; + back_buffer->Get_Description(desc); + // TheSuperHackers @build bobtista 01/06/2026 SurfaceClass(void*) is + // private; use the public Create_Legacy_Surface_Wrapper factory. + SurfaceClass * copy = Create_Legacy_Surface_Wrapper( + DX8Wrapper::_Create_DX8_Surface(desc.Width, desc.Height, desc.Format)); + if (copy != nullptr) + { + DX8Wrapper::_Copy_DX8_Rects( + Peek_Legacy_Surface(*back_buffer), + nullptr, + 0, + Peek_Legacy_Surface(*copy), + nullptr); + } + + back_buffer->Release_Ref(); + return copy; +} + +bool DX8Backend::Capture_Back_Buffer_Image(unsigned int num, RenderBackendImage & image) +{ + image = RenderBackendImage(); + + SurfaceClass * copy = Capture_Back_Buffer_Surface(num); + if (copy == nullptr) + { + return false; + } + + SurfaceClass::SurfaceDescription desc; + copy->Get_Description(desc); + + int source_pitch = 0; + unsigned char *source_bits = static_cast(copy->Lock(&source_pitch)); + if (source_bits == nullptr) + { + copy->Release_Ref(); + return false; + } + + const unsigned row_bytes = desc.Width * 4; + image.Width = desc.Width; + image.Height = desc.Height; + image.Format = desc.Format; + image.Pitch = row_bytes; + image.Bytes.resize(static_cast(image.Pitch) * image.Height); + + for (unsigned row = 0; row < image.Height; ++row) + { + memcpy(image.Bytes.data() + static_cast(row) * image.Pitch, + source_bits + static_cast(row) * source_pitch, + row_bytes); + } + + copy->Unlock(); + copy->Release_Ref(); + return true; +} + +// TheSuperHackers @bugfix bobtista 03/06/2026 GPU-direct back-buffer → +// texture-surface copy for the smudge background snapshot. Replaces the +// per-frame Capture_Back_Buffer_Image + CPU readback that was leaking +// ~4 MB of system memory per call (causing dx8 to exhaust the 2 GB +// virtual address space within ~14 s on heavy combat saves). CopyRects +// stays entirely on the GPU and reuses dst_texture's POOL_DEFAULT +// surface across frames — zero allocations. +bool DX8Backend::Copy_Back_Buffer_To_Texture(unsigned int num, TextureClass * dst_texture) +{ + if (dst_texture == nullptr) { + return false; + } + SurfaceClass * back_buffer = DX8Wrapper::_Get_DX8_Back_Buffer(num); + if (back_buffer == nullptr) { + return false; + } + // TextureClass::Get_Surface_Level returns a fresh wrapper around the + // CPU mip data — copying onto that wouldn't update the GPU texture. + // Pull the actual D3D8 surface level via the compatibility interop. + // TheSuperHackers @bugfix bobtista 10/07/2026 Get_Native_Compatibility_Surface_Level goes through + // GetSurfaceLevel, which AddRefs the returned surface, so dst_native must be released on every path. + // bb_native comes from Peek_Legacy_Surface (no AddRef) and must not be. + IDirect3DSurface8 * dst_native = Get_Native_Compatibility_Surface_Level(*dst_texture, 0); + IDirect3DSurface8 * bb_native = Peek_Legacy_Surface(*back_buffer); + if (dst_native == nullptr || bb_native == nullptr) { + if (dst_native != nullptr) { dst_native->Release(); } + REF_PTR_RELEASE(back_buffer); + return false; + } + DX8Wrapper::_Copy_DX8_Rects(bb_native, nullptr, 0, dst_native, nullptr); + dst_native->Release(); + REF_PTR_RELEASE(back_buffer); + return true; +} + +void DX8Backend::Set_Texture_Bitdepth(int bitdepth) +{ + DX8Wrapper::Set_Texture_Bitdepth(bitdepth); +} + +int DX8Backend::Get_Texture_Bitdepth() const +{ + return DX8Wrapper::Get_Texture_Bitdepth(); +} + +bool DX8Backend::Supports_Texture_Format(WW3DFormat format) const +{ + const auto * caps = DX8Wrapper::Get_Current_Caps(); + return caps != nullptr && caps->Support_Texture_Format(format); +} + +bool DX8Backend::Supports_Compressed_Textures() const +{ + const auto * caps = DX8Wrapper::Get_Current_Caps(); + return caps != nullptr && caps->Support_DXTC(); +} + +bool DX8Backend::Supports_Bump_Envmap() const +{ + const auto * caps = DX8Wrapper::Get_Current_Caps(); + return caps != nullptr && caps->Support_Bump_Envmap(); +} + +bool DX8Backend::Supports_Bump_Envmap_Luminance() const +{ + const auto * caps = DX8Wrapper::Get_Current_Caps(); + return caps != nullptr && caps->Support_Bump_Envmap_Luminance(); +} + +bool DX8Backend::Supports_Texture_Filter(RenderBackendTextureFilterCapability capability) const +{ + const auto * caps = DX8Wrapper::Get_Current_Caps(); + if (caps == nullptr) + { + return false; + } + + const DWORD filter_caps = caps->Get_DX8_Caps().TextureFilterCaps; + switch (capability) + { + case RB_TEXTURE_FILTER_MIN_LINEAR: + return (filter_caps & D3DPTFILTERCAPS_MINFLINEAR) != 0; + case RB_TEXTURE_FILTER_MAG_LINEAR: + return (filter_caps & D3DPTFILTERCAPS_MAGFLINEAR) != 0; + case RB_TEXTURE_FILTER_MIP_LINEAR: + return (filter_caps & D3DPTFILTERCAPS_MIPFLINEAR) != 0; + case RB_TEXTURE_FILTER_MIN_ANISOTROPIC: + return (filter_caps & D3DPTFILTERCAPS_MINFANISOTROPIC) != 0; + case RB_TEXTURE_FILTER_MAG_ANISOTROPIC: + return (filter_caps & D3DPTFILTERCAPS_MAGFANISOTROPIC) != 0; + default: + return false; + } +} + +bool DX8Backend::Supports_Texture_Op(RenderBackendTextureOpCapability capability) const +{ + const auto * caps = DX8Wrapper::Get_Current_Caps(); + if (caps == nullptr) + { + return false; + } + + const DWORD texture_op_caps = caps->Get_DX8_Caps().TextureOpCaps; + switch (capability) + { + case RB_TEXTURE_OP_SELECTARG1: + return (texture_op_caps & D3DTEXOPCAPS_SELECTARG1) != 0; + case RB_TEXTURE_OP_MODULATE: + return (texture_op_caps & D3DTEXOPCAPS_MODULATE) != 0; + case RB_TEXTURE_OP_MODULATE2X: + return (texture_op_caps & D3DTEXOPCAPS_MODULATE2X) != 0; + case RB_TEXTURE_OP_ADD: + return (texture_op_caps & D3DTEXOPCAPS_ADD) != 0; + case RB_TEXTURE_OP_BUMPENVMAP: + return (texture_op_caps & D3DTEXOPCAPS_BUMPENVMAP) != 0; + case RB_TEXTURE_OP_BUMPENVMAPLUMINANCE: + return (texture_op_caps & D3DTEXOPCAPS_BUMPENVMAPLUMINANCE) != 0; + case RB_TEXTURE_OP_ADDSMOOTH: + return (texture_op_caps & D3DTEXOPCAPS_ADDSMOOTH) != 0; + case RB_TEXTURE_OP_SUBTRACT: + return (texture_op_caps & D3DTEXOPCAPS_SUBTRACT) != 0; + case RB_TEXTURE_OP_BLENDTEXTUREALPHA: + return (texture_op_caps & D3DTEXOPCAPS_BLENDTEXTUREALPHA) != 0; + case RB_TEXTURE_OP_BLENDCURRENTALPHA: + return (texture_op_caps & D3DTEXOPCAPS_BLENDCURRENTALPHA) != 0; + case RB_TEXTURE_OP_ADDSIGNED: + return (texture_op_caps & D3DTEXOPCAPS_ADDSIGNED) != 0; + case RB_TEXTURE_OP_ADDSIGNED2X: + return (texture_op_caps & D3DTEXOPCAPS_ADDSIGNED2X) != 0; + case RB_TEXTURE_OP_MODULATEALPHA_ADDCOLOR: + return caps->Support_ModAlphaAddClr(); + default: + return false; + } +} + +bool DX8Backend::Supports_Fog() const +{ + const auto * caps = DX8Wrapper::Get_Current_Caps(); + return caps != nullptr && caps->Is_Fog_Allowed(); +} + +bool DX8Backend::Is_Legacy_Voodoo3() const +{ + const auto * caps = DX8Wrapper::Get_Current_Caps(); + return caps != nullptr + && caps->Get_Vendor() == DX8Caps::VENDOR_3DFX + && caps->Get_Device() == DX8Caps::DEVICE_3DFX_VOODOO_3; +} + +bool DX8Backend::Supports_NPatches() const +{ + const auto * caps = DX8Wrapper::Get_Current_Caps(); + return caps != nullptr && caps->Support_NPatches(); +} + +bool DX8Backend::Supports_Hardware_Transform_And_Lighting() const +{ + const auto * caps = DX8Wrapper::Get_Current_Caps(); + return caps != nullptr && caps->Support_TnL(); +} + +bool DX8Backend::Supports_Point_Sprites() const +{ + const auto * caps = DX8Wrapper::Get_Current_Caps(); + return caps != nullptr && caps->Support_PointSprites(); +} + +RenderBackendTextureLimits DX8Backend::Get_Texture_Limits() const +{ + const auto * caps = DX8Wrapper::Get_Current_Caps(); + if (caps == nullptr) + { + return IRenderBackend::Get_Texture_Limits(); + } + + const D3DCAPS8 & dx8caps = caps->Get_DX8_Caps(); + return { + dx8caps.MaxTextureWidth, + dx8caps.MaxTextureHeight, + dx8caps.MaxVolumeExtent, + // TheSuperHackers @bugfix bobtista 22/06/2026 The original clamped to a literal 8 + // regardless of the (often 0 = unrestricted) MaxTextureAspectRatio cap; keep that. + 8u + }; +} + +int DX8Backend::Get_Max_Texture_Stages() const +{ + const auto * caps = DX8Wrapper::Get_Current_Caps(); + return caps != nullptr ? caps->Get_Max_Textures_Per_Pass() : RB_MAX_TEXTURE_STAGES; +} + +bool DX8Backend::Supports_Z_Bias() const +{ + const auto * caps = DX8Wrapper::Get_Current_Caps(); + return caps != nullptr && caps->Support_ZBias(); +} + +void DX8Backend::Set_Gamma(float gamma, float bright, float contrast, bool calibrate, bool uselimit) +{ + DX8Wrapper::Set_Gamma(gamma, bright, contrast, calibrate, uselimit); +} + +// -- Frame lifecycle --------------------------------------------------------- +// +// TheSuperHackers @refactor bobtista 11/04/2026 The g_renderBackend Begin/End_Scene pair is +// a parallel per-frame hook alongside ww3d.cpp's direct DX8Wrapper::Begin_Scene/End_Scene calls. + +void DX8Backend::Begin_Scene() +{ + DX8Wrapper::Begin_Scene(); +} + +// TheSuperHackers @feature bobtista 03/06/2026 Simulation frame accessor (defined +// in GlobalData.cpp) so the dx8 screenshot can fire at a deterministic logic frame, +// matching the bgfx GGC_BGFX_SCREENSHOT_LOGICFRAME trigger for cross-backend A/B. +extern "C" int GGC_GetCurrentLogicFrame(); + +namespace +{ + +// TheSuperHackers @feature bobtista 01/06/2026 In-engine back-buffer screenshot for dx8 via +// GGC_DX8_SCREENSHOT_AFTER/INTERVAL/PATH (persistent user env vars), mirroring the bgfx +// mechanism; GDI capture cannot see D3D8 surfaces, so this writes a BMP from the back buffer. +struct Dx8ScreenshotState +{ + int targetFrame = -1; + int interval = 0; + char basePath[512] = {}; + bool envResolved = false; + int frameIndex = 0; + int targetLogicFrame = 0; + bool logicShotDone = false; +}; + +Dx8ScreenshotState & Get_Dx8_Screenshot_State() +{ + static Dx8ScreenshotState s; + return s; +} + +void Resolve_Dx8_Screenshot_Env() +{ + Dx8ScreenshotState & s = Get_Dx8_Screenshot_State(); + if (s.envResolved) { + return; + } + s.envResolved = true; + if (const char * a = GgcFlags::StringValue(GgcFlag_Dx8ScreenshotAfter)) { + s.targetFrame = std::atoi(a); + } + s.interval = GgcFlags::IntValue(GgcFlag_Dx8ScreenshotInterval); + if (const char * p = GgcFlags::StringValue(GgcFlag_Dx8ScreenshotPath)) { + std::strncpy(s.basePath, p, sizeof(s.basePath) - 1); + } + s.targetLogicFrame = GgcFlags::IntValue(GgcFlag_Dx8ScreenshotLogicFrame); +} + +bool Should_Take_Dx8_Screenshot() +{ + Dx8ScreenshotState & s = Get_Dx8_Screenshot_State(); + if (s.targetFrame <= 0 || s.basePath[0] == '\0') { + return false; + } + if (s.frameIndex == s.targetFrame) { + return true; + } + if (s.interval > 0 + && s.frameIndex > s.targetFrame + && ((s.frameIndex - s.targetFrame) % s.interval) == 0) { + return true; + } + return false; +} + +// Write a 32-bit top-down BGRA bitmap. Treats the source as BGRA8 regardless +// of the reported format — D3D8's swap buffer is normally A8R8G8B8 (which is +// little-endian BGRA) so this is correct for the common case. Returns true on +// successful write. +bool Save_Bgra_Bmp(const char * path, unsigned width, unsigned height, + unsigned src_pitch, const std::uint8_t * src_bytes) +{ + FILE * f = std::fopen(path, "wb"); + if (f == nullptr) { + return false; + } + const std::uint32_t row_bytes = width * 4; + const std::uint32_t pixel_data_bytes = row_bytes * height; + const std::uint32_t bf_size = 14 + 40 + pixel_data_bytes; + + // BITMAPFILEHEADER + std::uint8_t header[14] = {}; + header[0] = 'B'; header[1] = 'M'; + header[2] = static_cast(bf_size & 0xFF); + header[3] = static_cast((bf_size >> 8) & 0xFF); + header[4] = static_cast((bf_size >> 16) & 0xFF); + header[5] = static_cast((bf_size >> 24) & 0xFF); + header[10] = 14 + 40; // offset to pixel data + std::fwrite(header, 1, sizeof(header), f); + + // BITMAPINFOHEADER (40 bytes), height NEGATIVE so it's top-down + std::uint8_t info[40] = {}; + info[0] = 40; + info[4] = static_cast(width & 0xFF); + info[5] = static_cast((width >> 8) & 0xFF); + info[6] = static_cast((width >> 16) & 0xFF); + info[7] = static_cast((width >> 24) & 0xFF); + const std::int32_t neg_h = -static_cast(height); + std::memcpy(info + 8, &neg_h, 4); + info[12] = 1; // planes + info[14] = 32; // bits per pixel + std::memcpy(info + 20, &pixel_data_bytes, 4); + std::fwrite(info, 1, sizeof(info), f); + + for (unsigned row = 0; row < height; ++row) { + std::fwrite(src_bytes + static_cast(row) * src_pitch, 1, row_bytes, f); + } + std::fclose(f); + return true; +} + +} // namespace + +void DX8Backend::End_Scene(bool flip_frame) +{ + DrawCallLog_End_Frame(); + RenderDoc_Maybe_Trigger_Capture(); + + Resolve_Dx8_Screenshot_Env(); + if (Should_Take_Dx8_Screenshot()) { + RenderBackendImage img; + if (Capture_Back_Buffer_Image(0, img) && img.Is_Valid()) { + Dx8ScreenshotState & s = Get_Dx8_Screenshot_State(); + char path[640]; + std::snprintf(path, sizeof(path), "%s.%06d.bmp", + s.basePath, s.frameIndex); + Save_Bgra_Bmp(path, img.Width, img.Height, img.Pitch, img.Bytes.data()); + } + } + + // TheSuperHackers @feature bobtista 03/06/2026 Deterministic same-moment capture. + // GGC_DX8_SCREENSHOT_LOGICFRAME=N writes one screenshot at the first frame where + // the simulation reaches logic frame N, matching the bgfx trigger so dx8 and bgfx + // captures can be compared at the identical scene state. Output: .L.bmp + { + Dx8ScreenshotState & s = Get_Dx8_Screenshot_State(); + if (s.targetLogicFrame > 0 && !s.logicShotDone && s.basePath[0] != '\0') { + const int curLogicFrame = GGC_GetCurrentLogicFrame(); + if (curLogicFrame >= s.targetLogicFrame) { + s.logicShotDone = true; + RenderBackendImage img; + if (Capture_Back_Buffer_Image(0, img) && img.Is_Valid()) { + char path[640]; + std::snprintf(path, sizeof(path), "%s.L%06d.bmp", + s.basePath, curLogicFrame); + Save_Bgra_Bmp(path, img.Width, img.Height, img.Pitch, img.Bytes.data()); + } + } + } + } + ++Get_Dx8_Screenshot_State().frameIndex; + + DX8Wrapper::End_Scene(flip_frame); +} + +void DX8Backend::Flip_To_Primary() +{ + DX8Wrapper::Flip_To_Primary(); +} + +void DX8Backend::Begin_Device_Statistics() +{ + DX8Wrapper::Begin_Statistics(); +} + +void DX8Backend::End_Device_Statistics() +{ + DX8Wrapper::End_Statistics(); +} + +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); +} + +bool DX8Backend::Initialize_View_Capture(RenderBackendViewCaptureKind kind) +{ + DX8ViewCaptureState * state = GetViewCaptureState(kind); + if (state == nullptr) { + return false; + } + + ReleaseViewCaptureState(*state); + + IDirect3DDevice8 * device = DX8Wrapper::_Get_D3D_Device8(); + if (device == nullptr) { + return false; + } + + HRESULT hr = device->GetRenderTarget(&state->oldRenderSurface); + if (hr != S_OK || state->oldRenderSurface == nullptr) { + ReleaseViewCaptureState(*state); + return false; + } + + D3DSURFACE_DESC desc; + state->oldRenderSurface->GetDesc(&desc); + + // The legacy DX8 RTT path cannot pair a non-MSAA texture with an MSAA + // depth surface. Preserve the existing failure behavior instead of + // trying to paper over driver-forced MSAA. + if (desc.MultiSampleType != D3DMULTISAMPLE_NONE) { + ReleaseViewCaptureState(*state); + return false; + } + + hr = device->CreateTexture(desc.Width, desc.Height, 1, D3DUSAGE_RENDERTARGET, + desc.Format, D3DPOOL_DEFAULT, &state->renderTexture); + if (hr != S_OK || state->renderTexture == nullptr) { + ReleaseViewCaptureState(*state); + return false; + } + + hr = state->renderTexture->GetSurfaceLevel(0, &state->newRenderSurface); + if (hr != S_OK || state->newRenderSurface == nullptr) { + ReleaseViewCaptureState(*state); + return false; + } + + hr = device->GetDepthStencilSurface(&state->oldDepthSurface); + if (hr != S_OK || state->oldDepthSurface == nullptr) { + ReleaseViewCaptureState(*state); + return false; + } + + return true; +} + +void DX8Backend::Release_View_Capture(RenderBackendViewCaptureKind kind) +{ + DX8ViewCaptureState * state = GetViewCaptureState(kind); + if (state != nullptr) { + ReleaseViewCaptureState(*state); + } +} + +bool DX8Backend::Supports_View_Capture(RenderBackendViewCaptureKind kind) const +{ + const DX8ViewCaptureState * state = GetViewCaptureState(kind); + return state != nullptr && state->newRenderSurface != nullptr && state->oldDepthSurface != nullptr; +} + +bool DX8Backend::Begin_View_Capture(RenderBackendViewCaptureKind kind) +{ + DX8ViewCaptureState * state = GetViewCaptureState(kind); + if (state == nullptr || state->active || state->newRenderSurface == nullptr || state->oldDepthSurface == nullptr) { + return false; + } + + HRESULT hr = DX8Wrapper::_Get_D3D_Device8()->SetRenderTarget(state->newRenderSurface, state->oldDepthSurface); + if (hr != S_OK) { + ReleaseViewCaptureState(*state); + return false; + } + + state->active = true; + return true; +} + +bool DX8Backend::End_View_Capture(RenderBackendViewCaptureKind kind) +{ + DX8ViewCaptureState * state = GetViewCaptureState(kind); + if (state == nullptr || !state->active) { + return false; + } + + HRESULT hr = DX8Wrapper::_Get_D3D_Device8()->SetRenderTarget(state->oldRenderSurface, state->oldDepthSurface); + if (hr != S_OK) { + state->active = false; + return false; + } + + DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); + DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); + DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_ADDRESSW, D3DTADDRESS_CLAMP); + DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); + DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MINFILTER, D3DTEXF_LINEAR); + DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_MIPFILTER, D3DTEXF_NONE); + + state->active = false; + return true; +} + +bool DX8Backend::Is_View_Capture_Active(RenderBackendViewCaptureKind kind) const +{ + const DX8ViewCaptureState * state = GetViewCaptureState(kind); + return state != nullptr && state->active; +} + +bool DX8Backend::Has_View_Capture(RenderBackendViewCaptureKind kind) const +{ + const DX8ViewCaptureState * state = GetViewCaptureState(kind); + return state != nullptr && state->renderTexture != nullptr; +} + +bool DX8Backend::Bind_View_Capture_Texture(RenderBackendViewCaptureKind kind, unsigned int stage) +{ + DX8ViewCaptureState * state = GetViewCaptureState(kind); + if (state == nullptr || state->renderTexture == nullptr) { + return false; + } + + DX8Wrapper::Set_DX8_Texture(stage, state->renderTexture); + DX8Wrapper::Set_Texture(stage, nullptr); + return true; +} + +bool DX8Backend::Draw_View_Capture_Quad(RenderBackendViewCaptureKind kind, + const RenderBackendScreenVertex * vertices, + unsigned int vertex_count, + bool use_second_uv) +{ + if (vertex_count < 4 || vertices == nullptr || !Bind_View_Capture_Texture(kind, 0)) { + return false; + } + + return Draw_Screen_Quad(vertices, vertex_count, use_second_uv); +} + +bool DX8Backend::Draw_Screen_Quad(const RenderBackendScreenVertex * vertices, + unsigned int vertex_count, + bool use_second_uv) +{ + if (vertex_count < 4 || vertices == nullptr) { + return false; + } + + IDirect3DDevice8 * device = DX8Wrapper::_Get_D3D_Device8(); + if (device == nullptr) { + return false; + } + + device->SetVertexShader(GetScreenQuadFVF(use_second_uv)); + HRESULT hr = device->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, vertices, sizeof(RenderBackendScreenVertex)); + return hr == S_OK; +} + +bool DX8Backend::Capture_Back_Buffer_RGBA(unsigned int display_width, + unsigned int display_height, + unsigned int image_size, + unsigned char * output_pixels, + unsigned int output_capacity, + unsigned int * output_width, + unsigned int * output_height) +{ + if (display_width == 0 || display_height == 0 || image_size == 0 || + output_pixels == nullptr || output_width == nullptr || output_height == nullptr || + !EnsureProfilerSwizzleShader()) + { + return false; + } + + const float aspect_ratio = static_cast(display_height) / static_cast(display_width); + unsigned int capture_height = static_cast((image_size * aspect_ratio) + 0.5f); + if (capture_height > image_size) { + capture_height = image_size; + } + if (capture_height == 0) { + return false; + } + + const unsigned int row_bytes = image_size * 4; + const unsigned int required_bytes = row_bytes * capture_height; + if (output_capacity < required_bytes) { + return false; + } + *output_width = 0; + *output_height = 0; + + bool result = false; + TextureClass * render_target = nullptr; + SurfaceClass * surface_class = nullptr; + SurfaceClass * back_buffer = nullptr; + IDirect3DTexture8 * intermediate_texture = nullptr; + IDirect3DSurface8 * intermediate_surface = nullptr; + IDirect3DSurface8 * small_render_target_surface = nullptr; + D3DVIEWPORT8 restore_viewport; + memset(&restore_viewport, 0, sizeof(restore_viewport)); + bool viewport_valid = false; + bool render_target_changed = false; + bool shader_changed = false; + bool texture_changed = false; + + render_target = DX8Wrapper::Create_Render_Target(image_size, image_size, WW3D_FORMAT_A8R8G8B8); + surface_class = NEW_REF(SurfaceClass, (image_size, capture_height, WW3D_FORMAT_A8R8G8B8)); + back_buffer = DX8Wrapper::_Get_DX8_Back_Buffer(); + if (render_target == nullptr || surface_class == nullptr || back_buffer == nullptr) { + goto cleanup; + } + + IDirect3DSurface8 * back_buffer_surface; + back_buffer_surface = Peek_Legacy_Surface(*back_buffer); + if (back_buffer_surface == nullptr) { + goto cleanup; + } + + D3DSURFACE_DESC back_buffer_desc; + if (FAILED(back_buffer_surface->GetDesc(&back_buffer_desc))) { + goto cleanup; + } + + if (FAILED(DX8Wrapper::_Get_D3D_Device8()->CreateTexture( + back_buffer_desc.Width, + back_buffer_desc.Height, + 1, + D3DUSAGE_RENDERTARGET, + back_buffer_desc.Format, + D3DPOOL_DEFAULT, + &intermediate_texture)) || + intermediate_texture == nullptr) + { + goto cleanup; + } + + if (FAILED(intermediate_texture->GetSurfaceLevel(0, &intermediate_surface)) || + intermediate_surface == nullptr) + { + goto cleanup; + } + DX8Wrapper::_Copy_DX8_Rects(back_buffer_surface, nullptr, 0, intermediate_surface, nullptr); + + small_render_target_surface = Get_Native_Compatibility_Surface_Level(*render_target); + if (small_render_target_surface == nullptr) { + goto cleanup; + } + DX8Wrapper::Set_Render_Target(small_render_target_surface, false); + render_target_changed = true; + + IDirect3DDevice8 * device; + device = DX8Wrapper::_Get_D3D_Device8(); + if (device == nullptr || FAILED(device->GetViewport(&restore_viewport))) { + goto cleanup; + } + viewport_valid = true; + + D3DVIEWPORT8 viewport; + viewport.X = 0; + viewport.Y = 0; + viewport.Width = image_size; + viewport.Height = capture_height; + viewport.MinZ = 0.0f; + viewport.MaxZ = 1.0f; + DX8Wrapper::Set_Viewport(&viewport); + + DX8Wrapper::Set_Pixel_Shader(g_profilerSwizzleShader); + shader_changed = true; + static const float kMaskR[4] = {1.0f, 0.0f, 0.0f, 0.0f}; + static const float kMaskG[4] = {0.0f, 1.0f, 0.0f, 0.0f}; + static const float kMaskB[4] = {0.0f, 0.0f, 1.0f, 0.0f}; + Set_Pixel_Shader_Constant(0, kMaskR, 1); + Set_Pixel_Shader_Constant(1, kMaskG, 1); + Set_Pixel_Shader_Constant(2, kMaskB, 1); + + // TheSuperHackers @build bobtista 01/06/2026 Wrap initialized locals in + // block scopes so the subsequent `goto cleanup` statements don't cross + // their initialization (ill-formed in C++ -- C2362 under MSVC). + { + struct QuadVertex + { + float x, y, z, rhw; + float u, v; + } vtx[4]; + const float left = -0.5f; + const float top = -0.5f; + const float right = static_cast(image_size) - 0.5f; + const float bottom = static_cast(capture_height) - 0.5f; + vtx[0] = {right, bottom, 0.0f, 1.0f, 1.0f, 1.0f}; + vtx[1] = {right, top, 0.0f, 1.0f, 1.0f, 0.0f}; + vtx[2] = {left, bottom, 0.0f, 1.0f, 0.0f, 1.0f}; + vtx[3] = {left, top, 0.0f, 1.0f, 0.0f, 0.0f}; + DX8Wrapper::Set_DX8_Texture(0, intermediate_texture); + texture_changed = true; + DX8Wrapper::Set_Vertex_Shader(D3DFVF_XYZRHW | D3DFVF_TEX1); + if (FAILED(device->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, vtx, sizeof(QuadVertex)))) { + goto cleanup; + } + } + + { + RECT src_rect; + src_rect.left = 0; + src_rect.top = 0; + src_rect.right = image_size; + src_rect.bottom = capture_height; + POINT dst_point; + dst_point.x = 0; + dst_point.y = 0; + DX8Wrapper::_Copy_DX8_Rects( + small_render_target_surface, + &src_rect, + 1, + Peek_Legacy_Surface(*surface_class), + &dst_point); + + int pitch = 0; + void * bits = surface_class->Lock(&pitch); + if (bits == nullptr) { + goto cleanup; + } + + for (unsigned int row = 0; row < capture_height; ++row) + { + memcpy(output_pixels + row * row_bytes, + static_cast(bits) + row * pitch, + row_bytes); + } + surface_class->Unlock(); + } + + *output_width = image_size; + *output_height = capture_height; + result = true; + +cleanup: + if (shader_changed) { + DX8Wrapper::Set_Pixel_Shader(0); + } + if (texture_changed) { + DX8Wrapper::Set_DX8_Texture(0, nullptr); + } + if (viewport_valid) { + DX8Wrapper::Set_Viewport(&restore_viewport); + } + if (render_target_changed) { + DX8Wrapper::Set_Render_Target(static_cast(nullptr)); + } + if (small_render_target_surface != nullptr) { + small_render_target_surface->Release(); + } + if (intermediate_surface != nullptr) { + intermediate_surface->Release(); + } + if (intermediate_texture != nullptr) { + intermediate_texture->Release(); + } + REF_PTR_RELEASE(back_buffer); + REF_PTR_RELEASE(surface_class); + REF_PTR_RELEASE(render_target); + return result; +} + +// -- 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); +} + +void DX8Backend::Apply_Sorted_Batch_State(const RenderBackendSortedBatchState & state) +{ + if (state.shader != nullptr) + { + Set_Shader(*state.shader); + } + Set_Material(state.material); + // TheSuperHackers @bugfix bobtista 22/06/2026 Bind only the stages the device + // supports (the original sorted-draw path looped Get_Max_Textures_Per_Pass()). + // Setting beyond the cap trips a debug assert in Commit_Fixed_Function_Texture. + const auto * caps = DX8Wrapper::Get_Current_Caps(); + unsigned max_stages = RB_MAX_TEXTURE_STAGES; + if (caps != nullptr && (unsigned)caps->Get_Max_Textures_Per_Pass() < max_stages) + { + max_stages = (unsigned)caps->Get_Max_Textures_Per_Pass(); + } + for (unsigned i = 0; i < max_stages; ++i) + { + Set_Texture(i, state.textures[i]); + } + // TheSuperHackers @bugfix bobtista 19/06/2026 state.world/view are RenderStateStruct's + // LegacyTransformMatrix (== D3DMATRIX, already row-major device-ready) reinterpret-cast to + // Matrix4x4 by the sorting renderer. Running them through To_D3DMATRIX transposes a second + // time, corrupting every sorted/translucent draw's transform (e.g. helicopter rotor blur + // vanishes). Reinterpret straight back to D3DMATRIX to match the original passthrough. + if (state.world != nullptr) + { + DX8Wrapper::_Set_DX8_Transform( + D3DTS_WORLD, + reinterpret_cast(*state.world)); + } + if (state.view != nullptr) + { + DX8Wrapper::_Set_DX8_Transform( + D3DTS_VIEW, + reinterpret_cast(*state.view)); + } + for (int i = 0; i < 4; ++i) + { + if (state.lights.enabled[i]) + { + const RenderBackendLight & src = state.lights.lights[i]; + D3DLIGHT8 light; + memset(&light, 0, sizeof(light)); + light.Type = static_cast(src.type); + light.Position.x = src.position[0]; + light.Position.y = src.position[1]; + light.Position.z = src.position[2]; + light.Direction.x = src.direction[0]; + light.Direction.y = src.direction[1]; + light.Direction.z = src.direction[2]; + light.Diffuse.r = src.diffuse[0]; + light.Diffuse.g = src.diffuse[1]; + light.Diffuse.b = src.diffuse[2]; + light.Diffuse.a = 1.0f; + light.Ambient.r = src.ambient[0]; + light.Ambient.g = src.ambient[1]; + light.Ambient.b = src.ambient[2]; + light.Ambient.a = 1.0f; + light.Specular.r = src.specular[0]; + light.Specular.g = src.specular[1]; + light.Specular.b = src.specular[2]; + light.Specular.a = 1.0f; + light.Range = src.range; + light.Falloff = src.falloff; + light.Attenuation0 = src.attenuation[0]; + light.Attenuation1 = src.attenuation[1]; + light.Attenuation2 = src.attenuation[2]; + light.Theta = src.theta; + light.Phi = src.phi; + DX8Wrapper::Set_DX8_Light(i, &light); + } + else + { + DX8Wrapper::Set_DX8_Light(i, nullptr); + } + } +} + +void DX8Backend::Restore_Legacy_Render_State_For_Sorted_Draw(const RenderStateStruct & state) +{ + FixedFunctionState::Restore_Render_State(state); +} + +void DX8Backend::Capture_Legacy_Render_State_For_Sorted_Draw(RenderStateStruct & state) +{ + FixedFunctionState::Capture_Render_State(state); +} + +void DX8Backend::Release_Legacy_Render_State_For_Sorted_Draw() +{ + FixedFunctionState::Release_Render_State(); +} + +// -- 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::Apply_Material_State(const RenderBackendMaterialState & material) +{ + D3DMATERIAL8 dx_material; + memcpy(&dx_material.Diffuse, material.diffuse, sizeof(dx_material.Diffuse)); + memcpy(&dx_material.Ambient, material.ambient, sizeof(dx_material.Ambient)); + memcpy(&dx_material.Specular, material.specular, sizeof(dx_material.Specular)); + memcpy(&dx_material.Emissive, material.emissive, sizeof(dx_material.Emissive)); + dx_material.Power = material.power; + DX8Wrapper::Set_DX8_Material(&dx_material); +} + +void DX8Backend::Set_Material_Color_Source(RenderBackendMaterialColorSource ambient_source, + RenderBackendMaterialColorSource diffuse_source, + RenderBackendMaterialColorSource emissive_source) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_AMBIENTMATERIALSOURCE, static_cast(ambient_source)); + DX8Wrapper::Set_DX8_Render_State(D3DRS_DIFFUSEMATERIALSOURCE, static_cast(diffuse_source)); + DX8Wrapper::Set_DX8_Render_State(D3DRS_EMISSIVEMATERIALSOURCE, static_cast(emissive_source)); +} + +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 = Peek_Legacy_Texture2D(*dst_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::Bind_Texture_Immediate(unsigned int stage, TextureBaseClass * texture) +{ + IDirect3DBaseTexture8 * raw = (texture != nullptr) ? Peek_Legacy_Base_Texture(*texture) : nullptr; + DX8Wrapper::Set_DX8_Texture(stage, raw); + 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(); +} + +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::Set_Alpha_Test_Enable(bool enable) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, enable ? TRUE : FALSE); +} + +void DX8Backend::Set_Alpha_Test_Reference(unsigned ref) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF, ref); +} + +void DX8Backend::Set_Alpha_Test_Function(CompareFunc func) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC, static_cast(func)); +} + +void DX8Backend::Set_Normalize_Normals(bool enable) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_NORMALIZENORMALS, 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, const RenderBackendImage & image) +{ + IDirect3DDevice8 * pDev = DX8Wrapper::_Get_D3D_Device8(); + if (pDev != nullptr && image.Is_Valid()) + { + SurfaceClass surface(image.Width, image.Height, image.Format); + surface.Copy(image.Bytes.data(), image.Pitch); + pDev->SetCursorProperties( + static_cast(hotspot_x), + static_cast(hotspot_y), + Peek_Legacy_Surface(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)); +} + +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_Shade_Mode(ShadeMode mode) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_SHADEMODE, 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)); +} + +unsigned DX8Backend::Get_Color_Write_Mask() const +{ + // TheSuperHackers @bugfix bobtista 22/06/2026 Read the live device, not the + // redundancy cache. The occluded-player wash and stencil-shadow fill save and + // restore COLORWRITEENABLE around their masked passes; a stale cache made them + // restore the wrong value and leak the mask. Baseline read the device here. + IDirect3DDevice8 * device = DX8Wrapper::_Get_D3D_Device8(); + if (device != nullptr) + { + DWORD value = 0; + if (SUCCEEDED(device->GetRenderState(D3DRS_COLORWRITEENABLE, &value))) + { + return value; + } + } + return DX8Wrapper::Get_DX8_Render_State(D3DRS_COLORWRITEENABLE); +} + +bool DX8Backend::Supports_Color_Write_Mask() const +{ + if (!DX8Wrapper::Get_Current_Caps()) { + return false; + } + return (DX8Wrapper::Get_Current_Caps()->Get_DX8_Caps().PrimitiveMiscCaps & D3DPMISCCAPS_COLORWRITEENABLE) != 0; +} + +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_Point_Sprite_Enable(bool enable) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_POINTSPRITEENABLE, enable ? TRUE : FALSE); +} + +void DX8Backend::Set_Point_Scale_Enable(bool enable) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_POINTSCALEENABLE, enable ? TRUE : FALSE); +} + +void DX8Backend::Set_Point_Size(float size, float min_size, float max_size) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_POINTSIZE, FloatAsDword(size)); + DX8Wrapper::Set_DX8_Render_State(D3DRS_POINTSIZE_MIN, FloatAsDword(min_size)); + DX8Wrapper::Set_DX8_Render_State(D3DRS_POINTSIZE_MAX, FloatAsDword(max_size)); +} + +void DX8Backend::Set_Point_Scale(float a, float b, float c) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_POINTSCALE_A, FloatAsDword(a)); + DX8Wrapper::Set_DX8_Render_State(D3DRS_POINTSCALE_B, FloatAsDword(b)); + DX8Wrapper::Set_DX8_Render_State(D3DRS_POINTSCALE_C, FloatAsDword(c)); +} + +void DX8Backend::Set_Texture_Factor(unsigned argb) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, argb); +} + +void DX8Backend::Configure_Grayscale_Texture_Stages() +{ + if (Supports_Dot3()) + { + DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, kGrayscaleLuminanceWeights); + 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); + DX8Wrapper::Set_DX8_Texture_Stage_State(0, D3DTSS_COLOROP, D3DTOP_MULTIPLYADD); + + DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_COLORARG1, D3DTA_CURRENT); + DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_COLORARG2, D3DTA_TFACTOR); + DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_COLOROP, D3DTOP_DOTPRODUCT3); + } + else + { + // Fallback for hardware without DOT3 blend support. + DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, kGrayscaleFlatGray); + 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); + + DX8Wrapper::Set_DX8_Texture_Stage_State(1, D3DTSS_COLOROP, D3DTOP_DISABLE); + } +} + +void DX8Backend::Set_Texture_Transform(unsigned stage, const Matrix4x4 & matrix) +{ + DX8Wrapper::_Set_DX8_Transform( + static_cast(D3DTS_TEXTURE0 + stage), + To_D3DMATRIX(matrix)); +} + +void DX8Backend::Set_Texture_Coord_Source(unsigned stage, + RenderBackendTexcoordSource source, + unsigned uv_array_index) +{ + unsigned tci = uv_array_index; + switch (source) + { + case RB_TEXCOORD_MESH_UV: + tci = D3DTSS_TCI_PASSTHRU | uv_array_index; + break; + case RB_TEXCOORD_CAMERA_SPACE_NORMAL: + tci = D3DTSS_TCI_CAMERASPACENORMAL | uv_array_index; + break; + case RB_TEXCOORD_CAMERA_SPACE_REFLECTION: + tci = D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR | uv_array_index; + break; + case RB_TEXCOORD_CAMERA_SPACE_POSITION: + tci = D3DTSS_TCI_CAMERASPACEPOSITION | uv_array_index; + break; + } + + DX8Wrapper::Set_DX8_Texture_Stage_State( + stage, + D3DTSS_TEXCOORDINDEX, + tci); +} + +void DX8Backend::Set_Texture_Transform_Mode(unsigned stage, unsigned coord_count, bool projected) +{ + unsigned flags = coord_count == 0 ? D3DTTFF_DISABLE : coord_count; + if (projected) + { + flags |= D3DTTFF_PROJECTED; + } + + DX8Wrapper::Set_DX8_Texture_Stage_State( + stage, + D3DTSS_TEXTURETRANSFORMFLAGS, + flags); +} + +void DX8Backend::Set_Texture_UV_Wrap(unsigned stage, bool enable) +{ + if (stage >= RB_MAX_TEXTURE_STAGES) + { + return; + } + + DX8Wrapper::Set_DX8_Render_State( + static_cast(D3DRS_WRAP0 + stage), + enable ? (D3DWRAP_U | D3DWRAP_V) : 0); +} + +static D3DTEXTUREADDRESS TextureAddressModeToDX8(RenderBackendTextureAddressMode mode) +{ + switch (mode) + { + case RB_TEXTURE_ADDRESS_CLAMP: + return D3DTADDRESS_CLAMP; + case RB_TEXTURE_ADDRESS_BORDER: + return D3DTADDRESS_BORDER; + case RB_TEXTURE_ADDRESS_WRAP: + default: + return D3DTADDRESS_WRAP; + } +} + +void DX8Backend::Set_Texture_Address_Mode(unsigned stage, + RenderBackendTextureAddressMode u, + RenderBackendTextureAddressMode v, + RenderBackendTextureAddressMode w) +{ + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_ADDRESSU, TextureAddressModeToDX8(u)); + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_ADDRESSV, TextureAddressModeToDX8(v)); + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_ADDRESSW, TextureAddressModeToDX8(w)); +} + +static D3DTEXTUREFILTERTYPE TextureSampleFilterToDX8(RenderBackendTextureSampleFilter filter) +{ + switch (filter) + { + case RB_TEXTURE_SAMPLE_NONE: + return D3DTEXF_NONE; + case RB_TEXTURE_SAMPLE_POINT: + return D3DTEXF_POINT; + case RB_TEXTURE_SAMPLE_ANISOTROPIC: + return D3DTEXF_ANISOTROPIC; + case RB_TEXTURE_SAMPLE_LINEAR: + default: + return D3DTEXF_LINEAR; + } +} + +void DX8Backend::Set_Texture_Sample_Filter(unsigned stage, + RenderBackendTextureSampleFilter min_filter, + RenderBackendTextureSampleFilter mag_filter, + RenderBackendTextureSampleFilter mip_filter) +{ + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MINFILTER, TextureSampleFilterToDX8(min_filter)); + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MAGFILTER, TextureSampleFilterToDX8(mag_filter)); + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MIPFILTER, TextureSampleFilterToDX8(mip_filter)); +} + +void DX8Backend::Set_Texture_Min_Mag_Filter(unsigned stage, + RenderBackendTextureSampleFilter min_filter, + RenderBackendTextureSampleFilter mag_filter) +{ + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MINFILTER, TextureSampleFilterToDX8(min_filter)); + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MAGFILTER, TextureSampleFilterToDX8(mag_filter)); +} + +void DX8Backend::Set_Texture_Mip_Filter(unsigned stage, RenderBackendTextureSampleFilter mip_filter) +{ + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MIPFILTER, TextureSampleFilterToDX8(mip_filter)); +} + +void DX8Backend::Set_Texture_Max_Anisotropy(unsigned stage, unsigned max_anisotropy) +{ + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_MAXANISOTROPY, max_anisotropy); +} + +void DX8Backend::Set_Texture_Bump_Env_Matrix(unsigned stage, + float m00, + float m01, + float m10, + float m11) +{ + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_BUMPENVMAT00, FloatAsDword(m00)); + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_BUMPENVMAT01, FloatAsDword(m01)); + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_BUMPENVMAT10, FloatAsDword(m10)); + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_BUMPENVMAT11, FloatAsDword(m11)); +} + +void DX8Backend::Set_Texture_Bump_Env_Luminance(unsigned stage, + float scale, + float offset) +{ + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_BUMPENVLSCALE, FloatAsDword(scale)); + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_BUMPENVLOFFSET, FloatAsDword(offset)); +} + +void DX8Backend::Set_Texture_Color_Operation(unsigned stage, RenderBackendTextureOperation op) +{ + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_COLOROP, static_cast(op)); +} + +void DX8Backend::Set_Texture_Alpha_Operation(unsigned stage, RenderBackendTextureOperation op) +{ + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_ALPHAOP, static_cast(op)); +} + +void DX8Backend::Set_Texture_Color_Argument(unsigned stage, + unsigned argument_index, + RenderBackendTextureArgument arg) +{ + static const D3DTEXTURESTAGESTATETYPE states[] = { + D3DTSS_COLORARG0, + D3DTSS_COLORARG1, + D3DTSS_COLORARG2, + }; + if (argument_index >= sizeof(states) / sizeof(states[0])) + { + return; + } + + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, states[argument_index], static_cast(arg)); +} + +void DX8Backend::Set_Texture_Alpha_Argument(unsigned stage, + unsigned argument_index, + RenderBackendTextureArgument arg) +{ + static const D3DTEXTURESTAGESTATETYPE states[] = { + D3DTSS_ALPHAARG0, + D3DTSS_ALPHAARG1, + D3DTSS_ALPHAARG2, + }; + if (argument_index >= sizeof(states) / sizeof(states[0])) + { + return; + } + + DX8Wrapper::Set_DX8_Texture_Stage_State(stage, states[argument_index], static_cast(arg)); +} + +void DX8Backend::Set_Texture_Stage_State(unsigned stage, unsigned state, unsigned value) +{ + DX8Wrapper::Set_DX8_Texture_Stage_State( + stage, + static_cast(state), + value); +} + +void DX8Backend::Configure_Custom_Edging_Cloud_Texture_Stages() +{ + Set_Texture_Stage_State(0, D3DTSS_ALPHAARG1, D3DTA_CURRENT); + Set_Texture_Stage_State(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + + Set_Texture_Stage_State(1, D3DTSS_COLORARG1, D3DTA_CURRENT); + Set_Texture_Stage_State(1, D3DTSS_COLORARG2, D3DTA_TEXTURE); + Set_Texture_Stage_State(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + Set_Texture_Stage_State(1, D3DTSS_ALPHAARG1, D3DTA_CURRENT); + Set_Texture_Stage_State(1, D3DTSS_ALPHAARG2, D3DTA_TEXTURE); + Set_Texture_Stage_State(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + Set_Texture_Stage_State(1, D3DTSS_TEXCOORDINDEX, 1); +} + +void DX8Backend::Configure_Shadow_Volume_Fill_Texture_Stages() +{ + Set_Texture_Stage_State(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + Set_Texture_Stage_State(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + Set_Texture_Stage_State(0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); + Set_Texture_Stage_State(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + Set_Texture_Stage_State(0, D3DTSS_TEXCOORDINDEX, 0); + + Set_Texture_Stage_State(1, D3DTSS_COLOROP, D3DTOP_DISABLE); + Set_Texture_Stage_State(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + Set_Texture_Stage_State(1, D3DTSS_TEXCOORDINDEX, 1); +} + +CullMode DX8Backend::Get_Cull_Mode() const +{ + return static_cast(DX8Wrapper::Get_DX8_Render_State(D3DRS_CULLMODE)); +} + +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) +{ + 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) const +{ + 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() const +{ + return DX8Wrapper::Is_World_Identity(); +} + +bool DX8Backend::Is_View_Identity() const +{ + 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::Clear_Light(unsigned int index) +{ + DX8Wrapper::Set_Light(index, nullptr); +} + +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); +} + +void DX8Backend::Set_Fog_Enable(bool enable) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_FOGENABLE, enable ? TRUE : FALSE); +} + +void DX8Backend::Set_Fog_Color(unsigned argb) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_FOGCOLOR, argb); +} + +unsigned DX8Backend::Get_Fog_Color() const +{ + return DX8Wrapper::Get_Fog_Color(); +} + +// TheSuperHackers @bugfix bobtista 02/06/2026 Additional forwarders for +// the override calls W3DWater makes around its batched water draws (commit +// 0dc6548f2). Override_Alpha_Blend_Enable is already implemented above as +// part of the Override_* set; Override_Material_Opacity and Clear_State_Overrides +// are unique to the water batched draw and stay defensive. +// Override_Material_Opacity: D3DRS_TEXTUREFACTOR alpha +// Clear_State_Overrides: TFACTOR alpha back to 1.0 +void DX8Backend::Override_Material_Opacity(float opacity) +{ + unsigned a = static_cast(opacity * 255.0f); + if (a > 255) { a = 255; } + DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, (a << 24) | 0x00ffffff); +} + +void DX8Backend::Clear_State_Overrides() +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, 0xffffffff); +} + +void DX8Backend::Apply_Stencil_Shadow_Darken(unsigned shadow_color, + unsigned stencil_read_mask, + unsigned stencil_ref, + int x, + int y, + int width, + int height) +{ + LPDIRECT3DDEVICE8 device = DX8Wrapper::_Get_D3D_Device8(); + if (device == nullptr) + { + return; + } + + struct TranslitVertex + { + float x; + float y; + float z; + float w; + DWORD color; + } vertices[4]; + + vertices[0].x = x + width; vertices[0].y = y + height; vertices[0].z = 0.0f; vertices[0].w = 1.0f; + vertices[1].x = x + width; vertices[1].y = (float)y; vertices[1].z = 0.0f; vertices[1].w = 1.0f; + vertices[2].x = x; vertices[2].y = y + height; vertices[2].z = 0.0f; vertices[2].w = 1.0f; + vertices[3].x = x; vertices[3].y = (float)y; vertices[3].z = 0.0f; vertices[3].w = 1.0f; + + vertices[0].color = shadow_color; + vertices[1].color = shadow_color; + vertices[2].color = shadow_color; + vertices[3].color = shadow_color; + + device->SetVertexShader(DX8_FVF_FLAG_XYZRHW | DX8_FVF_FLAG_DIFFUSE); + + Set_Alpha_Blend_Enable(true); + Set_Blend_Factors(RB_BLEND_DEST_COLOR, RB_BLEND_ZERO); + Set_Depth_Test_Enable(true); + Set_Depth_Func(RB_CMP_ALWAYS); + Set_Stencil_Enable(true); + Set_Stencil_Func(RB_CMP_LESS_EQUAL); + Set_Stencil_Pass_Op(RB_STENCIL_OP_KEEP); + Set_Stencil_Mask(stencil_read_mask); + Set_Stencil_Ref(stencil_ref); + Set_Shade_Mode(RB_SHADE_FLAT); + + if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) + { + device->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, vertices, sizeof(TranslitVertex)); + } +} + +bool DX8Backend::Get_Fog_Enable() const +{ + return DX8Wrapper::Get_Fog_Enable(); +} + +void DX8Backend::Set_Specular_Enable(bool enable) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_SPECULARENABLE, enable ? TRUE : FALSE); +} + +void DX8Backend::Set_Patch_Segments(float level) +{ + DX8Wrapper::Set_DX8_Render_State(D3DRS_PATCHSEGMENTS, FloatAsDword(level)); +} + +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); +} + +bool DX8Backend::Is_Triangle_Draw_Enabled() const +{ + return DX8Wrapper::_Is_Triangle_Draw_Enabled(); +} + +void DX8Backend::Set_Triangle_Draw_Enabled(bool enable) +{ + DX8Wrapper::_Enable_Triangle_Draw(enable); +} + +void DX8Backend::Draw_Screen_Color_Quad(unsigned color, int x, int y, int width, int height) +{ + LPDIRECT3DDEVICE8 device = DX8Wrapper::_Get_D3D_Device8(); + if (device == nullptr || !DX8Wrapper::_Is_Triangle_Draw_Enabled()) + { + return; + } + + struct TranslitVertex + { + float x; + float y; + float z; + float w; + DWORD color; + } vertices[4]; + + vertices[0].x = x + width; vertices[0].y = y + height; vertices[0].z = 0.0f; vertices[0].w = 1.0f; + vertices[1].x = x + width; vertices[1].y = (float)y; vertices[1].z = 0.0f; vertices[1].w = 1.0f; + vertices[2].x = x; vertices[2].y = y + height; vertices[2].z = 0.0f; vertices[2].w = 1.0f; + vertices[3].x = x; vertices[3].y = (float)y; vertices[3].z = 0.0f; vertices[3].w = 1.0f; + + vertices[0].color = color; + vertices[1].color = color; + vertices[2].color = color; + vertices[3].color = color; + + device->SetVertexShader(DX8_FVF_FLAG_XYZRHW | DX8_FVF_FLAG_DIFFUSE); + device->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, vertices, sizeof(TranslitVertex)); +} + +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 --------------------------------------------------- + +const unsigned int * DX8Backend::Get_Legacy_Vertex_Shader_Declaration( + RenderBackendLegacyVertexDeclaration declaration) const +{ + switch (declaration) { + case RB_LEGACY_VERTEX_DECL_XYZNDUV1: + return reinterpret_cast(GetXYZNDUV1Declaration()); + } + + return nullptr; +} + +bool DX8Backend::Create_Vertex_Shader(const unsigned int * declaration, + const unsigned int * shader, + unsigned int usage, + unsigned long * handle) +{ + if (handle == nullptr) { + return false; + } + + DWORD dx_handle = 0; + HRESULT hr = DX8Wrapper::_Get_D3D_Device8()->CreateVertexShader( + reinterpret_cast(declaration), + reinterpret_cast(shader), + &dx_handle, + static_cast(usage)); + if (FAILED(hr)) { + return false; + } + + *handle = static_cast(dx_handle); + return true; +} + +bool DX8Backend::Create_Pixel_Shader(const unsigned int * shader, + unsigned long * handle) +{ + if (handle == nullptr) { + return false; + } + + DWORD dx_handle = 0; + HRESULT hr = DX8Wrapper::_Get_D3D_Device8()->CreatePixelShader( + reinterpret_cast(shader), + &dx_handle); + if (FAILED(hr)) { + return false; + } + + *handle = static_cast(dx_handle); + return true; +} + +void DX8Backend::Delete_Vertex_Shader(unsigned long vertex_shader) +{ + DX8Wrapper::_Get_D3D_Device8()->DeleteVertexShader(static_cast(vertex_shader)); +} + +void DX8Backend::Delete_Pixel_Shader(unsigned long pixel_shader) +{ + DX8Wrapper::_Get_D3D_Device8()->DeletePixelShader(static_cast(pixel_shader)); +} + +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) +{ + TextureClass * tex = DX8Wrapper::Create_Render_Target(width, height, format); + return tex; +} + +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() const +{ + 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) const +{ + return DX8Wrapper::Get_Shadow_Map(idx); +} + +// -- Resource creation (asset ingress) ------------------------------- +// +// RenderResource.id encoding for DX8Backend: the raw IDirect3D*8 pointer +// cast to uint64. This keeps the handle trivially invertible back to the +// D3D8 resource type for any DX8-specific code that needs it, and lets +// the BgfxBackend ref-popup mirror path look up the D3D8 pointer from the +// returned handle without any side table. + +RenderResource DX8Backend::Create_Texture(const TextureDesc & desc) +{ + const MipCountType mip_count = static_cast(desc.mip_count); + IDirect3DTexture8 * tex = DX8Wrapper::_Create_DX8_Texture( + desc.width, desc.height, desc.format, mip_count, + D3DPOOL_MANAGED, desc.is_render_target); + + if (tex != nullptr && !desc.is_render_target && desc.mips != nullptr) { + for (unsigned char level = 0; level < desc.mip_count; ++level) { + const MipSlice & slice = desc.mips[level]; + if (slice.data == nullptr || slice.size_bytes == 0) { + continue; + } + D3DLOCKED_RECT locked; + if (SUCCEEDED(tex->LockRect(level, &locked, nullptr, 0))) { + if (slice.pitch != 0 && static_cast(locked.Pitch) == slice.pitch) { + memcpy(locked.pBits, slice.data, slice.size_bytes); + } else if (slice.pitch != 0) { + const unsigned rows = slice.size_bytes / slice.pitch; + for (unsigned row = 0; row < rows; ++row) { + memcpy( + static_cast(locked.pBits) + row * locked.Pitch, + static_cast(slice.data) + row * slice.pitch, + slice.pitch); + } + } else { + // Compressed: the caller's size_bytes already accounts + // for block-compressed row packing. + memcpy(locked.pBits, slice.data, slice.size_bytes); + } + tex->UnlockRect(level); + } + } + } + + RenderResource rr; + rr.id = reinterpret_cast(tex); + return rr; +} + +RenderResource DX8Backend::Create_Vertex_Buffer(const BufferDesc & desc, const void * initial_data) +{ + IDirect3DVertexBuffer8 * vb = nullptr; + const DWORD usage = desc.dynamic ? (D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY) : D3DUSAGE_WRITEONLY; + const D3DPOOL pool = desc.dynamic ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED; + DX8Wrapper::_Get_D3D_Device8()->CreateVertexBuffer( + desc.size_bytes, usage, desc.layout.fvf, pool, &vb); + + if (vb != nullptr && initial_data != nullptr) + { + unsigned char * dst = nullptr; + if (SUCCEEDED(vb->Lock(0, desc.size_bytes, &dst, 0))) + { + memcpy(dst, initial_data, desc.size_bytes); + vb->Unlock(); + } + } + + RenderResource rr; + rr.id = reinterpret_cast(vb); + return rr; +} + +RenderResource DX8Backend::Create_Index_Buffer(const BufferDesc & desc, const void * initial_data, bool indices_are_32bit) +{ + IDirect3DIndexBuffer8 * ib = nullptr; + const DWORD usage = desc.dynamic ? (D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY) : D3DUSAGE_WRITEONLY; + const D3DPOOL pool = desc.dynamic ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED; + const D3DFORMAT fmt = indices_are_32bit ? D3DFMT_INDEX32 : D3DFMT_INDEX16; + DX8Wrapper::_Get_D3D_Device8()->CreateIndexBuffer( + desc.size_bytes, usage, fmt, pool, &ib); + + if (ib != nullptr && initial_data != nullptr) + { + unsigned char * dst = nullptr; + if (SUCCEEDED(ib->Lock(0, desc.size_bytes, &dst, 0))) + { + memcpy(dst, initial_data, desc.size_bytes); + ib->Unlock(); + } + } + + RenderResource rr; + rr.id = reinterpret_cast(ib); + return rr; +} + +void DX8Backend::Destroy_Resource(RenderResource h) +{ + IUnknown * obj = reinterpret_cast(h.id); + if (obj != nullptr) + { + obj->Release(); + } +} + +// Transitional: the legacy DX8 path already owns these +// resources through TextureBaseClass / DX8VertexBufferClass / +// DX8IndexBufferClass. DX8Backend must stay a passive forwarding adapter, +// so registered legacy resources do not get an owning RenderResource. + +RenderResource DX8Backend::Register_Texture_Resource(TextureBaseClass * /*tex*/) +{ + return kInvalidRenderResource; +} + +RenderResource DX8Backend::Register_Vertex_Buffer_Resource(VertexBufferClass * /*vb*/) +{ + return kInvalidRenderResource; +} + +RenderResource DX8Backend::Register_Index_Buffer_Resource(IndexBufferClass * /*ib*/) +{ + return kInvalidRenderResource; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h new file mode 100644 index 00000000000..6d59fa02041 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/DX8Backend.h @@ -0,0 +1,364 @@ +/* +** 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. + +#pragma once + +#include "IRenderBackend.h" + +class DX8Backend : public IRenderBackend +{ +public: + DX8Backend(); + virtual ~DX8Backend(); + + // -- Backend lifecycle ---------------------------------------------------- + + virtual void Initialize(void * hwnd, int width, int height); + virtual void Shutdown(); + + virtual bool Init_Render_System(void * hwnd, bool lite) override; + virtual void Shutdown_Render_System() override; + + // -- Device selection, windowing and display-mode control ----------------- + + virtual bool Set_Render_Device(const char * dev_name, int width, int height, int bits, int windowed, bool resize_window) override; + virtual bool Set_Render_Device(int dev, int width, int height, int bits, int windowed, bool resize_window, bool reset_device, bool restore_assets) override; + virtual bool Set_Any_Render_Device() override; + virtual bool Set_Next_Render_Device() override; + virtual bool Toggle_Windowed() override; + virtual bool Is_Windowed() const override; + virtual int Get_Render_Device() const override; + virtual const RenderDeviceDescClass & Get_Render_Device_Desc(int deviceidx) override; + virtual int Get_Render_Device_Count() const override; + virtual const char * Get_Render_Device_Name(int device_index) override; + virtual bool Set_Device_Resolution(int width, int height, int bits, int windowed, bool resize_window) override; + virtual void Get_Render_Target_Resolution(int & set_w, int & set_h, int & set_bits, bool & set_windowed) override; + virtual void Get_Device_Resolution(int & set_w, int & set_h, int & set_bits, bool & set_windowed) override; + virtual int Get_Device_Resolution_Width() const override; + virtual int Get_Device_Resolution_Height() const override; + virtual bool Registry_Save_Render_Device(const char * sub_key) override; + virtual bool Registry_Save_Render_Device(const char * sub_key, int device, int width, int height, int depth, bool windowed, int texture_depth) override; + virtual bool Registry_Load_Render_Device(const char * sub_key, bool resize_window) override; + virtual bool Registry_Load_Render_Device(const char * sub_key, char * device, int device_len, int & width, int & height, int & depth, int & windowed, int & texture_depth) override; + virtual void Set_Swap_Interval(int swap) override; + virtual int Get_Swap_Interval() const override; + + // -- Device state queries ------------------------------------------------- + + virtual bool Is_Device_Lost() const; + virtual RenderBackendDeviceStatus Get_Device_Status() const; + virtual void Reset_Device(); + virtual void Set_Device_Cleanup_Hook(RenderDeviceCleanupHook * hook) override; + virtual bool Has_Stencil() const; + virtual WW3DFormat Get_Back_Buffer_Format() const; + virtual bool Get_Back_Buffer_Description(unsigned int num, RenderBackendSurfaceDescription & desc) const override; + virtual bool Capture_Back_Buffer_Image(unsigned int num, RenderBackendImage & image) override; + virtual bool Copy_Back_Buffer_To_Texture(unsigned int num, TextureClass * dst_texture) override; + virtual void Set_Texture_Bitdepth(int bitdepth) override; + virtual int Get_Texture_Bitdepth() const override; + virtual bool Supports_Texture_Format(WW3DFormat format) const override; + virtual bool Supports_Compressed_Textures() const override; + virtual bool Supports_Bump_Envmap() const override; + virtual bool Supports_Bump_Envmap_Luminance() const override; + virtual bool Supports_Texture_Filter(RenderBackendTextureFilterCapability capability) const override; + virtual bool Supports_Texture_Op(RenderBackendTextureOpCapability capability) const override; + virtual bool Supports_Fog() const override; + virtual bool Is_Legacy_Voodoo3() const override; + virtual bool Supports_NPatches() const override; + virtual bool Supports_Hardware_Transform_And_Lighting() const override; + virtual bool Supports_Point_Sprites() const override; + virtual RenderBackendTextureLimits Get_Texture_Limits() const override; + virtual int Get_Max_Texture_Stages() const override; + virtual bool Supports_Z_Bias() const override; + virtual void Set_MSAA_Mode(RenderBackendMSAAMode mode); + virtual RenderBackendMSAAMode Get_MSAA_Mode() const; + virtual bool Supports_Dot3() const; + virtual bool Get_Device_Identity(RenderBackendDeviceIdentity & identity) const override; + 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 Begin_Device_Statistics() override; + virtual void End_Device_Statistics() override; + 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); + virtual bool Initialize_View_Capture(RenderBackendViewCaptureKind kind) override; + virtual void Release_View_Capture(RenderBackendViewCaptureKind kind) override; + virtual bool Supports_View_Capture(RenderBackendViewCaptureKind kind) const override; + virtual bool Begin_View_Capture(RenderBackendViewCaptureKind kind) override; + virtual bool End_View_Capture(RenderBackendViewCaptureKind kind) override; + virtual bool Is_View_Capture_Active(RenderBackendViewCaptureKind kind) const override; + virtual bool Has_View_Capture(RenderBackendViewCaptureKind kind) const override; + virtual bool Bind_View_Capture_Texture(RenderBackendViewCaptureKind kind, + unsigned int stage) override; + virtual bool Draw_View_Capture_Quad(RenderBackendViewCaptureKind kind, + const RenderBackendScreenVertex * vertices, + unsigned int vertex_count, + bool use_second_uv) override; + virtual bool Draw_Screen_Quad(const RenderBackendScreenVertex * vertices, + unsigned int vertex_count, + bool use_second_uv) override; + virtual bool Capture_Back_Buffer_RGBA(unsigned int display_width, + unsigned int display_height, + unsigned int image_size, + unsigned char * output_pixels, + unsigned int output_capacity, + unsigned int * output_width, + unsigned int * output_height) override; + + // -- 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); + virtual void Apply_Sorted_Batch_State(const RenderBackendSortedBatchState & state) override; + virtual void Capture_Legacy_Render_State_For_Sorted_Draw(RenderStateStruct & state) override; + virtual void Restore_Legacy_Render_State_For_Sorted_Draw(const RenderStateStruct & state) override; + virtual void Release_Legacy_Render_State_For_Sorted_Draw() override; + + // -- 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 Apply_Material_State(const RenderBackendMaterialState & material) override; + virtual void Set_Material_Color_Source(RenderBackendMaterialColorSource ambient_source, + RenderBackendMaterialColorSource diffuse_source, + RenderBackendMaterialColorSource emissive_source) override; + virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture); + virtual void Bind_Texture_Immediate(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 Set_Alpha_Test_Enable(bool enable); + virtual void Set_Alpha_Test_Reference(unsigned ref); + virtual void Set_Alpha_Test_Function(CompareFunc func); + virtual void Set_Normalize_Normals(bool enable); + virtual void Show_Hardware_Cursor(bool show); + virtual void Set_Hardware_Cursor_Image(int hotspot_x, int hotspot_y, const RenderBackendImage & image) override; + 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); + + // Extended render-state setters (see IRenderBackend.h). + virtual void Set_Z_Bias(int bias); + virtual void Set_Fill_Mode(FillMode mode); + virtual void Set_Shade_Mode(ShadeMode 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 bool Supports_Color_Write_Mask() const override; + virtual unsigned Get_Color_Write_Mask() const override; + virtual void Set_Color_Write_Mask(unsigned mask) override; + virtual void Set_Lighting_Enable(bool enable); + virtual void Set_Point_Sprite_Enable(bool enable) override; + virtual void Set_Point_Scale_Enable(bool enable) override; + virtual void Set_Point_Size(float size, float min_size, float max_size) override; + virtual void Set_Point_Scale(float a, float b, float c) override; + virtual void Set_Texture_Factor(unsigned argb); + virtual void Configure_Grayscale_Texture_Stages() override; + virtual void Configure_Custom_Edging_Cloud_Texture_Stages() override; + virtual void Configure_Shadow_Volume_Fill_Texture_Stages() override; + virtual void Set_Texture_Transform(unsigned stage, const Matrix4x4 & matrix) override; + virtual void Set_Texture_Coord_Source(unsigned stage, + RenderBackendTexcoordSource source, + unsigned uv_array_index = 0) override; + virtual void Set_Texture_Transform_Mode(unsigned stage, unsigned coord_count, bool projected) override; + virtual void Set_Texture_UV_Wrap(unsigned stage, bool enable) override; + virtual void Set_Texture_Address_Mode(unsigned stage, + RenderBackendTextureAddressMode u, + RenderBackendTextureAddressMode v, + RenderBackendTextureAddressMode w) override; + virtual void Set_Texture_Sample_Filter(unsigned stage, + RenderBackendTextureSampleFilter min_filter, + RenderBackendTextureSampleFilter mag_filter, + RenderBackendTextureSampleFilter mip_filter) override; + virtual void Set_Texture_Min_Mag_Filter(unsigned stage, + RenderBackendTextureSampleFilter min_filter, + RenderBackendTextureSampleFilter mag_filter) override; + virtual void Set_Texture_Mip_Filter(unsigned stage, + RenderBackendTextureSampleFilter mip_filter) override; + virtual void Set_Texture_Max_Anisotropy(unsigned stage, unsigned max_anisotropy) override; + virtual void Set_Texture_Bump_Env_Matrix(unsigned stage, + float m00, + float m01, + float m10, + float m11) override; + virtual void Set_Texture_Bump_Env_Luminance(unsigned stage, + float scale, + float offset) override; + virtual void Set_Texture_Color_Operation(unsigned stage, + RenderBackendTextureOperation op) override; + virtual void Set_Texture_Alpha_Operation(unsigned stage, + RenderBackendTextureOperation op) override; + virtual void Set_Texture_Color_Argument(unsigned stage, + unsigned argument_index, + RenderBackendTextureArgument arg) override; + virtual void Set_Texture_Alpha_Argument(unsigned stage, + unsigned argument_index, + RenderBackendTextureArgument arg) override; + virtual void Set_Texture_Stage_State(unsigned stage, unsigned state, unsigned value) override; + virtual CullMode Get_Cull_Mode() const override; + virtual void Set_Cull_Mode(CullMode mode) override; + + // 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); + virtual void Set_Transform(TransformKind transform, const Matrix3D & 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() 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 ----------------------------------------------------- + + virtual void Set_Light(unsigned int index, const LightClass & light); + virtual void Clear_Light(unsigned int index); + 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 void Set_Fog_Enable(bool enable) override; + virtual void Set_Fog_Color(unsigned argb) override; + virtual unsigned Get_Fog_Color() const override; + + // TheSuperHackers @bugfix bobtista 02/06/2026 Additional forwarders + // for the override calls the W3DWater batched draw path makes (commit + // 0dc6548f2). BgfxBackend implements them; the IRenderBackend defaults + // are empty no-ops. Override_Alpha_Blend_Enable is already declared + // above as part of the Override_* set; the two below are unique to the + // water batched draw and keep the override mechanism functional on dx8. + virtual void Override_Material_Opacity(float opacity) override; + virtual void Clear_State_Overrides() override; + virtual void Apply_Stencil_Shadow_Darken(unsigned shadow_color, + unsigned stencil_read_mask, + unsigned stencil_ref, + int x, + int y, + int width, + int height) override; + virtual bool Get_Fog_Enable() const; + virtual void Set_Light_Environment(LightEnvironmentClass * light_env); + virtual LightEnvironmentClass * Get_Light_Environment() const; + virtual void Set_Specular_Enable(bool enable) override; + virtual void Set_Patch_Segments(float level) override; + + // -- 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 bool Is_Triangle_Draw_Enabled() const override; + virtual void Set_Triangle_Draw_Enabled(bool enable) override; + virtual void Draw_Screen_Color_Quad(unsigned color, + int x, + int y, + int width, + int height) override; + virtual void Draw_Strip(unsigned short start_index, + unsigned short index_count, + unsigned short min_vertex_index, + unsigned short vertex_count); + + // -- Programmable pipeline ------------------------------------------------ + + virtual const unsigned int * Get_Legacy_Vertex_Shader_Declaration( + RenderBackendLegacyVertexDeclaration declaration) const override; + virtual bool Create_Vertex_Shader(const unsigned int * declaration, + const unsigned int * shader, + unsigned int usage, + unsigned long * handle) override; + virtual bool Create_Pixel_Shader(const unsigned int * shader, + unsigned long * handle) override; + virtual void Delete_Vertex_Shader(unsigned long vertex_shader) override; + virtual void Delete_Pixel_Shader(unsigned long pixel_shader) override; + 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() const; + virtual void Set_Shadow_Map(int idx, ZTextureClass * ztex); + virtual ZTextureClass * Get_Shadow_Map(int idx) const; + + // -- Resource creation (asset ingress) ----------------------------------- + + virtual RenderResource Create_Texture(const TextureDesc & desc); + virtual RenderResource Create_Vertex_Buffer(const BufferDesc & desc, const void * initial_data); + virtual RenderResource Create_Index_Buffer(const BufferDesc & desc, const void * initial_data, bool indices_are_32bit); + virtual void Destroy_Resource(RenderResource h); + + virtual RenderResource Register_Texture_Resource(TextureBaseClass * tex); + virtual RenderResource Register_Vertex_Buffer_Resource(VertexBufferClass * vb); + virtual RenderResource Register_Index_Buffer_Resource(IndexBufferClass * ib); +}; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DXTUtils.h b/Core/Libraries/Source/WWVegas/WW3D2/DXTUtils.h new file mode 100644 index 00000000000..619e9ce4c37 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/DXTUtils.h @@ -0,0 +1,41 @@ +/* +** 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 26/04/2026 Shared DXT block-compressed +// texture math used by the bgfx texture loader. Keeping these in one place +// prevents the pitch/row calculations from diverging. + +#pragma once + +// TheSuperHackers @info bobtista 26/04/2026 DXT block dimensions are always +// 4x4 pixels. DXT1 uses 8 bytes per block, DXT2-5 use 16 bytes per block. + +inline unsigned DXT_SurfacePitch(unsigned width, unsigned blockBytes) +{ + return ((width + 3) / 4) * blockBytes; +} + +inline unsigned DXT_SurfaceRows(unsigned height) +{ + return (height + 3) / 4; +} + +inline unsigned DXT_SurfaceStorageSize(unsigned width, unsigned height, unsigned blockBytes) +{ + return DXT_SurfacePitch(width, blockBytes) * DXT_SurfaceRows(height); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DrawCallLog.cpp b/Core/Libraries/Source/WWVegas/WW3D2/DrawCallLog.cpp new file mode 100644 index 00000000000..e558dad3cb3 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/DrawCallLog.cpp @@ -0,0 +1,145 @@ +// TheSuperHackers @feature bobtista 01/06/2026 See DrawCallLog.h. + +#include "DrawCallLog.h" +#include "GgcRuntimeFlags.h" + +#include +#include +#include + +namespace +{ + +static int s_targetFrame = -1; +static int s_interval = 0; +static char s_basePath[512] = {}; +static bool s_envResolved = false; +static int s_frameIndex = 0; +static FILE * s_currentFile = nullptr; +static int s_drawIdx = 0; + +void Resolve_Env() +{ + if (s_envResolved) { + return; + } + s_envResolved = true; + s_targetFrame = GgcFlags::IntValue(GgcFlag_DrawLogAfter); + s_interval = GgcFlags::IntValue(GgcFlag_DrawLogInterval); + const char * pathEnv = GgcFlags::StringValue(GgcFlag_DrawLogPath); + if (pathEnv != nullptr) { + std::strncpy(s_basePath, pathEnv, sizeof(s_basePath) - 1); + } + + // Confirmation file so the operator can verify env vars were picked up. + // GGC_DRAWLOG_* must be set as persistent user env vars (setx / + // [Environment]::SetEnvironmentVariable) — process-scoped env vars do + // NOT propagate to the game from PowerShell or cmd because of how the + // game's spawn chain resets the env block. See README for details. + if (s_basePath[0] != '\0') { + char marker[640]; + std::snprintf(marker, sizeof(marker), "%s.startup.txt", s_basePath); + FILE * f = std::fopen(marker, "w"); + if (f != nullptr) { + std::fprintf(f, "after=%d interval=%d path=%s\n", + s_targetFrame, s_interval, s_basePath); + std::fclose(f); + } + } +} + +bool Should_Log_This_Frame() +{ + if (s_targetFrame <= 0 || s_basePath[0] == '\0') { + return false; + } + if (s_frameIndex == s_targetFrame) { + return true; + } + if (s_interval > 0 + && s_frameIndex > s_targetFrame + && ((s_frameIndex - s_targetFrame) % s_interval) == 0) { + return true; + } + return false; +} + +void Open_Current_File_If_Needed() +{ + if (s_currentFile != nullptr) { + return; + } + char path[640]; + std::snprintf(path, sizeof(path), "%s.%06d.csv", s_basePath, s_frameIndex); + s_currentFile = std::fopen(path, "w"); + if (s_currentFile != nullptr) { + std::fprintf(s_currentFile, + "draw_idx,prim_type,poly_count,vert_count,vb_type,ib_type," + "shader_bits,sorted_draw_flags,tex0\n"); + } + s_drawIdx = 0; +} + +} // namespace + +bool DrawCallLog_Is_Active() +{ + Resolve_Env(); + return Should_Log_This_Frame(); +} + +void DrawCallLog_Record( + unsigned primitive_type, + unsigned polygon_count, + unsigned vertex_count, + unsigned vb_type, + unsigned ib_type, + unsigned shader_bits, + unsigned sorted_draw_flags, + const char * texture0_name) +{ + Resolve_Env(); + if (!Should_Log_This_Frame()) { + return; + } + Open_Current_File_If_Needed(); + if (s_currentFile == nullptr) { + return; + } + const char * name = (texture0_name != nullptr) ? texture0_name : ""; + char safe[256]; + unsigned i = 0; + for (; name[i] != '\0' && i + 1 < sizeof(safe); ++i) { + const char c = name[i]; + safe[i] = (c == ',' || c == '"' || c == '\n' || c == '\r') ? '_' : c; + } + safe[i] = '\0'; + + std::fprintf(s_currentFile, + "%d,%u,%u,%u,%u,%u,0x%08x,0x%08x,%s\n", + s_drawIdx++, + primitive_type, polygon_count, vertex_count, + vb_type, ib_type, + shader_bits, sorted_draw_flags, + safe); +} + +void DrawCallLog_End_Frame() +{ + Resolve_Env(); + if (s_currentFile != nullptr) { + std::fclose(s_currentFile); + s_currentFile = nullptr; + } + ++s_frameIndex; + + if (s_basePath[0] != '\0' && (s_frameIndex % 60) == 0) { + char marker[640]; + std::snprintf(marker, sizeof(marker), "%s.heartbeat.txt", s_basePath); + FILE * f = std::fopen(marker, "a"); + if (f != nullptr) { + std::fprintf(f, "frame=%d\n", s_frameIndex); + std::fclose(f); + } + } +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/DrawCallLog.h b/Core/Libraries/Source/WWVegas/WW3D2/DrawCallLog.h new file mode 100644 index 00000000000..a7dcbb00260 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/DrawCallLog.h @@ -0,0 +1,21 @@ +// TheSuperHackers @feature bobtista 01/06/2026 +// Per-frame draw-call dump for backend diagnostic. GGC_DRAWLOG_AFTER= +// + GGC_DRAWLOG_PATH= opens one CSV per logged frame at +// ..csv with one row per DX8Wrapper::Draw call. +// Optional GGC_DRAWLOG_INTERVAL= repeats every K frames after the first. + +#pragma once + +bool DrawCallLog_Is_Active(); + +void DrawCallLog_Record( + unsigned primitive_type, + unsigned polygon_count, + unsigned vertex_count, + unsigned vb_type, + unsigned ib_type, + unsigned shader_bits, + unsigned sorted_draw_flags, + const char * texture0_name); + +void DrawCallLog_End_Frame(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/FixedFunctionState.cpp b/Core/Libraries/Source/WWVegas/WW3D2/FixedFunctionState.cpp new file mode 100644 index 00000000000..999a20fe746 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/FixedFunctionState.cpp @@ -0,0 +1,1264 @@ +/* +** 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. +*/ + +#include "FixedFunctionState.h" + +#if !defined(GGC_RENDER_BACKEND_BGFX) +#include "d3d8.h" +#endif +#include "indexbuffer.h" +#include "RenderStateDefs.h" +#include "vertexbuffer.h" + +#include + +namespace +{ + RenderStateStruct s_renderState; + unsigned s_changedMask; + LegacyRawTexture * s_rawTextures[MAX_TEXTURE_STAGES]; + unsigned s_renderStates[FixedFunctionState::RENDER_STATE_COUNT]; + unsigned s_textureStageStates[FixedFunctionState::TEXTURE_STAGE_COUNT][FixedFunctionState::TEXTURE_STAGE_STATE_COUNT]; + LegacyTransformMatrix s_transforms[FixedFunctionState::TRANSFORM_COUNT]; + + struct SemanticRenderState + { + bool cullModeValid; + unsigned cullMode; + bool lightingValid; + bool lightingEnabled; + bool fogColorValid; + unsigned fogColor; + bool colorWriteMaskValid; + unsigned colorWriteMask; + bool ambientColorValid; + unsigned ambientColor; + bool ambientMaterialSourceValid; + unsigned ambientMaterialSource; + bool diffuseMaterialSourceValid; + unsigned diffuseMaterialSource; + bool emissiveMaterialSourceValid; + unsigned emissiveMaterialSource; + bool sourceBlendFactorValid; + unsigned sourceBlendFactor; + bool destinationBlendFactorValid; + unsigned destinationBlendFactor; + bool blendOpValid; + unsigned blendOp; + bool alphaBlendValid; + bool alphaBlendEnabled; + bool alphaTestValid; + bool alphaTestEnabled; + bool alphaTestReferenceValid; + unsigned alphaTestReference; + bool alphaTestFunctionValid; + unsigned alphaTestFunction; + bool zBiasValid; + unsigned zBias; + bool fillModeValid; + unsigned fillMode; + bool shadeModeValid; + unsigned shadeMode; + bool depthTestValid; + bool depthTestEnabled; + bool depthWriteValid; + bool depthWriteEnabled; + bool depthFunctionValid; + unsigned depthFunction; + bool fogEnableValid; + bool fogEnabled; + bool specularEnableValid; + bool specularEnabled; + bool patchSegmentsValid; + unsigned patchSegments; + bool normalizeNormalsValid; + bool normalizeNormalsEnabled; + bool textureFactorValid; + unsigned textureFactor; + bool pointSpriteValid; + bool pointSpriteEnabled; + bool pointScaleValid; + bool pointScaleEnabled; + bool pointSizeValid; + unsigned pointSize; + unsigned pointSizeMin; + unsigned pointSizeMax; + bool pointScaleValuesValid; + unsigned pointScaleA; + unsigned pointScaleB; + unsigned pointScaleC; + bool stencilEnableValid; + bool stencilEnabled; + bool stencilFunctionValid; + unsigned stencilFunction; + bool stencilReferenceValid; + unsigned stencilReference; + bool stencilReadMaskValid; + unsigned stencilReadMask; + bool stencilWriteMaskValid; + unsigned stencilWriteMask; + bool stencilOpsValid; + unsigned stencilPassOp; + unsigned stencilFailOp; + unsigned stencilZFailOp; + }; + + SemanticRenderState s_semanticRenderState; + + void LegacyMatrixIdentity(LegacyTransformMatrix * dxm) + { + memset(dxm, 0, sizeof(*dxm)); + dxm->_11 = 1.0f; + dxm->_22 = 1.0f; + dxm->_33 = 1.0f; + dxm->_44 = 1.0f; + } + + void ClearSemanticRenderState() + { + s_semanticRenderState.cullModeValid = true; + s_semanticRenderState.cullMode = 0; + s_semanticRenderState.lightingValid = true; + s_semanticRenderState.lightingEnabled = false; + s_semanticRenderState.fogColorValid = true; + s_semanticRenderState.fogColor = 0; + s_semanticRenderState.colorWriteMaskValid = true; + s_semanticRenderState.colorWriteMask = 0; + s_semanticRenderState.ambientColorValid = true; + s_semanticRenderState.ambientColor = 0; + s_semanticRenderState.ambientMaterialSourceValid = true; + s_semanticRenderState.ambientMaterialSource = 0; + s_semanticRenderState.diffuseMaterialSourceValid = true; + s_semanticRenderState.diffuseMaterialSource = 0; + s_semanticRenderState.emissiveMaterialSourceValid = true; + s_semanticRenderState.emissiveMaterialSource = 0; + s_semanticRenderState.sourceBlendFactorValid = true; + s_semanticRenderState.sourceBlendFactor = 0; + s_semanticRenderState.destinationBlendFactorValid = true; + s_semanticRenderState.destinationBlendFactor = 0; + s_semanticRenderState.blendOpValid = true; + s_semanticRenderState.blendOp = 0; + s_semanticRenderState.alphaBlendValid = true; + s_semanticRenderState.alphaBlendEnabled = false; + s_semanticRenderState.alphaTestValid = true; + s_semanticRenderState.alphaTestEnabled = false; + s_semanticRenderState.alphaTestReferenceValid = true; + s_semanticRenderState.alphaTestReference = 0; + s_semanticRenderState.alphaTestFunctionValid = true; + s_semanticRenderState.alphaTestFunction = 0; + s_semanticRenderState.zBiasValid = true; + s_semanticRenderState.zBias = 0; + s_semanticRenderState.fillModeValid = true; + s_semanticRenderState.fillMode = 0; + s_semanticRenderState.shadeModeValid = true; + s_semanticRenderState.shadeMode = 0; + s_semanticRenderState.depthTestValid = true; + s_semanticRenderState.depthTestEnabled = false; + s_semanticRenderState.depthWriteValid = true; + s_semanticRenderState.depthWriteEnabled = false; + s_semanticRenderState.depthFunctionValid = true; + s_semanticRenderState.depthFunction = 0; + s_semanticRenderState.fogEnableValid = true; + s_semanticRenderState.fogEnabled = false; + s_semanticRenderState.specularEnableValid = true; + s_semanticRenderState.specularEnabled = false; + s_semanticRenderState.patchSegmentsValid = true; + s_semanticRenderState.patchSegments = 0; + s_semanticRenderState.normalizeNormalsValid = true; + s_semanticRenderState.normalizeNormalsEnabled = false; + s_semanticRenderState.textureFactorValid = true; + s_semanticRenderState.textureFactor = 0; + s_semanticRenderState.pointSpriteValid = true; + s_semanticRenderState.pointSpriteEnabled = false; + s_semanticRenderState.pointScaleValid = true; + s_semanticRenderState.pointScaleEnabled = false; + s_semanticRenderState.pointSizeValid = true; + s_semanticRenderState.pointSize = 0; + s_semanticRenderState.pointSizeMin = 0; + s_semanticRenderState.pointSizeMax = 0; + s_semanticRenderState.pointScaleValuesValid = true; + s_semanticRenderState.pointScaleA = 0; + s_semanticRenderState.pointScaleB = 0; + s_semanticRenderState.pointScaleC = 0; + s_semanticRenderState.stencilEnableValid = true; + s_semanticRenderState.stencilEnabled = false; + s_semanticRenderState.stencilFunctionValid = true; + s_semanticRenderState.stencilFunction = 0; + s_semanticRenderState.stencilReferenceValid = true; + s_semanticRenderState.stencilReference = 0; + s_semanticRenderState.stencilReadMaskValid = true; + s_semanticRenderState.stencilReadMask = 0; + s_semanticRenderState.stencilWriteMaskValid = true; + s_semanticRenderState.stencilWriteMask = 0; + s_semanticRenderState.stencilOpsValid = true; + s_semanticRenderState.stencilPassOp = 0; + s_semanticRenderState.stencilFailOp = 0; + s_semanticRenderState.stencilZFailOp = 0; + } + + void InvalidateSemanticRenderState() + { + s_semanticRenderState.cullModeValid = false; + s_semanticRenderState.cullMode = 0; + s_semanticRenderState.lightingValid = false; + s_semanticRenderState.lightingEnabled = false; + s_semanticRenderState.fogColorValid = false; + s_semanticRenderState.fogColor = 0; + s_semanticRenderState.colorWriteMaskValid = false; + s_semanticRenderState.colorWriteMask = 0; + s_semanticRenderState.ambientColorValid = false; + s_semanticRenderState.ambientColor = 0; + s_semanticRenderState.ambientMaterialSourceValid = false; + s_semanticRenderState.ambientMaterialSource = 0; + s_semanticRenderState.diffuseMaterialSourceValid = false; + s_semanticRenderState.diffuseMaterialSource = 0; + s_semanticRenderState.emissiveMaterialSourceValid = false; + s_semanticRenderState.emissiveMaterialSource = 0; + s_semanticRenderState.sourceBlendFactorValid = false; + s_semanticRenderState.sourceBlendFactor = 0; + s_semanticRenderState.destinationBlendFactorValid = false; + s_semanticRenderState.destinationBlendFactor = 0; + s_semanticRenderState.blendOpValid = false; + s_semanticRenderState.blendOp = 0; + s_semanticRenderState.alphaBlendValid = false; + s_semanticRenderState.alphaBlendEnabled = false; + s_semanticRenderState.alphaTestValid = false; + s_semanticRenderState.alphaTestEnabled = false; + s_semanticRenderState.alphaTestReferenceValid = false; + s_semanticRenderState.alphaTestReference = 0; + s_semanticRenderState.alphaTestFunctionValid = false; + s_semanticRenderState.alphaTestFunction = 0; + s_semanticRenderState.zBiasValid = false; + s_semanticRenderState.zBias = 0; + s_semanticRenderState.fillModeValid = false; + s_semanticRenderState.fillMode = 0; + s_semanticRenderState.shadeModeValid = false; + s_semanticRenderState.shadeMode = 0; + s_semanticRenderState.depthTestValid = false; + s_semanticRenderState.depthTestEnabled = false; + s_semanticRenderState.depthWriteValid = false; + s_semanticRenderState.depthWriteEnabled = false; + s_semanticRenderState.depthFunctionValid = false; + s_semanticRenderState.depthFunction = 0; + s_semanticRenderState.fogEnableValid = false; + s_semanticRenderState.fogEnabled = false; + s_semanticRenderState.specularEnableValid = false; + s_semanticRenderState.specularEnabled = false; + s_semanticRenderState.patchSegmentsValid = false; + s_semanticRenderState.patchSegments = 0; + s_semanticRenderState.normalizeNormalsValid = false; + s_semanticRenderState.normalizeNormalsEnabled = false; + s_semanticRenderState.textureFactorValid = false; + s_semanticRenderState.textureFactor = 0; + s_semanticRenderState.pointSpriteValid = false; + s_semanticRenderState.pointSpriteEnabled = false; + s_semanticRenderState.pointScaleValid = false; + s_semanticRenderState.pointScaleEnabled = false; + s_semanticRenderState.pointSizeValid = false; + s_semanticRenderState.pointSize = 0; + s_semanticRenderState.pointSizeMin = 0; + s_semanticRenderState.pointSizeMax = 0; + s_semanticRenderState.pointScaleValuesValid = false; + s_semanticRenderState.pointScaleA = 0; + s_semanticRenderState.pointScaleB = 0; + s_semanticRenderState.pointScaleC = 0; + s_semanticRenderState.stencilEnableValid = false; + s_semanticRenderState.stencilEnabled = false; + s_semanticRenderState.stencilFunctionValid = false; + s_semanticRenderState.stencilFunction = 0; + s_semanticRenderState.stencilReferenceValid = false; + s_semanticRenderState.stencilReference = 0; + s_semanticRenderState.stencilReadMaskValid = false; + s_semanticRenderState.stencilReadMask = 0; + s_semanticRenderState.stencilWriteMaskValid = false; + s_semanticRenderState.stencilWriteMask = 0; + s_semanticRenderState.stencilOpsValid = false; + s_semanticRenderState.stencilPassOp = 0; + s_semanticRenderState.stencilFailOp = 0; + s_semanticRenderState.stencilZFailOp = 0; + } + + void MirrorSemanticRenderState(unsigned state, unsigned value) + { + switch (state) { + case RS::CULLMODE: + s_semanticRenderState.cullModeValid = true; + s_semanticRenderState.cullMode = value; + break; + case RS::LIGHTING: + s_semanticRenderState.lightingValid = true; + s_semanticRenderState.lightingEnabled = (value != 0); + break; + case RS::FOGCOLOR: + s_semanticRenderState.fogColorValid = true; + s_semanticRenderState.fogColor = value; + break; + case RS::COLORWRITEENABLE: + s_semanticRenderState.colorWriteMaskValid = true; + s_semanticRenderState.colorWriteMask = value; + break; + case RS::AMBIENT: + s_semanticRenderState.ambientColorValid = true; + s_semanticRenderState.ambientColor = value; + break; + case RS::AMBIENTMATERIALSOURCE: + s_semanticRenderState.ambientMaterialSourceValid = true; + s_semanticRenderState.ambientMaterialSource = value; + break; + case RS::DIFFUSEMATERIALSOURCE: + s_semanticRenderState.diffuseMaterialSourceValid = true; + s_semanticRenderState.diffuseMaterialSource = value; + break; + case RS::EMISSIVEMATERIALSOURCE: + s_semanticRenderState.emissiveMaterialSourceValid = true; + s_semanticRenderState.emissiveMaterialSource = value; + break; + case RS::SRCBLEND: + s_semanticRenderState.sourceBlendFactorValid = true; + s_semanticRenderState.sourceBlendFactor = value; + break; + case RS::DESTBLEND: + s_semanticRenderState.destinationBlendFactorValid = true; + s_semanticRenderState.destinationBlendFactor = value; + break; + case RS::BLENDOP: + s_semanticRenderState.blendOpValid = true; + s_semanticRenderState.blendOp = value; + break; + case RS::ALPHABLENDENABLE: + s_semanticRenderState.alphaBlendValid = true; + s_semanticRenderState.alphaBlendEnabled = (value != 0); + break; + case RS::ALPHATESTENABLE: + s_semanticRenderState.alphaTestValid = true; + s_semanticRenderState.alphaTestEnabled = (value != 0); + break; + case RS::ALPHAREF: + s_semanticRenderState.alphaTestReferenceValid = true; + s_semanticRenderState.alphaTestReference = value; + break; + case RS::ALPHAFUNC: + s_semanticRenderState.alphaTestFunctionValid = true; + s_semanticRenderState.alphaTestFunction = value; + break; + case RS::ZBIAS: + s_semanticRenderState.zBiasValid = true; + s_semanticRenderState.zBias = value; + break; + case RS::FILLMODE: + s_semanticRenderState.fillModeValid = true; + s_semanticRenderState.fillMode = value; + break; + case RS::SHADEMODE: + s_semanticRenderState.shadeModeValid = true; + s_semanticRenderState.shadeMode = value; + break; + case RS::ZENABLE: + s_semanticRenderState.depthTestValid = true; + s_semanticRenderState.depthTestEnabled = (value != 0); + break; + case RS::ZWRITEENABLE: + s_semanticRenderState.depthWriteValid = true; + s_semanticRenderState.depthWriteEnabled = (value != 0); + break; + case RS::ZFUNC: + s_semanticRenderState.depthFunctionValid = true; + s_semanticRenderState.depthFunction = value; + break; + case RS::FOGENABLE: + s_semanticRenderState.fogEnableValid = true; + s_semanticRenderState.fogEnabled = (value != 0); + break; + case RS::SPECULARENABLE: + s_semanticRenderState.specularEnableValid = true; + s_semanticRenderState.specularEnabled = (value != 0); + break; + case RS::PATCHSEGMENTS: + s_semanticRenderState.patchSegmentsValid = true; + s_semanticRenderState.patchSegments = value; + break; + case RS::NORMALIZENORMALS: + s_semanticRenderState.normalizeNormalsValid = true; + s_semanticRenderState.normalizeNormalsEnabled = (value != 0); + break; + case RS::TEXTUREFACTOR: + s_semanticRenderState.textureFactorValid = true; + s_semanticRenderState.textureFactor = value; + break; + case RS::POINTSPRITEENABLE: + s_semanticRenderState.pointSpriteValid = true; + s_semanticRenderState.pointSpriteEnabled = (value != 0); + break; + case RS::POINTSCALEENABLE: + s_semanticRenderState.pointScaleValid = true; + s_semanticRenderState.pointScaleEnabled = (value != 0); + break; + case RS::POINTSIZE: + s_semanticRenderState.pointSizeValid = true; + s_semanticRenderState.pointSize = value; + break; + case RS::POINTSIZEMIN: + s_semanticRenderState.pointSizeValid = true; + s_semanticRenderState.pointSizeMin = value; + break; + case RS::POINTSIZEMAX: + s_semanticRenderState.pointSizeValid = true; + s_semanticRenderState.pointSizeMax = value; + break; + case RS::POINTSCALE_A: + s_semanticRenderState.pointScaleValuesValid = true; + s_semanticRenderState.pointScaleA = value; + break; + case RS::POINTSCALE_B: + s_semanticRenderState.pointScaleValuesValid = true; + s_semanticRenderState.pointScaleB = value; + break; + case RS::POINTSCALE_C: + s_semanticRenderState.pointScaleValuesValid = true; + s_semanticRenderState.pointScaleC = value; + break; + case RS::STENCILENABLE: + s_semanticRenderState.stencilEnableValid = true; + s_semanticRenderState.stencilEnabled = (value != 0); + break; + case RS::STENCILFUNC: + s_semanticRenderState.stencilFunctionValid = true; + s_semanticRenderState.stencilFunction = value; + break; + case RS::STENCILREF: + s_semanticRenderState.stencilReferenceValid = true; + s_semanticRenderState.stencilReference = value; + break; + case RS::STENCILMASK: + s_semanticRenderState.stencilReadMaskValid = true; + s_semanticRenderState.stencilReadMask = value; + break; + case RS::STENCILWRITEMASK: + s_semanticRenderState.stencilWriteMaskValid = true; + s_semanticRenderState.stencilWriteMask = value; + break; + case RS::STENCILPASS: + s_semanticRenderState.stencilOpsValid = true; + s_semanticRenderState.stencilPassOp = value; + break; + case RS::STENCILFAIL: + s_semanticRenderState.stencilOpsValid = true; + s_semanticRenderState.stencilFailOp = value; + break; + case RS::STENCILZFAIL: + s_semanticRenderState.stencilOpsValid = true; + s_semanticRenderState.stencilZFailOp = value; + break; + default: + break; + } + } +} + +RenderStateStruct & FixedFunctionState::Render_State() +{ + return s_renderState; +} + +const RenderStateStruct & FixedFunctionState::Peek_Render_State() +{ + return s_renderState; +} + +unsigned & FixedFunctionState::Changed_Mask() +{ + return s_changedMask; +} + +void FixedFunctionState::Clear_Raw() +{ + memset(&s_renderState, 0, sizeof(s_renderState)); + memset(s_rawTextures, 0, sizeof(s_rawTextures)); + Clear_Cached_State(); + s_changedMask = 0; +} + +void FixedFunctionState::Capture_Render_State(RenderStateStruct & state) +{ + state = s_renderState; +} + +void FixedFunctionState::Restore_Render_State(const RenderStateStruct & state) +{ + int i; + + if (s_renderState.index_buffer) { + s_renderState.index_buffer->Release_Engine_Ref(); + } + + for (i=0;iRelease_Engine_Ref(); + } + } + + s_renderState=state; + s_changedMask=0xffffffff; + + if (s_renderState.index_buffer) { + s_renderState.index_buffer->Add_Engine_Ref(); + } + + for (i=0;iAdd_Engine_Ref(); + } + } +} + +void FixedFunctionState::Release_Render_State() +{ + int i; + + if (s_renderState.index_buffer) { + s_renderState.index_buffer->Release_Engine_Ref(); + } + + for (i=0;iRelease_Engine_Ref(); + } + } + + for (i=0;i(material)); + s_changedMask |= MATERIAL_CHANGED; +} + +bool FixedFunctionState::Set_Texture(unsigned stage, TextureBaseClass * texture) +{ + if (stage >= MAX_TEXTURE_STAGES) { + return false; + } + + if (texture == s_renderState.Textures[stage]) { + return false; + } + + REF_PTR_SET(s_renderState.Textures[stage], texture); + s_changedMask |= (TEXTURE0_CHANGED << stage); + return true; +} + +void FixedFunctionState::Set_Vertex_Buffer(const VertexBufferClass * vertex_buffer, unsigned stream) +{ + s_renderState.vba_offset = 0; + s_renderState.vba_count = 0; + if (s_renderState.vertex_buffers[stream]) { + s_renderState.vertex_buffers[stream]->Release_Engine_Ref(); + } + REF_PTR_SET(s_renderState.vertex_buffers[stream], const_cast(vertex_buffer)); + if (vertex_buffer) { + vertex_buffer->Add_Engine_Ref(); + s_renderState.vertex_buffer_types[stream] = vertex_buffer->Type(); + } else { + s_renderState.vertex_buffer_types[stream] = BUFFER_TYPE_INVALID; + } + s_changedMask |= VERTEX_BUFFER_CHANGED; +} + +void FixedFunctionState::Set_Vertex_Buffer(const DynamicVBAccessClass & vertex_buffer_access) +{ + for (int i = 1; i < MAX_VERTEX_STREAMS; ++i) { + Set_Vertex_Buffer(nullptr, i); + } + + if (s_renderState.vertex_buffers[0]) { + s_renderState.vertex_buffers[0]->Release_Engine_Ref(); + } + + s_renderState.vertex_buffer_types[0] = vertex_buffer_access.Get_Type(); + s_renderState.vba_offset = vertex_buffer_access.Get_Vertex_Buffer_Offset(); + s_renderState.vba_count = vertex_buffer_access.Get_Vertex_Count(); + REF_PTR_SET(s_renderState.vertex_buffers[0], vertex_buffer_access.Get_Vertex_Buffer()); + s_renderState.vertex_buffers[0]->Add_Engine_Ref(); + s_changedMask |= VERTEX_BUFFER_CHANGED; + s_changedMask |= INDEX_BUFFER_CHANGED; +} + +void FixedFunctionState::Set_Index_Buffer(const IndexBufferClass * index_buffer, unsigned short index_base_offset) +{ + s_renderState.iba_offset = 0; + if (s_renderState.index_buffer) { + s_renderState.index_buffer->Release_Engine_Ref(); + } + REF_PTR_SET(s_renderState.index_buffer, const_cast(index_buffer)); + s_renderState.index_base_offset = index_base_offset; + if (index_buffer) { + index_buffer->Add_Engine_Ref(); + s_renderState.index_buffer_type = index_buffer->Type(); + } else { + s_renderState.index_buffer_type = BUFFER_TYPE_INVALID; + } + s_changedMask |= INDEX_BUFFER_CHANGED; +} + +void FixedFunctionState::Set_Index_Buffer(const DynamicIBAccessClass & index_buffer_access, unsigned short index_base_offset) +{ + if (s_renderState.index_buffer) { + s_renderState.index_buffer->Release_Engine_Ref(); + } + + s_renderState.index_base_offset = index_base_offset; + s_renderState.index_buffer_type = index_buffer_access.Get_Type(); + s_renderState.iba_offset = index_buffer_access.Get_Index_Buffer_Offset(); + REF_PTR_SET(s_renderState.index_buffer, index_buffer_access.Get_Index_Buffer()); + s_renderState.index_buffer->Add_Engine_Ref(); + s_changedMask |= INDEX_BUFFER_CHANGED; +} + +void FixedFunctionState::Set_World_Identity() +{ + if (s_changedMask & WORLD_IDENTITY) { + return; + } + + LegacyMatrixIdentity(&s_renderState.world); + s_changedMask |= WORLD_CHANGED | WORLD_IDENTITY; +} + +void FixedFunctionState::Set_View_Identity() +{ + if (s_changedMask & VIEW_IDENTITY) { + return; + } + + LegacyMatrixIdentity(&s_renderState.view); + s_changedMask |= VIEW_CHANGED | VIEW_IDENTITY; +} + +bool FixedFunctionState::Is_World_Identity() +{ + return !!(s_changedMask & WORLD_IDENTITY); +} + +bool FixedFunctionState::Is_View_Identity() +{ + return !!(s_changedMask & VIEW_IDENTITY); +} + +LegacyRawTexture * FixedFunctionState::Raw_Texture(unsigned stage) +{ + if (stage >= MAX_TEXTURE_STAGES) { + return nullptr; + } + + return s_rawTextures[stage]; +} + +bool FixedFunctionState::Set_Raw_Texture(unsigned stage, LegacyRawTexture * texture) +{ + if (stage >= MAX_TEXTURE_STAGES) { + return false; + } + + if (s_rawTextures[stage] == texture) { + return false; + } + +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + s_rawTextures[stage] == nullptr && texture == nullptr, + "FixedFunctionState::Set_Raw_Texture: standalone bgfx cannot own fake-D3D raw textures"); + s_rawTextures[stage] = nullptr; +#else + if (s_rawTextures[stage]) { + s_rawTextures[stage]->Release(); + } + s_rawTextures[stage] = texture; + if (s_rawTextures[stage]) { + s_rawTextures[stage]->AddRef(); + } +#endif + return true; +} + +void FixedFunctionState::Release_Raw_Textures() +{ + for (unsigned stage = 0; stage < MAX_TEXTURE_STAGES; ++stage) { + if (s_rawTextures[stage]) { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "FixedFunctionState::Release_Raw_Textures: standalone bgfx cannot release fake-D3D raw textures"); +#else + s_rawTextures[stage]->Release(); +#endif + s_rawTextures[stage] = nullptr; + } + } +} + +void FixedFunctionState::Clear_Cached_State() +{ + memset(s_renderStates, 0, sizeof(s_renderStates)); + memset(s_textureStageStates, 0, sizeof(s_textureStageStates)); + memset(s_transforms, 0, sizeof(s_transforms)); + ClearSemanticRenderState(); +} + +void FixedFunctionState::Invalidate_Cached_State() +{ + unsigned state; + for (state = 0; state < RENDER_STATE_COUNT; ++state) { + s_renderStates[state] = INVALID_STATE_VALUE; + } + + unsigned stage; + for (stage = 0; stage < TEXTURE_STAGE_COUNT; ++stage) { + for (state = 0; state < TEXTURE_STAGE_STATE_COUNT; ++state) { + s_textureStageStates[stage][state] = INVALID_STATE_VALUE; + } + } + + memset(s_transforms, 0, sizeof(s_transforms)); + InvalidateSemanticRenderState(); +} + +unsigned FixedFunctionState::Cached_Render_State(unsigned state) +{ + if (state >= RENDER_STATE_COUNT) { + return INVALID_STATE_VALUE; + } + + return s_renderStates[state]; +} + +bool FixedFunctionState::Set_Cached_Render_State(unsigned state, unsigned value) +{ + if (state >= RENDER_STATE_COUNT) { + return false; + } + + if (s_renderStates[state] == value) { + return false; + } + + s_renderStates[state] = value; + MirrorSemanticRenderState(state, value); + return true; +} + +unsigned FixedFunctionState::Cached_Texture_Stage_State(unsigned stage, unsigned state) +{ + if (stage >= TEXTURE_STAGE_COUNT || state >= TEXTURE_STAGE_STATE_COUNT) { + return INVALID_STATE_VALUE; + } + + return s_textureStageStates[stage][state]; +} + +bool FixedFunctionState::Set_Cached_Texture_Stage_State(unsigned stage, unsigned state, unsigned value) +{ + if (stage >= TEXTURE_STAGE_COUNT || state >= TEXTURE_STAGE_STATE_COUNT) { + return false; + } + + if (s_textureStageStates[stage][state] == value) { + return false; + } + + s_textureStageStates[stage][state] = value; + return true; +} + +bool FixedFunctionState::Set_Texture_Stage_State(unsigned stage, unsigned state, unsigned value) +{ + return Set_Cached_Texture_Stage_State(stage, state, value); +} + +void FixedFunctionState::Cached_Transform(unsigned transform, LegacyTransformMatrix & matrix) +{ + if (transform >= TRANSFORM_COUNT) { + memset(&matrix, 0, sizeof(matrix)); + return; + } + + matrix = s_transforms[transform]; +} + +bool FixedFunctionState::Set_Cached_Transform(unsigned transform, const LegacyTransformMatrix & matrix) +{ + if (transform >= TRANSFORM_COUNT) { + return false; + } + + s_transforms[transform] = matrix; + return true; +} + +unsigned FixedFunctionState::Cull_Mode(unsigned default_value) +{ + return s_semanticRenderState.cullModeValid ? s_semanticRenderState.cullMode : default_value; +} + +bool FixedFunctionState::Set_Cull_Mode(unsigned value) +{ + return Set_Cached_Render_State(RS::CULLMODE, value); +} + +bool FixedFunctionState::Lighting_Enabled(bool default_value) +{ + return s_semanticRenderState.lightingValid ? s_semanticRenderState.lightingEnabled : default_value; +} + +bool FixedFunctionState::Set_Lighting_Enabled(bool enabled) +{ + return Set_Cached_Render_State(RS::LIGHTING, enabled ? 1U : 0U); +} + +unsigned FixedFunctionState::Fog_Color(unsigned default_value) +{ + return s_semanticRenderState.fogColorValid ? s_semanticRenderState.fogColor : default_value; +} + +bool FixedFunctionState::Set_Fog_Color(unsigned value) +{ + return Set_Cached_Render_State(RS::FOGCOLOR, value); +} + +unsigned FixedFunctionState::Color_Write_Mask(unsigned default_value) +{ + return s_semanticRenderState.colorWriteMaskValid ? s_semanticRenderState.colorWriteMask : default_value; +} + +bool FixedFunctionState::Set_Color_Write_Mask(unsigned value) +{ + return Set_Cached_Render_State(RS::COLORWRITEENABLE, value); +} + +unsigned FixedFunctionState::Ambient_Color(unsigned default_value) +{ + return s_semanticRenderState.ambientColorValid ? s_semanticRenderState.ambientColor : default_value; +} + +bool FixedFunctionState::Set_Ambient_Color(unsigned value) +{ + return Set_Cached_Render_State(RS::AMBIENT, value); +} + +unsigned FixedFunctionState::Ambient_Material_Source(unsigned default_value) +{ + return s_semanticRenderState.ambientMaterialSourceValid ? s_semanticRenderState.ambientMaterialSource : default_value; +} + +unsigned FixedFunctionState::Diffuse_Material_Source(unsigned default_value) +{ + return s_semanticRenderState.diffuseMaterialSourceValid ? s_semanticRenderState.diffuseMaterialSource : default_value; +} + +unsigned FixedFunctionState::Emissive_Material_Source(unsigned default_value) +{ + return s_semanticRenderState.emissiveMaterialSourceValid ? s_semanticRenderState.emissiveMaterialSource : default_value; +} + +bool FixedFunctionState::Set_Material_Color_Sources(unsigned ambient_source, unsigned diffuse_source, unsigned emissive_source) +{ + bool changed = false; + changed |= Set_Cached_Render_State(RS::AMBIENTMATERIALSOURCE, ambient_source); + changed |= Set_Cached_Render_State(RS::DIFFUSEMATERIALSOURCE, diffuse_source); + changed |= Set_Cached_Render_State(RS::EMISSIVEMATERIALSOURCE, emissive_source); + return changed; +} + +unsigned FixedFunctionState::Source_Blend_Factor(unsigned default_value) +{ + return s_semanticRenderState.sourceBlendFactorValid ? s_semanticRenderState.sourceBlendFactor : default_value; +} + +unsigned FixedFunctionState::Destination_Blend_Factor(unsigned default_value) +{ + return s_semanticRenderState.destinationBlendFactorValid ? s_semanticRenderState.destinationBlendFactor : default_value; +} + +bool FixedFunctionState::Set_Blend_Factors(unsigned source_factor, unsigned destination_factor) +{ + bool changed = false; + changed |= Set_Cached_Render_State(RS::SRCBLEND, source_factor); + changed |= Set_Cached_Render_State(RS::DESTBLEND, destination_factor); + return changed; +} + +unsigned FixedFunctionState::Blend_Op(unsigned default_value) +{ + return s_semanticRenderState.blendOpValid ? s_semanticRenderState.blendOp : default_value; +} + +bool FixedFunctionState::Set_Blend_Op(unsigned value) +{ + return Set_Cached_Render_State(RS::BLENDOP, value); +} + +bool FixedFunctionState::Alpha_Blend_Enabled(bool default_value) +{ + return s_semanticRenderState.alphaBlendValid ? s_semanticRenderState.alphaBlendEnabled : default_value; +} + +bool FixedFunctionState::Set_Alpha_Blend_Enabled(bool enabled) +{ + return Set_Cached_Render_State(RS::ALPHABLENDENABLE, enabled ? 1U : 0U); +} + +bool FixedFunctionState::Alpha_Test_Enabled(bool default_value) +{ + return s_semanticRenderState.alphaTestValid ? s_semanticRenderState.alphaTestEnabled : default_value; +} + +unsigned FixedFunctionState::Alpha_Test_Reference(unsigned default_value) +{ + return s_semanticRenderState.alphaTestReferenceValid ? s_semanticRenderState.alphaTestReference : default_value; +} + +unsigned FixedFunctionState::Alpha_Test_Function(unsigned default_value) +{ + return s_semanticRenderState.alphaTestFunctionValid ? s_semanticRenderState.alphaTestFunction : default_value; +} + +bool FixedFunctionState::Set_Alpha_Test_State(bool enabled, unsigned reference, unsigned function) +{ + bool changed = false; + changed |= Set_Cached_Render_State(RS::ALPHATESTENABLE, enabled ? 1U : 0U); + changed |= Set_Cached_Render_State(RS::ALPHAREF, reference); + changed |= Set_Cached_Render_State(RS::ALPHAFUNC, function); + return changed; +} + +int FixedFunctionState::Z_Bias(int default_value) +{ + return s_semanticRenderState.zBiasValid ? static_cast(s_semanticRenderState.zBias) : default_value; +} + +bool FixedFunctionState::Set_Z_Bias(int value) +{ + return Set_Cached_Render_State(RS::ZBIAS, static_cast(value)); +} + +unsigned FixedFunctionState::Fill_Mode(unsigned default_value) +{ + return s_semanticRenderState.fillModeValid ? s_semanticRenderState.fillMode : default_value; +} + +bool FixedFunctionState::Set_Fill_Mode(unsigned value) +{ + return Set_Cached_Render_State(RS::FILLMODE, value); +} + +unsigned FixedFunctionState::Shade_Mode(unsigned default_value) +{ + return s_semanticRenderState.shadeModeValid ? s_semanticRenderState.shadeMode : default_value; +} + +bool FixedFunctionState::Set_Shade_Mode(unsigned value) +{ + return Set_Cached_Render_State(RS::SHADEMODE, value); +} + +bool FixedFunctionState::Depth_Test_Enabled(bool default_value) +{ + return s_semanticRenderState.depthTestValid ? s_semanticRenderState.depthTestEnabled : default_value; +} + +bool FixedFunctionState::Set_Depth_Test_Enabled(bool enabled) +{ + return Set_Cached_Render_State(RS::ZENABLE, enabled ? 1U : 0U); +} + +bool FixedFunctionState::Depth_Write_Enabled(bool default_value) +{ + return s_semanticRenderState.depthWriteValid ? s_semanticRenderState.depthWriteEnabled : default_value; +} + +bool FixedFunctionState::Set_Depth_Write_Enabled(bool enabled) +{ + return Set_Cached_Render_State(RS::ZWRITEENABLE, enabled ? 1U : 0U); +} + +unsigned FixedFunctionState::Depth_Function(unsigned default_value) +{ + return s_semanticRenderState.depthFunctionValid ? s_semanticRenderState.depthFunction : default_value; +} + +bool FixedFunctionState::Set_Depth_Function(unsigned value) +{ + return Set_Cached_Render_State(RS::ZFUNC, value); +} + +bool FixedFunctionState::Fog_Enabled(bool default_value) +{ + return s_semanticRenderState.fogEnableValid ? s_semanticRenderState.fogEnabled : default_value; +} + +bool FixedFunctionState::Set_Fog_Enabled(bool enabled) +{ + return Set_Cached_Render_State(RS::FOGENABLE, enabled ? 1U : 0U); +} + +bool FixedFunctionState::Specular_Enabled(bool default_value) +{ + return s_semanticRenderState.specularEnableValid ? s_semanticRenderState.specularEnabled : default_value; +} + +bool FixedFunctionState::Set_Specular_Enabled(bool enabled) +{ + return Set_Cached_Render_State(RS::SPECULARENABLE, enabled ? 1U : 0U); +} + +unsigned FixedFunctionState::Patch_Segments_Bits(unsigned default_value) +{ + return s_semanticRenderState.patchSegmentsValid ? s_semanticRenderState.patchSegments : default_value; +} + +bool FixedFunctionState::Set_Patch_Segments_Bits(unsigned value) +{ + return Set_Cached_Render_State(RS::PATCHSEGMENTS, value); +} + +bool FixedFunctionState::Normalize_Normals_Enabled(bool default_value) +{ + return s_semanticRenderState.normalizeNormalsValid ? s_semanticRenderState.normalizeNormalsEnabled : default_value; +} + +bool FixedFunctionState::Set_Normalize_Normals_Enabled(bool enabled) +{ + return Set_Cached_Render_State(RS::NORMALIZENORMALS, enabled ? 1U : 0U); +} + +unsigned FixedFunctionState::Texture_Factor(unsigned default_value) +{ + return s_semanticRenderState.textureFactorValid ? s_semanticRenderState.textureFactor : default_value; +} + +bool FixedFunctionState::Set_Texture_Factor(unsigned value) +{ + return Set_Cached_Render_State(RS::TEXTUREFACTOR, value); +} + +bool FixedFunctionState::Point_Sprite_Enabled(bool default_value) +{ + return s_semanticRenderState.pointSpriteValid ? s_semanticRenderState.pointSpriteEnabled : default_value; +} + +bool FixedFunctionState::Set_Point_Sprite_Enabled(bool enabled) +{ + return Set_Cached_Render_State(RS::POINTSPRITEENABLE, enabled ? 1U : 0U); +} + +bool FixedFunctionState::Point_Scale_Enabled(bool default_value) +{ + return s_semanticRenderState.pointScaleValid ? s_semanticRenderState.pointScaleEnabled : default_value; +} + +bool FixedFunctionState::Set_Point_Scale_Enabled(bool enabled) +{ + return Set_Cached_Render_State(RS::POINTSCALEENABLE, enabled ? 1U : 0U); +} + +bool FixedFunctionState::Set_Point_Size_Bits(unsigned size, unsigned min_size, unsigned max_size) +{ + bool changed = false; + changed |= Set_Cached_Render_State(RS::POINTSIZE, size); + changed |= Set_Cached_Render_State(RS::POINTSIZEMIN, min_size); + changed |= Set_Cached_Render_State(RS::POINTSIZEMAX, max_size); + return changed; +} + +bool FixedFunctionState::Set_Point_Scale_Bits(unsigned a, unsigned b, unsigned c) +{ + bool changed = false; + changed |= Set_Cached_Render_State(RS::POINTSCALE_A, a); + changed |= Set_Cached_Render_State(RS::POINTSCALE_B, b); + changed |= Set_Cached_Render_State(RS::POINTSCALE_C, c); + return changed; +} + +bool FixedFunctionState::Stencil_Enabled(bool default_value) +{ + return s_semanticRenderState.stencilEnableValid ? s_semanticRenderState.stencilEnabled : default_value; +} + +bool FixedFunctionState::Set_Stencil_Enabled(bool enabled) +{ + return Set_Cached_Render_State(RS::STENCILENABLE, enabled ? 1U : 0U); +} + +unsigned FixedFunctionState::Stencil_Function(unsigned default_value) +{ + return s_semanticRenderState.stencilFunctionValid ? s_semanticRenderState.stencilFunction : default_value; +} + +bool FixedFunctionState::Set_Stencil_Function(unsigned value) +{ + return Set_Cached_Render_State(RS::STENCILFUNC, value); +} + +unsigned FixedFunctionState::Stencil_Reference(unsigned default_value) +{ + return s_semanticRenderState.stencilReferenceValid ? s_semanticRenderState.stencilReference : default_value; +} + +bool FixedFunctionState::Set_Stencil_Reference(unsigned value) +{ + return Set_Cached_Render_State(RS::STENCILREF, value); +} + +unsigned FixedFunctionState::Stencil_Read_Mask(unsigned default_value) +{ + return s_semanticRenderState.stencilReadMaskValid ? s_semanticRenderState.stencilReadMask : default_value; +} + +bool FixedFunctionState::Set_Stencil_Read_Mask(unsigned value) +{ + return Set_Cached_Render_State(RS::STENCILMASK, value); +} + +unsigned FixedFunctionState::Stencil_Write_Mask(unsigned default_value) +{ + return s_semanticRenderState.stencilWriteMaskValid ? s_semanticRenderState.stencilWriteMask : default_value; +} + +bool FixedFunctionState::Set_Stencil_Write_Mask(unsigned value) +{ + return Set_Cached_Render_State(RS::STENCILWRITEMASK, value); +} + +bool FixedFunctionState::Set_Stencil_Pass_Op(unsigned value) +{ + return Set_Cached_Render_State(RS::STENCILPASS, value); +} + +bool FixedFunctionState::Set_Stencil_Fail_Op(unsigned value) +{ + return Set_Cached_Render_State(RS::STENCILFAIL, value); +} + +bool FixedFunctionState::Set_Stencil_ZFail_Op(unsigned value) +{ + return Set_Cached_Render_State(RS::STENCILZFAIL, value); +} + +void FixedFunctionState::Transform_Matrix(unsigned transform, LegacyTransformMatrix & matrix) +{ + Cached_Transform(transform, matrix); +} + +bool FixedFunctionState::Set_Transform_Matrix(unsigned transform, const LegacyTransformMatrix & matrix) +{ + return Set_Cached_Transform(transform, matrix); +} + +RenderStateStruct::RenderStateStruct() + : + material(0), + index_buffer(0), + sorted_draw_flags(0), + sorted_array_page(-1), + sorted_array_layer(-1), + sorted_array_scale_u(1.0f), + sorted_array_scale_v(1.0f), + resolved_state_lo(0), + resolved_state_hi(0), + resolved_state_valid(false) +{ + unsigned i; + for (i=0;i. +*/ + +// 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. + +#pragma once + +#include +#include + +#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; +class RenderDeviceCleanupHook; +class RenderDeviceDescClass; +struct RenderStateStruct; + +// ----------------------------------------------------------------------------- +// POD types owned by the interface +// ----------------------------------------------------------------------------- + +// A single light captured for a sorted batch. Direction follows the legacy +// convention: from the light toward the surface. +struct RenderBackendLight +{ + unsigned int type; + float position[3]; + float direction[3]; + float diffuse[3]; + float ambient[3]; + float specular[3]; + float range; + float falloff; + float attenuation[3]; + float theta; + float phi; +}; + +struct RenderBackendImage +{ + unsigned Width = 0; + unsigned Height = 0; + WW3DFormat Format = WW3D_FORMAT_UNKNOWN; + unsigned Pitch = 0; + std::vector Bytes; + + bool Is_Valid() const + { + return Width != 0 && Height != 0 && Pitch != 0 && !Bytes.empty(); + } +}; + +struct RenderBackendSurfaceDescription +{ + unsigned Width = 0; + unsigned Height = 0; + WW3DFormat Format = WW3D_FORMAT_UNKNOWN; + + bool Is_Valid() const + { + return Width != 0 && Height != 0 && Format != WW3D_FORMAT_UNKNOWN; + } +}; + +static const unsigned RB_MAX_TEXTURE_STAGES = 8; +static const unsigned RB_MAX_LIGHTS = 4; + +enum RenderBackendLockFlags +{ + RB_LOCK_NONE = 0, + RB_LOCK_NOSYSLOCK = 0x00000800, + RB_LOCK_NOOVERWRITE = 0x00001000, + RB_LOCK_DISCARD = 0x00002000, +}; + +enum RenderBackendTextureFilterCapability +{ + RB_TEXTURE_FILTER_MIN_LINEAR, + RB_TEXTURE_FILTER_MAG_LINEAR, + RB_TEXTURE_FILTER_MIP_LINEAR, + RB_TEXTURE_FILTER_MIN_ANISOTROPIC, + RB_TEXTURE_FILTER_MAG_ANISOTROPIC, +}; + +enum RenderBackendTextureAddressMode +{ + RB_TEXTURE_ADDRESS_WRAP, + RB_TEXTURE_ADDRESS_CLAMP, + RB_TEXTURE_ADDRESS_BORDER, +}; + +enum RenderBackendTextureSampleFilter +{ + RB_TEXTURE_SAMPLE_NONE, + RB_TEXTURE_SAMPLE_POINT, + RB_TEXTURE_SAMPLE_LINEAR, + RB_TEXTURE_SAMPLE_ANISOTROPIC, +}; + +enum RenderBackendTextureOpCapability +{ + RB_TEXTURE_OP_SELECTARG1, + RB_TEXTURE_OP_MODULATE, + RB_TEXTURE_OP_MODULATE2X, + RB_TEXTURE_OP_ADD, + RB_TEXTURE_OP_BUMPENVMAP, + RB_TEXTURE_OP_BUMPENVMAPLUMINANCE, + RB_TEXTURE_OP_ADDSMOOTH, + RB_TEXTURE_OP_SUBTRACT, + RB_TEXTURE_OP_BLENDTEXTUREALPHA, + RB_TEXTURE_OP_BLENDCURRENTALPHA, + RB_TEXTURE_OP_ADDSIGNED, + RB_TEXTURE_OP_ADDSIGNED2X, + RB_TEXTURE_OP_MODULATEALPHA_ADDCOLOR, +}; + +enum RenderBackendTextureOperation +{ + RB_TEXOP_DISABLE = 1, + RB_TEXOP_SELECTARG1 = 2, + RB_TEXOP_SELECTARG2 = 3, + RB_TEXOP_MODULATE = 4, + RB_TEXOP_MODULATE2X = 5, + RB_TEXOP_ADD = 7, + RB_TEXOP_ADDSIGNED = 8, + RB_TEXOP_ADDSIGNED2X = 9, + RB_TEXOP_SUBTRACT = 10, + RB_TEXOP_ADDSMOOTH = 11, + RB_TEXOP_BLENDTEXTUREALPHA = 13, + RB_TEXOP_BLENDCURRENTALPHA = 16, + RB_TEXOP_MODULATEALPHA_ADDCOLOR = 18, + RB_TEXOP_BUMPENVMAP = 22, + RB_TEXOP_BUMPENVMAPLUMINANCE = 23, + RB_TEXOP_DOTPRODUCT3 = 24, + RB_TEXOP_MULTIPLYADD = 25, +}; + +enum RenderBackendTextureArgument +{ + RB_TEXARG_DIFFUSE = 0x00000000, + RB_TEXARG_CURRENT = 0x00000001, + RB_TEXARG_TEXTURE = 0x00000002, + RB_TEXARG_TFACTOR = 0x00000003, + RB_TEXARG_COMPLEMENT = 0x00000010, + RB_TEXARG_ALPHAREPLICATE = 0x00000020, +}; + +inline RenderBackendTextureArgument operator|(RenderBackendTextureArgument lhs, RenderBackendTextureArgument rhs) +{ + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +struct RenderBackendLightState +{ + RenderBackendLight lights[RB_MAX_LIGHTS]; + bool enabled[RB_MAX_LIGHTS]; +}; + +struct RenderBackendMaterialState +{ + float diffuse[4]; + float ambient[4]; + float specular[4]; + float emissive[4]; + float power; +}; + +struct RenderBackendSortedMaterialSnapshot +{ + float diffuse[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + float ambient[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + float specular[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + float emissive[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float vertex_color_flags[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float lighting_enabled[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + bool valid = false; +}; + +struct RenderBackendDeviceIdentity +{ + unsigned int vendor_id; + unsigned int device_id; + std::uint64_t driver_version; + int max_simultaneous_textures; + int pixel_shader_major; + int pixel_shader_minor; +}; + +struct RenderBackendTextureLimits +{ + unsigned int max_width; + unsigned int max_height; + unsigned int max_volume_extent; + unsigned int max_aspect_ratio; +}; + +struct RenderBackendSortedBatchState +{ + const ShaderClass * shader; + const VertexMaterialClass * material; + TextureBaseClass * textures[RB_MAX_TEXTURE_STAGES]; + const Matrix4x4 * world; + const Matrix4x4 * view; + RenderBackendLightState lights; + RenderBackendSortedMaterialSnapshot material_snapshot; + unsigned int draw_flags; + // TheSuperHackers @performance bobtista 10/07/2026 Backend-opaque pipeline-state + // word resolved at capture time (see RenderStateStruct::resolved_state_*). + // Consumed by the packet submit when valid and the resolved-pipeline flag is + // on; compared against the live derivation under trace. + std::uint64_t resolved_state; + bool resolved_state_valid; +}; + +enum RenderBackendSortedDrawFlags +{ + RB_SORTED_DRAW_NONE = 0, + RB_SORTED_DRAW_POINT_GROUP = 1 << 0, + RB_SORTED_DRAW_STREAK = 1 << 1, + RB_SORTED_DRAW_MESH = 1 << 2 +}; + +enum TransformKind +{ + // Values chosen so they can be mapped directly to legacy transform slots 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, + RB_TRANSFORM_PROJECTION = 3, + RB_TRANSFORM_WORLD = 256 +}; + +enum RenderBackendProjectedDecalMode +{ + RB_PROJECTED_DECAL_NONE = 0, + RB_PROJECTED_DECAL_BLOB_SHADOW = 1, + RB_PROJECTED_DECAL_ADDITIVE = 2, + RB_PROJECTED_DECAL_ALPHA = 3, + RB_PROJECTED_DECAL_MULTIPLY = 4 +}; + +enum RenderBackendShaderKind +{ + RB_SHADER_PIXEL = 0, + RB_SHADER_VERTEX = 1 +}; + +enum RenderBackendLegacyVertexDeclaration +{ + RB_LEGACY_VERTEX_DECL_XYZNDUV1, +}; + +enum RenderBackendLegacyPixelShaderMode +{ + RB_LEGACY_PIXEL_SHADER_NONE = 0, + RB_LEGACY_PIXEL_SHADER_RIVER_WATER = 1, + RB_LEGACY_PIXEL_SHADER_REFLECTIVE_WATER = 2, + RB_LEGACY_PIXEL_SHADER_TRAPEZOID_WATER = 3 +}; + +enum RenderBackendTexcoordSource +{ + // Values match the bgfx uber-shader uniform encoding: + // 0=mesh UV, 1=camera normal, 2=camera reflection, 3=camera position. + RB_TEXCOORD_MESH_UV = 0, + RB_TEXCOORD_CAMERA_SPACE_NORMAL = 1, + RB_TEXCOORD_CAMERA_SPACE_REFLECTION = 2, + RB_TEXCOORD_CAMERA_SPACE_POSITION = 3 +}; + +enum RenderBackendMaterialColorSource +{ + // Values match legacy material color-source ordinals so DX8Backend can forward them directly. + RB_MATERIAL_COLOR_SOURCE_MATERIAL = 0, + RB_MATERIAL_COLOR_SOURCE_COLOR1 = 1, + RB_MATERIAL_COLOR_SOURCE_COLOR2 = 2 +}; + +enum RenderBackendViewCaptureKind +{ + RB_VIEW_CAPTURE_TACTICAL = 0 +}; + +struct RenderBackendScreenVertex +{ + float x; + float y; + float z; + float w; + unsigned int diffuse; + float u0; + float v0; + float u1; + float v1; +}; + +struct RenderBackendViewport +{ + unsigned int x; + unsigned int y; + unsigned int width; + unsigned int height; + float min_z; + float max_z; +}; + +// TheSuperHackers @refactor bobtista 21/04/2026 Asset ingress types. +// These let W3D asset loaders produce CPU-side pixel / vertex / index data +// and hand it to whichever backend is active, without the loaders caring +// about legacy renderer specifics. Each backend creates its own native GPU resource +// from the bytes and returns an opaque RenderResource handle. + +// Opaque handle into any backend's resource table. Backends encode their +// native handle (D3D pointer cast to uint64, bgfx handle index, etc.) into +// the id field. Other code treats it as opaque. id == 0 means invalid. +struct RenderResource +{ + std::uint64_t id; +}; + +inline bool operator==(const RenderResource & a, const RenderResource & b) { return a.id == b.id; } +inline bool operator!=(const RenderResource & a, const RenderResource & b) { return a.id != b.id; } + +// Sentinel for invalid handles, used by the default transitional hooks. +static const RenderResource kInvalidRenderResource = { 0 }; + +// A single mip level's pixel data for Create_Texture. For compressed +// formats (DXT1/3/5), pitch is set to 0 — backends compute the compressed +// row size from block dimensions and the format enum. +struct MipSlice +{ + const void * data; + unsigned int pitch; // row pitch in bytes; 0 for compressed + unsigned int size_bytes; // total bytes at this mip level + unsigned short width; + unsigned short height; +}; + +struct TextureDesc +{ + unsigned short width; + unsigned short height; + WW3DFormat format; + unsigned char mip_count; + bool is_render_target; + const MipSlice * mips; // array of length mip_count; nullptr when is_render_target +}; + +struct VertexLayoutDesc +{ + unsigned int fvf; // legacy FVF bitmap; each backend translates to its native format + unsigned int stride; // bytes per vertex +}; + +struct BufferDesc +{ + unsigned int size_bytes; + VertexLayoutDesc layout; // ignored for index buffers + bool dynamic; +}; + +// TheSuperHackers @refactor bobtista 10/04/2026 Interface extension +// to unblock W3DStatusCircle fade effects and FlatHeightMap shroud trickery +// without exposing raw legacy render-state types in the interface. +// +// Values chosen to match legacy blend ordinals 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, + RB_BLEND_OP_SUBTRACT = 2, + RB_BLEND_OP_REV_SUBTRACT = 3, + RB_BLEND_OP_MIN = 4, + RB_BLEND_OP_MAX = 5 +}; + +enum BlendFactor +{ + RB_BLEND_ZERO = 1, + RB_BLEND_ONE = 2, + RB_BLEND_SRC_COLOR = 3, + RB_BLEND_INV_SRC_COLOR = 4, + RB_BLEND_SRC_ALPHA = 5, + RB_BLEND_INV_SRC_ALPHA = 6, + RB_BLEND_DEST_ALPHA = 7, + RB_BLEND_INV_DEST_ALPHA = 8, + RB_BLEND_DEST_COLOR = 9, + RB_BLEND_INV_DEST_COLOR = 10, + RB_BLEND_SRC_ALPHA_SAT = 11 +}; + +// TheSuperHackers @refactor bobtista 10/04/2026 Stencil state +// extension. Generic CompareFunc enum is also reusable for depth-test +// comparison. +enum CompareFunc +{ + // Values match legacy compare ordinals 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 +}; + +// TheSuperHackers @refactor bobtista 28/04/2026 Channel masks for +// Set_Color_Write_Mask. Values match legacy color-write bits so DX8Backend +// can cast directly. Game code that wants to disable color writes +// entirely should pass 0; for ALL channels use RB_COLOR_RGBA. +enum ColorWriteMask +{ + RB_COLOR_RED = 1, + RB_COLOR_GREEN = 2, + RB_COLOR_BLUE = 4, + RB_COLOR_ALPHA = 8, + RB_COLOR_RGB = RB_COLOR_RED | RB_COLOR_GREEN | RB_COLOR_BLUE, + RB_COLOR_RGBA = RB_COLOR_RGB | RB_COLOR_ALPHA +}; + +// TheSuperHackers @refactor bobtista 14/04/2026 Fill-mode +// values match legacy ordinals so DX8Backend can cast directly. +enum FillMode +{ + RB_FILL_POINT = 1, + RB_FILL_WIREFRAME = 2, + RB_FILL_SOLID = 3 +}; + +// Values match legacy shade ordinals so DX8Backend can cast directly. +enum ShadeMode +{ + RB_SHADE_FLAT = 1, + RB_SHADE_GOURAUD = 2, + RB_SHADE_PHONG = 3 +}; + +// Values match legacy cull ordinals so DX8Backend can cast directly. +enum CullMode +{ + RB_CULL_NONE = 1, + RB_CULL_CW = 2, + RB_CULL_CCW = 3 +}; + +enum RenderBackendDeviceStatus +{ + RB_DEVICE_OK = 0, + RB_DEVICE_LOST, + RB_DEVICE_NOT_RESET +}; + +enum RenderBackendMSAAMode +{ + RB_MSAA_NONE = 0, + RB_MSAA_2X, + RB_MSAA_4X, + RB_MSAA_8X +}; + +enum StencilOp +{ + // Values match legacy stencil operation ordinals 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. Exposes the backend-neutral +// subset of DX8Wrapper's API; method names match DX8Wrapper so callers migrate mechanically. +// Keep this header C++98/VC6-compatible: no STL in signatures, no C++11-only keywords. + +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 + // Legacy-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*/) {} + + // Copy a backend render target into a regular cached texture. Legacy + // projector code renders into a temporary POOL_DEFAULT target, copies it + // into another POOL_DEFAULT TextureClass, then samples that destination. + // DX8 owns both GPU resources, but bgfx needs an explicit cache bridge. + virtual void Copy_Render_Target_To_Texture(TextureClass * /*dst_texture*/, + TextureClass * /*src_render_target*/) {} + + // TheSuperHackers @feature bobtista 20/04/2026 Release a cached + // texture. Called from TextureBaseClass::~TextureBaseClass before the + // legacy 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*/) {} + + // TheSuperHackers @feature bobtista 08/06/2026 Letterbox present. When enabled, the backend + // renders the game into a centered sub-rect of the window at the requested aspect and fills the + // remainder with black bars; this gives a consistent viewable area regardless of window/display + // aspect (used to keep multiplayer fair). When disabled, present is direct (content == window). + // The default backend ignores it and always presents directly. The accessors report the current + // content rect so the engine can match its display resolution and offset mouse input. + virtual void Set_Present_Letterbox(bool /*enabled*/, float /*aspectW*/, float /*aspectH*/) {} + virtual bool Is_Present_Letterbox_Active() const { return false; } + virtual int Get_Present_Content_Width() const { return 0; } + virtual int Get_Present_Content_Height() const { return 0; } + virtual int Get_Present_Offset_X() const { return 0; } + virtual int Get_Present_Offset_Y() const { return 0; } + + // ------------------------------------------------------------------------- + // 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) {} + virtual void Shutdown() {} + + // ------------------------------------------------------------------------- + // Render-system bring-up / tear-down (device enumeration layer) + // + // Distinct from Initialize/Shutdown above, which create and destroy the + // per-window rendering context. These mirror the legacy DX8Wrapper + // Init/Shutdown entry points that enumerate adapters and display modes. + // ------------------------------------------------------------------------- + + virtual bool Init_Render_System(void * hwnd, bool lite) { return false; } + virtual void Shutdown_Render_System() {} + + // ------------------------------------------------------------------------- + // Device selection, windowing and display-mode control + // ------------------------------------------------------------------------- + + virtual bool Set_Render_Device(const char * dev_name, int width, int height, int bits, int windowed, bool resize_window) { return false; } + virtual bool Set_Render_Device(int dev, int width, int height, int bits, int windowed, bool resize_window, bool reset_device, bool restore_assets) { return false; } + virtual bool Set_Any_Render_Device() { return false; } + virtual bool Set_Next_Render_Device() { return false; } + virtual bool Toggle_Windowed() { return false; } + virtual bool Is_Windowed() const { return false; } + virtual int Get_Render_Device() const { return -1; } + virtual const RenderDeviceDescClass & Get_Render_Device_Desc(int deviceidx) = 0; + virtual int Get_Render_Device_Count() const { return 0; } + virtual const char * Get_Render_Device_Name(int device_index) { return ""; } + virtual bool Set_Device_Resolution(int width, int height, int bits, int windowed, bool resize_window) { return false; } + virtual void Get_Render_Target_Resolution(int & set_w, int & set_h, int & set_bits, bool & set_windowed) {} + virtual void Get_Device_Resolution(int & set_w, int & set_h, int & set_bits, bool & set_windowed) {} + virtual int Get_Device_Resolution_Width() const { return 0; } + virtual int Get_Device_Resolution_Height() const { return 0; } + virtual bool Registry_Save_Render_Device(const char * sub_key) { return false; } + virtual bool Registry_Save_Render_Device(const char * sub_key, int device, int width, int height, int depth, bool windowed, int texture_depth) { return false; } + virtual bool Registry_Load_Render_Device(const char * sub_key, bool resize_window) { return false; } + virtual bool Registry_Load_Render_Device(const char * sub_key, char * device, int device_len, int & width, int & height, int & depth, int & windowed, int & texture_depth) { return false; } + + // Present sync interval. 0 = no vsync, non-zero = sync to display refresh. + virtual void Set_Swap_Interval(int swap) {} + virtual int Get_Swap_Interval() const { return 0; } + + // ------------------------------------------------------------------------- + // Device state queries + // ------------------------------------------------------------------------- + + virtual bool Is_Device_Lost() const { return false; } + virtual RenderBackendDeviceStatus Get_Device_Status() const { return RB_DEVICE_OK; } + virtual void Reset_Device() {} + virtual void Set_Device_Cleanup_Hook(RenderDeviceCleanupHook * hook) {} + virtual bool Has_Stencil() const { return false; } + virtual WW3DFormat Get_Back_Buffer_Format() const { return WW3D_FORMAT_UNKNOWN; } + virtual bool Get_Back_Buffer_Description(unsigned int num, RenderBackendSurfaceDescription & desc) const { desc = RenderBackendSurfaceDescription(); return false; } + virtual bool Capture_Back_Buffer_Image(unsigned int num, RenderBackendImage & image) { return false; } + + // TheSuperHackers @bugfix bobtista 03/06/2026 GPU-direct backbuffer copy + // to a texture's level-0 surface. Used by W3DSmudge to keep a persistent + // background snapshot without allocating per-frame system-memory surfaces + // (the previous Capture_Back_Buffer_Image route on dx8 was leaking ~4 MB + // per call and exhausting the 2 GB virtual address space). DX8 backend + // does a CopyRects from back buffer to the texture's POOL_DEFAULT surface; + // bgfx can blit from its scene RT. Default returns false so callers + // know to fall back. + virtual bool Copy_Back_Buffer_To_Texture(unsigned int /*num*/, TextureClass * /*dst_texture*/) { return false; } + // TheSuperHackers @feature bobtista 09/07/2026 A backend with a native screenshot path encodes and + // writes the file itself (the path extension selects the format); callers should prefer it over the + // CPU back-buffer readback. Returns false on backends without one (DX8 reference build). + virtual bool Supports_Native_Screen_Shot() const { return false; } + virtual bool Request_Native_Screen_Shot(const char * /*path*/) { return false; } + virtual void Set_Texture_Bitdepth(int bitdepth) {} + virtual int Get_Texture_Bitdepth() const { return 16; } + virtual bool Supports_Texture_Format(WW3DFormat format) const { return false; } + virtual bool Supports_Compressed_Textures() const { return false; } + virtual bool Supports_Bump_Envmap() const { return false; } + virtual bool Supports_Bump_Envmap_Luminance() const { return false; } + virtual bool Supports_Texture_Filter(RenderBackendTextureFilterCapability /*capability*/) const { return false; } + virtual bool Supports_Texture_Op(RenderBackendTextureOpCapability /*capability*/) const { return false; } + virtual bool Supports_Fog() const { return false; } + virtual bool Is_Legacy_Voodoo3() const { return false; } + virtual bool Supports_NPatches() const { return false; } + virtual bool Supports_Hardware_Transform_And_Lighting() const { return false; } + virtual bool Supports_Point_Sprites() const { return false; } + virtual RenderBackendTextureLimits Get_Texture_Limits() const + { + return { 2048, 2048, 2048, 8 }; + } + virtual int Get_Max_Texture_Stages() const { return RB_MAX_TEXTURE_STAGES; } + virtual bool Supports_Z_Bias() const { return false; } + virtual void Set_MSAA_Mode(RenderBackendMSAAMode mode) {} + virtual RenderBackendMSAAMode Get_MSAA_Mode() const { return RB_MSAA_NONE; } + virtual bool Supports_Dot3() const { return false; } + virtual bool Get_Device_Identity(RenderBackendDeviceIdentity & identity) const { return false; } + 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 Begin_Device_Statistics() {} + virtual void End_Device_Statistics() {} + // 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 = 0.0f, float z = 1.0f, unsigned int stencil = 0) {} + virtual void Set_Viewport(const RenderBackendViewport & viewport) {} + + // ------------------------------------------------------------------------- + // View capture / post-effect primitives + // ------------------------------------------------------------------------- + // + // High-level replacement for W3DShaderManager's old raw D3D render-target + // ownership. Callers express that they want to capture and later sample + // the tactical view; each backend decides whether that is a D3D texture, + // a bgfx framebuffer, or unsupported. + virtual bool Initialize_View_Capture(RenderBackendViewCaptureKind /*kind*/) { return false; } + virtual void Release_View_Capture(RenderBackendViewCaptureKind /*kind*/) {} + virtual bool Supports_View_Capture(RenderBackendViewCaptureKind /*kind*/) const { return false; } + virtual bool Begin_View_Capture(RenderBackendViewCaptureKind /*kind*/) { return false; } + virtual bool End_View_Capture(RenderBackendViewCaptureKind /*kind*/) { return false; } + virtual bool Is_View_Capture_Active(RenderBackendViewCaptureKind /*kind*/) const { return false; } + virtual bool Has_View_Capture(RenderBackendViewCaptureKind /*kind*/) const { return false; } + virtual bool Bind_View_Capture_Texture(RenderBackendViewCaptureKind /*kind*/, + unsigned int /*stage*/) { return false; } + virtual bool Draw_View_Capture_Quad(RenderBackendViewCaptureKind /*kind*/, + const RenderBackendScreenVertex * /*vertices*/, + unsigned int /*vertex_count*/, + bool /*use_second_uv*/) { return false; } + virtual bool Draw_Screen_Quad(const RenderBackendScreenVertex * /*vertices*/, + unsigned int /*vertex_count*/, + bool /*use_second_uv*/) { return false; } + + virtual bool Capture_Back_Buffer_RGBA(unsigned int /*display_width*/, + unsigned int /*display_height*/, + unsigned int /*image_size*/, + unsigned char * /*output_pixels*/, + unsigned int /*output_capacity*/, + unsigned int * /*output_width*/, + unsigned int * /*output_height*/) { return false; } + + // ------------------------------------------------------------------------- + // Vertex / index buffers + // ------------------------------------------------------------------------- + + virtual void Set_Vertex_Buffer(const VertexBufferClass * vb, unsigned int stream = 0) {} + 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) {} + + // TheSuperHackers @refactor bobtista 11/04/2026 Write-side + // upload 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 upload 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 Upload_Vertex_Buffer_Data(const VertexBufferClass * /*vb*/, + const void * /*data*/, + unsigned int /*size_bytes*/) {} + virtual void Upload_Index_Buffer_Data(const IndexBufferClass * /*ib*/, + const void * /*data*/, + unsigned int /*size_bytes*/) {} + + // TheSuperHackers @refactor bobtista 11/04/2026 Dynamic + // upload 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*/) {} + + virtual bool Supports_Instancing() const { return false; } + virtual bool Begin_Instanced_Batch(unsigned max_instances) { return false; } + virtual void Add_Instance(const float * world_matrix_4x4) {} + virtual void Submit_Instanced_Batch(unsigned index_offset, unsigned triangle_count, + unsigned min_vertex_index, unsigned vertex_count) {} + + virtual void * Begin_Dynamic_Vertex_Write(const DynamicVBAccessClass * /*vba*/, + unsigned int /*size_bytes*/) { return nullptr; } + virtual void End_Dynamic_Vertex_Write(const DynamicVBAccessClass * /*vba*/, + const void * /*data*/, + unsigned int /*size_bytes*/) {} + virtual void * Begin_Dynamic_Index_Write(const DynamicIBAccessClass * /*iba*/, + unsigned int /*size_bytes*/) { return nullptr; } + virtual void End_Dynamic_Index_Write(const DynamicIBAccessClass * /*iba*/, + const void * /*data*/, + unsigned int /*size_bytes*/) {} + + // TheSuperHackers @refactor bobtista 11/04/2026 Sub-range + // upload. 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 Upload_Vertex_Buffer_Sub_Range(const VertexBufferClass * /*vb*/, + const void * /*data*/, + unsigned int /*start_vertex*/, + unsigned int /*size_bytes*/) {} + virtual void Upload_Index_Buffer_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 applies one + // RenderBackendSortedBatchState per batch. 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 Apply_Sorted_Batch_State(const RenderBackendSortedBatchState & /*state*/) {} + virtual void Set_Point_Group_Render_Active(bool /*active*/) {} + virtual void Set_Streak_Render_Active(bool /*active*/) {} + // TheSuperHackers @feature bobtista 07/07/2026 Marks sorted-pool inserts that originate + // from a mesh polygon renderer. Mesh geometry is authored in model space and needs its + // live per-mesh world captured for the sorted replay; point groups/streaks author their + // vertices in world space and must not carry one. Empty default = no-op on DX8Backend. + virtual void Set_Mesh_Render_Active(bool /*active*/) {} + virtual void Capture_Legacy_Render_State_For_Sorted_Draw(RenderStateStruct & /*state*/) {} + virtual void Restore_Legacy_Render_State_For_Sorted_Draw(const RenderStateStruct & /*state*/) {} + virtual void Release_Legacy_Render_State_For_Sorted_Draw() {} + // TheSuperHackers @performance bobtista 08/07/2026 Sorted texture-array merge support. + // The backend resolves a node's texture2DArray slot during + // Capture_Legacy_Render_State_For_Sorted_Draw (when the live translated draw state is + // authoritative) and carries it on the RenderStateStruct sorted_array_* fields. + // Set_Sorted_Texture_Array_Page(-1 to clear) marks the next sorted submit as a merged + // run that spans several stage-0 textures via a per-vertex layer index. The empty + // default keeps DX8Backend on the classic per-texture run splits. + virtual void Set_Sorted_Texture_Array_Page(int /*page*/) {} + + // TheSuperHackers @refactor bobtista 10/07/2026 Single-call sorted-pool run submit. + // Applies the captured batch packet and issues the indexed draw in one backend + // entry, replacing the per-run Apply_Sorted_Batch_State + Draw_Triangles + // round-trip on backends with a shader pipeline. start_index is in index units + // (triangle start * 3), matching Draw_Triangles. Returns false when the backend + // does not implement the packet path; the caller must then run the legacy + // apply/draw sequence. Default = not implemented (DX8Backend). + virtual bool Submit_Sorted_Packet(const RenderBackendSortedBatchState & /*packet*/, + unsigned int /*start_index*/, + unsigned int /*polygon_count*/, + unsigned int /*vertex_count*/, + int /*array_page*/) { return false; } + + // TheSuperHackers @refactor bobtista 11/07/2026 Single-call rigid mesh draw: + // the polygon renderer hands the complete indexed draw (index-buffer base + // offset plus ranges) to the backend in one entry instead of the legacy + // Set_Index_Buffer_Index_Offset + Draw_Triangles/Draw_Strip pair. Returns + // false when not implemented (DX8Backend); the caller then runs the legacy + // sequence. + virtual bool Submit_Rigid_Packet(int /*ib_base_offset*/, + unsigned int /*start_index*/, + unsigned int /*primitive_count*/, + unsigned int /*min_vertex_index*/, + unsigned int /*vertex_count*/, + bool /*triangle_strip*/) { return false; } + + // TheSuperHackers @refactor bobtista 11/04/2026 Lets Draw_Sorting_IB_VB hand its inner + // dynamic VB/IB to the backend so bgfx can claim their transient buffers and submit a + // remapped draw, skipping the outer stale Draw_Triangles. 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 + // ------------------------------------------------------------------------- + + virtual void Set_Shader(const ShaderClass & shader) {} + virtual void Get_Shader(ShaderClass & shader) {} + virtual void Set_Material(const VertexMaterialClass * material) {} + virtual void Apply_Material_State(const RenderBackendMaterialState & material) {} + virtual void Set_Material_Color_Source(RenderBackendMaterialColorSource ambient_source, + RenderBackendMaterialColorSource diffuse_source, + RenderBackendMaterialColorSource emissive_source) {} + virtual void Set_Texture(unsigned int stage, TextureBaseClass * texture) {} + // Immediate texture-stage bind for legacy custom passes that do not call + // Apply_Render_State_Changes between pass setup and draw. DX8 binds the + // native stage immediately; bgfx captures the same stage in its draw state. + virtual void Bind_Texture_Immediate(unsigned int stage, TextureBaseClass * texture) { Set_Texture(stage, texture); } + + // 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) {} + + virtual void Apply_Render_State_Changes() {} + virtual void Apply_Default_State() {} + virtual void Invalidate_Cached_Render_States() {} + + // TheSuperHackers @refactor bobtista 10/04/2026 Typed blend + + // color-write setters. These exist so subsystems can migrate without the + // interface re-exposing raw D3D render-state identifiers. + 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) {} + // TheSuperHackers @refactor bobtista 10/04/2026 Natural complement + // to theblend extension. + virtual void Set_Alpha_Blend_Enable(bool enable) {} + virtual void Set_Alpha_Test_Enable(bool enable) {} + virtual void Set_Alpha_Test_Reference(unsigned ref) {} + virtual void Set_Alpha_Test_Function(CompareFunc func) {} + virtual void Set_Alpha_Test(bool enable, unsigned ref, CompareFunc func) + { + Set_Alpha_Test_Reference(ref); + Set_Alpha_Test_Function(func); + Set_Alpha_Test_Enable(enable); + } + virtual void Set_Normalize_Normals(bool enable) {} + + // TheSuperHackers @refactor bobtista 10/04/2026 Hardware cursor + // extension. Lets W3DMouse drive the device's hardware cursor without + // touching the raw device directly. + virtual void Show_Hardware_Cursor(bool show) {} + virtual void Set_Hardware_Cursor_Image(int hotspot_x, int hotspot_y, const RenderBackendImage & image) {} + virtual void Set_Hardware_Cursor_Position(int x, int y) {} + + // TheSuperHackers @refactor bobtista 10/04/2026 Stencil state + // group. Each method maps 1:1 onto an existing legacy stencil state. The + // CompareFunc and StencilOp enums above are reusable for future depth + // and stencil work. + 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) {} + + // TheSuperHackers @refactor bobtista 14/04/2026 Render-state remainders. + // These wrap the last legacy render-state 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) {} + virtual void Set_Normal_Bias(float bias) {} + virtual void Set_Fill_Mode(FillMode mode) {} + virtual void Set_Shade_Mode(ShadeMode 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 bool Supports_Color_Write_Mask() const { return true; } + virtual unsigned Get_Color_Write_Mask() const { return RB_COLOR_RGBA; } + virtual void Set_Color_Write_Mask(unsigned mask) {} + virtual void Set_Lighting_Enable(bool enable) {} + virtual void Set_Point_Sprite_Enable(bool enable) {} + virtual void Set_Point_Scale_Enable(bool enable) {} + virtual void Set_Point_Size(float size, float min_size, float max_size) {} + virtual void Set_Point_Scale(float a, float b, float c) {} + virtual void Set_Texture_Factor(unsigned argb) {} + virtual CullMode Get_Cull_Mode() const { return RB_CULL_CW; } + virtual void Set_Cull_Mode(CullMode mode) {} + + // 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) {} + + // TheSuperHackers @feature bobtista 20/04/2026 Grayscale + // output for disabled 2D UI elements (Render2DClass::Enable_Grayscale). + // DX8 backend programs the DOTPRODUCT3/MODULATE TSS cascade for the + // legacy path. bgfx backend drives a luminance-conversion uniform in + // fs_uber and does not need texture-stage setup. + virtual void Set_Grayscale_Mode(bool enable) {} + virtual void Configure_Grayscale_Texture_Stages() {} + virtual void Configure_Custom_Edging_Cloud_Texture_Stages() {} + virtual void Configure_Shadow_Volume_Fill_Texture_Stages() {} + + // 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) {} + virtual void Set_Light_Map_Params(bool /*enable*/, float /*stretch*/, TextureClass * /*noise_tex*/) {} + + // ------------------------------------------------------------------------- + // 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) const {} + virtual void Set_World_Identity() {} + virtual void Set_View_Identity() {} + virtual bool Is_World_Identity() const { return false; } + virtual bool Is_View_Identity() const { return false; } + 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 Clear_Light(unsigned int index) {} + virtual void Set_Ambient(const Vector3 & color) {} + // Returns a reference — no sensible default without the Vector3 constructor. + // Standalone backends must override; ref-popup DX8Backend provides the real value. + virtual const Vector3 & Get_Ambient() const = 0; + virtual void Set_Fog(bool enable, const Vector3 & color, float start, float end) {} + virtual void Set_Fog_Enable(bool enable) {} + virtual void Set_Fog_Color(unsigned argb) {} + virtual unsigned Get_Fog_Color() const { return 0; } + virtual bool Get_Fog_Enable() const { return false; } + virtual void Set_Light_Environment(LightEnvironmentClass * light_env) {} + virtual LightEnvironmentClass * Get_Light_Environment() const { return nullptr; } + virtual void Set_Specular_Enable(bool enable) {} + virtual void Set_Patch_Segments(float level) {} + + // 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(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) {} + virtual void Override_Material_Opacity(float opacity) {} + virtual void Set_Texture_Transform(unsigned stage, const Matrix4x4& matrix) {} + virtual void Clear_Texture_Transform(unsigned stage) {} + virtual void Set_Texture_Coord_Source(unsigned stage, + RenderBackendTexcoordSource source, + unsigned uv_array_index = 0) {} + virtual void Set_Texture_Transform_Mode(unsigned stage, unsigned coord_count, bool projected) {} + virtual void Set_Texture_Bump_Env_Matrix(unsigned stage, + float m00, + float m01, + float m10, + float m11) {} + virtual void Set_Texture_Bump_Env_Luminance(unsigned stage, + float scale, + float offset) {} + virtual void Set_Texture_Color_Operation(unsigned stage, + RenderBackendTextureOperation op) {} + virtual void Set_Texture_Alpha_Operation(unsigned stage, + RenderBackendTextureOperation op) {} + virtual void Set_Texture_Color_Argument(unsigned stage, + unsigned argument_index, + RenderBackendTextureArgument arg) {} + virtual void Set_Texture_Alpha_Argument(unsigned stage, + unsigned argument_index, + RenderBackendTextureArgument arg) {} + virtual void Set_Texture_Coord_Generation(unsigned stage, bool cameraPosEnabled) + { + Set_Texture_Coord_Source(stage, + cameraPosEnabled ? RB_TEXCOORD_CAMERA_SPACE_POSITION : RB_TEXCOORD_MESH_UV, + stage); + } + virtual void Set_Texture_UV_Wrap(unsigned stage, bool enable) {} + virtual void Set_Texture_Address_Mode(unsigned stage, + RenderBackendTextureAddressMode u, + RenderBackendTextureAddressMode v, + RenderBackendTextureAddressMode w) {} + virtual void Set_Texture_Sample_Filter(unsigned stage, + RenderBackendTextureSampleFilter min_filter, + RenderBackendTextureSampleFilter mag_filter, + RenderBackendTextureSampleFilter mip_filter) {} + virtual void Set_Texture_Min_Mag_Filter(unsigned stage, + RenderBackendTextureSampleFilter min_filter, + RenderBackendTextureSampleFilter mag_filter) {} + virtual void Set_Texture_Mip_Filter(unsigned stage, + RenderBackendTextureSampleFilter mip_filter) {} + virtual void Set_Texture_Max_Anisotropy(unsigned stage, unsigned max_anisotropy) {} + virtual void Set_Texture_Clamp_Mode(unsigned stage, bool clampU, bool clampV) {} + virtual void Set_Texture_Stage_State(unsigned stage, unsigned state, unsigned value) {} + virtual void Set_Shroud_Texture_Pass_Active(bool active, unsigned stage) {} + virtual void Set_Object_Shroud_Texture_Pass_Active(bool active) {} + virtual void Set_Object_Shroud_Alpha_Mask_Texture(TextureBaseClass * texture) {} + virtual void Set_Shroud_Texture_Params(float offset_x, float offset_y, + float scale_x, float scale_y) {} + // Some backends need object shroud material passes delayed until after all + // opaque object base draws so the multiplicative pass cannot be overwritten + // by another FVF container's later base pass. + virtual bool Requires_Delayed_Object_Shroud_Pass() const { return false; } + virtual void Begin_Water_Overlay() {} + virtual void End_Water_Overlay() {} + // 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 bool Begin_Smudge_Distortion(float tactical_width_fraction = 1.0f, + float tactical_height_fraction = 1.0f) { return false; } + virtual void End_Smudge_Distortion() {} + virtual void Clear_State_Overrides() {} + + // ------------------------------------------------------------------------- + // 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 bool Is_Triangle_Draw_Enabled() const { return true; } + virtual void Set_Triangle_Draw_Enabled(bool /*enable*/) {} + virtual void Draw_Screen_Color_Quad(unsigned /*color*/, + int /*x*/, + int /*y*/, + int /*width*/, + int /*height*/) {} + virtual void Draw_Screen_Multiply_Quad(unsigned /*color*/, + int /*x*/, + int /*y*/, + int /*width*/, + int /*height*/) {} + + virtual void Draw_Strip(unsigned short start_index, + unsigned short index_count, + unsigned short min_vertex_index, + unsigned short vertex_count) {} + + // 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 07/05/2026 Marks the W3D projected + // shadow decal flush. bgfx uses this pass provenance to distinguish real + // blob shadows from effect draws that may bind the same texture names. + // No-op in non-bgfx backends. + virtual void Set_Projected_Shadow_Decal_Active(bool /*active*/) {} + virtual void Set_Projected_Decal_Mode(RenderBackendProjectedDecalMode /*mode*/) {} + + // 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. + virtual void Apply_Stencil_Shadow_Darken(unsigned /*shadow_color*/, + unsigned /*stencil_read_mask*/, + unsigned /*stencil_ref*/, + int /*x*/, + int /*y*/, + int /*width*/, + int /*height*/) {} + + // TheSuperHackers @refactor bobtista 15/04/2026 Called after the side-wall draw to submit + // front+back caps so bgfx's stencil algorithm sees a closed volume. Silhouette verts sit + // at strip_start_vertex + 2*i (caster) and + 2*i + 1 (extruded). No-op on DX8Backend. + virtual void Submit_Shadow_Volume_Caps(unsigned /*strip_start_vertex*/, + unsigned /*num_silhouette_verts*/) {} + + // TheSuperHackers @refactor bobtista 15/04/2026 Caller-triangulated caps: cap_indices holds + // local silhouette indices (3 per triangle); the backend maps front caps to + // strip_start_vertex + 2*i and back caps to + 2*i + 1 with reversed winding. No-op on DX8. + virtual void Submit_Shadow_Volume_Triangulated_Caps( + unsigned /*strip_start_vertex*/, + const short * /*local_cap_indices*/, + unsigned /*cap_index_count*/) {} + + // DX8's original stencil-volume path used open side-wall tubes. Modern + // backends need closed volumes because near-plane clipping otherwise + // leaves unbalanced stencil counts. Default false keeps DX8 unchanged. + virtual bool Needs_Closed_Shadow_Volumes() const { return false; } + + // TheSuperHackers @feature bobtista 17/04/2026 Push shroud system-memory pixels to the + // backend (bgfx cannot lock the POOL_DEFAULT destination). border_pixel fills texels + // outside the source rect; off-map terrain samples that border ring. 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 /*src_x*/, + unsigned /*src_y*/, + unsigned /*dst_x*/, + unsigned /*dst_y*/, + unsigned /*pitch*/, + WW3DFormat /*format*/, + unsigned /*border_pixel*/ = 0xFFFFu) {} + + // ------------------------------------------------------------------------- + // 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. + + // Legacy shader-object lifetime. File-backed shaders may be + // handled directly by a backend before the caller loads bytecode; bgfx + // uses this to provide compatibility handles for native shader paths + // without requiring obsolete .vso/.pso bytecode files. + virtual const unsigned int * Get_Legacy_Vertex_Shader_Declaration( + RenderBackendLegacyVertexDeclaration /*declaration*/) const { return nullptr; } + virtual bool Load_Legacy_Shader(const char * /*path*/, + const unsigned int * /*declaration*/, + unsigned int /*usage*/, + RenderBackendShaderKind /*kind*/, + unsigned long * /*handle*/) { return false; } + virtual bool Create_Vertex_Shader(const unsigned int * /*declaration*/, + const unsigned int * /*shader*/, + unsigned int /*usage*/, + unsigned long * /*handle*/) { return false; } + virtual bool Create_Pixel_Shader(const unsigned int * /*shader*/, + unsigned long * /*handle*/) { return false; } + virtual bool Create_Legacy_Pixel_Shader(RenderBackendLegacyPixelShaderMode /*mode*/, + unsigned long * /*handle*/) { return false; } + virtual void Delete_Vertex_Shader(unsigned long /*vertex_shader*/) {} + virtual void Delete_Pixel_Shader(unsigned long /*pixel_shader*/) {} + 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 = WW3D_FORMAT_UNKNOWN) { return nullptr; } + virtual void Set_Render_Target_With_Z(TextureClass * texture, ZTextureClass * ztexture = nullptr) {} + virtual bool Is_Render_To_Texture() const { return false; } + virtual void Set_Shadow_Map(int idx, ZTextureClass * ztex) {} + virtual ZTextureClass * Get_Shadow_Map(int idx) const { return nullptr; } + + // ------------------------------------------------------------------------- + // Resource creation (asset ingress) + // ------------------------------------------------------------------------- + // + // These methods let asset loaders produce CPU-side pixel / vertex / index + // data and hand it to whichever backend is active. Each backend creates + // its native GPU resource from the bytes and returns an opaque + // RenderResource handle. + // + // W3D asset wrapper classes (TextureBaseClass, VertexBufferClass, + // IndexBufferClass) store the returned handle beside their existing + // raw legacy resource field; on DX8 builds that field still drives + // rendering, on bgfx builds the handle returned here is authoritative. + + virtual bool Requires_Legacy_Buffer_Resources() const { return true; } + virtual RenderResource Create_Texture(const TextureDesc & desc) { return kInvalidRenderResource; } + virtual RenderResource Create_Vertex_Buffer(const BufferDesc & desc, + const void * initial_data) { return kInvalidRenderResource; } + virtual RenderResource Create_Index_Buffer(const BufferDesc & desc, + const void * initial_data, + bool indices_are_32bit) { return kInvalidRenderResource; } + virtual void Destroy_Resource(RenderResource h) {} + + // Transitional owner-backed resource hooks: populate m_backendHandle at the end of + // wrapper construction. Default returns the invalid handle. + + virtual RenderResource Register_Texture_Resource(TextureBaseClass * /*tex*/) { return kInvalidRenderResource; } + virtual RenderResource Register_Vertex_Buffer_Resource(VertexBufferClass * /*vb*/) { return kInvalidRenderResource; } + virtual RenderResource Register_Index_Buffer_Resource(IndexBufferClass * /*ib*/) { return kInvalidRenderResource; } +}; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/RenderBackend.cpp b/Core/Libraries/Source/WWVegas/WW3D2/RenderBackend.cpp new file mode 100644 index 00000000000..43817592c3a --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/RenderBackend.cpp @@ -0,0 +1,54 @@ +/* +** 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, selected +// at compile time via GGC_RENDER_BACKEND_DX8 (default) or GGC_RENDER_BACKEND_BGFX. + +#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..faf6e6a992f --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/RenderBackend.h @@ -0,0 +1,41 @@ +/* +** 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. + +#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. The concrete backend +// (DX8Backend or BgfxBackend) is selected at compile time via the +// GGC_RENDER_BACKEND CMake flag. +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/RenderBufferTypes.h b/Core/Libraries/Source/WWVegas/WW3D2/RenderBufferTypes.h new file mode 100644 index 00000000000..b141d14982b --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/RenderBufferTypes.h @@ -0,0 +1,30 @@ +/* +** 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 + +enum { + BUFFER_TYPE_STATIC, + BUFFER_TYPE_SORTING, + BUFFER_TYPE_DYNAMIC, + BUFFER_TYPE_DYNAMIC_SORTING, + BUFFER_TYPE_INVALID +}; + +constexpr unsigned BUFFER_TYPE_DX8 = BUFFER_TYPE_STATIC; +constexpr unsigned BUFFER_TYPE_DYNAMIC_DX8 = BUFFER_TYPE_DYNAMIC; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/RenderDeviceCleanupHook.h b/Core/Libraries/Source/WWVegas/WW3D2/RenderDeviceCleanupHook.h new file mode 100644 index 00000000000..d8f9651979d --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/RenderDeviceCleanupHook.h @@ -0,0 +1,31 @@ +/* +** 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 + +// Called before and after render-device reset so device-dependent resources can +// be released and reacquired without exposing the full DX8 wrapper facade. +class RenderDeviceCleanupHook +{ +public: + virtual ~RenderDeviceCleanupHook() = default; + virtual void ReleaseResources() = 0; + virtual void ReAcquireResources() = 0; +}; + +using DX8_CleanupHook = RenderDeviceCleanupHook; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/RenderDocTrigger.cpp b/Core/Libraries/Source/WWVegas/WW3D2/RenderDocTrigger.cpp new file mode 100644 index 00000000000..f0e9773db8a --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/RenderDocTrigger.cpp @@ -0,0 +1,108 @@ +// TheSuperHackers @feature bobtista 01/06/2026 See RenderDocTrigger.h. + +#include "RenderDocTrigger.h" +#include "GgcRuntimeFlags.h" + +#include + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +namespace +{ + +struct RenderDocApiTable +{ + void (*entries[22])(); +}; + +using TriggerCaptureFn = void(__cdecl *)(void); +using GetAPIFn = int(__cdecl *)(int version, void **outApiPointers); + +static const int kRENDERDOC_API_Version_1_0_0 = 10000; +static const int kTriggerCaptureIndex = 15; + +static RenderDocApiTable * s_api = nullptr; +static bool s_apiResolved = false; +static int s_targetFrame = -1; +static int s_interval = 0; +static int s_frameIndex = 0; +static bool s_envResolved = false; + +void Resolve_Env() +{ + if (s_envResolved) { + return; + } + s_envResolved = true; + s_targetFrame = GgcFlags::IntValue(GgcFlag_RenderDocCaptureAfter); + s_interval = GgcFlags::IntValue(GgcFlag_RenderDocCaptureInterval); +} + +void Resolve_Api() +{ + if (s_apiResolved) { + return; + } + s_apiResolved = true; + HMODULE mod = GetModuleHandleA("renderdoc.dll"); + if (mod == NULL) { + return; + } + GetAPIFn getApi = reinterpret_cast( + GetProcAddress(mod, "RENDERDOC_GetAPI")); + if (getApi == nullptr) { + return; + } + void * outPtr = nullptr; + if (getApi(kRENDERDOC_API_Version_1_0_0, &outPtr) != 1) { + return; + } + s_api = reinterpret_cast(outPtr); +} + +} // namespace + +void RenderDoc_Maybe_Trigger_Capture() +{ + Resolve_Env(); + if (s_targetFrame <= 0) { + return; + } + + ++s_frameIndex; + + bool trigger = false; + if (s_frameIndex == s_targetFrame) { + trigger = true; + } + else if (s_interval > 0 + && s_frameIndex > s_targetFrame + && ((s_frameIndex - s_targetFrame) % s_interval) == 0) { + trigger = true; + } + if (!trigger) { + return; + } + + Resolve_Api(); + if (s_api == nullptr) { + return; + } + TriggerCaptureFn fn = reinterpret_cast( + s_api->entries[kTriggerCaptureIndex]); + if (fn != nullptr) { + fn(); + } +} + +#else // !_WIN32 + +void RenderDoc_Maybe_Trigger_Capture() +{ +} + +#endif diff --git a/Core/Libraries/Source/WWVegas/WW3D2/RenderDocTrigger.h b/Core/Libraries/Source/WWVegas/WW3D2/RenderDocTrigger.h new file mode 100644 index 00000000000..cc91da9a651 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/RenderDocTrigger.h @@ -0,0 +1,9 @@ +// TheSuperHackers @feature bobtista 01/06/2026 +// Backend-agnostic per-frame hook that drives RenderDoc's TriggerCapture() +// when GGC_RENDERDOC_CAPTURE_AFTER= is set. Optional +// GGC_RENDERDOC_CAPTURE_INTERVAL= re-triggers every K frames after the +// first capture. No-op if renderdoc.dll is not injected. + +#pragma once + +void RenderDoc_Maybe_Trigger_Capture(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/RenderStateDefs.h b/Core/Libraries/Source/WWVegas/WW3D2/RenderStateDefs.h new file mode 100644 index 00000000000..2267f68689c --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/RenderStateDefs.h @@ -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. +*/ + +#pragma once + +// Render state indices for FixedFunctionState::Cached/Set_Cached_Render_State. +// Values match legacy render state ordinals so the DX8 backend can use the same cache. +namespace RS { +constexpr unsigned ZENABLE = 7; +constexpr unsigned FILLMODE = 8; +constexpr unsigned SHADEMODE = 9; +constexpr unsigned ZWRITEENABLE = 14; +constexpr unsigned ALPHATESTENABLE = 15; +constexpr unsigned SRCBLEND = 19; +constexpr unsigned DESTBLEND = 20; +constexpr unsigned CULLMODE = 22; +constexpr unsigned ZFUNC = 23; +constexpr unsigned ALPHAREF = 24; +constexpr unsigned ALPHAFUNC = 25; +constexpr unsigned ALPHABLENDENABLE = 27; +constexpr unsigned FOGENABLE = 28; +constexpr unsigned SPECULARENABLE = 29; +constexpr unsigned FOGCOLOR = 34; +constexpr unsigned ZBIAS = 47; +constexpr unsigned STENCILENABLE = 52; +constexpr unsigned STENCILFAIL = 53; +constexpr unsigned STENCILZFAIL = 54; +constexpr unsigned STENCILPASS = 55; +constexpr unsigned STENCILFUNC = 56; +constexpr unsigned STENCILREF = 57; +constexpr unsigned STENCILMASK = 58; +constexpr unsigned STENCILWRITEMASK = 59; +constexpr unsigned TEXTUREFACTOR = 60; +constexpr unsigned LIGHTING = 137; +constexpr unsigned AMBIENT = 139; +constexpr unsigned NORMALIZENORMALS = 143; +constexpr unsigned DIFFUSEMATERIALSOURCE = 145; +constexpr unsigned AMBIENTMATERIALSOURCE = 147; +constexpr unsigned EMISSIVEMATERIALSOURCE = 148; +constexpr unsigned POINTSIZE = 154; +constexpr unsigned POINTSIZEMIN = 155; +constexpr unsigned POINTSPRITEENABLE = 156; +constexpr unsigned POINTSCALEENABLE = 157; +constexpr unsigned POINTSCALE_A = 158; +constexpr unsigned POINTSCALE_B = 159; +constexpr unsigned POINTSCALE_C = 160; +constexpr unsigned PATCHSEGMENTS = 164; +constexpr unsigned POINTSIZEMAX = 166; +constexpr unsigned COLORWRITEENABLE = 168; +constexpr unsigned BLENDOP = 171; +} // namespace RS + +// Texture stage state indices for FixedFunctionState::Cached/Set_Cached_Texture_Stage_State. +// Values match legacy texture stage state ordinals so the DX8 backend can use the same cache. +namespace TSS { +constexpr unsigned COLOROP = 1; +constexpr unsigned COLORARG1 = 2; +constexpr unsigned COLORARG2 = 3; +constexpr unsigned ALPHAOP = 4; +constexpr unsigned ALPHAARG1 = 5; +constexpr unsigned ALPHAARG2 = 6; +constexpr unsigned BUMPENVMAT00 = 7; +constexpr unsigned BUMPENVMAT01 = 8; +constexpr unsigned BUMPENVMAT10 = 9; +constexpr unsigned BUMPENVMAT11 = 10; +constexpr unsigned TEXCOORDINDEX = 11; +constexpr unsigned ADDRESSU = 13; +constexpr unsigned ADDRESSV = 14; +constexpr unsigned MAGFILTER = 16; +constexpr unsigned MINFILTER = 17; +constexpr unsigned MIPFILTER = 18; +constexpr unsigned MAXANISOTROPY = 21; +constexpr unsigned BUMPENVLSCALE = 22; +constexpr unsigned BUMPENVLOFFSET = 23; +constexpr unsigned TEXTURETRANSFORMFLAGS = 24; +constexpr unsigned ADDRESSW = 25; +constexpr unsigned COLORARG0 = 26; +constexpr unsigned ALPHAARG0 = 27; +} // namespace TSS diff --git a/Core/Libraries/Source/WWVegas/WW3D2/TextureResourceManager.cpp b/Core/Libraries/Source/WWVegas/WW3D2/TextureResourceManager.cpp new file mode 100644 index 00000000000..3ee442551e0 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/TextureResourceManager.cpp @@ -0,0 +1,79 @@ +/* +** 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 . +*/ + +#include "TextureResourceManager.h" + +TextureTrackerList TextureResourceManagerClass::Managed_Textures; + +void TextureResourceManagerClass::Shutdown() +{ + while (!Managed_Textures.Is_Empty()) + { + TextureTrackerClass *track = Managed_Textures.Remove_Head(); + delete track; + } +} + +void TextureResourceManagerClass::Add(TextureTrackerClass *track) +{ + // this function should only be called by the texture constructor + Managed_Textures.Add(track); +} + +void TextureResourceManagerClass::Remove(TextureBaseClass *tex) +{ + // this function should only be called by the texture destructor + TextureTrackerListIterator it(&Managed_Textures); + + while (!it.Is_Done()) + { + TextureTrackerClass *track = it.Peek_Obj(); + if (track->Get_Texture() == tex) + { + it.Remove_Current_Object(); + delete track; + break; + } + it.Next(); + } +} + +void TextureResourceManagerClass::Release_Textures() +{ + TextureTrackerListIterator it(&Managed_Textures); + + while (!it.Is_Done()) + { + TextureTrackerClass *track = it.Peek_Obj(); + track->Release(); + it.Next(); + } +} + +void TextureResourceManagerClass::Recreate_Textures() +{ + TextureTrackerListIterator it(&Managed_Textures); + + while (!it.Is_Done()) + { + TextureTrackerClass *track = it.Peek_Obj(); + track->Recreate(); + track->Get_Texture()->Set_Dirty(); + it.Next(); + } +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/TextureResourceManager.h b/Core/Libraries/Source/WWVegas/WW3D2/TextureResourceManager.h new file mode 100644 index 00000000000..4ba96b4e543 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/TextureResourceManager.h @@ -0,0 +1,67 @@ +/* +** 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 "always.h" +#include "dx8list.h" +#include "multilist.h" +#include "texture.h" + +class TextureTrackerClass : public MultiListObjectClass +{ +public: + TextureTrackerClass + ( + unsigned int w, + unsigned int h, + MipCountType count, + TextureBaseClass *tex + ) + : Width(w), + Height(h), + Mip_level_count(count), + Texture(tex) + { + } + + virtual ~TextureTrackerClass() = default; + virtual void Release() const = 0; + virtual void Recreate() const = 0; + + TextureBaseClass *Get_Texture() const { return Texture; } + +protected: + unsigned int Width; + unsigned int Height; + MipCountType Mip_level_count; + TextureBaseClass *Texture; +}; + +class TextureResourceManagerClass +{ +public: + static void Shutdown(); + static void Add(TextureTrackerClass *track); + static void Remove(TextureBaseClass *tex); + static void Release_Textures(); + static void Recreate_Textures(); + +private: + static TextureTrackerList Managed_Textures; +}; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/WW3DDeviceInit.cpp b/Core/Libraries/Source/WWVegas/WW3D2/WW3DDeviceInit.cpp new file mode 100644 index 00000000000..4169d5a3745 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/WW3DDeviceInit.cpp @@ -0,0 +1,61 @@ +/* +** 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. +*/ + +#include "always.h" + +#include "WW3DDeviceInit.h" + +#include "FixedFunctionState.h" +#include "WW3D2/boxrobj.h" +#include "dx8indexbuffer.h" +#include "dx8renderer.h" +#include "dx8vertexbuffer.h" +#include "missingtexture.h" +#include "pointgr.h" +#include "shattersystem.h" +#include "shdlib.h" +#include "sortingrenderer.h" +#include "texturefilter.h" +#include "textureloader.h" +#include "WW3D2/vertmaterial.h" +#include "WW3D2/ww3d.h" + +void WW3DDeviceInit::Init_Subsystems() +{ + MissingTexture::_Init(); + TextureFilterClass::_Init_Filters( + (TextureFilterClass::TextureFilterMode)WW3D::Get_Texture_Filter(), + (TextureFilterClass::AnisotropicFilterMode)WW3D::Get_Anisotropy_Level() + ); + TheDX8MeshRenderer.Init(); + SHD_INIT; + BoxRenderObjClass::Init(); + VertexMaterialClass::Init(); + PointGroupClass::_Init(); // This needs the VertexMaterialClass to be initted + ShatterSystem::Init(); + TextureLoader::Init(); +} + +void WW3DDeviceInit::Shutdown_Subsystems() +{ + FixedFunctionState::Release_Render_State(); + + TextureLoader::Deinit(); + SortingRendererClass::Deinit(); + DynamicVBAccessClass::_Deinit(); + DynamicIBAccessClass::_Deinit(); + ShatterSystem::Shutdown(); + PointGroupClass::_Shutdown(); + VertexMaterialClass::Shutdown(); + BoxRenderObjClass::Shutdown(); + SHD_SHUTDOWN; + TheDX8MeshRenderer.Shutdown(); + MissingTexture::_Deinit(); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/WW3DDeviceInit.h b/Core/Libraries/Source/WWVegas/WW3D2/WW3DDeviceInit.h new file mode 100644 index 00000000000..e73b7ed5f92 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/WW3DDeviceInit.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. +*/ + +#pragma once + +// TheSuperHackers @refactor bobtista 11/06/2026 Backend-neutral WW3D subsystem +// bring-up/teardown, extracted verbatim from DX8Wrapper's device-dependent +// init/shutdown so the bgfx backend can drive the device lifecycle without +// compiling dx8wrapper.cpp. These touch only WW3D engine subsystems; the +// backend's own g_renderBackend Initialize/Shutdown and the DX8Wrapper-internal +// caps + default-render-state setup stay with their owners. +namespace WW3DDeviceInit +{ + void Init_Subsystems(); + void Shutdown_Subsystems(); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/animatedsoundmgr.cpp b/Core/Libraries/Source/WWVegas/WW3D2/animatedsoundmgr.cpp index be3d790d3ba..e6d28b13a83 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/animatedsoundmgr.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/animatedsoundmgr.cpp @@ -45,8 +45,6 @@ #include "WWSaveLoad/definition.h" #include "WWSaveLoad/definitionmgr.h" #include "WWSaveLoad/definitionclassids.h" -#include "WWAudio/WWAudio.h" -#include "WWAudio/AudibleSound.h" #include "htree.h" #include "hanim.h" #include "soundlibrarybridge.h" @@ -512,7 +510,7 @@ AnimatedSoundMgrClass::Trigger_Sound // // Don't trigger the sound if its skipped too far past... // - //if (WWMath::Fabs (new_frame - old_frame) < 3.0F) { + //if (WWMath::Fabsf (new_frame - old_frame) < 3.0F) { // // Stop the audio? diff --git a/Core/Libraries/Source/WWVegas/WW3D2/bmp2d.cpp b/Core/Libraries/Source/WWVegas/WW3D2/bmp2d.cpp index 27090e0293b..37b64dc9dd6 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/bmp2d.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/bmp2d.cpp @@ -45,6 +45,8 @@ #include "WW3D2/assetmgr.h" #include "textureloader.h" #include "ww3dformat.h" +#include +#include Bitmap2DObjClass::Bitmap2DObjClass ( @@ -77,14 +79,48 @@ Bitmap2DObjClass::Bitmap2DObjClass if (!tex->Is_Initialized()) TextureLoader::Request_Foreground_Loading(tex); - SurfaceClass *surface = tex->Get_Surface_Level(0); - - if (!surface) { - surface = NEW_REF(SurfaceClass, (32, 32, Get_Valid_Texture_Format(WW3D_FORMAT_R8G8B8,true))); + SurfaceClass::SurfaceDescription sd; + tex->Get_Level_Description(sd, 0); + + const TextureBaseClass::TextureMipSnapshot *source_mip = nullptr; + const std::vector &mips = tex->Get_CPU_Texture_Mips(); + if (!mips.empty()) { + const TextureBaseClass::TextureMipSnapshot &mip = mips[0]; + const unsigned bytes_per_pixel = Get_Bytes_Per_Pixel(mip.Format); + const unsigned row_size = mip.Width * bytes_per_pixel; + if (mip.Format != WW3D_FORMAT_UNKNOWN && + mip.Width != 0 && + mip.Height != 0 && + bytes_per_pixel != 0 && + mip.Pitch >= row_size && + mip.Data.size() >= static_cast(mip.Pitch) * mip.Height) + { + source_mip = &mip; + sd.Format = mip.Format; + sd.Width = mip.Width; + sd.Height = mip.Height; + } } - SurfaceClass::SurfaceDescription sd; - surface->Get_Description(sd); +#if !defined(GGC_RENDER_BACKEND_BGFX) + SurfaceClass *surface = nullptr; + if (source_mip == nullptr) { + surface = tex->Get_Surface_Level(0); + if (surface != nullptr) { + surface->Get_Description(sd); + } + } +#endif + + if (source_mip == nullptr +#if !defined(GGC_RENDER_BACKEND_BGFX) + && surface == nullptr +#endif + ) { + sd.Width = 32; + sd.Height = 32; + sd.Format = Get_Valid_Texture_Format(WW3D_FORMAT_R8G8B8,true); + } if (usable_width == -1) usable_width = sd.Width; @@ -166,12 +202,53 @@ Bitmap2DObjClass::Bitmap2DObjClass int pot = MAX(Find_POT(iw), Find_POT(ih)); // create the texture and turn MIP-mapping off. - SurfaceClass *piece_surface=NEW_REF(SurfaceClass,(pot,pot,sd.Format)); - piece_surface->Copy(0,0,tlpx,tlpy,pot,pot,surface); - TextureClass *piece_texture =NEW_REF(TextureClass,(piece_surface,MIP_LEVELS_1)); + TextureClass *piece_texture =NEW_REF(TextureClass,(pot,pot,sd.Format,MIP_LEVELS_1)); + TextureClass::MutableTextureMipView mip = piece_texture->Begin_Mip_Write(0); + const unsigned bytes_per_pixel = Get_Bytes_Per_Pixel(sd.Format); + if (mip.Is_Valid() && bytes_per_pixel != 0) { + if (source_mip != nullptr) { + unsigned copy_width = pot; + unsigned copy_height = pot; + if (tlpx + static_cast(copy_width) > static_cast(source_mip->Width)) { + copy_width = source_mip->Width > static_cast(tlpx) ? source_mip->Width - tlpx : 0; + } + if (tlpy + static_cast(copy_height) > static_cast(source_mip->Height)) { + copy_height = source_mip->Height > static_cast(tlpy) ? source_mip->Height - tlpy : 0; + } + if (copy_width > mip.Width) { + copy_width = mip.Width; + } + if (copy_height > mip.Height) { + copy_height = mip.Height; + } + + const unsigned row_bytes = copy_width * bytes_per_pixel; + for (unsigned row = 0; row < copy_height; ++row) { + const unsigned char *src = source_mip->Data.data() + (tlpy + row) * source_mip->Pitch + tlpx * bytes_per_pixel; + unsigned char *dst = mip.Data + row * mip.Pitch; + ::memcpy(dst, src, row_bytes); + } + } +#if !defined(GGC_RENDER_BACKEND_BGFX) + else if (surface != nullptr) { + SurfaceClass *piece_surface=NEW_REF(SurfaceClass,(pot,pot,sd.Format)); + piece_surface->Copy(0,0,tlpx,tlpy,pot,pot,surface); + int source_pitch = 0; + const unsigned char *source_bits = static_cast(piece_surface->Lock(&source_pitch)); + if (source_bits != nullptr && source_pitch > 0) { + const unsigned row_bytes = mip.Width * bytes_per_pixel; + for (unsigned row = 0; row < mip.Height; ++row) { + ::memcpy(mip.Data + row * mip.Pitch, source_bits + row * source_pitch, row_bytes); + } + piece_surface->Unlock(); + } + REF_PTR_RELEASE(piece_surface); + } +#endif + } + piece_texture->End_Mip_Write(0); piece_texture->Get_Filter().Set_U_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); piece_texture->Get_Filter().Set_V_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); - REF_PTR_RELEASE(piece_surface); // calculate our actual texture coordinates based on the difference between // the width and height of the texture and the width and height the font @@ -200,7 +277,9 @@ Bitmap2DObjClass::Bitmap2DObjClass } } REF_PTR_RELEASE(tex); +#if !defined(GGC_RENDER_BACKEND_BGFX) REF_PTR_RELEASE(surface); +#endif Set_Dirty(); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/colorspace.h b/Core/Libraries/Source/WWVegas/WW3D2/colorspace.h index 4b366e6f895..d8cde45b3d4 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/colorspace.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/colorspace.h @@ -38,7 +38,7 @@ #pragma once -#include "dx8wrapper.h" +#include "WW3D2/ww3dcolor.h" #include void RGB_To_HSV(Vector3 &hsv,const Vector3 &rgb); @@ -95,7 +95,7 @@ inline void HSV_To_RGB(Vector3 &rgb, const Vector3 &hsv) if (h==360.0f) h=0.0f; h/=60.0f; - i=WWMath::Floor(h); + i=WWMath::Floorf(h); f=h-i; p=v*(1.0f-s); q=v*(1.0f-(s*f)); @@ -146,7 +146,7 @@ inline void Recolor(Vector3 &rgb, const Vector3 &hsv_shift) inline void Recolor(unsigned& rgba, const Vector3 &hsv_shift) { - Vector4 rgba_v = DX8Wrapper::Convert_Color(rgba); + Vector4 rgba_v = WW3DColor::From_ARGB(rgba); Recolor((Vector3&)rgba_v, hsv_shift); - rgba = DX8Wrapper::Convert_Color(rgba_v); + rgba = WW3DColor::To_ARGB(rgba_v); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/coltest.cpp b/Core/Libraries/Source/WWVegas/WW3D2/coltest.cpp index 5639973d50b..71ff8c728fd 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/coltest.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/coltest.cpp @@ -71,7 +71,7 @@ AABoxCollisionTestClass::AABoxCollisionTestClass(const AABoxClass & aabox,const bool AABoxCollisionTestClass::Cull(const AABoxClass & box) { // const float MOVE_THRESHOLD = 2.0f; -// if (WWMath::Fabs(Move.X) + WWMath::Fabs(Move.Y) + WWMath::Fabs(Move.Z) > MOVE_THRESHOLD) { +// if (WWMath::Fabsf(Move.X) + WWMath::Fabsf(Move.Y) + WWMath::Fabsf(Move.Z) > MOVE_THRESHOLD) { // CastResultStruct res; // return !Box.Cast_To_Box(Move,box,&res); // } else { @@ -287,17 +287,17 @@ OBBoxCollisionTestClass::OBBoxCollisionTestClass Move(move) { Vector3 max_extent; - max_extent.X = WWMath::Fabs(Box.Basis[0][0] * Box.Extent.X) + - WWMath::Fabs(Box.Basis[0][1] * Box.Extent.Y) + - WWMath::Fabs(Box.Basis[0][2] * Box.Extent.Z) + 0.01f; + max_extent.X = WWMath::Fabsf_Legacy(Box.Basis[0][0] * Box.Extent.X) + + WWMath::Fabsf_Legacy(Box.Basis[0][1] * Box.Extent.Y) + + WWMath::Fabsf_Legacy(Box.Basis[0][2] * Box.Extent.Z) + 0.01f; - max_extent.Y = WWMath::Fabs(Box.Basis[1][0] * Box.Extent.X) + - WWMath::Fabs(Box.Basis[1][1] * Box.Extent.Y) + - WWMath::Fabs(Box.Basis[1][2] * Box.Extent.Z) + 0.01f; + max_extent.Y = WWMath::Fabsf_Legacy(Box.Basis[1][0] * Box.Extent.X) + + WWMath::Fabsf_Legacy(Box.Basis[1][1] * Box.Extent.Y) + + WWMath::Fabsf_Legacy(Box.Basis[1][2] * Box.Extent.Z) + 0.01f; - max_extent.Z = WWMath::Fabs(Box.Basis[2][0] * Box.Extent.X) + - WWMath::Fabs(Box.Basis[2][1] * Box.Extent.Y) + - WWMath::Fabs(Box.Basis[2][2] * Box.Extent.Z) + 0.01f; + max_extent.Z = WWMath::Fabsf_Legacy(Box.Basis[2][0] * Box.Extent.X) + + WWMath::Fabsf_Legacy(Box.Basis[2][1] * Box.Extent.Y) + + WWMath::Fabsf_Legacy(Box.Basis[2][2] * Box.Extent.Z) + 0.01f; SweepMin = Box.Center - max_extent; SweepMax = Box.Center + max_extent; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/decalsys.cpp b/Core/Libraries/Source/WWVegas/WW3D2/decalsys.cpp index a7b54dc0b7b..2d107c09a0c 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/decalsys.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/decalsys.cpp @@ -394,10 +394,10 @@ void MultiFixedPoolDecalSystemClass::Clear_All_Decals() MultiFixedPoolDecalSystemClass::LogicalDecalClass & MultiFixedPoolDecalSystemClass::find_logical_decal(uint32 pool_id, uint32 slot_id) { assert(pool_id < PoolCount); - pool_id = MIN(pool_id, PoolCount); + pool_id = MIN(pool_id, PoolCount - 1); LogicalDecalPoolClass & pool = Pools[pool_id]; assert(slot_id < pool.Size); - slot_id = MIN(slot_id, pool.Size); + slot_id = MIN(slot_id, pool.Size - 1); return pool.Array[slot_id]; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8caps.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8caps.cpp index 48e368a31c5..a786602d3bb 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8caps.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8caps.cpp @@ -40,16 +40,51 @@ #include "WWLib/always.h" #include "dx8caps.h" #include "dx8wrapper.h" -#include "formconv.h" +#include "dx8formatconv.h" #pragma warning (disable : 4201) // nonstandard extension - nameless struct #include -#include static StringClass CapsWorkString; #define DXLOG(n) CapsWorkString.Format n ; CapsLog+=CapsWorkString; #define COMPACTLOG(n) CapsWorkString.Format n ; CompactLog+=CapsWorkString; +namespace +{ + using LegacyDirect3D = IDirect3D8; + using LegacyDevice = IDirect3DDevice8; + using LegacyCaps = D3DCAPS8; + using LegacyAdapterIdentifier = D3DADAPTER_IDENTIFIER8; + using LegacyFormat = D3DFORMAT; + + LegacyCaps& Mutable_Legacy_Caps(void* caps) + { + return *static_cast(caps); + } + + const LegacyCaps& Legacy_Caps(const void* caps) + { + return *static_cast(caps); + } + +#if !defined(GGC_RENDER_BACKEND_BGFX) + constexpr auto kLegacySoftwareVertexProcessingState = D3DRS_SOFTWAREVERTEXPROCESSING; +#endif + constexpr auto kLegacyHardwareTransformAndLight = D3DDEVCAPS_HWTRANSFORMANDLIGHT, kLegacyNPatches = D3DDEVCAPS_NPATCHES; + constexpr auto kLegacyZBias = D3DPRASTERCAPS_ZBIAS, kLegacyRangeFog = D3DPRASTERCAPS_FOGRANGE, kLegacyFullscreenGamma = D3DCAPS2_FULLSCREENGAMMA; + constexpr auto kLegacyModulateAlphaAddColor = D3DTEXOPCAPS_MODULATEALPHA_ADDCOLOR, kLegacyDotProduct3 = D3DTEXOPCAPS_DOTPRODUCT3, kLegacyBumpEnv = D3DTEXOPCAPS_BUMPENVMAP, kLegacyBumpEnvLuminance = D3DTEXOPCAPS_BUMPENVMAPLUMINANCE; + constexpr auto kLegacyCubeMap = D3DPTEXTURECAPS_CUBEMAP, kLegacyMagAnisotropic = D3DPTFILTERCAPS_MAGFANISOTROPIC, kLegacyMinAnisotropic = D3DPTFILTERCAPS_MINFANISOTROPIC; + constexpr auto kLegacyTextureResource = D3DRTYPE_TEXTURE; + constexpr auto kLegacyRenderTargetUsage = D3DUSAGE_RENDERTARGET, kLegacyDepthStencilUsage = D3DUSAGE_DEPTHSTENCIL; + +#if !defined(GGC_RENDER_BACKEND_BGFX) + void Set_Legacy_Software_Vertex_Processing(LegacyDevice *device, BOOL enabled) + { + device->SetRenderState(kLegacySoftwareVertexProcessingState, enabled); + } +#endif +} + static const char* const VendorNames[]={ "Unknown", "NVidia", @@ -467,31 +502,32 @@ DX8Caps::DeviceTypeIntel DX8Caps::Get_Intel_Device(unsigned device_id) } DX8Caps::DX8Caps( - IDirect3D8* direct3d, - IDirect3DDevice8* D3DDevice, + void* direct3d, + void* device, WW3DFormat display_format, - const D3DADAPTER_IDENTIFIER8& adapter_id) + const void* adapter_id) : - Direct3D(direct3d), MaxDisplayWidth(0), - MaxDisplayHeight(0) + MaxDisplayHeight(0), + Caps(new LegacyCaps), + Direct3D(direct3d) { - Init_Caps(D3DDevice); + Init_Caps(device); Compute_Caps(display_format, adapter_id); } DX8Caps::DX8Caps( - IDirect3D8* direct3d, - const D3DCAPS8& caps, + void* direct3d, + const void* caps, WW3DFormat display_format, - const D3DADAPTER_IDENTIFIER8& adapter_id) + const void* adapter_id) : - Direct3D(direct3d), - Caps(caps), MaxDisplayWidth(0), - MaxDisplayHeight(0) + MaxDisplayHeight(0), + Caps(new LegacyCaps(Legacy_Caps(caps))), + Direct3D(direct3d) { - if ((Caps.DevCaps&D3DDEVCAPS_HWTRANSFORMANDLIGHT)==D3DDEVCAPS_HWTRANSFORMANDLIGHT) { + if ((Legacy_Caps(Caps).DevCaps&kLegacyHardwareTransformAndLight)==kLegacyHardwareTransformAndLight) { SupportTnL=true; } else { SupportTnL=false; @@ -500,6 +536,18 @@ DX8Caps::DX8Caps( Compute_Caps(display_format,adapter_id); } +DX8Caps::~DX8Caps() +{ + delete static_cast(Caps); +} + +#if !defined(GGC_RENDER_BACKEND_BGFX) +D3DCAPS8 const& DX8Caps::Get_DX8_Caps() const +{ + return Legacy_Caps(Caps); +} +#endif + //Don't really need this but I added this function to free static variables so //they don't show up in our memory manager as a leak. -MW 7-22-03 void DX8Caps::Shutdown() @@ -513,16 +561,22 @@ void DX8Caps::Shutdown() // // ---------------------------------------------------------------------------- -void DX8Caps::Init_Caps(IDirect3DDevice8* D3DDevice) +void DX8Caps::Init_Caps(void* device) { - D3DDevice->SetRenderState(D3DRS_SOFTWAREVERTEXPROCESSING,TRUE); - DX8CALL(GetDeviceCaps(&Caps)); - - if ((Caps.DevCaps&D3DDEVCAPS_HWTRANSFORMANDLIGHT)==D3DDEVCAPS_HWTRANSFORMANDLIGHT) { + LegacyCaps& caps = Mutable_Legacy_Caps(Caps); +#if !defined(GGC_RENDER_BACKEND_BGFX) + LegacyDevice* D3DDevice = static_cast(device); + Set_Legacy_Software_Vertex_Processing(D3DDevice, TRUE); + DX8CALL(GetDeviceCaps(&caps)); +#endif + + if ((caps.DevCaps&kLegacyHardwareTransformAndLight)==kLegacyHardwareTransformAndLight) { SupportTnL=true; - D3DDevice->SetRenderState(D3DRS_SOFTWAREVERTEXPROCESSING,FALSE); - DX8CALL(GetDeviceCaps(&Caps)); +#if !defined(GGC_RENDER_BACKEND_BGFX) + Set_Legacy_Software_Vertex_Processing(D3DDevice, FALSE); + DX8CALL(GetDeviceCaps(&caps)); +#endif } else { SupportTnL=false; } @@ -533,8 +587,10 @@ void DX8Caps::Init_Caps(IDirect3DDevice8* D3DDevice) // Compute the caps bits // // ---------------------------------------------------------------------------- -void DX8Caps::Compute_Caps(WW3DFormat display_format, const D3DADAPTER_IDENTIFIER8& adapter_id) +void DX8Caps::Compute_Caps(WW3DFormat display_format, const void* adapter_id_ptr) { + const LegacyAdapterIdentifier& adapter_id = *static_cast(adapter_id_ptr); + const LegacyCaps& caps = Legacy_Caps(Caps); // Init_Caps(D3DDevice); CanDoMultiPass=true; @@ -546,10 +602,17 @@ void DX8Caps::Compute_Caps(WW3DFormat display_format, const D3DADAPTER_IDENTIFIE DXLOG(("Driver: %s\r\n",adapter_id.Driver)); DriverDLL=adapter_id.Driver; +#ifdef _WIN32 int Product = HIWORD(adapter_id.DriverVersion.HighPart); int Version = LOWORD(adapter_id.DriverVersion.HighPart); int SubVersion = HIWORD(adapter_id.DriverVersion.LowPart); DriverBuildVersion = LOWORD(adapter_id.DriverVersion.LowPart); +#else + int Product = HIWORD(adapter_id.DriverVersionHighPart); + int Version = LOWORD(adapter_id.DriverVersionHighPart); + int SubVersion = HIWORD(adapter_id.DriverVersionLowPart); + DriverBuildVersion = LOWORD(adapter_id.DriverVersionLowPart); +#endif DXLOG(("Product=%d, Version=%d, SubVersion=%d, Build=%d\r\n",Product, Version, SubVersion, DriverBuildVersion)); @@ -633,15 +696,16 @@ void DX8Caps::Compute_Caps(WW3DFormat display_format, const D3DADAPTER_IDENTIFIE adapter_id.DeviceIdentifier.Data4[7])); - SupportPointSprites = (Caps.MaxPointSize > 1.0f); - SupportNPatches = ((Caps.DevCaps&D3DDEVCAPS_NPATCHES)==D3DDEVCAPS_NPATCHES); - SupportZBias = ((Caps.RasterCaps&D3DPRASTERCAPS_ZBIAS)==D3DPRASTERCAPS_ZBIAS); - supportGamma=((Caps.Caps2&D3DCAPS2_FULLSCREENGAMMA)==D3DCAPS2_FULLSCREENGAMMA); - SupportModAlphaAddClr = (Caps.TextureOpCaps & D3DTEXOPCAPS_MODULATEALPHA_ADDCOLOR) == D3DTEXOPCAPS_MODULATEALPHA_ADDCOLOR; - SupportDot3=(Caps.TextureOpCaps & D3DTEXOPCAPS_DOTPRODUCT3) == D3DTEXOPCAPS_DOTPRODUCT3; - SupportCubemaps=(Caps.TextureCaps & D3DPTEXTURECAPS_CUBEMAP) == D3DPTEXTURECAPS_CUBEMAP; + SupportPointSprites = (caps.MaxPointSize > 1.0f); + SupportNPatches = ((caps.DevCaps&kLegacyNPatches)==kLegacyNPatches); + SupportZBias = ((caps.RasterCaps&kLegacyZBias)==kLegacyZBias); + SupportRangeFog = ((caps.RasterCaps&kLegacyRangeFog)==kLegacyRangeFog); + supportGamma=((caps.Caps2&kLegacyFullscreenGamma)==kLegacyFullscreenGamma); + SupportModAlphaAddClr = (caps.TextureOpCaps & kLegacyModulateAlphaAddColor) == kLegacyModulateAlphaAddColor; + SupportDot3=(caps.TextureOpCaps & kLegacyDotProduct3) == kLegacyDotProduct3; + SupportCubemaps=(caps.TextureCaps & kLegacyCubeMap) == kLegacyCubeMap; SupportAnisotropicFiltering= - (Caps.TextureFilterCaps&D3DPTFILTERCAPS_MAGFANISOTROPIC) && (Caps.TextureFilterCaps&D3DPTFILTERCAPS_MINFANISOTROPIC); + (caps.TextureFilterCaps&kLegacyMagAnisotropic) && (caps.TextureFilterCaps&kLegacyMinAnisotropic); DXLOG(("Hardware T&L support: %s\r\n",SupportTnL ? "Yes" : "No")); DXLOG(("NPatch support: %s\r\n",SupportNPatches ? "Yes" : "No")); @@ -651,20 +715,20 @@ void DX8Caps::Compute_Caps(WW3DFormat display_format, const D3DADAPTER_IDENTIFIE DXLOG(("Dot3 support: %s\r\n",SupportDot3 ? "Yes" : "No")); DXLOG(("Anisotropic filtering support: %s\r\n",SupportAnisotropicFiltering ? "Yes" : "No")); - Check_Texture_Format_Support(display_format,Caps); - Check_Render_To_Texture_Support(display_format,Caps); - Check_Depth_Stencil_Support(display_format,Caps); - Check_Texture_Compression_Support(Caps); - Check_Bumpmap_Support(Caps); - Check_Shader_Support(Caps); + Check_Texture_Format_Support(display_format,&caps); + Check_Render_To_Texture_Support(display_format,&caps); + Check_Depth_Stencil_Support(display_format,&caps); + Check_Texture_Compression_Support(&caps); + Check_Bumpmap_Support(&caps); + Check_Shader_Support(&caps); Check_Driver_Version_Status(); - Check_Maximum_Texture_Support(Caps); + Check_Maximum_Texture_Support(&caps); - MaxTexturesPerPass=Caps.MaxSimultaneousTextures; + MaxTexturesPerPass=caps.MaxSimultaneousTextures; DXLOG(("Max textures per pass: %d\r\n",MaxTexturesPerPass)); - Vendor_Specific_Hacks(adapter_id); + Vendor_Specific_Hacks(&adapter_id); CapsWorkString=""; } @@ -674,10 +738,11 @@ void DX8Caps::Compute_Caps(WW3DFormat display_format, const D3DADAPTER_IDENTIFIE // // ---------------------------------------------------------------------------- -void DX8Caps::Check_Bumpmap_Support(const D3DCAPS8& caps) +void DX8Caps::Check_Bumpmap_Support(const void* caps_ptr) { - SupportBumpEnvmap=!!(caps.TextureOpCaps & D3DTEXOPCAPS_BUMPENVMAP); - SupportBumpEnvmapLuminance=!!(caps.TextureOpCaps & D3DTEXOPCAPS_BUMPENVMAPLUMINANCE); + const LegacyCaps& caps = *static_cast(caps_ptr); + SupportBumpEnvmap=!!(caps.TextureOpCaps & kLegacyBumpEnv); + SupportBumpEnvmapLuminance=!!(caps.TextureOpCaps & kLegacyBumpEnvLuminance); DXLOG(("Bumpmap support: %s\r\n",SupportBumpEnvmap ? "Yes" : "No")); DXLOG(("Bumpmap luminance support: %s\r\n",SupportBumpEnvmapLuminance ? "Yes" : "No")); } @@ -688,8 +753,9 @@ void DX8Caps::Check_Bumpmap_Support(const D3DCAPS8& caps) // // ---------------------------------------------------------------------------- -void DX8Caps::Check_Texture_Compression_Support(const D3DCAPS8& caps) +void DX8Caps::Check_Texture_Compression_Support(const void* caps_ptr) { + (void)caps_ptr; SupportDXTC=SupportTextureFormat[WW3D_FORMAT_DXT1]| SupportTextureFormat[WW3D_FORMAT_DXT2]| SupportTextureFormat[WW3D_FORMAT_DXT3]| @@ -698,15 +764,26 @@ void DX8Caps::Check_Texture_Compression_Support(const D3DCAPS8& caps) DXLOG(("Texture compression support: %s\r\n",SupportDXTC ? "Yes" : "No")); } -void DX8Caps::Check_Texture_Format_Support(WW3DFormat display_format,const D3DCAPS8& caps) +void DX8Caps::Check_Texture_Format_Support(WW3DFormat display_format,const void* caps_ptr) { + const LegacyCaps& caps = *static_cast(caps_ptr); if (display_format==WW3D_FORMAT_UNKNOWN) { for (unsigned i=0;iCheckDeviceFormat( + static_cast(Direct3D)->CheckDeviceFormat( caps.AdapterOrdinal, caps.DeviceType, d3d_display_format, 0, - D3DRTYPE_TEXTURE, + kLegacyTextureResource, WW3DFormat_To_D3DFormat(format))); if (SupportTextureFormat[i]) { StringClass name(0,true); @@ -728,17 +805,30 @@ void DX8Caps::Check_Texture_Format_Support(WW3DFormat display_format,const D3DCA } } } +#endif + (void)caps; } -void DX8Caps::Check_Render_To_Texture_Support(WW3DFormat display_format,const D3DCAPS8& caps) +void DX8Caps::Check_Render_To_Texture_Support(WW3DFormat display_format,const void* caps_ptr) { + const LegacyCaps& caps = *static_cast(caps_ptr); if (display_format==WW3D_FORMAT_UNKNOWN) { for (unsigned i=0;iCheckDeviceFormat( + static_cast(Direct3D)->CheckDeviceFormat( caps.AdapterOrdinal, caps.DeviceType, d3d_display_format, - D3DUSAGE_RENDERTARGET, - D3DRTYPE_TEXTURE, + kLegacyRenderTargetUsage, + kLegacyTextureResource, WW3DFormat_To_D3DFormat(format))); if (SupportRenderToTextureFormat[i]) { StringClass name(0,true); @@ -760,14 +850,17 @@ void DX8Caps::Check_Render_To_Texture_Support(WW3DFormat display_format,const D3 } } } +#endif + (void)caps; } //********************************************************************************************** //! Check Depth Stencil Format Support /*! KJM */ -void DX8Caps::Check_Depth_Stencil_Support(WW3DFormat display_format, const D3DCAPS8& caps) +void DX8Caps::Check_Depth_Stencil_Support(WW3DFormat display_format, const void* caps_ptr) { + const LegacyCaps& caps = *static_cast(caps_ptr); if (display_format==WW3D_FORMAT_UNKNOWN) { for (unsigned i=0;iCheckDeviceFormat + static_cast(Direct3D)->CheckDeviceFormat ( caps.AdapterOrdinal, caps.DeviceType, d3d_display_format, - D3DUSAGE_DEPTHSTENCIL, - D3DRTYPE_TEXTURE, + kLegacyDepthStencilUsage, + kLegacyTextureResource, WW3DZFormat_To_D3DFormat(format) ) ); @@ -809,15 +914,19 @@ void DX8Caps::Check_Depth_Stencil_Support(WW3DFormat display_format, const D3DCA } } } +#endif + (void)caps; } -void DX8Caps::Check_Maximum_Texture_Support(const D3DCAPS8& caps) +void DX8Caps::Check_Maximum_Texture_Support(const void* caps_ptr) { + const LegacyCaps& caps = *static_cast(caps_ptr); MaxSimultaneousTextures=caps.MaxSimultaneousTextures; } -void DX8Caps::Check_Shader_Support(const D3DCAPS8& caps) +void DX8Caps::Check_Shader_Support(const void* caps_ptr) { + const LegacyCaps& caps = *static_cast(caps_ptr); VertexShaderVersion=caps.VertexShaderVersion; PixelShaderVersion=caps.PixelShaderVersion; DXLOG(("Vertex shader version: %d.%d, pixel shader version: %d.%d\r\n", @@ -999,6 +1108,16 @@ bool DX8Caps::Is_Valid_Display_Format(int width, int height, WW3DFormat format) return true; } +unsigned DX8Caps::Get_Max_Texture_Width() const +{ + return Legacy_Caps(Caps).MaxTextureWidth; +} + +unsigned DX8Caps::Get_Max_Texture_Height() const +{ + return Legacy_Caps(Caps).MaxTextureHeight; +} + // ---------------------------------------------------------------------------- // // Implement some vendor-specific hacks to fix certain driver bugs that can't be @@ -1006,8 +1125,9 @@ bool DX8Caps::Is_Valid_Display_Format(int width, int height, WW3DFormat format) // // ---------------------------------------------------------------------------- -void DX8Caps::Vendor_Specific_Hacks(const D3DADAPTER_IDENTIFIER8& adapter_id) +void DX8Caps::Vendor_Specific_Hacks(const void* adapter_id_ptr) { + const LegacyAdapterIdentifier& adapter_id = *static_cast(adapter_id_ptr); if (VendorId==VENDOR_NVIDIA) { if (SupportNPatches) { @@ -1163,9 +1283,8 @@ void DX8Caps::Vendor_Specific_Hacks(const D3DADAPTER_IDENTIFIER8& adapter_id) if (VendorId==VENDOR_VMWARE) { // TheSuperHackers @bugfix Stubbjax 15/01/2026 Disable DOT3 support for VMWare's virtual GPU. - // The D3DTA_ALPHAREPLICATE modifier fails when passed to a D3DTOP_MULTIPLYADD operation. + // The alpha-replicate modifier fails when passed to multiply-add on this legacy driver. DXLOG(("Disabling DOT3 on VMWare\r\n")); SupportDot3 = false; } } - diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8caps.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8caps.h index 9ecde597dd0..83bdb1fa685 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8caps.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8caps.h @@ -41,7 +41,11 @@ #include "WWLib/always.h" #include "ww3dformat.h" +#if defined(GGC_RENDER_BACKEND_BGFX) +#include "dx8standalonetypes.h" +#else #include +#endif class DX8Caps { @@ -205,11 +209,14 @@ class DX8Caps }; - DX8Caps(IDirect3D8* direct3d, const D3DCAPS8& caps,WW3DFormat display_format, const D3DADAPTER_IDENTIFIER8& adapter_id); - DX8Caps(IDirect3D8* direct3d, IDirect3DDevice8* D3DDevice,WW3DFormat display_format, const D3DADAPTER_IDENTIFIER8& adapter_id); + DX8Caps(void* direct3d, const void* caps,WW3DFormat display_format, const void* adapter_id); + DX8Caps(void* direct3d, void* device,WW3DFormat display_format, const void* adapter_id); + ~DX8Caps(); + DX8Caps(const DX8Caps&) = delete; + DX8Caps& operator=(const DX8Caps&) = delete; static void Shutdown(); - void Compute_Caps(WW3DFormat display_format, const D3DADAPTER_IDENTIFIER8& adapter_id); + void Compute_Caps(WW3DFormat display_format, const void* adapter_id); bool Support_TnL() const { return SupportTnL; }; bool Support_DXTC() const { return SupportDXTC; } bool Support_Gamma() const { return supportGamma; } @@ -224,10 +231,13 @@ class DX8Caps bool Support_Cubemaps() const { return SupportCubemaps; } bool Can_Do_Multi_Pass() const { return CanDoMultiPass; } bool Is_Fog_Allowed() const { return IsFogAllowed; } + bool Support_Range_Fog() const { return SupportRangeFog; } bool Is_Valid_Display_Format(int width, int height, WW3DFormat format); int Get_Max_Textures_Per_Pass() const { return MaxTexturesPerPass; } + unsigned Get_Max_Texture_Width() const; + unsigned Get_Max_Texture_Height() const; // ------------------------------------------------------------------------- // @@ -246,7 +256,9 @@ class DX8Caps bool Support_Render_To_Texture_Format(WW3DFormat format) const { return SupportRenderToTextureFormat[format]; } bool Support_Depth_Stencil_Format(WW3DZFormat format) const { return SupportDepthStencilFormat[format]; } - D3DCAPS8 const & Get_DX8_Caps() const { return Caps; } +#if !defined(GGC_RENDER_BACKEND_BGFX) + D3DCAPS8 const & Get_DX8_Caps() const; +#endif const StringClass& Get_Log() const { return CapsLog; } const StringClass& Get_Compact_Log() const { return CompactLog; } @@ -270,21 +282,21 @@ class DX8Caps static DeviceTypeS3 Get_S3_Device(unsigned device_id); static DeviceTypeIntel Get_Intel_Device(unsigned device_id); - void Init_Caps(IDirect3DDevice8* D3DDevice); - void Check_Texture_Format_Support(WW3DFormat display_format,const D3DCAPS8& caps); - void Check_Render_To_Texture_Support(WW3DFormat display_format,const D3DCAPS8& caps); - void Check_Depth_Stencil_Support(WW3DFormat display_format, const D3DCAPS8& caps); - void Check_Texture_Compression_Support(const D3DCAPS8& caps); - void Check_Bumpmap_Support(const D3DCAPS8& caps); - void Check_Shader_Support(const D3DCAPS8& caps); - void Check_Maximum_Texture_Support(const D3DCAPS8& caps); + void Init_Caps(void* device); + void Check_Texture_Format_Support(WW3DFormat display_format,const void* caps); + void Check_Render_To_Texture_Support(WW3DFormat display_format,const void* caps); + void Check_Depth_Stencil_Support(WW3DFormat display_format, const void* caps); + void Check_Texture_Compression_Support(const void* caps); + void Check_Bumpmap_Support(const void* caps); + void Check_Shader_Support(const void* caps); + void Check_Maximum_Texture_Support(const void* caps); void Check_Driver_Version_Status(); - void Vendor_Specific_Hacks(const D3DADAPTER_IDENTIFIER8& adapter_id); + void Vendor_Specific_Hacks(const void* adapter_id); int MaxDisplayWidth; int MaxDisplayHeight; - D3DCAPS8 Caps; + void* Caps; bool SupportTnL; bool SupportDXTC; bool supportGamma; @@ -300,6 +312,7 @@ class DX8Caps bool SupportDot3; bool SupportPointSprites; bool SupportCubemaps; + bool SupportRangeFog; bool CanDoMultiPass; bool IsFogAllowed; int MaxTexturesPerPass; @@ -311,7 +324,7 @@ class DX8Caps DriverVersionStatusType DriverVersionStatus; VendorIdType VendorId; StringClass DriverDLL; - IDirect3D8* Direct3D; // warning XDK name conflict KJM + void* Direct3D; // warning XDK name conflict KJM StringClass CapsLog; StringClass CompactLog; }; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8deviceinterop.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8deviceinterop.h new file mode 100644 index 00000000000..5de69b77818 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8deviceinterop.h @@ -0,0 +1,28 @@ +/* +** 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 + +void DX8_Assert(); +void Log_DX8_ErrorCode(unsigned res); + +#if !defined(GGC_RENDER_BACKEND_BGFX) +#include "d3d8.h" +IDirect3DDevice8* DX8_Call_Device(); +IDirect3D8* DX8_Call_Interface(); +#endif diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8formatconv.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8formatconv.h new file mode 100644 index 00000000000..1f9dcb12665 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8formatconv.h @@ -0,0 +1,140 @@ +/* +** 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 "ww3dformat.h" +#if defined(GGC_RENDER_BACKEND_BGFX) +#include "dx8standalonetypes.h" +#else +#include +#endif + +/* +** Legacy format conversion boundary. Keep this header out of neutral +** renderer-facing code; callers that include it are explicitly translating to +** or from native legacy format values. +*/ + +#ifdef GGC_RENDER_BACKEND_BGFX + +inline D3DFORMAT WW3DFormat_To_D3DFormat(WW3DFormat ww3d_format) +{ + switch (ww3d_format) { + case WW3D_FORMAT_R8G8B8: return D3DFMT_R8G8B8; + case WW3D_FORMAT_A8R8G8B8: return D3DFMT_A8R8G8B8; + case WW3D_FORMAT_X8R8G8B8: return D3DFMT_X8R8G8B8; + case WW3D_FORMAT_R5G6B5: return D3DFMT_R5G6B5; + case WW3D_FORMAT_X1R5G5B5: return D3DFMT_X1R5G5B5; + case WW3D_FORMAT_A1R5G5B5: return D3DFMT_A1R5G5B5; + case WW3D_FORMAT_A4R4G4B4: return D3DFMT_A4R4G4B4; + case WW3D_FORMAT_R3G3B2: return D3DFMT_R3G3B2; + case WW3D_FORMAT_A8: return D3DFMT_A8; + case WW3D_FORMAT_A8R3G3B2: return D3DFMT_A8R3G3B2; + case WW3D_FORMAT_X4R4G4B4: return D3DFMT_X4R4G4B4; + case WW3D_FORMAT_A8P8: return D3DFMT_A8P8; + case WW3D_FORMAT_P8: return D3DFMT_P8; + case WW3D_FORMAT_L8: return D3DFMT_L8; + case WW3D_FORMAT_A8L8: return D3DFMT_A8L8; + case WW3D_FORMAT_A4L4: return D3DFMT_A4L4; + case WW3D_FORMAT_U8V8: return D3DFMT_V8U8; + case WW3D_FORMAT_L6V5U5: return D3DFMT_L6V5U5; + case WW3D_FORMAT_X8L8V8U8: return D3DFMT_X8L8V8U8; + case WW3D_FORMAT_DXT1: return D3DFMT_DXT1; + case WW3D_FORMAT_DXT2: return D3DFMT_DXT2; + case WW3D_FORMAT_DXT3: return D3DFMT_DXT3; + case WW3D_FORMAT_DXT4: return D3DFMT_DXT4; + case WW3D_FORMAT_DXT5: return D3DFMT_DXT5; + default: return D3DFMT_UNKNOWN; + } +} + +inline WW3DFormat D3DFormat_To_WW3DFormat(D3DFORMAT d3d_format) +{ + switch (d3d_format) { + case D3DFMT_R8G8B8: return WW3D_FORMAT_R8G8B8; + case D3DFMT_A8R8G8B8: return WW3D_FORMAT_A8R8G8B8; + case D3DFMT_X8R8G8B8: return WW3D_FORMAT_X8R8G8B8; + case D3DFMT_R5G6B5: return WW3D_FORMAT_R5G6B5; + case D3DFMT_X1R5G5B5: return WW3D_FORMAT_X1R5G5B5; + case D3DFMT_A1R5G5B5: return WW3D_FORMAT_A1R5G5B5; + case D3DFMT_A4R4G4B4: return WW3D_FORMAT_A4R4G4B4; + case D3DFMT_R3G3B2: return WW3D_FORMAT_R3G3B2; + case D3DFMT_A8: return WW3D_FORMAT_A8; + case D3DFMT_A8R3G3B2: return WW3D_FORMAT_A8R3G3B2; + case D3DFMT_X4R4G4B4: return WW3D_FORMAT_X4R4G4B4; + case D3DFMT_A8P8: return WW3D_FORMAT_A8P8; + case D3DFMT_P8: return WW3D_FORMAT_P8; + case D3DFMT_L8: return WW3D_FORMAT_L8; + case D3DFMT_A8L8: return WW3D_FORMAT_A8L8; + case D3DFMT_A4L4: return WW3D_FORMAT_A4L4; + case D3DFMT_V8U8: return WW3D_FORMAT_U8V8; + case D3DFMT_L6V5U5: return WW3D_FORMAT_L6V5U5; + case D3DFMT_X8L8V8U8: return WW3D_FORMAT_X8L8V8U8; + case D3DFMT_DXT1: return WW3D_FORMAT_DXT1; + case D3DFMT_DXT2: return WW3D_FORMAT_DXT2; + case D3DFMT_DXT3: return WW3D_FORMAT_DXT3; + case D3DFMT_DXT4: return WW3D_FORMAT_DXT4; + case D3DFMT_DXT5: return WW3D_FORMAT_DXT5; + default: return WW3D_FORMAT_UNKNOWN; + } +} + +inline D3DFORMAT WW3DZFormat_To_D3DFormat(WW3DZFormat ww3d_zformat) +{ + switch (ww3d_zformat) { + case WW3D_ZFORMAT_D16_LOCKABLE: return D3DFMT_D16_LOCKABLE; + case WW3D_ZFORMAT_D32: return D3DFMT_D32; + case WW3D_ZFORMAT_D15S1: return D3DFMT_D15S1; + case WW3D_ZFORMAT_D24S8: return D3DFMT_D24S8; + case WW3D_ZFORMAT_D16: return D3DFMT_D16; + case WW3D_ZFORMAT_D24X8: return D3DFMT_D24X8; + case WW3D_ZFORMAT_D24X4S4: return D3DFMT_D24X4S4; + default: return D3DFMT_UNKNOWN; + } +} + +inline WW3DZFormat D3DFormat_To_WW3DZFormat(D3DFORMAT d3d_format) +{ + switch (d3d_format) { + case D3DFMT_D16_LOCKABLE: return WW3D_ZFORMAT_D16_LOCKABLE; + case D3DFMT_D32: return WW3D_ZFORMAT_D32; + case D3DFMT_D15S1: return WW3D_ZFORMAT_D15S1; + case D3DFMT_D24S8: return WW3D_ZFORMAT_D24S8; + case D3DFMT_D16: return WW3D_ZFORMAT_D16; + case D3DFMT_D24X8: return WW3D_ZFORMAT_D24X8; + case D3DFMT_D24X4S4: return WW3D_ZFORMAT_D24X4S4; + default: return WW3D_ZFORMAT_UNKNOWN; + } +} + +inline void Init_D3D_To_WW3_Conversion() +{ +} + +#else + +D3DFORMAT WW3DFormat_To_D3DFormat(WW3DFormat ww3d_format); +WW3DFormat D3DFormat_To_WW3DFormat(D3DFORMAT d3d_format); + +D3DFORMAT WW3DZFormat_To_D3DFormat(WW3DZFormat ww3d_zformat); +WW3DZFormat D3DFormat_To_WW3DZFormat(D3DFORMAT d3d_format); + +void Init_D3D_To_WW3_Conversion(); + +#endif diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8fvf.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8fvf.cpp index a96d99c130b..c34c31659f0 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8fvf.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8fvf.cpp @@ -41,11 +41,69 @@ #include "dx8fvf.h" #include "WWLib/wwstring.h" -#include + +static unsigned Get_FVF_Texcoord_Component_Count(unsigned FVF, unsigned coord_index) +{ + const unsigned format = (FVF >> (coord_index * 2 + 16)) & 0x3; + + switch (format) { + case 3: + return 1; + case 0: + return 2; + case 1: + return 3; + case 2: + return 4; + default: + return 2; + } +} + +static unsigned Get_FVF_Position_Size(unsigned FVF) +{ + switch (FVF & DX8_FVF_POSITION_MASK) { + case DX8_FVF_FLAG_XYZ: + return 3 * sizeof(float); + case DX8_FVF_FLAG_XYZRHW: + return 4 * sizeof(float); + case DX8_FVF_FLAG_XYZB1: + return 4 * sizeof(float); + case DX8_FVF_FLAG_XYZB2: + return 5 * sizeof(float); + case DX8_FVF_FLAG_XYZB3: + return 6 * sizeof(float); + case DX8_FVF_FLAG_XYZB4: + return ((FVF & DX8_FVF_LASTBETA_UBYTE4) == DX8_FVF_LASTBETA_UBYTE4) + ? 6 * sizeof(float) + sizeof(unsigned) + : 7 * sizeof(float); + case DX8_FVF_FLAG_XYZB5: + return 8 * sizeof(float); + default: + return 0; + } +} static unsigned Get_FVF_Vertex_Size(unsigned FVF) { - return D3DXGetFVFVertexSize(FVF); + unsigned size = Get_FVF_Position_Size(FVF); + + if ((FVF & DX8_FVF_FLAG_NORMAL) == DX8_FVF_FLAG_NORMAL) { + size += 3 * sizeof(float); + } + if ((FVF & DX8_FVF_FLAG_DIFFUSE) == DX8_FVF_FLAG_DIFFUSE) { + size += sizeof(unsigned); + } + if ((FVF & DX8_FVF_FLAG_SPECULAR) == DX8_FVF_FLAG_SPECULAR) { + size += sizeof(unsigned); + } + + const unsigned tex_count = (FVF & DX8_FVF_TEXCOUNT_MASK) >> DX8_FVF_TEXCOUNT_SHIFT; + for (unsigned i = 0; i < tex_count && i < DX8_FVF_MAX_TEXCOORD; ++i) { + size += Get_FVF_Texcoord_Component_Count(FVF, i) * sizeof(float); + } + + return size; } FVFInfoClass::FVFInfoClass(unsigned FVF_) @@ -56,48 +114,85 @@ FVFInfoClass::FVFInfoClass(unsigned FVF_) location_offset=0; blend_offset=location_offset; - if ((FVF&D3DFVF_XYZ)==D3DFVF_XYZ) blend_offset+=3*sizeof(float); + if ((FVF&DX8_FVF_FLAG_XYZ)==DX8_FVF_FLAG_XYZ) blend_offset+=3*sizeof(float); normal_offset=blend_offset; - if ( ((FVF&D3DFVF_XYZB4)==D3DFVF_XYZB4) && - ((FVF&D3DFVF_LASTBETA_UBYTE4)==D3DFVF_LASTBETA_UBYTE4) ) normal_offset+=3*sizeof(float)+sizeof(DWORD); + if ( ((FVF&DX8_FVF_FLAG_XYZB4)==DX8_FVF_FLAG_XYZB4) && + ((FVF&DX8_FVF_LASTBETA_UBYTE4)==DX8_FVF_LASTBETA_UBYTE4) ) normal_offset+=3*sizeof(float)+sizeof(unsigned); diffuse_offset=normal_offset; - if ((FVF&D3DFVF_NORMAL)==D3DFVF_NORMAL) diffuse_offset+=3*sizeof(float); + if ((FVF&DX8_FVF_FLAG_NORMAL)==DX8_FVF_FLAG_NORMAL) diffuse_offset+=3*sizeof(float); specular_offset=diffuse_offset; - if ((FVF&D3DFVF_DIFFUSE)==D3DFVF_DIFFUSE) specular_offset+=sizeof(DWORD); + if ((FVF&DX8_FVF_FLAG_DIFFUSE)==DX8_FVF_FLAG_DIFFUSE) specular_offset+=sizeof(unsigned); texcoord_offset[0]=specular_offset; - if ((FVF&D3DFVF_SPECULAR)==D3DFVF_SPECULAR) texcoord_offset[0]+=sizeof(DWORD); + if ((FVF&DX8_FVF_FLAG_SPECULAR)==DX8_FVF_FLAG_SPECULAR) texcoord_offset[0]+=sizeof(unsigned); - for (unsigned int i=1; i> DX8_FVF_TEXCOUNT_SHIFT; +} + +bool FVFInfoClass::Has_Normal() const +{ + return (FVF & DX8_FVF_FLAG_NORMAL) == DX8_FVF_FLAG_NORMAL; +} + +bool FVFInfoClass::Has_Diffuse() const +{ + return (FVF & DX8_FVF_FLAG_DIFFUSE) == DX8_FVF_FLAG_DIFFUSE; +} + +bool FVFInfoClass::Has_Specular() const +{ + return (FVF & DX8_FVF_FLAG_SPECULAR) == DX8_FVF_FLAG_SPECULAR; +} + +unsigned FVFInfoClass::Build_FVF(bool has_normal, bool has_diffuse, bool has_specular, unsigned tex_coord_count) +{ + unsigned fvf = DX8_FVF_FLAG_XYZ; + + if (has_normal) { + fvf |= DX8_FVF_FLAG_NORMAL; + } + if (has_diffuse) { + fvf |= DX8_FVF_FLAG_DIFFUSE; } + if (has_specular) { + fvf |= DX8_FVF_FLAG_SPECULAR; + } + + if (tex_coord_count <= DX8_FVF_MAX_TEXCOORD) { + fvf |= tex_coord_count << DX8_FVF_TEXCOUNT_SHIFT; + } + + return fvf; } void FVFInfoClass::Get_FVF_Name(StringClass& fvfname) const { switch (Get_FVF()) { - case DX8_FVF_XYZ: fvfname="D3DFVF_XYZ"; break; - case DX8_FVF_XYZN: fvfname="D3DFVF_XYZ|D3DFVF_NORMAL"; break; - case DX8_FVF_XYZNUV1: fvfname="D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1"; break; - case DX8_FVF_XYZNUV2: fvfname="D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX2"; break; - case DX8_FVF_XYZNDUV1: fvfname="D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1|D3DFVF_DIFFUSE"; break; - case DX8_FVF_XYZNDUV2: fvfname="D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX2|D3DFVF_DIFFUSE"; break; - case DX8_FVF_XYZDUV1: fvfname="D3DFVF_XYZ|D3DFVF_TEX1|D3DFVF_DIFFUSE"; break; - case DX8_FVF_XYZDUV2: fvfname="D3DFVF_XYZ|D3DFVF_TEX2|D3DFVF_DIFFUSE"; break; - case DX8_FVF_XYZUV1: fvfname="D3DFVF_XYZ|D3DFVF_TEX1"; break; - case DX8_FVF_XYZUV2: fvfname="D3DFVF_XYZ|D3DFVF_TEX2"; break; - case DX8_FVF_XYZNDUV1TG3 : fvfname="(D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_DIFFUSE|D3DFVF_TEX4|D3DFVF_TEXCOORDSIZE2(0)|D3DFVF_TEXCOORDSIZE3(1)|D3DFVF_TEXCOORDSIZE3(2)|D3DFVF_TEXCOORDSIZE3(3))"; break; - case DX8_FVF_XYZNUV2DMAP : fvfname="(D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX3|D3DFVF_TEXCOORDSIZE1(0)|D3DFVF_TEXCOORDSIZE4(1)|D3DFVF_TEXCOORDSIZE2(2))"; break; - case DX8_FVF_XYZNDCUBEMAP : fvfname="(D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_DIFFUSE|D3DFVF_TEX1|D3DFVFTEXCOORDSIZE3(0)"; break; + case DX8_FVF_XYZ: fvfname="FVF_XYZ"; break; + case DX8_FVF_XYZN: fvfname="FVF_XYZ|NORMAL"; break; + case DX8_FVF_XYZNUV1: fvfname="FVF_XYZ|NORMAL|TEX1"; break; + case DX8_FVF_XYZNUV2: fvfname="FVF_XYZ|NORMAL|TEX2"; break; + case DX8_FVF_XYZNDUV1: fvfname="FVF_XYZ|NORMAL|TEX1|DIFFUSE"; break; + case DX8_FVF_XYZNDUV2: fvfname="FVF_XYZ|NORMAL|TEX2|DIFFUSE"; break; + case DX8_FVF_XYZDUV1: fvfname="FVF_XYZ|TEX1|DIFFUSE"; break; + case DX8_FVF_XYZDUV2: fvfname="FVF_XYZ|TEX2|DIFFUSE"; break; + case DX8_FVF_XYZUV1: fvfname="FVF_XYZ|TEX1"; break; + case DX8_FVF_XYZUV2: fvfname="FVF_XYZ|TEX2"; break; + case DX8_FVF_XYZNDUV1TG3 : fvfname="FVF_XYZ|NORMAL|DIFFUSE|TEX4|TEXCOORDSIZE2(0)|TEXCOORDSIZE3(1)|TEXCOORDSIZE3(2)|TEXCOORDSIZE3(3)"; break; + case DX8_FVF_XYZNUV2DMAP : fvfname="FVF_XYZ|NORMAL|TEX3|TEXCOORDSIZE1(0)|TEXCOORDSIZE4(1)|TEXCOORDSIZE2(2)"; break; + case DX8_FVF_XYZNDCUBEMAP : fvfname="FVF_XYZ|NORMAL|DIFFUSE"; break; default: fvfname="Unknown!"; } } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8fvf.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8fvf.h index d4abde6f0fa..7d99ac64684 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8fvf.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8fvf.h @@ -42,29 +42,92 @@ #pragma once #include "WWLib/always.h" -#include #ifdef WWDEBUG #include "WWDebug/wwdebug.h" #endif class StringClass; +static constexpr unsigned DX8_FVF_MAX_TEXCOORD = 8; +static constexpr unsigned DX8_FVF_POSITION_MASK = 0x00e; +static constexpr unsigned DX8_FVF_FLAG_XYZ = 0x002; +static constexpr unsigned DX8_FVF_FLAG_XYZRHW = 0x004; +static constexpr unsigned DX8_FVF_FLAG_XYZB1 = 0x006; +static constexpr unsigned DX8_FVF_FLAG_XYZB2 = 0x008; +static constexpr unsigned DX8_FVF_FLAG_XYZB3 = 0x00a; +static constexpr unsigned DX8_FVF_FLAG_XYZB4 = 0x00c; +static constexpr unsigned DX8_FVF_FLAG_XYZB5 = 0x00e; +static constexpr unsigned DX8_FVF_FLAG_NORMAL = 0x010; +static constexpr unsigned DX8_FVF_FLAG_DIFFUSE = 0x040; +static constexpr unsigned DX8_FVF_FLAG_SPECULAR = 0x080; +static constexpr unsigned DX8_FVF_TEXCOUNT_MASK = 0xf00; +static constexpr unsigned DX8_FVF_TEXCOUNT_SHIFT = 8; +static constexpr unsigned DX8_FVF_TEX0 = 0u << DX8_FVF_TEXCOUNT_SHIFT; +static constexpr unsigned DX8_FVF_TEX1 = 1u << DX8_FVF_TEXCOUNT_SHIFT; +static constexpr unsigned DX8_FVF_TEX2 = 2u << DX8_FVF_TEXCOUNT_SHIFT; +static constexpr unsigned DX8_FVF_TEX3 = 3u << DX8_FVF_TEXCOUNT_SHIFT; +static constexpr unsigned DX8_FVF_TEX4 = 4u << DX8_FVF_TEXCOUNT_SHIFT; +static constexpr unsigned DX8_FVF_TEX5 = 5u << DX8_FVF_TEXCOUNT_SHIFT; +static constexpr unsigned DX8_FVF_TEX6 = 6u << DX8_FVF_TEXCOUNT_SHIFT; +static constexpr unsigned DX8_FVF_TEX7 = 7u << DX8_FVF_TEXCOUNT_SHIFT; +static constexpr unsigned DX8_FVF_TEX8 = 8u << DX8_FVF_TEXCOUNT_SHIFT; +static constexpr unsigned DX8_FVF_LASTBETA_UBYTE4 = 0x1000; + +static constexpr unsigned DX8_FVF_TEXCOORDSIZE1(unsigned coord_index) +{ + return 3u << (coord_index * 2 + 16); +} + +static constexpr unsigned DX8_FVF_TEXCOORDSIZE2(unsigned) +{ + return 0u; +} + +static constexpr unsigned DX8_FVF_TEXCOORDSIZE3(unsigned coord_index) +{ + return 1u << (coord_index * 2 + 16); +} + +static constexpr unsigned DX8_FVF_TEXCOORDSIZE4(unsigned coord_index) +{ + return 2u << (coord_index * 2 + 16); +} + enum { - DX8_FVF_XYZ = D3DFVF_XYZ, - DX8_FVF_XYZN = D3DFVF_XYZ|D3DFVF_NORMAL, - DX8_FVF_XYZNUV1 = D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1, - DX8_FVF_XYZNUV2 = D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX2, - DX8_FVF_XYZNDUV1 = D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1|D3DFVF_DIFFUSE, - DX8_FVF_XYZNDUV2 = D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX2|D3DFVF_DIFFUSE, - DX8_FVF_XYZDUV1 = D3DFVF_XYZ|D3DFVF_TEX1|D3DFVF_DIFFUSE, - DX8_FVF_XYZDUV2 = D3DFVF_XYZ|D3DFVF_TEX2|D3DFVF_DIFFUSE, - DX8_FVF_XYZUV1 = D3DFVF_XYZ|D3DFVF_TEX1, - DX8_FVF_XYZUV2 = D3DFVF_XYZ|D3DFVF_TEX2, - DX8_FVF_XYZNDUV1TG3 = (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_DIFFUSE|D3DFVF_TEX4|D3DFVF_TEXCOORDSIZE2(0)|D3DFVF_TEXCOORDSIZE3(1)|D3DFVF_TEXCOORDSIZE3(2)|D3DFVF_TEXCOORDSIZE3(3)), - DX8_FVF_XYZNUV2DMAP = (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX3 | D3DFVF_TEXCOORDSIZE1(0) | D3DFVF_TEXCOORDSIZE4(1) | D3DFVF_TEXCOORDSIZE2(2) ), - DX8_FVF_XYZNDCUBEMAP = D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_DIFFUSE //|D3DFVF_TEX1|D3DFVF_TEXCOORDSIZE3(0) + DX8_FVF_XYZ = DX8_FVF_FLAG_XYZ, + DX8_FVF_XYZN = DX8_FVF_FLAG_XYZ|DX8_FVF_FLAG_NORMAL, + DX8_FVF_XYZNUV1 = DX8_FVF_FLAG_XYZ|DX8_FVF_FLAG_NORMAL|DX8_FVF_TEX1, + DX8_FVF_XYZNUV2 = DX8_FVF_FLAG_XYZ|DX8_FVF_FLAG_NORMAL|DX8_FVF_TEX2, + DX8_FVF_XYZNDUV1 = DX8_FVF_FLAG_XYZ|DX8_FVF_FLAG_NORMAL|DX8_FVF_TEX1|DX8_FVF_FLAG_DIFFUSE, + DX8_FVF_XYZNDUV2 = DX8_FVF_FLAG_XYZ|DX8_FVF_FLAG_NORMAL|DX8_FVF_TEX2|DX8_FVF_FLAG_DIFFUSE, + DX8_FVF_XYZDUV1 = DX8_FVF_FLAG_XYZ|DX8_FVF_TEX1|DX8_FVF_FLAG_DIFFUSE, + DX8_FVF_XYZDUV2 = DX8_FVF_FLAG_XYZ|DX8_FVF_TEX2|DX8_FVF_FLAG_DIFFUSE, + DX8_FVF_XYZUV1 = DX8_FVF_FLAG_XYZ|DX8_FVF_TEX1, + DX8_FVF_XYZUV2 = DX8_FVF_FLAG_XYZ|DX8_FVF_TEX2, + DX8_FVF_XYZNDUV1TG3 = (DX8_FVF_FLAG_XYZ|DX8_FVF_FLAG_NORMAL|DX8_FVF_FLAG_DIFFUSE|DX8_FVF_TEX4|DX8_FVF_TEXCOORDSIZE2(0)|DX8_FVF_TEXCOORDSIZE3(1)|DX8_FVF_TEXCOORDSIZE3(2)|DX8_FVF_TEXCOORDSIZE3(3)), + DX8_FVF_XYZNUV2DMAP = (DX8_FVF_FLAG_XYZ|DX8_FVF_FLAG_NORMAL|DX8_FVF_TEX3 | DX8_FVF_TEXCOORDSIZE1(0) | DX8_FVF_TEXCOORDSIZE4(1) | DX8_FVF_TEXCOORDSIZE2(2) ), + DX8_FVF_XYZNDCUBEMAP = DX8_FVF_FLAG_XYZ|DX8_FVF_FLAG_NORMAL|DX8_FVF_FLAG_DIFFUSE }; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZ = DX8_FVF_XYZ; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZD = DX8_FVF_FLAG_XYZ|DX8_FVF_FLAG_DIFFUSE; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZUV1 = DX8_FVF_XYZUV1; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZUV2 = DX8_FVF_XYZUV2; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZDUV1 = DX8_FVF_XYZDUV1; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZDUV2 = DX8_FVF_XYZDUV2; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZN = DX8_FVF_XYZN; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZND = DX8_FVF_FLAG_XYZ|DX8_FVF_FLAG_NORMAL|DX8_FVF_FLAG_DIFFUSE; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZNUV1 = DX8_FVF_XYZNUV1; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZNUV2 = DX8_FVF_XYZNUV2; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZNDUV1 = DX8_FVF_XYZNDUV1; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZNDUV2 = DX8_FVF_XYZNDUV2; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZRHW = DX8_FVF_FLAG_XYZRHW; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZRHWD = DX8_FVF_FLAG_XYZRHW|DX8_FVF_FLAG_DIFFUSE; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZRHWUV1 = DX8_FVF_FLAG_XYZRHW|DX8_FVF_TEX1; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZRHWUV2 = DX8_FVF_FLAG_XYZRHW|DX8_FVF_TEX2; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZRHWDUV1 = DX8_FVF_FLAG_XYZRHW|DX8_FVF_FLAG_DIFFUSE|DX8_FVF_TEX1; +static constexpr unsigned RENDER_VERTEX_FORMAT_XYZRHWDUV2 = DX8_FVF_FLAG_XYZRHW|DX8_FVF_FLAG_DIFFUSE|DX8_FVF_TEX2; + // ---------------------------------------------------------------------------- // // Util structures for vertex buffer handling. Cast the void pointer returned @@ -254,7 +317,7 @@ class FVFInfoClass unsigned location_offset; unsigned normal_offset; unsigned blend_offset; - unsigned texcoord_offset[D3DDP_MAXTEXCOORD]; + unsigned texcoord_offset[DX8_FVF_MAX_TEXCOORD]; unsigned diffuse_offset; unsigned specular_offset; public: @@ -263,7 +326,7 @@ class FVFInfoClass unsigned Get_Location_Offset() const { return location_offset; } unsigned Get_Normal_Offset() const { return normal_offset; } #ifdef WWDEBUG - inline unsigned Get_Tex_Offset(unsigned int n) const { WWASSERT(n. -*/ - -/*********************************************************************************************** - *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** - *********************************************************************************************** - * * - * Project Name : ww3d * - * * - * $Archive:: /Commando/Code/ww3d2/dx8indexbuffer.cpp $* - * * - * Original Author:: Jani Penttinen * - * * - * $Author:: Jani_p $* - * * - * $Modtime:: 11/09/01 3:12p $* - * * - * $Revision:: 26 $* - * * - *---------------------------------------------------------------------------------------------* - * Functions: * - * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - -//#define INDEX_BUFFER_LOG - -#include "dx8indexbuffer.h" -#include "dx8wrapper.h" -#include "dx8caps.h" -#include "WWMath/sphere.h" -#include "WWLib/thread.h" -#include "WWDebug/wwmemlog.h" - -#define DEFAULT_IB_SIZE 5000 - -static bool _DynamicSortingIndexArrayInUse=false; -static SortingIndexBufferClass* _DynamicSortingIndexArray; -static unsigned short _DynamicSortingIndexArraySize=0; -static unsigned short _DynamicSortingIndexArrayOffset=0; - -static bool _DynamicDX8IndexBufferInUse=false; -static DX8IndexBufferClass* _DynamicDX8IndexBuffer=nullptr; -static unsigned short _DynamicDX8IndexBufferSize=DEFAULT_IB_SIZE; -static unsigned short _DynamicDX8IndexBufferOffset=0; - -static int _IndexBufferCount; -static int _IndexBufferTotalIndices; -static int _IndexBufferTotalSize; - -// ---------------------------------------------------------------------------- -// -// -// -// ---------------------------------------------------------------------------- - -IndexBufferClass::IndexBufferClass(unsigned type_, unsigned short index_count_) - : - index_count(index_count_), - type(type_), - engine_refs(0) -{ - WWASSERT(type==BUFFER_TYPE_DX8 || type==BUFFER_TYPE_SORTING); - WWASSERT(index_count); - - _IndexBufferCount++; - _IndexBufferTotalIndices+=index_count; - _IndexBufferTotalSize+=index_count*sizeof(unsigned short); -#ifdef VERTEX_BUFFER_LOG - WWDEBUG_SAY(("New IB, %d indices, size %d bytes",index_count,index_count*sizeof(unsigned short))); - WWDEBUG_SAY(("Total IB count: %d, total %d indices, total size %d bytes", - _IndexBufferCount, - _IndexBufferTotalIndices, - _IndexBufferTotalSize)); -#endif -} - -IndexBufferClass::~IndexBufferClass() -{ - _IndexBufferCount--; - _IndexBufferTotalIndices-=index_count; - _IndexBufferTotalSize-=index_count*sizeof(unsigned short); -#ifdef VERTEX_BUFFER_LOG - WWDEBUG_SAY(("Delete IB, %d indices, size %d bytes",index_count,index_count*sizeof(unsigned short))); - WWDEBUG_SAY(("Total IB count: %d, total %d indices, total size %d bytes", - _IndexBufferCount, - _IndexBufferTotalIndices, - _IndexBufferTotalSize)); -#endif -} - -unsigned IndexBufferClass::Get_Total_Buffer_Count() -{ - return _IndexBufferCount; -} - -unsigned IndexBufferClass::Get_Total_Allocated_Indices() -{ - return _IndexBufferTotalIndices; -} - -unsigned IndexBufferClass::Get_Total_Allocated_Memory() -{ - return _IndexBufferTotalSize; -} - -void IndexBufferClass::Add_Engine_Ref() const -{ - engine_refs++; -} - -void IndexBufferClass::Release_Engine_Ref() const -{ - engine_refs--; - WWASSERT(engine_refs>=0); -} - -// ---------------------------------------------------------------------------- -// -// -// -// ---------------------------------------------------------------------------- - -void IndexBufferClass::Copy(unsigned int* indices,unsigned first_index,unsigned count) -{ - WWASSERT(indices); - - if (first_index) { - DX8IndexBufferClass::AppendLockClass l(this,first_index,count); - unsigned short* inds=l.Get_Index_Array(); - for (unsigned v=0;vEngine_Refs()); - index_buffer->Add_Ref(); - switch (index_buffer->Type()) { - case BUFFER_TYPE_DX8: - DX8_Assert(); - DX8_ErrorCode(static_cast(index_buffer)->Get_DX8_Index_Buffer()->Lock( - 0, - index_buffer->Get_Index_Count()*sizeof(WORD), - (unsigned char**)&indices, - flags)); - break; - case BUFFER_TYPE_SORTING: - indices=static_cast(index_buffer)->index_buffer; - break; - default: - WWASSERT(0); - break; - } -} - -// ---------------------------------------------------------------------------- -// -// -// ---------------------------------------------------------------------------- - -IndexBufferClass::WriteLockClass::~WriteLockClass() -{ - DX8_THREAD_ASSERT(); - switch (index_buffer->Type()) { - case BUFFER_TYPE_DX8: - DX8_Assert(); - DX8_ErrorCode(static_cast(index_buffer)->index_buffer->Unlock()); - break; - case BUFFER_TYPE_SORTING: - break; - default: - WWASSERT(0); - break; - } - index_buffer->Release_Ref(); -} - -// ---------------------------------------------------------------------------- - -IndexBufferClass::AppendLockClass::AppendLockClass(IndexBufferClass* index_buffer_,unsigned start_index, unsigned index_range) - : - index_buffer(index_buffer_) -{ - DX8_THREAD_ASSERT(); - WWASSERT(start_index+index_range<=index_buffer->Get_Index_Count()); - WWASSERT(index_buffer); - WWASSERT(!index_buffer->Engine_Refs()); - index_buffer->Add_Ref(); - switch (index_buffer->Type()) { - case BUFFER_TYPE_DX8: - DX8_Assert(); - DX8_ErrorCode(static_cast(index_buffer)->index_buffer->Lock( - start_index*sizeof(unsigned short), - index_range*sizeof(unsigned short), - (unsigned char**)&indices, - 0)); - break; - case BUFFER_TYPE_SORTING: - indices=static_cast(index_buffer)->index_buffer+start_index; - break; - default: - WWASSERT(0); - break; - } -} - -// ---------------------------------------------------------------------------- - -IndexBufferClass::AppendLockClass::~AppendLockClass() -{ - DX8_THREAD_ASSERT(); - switch (index_buffer->Type()) { - case BUFFER_TYPE_DX8: - DX8_Assert(); - DX8_ErrorCode(static_cast(index_buffer)->index_buffer->Unlock()); - break; - case BUFFER_TYPE_SORTING: - break; - default: - WWASSERT(0); - break; - } - index_buffer->Release_Ref(); -} - -// ---------------------------------------------------------------------------- -// -// -// -// ---------------------------------------------------------------------------- - -DX8IndexBufferClass::DX8IndexBufferClass(unsigned short index_count_,UsageType usage) - : - IndexBufferClass(BUFFER_TYPE_DX8,index_count_) -{ - DX8_THREAD_ASSERT(); - WWASSERT(index_count); - unsigned usage_flags= - D3DUSAGE_WRITEONLY| - ((usage&USAGE_DYNAMIC) ? D3DUSAGE_DYNAMIC : 0)| - ((usage&USAGE_NPATCHES) ? D3DUSAGE_NPATCHES : 0)| - ((usage&USAGE_SOFTWAREPROCESSING) ? D3DUSAGE_SOFTWAREPROCESSING : 0); - if (!DX8Wrapper::Get_Current_Caps()->Support_TnL()) { - usage_flags|=D3DUSAGE_SOFTWAREPROCESSING; - } - - HRESULT ret=DX8Wrapper::_Get_D3D_Device8()->CreateIndexBuffer( - sizeof(WORD)*index_count, - usage_flags, - D3DFMT_INDEX16, - (usage&USAGE_DYNAMIC) ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED, - &index_buffer); - - if (SUCCEEDED(ret)) { - return; - } - - WWDEBUG_SAY(("Index buffer creation failed, trying to release assets...")); - - // Index buffer creation failed, so try releasing least used textures and flushing the mesh cache. - - // Free all textures that haven't been used in the last 5 seconds - TextureClass::Invalidate_Old_Unused_Textures(5000); - - // Invalidate the mesh cache - WW3D::_Invalidate_Mesh_Cache(); - - // Try again... - ret=DX8Wrapper::_Get_D3D_Device8()->CreateIndexBuffer( - sizeof(WORD)*index_count, - usage_flags, - D3DFMT_INDEX16, - (usage&USAGE_DYNAMIC) ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED, - &index_buffer); - - if (SUCCEEDED(ret)) { - WWDEBUG_SAY(("...Index buffer creation successful")); - } - - // If it still fails it is fatal - DX8_ErrorCode(ret); -} - -// ---------------------------------------------------------------------------- - -DX8IndexBufferClass::~DX8IndexBufferClass() -{ - index_buffer->Release(); -} - -// ---------------------------------------------------------------------------- -// -// -// -// ---------------------------------------------------------------------------- - -SortingIndexBufferClass::SortingIndexBufferClass(unsigned short index_count_) - : - IndexBufferClass(BUFFER_TYPE_SORTING,index_count_) -{ - WWMEMLOG(MEM_RENDERER); - WWASSERT(index_count); - - index_buffer=W3DNEWARRAY unsigned short[index_count]; -} - -// ---------------------------------------------------------------------------- - -SortingIndexBufferClass::~SortingIndexBufferClass() -{ - delete[] index_buffer; -} - -// ---------------------------------------------------------------------------- -// -// -// -// ---------------------------------------------------------------------------- - -DynamicIBAccessClass::DynamicIBAccessClass(unsigned short type_, unsigned short index_count_) - : - IndexCount(index_count_), - IndexBuffer(nullptr), - Type(type_) -{ - WWASSERT(Type==BUFFER_TYPE_DYNAMIC_DX8 || Type==BUFFER_TYPE_DYNAMIC_SORTING); - if (Type==BUFFER_TYPE_DYNAMIC_DX8) { - Allocate_DX8_Dynamic_Buffer(); - } - else { - Allocate_Sorting_Dynamic_Buffer(); - } -} - -DynamicIBAccessClass::~DynamicIBAccessClass() -{ - REF_PTR_RELEASE(IndexBuffer); - if (Type==BUFFER_TYPE_DYNAMIC_DX8) { - _DynamicDX8IndexBufferInUse=false; - _DynamicDX8IndexBufferOffset+=IndexCount; - } - else { - _DynamicSortingIndexArrayInUse=false; - _DynamicSortingIndexArrayOffset+=IndexCount; - } -} - -void DynamicIBAccessClass::_Deinit() -{ - WWASSERT ((_DynamicDX8IndexBuffer == nullptr) || (_DynamicDX8IndexBuffer->Num_Refs() == 1)); - REF_PTR_RELEASE(_DynamicDX8IndexBuffer); - _DynamicDX8IndexBufferInUse=false; - _DynamicDX8IndexBufferSize=DEFAULT_IB_SIZE; - _DynamicDX8IndexBufferOffset=0; - - WWASSERT ((_DynamicSortingIndexArray == nullptr) || (_DynamicSortingIndexArray->Num_Refs() == 1)); - REF_PTR_RELEASE(_DynamicSortingIndexArray); - _DynamicSortingIndexArrayInUse=false; - _DynamicSortingIndexArraySize=0; - _DynamicSortingIndexArrayOffset=0; -} - -// ---------------------------------------------------------------------------- -// -// -// -// ---------------------------------------------------------------------------- - -DynamicIBAccessClass::WriteLockClass::WriteLockClass(DynamicIBAccessClass* ib_access_) - : - DynamicIBAccess(ib_access_) -{ - DX8_THREAD_ASSERT(); - DynamicIBAccess->IndexBuffer->Add_Ref(); - switch (DynamicIBAccess->Get_Type()) { - case BUFFER_TYPE_DYNAMIC_DX8: - WWASSERT(DynamicIBAccess); -// WWASSERT(!dynamic_dx8_index_buffer->Engine_Refs()); - DX8_Assert(); - DX8_ErrorCode( - static_cast(DynamicIBAccess->IndexBuffer)->Get_DX8_Index_Buffer()->Lock( - DynamicIBAccess->IndexBufferOffset*sizeof(WORD), - DynamicIBAccess->Get_Index_Count()*sizeof(WORD), - (unsigned char**)&Indices, - !DynamicIBAccess->IndexBufferOffset ? D3DLOCK_DISCARD : D3DLOCK_NOOVERWRITE)); - break; - case BUFFER_TYPE_DYNAMIC_SORTING: - Indices=static_cast(DynamicIBAccess->IndexBuffer)->index_buffer; - Indices+=DynamicIBAccess->IndexBufferOffset; - break; - default: - WWASSERT(0); - break; - } -} - -DynamicIBAccessClass::WriteLockClass::~WriteLockClass() -{ - DX8_THREAD_ASSERT(); - switch (DynamicIBAccess->Get_Type()) { - case BUFFER_TYPE_DYNAMIC_DX8: - DX8_Assert(); - DX8_ErrorCode(static_cast(DynamicIBAccess->IndexBuffer)->Get_DX8_Index_Buffer()->Unlock()); - break; - case BUFFER_TYPE_DYNAMIC_SORTING: - break; - default: - WWASSERT(0); - break; - } - DynamicIBAccess->IndexBuffer->Release_Ref(); -} - -// ---------------------------------------------------------------------------- -// -// -// -// ---------------------------------------------------------------------------- - -void DynamicIBAccessClass::Allocate_DX8_Dynamic_Buffer() -{ - WWMEMLOG(MEM_RENDERER); - WWASSERT(!_DynamicDX8IndexBufferInUse); - _DynamicDX8IndexBufferInUse=true; - - // If requesting more indices than dynamic index buffer can fit, delete the ib - // and adjust the size to the new count. - if (IndexCount>_DynamicDX8IndexBufferSize) { - REF_PTR_RELEASE(_DynamicDX8IndexBuffer); - _DynamicDX8IndexBufferSize=IndexCount; - if (_DynamicDX8IndexBufferSizeSupport_NPatches()) { - usage|=DX8IndexBufferClass::USAGE_NPATCHES; - } - - _DynamicDX8IndexBuffer=NEW_REF(DX8IndexBufferClass,( - _DynamicDX8IndexBufferSize, - (DX8IndexBufferClass::UsageType)usage)); - _DynamicDX8IndexBufferOffset=0; - } - - // Any room at the end of the buffer? - if (((unsigned)IndexCount+_DynamicDX8IndexBufferOffset)>_DynamicDX8IndexBufferSize) { - _DynamicDX8IndexBufferOffset=0; - } - - REF_PTR_SET(IndexBuffer,_DynamicDX8IndexBuffer); - IndexBufferOffset=_DynamicDX8IndexBufferOffset; -} - -void DynamicIBAccessClass::Allocate_Sorting_Dynamic_Buffer() -{ - WWMEMLOG(MEM_RENDERER); - WWASSERT(!_DynamicSortingIndexArrayInUse); - _DynamicSortingIndexArrayInUse=true; - - unsigned new_index_count=_DynamicSortingIndexArrayOffset+IndexCount; - WWASSERT(new_index_count<65536); - if (new_index_count>_DynamicSortingIndexArraySize) { - REF_PTR_RELEASE(_DynamicSortingIndexArray); - _DynamicSortingIndexArraySize=new_index_count; - if (_DynamicSortingIndexArraySizeHas_Shader_Pipeline() + && g_renderBackend->Submit_Rigid_Packet( + base_vertex_offset, + index_offset, + strip ? index_count-2 : index_count/3, + min_vertex_index, + vertex_index_range, + strip)) + { + return; + } 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( + g_renderBackend->Draw_Strip( index_offset, index_count-2, min_vertex_index, @@ -125,7 +139,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, @@ -133,20 +147,33 @@ inline void DX8PolygonRendererClass::Render(/*const Matrix3D & tm,*/int base_ver } } +inline void DX8PolygonRendererClass::Render_Instanced(int base_vertex_offset) +{ + g_renderBackend->Set_Index_Buffer_Index_Offset(base_vertex_offset); + g_renderBackend->Submit_Instanced_Batch( + index_offset, + strip ? index_count - 2 : index_count / 3, + min_vertex_index, + vertex_index_range); +} + inline void DX8PolygonRendererClass::Render_Sorted(/*const Matrix3D & tm,*/int base_vertex_offset,const SphereClass & bounding_sphere) { WWASSERT(!strip); // Strips can't be sorted for now -// DX8Wrapper::Set_Transform(D3DTS_WORLD,tm); -// SNAPSHOT_SAY(("Set_Transform")); 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); + // TheSuperHackers @feature bobtista 07/07/2026 Mark the insert as mesh-origin so the + // backend captures the live per-mesh world for the sorted replay instead of relying + // on per-texture special cases. + g_renderBackend->Set_Mesh_Render_Active(true); SortingRendererClass::Insert_Triangles( bounding_sphere, index_offset, index_count/3, min_vertex_index, vertex_index_range); + g_renderBackend->Set_Mesh_Render_Active(false); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp index 31394a37617..85174893e48 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp @@ -41,18 +41,25 @@ //#define ENABLE_STRIPING #include "dx8renderer.h" -#include "dx8wrapper.h" #include "dx8polygonrenderer.h" -#include "dx8vertexbuffer.h" -#include "dx8indexbuffer.h" +#include +#include +#include +#include "vertexbuffer.h" +#include "indexbuffer.h" #include "dx8fvf.h" -#include "dx8caps.h" #include "dx8rendererdebugger.h" +#include "GgcRuntimeFlags.h" +#include "WW3D2/RenderBufferTypes.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/renderbufferclasses.h" #include "WWDebug/wwdebug.h" #include "WWDebug/wwprofile.h" #include "WWDebug/wwmemlog.h" #include "WW3D2/rinfo.h" #include "statistics.h" +#include "WW3D2/texture.h" #include "WW3D2/meshmdl.h" #include "WWMath/vp.h" #include "WW3D2/decalmsh.h" @@ -116,7 +123,9 @@ class PolyRenderTaskClass : public AutoPoolClass } DX8PolygonRendererClass * Peek_Polygon_Renderer() { return Renderer; } + const DX8PolygonRendererClass * Peek_Polygon_Renderer() const { return Renderer; } MeshClass * Peek_Mesh() { return Mesh; } + const MeshClass * Peek_Mesh() const { return Mesh; } PolyRenderTaskClass * Get_Next_Visible() { return NextVisible; } void Set_Next_Visible(PolyRenderTaskClass * prtc) { NextVisible = prtc; } @@ -348,8 +357,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()")); @@ -433,14 +442,8 @@ DX8FVFCategoryContainer::DX8FVFCategoryContainer(unsigned FVF_,bool sorting_) AnythingToRender(false), AnyDelayedPassesToRender(false) { - if ((FVF&D3DFVF_TEX1)==D3DFVF_TEX1) uv_coordinate_channels=1; - if ((FVF&D3DFVF_TEX2)==D3DFVF_TEX2) uv_coordinate_channels=2; - if ((FVF&D3DFVF_TEX3)==D3DFVF_TEX3) uv_coordinate_channels=3; - if ((FVF&D3DFVF_TEX4)==D3DFVF_TEX4) uv_coordinate_channels=4; - if ((FVF&D3DFVF_TEX5)==D3DFVF_TEX5) uv_coordinate_channels=5; - if ((FVF&D3DFVF_TEX6)==D3DFVF_TEX6) uv_coordinate_channels=6; - if ((FVF&D3DFVF_TEX7)==D3DFVF_TEX7) uv_coordinate_channels=7; - if ((FVF&D3DFVF_TEX8)==D3DFVF_TEX8) uv_coordinate_channels=8; + FVFInfoClass fi(FVF); + uv_coordinate_channels=fi.Get_UV_Channel_Count(); } // ---------------------------------------------------------------------------- @@ -705,37 +708,12 @@ unsigned DX8FVFCategoryContainer::Define_FVF(MeshModelClass* mmc,bool enable_lig return dynamic_fvf_type; } - unsigned fvf=D3DFVF_XYZ; - int tex_coord_count=mmc->Get_UV_Array_Count(); - - if (mmc->Get_Color_Array(0,false)) { - fvf|=D3DFVF_DIFFUSE; - } - if (mmc->Get_Color_Array(1,false)) { - fvf|=D3DFVF_SPECULAR; - } - - switch (tex_coord_count) { - default: - case 0: - break; - case 1: fvf|=D3DFVF_TEX1; break; - case 2: fvf|=D3DFVF_TEX2; break; - case 3: fvf|=D3DFVF_TEX3; break; - case 4: fvf|=D3DFVF_TEX4; break; - case 5: fvf|=D3DFVF_TEX5; break; - case 6: fvf|=D3DFVF_TEX6; break; - case 7: fvf|=D3DFVF_TEX7; break; - case 8: fvf|=D3DFVF_TEX8; break; - } - - if (!mmc->Needs_Vertex_Normals()) { //enable_lighting || mmc->Get_Flag(MeshModelClass::PRELIT_MASK)) { - return fvf; - } - - fvf|=D3DFVF_NORMAL; // Realtime-lit - return fvf; + return FVFInfoClass::Build_FVF( + mmc->Needs_Vertex_Normals(), // Realtime-lit. + mmc->Get_Color_Array(0,false) != nullptr, + mmc->Get_Color_Array(1,false) != nullptr, + tex_coord_count); } // ---------------------------------------------------------------------------- @@ -805,8 +783,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;pSupport_NPatches() && mmc->Needs_Vertex_Normals()) { + if (g_renderBackend && g_renderBackend->Supports_NPatches() && mmc->Needs_Vertex_Normals()) { if (mmc->Get_Flag(MeshGeometryClass::ALLOW_NPATCHES)) { npatch_enable=true; } @@ -1009,10 +987,10 @@ void DX8RigidFVFCategoryContainer::Add_Mesh(MeshModelClass* mmc_) WWASSERT(vertex_buffer->FVF_Info().Get_FVF()==FVF); // Only one sorting FVF type! } else { - vertex_buffer=NEW_REF(DX8VertexBufferClass,( + vertex_buffer=NEW_REF(RenderVertexBufferClass,( FVF, vb_size, - (DX8Wrapper::Get_Current_Caps()->Support_NPatches() && WW3D::Get_NPatches_Level()>1) ? DX8VertexBufferClass::USAGE_NPATCHES : DX8VertexBufferClass::USAGE_DEFAULT)); + (g_renderBackend && g_renderBackend->Supports_NPatches() && WW3D::Get_NPatches_Level()>1) ? RenderVertexBufferClass::USAGE_NPATCHES : RenderVertexBufferClass::USAGE_DEFAULT)); } } @@ -1032,11 +1010,11 @@ void DX8RigidFVFCategoryContainer::Add_Mesh(MeshModelClass* mmc_) { *(Vector3*)(vb+fi.Get_Location_Offset())=locs[i]; - if ((FVF&D3DFVF_NORMAL)==D3DFVF_NORMAL && norms) { + if (fi.Has_Normal() && norms) { *(Vector3*)(vb+fi.Get_Normal_Offset())=norms[i]; } - if ((FVF&D3DFVF_DIFFUSE)==D3DFVF_DIFFUSE) { + if (fi.Has_Diffuse()) { if (diffuse) { *(unsigned int*)(vb+fi.Get_Diffuse_Offset())=diffuse[i]; } else { @@ -1044,7 +1022,7 @@ void DX8RigidFVFCategoryContainer::Add_Mesh(MeshModelClass* mmc_) } } - if ((FVF&D3DFVF_SPECULAR)==D3DFVF_SPECULAR) { + if (fi.Has_Specular()) { if (specular) { *(unsigned int*)(vb+fi.Get_Specular_Offset())=specular[i]; } else { @@ -1059,31 +1037,7 @@ void DX8RigidFVFCategoryContainer::Add_Mesh(MeshModelClass* mmc_) /* ** Append the UV coordinates to the vertex buffer */ - int uvcount = 0; - if ((FVF&D3DFVF_TEX1) == D3DFVF_TEX1) { - uvcount = 1; - } - if ((FVF&D3DFVF_TEX2) == D3DFVF_TEX2) { - uvcount = 2; - } - if ((FVF&D3DFVF_TEX3) == D3DFVF_TEX3) { - uvcount = 3; - } - if ((FVF&D3DFVF_TEX4) == D3DFVF_TEX4) { - uvcount = 4; - } - if ((FVF&D3DFVF_TEX5) == D3DFVF_TEX5) { - uvcount = 5; - } - if ((FVF&D3DFVF_TEX6) == D3DFVF_TEX6) { - uvcount = 6; - } - if ((FVF&D3DFVF_TEX7) == D3DFVF_TEX7) { - uvcount = 7; - } - if ((FVF&D3DFVF_TEX8) == D3DFVF_TEX8) { - uvcount = 8; - } + int uvcount = fi.Get_UV_Channel_Count(); for (int j=0; jSupport_NPatches() && WW3D::Get_NPatches_Level()>1) ? DX8IndexBufferClass::USAGE_NPATCHES : DX8IndexBufferClass::USAGE_DEFAULT)); + (g_renderBackend && g_renderBackend->Supports_NPatches() && WW3D::Get_NPatches_Level()>1) ? RenderIndexBufferClass::USAGE_NPATCHES : RenderIndexBufferClass::USAGE_DEFAULT)); } } @@ -1294,7 +1248,7 @@ void DX8SkinFVFCategoryContainer::Render() } AnythingToRender=false; - DX8Wrapper::Set_Vertex_Buffer(nullptr); // Free up the reference to the current vertex buffer + g_renderBackend->Set_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 @@ -1305,7 +1259,7 @@ void DX8SkinFVFCategoryContainer::Render() } DynamicVBAccessClass vb( - sorting ? BUFFER_TYPE_DYNAMIC_SORTING : BUFFER_TYPE_DYNAMIC_DX8, + sorting ? BUFFER_TYPE_DYNAMIC_SORTING : BUFFER_TYPE_DYNAMIC, dynamic_fvf_type, maxVertexCount); SNAPSHOT_SAY(("DynamicVBAccess - %s - %d vertices",sorting ? "sorting" : "non-sorting",VisibleVertexCount)); @@ -1365,7 +1319,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 +1360,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 +1663,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,22 +1676,85 @@ 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); + //g_renderBackend->Set_Material(material); //REF_PTR_RELEASE(material); - DX8Wrapper::Apply_Render_State_Changes(); - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR); + g_renderBackend->Apply_Render_State_Changes(); + // Override SRCBLEND to DESTCOLOR for multiply mode. DESTBLEND + // stays at whatever ShaderClass set (typically SRCCOLOR). + g_renderBackend->Set_Blend_Factors(RB_BLEND_DEST_COLOR, RB_BLEND_SRC_COLOR); } bool renderTasksRemaining=false; + // TheSuperHackers @performance bobtista 01/07/2026 Batch consecutive identical rigid meshes into + // one instanced draw. Default ON: the bgfx Metal backend reserves a fixed uniform-buffer slot per + // draw submit, so a heavy scene's thousands of per-mesh draws can exhaust bgfx's fixed 8MB Metal + // uniform buffer and fault the render thread (repro: China Nuke general challenge). Instancing + // collapses the repeated-prop draws that dominate that count. Gated by Supports_Instancing(), so + // it is a no-op on backends without instancing caps. Opt out with GGC_BGFX_NO_INSTANCING + // or GGC_BGFX_DISABLE_INSTANCING. + static const bool s_instancingEnabled = !GgcFlags::Enabled(GgcFlag_BgfxNoInstancing); + static const bool s_instancingReorder = !GgcFlags::Enabled(GgcFlag_BgfxInstancingNoReorder); + if (s_instancingEnabled + && s_instancingReorder + && g_renderBackend->Supports_Instancing() + && render_task_head != nullptr) + { + unsigned count = 0; + for (PolyRenderTaskClass * c = render_task_head; c != nullptr; c = c->Get_Next_Visible()) { + count++; + } + if (count >= 2) + { + std::vector tasks; + tasks.reserve(count); + for (PolyRenderTaskClass * c = render_task_head; c != nullptr; c = c->Get_Next_Visible()) { + tasks.push_back(c); + } + // TheSuperHackers @bugfix bobtista 17/07/2026 Only reorder tasks that are actually + // instancing candidates (opaque rigid meshes, whose draw order is depth-tested and + // visually irrelevant). Order-sensitive tasks - sorted dispatches, alpha-override + // translucents, overridden/aligned/skin meshes - keep their relative order and move + // after the opaque group so translucents still draw over the opaques they overlap. + // The previous whole-list sort could shuffle translucent draw order per frame. + auto reorderEligible = [](PolyRenderTaskClass * t) { + MeshClass * m = t->Peek_Mesh(); + return m->Get_Base_Vertex_Offset() != VERTEX_BUFFER_OVERFLOW + && m->Get_Alpha_Override() == 1.0f + && m->Get_ObjectScale() == 1.0f + && !(m->Get_User_Data() && *(int *)m->Get_User_Data() == RenderObjClass::USER_DATA_MATERIAL_OVERRIDE) + && !m->Peek_Model()->Get_Flag(MeshModelClass::ALIGNED) + && !m->Peek_Model()->Get_Flag(MeshModelClass::ORIENTED) + && !m->Peek_Model()->Get_Flag(MeshModelClass::SKIN) + && !m->Peek_Model()->Get_Flag(MeshGeometryClass::COPLANAR_NORMAL_BIAS) + && !((!!m->Peek_Model()->Get_Flag(MeshGeometryClass::SORT)) && WW3D::Is_Sorting_Enabled()); + }; + std::stable_sort(tasks.begin(), tasks.end(), [&reorderEligible](PolyRenderTaskClass * a, PolyRenderTaskClass * b) { + const bool ea = reorderEligible(a); + const bool eb = reorderEligible(b); + if (ea != eb) { return ea; } // eligible opaques first, order-sensitive tail keeps relative order + if (!ea) { return false; } + auto * ra = a->Peek_Polygon_Renderer(); + auto * rb = b->Peek_Polygon_Renderer(); + if (ra != rb) { return ra < rb; } + return a->Peek_Mesh()->Get_Base_Vertex_Offset() < b->Peek_Mesh()->Get_Base_Vertex_Offset(); + }); + render_task_head = tasks[0]; + for (unsigned i = 0; i + 1 < count; i++) { + tasks[i]->Set_Next_Visible(tasks[i + 1]); + } + tasks[count - 1]->Set_Next_Visible(nullptr); + } + } + PolyRenderTaskClass * prt = render_task_head; PolyRenderTaskClass * last_prt = nullptr; @@ -1756,7 +1788,7 @@ void DX8TextureCategoryClass::Render() // Disable texturing on all stages and passes. for (i = 0; i < MeshMatDescClass::MAX_TEX_STAGES; i++) { - DX8Wrapper::Set_Texture (i, nullptr); + g_renderBackend->Set_Texture(i, nullptr); } break; @@ -1766,11 +1798,11 @@ void DX8TextureCategoryClass::Render() if (pass == mesh->Peek_Model()->Get_Pass_Count() - 1) { for (i = 0; i < MeshMatDescClass::MAX_TEX_STAGES; i++) { - DX8Wrapper::Set_Texture (i, Peek_Texture (i)); + g_renderBackend->Set_Texture(i, Peek_Texture(i)); } } else { - for (i = 0; i < MAX_TEXTURE_STAGES; i++) { - DX8Wrapper::Set_Texture (i, nullptr); + for (i = 0; i < RB_MAX_TEXTURE_STAGES; i++) { + g_renderBackend->Set_Texture(i, nullptr); } } break; @@ -1778,17 +1810,17 @@ void DX8TextureCategoryClass::Render() case MeshGeometryClass::PRELIT_LIGHTMAP_MULTI_TEXTURE: // Disable texturing on all but the zeroth stage of each pass. - DX8Wrapper::Set_Texture (0, Peek_Texture (0)); + g_renderBackend->Set_Texture(0, Peek_Texture(0)); for (i = 1; i < MeshMatDescClass::MAX_TEX_STAGES; i++) { - DX8Wrapper::Set_Texture (i, nullptr); + g_renderBackend->Set_Texture(i, nullptr); } break; default: for (i = 0; i < MeshMatDescClass::MAX_TEX_STAGES; i++) { - DX8Wrapper::Set_Texture (i, Peek_Texture (i)); + g_renderBackend->Set_Texture(i, Peek_Texture(i)); } break; } @@ -1802,7 +1834,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")); @@ -1850,21 +1882,24 @@ 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); } //-------------------------------------------------------------------- if (mesh->Get_ObjectScale() != 1.0f) - DX8Wrapper::Set_DX8_Render_State(D3DRS_NORMALIZENORMALS, TRUE); + g_renderBackend->Set_Normalize_Normals(true); //-------------------------------------------------------------------- /* ** Render mesh using either sorting or immediate pipeline */ + const bool coplanarNormalBias = mesh->Peek_Model()->Get_Flag(MeshGeometryClass::COPLANAR_NORMAL_BIAS) != 0; + g_renderBackend->Set_Normal_Bias(coplanarNormalBias ? 0.02f : 0.0f); + //(gth) this if statement's contents are not tabbed to avoid perforce merge problems... if (!DX8RendererDebugger::Is_Enabled() || !mesh->Is_Disabled_By_Debugger()) { @@ -1901,16 +1936,16 @@ void DX8TextureCategoryClass::Render() theAlphaShader = theShader; //keep using additive blending. } vmaterial->Set_Opacity(mesh->Get_Alpha_Override()); - DX8Wrapper::Set_Shader(theAlphaShader); - DX8Wrapper::Apply_Render_State_Changes(); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,(int)((float)0x60*mesh->Get_Alpha_Override())); + g_renderBackend->Set_Shader(theAlphaShader); + g_renderBackend->Apply_Render_State_Changes(); + g_renderBackend->Set_Alpha_Test_Reference((int)((float)0x60*mesh->Get_Alpha_Override())); renderer->Render(mesh->Get_Base_Vertex_Offset()); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0x60); + g_renderBackend->Set_Alpha_Test_Reference(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,15 +1954,81 @@ 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()); + { + bool instanced = false; + const bool currentInstancingEligible = + !coplanarNormalBias + && mesh->Get_ObjectScale() == 1.0f + && mesh->Get_Base_Vertex_Offset() != VERTEX_BUFFER_OVERFLOW + && !mesh->Peek_Model()->Get_Flag(MeshModelClass::ALIGNED) + && !mesh->Peek_Model()->Get_Flag(MeshModelClass::ORIENTED) + && !mesh->Peek_Model()->Get_Flag(MeshModelClass::SKIN) + && !((!!mesh->Peek_Model()->Get_Flag(MeshGeometryClass::SORT)) && WW3D::Is_Sorting_Enabled()); + if (s_instancingEnabled + && g_renderBackend->Supports_Instancing() + && currentInstancingEligible) + { + PolyRenderTaskClass * nextScan = prt->Get_Next_Visible(); + if (nextScan != nullptr + && nextScan->Peek_Polygon_Renderer() == renderer + && nextScan->Peek_Mesh()->Get_Base_Vertex_Offset() != VERTEX_BUFFER_OVERFLOW + && nextScan->Peek_Mesh()->Get_Base_Vertex_Offset() == mesh->Get_Base_Vertex_Offset() + && nextScan->Peek_Mesh()->Get_Lighting_Environment() == lenv) + { + unsigned batchCount = 1; + for (PolyRenderTaskClass * s = nextScan; s != nullptr; s = s->Get_Next_Visible()) + { + MeshClass * sm = s->Peek_Mesh(); + if (s->Peek_Polygon_Renderer() != renderer + || sm->Get_Base_Vertex_Offset() != mesh->Get_Base_Vertex_Offset() + || sm->Get_Base_Vertex_Offset() == VERTEX_BUFFER_OVERFLOW + || sm->Get_Alpha_Override() != 1.0f + || sm->Get_Lighting_Environment() != lenv + || (sm->Get_User_Data() && *(int *)sm->Get_User_Data() == RenderObjClass::USER_DATA_MATERIAL_OVERRIDE) + || sm->Peek_Model()->Get_Flag(MeshModelClass::ALIGNED) + || sm->Peek_Model()->Get_Flag(MeshModelClass::ORIENTED) + || sm->Peek_Model()->Get_Flag(MeshModelClass::SKIN) + || ((!!sm->Peek_Model()->Get_Flag(MeshGeometryClass::SORT)) && WW3D::Is_Sorting_Enabled()) + || sm->Get_ObjectScale() != 1.0f + || sm->Peek_Model()->Get_Flag(MeshGeometryClass::COPLANAR_NORMAL_BIAS) + ) { + break; + } + batchCount++; + } + if (batchCount >= 2 && g_renderBackend->Begin_Instanced_Batch(batchCount)) + { + g_renderBackend->Add_Instance((const float *)world_transform); + unsigned added = 1; + while (added < batchCount) + { + PolyRenderTaskClass * batchPrt = prt->Get_Next_Visible(); + MeshClass * bMesh = batchPrt->Peek_Mesh(); + g_renderBackend->Add_Instance((const float *)&bMesh->Get_Transform()); + added++; + prt->Set_Next_Visible(batchPrt->Get_Next_Visible()); + delete batchPrt; + } + renderer->Render_Instanced(mesh->Get_Base_Vertex_Offset()); + instanced = true; + } + } + } + if (!instanced) { + renderer->Render(mesh->Get_Base_Vertex_Offset()); + } + } + } + if (coplanarNormalBias) { + g_renderBackend->Set_Normal_Bias(0.0f); } //-------------------------------------------------------------------- if (mesh->Get_ObjectScale() != 1.0f) - DX8Wrapper::Set_DX8_Render_State(D3DRS_NORMALIZENORMALS, FALSE); + g_renderBackend->Set_Normalize_Normals(false); //-------------------------------------------------------------------- @@ -2201,8 +2302,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); } @@ -2218,7 +2319,7 @@ void DX8MeshRendererClass::Render_Decal_Meshes() DecalMeshClass * decal_mesh = visible_decal_meshes; if (!decal_mesh) return; - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZBIAS,8); + g_renderBackend->Set_Z_Bias(8); while (decal_mesh != nullptr) { decal_mesh->Render(); @@ -2226,7 +2327,7 @@ void DX8MeshRendererClass::Render_Decal_Meshes() } visible_decal_meshes = nullptr; - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZBIAS,0); + g_renderBackend->Set_Z_Bias(0); } // ---------------------------------------------------------------------------- @@ -2278,10 +2379,3 @@ void DX8MeshRendererClass::Invalidate( bool shutdown) texture_category_container_lists_rigid.Delete_All(); } - - - - - - - diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.h index ff4f593e03a..f560664a3b1 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.h @@ -45,7 +45,6 @@ #include "WWLib/Vector.h" #include "dx8list.h" #include "WW3D2/shader.h" -#include "dx8wrapper.h" #include "WW3D2/meshmatdesc.h" class IndexBufferClass; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8sdk/d3d8.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8sdk/d3d8.h new file mode 100644 index 00000000000..347a86cc134 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8sdk/d3d8.h @@ -0,0 +1,1279 @@ +/*==========================================================================; + * + * Copyright (C) Microsoft Corporation. All Rights Reserved. + * + * File: d3d8.h + * Content: Direct3D include file + * + ****************************************************************************/ + +#ifndef _D3D8_H_ +#define _D3D8_H_ + +#ifndef DIRECT3D_VERSION +#define DIRECT3D_VERSION 0x0800 +#endif //DIRECT3D_VERSION + +// include this file content only if compiling for DX8 interfaces +#if(DIRECT3D_VERSION >= 0x0800) + + +/* This identifier is passed to Direct3DCreate8 in order to ensure that an + * application was built against the correct header files. This number is + * incremented whenever a header (or other) change would require applications + * to be rebuilt. If the version doesn't match, Direct3DCreate8 will fail. + * (The number itself has no meaning.)*/ + +#define D3D_SDK_VERSION 220 + + +#include + +#define COM_NO_WINDOWS_H +#include + +#include + +#if !defined(HMONITOR_DECLARED) && (WINVER < 0x0500) + #define HMONITOR_DECLARED + DECLARE_HANDLE(HMONITOR); +#endif + +#define D3DAPI WINAPI + +/* + * Interface IID's + */ +#if defined( _WIN32 ) && !defined( _NO_COM) + +/* IID_IDirect3D8 */ +/* {1DD9E8DA-1C77-4d40-B0CF-98FEFDFF9512} */ +DEFINE_GUID(IID_IDirect3D8, 0x1dd9e8da, 0x1c77, 0x4d40, 0xb0, 0xcf, 0x98, 0xfe, 0xfd, 0xff, 0x95, 0x12); + +/* IID_IDirect3DDevice8 */ +/* {7385E5DF-8FE8-41D5-86B6-D7B48547B6CF} */ +DEFINE_GUID(IID_IDirect3DDevice8, 0x7385e5df, 0x8fe8, 0x41d5, 0x86, 0xb6, 0xd7, 0xb4, 0x85, 0x47, 0xb6, 0xcf); + +/* IID_IDirect3DResource8 */ +/* {1B36BB7B-09B7-410a-B445-7D1430D7B33F} */ +DEFINE_GUID(IID_IDirect3DResource8, 0x1b36bb7b, 0x9b7, 0x410a, 0xb4, 0x45, 0x7d, 0x14, 0x30, 0xd7, 0xb3, 0x3f); + +/* IID_IDirect3DBaseTexture8 */ +/* {B4211CFA-51B9-4a9f-AB78-DB99B2BB678E} */ +DEFINE_GUID(IID_IDirect3DBaseTexture8, 0xb4211cfa, 0x51b9, 0x4a9f, 0xab, 0x78, 0xdb, 0x99, 0xb2, 0xbb, 0x67, 0x8e); + +/* IID_IDirect3DTexture8 */ +/* {E4CDD575-2866-4f01-B12E-7EECE1EC9358} */ +DEFINE_GUID(IID_IDirect3DTexture8, 0xe4cdd575, 0x2866, 0x4f01, 0xb1, 0x2e, 0x7e, 0xec, 0xe1, 0xec, 0x93, 0x58); + +/* IID_IDirect3DCubeTexture8 */ +/* {3EE5B968-2ACA-4c34-8BB5-7E0C3D19B750} */ +DEFINE_GUID(IID_IDirect3DCubeTexture8, 0x3ee5b968, 0x2aca, 0x4c34, 0x8b, 0xb5, 0x7e, 0x0c, 0x3d, 0x19, 0xb7, 0x50); + +/* IID_IDirect3DVolumeTexture8 */ +/* {4B8AAAFA-140F-42ba-9131-597EAFAA2EAD} */ +DEFINE_GUID(IID_IDirect3DVolumeTexture8, 0x4b8aaafa, 0x140f, 0x42ba, 0x91, 0x31, 0x59, 0x7e, 0xaf, 0xaa, 0x2e, 0xad); + +/* IID_IDirect3DVertexBuffer8 */ +/* {8AEEEAC7-05F9-44d4-B591-000B0DF1CB95} */ +DEFINE_GUID(IID_IDirect3DVertexBuffer8, 0x8aeeeac7, 0x05f9, 0x44d4, 0xb5, 0x91, 0x00, 0x0b, 0x0d, 0xf1, 0xcb, 0x95); + +/* IID_IDirect3DIndexBuffer8 */ +/* {0E689C9A-053D-44a0-9D92-DB0E3D750F86} */ +DEFINE_GUID(IID_IDirect3DIndexBuffer8, 0x0e689c9a, 0x053d, 0x44a0, 0x9d, 0x92, 0xdb, 0x0e, 0x3d, 0x75, 0x0f, 0x86); + +/* IID_IDirect3DSurface8 */ +/* {B96EEBCA-B326-4ea5-882F-2FF5BAE021DD} */ +DEFINE_GUID(IID_IDirect3DSurface8, 0xb96eebca, 0xb326, 0x4ea5, 0x88, 0x2f, 0x2f, 0xf5, 0xba, 0xe0, 0x21, 0xdd); + +/* IID_IDirect3DVolume8 */ +/* {BD7349F5-14F1-42e4-9C79-972380DB40C0} */ +DEFINE_GUID(IID_IDirect3DVolume8, 0xbd7349f5, 0x14f1, 0x42e4, 0x9c, 0x79, 0x97, 0x23, 0x80, 0xdb, 0x40, 0xc0); + +/* IID_IDirect3DSwapChain8 */ +/* {928C088B-76B9-4C6B-A536-A590853876CD} */ +DEFINE_GUID(IID_IDirect3DSwapChain8, 0x928c088b, 0x76b9, 0x4c6b, 0xa5, 0x36, 0xa5, 0x90, 0x85, 0x38, 0x76, 0xcd); + +#endif + +#ifdef __cplusplus + +interface IDirect3D8; +interface IDirect3DDevice8; + +interface IDirect3DResource8; +interface IDirect3DBaseTexture8; +interface IDirect3DTexture8; +interface IDirect3DVolumeTexture8; +interface IDirect3DCubeTexture8; + +interface IDirect3DVertexBuffer8; +interface IDirect3DIndexBuffer8; + +interface IDirect3DSurface8; +interface IDirect3DVolume8; + +interface IDirect3DSwapChain8; + +#endif + + +typedef interface IDirect3D8 IDirect3D8; +typedef interface IDirect3DDevice8 IDirect3DDevice8; +typedef interface IDirect3DResource8 IDirect3DResource8; +typedef interface IDirect3DBaseTexture8 IDirect3DBaseTexture8; +typedef interface IDirect3DTexture8 IDirect3DTexture8; +typedef interface IDirect3DVolumeTexture8 IDirect3DVolumeTexture8; +typedef interface IDirect3DCubeTexture8 IDirect3DCubeTexture8; +typedef interface IDirect3DVertexBuffer8 IDirect3DVertexBuffer8; +typedef interface IDirect3DIndexBuffer8 IDirect3DIndexBuffer8; +typedef interface IDirect3DSurface8 IDirect3DSurface8; +typedef interface IDirect3DVolume8 IDirect3DVolume8; +typedef interface IDirect3DSwapChain8 IDirect3DSwapChain8; + +#include "d3d8types.h" +#include "d3d8caps.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * DLL Function for creating a Direct3D8 object. This object supports + * enumeration and allows the creation of Direct3DDevice8 objects. + * Pass the value of the constant D3D_SDK_VERSION to this function, so + * that the run-time can validate that your application was compiled + * against the right headers. + */ + +IDirect3D8 * WINAPI Direct3DCreate8(UINT SDKVersion); + + +/* + * Direct3D interfaces + */ + + + + + + +#undef INTERFACE +#define INTERFACE IDirect3D8 + +DECLARE_INTERFACE_(IDirect3D8, IUnknown) +{ + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + /*** IDirect3D8 methods ***/ + STDMETHOD(RegisterSoftwareDevice)(THIS_ void* pInitializeFunction) PURE; + STDMETHOD_(UINT, GetAdapterCount)(THIS) PURE; + STDMETHOD(GetAdapterIdentifier)(THIS_ UINT Adapter,DWORD Flags,D3DADAPTER_IDENTIFIER8* pIdentifier) PURE; + STDMETHOD_(UINT, GetAdapterModeCount)(THIS_ UINT Adapter) PURE; + STDMETHOD(EnumAdapterModes)(THIS_ UINT Adapter,UINT Mode,D3DDISPLAYMODE* pMode) PURE; + STDMETHOD(GetAdapterDisplayMode)(THIS_ UINT Adapter,D3DDISPLAYMODE* pMode) PURE; + STDMETHOD(CheckDeviceType)(THIS_ UINT Adapter,D3DDEVTYPE CheckType,D3DFORMAT DisplayFormat,D3DFORMAT BackBufferFormat,BOOL Windowed) PURE; + STDMETHOD(CheckDeviceFormat)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,DWORD Usage,D3DRESOURCETYPE RType,D3DFORMAT CheckFormat) PURE; + STDMETHOD(CheckDeviceMultiSampleType)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT SurfaceFormat,BOOL Windowed,D3DMULTISAMPLE_TYPE MultiSampleType) PURE; + STDMETHOD(CheckDepthStencilMatch)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,D3DFORMAT RenderTargetFormat,D3DFORMAT DepthStencilFormat) PURE; + STDMETHOD(GetDeviceCaps)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DCAPS8* pCaps) PURE; + STDMETHOD_(HMONITOR, GetAdapterMonitor)(THIS_ UINT Adapter) PURE; + STDMETHOD(CreateDevice)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DDevice8** ppReturnedDeviceInterface) PURE; +}; + +typedef struct IDirect3D8 *LPDIRECT3D8, *PDIRECT3D8; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirect3D8_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirect3D8_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirect3D8_Release(p) (p)->lpVtbl->Release(p) +#define IDirect3D8_RegisterSoftwareDevice(p,a) (p)->lpVtbl->RegisterSoftwareDevice(p,a) +#define IDirect3D8_GetAdapterCount(p) (p)->lpVtbl->GetAdapterCount(p) +#define IDirect3D8_GetAdapterIdentifier(p,a,b,c) (p)->lpVtbl->GetAdapterIdentifier(p,a,b,c) +#define IDirect3D8_GetAdapterModeCount(p,a) (p)->lpVtbl->GetAdapterModeCount(p,a) +#define IDirect3D8_EnumAdapterModes(p,a,b,c) (p)->lpVtbl->EnumAdapterModes(p,a,b,c) +#define IDirect3D8_GetAdapterDisplayMode(p,a,b) (p)->lpVtbl->GetAdapterDisplayMode(p,a,b) +#define IDirect3D8_CheckDeviceType(p,a,b,c,d,e) (p)->lpVtbl->CheckDeviceType(p,a,b,c,d,e) +#define IDirect3D8_CheckDeviceFormat(p,a,b,c,d,e,f) (p)->lpVtbl->CheckDeviceFormat(p,a,b,c,d,e,f) +#define IDirect3D8_CheckDeviceMultiSampleType(p,a,b,c,d,e) (p)->lpVtbl->CheckDeviceMultiSampleType(p,a,b,c,d,e) +#define IDirect3D8_CheckDepthStencilMatch(p,a,b,c,d,e) (p)->lpVtbl->CheckDepthStencilMatch(p,a,b,c,d,e) +#define IDirect3D8_GetDeviceCaps(p,a,b,c) (p)->lpVtbl->GetDeviceCaps(p,a,b,c) +#define IDirect3D8_GetAdapterMonitor(p,a) (p)->lpVtbl->GetAdapterMonitor(p,a) +#define IDirect3D8_CreateDevice(p,a,b,c,d,e,f) (p)->lpVtbl->CreateDevice(p,a,b,c,d,e,f) +#else +#define IDirect3D8_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirect3D8_AddRef(p) (p)->AddRef() +#define IDirect3D8_Release(p) (p)->Release() +#define IDirect3D8_RegisterSoftwareDevice(p,a) (p)->RegisterSoftwareDevice(a) +#define IDirect3D8_GetAdapterCount(p) (p)->GetAdapterCount() +#define IDirect3D8_GetAdapterIdentifier(p,a,b,c) (p)->GetAdapterIdentifier(a,b,c) +#define IDirect3D8_GetAdapterModeCount(p,a) (p)->GetAdapterModeCount(a) +#define IDirect3D8_EnumAdapterModes(p,a,b,c) (p)->EnumAdapterModes(a,b,c) +#define IDirect3D8_GetAdapterDisplayMode(p,a,b) (p)->GetAdapterDisplayMode(a,b) +#define IDirect3D8_CheckDeviceType(p,a,b,c,d,e) (p)->CheckDeviceType(a,b,c,d,e) +#define IDirect3D8_CheckDeviceFormat(p,a,b,c,d,e,f) (p)->CheckDeviceFormat(a,b,c,d,e,f) +#define IDirect3D8_CheckDeviceMultiSampleType(p,a,b,c,d,e) (p)->CheckDeviceMultiSampleType(a,b,c,d,e) +#define IDirect3D8_CheckDepthStencilMatch(p,a,b,c,d,e) (p)->CheckDepthStencilMatch(a,b,c,d,e) +#define IDirect3D8_GetDeviceCaps(p,a,b,c) (p)->GetDeviceCaps(a,b,c) +#define IDirect3D8_GetAdapterMonitor(p,a) (p)->GetAdapterMonitor(a) +#define IDirect3D8_CreateDevice(p,a,b,c,d,e,f) (p)->CreateDevice(a,b,c,d,e,f) +#endif + + + + + + + + + + + + + + + + + + + +#undef INTERFACE +#define INTERFACE IDirect3DDevice8 + +DECLARE_INTERFACE_(IDirect3DDevice8, IUnknown) +{ + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + /*** IDirect3DDevice8 methods ***/ + STDMETHOD(TestCooperativeLevel)(THIS) PURE; + STDMETHOD_(UINT, GetAvailableTextureMem)(THIS) PURE; + STDMETHOD(ResourceManagerDiscardBytes)(THIS_ DWORD Bytes) PURE; + STDMETHOD(GetDirect3D)(THIS_ IDirect3D8** ppD3D8) PURE; + STDMETHOD(GetDeviceCaps)(THIS_ D3DCAPS8* pCaps) PURE; + STDMETHOD(GetDisplayMode)(THIS_ D3DDISPLAYMODE* pMode) PURE; + STDMETHOD(GetCreationParameters)(THIS_ D3DDEVICE_CREATION_PARAMETERS *pParameters) PURE; + STDMETHOD(SetCursorProperties)(THIS_ UINT XHotSpot,UINT YHotSpot,IDirect3DSurface8* pCursorBitmap) PURE; + STDMETHOD_(void, SetCursorPosition)(THIS_ UINT XScreenSpace,UINT YScreenSpace,DWORD Flags) PURE; + STDMETHOD_(BOOL, ShowCursor)(THIS_ BOOL bShow) PURE; + STDMETHOD(CreateAdditionalSwapChain)(THIS_ D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DSwapChain8** pSwapChain) PURE; + STDMETHOD(Reset)(THIS_ D3DPRESENT_PARAMETERS* pPresentationParameters) PURE; + STDMETHOD(Present)(THIS_ CONST RECT* pSourceRect,CONST RECT* pDestRect,HWND hDestWindowOverride,CONST RGNDATA* pDirtyRegion) PURE; + STDMETHOD(GetBackBuffer)(THIS_ UINT BackBuffer,D3DBACKBUFFER_TYPE Type,IDirect3DSurface8** ppBackBuffer) PURE; + STDMETHOD(GetRasterStatus)(THIS_ D3DRASTER_STATUS* pRasterStatus) PURE; + STDMETHOD_(void, SetGammaRamp)(THIS_ DWORD Flags,CONST D3DGAMMARAMP* pRamp) PURE; + STDMETHOD_(void, GetGammaRamp)(THIS_ D3DGAMMARAMP* pRamp) PURE; + STDMETHOD(CreateTexture)(THIS_ UINT Width,UINT Height,UINT Levels,DWORD Usage,D3DFORMAT Format,D3DPOOL Pool,IDirect3DTexture8** ppTexture) PURE; + STDMETHOD(CreateVolumeTexture)(THIS_ UINT Width,UINT Height,UINT Depth,UINT Levels,DWORD Usage,D3DFORMAT Format,D3DPOOL Pool,IDirect3DVolumeTexture8** ppVolumeTexture) PURE; + STDMETHOD(CreateCubeTexture)(THIS_ UINT EdgeLength,UINT Levels,DWORD Usage,D3DFORMAT Format,D3DPOOL Pool,IDirect3DCubeTexture8** ppCubeTexture) PURE; + STDMETHOD(CreateVertexBuffer)(THIS_ UINT Length,DWORD Usage,DWORD FVF,D3DPOOL Pool,IDirect3DVertexBuffer8** ppVertexBuffer) PURE; + STDMETHOD(CreateIndexBuffer)(THIS_ UINT Length,DWORD Usage,D3DFORMAT Format,D3DPOOL Pool,IDirect3DIndexBuffer8** ppIndexBuffer) PURE; + STDMETHOD(CreateRenderTarget)(THIS_ UINT Width,UINT Height,D3DFORMAT Format,D3DMULTISAMPLE_TYPE MultiSample,BOOL Lockable,IDirect3DSurface8** ppSurface) PURE; + STDMETHOD(CreateDepthStencilSurface)(THIS_ UINT Width,UINT Height,D3DFORMAT Format,D3DMULTISAMPLE_TYPE MultiSample,IDirect3DSurface8** ppSurface) PURE; + STDMETHOD(CreateImageSurface)(THIS_ UINT Width,UINT Height,D3DFORMAT Format,IDirect3DSurface8** ppSurface) PURE; + STDMETHOD(CopyRects)(THIS_ IDirect3DSurface8* pSourceSurface,CONST RECT* pSourceRectsArray,UINT cRects,IDirect3DSurface8* pDestinationSurface,CONST POINT* pDestPointsArray) PURE; + STDMETHOD(UpdateTexture)(THIS_ IDirect3DBaseTexture8* pSourceTexture,IDirect3DBaseTexture8* pDestinationTexture) PURE; + STDMETHOD(GetFrontBuffer)(THIS_ IDirect3DSurface8* pDestSurface) PURE; + STDMETHOD(SetRenderTarget)(THIS_ IDirect3DSurface8* pRenderTarget,IDirect3DSurface8* pNewZStencil) PURE; + STDMETHOD(GetRenderTarget)(THIS_ IDirect3DSurface8** ppRenderTarget) PURE; + STDMETHOD(GetDepthStencilSurface)(THIS_ IDirect3DSurface8** ppZStencilSurface) PURE; + STDMETHOD(BeginScene)(THIS) PURE; + STDMETHOD(EndScene)(THIS) PURE; + STDMETHOD(Clear)(THIS_ DWORD Count,CONST D3DRECT* pRects,DWORD Flags,D3DCOLOR Color,float Z,DWORD Stencil) PURE; + STDMETHOD(SetTransform)(THIS_ D3DTRANSFORMSTATETYPE State,CONST D3DMATRIX* pMatrix) PURE; + STDMETHOD(GetTransform)(THIS_ D3DTRANSFORMSTATETYPE State,D3DMATRIX* pMatrix) PURE; + STDMETHOD(MultiplyTransform)(THIS_ D3DTRANSFORMSTATETYPE,CONST D3DMATRIX*) PURE; + STDMETHOD(SetViewport)(THIS_ CONST D3DVIEWPORT8* pViewport) PURE; + STDMETHOD(GetViewport)(THIS_ D3DVIEWPORT8* pViewport) PURE; + STDMETHOD(SetMaterial)(THIS_ CONST D3DMATERIAL8* pMaterial) PURE; + STDMETHOD(GetMaterial)(THIS_ D3DMATERIAL8* pMaterial) PURE; + STDMETHOD(SetLight)(THIS_ DWORD Index,CONST D3DLIGHT8*) PURE; + STDMETHOD(GetLight)(THIS_ DWORD Index,D3DLIGHT8*) PURE; + STDMETHOD(LightEnable)(THIS_ DWORD Index,BOOL Enable) PURE; + STDMETHOD(GetLightEnable)(THIS_ DWORD Index,BOOL* pEnable) PURE; + STDMETHOD(SetClipPlane)(THIS_ DWORD Index,CONST float* pPlane) PURE; + STDMETHOD(GetClipPlane)(THIS_ DWORD Index,float* pPlane) PURE; + STDMETHOD(SetRenderState)(THIS_ D3DRENDERSTATETYPE State,DWORD Value) PURE; + STDMETHOD(GetRenderState)(THIS_ D3DRENDERSTATETYPE State,DWORD* pValue) PURE; + STDMETHOD(BeginStateBlock)(THIS) PURE; + STDMETHOD(EndStateBlock)(THIS_ DWORD* pToken) PURE; + STDMETHOD(ApplyStateBlock)(THIS_ DWORD Token) PURE; + STDMETHOD(CaptureStateBlock)(THIS_ DWORD Token) PURE; + STDMETHOD(DeleteStateBlock)(THIS_ DWORD Token) PURE; + STDMETHOD(CreateStateBlock)(THIS_ D3DSTATEBLOCKTYPE Type,DWORD* pToken) PURE; + STDMETHOD(SetClipStatus)(THIS_ CONST D3DCLIPSTATUS8* pClipStatus) PURE; + STDMETHOD(GetClipStatus)(THIS_ D3DCLIPSTATUS8* pClipStatus) PURE; + STDMETHOD(GetTexture)(THIS_ DWORD Stage,IDirect3DBaseTexture8** ppTexture) PURE; + STDMETHOD(SetTexture)(THIS_ DWORD Stage,IDirect3DBaseTexture8* pTexture) PURE; + STDMETHOD(GetTextureStageState)(THIS_ DWORD Stage,D3DTEXTURESTAGESTATETYPE Type,DWORD* pValue) PURE; + STDMETHOD(SetTextureStageState)(THIS_ DWORD Stage,D3DTEXTURESTAGESTATETYPE Type,DWORD Value) PURE; + STDMETHOD(ValidateDevice)(THIS_ DWORD* pNumPasses) PURE; + STDMETHOD(GetInfo)(THIS_ DWORD DevInfoID,void* pDevInfoStruct,DWORD DevInfoStructSize) PURE; + STDMETHOD(SetPaletteEntries)(THIS_ UINT PaletteNumber,CONST PALETTEENTRY* pEntries) PURE; + STDMETHOD(GetPaletteEntries)(THIS_ UINT PaletteNumber,PALETTEENTRY* pEntries) PURE; + STDMETHOD(SetCurrentTexturePalette)(THIS_ UINT PaletteNumber) PURE; + STDMETHOD(GetCurrentTexturePalette)(THIS_ UINT *PaletteNumber) PURE; + STDMETHOD(DrawPrimitive)(THIS_ D3DPRIMITIVETYPE PrimitiveType,UINT StartVertex,UINT PrimitiveCount) PURE; + STDMETHOD(DrawIndexedPrimitive)(THIS_ D3DPRIMITIVETYPE,UINT minIndex,UINT NumVertices,UINT startIndex,UINT primCount) PURE; + STDMETHOD(DrawPrimitiveUP)(THIS_ D3DPRIMITIVETYPE PrimitiveType,UINT PrimitiveCount,CONST void* pVertexStreamZeroData,UINT VertexStreamZeroStride) PURE; + STDMETHOD(DrawIndexedPrimitiveUP)(THIS_ D3DPRIMITIVETYPE PrimitiveType,UINT MinVertexIndex,UINT NumVertexIndices,UINT PrimitiveCount,CONST void* pIndexData,D3DFORMAT IndexDataFormat,CONST void* pVertexStreamZeroData,UINT VertexStreamZeroStride) PURE; + STDMETHOD(ProcessVertices)(THIS_ UINT SrcStartIndex,UINT DestIndex,UINT VertexCount,IDirect3DVertexBuffer8* pDestBuffer,DWORD Flags) PURE; + STDMETHOD(CreateVertexShader)(THIS_ CONST DWORD* pDeclaration,CONST DWORD* pFunction,DWORD* pHandle,DWORD Usage) PURE; + STDMETHOD(SetVertexShader)(THIS_ DWORD Handle) PURE; + STDMETHOD(GetVertexShader)(THIS_ DWORD* pHandle) PURE; + STDMETHOD(DeleteVertexShader)(THIS_ DWORD Handle) PURE; + STDMETHOD(SetVertexShaderConstant)(THIS_ DWORD Register,CONST void* pConstantData,DWORD ConstantCount) PURE; + STDMETHOD(GetVertexShaderConstant)(THIS_ DWORD Register,void* pConstantData,DWORD ConstantCount) PURE; + STDMETHOD(GetVertexShaderDeclaration)(THIS_ DWORD Handle,void* pData,DWORD* pSizeOfData) PURE; + STDMETHOD(GetVertexShaderFunction)(THIS_ DWORD Handle,void* pData,DWORD* pSizeOfData) PURE; + STDMETHOD(SetStreamSource)(THIS_ UINT StreamNumber,IDirect3DVertexBuffer8* pStreamData,UINT Stride) PURE; + STDMETHOD(GetStreamSource)(THIS_ UINT StreamNumber,IDirect3DVertexBuffer8** ppStreamData,UINT* pStride) PURE; + STDMETHOD(SetIndices)(THIS_ IDirect3DIndexBuffer8* pIndexData,UINT BaseVertexIndex) PURE; + STDMETHOD(GetIndices)(THIS_ IDirect3DIndexBuffer8** ppIndexData,UINT* pBaseVertexIndex) PURE; + STDMETHOD(CreatePixelShader)(THIS_ CONST DWORD* pFunction,DWORD* pHandle) PURE; + STDMETHOD(SetPixelShader)(THIS_ DWORD Handle) PURE; + STDMETHOD(GetPixelShader)(THIS_ DWORD* pHandle) PURE; + STDMETHOD(DeletePixelShader)(THIS_ DWORD Handle) PURE; + STDMETHOD(SetPixelShaderConstant)(THIS_ DWORD Register,CONST void* pConstantData,DWORD ConstantCount) PURE; + STDMETHOD(GetPixelShaderConstant)(THIS_ DWORD Register,void* pConstantData,DWORD ConstantCount) PURE; + STDMETHOD(GetPixelShaderFunction)(THIS_ DWORD Handle,void* pData,DWORD* pSizeOfData) PURE; + STDMETHOD(DrawRectPatch)(THIS_ UINT Handle,CONST float* pNumSegs,CONST D3DRECTPATCH_INFO* pRectPatchInfo) PURE; + STDMETHOD(DrawTriPatch)(THIS_ UINT Handle,CONST float* pNumSegs,CONST D3DTRIPATCH_INFO* pTriPatchInfo) PURE; + STDMETHOD(DeletePatch)(THIS_ UINT Handle) PURE; +}; + +typedef struct IDirect3DDevice8 *LPDIRECT3DDEVICE8, *PDIRECT3DDEVICE8; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirect3DDevice8_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirect3DDevice8_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirect3DDevice8_Release(p) (p)->lpVtbl->Release(p) +#define IDirect3DDevice8_TestCooperativeLevel(p) (p)->lpVtbl->TestCooperativeLevel(p) +#define IDirect3DDevice8_GetAvailableTextureMem(p) (p)->lpVtbl->GetAvailableTextureMem(p) +#define IDirect3DDevice8_ResourceManagerDiscardBytes(p,a) (p)->lpVtbl->ResourceManagerDiscardBytes(p,a) +#define IDirect3DDevice8_GetDirect3D(p,a) (p)->lpVtbl->GetDirect3D(p,a) +#define IDirect3DDevice8_GetDeviceCaps(p,a) (p)->lpVtbl->GetDeviceCaps(p,a) +#define IDirect3DDevice8_GetDisplayMode(p,a) (p)->lpVtbl->GetDisplayMode(p,a) +#define IDirect3DDevice8_GetCreationParameters(p,a) (p)->lpVtbl->GetCreationParameters(p,a) +#define IDirect3DDevice8_SetCursorProperties(p,a,b,c) (p)->lpVtbl->SetCursorProperties(p,a,b,c) +#define IDirect3DDevice8_SetCursorPosition(p,a,b,c) (p)->lpVtbl->SetCursorPosition(p,a,b,c) +#define IDirect3DDevice8_ShowCursor(p,a) (p)->lpVtbl->ShowCursor(p,a) +#define IDirect3DDevice8_CreateAdditionalSwapChain(p,a,b) (p)->lpVtbl->CreateAdditionalSwapChain(p,a,b) +#define IDirect3DDevice8_Reset(p,a) (p)->lpVtbl->Reset(p,a) +#define IDirect3DDevice8_Present(p,a,b,c,d) (p)->lpVtbl->Present(p,a,b,c,d) +#define IDirect3DDevice8_GetBackBuffer(p,a,b,c) (p)->lpVtbl->GetBackBuffer(p,a,b,c) +#define IDirect3DDevice8_GetRasterStatus(p,a) (p)->lpVtbl->GetRasterStatus(p,a) +#define IDirect3DDevice8_SetGammaRamp(p,a,b) (p)->lpVtbl->SetGammaRamp(p,a,b) +#define IDirect3DDevice8_GetGammaRamp(p,a) (p)->lpVtbl->GetGammaRamp(p,a) +#define IDirect3DDevice8_CreateTexture(p,a,b,c,d,e,f,g) (p)->lpVtbl->CreateTexture(p,a,b,c,d,e,f,g) +#define IDirect3DDevice8_CreateVolumeTexture(p,a,b,c,d,e,f,g,h) (p)->lpVtbl->CreateVolumeTexture(p,a,b,c,d,e,f,g,h) +#define IDirect3DDevice8_CreateCubeTexture(p,a,b,c,d,e,f) (p)->lpVtbl->CreateCubeTexture(p,a,b,c,d,e,f) +#define IDirect3DDevice8_CreateVertexBuffer(p,a,b,c,d,e) (p)->lpVtbl->CreateVertexBuffer(p,a,b,c,d,e) +#define IDirect3DDevice8_CreateIndexBuffer(p,a,b,c,d,e) (p)->lpVtbl->CreateIndexBuffer(p,a,b,c,d,e) +#define IDirect3DDevice8_CreateRenderTarget(p,a,b,c,d,e,f) (p)->lpVtbl->CreateRenderTarget(p,a,b,c,d,e,f) +#define IDirect3DDevice8_CreateDepthStencilSurface(p,a,b,c,d,e) (p)->lpVtbl->CreateDepthStencilSurface(p,a,b,c,d,e) +#define IDirect3DDevice8_CreateImageSurface(p,a,b,c,d) (p)->lpVtbl->CreateImageSurface(p,a,b,c,d) +#define IDirect3DDevice8_CopyRects(p,a,b,c,d,e) (p)->lpVtbl->CopyRects(p,a,b,c,d,e) +#define IDirect3DDevice8_UpdateTexture(p,a,b) (p)->lpVtbl->UpdateTexture(p,a,b) +#define IDirect3DDevice8_GetFrontBuffer(p,a) (p)->lpVtbl->GetFrontBuffer(p,a) +#define IDirect3DDevice8_SetRenderTarget(p,a,b) (p)->lpVtbl->SetRenderTarget(p,a,b) +#define IDirect3DDevice8_GetRenderTarget(p,a) (p)->lpVtbl->GetRenderTarget(p,a) +#define IDirect3DDevice8_GetDepthStencilSurface(p,a) (p)->lpVtbl->GetDepthStencilSurface(p,a) +#define IDirect3DDevice8_BeginScene(p) (p)->lpVtbl->BeginScene(p) +#define IDirect3DDevice8_EndScene(p) (p)->lpVtbl->EndScene(p) +#define IDirect3DDevice8_Clear(p,a,b,c,d,e,f) (p)->lpVtbl->Clear(p,a,b,c,d,e,f) +#define IDirect3DDevice8_SetTransform(p,a,b) (p)->lpVtbl->SetTransform(p,a,b) +#define IDirect3DDevice8_GetTransform(p,a,b) (p)->lpVtbl->GetTransform(p,a,b) +#define IDirect3DDevice8_MultiplyTransform(p,a,b) (p)->lpVtbl->MultiplyTransform(p,a,b) +#define IDirect3DDevice8_SetViewport(p,a) (p)->lpVtbl->SetViewport(p,a) +#define IDirect3DDevice8_GetViewport(p,a) (p)->lpVtbl->GetViewport(p,a) +#define IDirect3DDevice8_SetMaterial(p,a) (p)->lpVtbl->SetMaterial(p,a) +#define IDirect3DDevice8_GetMaterial(p,a) (p)->lpVtbl->GetMaterial(p,a) +#define IDirect3DDevice8_SetLight(p,a,b) (p)->lpVtbl->SetLight(p,a,b) +#define IDirect3DDevice8_GetLight(p,a,b) (p)->lpVtbl->GetLight(p,a,b) +#define IDirect3DDevice8_LightEnable(p,a,b) (p)->lpVtbl->LightEnable(p,a,b) +#define IDirect3DDevice8_GetLightEnable(p,a,b) (p)->lpVtbl->GetLightEnable(p,a,b) +#define IDirect3DDevice8_SetClipPlane(p,a,b) (p)->lpVtbl->SetClipPlane(p,a,b) +#define IDirect3DDevice8_GetClipPlane(p,a,b) (p)->lpVtbl->GetClipPlane(p,a,b) +#define IDirect3DDevice8_SetRenderState(p,a,b) (p)->lpVtbl->SetRenderState(p,a,b) +#define IDirect3DDevice8_GetRenderState(p,a,b) (p)->lpVtbl->GetRenderState(p,a,b) +#define IDirect3DDevice8_BeginStateBlock(p) (p)->lpVtbl->BeginStateBlock(p) +#define IDirect3DDevice8_EndStateBlock(p,a) (p)->lpVtbl->EndStateBlock(p,a) +#define IDirect3DDevice8_ApplyStateBlock(p,a) (p)->lpVtbl->ApplyStateBlock(p,a) +#define IDirect3DDevice8_CaptureStateBlock(p,a) (p)->lpVtbl->CaptureStateBlock(p,a) +#define IDirect3DDevice8_DeleteStateBlock(p,a) (p)->lpVtbl->DeleteStateBlock(p,a) +#define IDirect3DDevice8_CreateStateBlock(p,a,b) (p)->lpVtbl->CreateStateBlock(p,a,b) +#define IDirect3DDevice8_SetClipStatus(p,a) (p)->lpVtbl->SetClipStatus(p,a) +#define IDirect3DDevice8_GetClipStatus(p,a) (p)->lpVtbl->GetClipStatus(p,a) +#define IDirect3DDevice8_GetTexture(p,a,b) (p)->lpVtbl->GetTexture(p,a,b) +#define IDirect3DDevice8_SetTexture(p,a,b) (p)->lpVtbl->SetTexture(p,a,b) +#define IDirect3DDevice8_GetTextureStageState(p,a,b,c) (p)->lpVtbl->GetTextureStageState(p,a,b,c) +#define IDirect3DDevice8_SetTextureStageState(p,a,b,c) (p)->lpVtbl->SetTextureStageState(p,a,b,c) +#define IDirect3DDevice8_ValidateDevice(p,a) (p)->lpVtbl->ValidateDevice(p,a) +#define IDirect3DDevice8_GetInfo(p,a,b,c) (p)->lpVtbl->GetInfo(p,a,b,c) +#define IDirect3DDevice8_SetPaletteEntries(p,a,b) (p)->lpVtbl->SetPaletteEntries(p,a,b) +#define IDirect3DDevice8_GetPaletteEntries(p,a,b) (p)->lpVtbl->GetPaletteEntries(p,a,b) +#define IDirect3DDevice8_SetCurrentTexturePalette(p,a) (p)->lpVtbl->SetCurrentTexturePalette(p,a) +#define IDirect3DDevice8_GetCurrentTexturePalette(p,a) (p)->lpVtbl->GetCurrentTexturePalette(p,a) +#define IDirect3DDevice8_DrawPrimitive(p,a,b,c) (p)->lpVtbl->DrawPrimitive(p,a,b,c) +#define IDirect3DDevice8_DrawIndexedPrimitive(p,a,b,c,d,e) (p)->lpVtbl->DrawIndexedPrimitive(p,a,b,c,d,e) +#define IDirect3DDevice8_DrawPrimitiveUP(p,a,b,c,d) (p)->lpVtbl->DrawPrimitiveUP(p,a,b,c,d) +#define IDirect3DDevice8_DrawIndexedPrimitiveUP(p,a,b,c,d,e,f,g,h) (p)->lpVtbl->DrawIndexedPrimitiveUP(p,a,b,c,d,e,f,g,h) +#define IDirect3DDevice8_ProcessVertices(p,a,b,c,d,e) (p)->lpVtbl->ProcessVertices(p,a,b,c,d,e) +#define IDirect3DDevice8_CreateVertexShader(p,a,b,c,d) (p)->lpVtbl->CreateVertexShader(p,a,b,c,d) +#define IDirect3DDevice8_SetVertexShader(p,a) (p)->lpVtbl->SetVertexShader(p,a) +#define IDirect3DDevice8_GetVertexShader(p,a) (p)->lpVtbl->GetVertexShader(p,a) +#define IDirect3DDevice8_DeleteVertexShader(p,a) (p)->lpVtbl->DeleteVertexShader(p,a) +#define IDirect3DDevice8_SetVertexShaderConstant(p,a,b,c) (p)->lpVtbl->SetVertexShaderConstant(p,a,b,c) +#define IDirect3DDevice8_GetVertexShaderConstant(p,a,b,c) (p)->lpVtbl->GetVertexShaderConstant(p,a,b,c) +#define IDirect3DDevice8_GetVertexShaderDeclaration(p,a,b,c) (p)->lpVtbl->GetVertexShaderDeclaration(p,a,b,c) +#define IDirect3DDevice8_GetVertexShaderFunction(p,a,b,c) (p)->lpVtbl->GetVertexShaderFunction(p,a,b,c) +#define IDirect3DDevice8_SetStreamSource(p,a,b,c) (p)->lpVtbl->SetStreamSource(p,a,b,c) +#define IDirect3DDevice8_GetStreamSource(p,a,b,c) (p)->lpVtbl->GetStreamSource(p,a,b,c) +#define IDirect3DDevice8_SetIndices(p,a,b) (p)->lpVtbl->SetIndices(p,a,b) +#define IDirect3DDevice8_GetIndices(p,a,b) (p)->lpVtbl->GetIndices(p,a,b) +#define IDirect3DDevice8_CreatePixelShader(p,a,b) (p)->lpVtbl->CreatePixelShader(p,a,b) +#define IDirect3DDevice8_SetPixelShader(p,a) (p)->lpVtbl->SetPixelShader(p,a) +#define IDirect3DDevice8_GetPixelShader(p,a) (p)->lpVtbl->GetPixelShader(p,a) +#define IDirect3DDevice8_DeletePixelShader(p,a) (p)->lpVtbl->DeletePixelShader(p,a) +#define IDirect3DDevice8_SetPixelShaderConstant(p,a,b,c) (p)->lpVtbl->SetPixelShaderConstant(p,a,b,c) +#define IDirect3DDevice8_GetPixelShaderConstant(p,a,b,c) (p)->lpVtbl->GetPixelShaderConstant(p,a,b,c) +#define IDirect3DDevice8_GetPixelShaderFunction(p,a,b,c) (p)->lpVtbl->GetPixelShaderFunction(p,a,b,c) +#define IDirect3DDevice8_DrawRectPatch(p,a,b,c) (p)->lpVtbl->DrawRectPatch(p,a,b,c) +#define IDirect3DDevice8_DrawTriPatch(p,a,b,c) (p)->lpVtbl->DrawTriPatch(p,a,b,c) +#define IDirect3DDevice8_DeletePatch(p,a) (p)->lpVtbl->DeletePatch(p,a) +#else +#define IDirect3DDevice8_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirect3DDevice8_AddRef(p) (p)->AddRef() +#define IDirect3DDevice8_Release(p) (p)->Release() +#define IDirect3DDevice8_TestCooperativeLevel(p) (p)->TestCooperativeLevel() +#define IDirect3DDevice8_GetAvailableTextureMem(p) (p)->GetAvailableTextureMem() +#define IDirect3DDevice8_ResourceManagerDiscardBytes(p,a) (p)->ResourceManagerDiscardBytes(a) +#define IDirect3DDevice8_GetDirect3D(p,a) (p)->GetDirect3D(a) +#define IDirect3DDevice8_GetDeviceCaps(p,a) (p)->GetDeviceCaps(a) +#define IDirect3DDevice8_GetDisplayMode(p,a) (p)->GetDisplayMode(a) +#define IDirect3DDevice8_GetCreationParameters(p,a) (p)->GetCreationParameters(a) +#define IDirect3DDevice8_SetCursorProperties(p,a,b,c) (p)->SetCursorProperties(a,b,c) +#define IDirect3DDevice8_SetCursorPosition(p,a,b,c) (p)->SetCursorPosition(a,b,c) +#define IDirect3DDevice8_ShowCursor(p,a) (p)->ShowCursor(a) +#define IDirect3DDevice8_CreateAdditionalSwapChain(p,a,b) (p)->CreateAdditionalSwapChain(a,b) +#define IDirect3DDevice8_Reset(p,a) (p)->Reset(a) +#define IDirect3DDevice8_Present(p,a,b,c,d) (p)->Present(a,b,c,d) +#define IDirect3DDevice8_GetBackBuffer(p,a,b,c) (p)->GetBackBuffer(a,b,c) +#define IDirect3DDevice8_GetRasterStatus(p,a) (p)->GetRasterStatus(a) +#define IDirect3DDevice8_SetGammaRamp(p,a,b) (p)->SetGammaRamp(a,b) +#define IDirect3DDevice8_GetGammaRamp(p,a) (p)->GetGammaRamp(a) +#define IDirect3DDevice8_CreateTexture(p,a,b,c,d,e,f,g) (p)->CreateTexture(a,b,c,d,e,f,g) +#define IDirect3DDevice8_CreateVolumeTexture(p,a,b,c,d,e,f,g,h) (p)->CreateVolumeTexture(a,b,c,d,e,f,g,h) +#define IDirect3DDevice8_CreateCubeTexture(p,a,b,c,d,e,f) (p)->CreateCubeTexture(a,b,c,d,e,f) +#define IDirect3DDevice8_CreateVertexBuffer(p,a,b,c,d,e) (p)->CreateVertexBuffer(a,b,c,d,e) +#define IDirect3DDevice8_CreateIndexBuffer(p,a,b,c,d,e) (p)->CreateIndexBuffer(a,b,c,d,e) +#define IDirect3DDevice8_CreateRenderTarget(p,a,b,c,d,e,f) (p)->CreateRenderTarget(a,b,c,d,e,f) +#define IDirect3DDevice8_CreateDepthStencilSurface(p,a,b,c,d,e) (p)->CreateDepthStencilSurface(a,b,c,d,e) +#define IDirect3DDevice8_CreateImageSurface(p,a,b,c,d) (p)->CreateImageSurface(a,b,c,d) +#define IDirect3DDevice8_CopyRects(p,a,b,c,d,e) (p)->CopyRects(a,b,c,d,e) +#define IDirect3DDevice8_UpdateTexture(p,a,b) (p)->UpdateTexture(a,b) +#define IDirect3DDevice8_GetFrontBuffer(p,a) (p)->GetFrontBuffer(a) +#define IDirect3DDevice8_SetRenderTarget(p,a,b) (p)->SetRenderTarget(a,b) +#define IDirect3DDevice8_GetRenderTarget(p,a) (p)->GetRenderTarget(a) +#define IDirect3DDevice8_GetDepthStencilSurface(p,a) (p)->GetDepthStencilSurface(a) +#define IDirect3DDevice8_BeginScene(p) (p)->BeginScene() +#define IDirect3DDevice8_EndScene(p) (p)->EndScene() +#define IDirect3DDevice8_Clear(p,a,b,c,d,e,f) (p)->Clear(a,b,c,d,e,f) +#define IDirect3DDevice8_SetTransform(p,a,b) (p)->SetTransform(a,b) +#define IDirect3DDevice8_GetTransform(p,a,b) (p)->GetTransform(a,b) +#define IDirect3DDevice8_MultiplyTransform(p,a,b) (p)->MultiplyTransform(a,b) +#define IDirect3DDevice8_SetViewport(p,a) (p)->SetViewport(a) +#define IDirect3DDevice8_GetViewport(p,a) (p)->GetViewport(a) +#define IDirect3DDevice8_SetMaterial(p,a) (p)->SetMaterial(a) +#define IDirect3DDevice8_GetMaterial(p,a) (p)->GetMaterial(a) +#define IDirect3DDevice8_SetLight(p,a,b) (p)->SetLight(a,b) +#define IDirect3DDevice8_GetLight(p,a,b) (p)->GetLight(a,b) +#define IDirect3DDevice8_LightEnable(p,a,b) (p)->LightEnable(a,b) +#define IDirect3DDevice8_GetLightEnable(p,a,b) (p)->GetLightEnable(a,b) +#define IDirect3DDevice8_SetClipPlane(p,a,b) (p)->SetClipPlane(a,b) +#define IDirect3DDevice8_GetClipPlane(p,a,b) (p)->GetClipPlane(a,b) +#define IDirect3DDevice8_SetRenderState(p,a,b) (p)->SetRenderState(a,b) +#define IDirect3DDevice8_GetRenderState(p,a,b) (p)->GetRenderState(a,b) +#define IDirect3DDevice8_BeginStateBlock(p) (p)->BeginStateBlock() +#define IDirect3DDevice8_EndStateBlock(p,a) (p)->EndStateBlock(a) +#define IDirect3DDevice8_ApplyStateBlock(p,a) (p)->ApplyStateBlock(a) +#define IDirect3DDevice8_CaptureStateBlock(p,a) (p)->CaptureStateBlock(a) +#define IDirect3DDevice8_DeleteStateBlock(p,a) (p)->DeleteStateBlock(a) +#define IDirect3DDevice8_CreateStateBlock(p,a,b) (p)->CreateStateBlock(a,b) +#define IDirect3DDevice8_SetClipStatus(p,a) (p)->SetClipStatus(a) +#define IDirect3DDevice8_GetClipStatus(p,a) (p)->GetClipStatus(a) +#define IDirect3DDevice8_GetTexture(p,a,b) (p)->GetTexture(a,b) +#define IDirect3DDevice8_SetTexture(p,a,b) (p)->SetTexture(a,b) +#define IDirect3DDevice8_GetTextureStageState(p,a,b,c) (p)->GetTextureStageState(a,b,c) +#define IDirect3DDevice8_SetTextureStageState(p,a,b,c) (p)->SetTextureStageState(a,b,c) +#define IDirect3DDevice8_ValidateDevice(p,a) (p)->ValidateDevice(a) +#define IDirect3DDevice8_GetInfo(p,a,b,c) (p)->GetInfo(a,b,c) +#define IDirect3DDevice8_SetPaletteEntries(p,a,b) (p)->SetPaletteEntries(a,b) +#define IDirect3DDevice8_GetPaletteEntries(p,a,b) (p)->GetPaletteEntries(a,b) +#define IDirect3DDevice8_SetCurrentTexturePalette(p,a) (p)->SetCurrentTexturePalette(a) +#define IDirect3DDevice8_GetCurrentTexturePalette(p,a) (p)->GetCurrentTexturePalette(a) +#define IDirect3DDevice8_DrawPrimitive(p,a,b,c) (p)->DrawPrimitive(a,b,c) +#define IDirect3DDevice8_DrawIndexedPrimitive(p,a,b,c,d,e) (p)->DrawIndexedPrimitive(a,b,c,d,e) +#define IDirect3DDevice8_DrawPrimitiveUP(p,a,b,c,d) (p)->DrawPrimitiveUP(a,b,c,d) +#define IDirect3DDevice8_DrawIndexedPrimitiveUP(p,a,b,c,d,e,f,g,h) (p)->DrawIndexedPrimitiveUP(a,b,c,d,e,f,g,h) +#define IDirect3DDevice8_ProcessVertices(p,a,b,c,d,e) (p)->ProcessVertices(a,b,c,d,e) +#define IDirect3DDevice8_CreateVertexShader(p,a,b,c,d) (p)->CreateVertexShader(a,b,c,d) +#define IDirect3DDevice8_SetVertexShader(p,a) (p)->SetVertexShader(a) +#define IDirect3DDevice8_GetVertexShader(p,a) (p)->GetVertexShader(a) +#define IDirect3DDevice8_DeleteVertexShader(p,a) (p)->DeleteVertexShader(a) +#define IDirect3DDevice8_SetVertexShaderConstant(p,a,b,c) (p)->SetVertexShaderConstant(a,b,c) +#define IDirect3DDevice8_GetVertexShaderConstant(p,a,b,c) (p)->GetVertexShaderConstant(a,b,c) +#define IDirect3DDevice8_GetVertexShaderDeclaration(p,a,b,c) (p)->GetVertexShaderDeclaration(a,b,c) +#define IDirect3DDevice8_GetVertexShaderFunction(p,a,b,c) (p)->GetVertexShaderFunction(a,b,c) +#define IDirect3DDevice8_SetStreamSource(p,a,b,c) (p)->SetStreamSource(a,b,c) +#define IDirect3DDevice8_GetStreamSource(p,a,b,c) (p)->GetStreamSource(a,b,c) +#define IDirect3DDevice8_SetIndices(p,a,b) (p)->SetIndices(a,b) +#define IDirect3DDevice8_GetIndices(p,a,b) (p)->GetIndices(a,b) +#define IDirect3DDevice8_CreatePixelShader(p,a,b) (p)->CreatePixelShader(a,b) +#define IDirect3DDevice8_SetPixelShader(p,a) (p)->SetPixelShader(a) +#define IDirect3DDevice8_GetPixelShader(p,a) (p)->GetPixelShader(a) +#define IDirect3DDevice8_DeletePixelShader(p,a) (p)->DeletePixelShader(a) +#define IDirect3DDevice8_SetPixelShaderConstant(p,a,b,c) (p)->SetPixelShaderConstant(a,b,c) +#define IDirect3DDevice8_GetPixelShaderConstant(p,a,b,c) (p)->GetPixelShaderConstant(a,b,c) +#define IDirect3DDevice8_GetPixelShaderFunction(p,a,b,c) (p)->GetPixelShaderFunction(a,b,c) +#define IDirect3DDevice8_DrawRectPatch(p,a,b,c) (p)->DrawRectPatch(a,b,c) +#define IDirect3DDevice8_DrawTriPatch(p,a,b,c) (p)->DrawTriPatch(a,b,c) +#define IDirect3DDevice8_DeletePatch(p,a) (p)->DeletePatch(a) +#endif + + + +#undef INTERFACE +#define INTERFACE IDirect3DSwapChain8 + +DECLARE_INTERFACE_(IDirect3DSwapChain8, IUnknown) +{ + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + /*** IDirect3DSwapChain8 methods ***/ + STDMETHOD(Present)(THIS_ CONST RECT* pSourceRect,CONST RECT* pDestRect,HWND hDestWindowOverride,CONST RGNDATA* pDirtyRegion) PURE; + STDMETHOD(GetBackBuffer)(THIS_ UINT BackBuffer,D3DBACKBUFFER_TYPE Type,IDirect3DSurface8** ppBackBuffer) PURE; +}; + +typedef struct IDirect3DSwapChain8 *LPDIRECT3DSWAPCHAIN8, *PDIRECT3DSWAPCHAIN8; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirect3DSwapChain8_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirect3DSwapChain8_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirect3DSwapChain8_Release(p) (p)->lpVtbl->Release(p) +#define IDirect3DSwapChain8_Present(p,a,b,c,d) (p)->lpVtbl->Present(p,a,b,c,d) +#define IDirect3DSwapChain8_GetBackBuffer(p,a,b,c) (p)->lpVtbl->GetBackBuffer(p,a,b,c) +#else +#define IDirect3DSwapChain8_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirect3DSwapChain8_AddRef(p) (p)->AddRef() +#define IDirect3DSwapChain8_Release(p) (p)->Release() +#define IDirect3DSwapChain8_Present(p,a,b,c,d) (p)->Present(a,b,c,d) +#define IDirect3DSwapChain8_GetBackBuffer(p,a,b,c) (p)->GetBackBuffer(a,b,c) +#endif + + + +#undef INTERFACE +#define INTERFACE IDirect3DResource8 + +DECLARE_INTERFACE_(IDirect3DResource8, IUnknown) +{ + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + /*** IDirect3DResource8 methods ***/ + STDMETHOD(GetDevice)(THIS_ IDirect3DDevice8** ppDevice) PURE; + STDMETHOD(SetPrivateData)(THIS_ REFGUID refguid,CONST void* pData,DWORD SizeOfData,DWORD Flags) PURE; + STDMETHOD(GetPrivateData)(THIS_ REFGUID refguid,void* pData,DWORD* pSizeOfData) PURE; + STDMETHOD(FreePrivateData)(THIS_ REFGUID refguid) PURE; + STDMETHOD_(DWORD, SetPriority)(THIS_ DWORD PriorityNew) PURE; + STDMETHOD_(DWORD, GetPriority)(THIS) PURE; + STDMETHOD_(void, PreLoad)(THIS) PURE; + STDMETHOD_(D3DRESOURCETYPE, GetType)(THIS) PURE; +}; + +typedef struct IDirect3DResource8 *LPDIRECT3DRESOURCE8, *PDIRECT3DRESOURCE8; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirect3DResource8_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirect3DResource8_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirect3DResource8_Release(p) (p)->lpVtbl->Release(p) +#define IDirect3DResource8_GetDevice(p,a) (p)->lpVtbl->GetDevice(p,a) +#define IDirect3DResource8_SetPrivateData(p,a,b,c,d) (p)->lpVtbl->SetPrivateData(p,a,b,c,d) +#define IDirect3DResource8_GetPrivateData(p,a,b,c) (p)->lpVtbl->GetPrivateData(p,a,b,c) +#define IDirect3DResource8_FreePrivateData(p,a) (p)->lpVtbl->FreePrivateData(p,a) +#define IDirect3DResource8_SetPriority(p,a) (p)->lpVtbl->SetPriority(p,a) +#define IDirect3DResource8_GetPriority(p) (p)->lpVtbl->GetPriority(p) +#define IDirect3DResource8_PreLoad(p) (p)->lpVtbl->PreLoad(p) +#define IDirect3DResource8_GetType(p) (p)->lpVtbl->GetType(p) +#else +#define IDirect3DResource8_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirect3DResource8_AddRef(p) (p)->AddRef() +#define IDirect3DResource8_Release(p) (p)->Release() +#define IDirect3DResource8_GetDevice(p,a) (p)->GetDevice(a) +#define IDirect3DResource8_SetPrivateData(p,a,b,c,d) (p)->SetPrivateData(a,b,c,d) +#define IDirect3DResource8_GetPrivateData(p,a,b,c) (p)->GetPrivateData(a,b,c) +#define IDirect3DResource8_FreePrivateData(p,a) (p)->FreePrivateData(a) +#define IDirect3DResource8_SetPriority(p,a) (p)->SetPriority(a) +#define IDirect3DResource8_GetPriority(p) (p)->GetPriority() +#define IDirect3DResource8_PreLoad(p) (p)->PreLoad() +#define IDirect3DResource8_GetType(p) (p)->GetType() +#endif + + + + +#undef INTERFACE +#define INTERFACE IDirect3DBaseTexture8 + +DECLARE_INTERFACE_(IDirect3DBaseTexture8, IDirect3DResource8) +{ + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + /*** IDirect3DResource8 methods ***/ + STDMETHOD(GetDevice)(THIS_ IDirect3DDevice8** ppDevice) PURE; + STDMETHOD(SetPrivateData)(THIS_ REFGUID refguid,CONST void* pData,DWORD SizeOfData,DWORD Flags) PURE; + STDMETHOD(GetPrivateData)(THIS_ REFGUID refguid,void* pData,DWORD* pSizeOfData) PURE; + STDMETHOD(FreePrivateData)(THIS_ REFGUID refguid) PURE; + STDMETHOD_(DWORD, SetPriority)(THIS_ DWORD PriorityNew) PURE; + STDMETHOD_(DWORD, GetPriority)(THIS) PURE; + STDMETHOD_(void, PreLoad)(THIS) PURE; + STDMETHOD_(D3DRESOURCETYPE, GetType)(THIS) PURE; + STDMETHOD_(DWORD, SetLOD)(THIS_ DWORD LODNew) PURE; + STDMETHOD_(DWORD, GetLOD)(THIS) PURE; + STDMETHOD_(DWORD, GetLevelCount)(THIS) PURE; +}; + +typedef struct IDirect3DBaseTexture8 *LPDIRECT3DBASETEXTURE8, *PDIRECT3DBASETEXTURE8; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirect3DBaseTexture8_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirect3DBaseTexture8_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirect3DBaseTexture8_Release(p) (p)->lpVtbl->Release(p) +#define IDirect3DBaseTexture8_GetDevice(p,a) (p)->lpVtbl->GetDevice(p,a) +#define IDirect3DBaseTexture8_SetPrivateData(p,a,b,c,d) (p)->lpVtbl->SetPrivateData(p,a,b,c,d) +#define IDirect3DBaseTexture8_GetPrivateData(p,a,b,c) (p)->lpVtbl->GetPrivateData(p,a,b,c) +#define IDirect3DBaseTexture8_FreePrivateData(p,a) (p)->lpVtbl->FreePrivateData(p,a) +#define IDirect3DBaseTexture8_SetPriority(p,a) (p)->lpVtbl->SetPriority(p,a) +#define IDirect3DBaseTexture8_GetPriority(p) (p)->lpVtbl->GetPriority(p) +#define IDirect3DBaseTexture8_PreLoad(p) (p)->lpVtbl->PreLoad(p) +#define IDirect3DBaseTexture8_GetType(p) (p)->lpVtbl->GetType(p) +#define IDirect3DBaseTexture8_SetLOD(p,a) (p)->lpVtbl->SetLOD(p,a) +#define IDirect3DBaseTexture8_GetLOD(p) (p)->lpVtbl->GetLOD(p) +#define IDirect3DBaseTexture8_GetLevelCount(p) (p)->lpVtbl->GetLevelCount(p) +#else +#define IDirect3DBaseTexture8_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirect3DBaseTexture8_AddRef(p) (p)->AddRef() +#define IDirect3DBaseTexture8_Release(p) (p)->Release() +#define IDirect3DBaseTexture8_GetDevice(p,a) (p)->GetDevice(a) +#define IDirect3DBaseTexture8_SetPrivateData(p,a,b,c,d) (p)->SetPrivateData(a,b,c,d) +#define IDirect3DBaseTexture8_GetPrivateData(p,a,b,c) (p)->GetPrivateData(a,b,c) +#define IDirect3DBaseTexture8_FreePrivateData(p,a) (p)->FreePrivateData(a) +#define IDirect3DBaseTexture8_SetPriority(p,a) (p)->SetPriority(a) +#define IDirect3DBaseTexture8_GetPriority(p) (p)->GetPriority() +#define IDirect3DBaseTexture8_PreLoad(p) (p)->PreLoad() +#define IDirect3DBaseTexture8_GetType(p) (p)->GetType() +#define IDirect3DBaseTexture8_SetLOD(p,a) (p)->SetLOD(a) +#define IDirect3DBaseTexture8_GetLOD(p) (p)->GetLOD() +#define IDirect3DBaseTexture8_GetLevelCount(p) (p)->GetLevelCount() +#endif + + + + + +#undef INTERFACE +#define INTERFACE IDirect3DTexture8 + +DECLARE_INTERFACE_(IDirect3DTexture8, IDirect3DBaseTexture8) +{ + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + /*** IDirect3DBaseTexture8 methods ***/ + STDMETHOD(GetDevice)(THIS_ IDirect3DDevice8** ppDevice) PURE; + STDMETHOD(SetPrivateData)(THIS_ REFGUID refguid,CONST void* pData,DWORD SizeOfData,DWORD Flags) PURE; + STDMETHOD(GetPrivateData)(THIS_ REFGUID refguid,void* pData,DWORD* pSizeOfData) PURE; + STDMETHOD(FreePrivateData)(THIS_ REFGUID refguid) PURE; + STDMETHOD_(DWORD, SetPriority)(THIS_ DWORD PriorityNew) PURE; + STDMETHOD_(DWORD, GetPriority)(THIS) PURE; + STDMETHOD_(void, PreLoad)(THIS) PURE; + STDMETHOD_(D3DRESOURCETYPE, GetType)(THIS) PURE; + STDMETHOD_(DWORD, SetLOD)(THIS_ DWORD LODNew) PURE; + STDMETHOD_(DWORD, GetLOD)(THIS) PURE; + STDMETHOD_(DWORD, GetLevelCount)(THIS) PURE; + STDMETHOD(GetLevelDesc)(THIS_ UINT Level,D3DSURFACE_DESC *pDesc) PURE; + STDMETHOD(GetSurfaceLevel)(THIS_ UINT Level,IDirect3DSurface8** ppSurfaceLevel) PURE; + STDMETHOD(LockRect)(THIS_ UINT Level,D3DLOCKED_RECT* pLockedRect,CONST RECT* pRect,DWORD Flags) PURE; + STDMETHOD(UnlockRect)(THIS_ UINT Level) PURE; + STDMETHOD(AddDirtyRect)(THIS_ CONST RECT* pDirtyRect) PURE; +}; + +typedef struct IDirect3DTexture8 *LPDIRECT3DTEXTURE8, *PDIRECT3DTEXTURE8; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirect3DTexture8_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirect3DTexture8_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirect3DTexture8_Release(p) (p)->lpVtbl->Release(p) +#define IDirect3DTexture8_GetDevice(p,a) (p)->lpVtbl->GetDevice(p,a) +#define IDirect3DTexture8_SetPrivateData(p,a,b,c,d) (p)->lpVtbl->SetPrivateData(p,a,b,c,d) +#define IDirect3DTexture8_GetPrivateData(p,a,b,c) (p)->lpVtbl->GetPrivateData(p,a,b,c) +#define IDirect3DTexture8_FreePrivateData(p,a) (p)->lpVtbl->FreePrivateData(p,a) +#define IDirect3DTexture8_SetPriority(p,a) (p)->lpVtbl->SetPriority(p,a) +#define IDirect3DTexture8_GetPriority(p) (p)->lpVtbl->GetPriority(p) +#define IDirect3DTexture8_PreLoad(p) (p)->lpVtbl->PreLoad(p) +#define IDirect3DTexture8_GetType(p) (p)->lpVtbl->GetType(p) +#define IDirect3DTexture8_SetLOD(p,a) (p)->lpVtbl->SetLOD(p,a) +#define IDirect3DTexture8_GetLOD(p) (p)->lpVtbl->GetLOD(p) +#define IDirect3DTexture8_GetLevelCount(p) (p)->lpVtbl->GetLevelCount(p) +#define IDirect3DTexture8_GetLevelDesc(p,a,b) (p)->lpVtbl->GetLevelDesc(p,a,b) +#define IDirect3DTexture8_GetSurfaceLevel(p,a,b) (p)->lpVtbl->GetSurfaceLevel(p,a,b) +#define IDirect3DTexture8_LockRect(p,a,b,c,d) (p)->lpVtbl->LockRect(p,a,b,c,d) +#define IDirect3DTexture8_UnlockRect(p,a) (p)->lpVtbl->UnlockRect(p,a) +#define IDirect3DTexture8_AddDirtyRect(p,a) (p)->lpVtbl->AddDirtyRect(p,a) +#else +#define IDirect3DTexture8_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirect3DTexture8_AddRef(p) (p)->AddRef() +#define IDirect3DTexture8_Release(p) (p)->Release() +#define IDirect3DTexture8_GetDevice(p,a) (p)->GetDevice(a) +#define IDirect3DTexture8_SetPrivateData(p,a,b,c,d) (p)->SetPrivateData(a,b,c,d) +#define IDirect3DTexture8_GetPrivateData(p,a,b,c) (p)->GetPrivateData(a,b,c) +#define IDirect3DTexture8_FreePrivateData(p,a) (p)->FreePrivateData(a) +#define IDirect3DTexture8_SetPriority(p,a) (p)->SetPriority(a) +#define IDirect3DTexture8_GetPriority(p) (p)->GetPriority() +#define IDirect3DTexture8_PreLoad(p) (p)->PreLoad() +#define IDirect3DTexture8_GetType(p) (p)->GetType() +#define IDirect3DTexture8_SetLOD(p,a) (p)->SetLOD(a) +#define IDirect3DTexture8_GetLOD(p) (p)->GetLOD() +#define IDirect3DTexture8_GetLevelCount(p) (p)->GetLevelCount() +#define IDirect3DTexture8_GetLevelDesc(p,a,b) (p)->GetLevelDesc(a,b) +#define IDirect3DTexture8_GetSurfaceLevel(p,a,b) (p)->GetSurfaceLevel(a,b) +#define IDirect3DTexture8_LockRect(p,a,b,c,d) (p)->LockRect(a,b,c,d) +#define IDirect3DTexture8_UnlockRect(p,a) (p)->UnlockRect(a) +#define IDirect3DTexture8_AddDirtyRect(p,a) (p)->AddDirtyRect(a) +#endif + + + + + +#undef INTERFACE +#define INTERFACE IDirect3DVolumeTexture8 + +DECLARE_INTERFACE_(IDirect3DVolumeTexture8, IDirect3DBaseTexture8) +{ + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + /*** IDirect3DBaseTexture8 methods ***/ + STDMETHOD(GetDevice)(THIS_ IDirect3DDevice8** ppDevice) PURE; + STDMETHOD(SetPrivateData)(THIS_ REFGUID refguid,CONST void* pData,DWORD SizeOfData,DWORD Flags) PURE; + STDMETHOD(GetPrivateData)(THIS_ REFGUID refguid,void* pData,DWORD* pSizeOfData) PURE; + STDMETHOD(FreePrivateData)(THIS_ REFGUID refguid) PURE; + STDMETHOD_(DWORD, SetPriority)(THIS_ DWORD PriorityNew) PURE; + STDMETHOD_(DWORD, GetPriority)(THIS) PURE; + STDMETHOD_(void, PreLoad)(THIS) PURE; + STDMETHOD_(D3DRESOURCETYPE, GetType)(THIS) PURE; + STDMETHOD_(DWORD, SetLOD)(THIS_ DWORD LODNew) PURE; + STDMETHOD_(DWORD, GetLOD)(THIS) PURE; + STDMETHOD_(DWORD, GetLevelCount)(THIS) PURE; + STDMETHOD(GetLevelDesc)(THIS_ UINT Level,D3DVOLUME_DESC *pDesc) PURE; + STDMETHOD(GetVolumeLevel)(THIS_ UINT Level,IDirect3DVolume8** ppVolumeLevel) PURE; + STDMETHOD(LockBox)(THIS_ UINT Level,D3DLOCKED_BOX* pLockedVolume,CONST D3DBOX* pBox,DWORD Flags) PURE; + STDMETHOD(UnlockBox)(THIS_ UINT Level) PURE; + STDMETHOD(AddDirtyBox)(THIS_ CONST D3DBOX* pDirtyBox) PURE; +}; + +typedef struct IDirect3DVolumeTexture8 *LPDIRECT3DVOLUMETEXTURE8, *PDIRECT3DVOLUMETEXTURE8; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirect3DVolumeTexture8_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirect3DVolumeTexture8_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirect3DVolumeTexture8_Release(p) (p)->lpVtbl->Release(p) +#define IDirect3DVolumeTexture8_GetDevice(p,a) (p)->lpVtbl->GetDevice(p,a) +#define IDirect3DVolumeTexture8_SetPrivateData(p,a,b,c,d) (p)->lpVtbl->SetPrivateData(p,a,b,c,d) +#define IDirect3DVolumeTexture8_GetPrivateData(p,a,b,c) (p)->lpVtbl->GetPrivateData(p,a,b,c) +#define IDirect3DVolumeTexture8_FreePrivateData(p,a) (p)->lpVtbl->FreePrivateData(p,a) +#define IDirect3DVolumeTexture8_SetPriority(p,a) (p)->lpVtbl->SetPriority(p,a) +#define IDirect3DVolumeTexture8_GetPriority(p) (p)->lpVtbl->GetPriority(p) +#define IDirect3DVolumeTexture8_PreLoad(p) (p)->lpVtbl->PreLoad(p) +#define IDirect3DVolumeTexture8_GetType(p) (p)->lpVtbl->GetType(p) +#define IDirect3DVolumeTexture8_SetLOD(p,a) (p)->lpVtbl->SetLOD(p,a) +#define IDirect3DVolumeTexture8_GetLOD(p) (p)->lpVtbl->GetLOD(p) +#define IDirect3DVolumeTexture8_GetLevelCount(p) (p)->lpVtbl->GetLevelCount(p) +#define IDirect3DVolumeTexture8_GetLevelDesc(p,a,b) (p)->lpVtbl->GetLevelDesc(p,a,b) +#define IDirect3DVolumeTexture8_GetVolumeLevel(p,a,b) (p)->lpVtbl->GetVolumeLevel(p,a,b) +#define IDirect3DVolumeTexture8_LockBox(p,a,b,c,d) (p)->lpVtbl->LockBox(p,a,b,c,d) +#define IDirect3DVolumeTexture8_UnlockBox(p,a) (p)->lpVtbl->UnlockBox(p,a) +#define IDirect3DVolumeTexture8_AddDirtyBox(p,a) (p)->lpVtbl->AddDirtyBox(p,a) +#else +#define IDirect3DVolumeTexture8_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirect3DVolumeTexture8_AddRef(p) (p)->AddRef() +#define IDirect3DVolumeTexture8_Release(p) (p)->Release() +#define IDirect3DVolumeTexture8_GetDevice(p,a) (p)->GetDevice(a) +#define IDirect3DVolumeTexture8_SetPrivateData(p,a,b,c,d) (p)->SetPrivateData(a,b,c,d) +#define IDirect3DVolumeTexture8_GetPrivateData(p,a,b,c) (p)->GetPrivateData(a,b,c) +#define IDirect3DVolumeTexture8_FreePrivateData(p,a) (p)->FreePrivateData(a) +#define IDirect3DVolumeTexture8_SetPriority(p,a) (p)->SetPriority(a) +#define IDirect3DVolumeTexture8_GetPriority(p) (p)->GetPriority() +#define IDirect3DVolumeTexture8_PreLoad(p) (p)->PreLoad() +#define IDirect3DVolumeTexture8_GetType(p) (p)->GetType() +#define IDirect3DVolumeTexture8_SetLOD(p,a) (p)->SetLOD(a) +#define IDirect3DVolumeTexture8_GetLOD(p) (p)->GetLOD() +#define IDirect3DVolumeTexture8_GetLevelCount(p) (p)->GetLevelCount() +#define IDirect3DVolumeTexture8_GetLevelDesc(p,a,b) (p)->GetLevelDesc(a,b) +#define IDirect3DVolumeTexture8_GetVolumeLevel(p,a,b) (p)->GetVolumeLevel(a,b) +#define IDirect3DVolumeTexture8_LockBox(p,a,b,c,d) (p)->LockBox(a,b,c,d) +#define IDirect3DVolumeTexture8_UnlockBox(p,a) (p)->UnlockBox(a) +#define IDirect3DVolumeTexture8_AddDirtyBox(p,a) (p)->AddDirtyBox(a) +#endif + + + + + +#undef INTERFACE +#define INTERFACE IDirect3DCubeTexture8 + +DECLARE_INTERFACE_(IDirect3DCubeTexture8, IDirect3DBaseTexture8) +{ + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + /*** IDirect3DBaseTexture8 methods ***/ + STDMETHOD(GetDevice)(THIS_ IDirect3DDevice8** ppDevice) PURE; + STDMETHOD(SetPrivateData)(THIS_ REFGUID refguid,CONST void* pData,DWORD SizeOfData,DWORD Flags) PURE; + STDMETHOD(GetPrivateData)(THIS_ REFGUID refguid,void* pData,DWORD* pSizeOfData) PURE; + STDMETHOD(FreePrivateData)(THIS_ REFGUID refguid) PURE; + STDMETHOD_(DWORD, SetPriority)(THIS_ DWORD PriorityNew) PURE; + STDMETHOD_(DWORD, GetPriority)(THIS) PURE; + STDMETHOD_(void, PreLoad)(THIS) PURE; + STDMETHOD_(D3DRESOURCETYPE, GetType)(THIS) PURE; + STDMETHOD_(DWORD, SetLOD)(THIS_ DWORD LODNew) PURE; + STDMETHOD_(DWORD, GetLOD)(THIS) PURE; + STDMETHOD_(DWORD, GetLevelCount)(THIS) PURE; + STDMETHOD(GetLevelDesc)(THIS_ UINT Level,D3DSURFACE_DESC *pDesc) PURE; + STDMETHOD(GetCubeMapSurface)(THIS_ D3DCUBEMAP_FACES FaceType,UINT Level,IDirect3DSurface8** ppCubeMapSurface) PURE; + STDMETHOD(LockRect)(THIS_ D3DCUBEMAP_FACES FaceType,UINT Level,D3DLOCKED_RECT* pLockedRect,CONST RECT* pRect,DWORD Flags) PURE; + STDMETHOD(UnlockRect)(THIS_ D3DCUBEMAP_FACES FaceType,UINT Level) PURE; + STDMETHOD(AddDirtyRect)(THIS_ D3DCUBEMAP_FACES FaceType,CONST RECT* pDirtyRect) PURE; +}; + +typedef struct IDirect3DCubeTexture8 *LPDIRECT3DCUBETEXTURE8, *PDIRECT3DCUBETEXTURE8; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirect3DCubeTexture8_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirect3DCubeTexture8_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirect3DCubeTexture8_Release(p) (p)->lpVtbl->Release(p) +#define IDirect3DCubeTexture8_GetDevice(p,a) (p)->lpVtbl->GetDevice(p,a) +#define IDirect3DCubeTexture8_SetPrivateData(p,a,b,c,d) (p)->lpVtbl->SetPrivateData(p,a,b,c,d) +#define IDirect3DCubeTexture8_GetPrivateData(p,a,b,c) (p)->lpVtbl->GetPrivateData(p,a,b,c) +#define IDirect3DCubeTexture8_FreePrivateData(p,a) (p)->lpVtbl->FreePrivateData(p,a) +#define IDirect3DCubeTexture8_SetPriority(p,a) (p)->lpVtbl->SetPriority(p,a) +#define IDirect3DCubeTexture8_GetPriority(p) (p)->lpVtbl->GetPriority(p) +#define IDirect3DCubeTexture8_PreLoad(p) (p)->lpVtbl->PreLoad(p) +#define IDirect3DCubeTexture8_GetType(p) (p)->lpVtbl->GetType(p) +#define IDirect3DCubeTexture8_SetLOD(p,a) (p)->lpVtbl->SetLOD(p,a) +#define IDirect3DCubeTexture8_GetLOD(p) (p)->lpVtbl->GetLOD(p) +#define IDirect3DCubeTexture8_GetLevelCount(p) (p)->lpVtbl->GetLevelCount(p) +#define IDirect3DCubeTexture8_GetLevelDesc(p,a,b) (p)->lpVtbl->GetLevelDesc(p,a,b) +#define IDirect3DCubeTexture8_GetCubeMapSurface(p,a,b,c) (p)->lpVtbl->GetCubeMapSurface(p,a,b,c) +#define IDirect3DCubeTexture8_LockRect(p,a,b,c,d,e) (p)->lpVtbl->LockRect(p,a,b,c,d,e) +#define IDirect3DCubeTexture8_UnlockRect(p,a,b) (p)->lpVtbl->UnlockRect(p,a,b) +#define IDirect3DCubeTexture8_AddDirtyRect(p,a,b) (p)->lpVtbl->AddDirtyRect(p,a,b) +#else +#define IDirect3DCubeTexture8_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirect3DCubeTexture8_AddRef(p) (p)->AddRef() +#define IDirect3DCubeTexture8_Release(p) (p)->Release() +#define IDirect3DCubeTexture8_GetDevice(p,a) (p)->GetDevice(a) +#define IDirect3DCubeTexture8_SetPrivateData(p,a,b,c,d) (p)->SetPrivateData(a,b,c,d) +#define IDirect3DCubeTexture8_GetPrivateData(p,a,b,c) (p)->GetPrivateData(a,b,c) +#define IDirect3DCubeTexture8_FreePrivateData(p,a) (p)->FreePrivateData(a) +#define IDirect3DCubeTexture8_SetPriority(p,a) (p)->SetPriority(a) +#define IDirect3DCubeTexture8_GetPriority(p) (p)->GetPriority() +#define IDirect3DCubeTexture8_PreLoad(p) (p)->PreLoad() +#define IDirect3DCubeTexture8_GetType(p) (p)->GetType() +#define IDirect3DCubeTexture8_SetLOD(p,a) (p)->SetLOD(a) +#define IDirect3DCubeTexture8_GetLOD(p) (p)->GetLOD() +#define IDirect3DCubeTexture8_GetLevelCount(p) (p)->GetLevelCount() +#define IDirect3DCubeTexture8_GetLevelDesc(p,a,b) (p)->GetLevelDesc(a,b) +#define IDirect3DCubeTexture8_GetCubeMapSurface(p,a,b,c) (p)->GetCubeMapSurface(a,b,c) +#define IDirect3DCubeTexture8_LockRect(p,a,b,c,d,e) (p)->LockRect(a,b,c,d,e) +#define IDirect3DCubeTexture8_UnlockRect(p,a,b) (p)->UnlockRect(a,b) +#define IDirect3DCubeTexture8_AddDirtyRect(p,a,b) (p)->AddDirtyRect(a,b) +#endif + + + + +#undef INTERFACE +#define INTERFACE IDirect3DVertexBuffer8 + +DECLARE_INTERFACE_(IDirect3DVertexBuffer8, IDirect3DResource8) +{ + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + /*** IDirect3DResource8 methods ***/ + STDMETHOD(GetDevice)(THIS_ IDirect3DDevice8** ppDevice) PURE; + STDMETHOD(SetPrivateData)(THIS_ REFGUID refguid,CONST void* pData,DWORD SizeOfData,DWORD Flags) PURE; + STDMETHOD(GetPrivateData)(THIS_ REFGUID refguid,void* pData,DWORD* pSizeOfData) PURE; + STDMETHOD(FreePrivateData)(THIS_ REFGUID refguid) PURE; + STDMETHOD_(DWORD, SetPriority)(THIS_ DWORD PriorityNew) PURE; + STDMETHOD_(DWORD, GetPriority)(THIS) PURE; + STDMETHOD_(void, PreLoad)(THIS) PURE; + STDMETHOD_(D3DRESOURCETYPE, GetType)(THIS) PURE; + STDMETHOD(Lock)(THIS_ UINT OffsetToLock,UINT SizeToLock,BYTE** ppbData,DWORD Flags) PURE; + STDMETHOD(Unlock)(THIS) PURE; + STDMETHOD(GetDesc)(THIS_ D3DVERTEXBUFFER_DESC *pDesc) PURE; +}; + +typedef struct IDirect3DVertexBuffer8 *LPDIRECT3DVERTEXBUFFER8, *PDIRECT3DVERTEXBUFFER8; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirect3DVertexBuffer8_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirect3DVertexBuffer8_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirect3DVertexBuffer8_Release(p) (p)->lpVtbl->Release(p) +#define IDirect3DVertexBuffer8_GetDevice(p,a) (p)->lpVtbl->GetDevice(p,a) +#define IDirect3DVertexBuffer8_SetPrivateData(p,a,b,c,d) (p)->lpVtbl->SetPrivateData(p,a,b,c,d) +#define IDirect3DVertexBuffer8_GetPrivateData(p,a,b,c) (p)->lpVtbl->GetPrivateData(p,a,b,c) +#define IDirect3DVertexBuffer8_FreePrivateData(p,a) (p)->lpVtbl->FreePrivateData(p,a) +#define IDirect3DVertexBuffer8_SetPriority(p,a) (p)->lpVtbl->SetPriority(p,a) +#define IDirect3DVertexBuffer8_GetPriority(p) (p)->lpVtbl->GetPriority(p) +#define IDirect3DVertexBuffer8_PreLoad(p) (p)->lpVtbl->PreLoad(p) +#define IDirect3DVertexBuffer8_GetType(p) (p)->lpVtbl->GetType(p) +#define IDirect3DVertexBuffer8_Lock(p,a,b,c,d) (p)->lpVtbl->Lock(p,a,b,c,d) +#define IDirect3DVertexBuffer8_Unlock(p) (p)->lpVtbl->Unlock(p) +#define IDirect3DVertexBuffer8_GetDesc(p,a) (p)->lpVtbl->GetDesc(p,a) +#else +#define IDirect3DVertexBuffer8_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirect3DVertexBuffer8_AddRef(p) (p)->AddRef() +#define IDirect3DVertexBuffer8_Release(p) (p)->Release() +#define IDirect3DVertexBuffer8_GetDevice(p,a) (p)->GetDevice(a) +#define IDirect3DVertexBuffer8_SetPrivateData(p,a,b,c,d) (p)->SetPrivateData(a,b,c,d) +#define IDirect3DVertexBuffer8_GetPrivateData(p,a,b,c) (p)->GetPrivateData(a,b,c) +#define IDirect3DVertexBuffer8_FreePrivateData(p,a) (p)->FreePrivateData(a) +#define IDirect3DVertexBuffer8_SetPriority(p,a) (p)->SetPriority(a) +#define IDirect3DVertexBuffer8_GetPriority(p) (p)->GetPriority() +#define IDirect3DVertexBuffer8_PreLoad(p) (p)->PreLoad() +#define IDirect3DVertexBuffer8_GetType(p) (p)->GetType() +#define IDirect3DVertexBuffer8_Lock(p,a,b,c,d) (p)->Lock(a,b,c,d) +#define IDirect3DVertexBuffer8_Unlock(p) (p)->Unlock() +#define IDirect3DVertexBuffer8_GetDesc(p,a) (p)->GetDesc(a) +#endif + + + + +#undef INTERFACE +#define INTERFACE IDirect3DIndexBuffer8 + +DECLARE_INTERFACE_(IDirect3DIndexBuffer8, IDirect3DResource8) +{ + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + /*** IDirect3DResource8 methods ***/ + STDMETHOD(GetDevice)(THIS_ IDirect3DDevice8** ppDevice) PURE; + STDMETHOD(SetPrivateData)(THIS_ REFGUID refguid,CONST void* pData,DWORD SizeOfData,DWORD Flags) PURE; + STDMETHOD(GetPrivateData)(THIS_ REFGUID refguid,void* pData,DWORD* pSizeOfData) PURE; + STDMETHOD(FreePrivateData)(THIS_ REFGUID refguid) PURE; + STDMETHOD_(DWORD, SetPriority)(THIS_ DWORD PriorityNew) PURE; + STDMETHOD_(DWORD, GetPriority)(THIS) PURE; + STDMETHOD_(void, PreLoad)(THIS) PURE; + STDMETHOD_(D3DRESOURCETYPE, GetType)(THIS) PURE; + STDMETHOD(Lock)(THIS_ UINT OffsetToLock,UINT SizeToLock,BYTE** ppbData,DWORD Flags) PURE; + STDMETHOD(Unlock)(THIS) PURE; + STDMETHOD(GetDesc)(THIS_ D3DINDEXBUFFER_DESC *pDesc) PURE; +}; + +typedef struct IDirect3DIndexBuffer8 *LPDIRECT3DINDEXBUFFER8, *PDIRECT3DINDEXBUFFER8; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirect3DIndexBuffer8_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirect3DIndexBuffer8_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirect3DIndexBuffer8_Release(p) (p)->lpVtbl->Release(p) +#define IDirect3DIndexBuffer8_GetDevice(p,a) (p)->lpVtbl->GetDevice(p,a) +#define IDirect3DIndexBuffer8_SetPrivateData(p,a,b,c,d) (p)->lpVtbl->SetPrivateData(p,a,b,c,d) +#define IDirect3DIndexBuffer8_GetPrivateData(p,a,b,c) (p)->lpVtbl->GetPrivateData(p,a,b,c) +#define IDirect3DIndexBuffer8_FreePrivateData(p,a) (p)->lpVtbl->FreePrivateData(p,a) +#define IDirect3DIndexBuffer8_SetPriority(p,a) (p)->lpVtbl->SetPriority(p,a) +#define IDirect3DIndexBuffer8_GetPriority(p) (p)->lpVtbl->GetPriority(p) +#define IDirect3DIndexBuffer8_PreLoad(p) (p)->lpVtbl->PreLoad(p) +#define IDirect3DIndexBuffer8_GetType(p) (p)->lpVtbl->GetType(p) +#define IDirect3DIndexBuffer8_Lock(p,a,b,c,d) (p)->lpVtbl->Lock(p,a,b,c,d) +#define IDirect3DIndexBuffer8_Unlock(p) (p)->lpVtbl->Unlock(p) +#define IDirect3DIndexBuffer8_GetDesc(p,a) (p)->lpVtbl->GetDesc(p,a) +#else +#define IDirect3DIndexBuffer8_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirect3DIndexBuffer8_AddRef(p) (p)->AddRef() +#define IDirect3DIndexBuffer8_Release(p) (p)->Release() +#define IDirect3DIndexBuffer8_GetDevice(p,a) (p)->GetDevice(a) +#define IDirect3DIndexBuffer8_SetPrivateData(p,a,b,c,d) (p)->SetPrivateData(a,b,c,d) +#define IDirect3DIndexBuffer8_GetPrivateData(p,a,b,c) (p)->GetPrivateData(a,b,c) +#define IDirect3DIndexBuffer8_FreePrivateData(p,a) (p)->FreePrivateData(a) +#define IDirect3DIndexBuffer8_SetPriority(p,a) (p)->SetPriority(a) +#define IDirect3DIndexBuffer8_GetPriority(p) (p)->GetPriority() +#define IDirect3DIndexBuffer8_PreLoad(p) (p)->PreLoad() +#define IDirect3DIndexBuffer8_GetType(p) (p)->GetType() +#define IDirect3DIndexBuffer8_Lock(p,a,b,c,d) (p)->Lock(a,b,c,d) +#define IDirect3DIndexBuffer8_Unlock(p) (p)->Unlock() +#define IDirect3DIndexBuffer8_GetDesc(p,a) (p)->GetDesc(a) +#endif + + + + +#undef INTERFACE +#define INTERFACE IDirect3DSurface8 + +DECLARE_INTERFACE_(IDirect3DSurface8, IUnknown) +{ + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + /*** IDirect3DSurface8 methods ***/ + STDMETHOD(GetDevice)(THIS_ IDirect3DDevice8** ppDevice) PURE; + STDMETHOD(SetPrivateData)(THIS_ REFGUID refguid,CONST void* pData,DWORD SizeOfData,DWORD Flags) PURE; + STDMETHOD(GetPrivateData)(THIS_ REFGUID refguid,void* pData,DWORD* pSizeOfData) PURE; + STDMETHOD(FreePrivateData)(THIS_ REFGUID refguid) PURE; + STDMETHOD(GetContainer)(THIS_ REFIID riid,void** ppContainer) PURE; + STDMETHOD(GetDesc)(THIS_ D3DSURFACE_DESC *pDesc) PURE; + STDMETHOD(LockRect)(THIS_ D3DLOCKED_RECT* pLockedRect,CONST RECT* pRect,DWORD Flags) PURE; + STDMETHOD(UnlockRect)(THIS) PURE; +}; + +typedef struct IDirect3DSurface8 *LPDIRECT3DSURFACE8, *PDIRECT3DSURFACE8; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirect3DSurface8_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirect3DSurface8_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirect3DSurface8_Release(p) (p)->lpVtbl->Release(p) +#define IDirect3DSurface8_GetDevice(p,a) (p)->lpVtbl->GetDevice(p,a) +#define IDirect3DSurface8_SetPrivateData(p,a,b,c,d) (p)->lpVtbl->SetPrivateData(p,a,b,c,d) +#define IDirect3DSurface8_GetPrivateData(p,a,b,c) (p)->lpVtbl->GetPrivateData(p,a,b,c) +#define IDirect3DSurface8_FreePrivateData(p,a) (p)->lpVtbl->FreePrivateData(p,a) +#define IDirect3DSurface8_GetContainer(p,a,b) (p)->lpVtbl->GetContainer(p,a,b) +#define IDirect3DSurface8_GetDesc(p,a) (p)->lpVtbl->GetDesc(p,a) +#define IDirect3DSurface8_LockRect(p,a,b,c) (p)->lpVtbl->LockRect(p,a,b,c) +#define IDirect3DSurface8_UnlockRect(p) (p)->lpVtbl->UnlockRect(p) +#else +#define IDirect3DSurface8_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirect3DSurface8_AddRef(p) (p)->AddRef() +#define IDirect3DSurface8_Release(p) (p)->Release() +#define IDirect3DSurface8_GetDevice(p,a) (p)->GetDevice(a) +#define IDirect3DSurface8_SetPrivateData(p,a,b,c,d) (p)->SetPrivateData(a,b,c,d) +#define IDirect3DSurface8_GetPrivateData(p,a,b,c) (p)->GetPrivateData(a,b,c) +#define IDirect3DSurface8_FreePrivateData(p,a) (p)->FreePrivateData(a) +#define IDirect3DSurface8_GetContainer(p,a,b) (p)->GetContainer(a,b) +#define IDirect3DSurface8_GetDesc(p,a) (p)->GetDesc(a) +#define IDirect3DSurface8_LockRect(p,a,b,c) (p)->LockRect(a,b,c) +#define IDirect3DSurface8_UnlockRect(p) (p)->UnlockRect() +#endif + + + + +#undef INTERFACE +#define INTERFACE IDirect3DVolume8 + +DECLARE_INTERFACE_(IDirect3DVolume8, IUnknown) +{ + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + /*** IDirect3DVolume8 methods ***/ + STDMETHOD(GetDevice)(THIS_ IDirect3DDevice8** ppDevice) PURE; + STDMETHOD(SetPrivateData)(THIS_ REFGUID refguid,CONST void* pData,DWORD SizeOfData,DWORD Flags) PURE; + STDMETHOD(GetPrivateData)(THIS_ REFGUID refguid,void* pData,DWORD* pSizeOfData) PURE; + STDMETHOD(FreePrivateData)(THIS_ REFGUID refguid) PURE; + STDMETHOD(GetContainer)(THIS_ REFIID riid,void** ppContainer) PURE; + STDMETHOD(GetDesc)(THIS_ D3DVOLUME_DESC *pDesc) PURE; + STDMETHOD(LockBox)(THIS_ D3DLOCKED_BOX * pLockedVolume,CONST D3DBOX* pBox,DWORD Flags) PURE; + STDMETHOD(UnlockBox)(THIS) PURE; +}; + +typedef struct IDirect3DVolume8 *LPDIRECT3DVOLUME8, *PDIRECT3DVOLUME8; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IDirect3DVolume8_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IDirect3DVolume8_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IDirect3DVolume8_Release(p) (p)->lpVtbl->Release(p) +#define IDirect3DVolume8_GetDevice(p,a) (p)->lpVtbl->GetDevice(p,a) +#define IDirect3DVolume8_SetPrivateData(p,a,b,c,d) (p)->lpVtbl->SetPrivateData(p,a,b,c,d) +#define IDirect3DVolume8_GetPrivateData(p,a,b,c) (p)->lpVtbl->GetPrivateData(p,a,b,c) +#define IDirect3DVolume8_FreePrivateData(p,a) (p)->lpVtbl->FreePrivateData(p,a) +#define IDirect3DVolume8_GetContainer(p,a,b) (p)->lpVtbl->GetContainer(p,a,b) +#define IDirect3DVolume8_GetDesc(p,a) (p)->lpVtbl->GetDesc(p,a) +#define IDirect3DVolume8_LockBox(p,a,b,c) (p)->lpVtbl->LockBox(p,a,b,c) +#define IDirect3DVolume8_UnlockBox(p) (p)->lpVtbl->UnlockBox(p) +#else +#define IDirect3DVolume8_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IDirect3DVolume8_AddRef(p) (p)->AddRef() +#define IDirect3DVolume8_Release(p) (p)->Release() +#define IDirect3DVolume8_GetDevice(p,a) (p)->GetDevice(a) +#define IDirect3DVolume8_SetPrivateData(p,a,b,c,d) (p)->SetPrivateData(a,b,c,d) +#define IDirect3DVolume8_GetPrivateData(p,a,b,c) (p)->GetPrivateData(a,b,c) +#define IDirect3DVolume8_FreePrivateData(p,a) (p)->FreePrivateData(a) +#define IDirect3DVolume8_GetContainer(p,a,b) (p)->GetContainer(a,b) +#define IDirect3DVolume8_GetDesc(p,a) (p)->GetDesc(a) +#define IDirect3DVolume8_LockBox(p,a,b,c) (p)->LockBox(a,b,c) +#define IDirect3DVolume8_UnlockBox(p) (p)->UnlockBox() +#endif + +/**************************************************************************** + * Flags for SetPrivateData method on all D3D8 interfaces + * + * The passed pointer is an IUnknown ptr. The SizeOfData argument to SetPrivateData + * must be set to sizeof(IUnknown*). Direct3D will call AddRef through this + * pointer and Release when the private data is destroyed. The data will be + * destroyed when another SetPrivateData with the same GUID is set, when + * FreePrivateData is called, or when the D3D8 object is freed. + ****************************************************************************/ +#define D3DSPD_IUNKNOWN 0x00000001L + +/**************************************************************************** + * + * Parameter for IDirect3D8 Enum and GetCaps8 functions to get the info for + * the current mode only. + * + ****************************************************************************/ + +#define D3DCURRENT_DISPLAY_MODE 0x00EFFFFFL + +/**************************************************************************** + * + * Flags for IDirect3D8::CreateDevice's BehaviorFlags + * + ****************************************************************************/ + +#define D3DCREATE_FPU_PRESERVE 0x00000002L +#define D3DCREATE_MULTITHREADED 0x00000004L + +#define D3DCREATE_PUREDEVICE 0x00000010L +#define D3DCREATE_SOFTWARE_VERTEXPROCESSING 0x00000020L +#define D3DCREATE_HARDWARE_VERTEXPROCESSING 0x00000040L +#define D3DCREATE_MIXED_VERTEXPROCESSING 0x00000080L + +#define D3DCREATE_DISABLE_DRIVER_MANAGEMENT 0x00000100L + + +/**************************************************************************** + * + * Parameter for IDirect3D8::CreateDevice's iAdapter + * + ****************************************************************************/ + +#define D3DADAPTER_DEFAULT 0 + +/**************************************************************************** + * + * Flags for IDirect3D8::EnumAdapters + * + ****************************************************************************/ + +#define D3DENUM_NO_WHQL_LEVEL 0x00000002L + +/**************************************************************************** + * + * Maximum number of back-buffers supported in DX8 + * + ****************************************************************************/ + +#define D3DPRESENT_BACK_BUFFERS_MAX 3L + +/**************************************************************************** + * + * Flags for IDirect3DDevice8::SetGammaRamp + * + ****************************************************************************/ + +#define D3DSGR_NO_CALIBRATION 0x00000000L +#define D3DSGR_CALIBRATE 0x00000001L + +/**************************************************************************** + * + * Flags for IDirect3DDevice8::SetCursorPosition + * + ****************************************************************************/ + +#define D3DCURSOR_IMMEDIATE_UPDATE 0x00000001L + +/**************************************************************************** + * + * Flags for DrawPrimitive/DrawIndexedPrimitive + * Also valid for Begin/BeginIndexed + * Also valid for VertexBuffer::CreateVertexBuffer + ****************************************************************************/ + + +/* + * DirectDraw error codes + */ +#define _FACD3D 0x876 +#define MAKE_D3DHRESULT( code ) MAKE_HRESULT( 1, _FACD3D, code ) + +/* + * Direct3D Errors + */ +#define D3D_OK S_OK + +#define D3DERR_WRONGTEXTUREFORMAT MAKE_D3DHRESULT(2072) +#define D3DERR_UNSUPPORTEDCOLOROPERATION MAKE_D3DHRESULT(2073) +#define D3DERR_UNSUPPORTEDCOLORARG MAKE_D3DHRESULT(2074) +#define D3DERR_UNSUPPORTEDALPHAOPERATION MAKE_D3DHRESULT(2075) +#define D3DERR_UNSUPPORTEDALPHAARG MAKE_D3DHRESULT(2076) +#define D3DERR_TOOMANYOPERATIONS MAKE_D3DHRESULT(2077) +#define D3DERR_CONFLICTINGTEXTUREFILTER MAKE_D3DHRESULT(2078) +#define D3DERR_UNSUPPORTEDFACTORVALUE MAKE_D3DHRESULT(2079) +#define D3DERR_CONFLICTINGRENDERSTATE MAKE_D3DHRESULT(2081) +#define D3DERR_UNSUPPORTEDTEXTUREFILTER MAKE_D3DHRESULT(2082) +#define D3DERR_CONFLICTINGTEXTUREPALETTE MAKE_D3DHRESULT(2086) +#define D3DERR_DRIVERINTERNALERROR MAKE_D3DHRESULT(2087) + +#define D3DERR_NOTFOUND MAKE_D3DHRESULT(2150) +#define D3DERR_MOREDATA MAKE_D3DHRESULT(2151) +#define D3DERR_DEVICELOST MAKE_D3DHRESULT(2152) +#define D3DERR_DEVICENOTRESET MAKE_D3DHRESULT(2153) +#define D3DERR_NOTAVAILABLE MAKE_D3DHRESULT(2154) +#define D3DERR_OUTOFVIDEOMEMORY MAKE_D3DHRESULT(380) +#define D3DERR_INVALIDDEVICE MAKE_D3DHRESULT(2155) +#define D3DERR_INVALIDCALL MAKE_D3DHRESULT(2156) +#define D3DERR_DRIVERINVALIDCALL MAKE_D3DHRESULT(2157) + +#ifdef __cplusplus +}; +#endif + +#endif /* (DIRECT3D_VERSION >= 0x0800) */ +#endif /* _D3D_H_ */ + diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8sdk/d3d8caps.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8sdk/d3d8caps.h new file mode 100644 index 00000000000..1cf60e7f8d0 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8sdk/d3d8caps.h @@ -0,0 +1,362 @@ +/*==========================================================================; + * + * Copyright (C) Microsoft Corporation. All Rights Reserved. + * + * File: d3d8caps.h + * Content: Direct3D capabilities include file + * + ***************************************************************************/ + +#ifndef _D3D8CAPS_H +#define _D3D8CAPS_H + +#ifndef DIRECT3D_VERSION +#define DIRECT3D_VERSION 0x0800 +#endif //DIRECT3D_VERSION + +// include this file content only if compiling for DX8 interfaces +#if(DIRECT3D_VERSION >= 0x0800) + +#pragma pack(4) + +typedef struct _D3DCAPS8 +{ + /* Device Info */ + D3DDEVTYPE DeviceType; + UINT AdapterOrdinal; + + /* Caps from DX7 Draw */ + DWORD Caps; + DWORD Caps2; + DWORD Caps3; + DWORD PresentationIntervals; + + /* Cursor Caps */ + DWORD CursorCaps; + + /* 3D Device Caps */ + DWORD DevCaps; + + DWORD PrimitiveMiscCaps; + DWORD RasterCaps; + DWORD ZCmpCaps; + DWORD SrcBlendCaps; + DWORD DestBlendCaps; + DWORD AlphaCmpCaps; + DWORD ShadeCaps; + DWORD TextureCaps; + DWORD TextureFilterCaps; // D3DPTFILTERCAPS for IDirect3DTexture8's + DWORD CubeTextureFilterCaps; // D3DPTFILTERCAPS for IDirect3DCubeTexture8's + DWORD VolumeTextureFilterCaps; // D3DPTFILTERCAPS for IDirect3DVolumeTexture8's + DWORD TextureAddressCaps; // D3DPTADDRESSCAPS for IDirect3DTexture8's + DWORD VolumeTextureAddressCaps; // D3DPTADDRESSCAPS for IDirect3DVolumeTexture8's + + DWORD LineCaps; // D3DLINECAPS + + DWORD MaxTextureWidth, MaxTextureHeight; + DWORD MaxVolumeExtent; + + DWORD MaxTextureRepeat; + DWORD MaxTextureAspectRatio; + DWORD MaxAnisotropy; + float MaxVertexW; + + float GuardBandLeft; + float GuardBandTop; + float GuardBandRight; + float GuardBandBottom; + + float ExtentsAdjust; + DWORD StencilCaps; + + DWORD FVFCaps; + DWORD TextureOpCaps; + DWORD MaxTextureBlendStages; + DWORD MaxSimultaneousTextures; + + DWORD VertexProcessingCaps; + DWORD MaxActiveLights; + DWORD MaxUserClipPlanes; + DWORD MaxVertexBlendMatrices; + DWORD MaxVertexBlendMatrixIndex; + + float MaxPointSize; + + DWORD MaxPrimitiveCount; // max number of primitives per DrawPrimitive call + DWORD MaxVertexIndex; + DWORD MaxStreams; + DWORD MaxStreamStride; // max stride for SetStreamSource + + DWORD VertexShaderVersion; + DWORD MaxVertexShaderConst; // number of vertex shader constant registers + + DWORD PixelShaderVersion; + float MaxPixelShaderValue; // max value of pixel shader arithmetic component + +} D3DCAPS8; + +// +// BIT DEFINES FOR D3DCAPS8 DWORD MEMBERS +// + +// +// Caps +// +#define D3DCAPS_READ_SCANLINE 0x00020000L + +// +// Caps2 +// +#define D3DCAPS2_NO2DDURING3DSCENE 0x00000002L +#define D3DCAPS2_FULLSCREENGAMMA 0x00020000L +#define D3DCAPS2_CANRENDERWINDOWED 0x00080000L +#define D3DCAPS2_CANCALIBRATEGAMMA 0x00100000L +#define D3DCAPS2_RESERVED 0x02000000L +#define D3DCAPS2_CANMANAGERESOURCE 0x10000000L +#define D3DCAPS2_DYNAMICTEXTURES 0x20000000L + +// +// Caps3 +// +#define D3DCAPS3_RESERVED 0x8000001fL + +// Indicates that the device can respect the ALPHABLENDENABLE render state +// when fullscreen while using the FLIP or DISCARD swap effect. +// COPY and COPYVSYNC swap effects work whether or not this flag is set. +#define D3DCAPS3_ALPHA_FULLSCREEN_FLIP_OR_DISCARD 0x00000020L + +// +// PresentationIntervals +// +#define D3DPRESENT_INTERVAL_DEFAULT 0x00000000L +#define D3DPRESENT_INTERVAL_ONE 0x00000001L +#define D3DPRESENT_INTERVAL_TWO 0x00000002L +#define D3DPRESENT_INTERVAL_THREE 0x00000004L +#define D3DPRESENT_INTERVAL_FOUR 0x00000008L +#define D3DPRESENT_INTERVAL_IMMEDIATE 0x80000000L + +// +// CursorCaps +// +// Driver supports HW color cursor in at least hi-res modes(height >=400) +#define D3DCURSORCAPS_COLOR 0x00000001L +// Driver supports HW cursor also in low-res modes(height < 400) +#define D3DCURSORCAPS_LOWRES 0x00000002L + +// +// DevCaps +// +#define D3DDEVCAPS_EXECUTESYSTEMMEMORY 0x00000010L /* Device can use execute buffers from system memory */ +#define D3DDEVCAPS_EXECUTEVIDEOMEMORY 0x00000020L /* Device can use execute buffers from video memory */ +#define D3DDEVCAPS_TLVERTEXSYSTEMMEMORY 0x00000040L /* Device can use TL buffers from system memory */ +#define D3DDEVCAPS_TLVERTEXVIDEOMEMORY 0x00000080L /* Device can use TL buffers from video memory */ +#define D3DDEVCAPS_TEXTURESYSTEMMEMORY 0x00000100L /* Device can texture from system memory */ +#define D3DDEVCAPS_TEXTUREVIDEOMEMORY 0x00000200L /* Device can texture from device memory */ +#define D3DDEVCAPS_DRAWPRIMTLVERTEX 0x00000400L /* Device can draw TLVERTEX primitives */ +#define D3DDEVCAPS_CANRENDERAFTERFLIP 0x00000800L /* Device can render without waiting for flip to complete */ +#define D3DDEVCAPS_TEXTURENONLOCALVIDMEM 0x00001000L /* Device can texture from nonlocal video memory */ +#define D3DDEVCAPS_DRAWPRIMITIVES2 0x00002000L /* Device can support DrawPrimitives2 */ +#define D3DDEVCAPS_SEPARATETEXTUREMEMORIES 0x00004000L /* Device is texturing from separate memory pools */ +#define D3DDEVCAPS_DRAWPRIMITIVES2EX 0x00008000L /* Device can support Extended DrawPrimitives2 i.e. DX7 compliant driver*/ +#define D3DDEVCAPS_HWTRANSFORMANDLIGHT 0x00010000L /* Device can support transformation and lighting in hardware and DRAWPRIMITIVES2EX must be also */ +#define D3DDEVCAPS_CANBLTSYSTONONLOCAL 0x00020000L /* Device supports a Tex Blt from system memory to non-local vidmem */ +#define D3DDEVCAPS_HWRASTERIZATION 0x00080000L /* Device has HW acceleration for rasterization */ +#define D3DDEVCAPS_PUREDEVICE 0x00100000L /* Device supports D3DCREATE_PUREDEVICE */ +#define D3DDEVCAPS_QUINTICRTPATCHES 0x00200000L /* Device supports quintic Beziers and BSplines */ +#define D3DDEVCAPS_RTPATCHES 0x00400000L /* Device supports Rect and Tri patches */ +#define D3DDEVCAPS_RTPATCHHANDLEZERO 0x00800000L /* Indicates that RT Patches may be drawn efficiently using handle 0 */ +#define D3DDEVCAPS_NPATCHES 0x01000000L /* Device supports N-Patches */ + +// +// PrimitiveMiscCaps +// +#define D3DPMISCCAPS_MASKZ 0x00000002L +#define D3DPMISCCAPS_LINEPATTERNREP 0x00000004L +#define D3DPMISCCAPS_CULLNONE 0x00000010L +#define D3DPMISCCAPS_CULLCW 0x00000020L +#define D3DPMISCCAPS_CULLCCW 0x00000040L +#define D3DPMISCCAPS_COLORWRITEENABLE 0x00000080L +#define D3DPMISCCAPS_CLIPPLANESCALEDPOINTS 0x00000100L /* Device correctly clips scaled points to clip planes */ +#define D3DPMISCCAPS_CLIPTLVERTS 0x00000200L /* device will clip post-transformed vertex primitives */ +#define D3DPMISCCAPS_TSSARGTEMP 0x00000400L /* device supports D3DTA_TEMP for temporary register */ +#define D3DPMISCCAPS_BLENDOP 0x00000800L /* device supports D3DRS_BLENDOP */ +#define D3DPMISCCAPS_NULLREFERENCE 0x00001000L /* Reference Device that doesnt render */ + +// +// LineCaps +// +#define D3DLINECAPS_TEXTURE 0x00000001L +#define D3DLINECAPS_ZTEST 0x00000002L +#define D3DLINECAPS_BLEND 0x00000004L +#define D3DLINECAPS_ALPHACMP 0x00000008L +#define D3DLINECAPS_FOG 0x00000010L + +// +// RasterCaps +// +#define D3DPRASTERCAPS_DITHER 0x00000001L +#define D3DPRASTERCAPS_PAT 0x00000008L +#define D3DPRASTERCAPS_ZTEST 0x00000010L +#define D3DPRASTERCAPS_FOGVERTEX 0x00000080L +#define D3DPRASTERCAPS_FOGTABLE 0x00000100L +#define D3DPRASTERCAPS_ANTIALIASEDGES 0x00001000L +#define D3DPRASTERCAPS_MIPMAPLODBIAS 0x00002000L +#define D3DPRASTERCAPS_ZBIAS 0x00004000L +#define D3DPRASTERCAPS_ZBUFFERLESSHSR 0x00008000L +#define D3DPRASTERCAPS_FOGRANGE 0x00010000L +#define D3DPRASTERCAPS_ANISOTROPY 0x00020000L +#define D3DPRASTERCAPS_WBUFFER 0x00040000L +#define D3DPRASTERCAPS_WFOG 0x00100000L +#define D3DPRASTERCAPS_ZFOG 0x00200000L +#define D3DPRASTERCAPS_COLORPERSPECTIVE 0x00400000L /* Device iterates colors perspective correct */ +#define D3DPRASTERCAPS_STRETCHBLTMULTISAMPLE 0x00800000L + +// +// ZCmpCaps, AlphaCmpCaps +// +#define D3DPCMPCAPS_NEVER 0x00000001L +#define D3DPCMPCAPS_LESS 0x00000002L +#define D3DPCMPCAPS_EQUAL 0x00000004L +#define D3DPCMPCAPS_LESSEQUAL 0x00000008L +#define D3DPCMPCAPS_GREATER 0x00000010L +#define D3DPCMPCAPS_NOTEQUAL 0x00000020L +#define D3DPCMPCAPS_GREATEREQUAL 0x00000040L +#define D3DPCMPCAPS_ALWAYS 0x00000080L + +// +// SourceBlendCaps, DestBlendCaps +// +#define D3DPBLENDCAPS_ZERO 0x00000001L +#define D3DPBLENDCAPS_ONE 0x00000002L +#define D3DPBLENDCAPS_SRCCOLOR 0x00000004L +#define D3DPBLENDCAPS_INVSRCCOLOR 0x00000008L +#define D3DPBLENDCAPS_SRCALPHA 0x00000010L +#define D3DPBLENDCAPS_INVSRCALPHA 0x00000020L +#define D3DPBLENDCAPS_DESTALPHA 0x00000040L +#define D3DPBLENDCAPS_INVDESTALPHA 0x00000080L +#define D3DPBLENDCAPS_DESTCOLOR 0x00000100L +#define D3DPBLENDCAPS_INVDESTCOLOR 0x00000200L +#define D3DPBLENDCAPS_SRCALPHASAT 0x00000400L +#define D3DPBLENDCAPS_BOTHSRCALPHA 0x00000800L +#define D3DPBLENDCAPS_BOTHINVSRCALPHA 0x00001000L + +// +// ShadeCaps +// +#define D3DPSHADECAPS_COLORGOURAUDRGB 0x00000008L +#define D3DPSHADECAPS_SPECULARGOURAUDRGB 0x00000200L +#define D3DPSHADECAPS_ALPHAGOURAUDBLEND 0x00004000L +#define D3DPSHADECAPS_FOGGOURAUD 0x00080000L + +// +// TextureCaps +// +#define D3DPTEXTURECAPS_PERSPECTIVE 0x00000001L /* Perspective-correct texturing is supported */ +#define D3DPTEXTURECAPS_POW2 0x00000002L /* Power-of-2 texture dimensions are required - applies to non-Cube/Volume textures only. */ +#define D3DPTEXTURECAPS_ALPHA 0x00000004L /* Alpha in texture pixels is supported */ +#define D3DPTEXTURECAPS_SQUAREONLY 0x00000020L /* Only square textures are supported */ +#define D3DPTEXTURECAPS_TEXREPEATNOTSCALEDBYSIZE 0x00000040L /* Texture indices are not scaled by the texture size prior to interpolation */ +#define D3DPTEXTURECAPS_ALPHAPALETTE 0x00000080L /* Device can draw alpha from texture palettes */ +// Device can use non-POW2 textures if: +// 1) D3DTEXTURE_ADDRESS is set to CLAMP for this texture's stage +// 2) D3DRS_WRAP(N) is zero for this texture's coordinates +// 3) mip mapping is not enabled (use magnification filter only) +#define D3DPTEXTURECAPS_NONPOW2CONDITIONAL 0x00000100L +#define D3DPTEXTURECAPS_PROJECTED 0x00000400L /* Device can do D3DTTFF_PROJECTED */ +#define D3DPTEXTURECAPS_CUBEMAP 0x00000800L /* Device can do cubemap textures */ +#define D3DPTEXTURECAPS_VOLUMEMAP 0x00002000L /* Device can do volume textures */ +#define D3DPTEXTURECAPS_MIPMAP 0x00004000L /* Device can do mipmapped textures */ +#define D3DPTEXTURECAPS_MIPVOLUMEMAP 0x00008000L /* Device can do mipmapped volume textures */ +#define D3DPTEXTURECAPS_MIPCUBEMAP 0x00010000L /* Device can do mipmapped cube maps */ +#define D3DPTEXTURECAPS_CUBEMAP_POW2 0x00020000L /* Device requires that cubemaps be power-of-2 dimension */ +#define D3DPTEXTURECAPS_VOLUMEMAP_POW2 0x00040000L /* Device requires that volume maps be power-of-2 dimension */ + +// +// TextureFilterCaps +// +#define D3DPTFILTERCAPS_MINFPOINT 0x00000100L /* Min Filter */ +#define D3DPTFILTERCAPS_MINFLINEAR 0x00000200L +#define D3DPTFILTERCAPS_MINFANISOTROPIC 0x00000400L +#define D3DPTFILTERCAPS_MIPFPOINT 0x00010000L /* Mip Filter */ +#define D3DPTFILTERCAPS_MIPFLINEAR 0x00020000L +#define D3DPTFILTERCAPS_MAGFPOINT 0x01000000L /* Mag Filter */ +#define D3DPTFILTERCAPS_MAGFLINEAR 0x02000000L +#define D3DPTFILTERCAPS_MAGFANISOTROPIC 0x04000000L +#define D3DPTFILTERCAPS_MAGFAFLATCUBIC 0x08000000L +#define D3DPTFILTERCAPS_MAGFGAUSSIANCUBIC 0x10000000L + +// +// TextureAddressCaps +// +#define D3DPTADDRESSCAPS_WRAP 0x00000001L +#define D3DPTADDRESSCAPS_MIRROR 0x00000002L +#define D3DPTADDRESSCAPS_CLAMP 0x00000004L +#define D3DPTADDRESSCAPS_BORDER 0x00000008L +#define D3DPTADDRESSCAPS_INDEPENDENTUV 0x00000010L +#define D3DPTADDRESSCAPS_MIRRORONCE 0x00000020L + +// +// StencilCaps +// +#define D3DSTENCILCAPS_KEEP 0x00000001L +#define D3DSTENCILCAPS_ZERO 0x00000002L +#define D3DSTENCILCAPS_REPLACE 0x00000004L +#define D3DSTENCILCAPS_INCRSAT 0x00000008L +#define D3DSTENCILCAPS_DECRSAT 0x00000010L +#define D3DSTENCILCAPS_INVERT 0x00000020L +#define D3DSTENCILCAPS_INCR 0x00000040L +#define D3DSTENCILCAPS_DECR 0x00000080L + +// +// TextureOpCaps +// +#define D3DTEXOPCAPS_DISABLE 0x00000001L +#define D3DTEXOPCAPS_SELECTARG1 0x00000002L +#define D3DTEXOPCAPS_SELECTARG2 0x00000004L +#define D3DTEXOPCAPS_MODULATE 0x00000008L +#define D3DTEXOPCAPS_MODULATE2X 0x00000010L +#define D3DTEXOPCAPS_MODULATE4X 0x00000020L +#define D3DTEXOPCAPS_ADD 0x00000040L +#define D3DTEXOPCAPS_ADDSIGNED 0x00000080L +#define D3DTEXOPCAPS_ADDSIGNED2X 0x00000100L +#define D3DTEXOPCAPS_SUBTRACT 0x00000200L +#define D3DTEXOPCAPS_ADDSMOOTH 0x00000400L +#define D3DTEXOPCAPS_BLENDDIFFUSEALPHA 0x00000800L +#define D3DTEXOPCAPS_BLENDTEXTUREALPHA 0x00001000L +#define D3DTEXOPCAPS_BLENDFACTORALPHA 0x00002000L +#define D3DTEXOPCAPS_BLENDTEXTUREALPHAPM 0x00004000L +#define D3DTEXOPCAPS_BLENDCURRENTALPHA 0x00008000L +#define D3DTEXOPCAPS_PREMODULATE 0x00010000L +#define D3DTEXOPCAPS_MODULATEALPHA_ADDCOLOR 0x00020000L +#define D3DTEXOPCAPS_MODULATECOLOR_ADDALPHA 0x00040000L +#define D3DTEXOPCAPS_MODULATEINVALPHA_ADDCOLOR 0x00080000L +#define D3DTEXOPCAPS_MODULATEINVCOLOR_ADDALPHA 0x00100000L +#define D3DTEXOPCAPS_BUMPENVMAP 0x00200000L +#define D3DTEXOPCAPS_BUMPENVMAPLUMINANCE 0x00400000L +#define D3DTEXOPCAPS_DOTPRODUCT3 0x00800000L +#define D3DTEXOPCAPS_MULTIPLYADD 0x01000000L +#define D3DTEXOPCAPS_LERP 0x02000000L + +// +// FVFCaps +// +#define D3DFVFCAPS_TEXCOORDCOUNTMASK 0x0000ffffL /* mask for texture coordinate count field */ +#define D3DFVFCAPS_DONOTSTRIPELEMENTS 0x00080000L /* Device prefers that vertex elements not be stripped */ +#define D3DFVFCAPS_PSIZE 0x00100000L /* Device can receive point size */ + +// +// VertexProcessingCaps +// +#define D3DVTXPCAPS_TEXGEN 0x00000001L /* device can do texgen */ +#define D3DVTXPCAPS_MATERIALSOURCE7 0x00000002L /* device can do DX7-level colormaterialsource ops */ +#define D3DVTXPCAPS_DIRECTIONALLIGHTS 0x00000008L /* device can do directional lights */ +#define D3DVTXPCAPS_POSITIONALLIGHTS 0x00000010L /* device can do positional lights (includes point and spot) */ +#define D3DVTXPCAPS_LOCALVIEWER 0x00000020L /* device can do local viewer */ +#define D3DVTXPCAPS_TWEENING 0x00000040L /* device can do vertex tweening */ +#define D3DVTXPCAPS_NO_VSDT_UBYTE4 0x00000080L /* device does not support D3DVSDT_UBYTE4 */ + +#pragma pack() + +#endif /* (DIRECT3D_VERSION >= 0x0800) */ +#endif /* _D3D8CAPS_H_ */ + diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8sdk/d3d8types.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8sdk/d3d8types.h new file mode 100644 index 00000000000..0ee00218556 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8sdk/d3d8types.h @@ -0,0 +1,1677 @@ +/*==========================================================================; + * + * Copyright (C) Microsoft Corporation. All Rights Reserved. + * + * File: d3d8types.h + * Content: Direct3D capabilities include file + * + ***************************************************************************/ + +#ifndef _D3D8TYPES_H_ +#define _D3D8TYPES_H_ + +#ifndef DIRECT3D_VERSION +#define DIRECT3D_VERSION 0x0800 +#endif //DIRECT3D_VERSION + +// include this file content only if compiling for DX8 interfaces +#if(DIRECT3D_VERSION >= 0x0800) + +#include + +#pragma warning(disable:4201) // anonymous unions warning +#if defined (_WIN32) && !defined (_WIN64) +#pragma pack(4) +#endif + +// D3DCOLOR is equivalent to D3DFMT_A8R8G8B8 +#ifndef D3DCOLOR_DEFINED +typedef DWORD D3DCOLOR; +#define D3DCOLOR_DEFINED +#endif + +// maps unsigned 8 bits/channel to D3DCOLOR +#define D3DCOLOR_ARGB(a,r,g,b) \ + ((D3DCOLOR)((((a)&0xff)<<24)|(((r)&0xff)<<16)|(((g)&0xff)<<8)|((b)&0xff))) +#define D3DCOLOR_RGBA(r,g,b,a) D3DCOLOR_ARGB(a,r,g,b) +#define D3DCOLOR_XRGB(r,g,b) D3DCOLOR_ARGB(0xff,r,g,b) + +// maps floating point channels (0.f to 1.f range) to D3DCOLOR +#define D3DCOLOR_COLORVALUE(r,g,b,a) \ + D3DCOLOR_RGBA((DWORD)((r)*255.f),(DWORD)((g)*255.f),(DWORD)((b)*255.f),(DWORD)((a)*255.f)) + + +#ifndef D3DVECTOR_DEFINED +typedef struct _D3DVECTOR { + float x; + float y; + float z; +} D3DVECTOR; +#define D3DVECTOR_DEFINED +#endif + +#ifndef D3DCOLORVALUE_DEFINED +typedef struct _D3DCOLORVALUE { + float r; + float g; + float b; + float a; +} D3DCOLORVALUE; +#define D3DCOLORVALUE_DEFINED +#endif + +#ifndef D3DRECT_DEFINED +typedef struct _D3DRECT { + LONG x1; + LONG y1; + LONG x2; + LONG y2; +} D3DRECT; +#define D3DRECT_DEFINED +#endif + +#ifndef D3DMATRIX_DEFINED +typedef struct _D3DMATRIX { + union { + struct { + float _11, _12, _13, _14; + float _21, _22, _23, _24; + float _31, _32, _33, _34; + float _41, _42, _43, _44; + + }; + float m[4][4]; + }; +} D3DMATRIX; +#define D3DMATRIX_DEFINED +#endif + +typedef struct _D3DVIEWPORT8 { + DWORD X; + DWORD Y; /* Viewport Top left */ + DWORD Width; + DWORD Height; /* Viewport Dimensions */ + float MinZ; /* Min/max of clip Volume */ + float MaxZ; +} D3DVIEWPORT8; + +/* + * Values for clip fields. + */ + +// Max number of user clipping planes, supported in D3D. +#define D3DMAXUSERCLIPPLANES 32 + +// These bits could be ORed together to use with D3DRS_CLIPPLANEENABLE +// +#define D3DCLIPPLANE0 (1 << 0) +#define D3DCLIPPLANE1 (1 << 1) +#define D3DCLIPPLANE2 (1 << 2) +#define D3DCLIPPLANE3 (1 << 3) +#define D3DCLIPPLANE4 (1 << 4) +#define D3DCLIPPLANE5 (1 << 5) + +// The following bits are used in the ClipUnion and ClipIntersection +// members of the D3DCLIPSTATUS8 +// + +#define D3DCS_LEFT 0x00000001L +#define D3DCS_RIGHT 0x00000002L +#define D3DCS_TOP 0x00000004L +#define D3DCS_BOTTOM 0x00000008L +#define D3DCS_FRONT 0x00000010L +#define D3DCS_BACK 0x00000020L +#define D3DCS_PLANE0 0x00000040L +#define D3DCS_PLANE1 0x00000080L +#define D3DCS_PLANE2 0x00000100L +#define D3DCS_PLANE3 0x00000200L +#define D3DCS_PLANE4 0x00000400L +#define D3DCS_PLANE5 0x00000800L + +#define D3DCS_ALL (D3DCS_LEFT | \ + D3DCS_RIGHT | \ + D3DCS_TOP | \ + D3DCS_BOTTOM | \ + D3DCS_FRONT | \ + D3DCS_BACK | \ + D3DCS_PLANE0 | \ + D3DCS_PLANE1 | \ + D3DCS_PLANE2 | \ + D3DCS_PLANE3 | \ + D3DCS_PLANE4 | \ + D3DCS_PLANE5) + +typedef struct _D3DCLIPSTATUS8 { + DWORD ClipUnion; + DWORD ClipIntersection; +} D3DCLIPSTATUS8; + +typedef struct _D3DMATERIAL8 { + D3DCOLORVALUE Diffuse; /* Diffuse color RGBA */ + D3DCOLORVALUE Ambient; /* Ambient color RGB */ + D3DCOLORVALUE Specular; /* Specular 'shininess' */ + D3DCOLORVALUE Emissive; /* Emissive color RGB */ + float Power; /* Sharpness if specular highlight */ +} D3DMATERIAL8; + +typedef enum _D3DLIGHTTYPE { + D3DLIGHT_POINT = 1, + D3DLIGHT_SPOT = 2, + D3DLIGHT_DIRECTIONAL = 3, + D3DLIGHT_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */ +} D3DLIGHTTYPE; + +typedef struct _D3DLIGHT8 { + D3DLIGHTTYPE Type; /* Type of light source */ + D3DCOLORVALUE Diffuse; /* Diffuse color of light */ + D3DCOLORVALUE Specular; /* Specular color of light */ + D3DCOLORVALUE Ambient; /* Ambient color of light */ + D3DVECTOR Position; /* Position in world space */ + D3DVECTOR Direction; /* Direction in world space */ + float Range; /* Cutoff range */ + float Falloff; /* Falloff */ + float Attenuation0; /* Constant attenuation */ + float Attenuation1; /* Linear attenuation */ + float Attenuation2; /* Quadratic attenuation */ + float Theta; /* Inner angle of spotlight cone */ + float Phi; /* Outer angle of spotlight cone */ +} D3DLIGHT8; + +/* + * Options for clearing + */ +#define D3DCLEAR_TARGET 0x00000001l /* Clear target surface */ +#define D3DCLEAR_ZBUFFER 0x00000002l /* Clear target z buffer */ +#define D3DCLEAR_STENCIL 0x00000004l /* Clear stencil planes */ + +/* + * The following defines the rendering states + */ + +typedef enum _D3DSHADEMODE { + D3DSHADE_FLAT = 1, + D3DSHADE_GOURAUD = 2, + D3DSHADE_PHONG = 3, + D3DSHADE_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */ +} D3DSHADEMODE; + +typedef enum _D3DFILLMODE { + D3DFILL_POINT = 1, + D3DFILL_WIREFRAME = 2, + D3DFILL_SOLID = 3, + D3DFILL_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */ +} D3DFILLMODE; + +typedef struct _D3DLINEPATTERN { + WORD wRepeatFactor; + WORD wLinePattern; +} D3DLINEPATTERN; + +typedef enum _D3DBLEND { + D3DBLEND_ZERO = 1, + D3DBLEND_ONE = 2, + D3DBLEND_SRCCOLOR = 3, + D3DBLEND_INVSRCCOLOR = 4, + D3DBLEND_SRCALPHA = 5, + D3DBLEND_INVSRCALPHA = 6, + D3DBLEND_DESTALPHA = 7, + D3DBLEND_INVDESTALPHA = 8, + D3DBLEND_DESTCOLOR = 9, + D3DBLEND_INVDESTCOLOR = 10, + D3DBLEND_SRCALPHASAT = 11, + D3DBLEND_BOTHSRCALPHA = 12, + D3DBLEND_BOTHINVSRCALPHA = 13, + D3DBLEND_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */ +} D3DBLEND; + +typedef enum _D3DBLENDOP { + D3DBLENDOP_ADD = 1, + D3DBLENDOP_SUBTRACT = 2, + D3DBLENDOP_REVSUBTRACT = 3, + D3DBLENDOP_MIN = 4, + D3DBLENDOP_MAX = 5, + D3DBLENDOP_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */ +} D3DBLENDOP; + +typedef enum _D3DTEXTUREADDRESS { + D3DTADDRESS_WRAP = 1, + D3DTADDRESS_MIRROR = 2, + D3DTADDRESS_CLAMP = 3, + D3DTADDRESS_BORDER = 4, + D3DTADDRESS_MIRRORONCE = 5, + D3DTADDRESS_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */ +} D3DTEXTUREADDRESS; + +typedef enum _D3DCULL { + D3DCULL_NONE = 1, + D3DCULL_CW = 2, + D3DCULL_CCW = 3, + D3DCULL_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */ +} D3DCULL; + +typedef enum _D3DCMPFUNC { + D3DCMP_NEVER = 1, + D3DCMP_LESS = 2, + D3DCMP_EQUAL = 3, + D3DCMP_LESSEQUAL = 4, + D3DCMP_GREATER = 5, + D3DCMP_NOTEQUAL = 6, + D3DCMP_GREATEREQUAL = 7, + D3DCMP_ALWAYS = 8, + D3DCMP_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */ +} D3DCMPFUNC; + +typedef enum _D3DSTENCILOP { + D3DSTENCILOP_KEEP = 1, + D3DSTENCILOP_ZERO = 2, + D3DSTENCILOP_REPLACE = 3, + D3DSTENCILOP_INCRSAT = 4, + D3DSTENCILOP_DECRSAT = 5, + D3DSTENCILOP_INVERT = 6, + D3DSTENCILOP_INCR = 7, + D3DSTENCILOP_DECR = 8, + D3DSTENCILOP_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */ +} D3DSTENCILOP; + +typedef enum _D3DFOGMODE { + D3DFOG_NONE = 0, + D3DFOG_EXP = 1, + D3DFOG_EXP2 = 2, + D3DFOG_LINEAR = 3, + D3DFOG_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */ +} D3DFOGMODE; + +typedef enum _D3DZBUFFERTYPE { + D3DZB_FALSE = 0, + D3DZB_TRUE = 1, // Z buffering + D3DZB_USEW = 2, // W buffering + D3DZB_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */ +} D3DZBUFFERTYPE; + +// Primitives supported by draw-primitive API +typedef enum _D3DPRIMITIVETYPE { + D3DPT_POINTLIST = 1, + D3DPT_LINELIST = 2, + D3DPT_LINESTRIP = 3, + D3DPT_TRIANGLELIST = 4, + D3DPT_TRIANGLESTRIP = 5, + D3DPT_TRIANGLEFAN = 6, + D3DPT_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */ +} D3DPRIMITIVETYPE; + +typedef enum _D3DTRANSFORMSTATETYPE { + D3DTS_VIEW = 2, + D3DTS_PROJECTION = 3, + D3DTS_TEXTURE0 = 16, + D3DTS_TEXTURE1 = 17, + D3DTS_TEXTURE2 = 18, + D3DTS_TEXTURE3 = 19, + D3DTS_TEXTURE4 = 20, + D3DTS_TEXTURE5 = 21, + D3DTS_TEXTURE6 = 22, + D3DTS_TEXTURE7 = 23, + D3DTS_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */ +} D3DTRANSFORMSTATETYPE; + +#define D3DTS_WORLDMATRIX(index) (D3DTRANSFORMSTATETYPE)(index + 256) +#define D3DTS_WORLD D3DTS_WORLDMATRIX(0) +#define D3DTS_WORLD1 D3DTS_WORLDMATRIX(1) +#define D3DTS_WORLD2 D3DTS_WORLDMATRIX(2) +#define D3DTS_WORLD3 D3DTS_WORLDMATRIX(3) + +typedef enum _D3DRENDERSTATETYPE { + D3DRS_ZENABLE = 7, /* D3DZBUFFERTYPE (or TRUE/FALSE for legacy) */ + D3DRS_FILLMODE = 8, /* D3DFILLMODE */ + D3DRS_SHADEMODE = 9, /* D3DSHADEMODE */ + D3DRS_LINEPATTERN = 10, /* D3DLINEPATTERN */ + D3DRS_ZWRITEENABLE = 14, /* TRUE to enable z writes */ + D3DRS_ALPHATESTENABLE = 15, /* TRUE to enable alpha tests */ + D3DRS_LASTPIXEL = 16, /* TRUE for last-pixel on lines */ + D3DRS_SRCBLEND = 19, /* D3DBLEND */ + D3DRS_DESTBLEND = 20, /* D3DBLEND */ + D3DRS_CULLMODE = 22, /* D3DCULL */ + D3DRS_ZFUNC = 23, /* D3DCMPFUNC */ + D3DRS_ALPHAREF = 24, /* D3DFIXED */ + D3DRS_ALPHAFUNC = 25, /* D3DCMPFUNC */ + D3DRS_DITHERENABLE = 26, /* TRUE to enable dithering */ + D3DRS_ALPHABLENDENABLE = 27, /* TRUE to enable alpha blending */ + D3DRS_FOGENABLE = 28, /* TRUE to enable fog blending */ + D3DRS_SPECULARENABLE = 29, /* TRUE to enable specular */ + D3DRS_ZVISIBLE = 30, /* TRUE to enable z checking */ + D3DRS_FOGCOLOR = 34, /* D3DCOLOR */ + D3DRS_FOGTABLEMODE = 35, /* D3DFOGMODE */ + D3DRS_FOGSTART = 36, /* Fog start (for both vertex and pixel fog) */ + D3DRS_FOGEND = 37, /* Fog end */ + D3DRS_FOGDENSITY = 38, /* Fog density */ + D3DRS_EDGEANTIALIAS = 40, /* TRUE to enable edge antialiasing */ + D3DRS_ZBIAS = 47, /* LONG Z bias */ + D3DRS_RANGEFOGENABLE = 48, /* Enables range-based fog */ + D3DRS_STENCILENABLE = 52, /* BOOL enable/disable stenciling */ + D3DRS_STENCILFAIL = 53, /* D3DSTENCILOP to do if stencil test fails */ + D3DRS_STENCILZFAIL = 54, /* D3DSTENCILOP to do if stencil test passes and Z test fails */ + D3DRS_STENCILPASS = 55, /* D3DSTENCILOP to do if both stencil and Z tests pass */ + D3DRS_STENCILFUNC = 56, /* D3DCMPFUNC fn. Stencil Test passes if ((ref & mask) stencilfn (stencil & mask)) is true */ + D3DRS_STENCILREF = 57, /* Reference value used in stencil test */ + D3DRS_STENCILMASK = 58, /* Mask value used in stencil test */ + D3DRS_STENCILWRITEMASK = 59, /* Write mask applied to values written to stencil buffer */ + D3DRS_TEXTUREFACTOR = 60, /* D3DCOLOR used for multi-texture blend */ + D3DRS_WRAP0 = 128, /* wrap for 1st texture coord. set */ + D3DRS_WRAP1 = 129, /* wrap for 2nd texture coord. set */ + D3DRS_WRAP2 = 130, /* wrap for 3rd texture coord. set */ + D3DRS_WRAP3 = 131, /* wrap for 4th texture coord. set */ + D3DRS_WRAP4 = 132, /* wrap for 5th texture coord. set */ + D3DRS_WRAP5 = 133, /* wrap for 6th texture coord. set */ + D3DRS_WRAP6 = 134, /* wrap for 7th texture coord. set */ + D3DRS_WRAP7 = 135, /* wrap for 8th texture coord. set */ + D3DRS_CLIPPING = 136, + D3DRS_LIGHTING = 137, + D3DRS_AMBIENT = 139, + D3DRS_FOGVERTEXMODE = 140, + D3DRS_COLORVERTEX = 141, + D3DRS_LOCALVIEWER = 142, + D3DRS_NORMALIZENORMALS = 143, + D3DRS_DIFFUSEMATERIALSOURCE = 145, + D3DRS_SPECULARMATERIALSOURCE = 146, + D3DRS_AMBIENTMATERIALSOURCE = 147, + D3DRS_EMISSIVEMATERIALSOURCE = 148, + D3DRS_VERTEXBLEND = 151, + D3DRS_CLIPPLANEENABLE = 152, + D3DRS_SOFTWAREVERTEXPROCESSING = 153, + D3DRS_POINTSIZE = 154, /* float point size */ + D3DRS_POINTSIZE_MIN = 155, /* float point size min threshold */ + D3DRS_POINTSPRITEENABLE = 156, /* BOOL point texture coord control */ + D3DRS_POINTSCALEENABLE = 157, /* BOOL point size scale enable */ + D3DRS_POINTSCALE_A = 158, /* float point attenuation A value */ + D3DRS_POINTSCALE_B = 159, /* float point attenuation B value */ + D3DRS_POINTSCALE_C = 160, /* float point attenuation C value */ + D3DRS_MULTISAMPLEANTIALIAS = 161, // BOOL - set to do FSAA with multisample buffer + D3DRS_MULTISAMPLEMASK = 162, // DWORD - per-sample enable/disable + D3DRS_PATCHEDGESTYLE = 163, // Sets whether patch edges will use float style tessellation + D3DRS_PATCHSEGMENTS = 164, // Number of segments per edge when drawing patches + D3DRS_DEBUGMONITORTOKEN = 165, // DEBUG ONLY - token to debug monitor + D3DRS_POINTSIZE_MAX = 166, /* float point size max threshold */ + D3DRS_INDEXEDVERTEXBLENDENABLE = 167, + D3DRS_COLORWRITEENABLE = 168, // per-channel write enable + D3DRS_TWEENFACTOR = 170, // float tween factor + D3DRS_BLENDOP = 171, // D3DBLENDOP setting + D3DRS_POSITIONORDER = 172, // NPatch position interpolation order. D3DORDER_LINEAR or D3DORDER_CUBIC (default) + D3DRS_NORMALORDER = 173, // NPatch normal interpolation order. D3DORDER_LINEAR (default) or D3DORDER_QUADRATIC + + D3DRS_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */ +} D3DRENDERSTATETYPE; + +// Values for material source +typedef enum _D3DMATERIALCOLORSOURCE +{ + D3DMCS_MATERIAL = 0, // Color from material is used + D3DMCS_COLOR1 = 1, // Diffuse vertex color is used + D3DMCS_COLOR2 = 2, // Specular vertex color is used + D3DMCS_FORCE_DWORD = 0x7fffffff, // force 32-bit size enum +} D3DMATERIALCOLORSOURCE; + +// Bias to apply to the texture coordinate set to apply a wrap to. +#define D3DRENDERSTATE_WRAPBIAS 128UL + +/* Flags to construct the WRAP render states */ +#define D3DWRAP_U 0x00000001L +#define D3DWRAP_V 0x00000002L +#define D3DWRAP_W 0x00000004L + +/* Flags to construct the WRAP render states for 1D thru 4D texture coordinates */ +#define D3DWRAPCOORD_0 0x00000001L // same as D3DWRAP_U +#define D3DWRAPCOORD_1 0x00000002L // same as D3DWRAP_V +#define D3DWRAPCOORD_2 0x00000004L // same as D3DWRAP_W +#define D3DWRAPCOORD_3 0x00000008L + +/* Flags to construct D3DRS_COLORWRITEENABLE */ +#define D3DCOLORWRITEENABLE_RED (1L<<0) +#define D3DCOLORWRITEENABLE_GREEN (1L<<1) +#define D3DCOLORWRITEENABLE_BLUE (1L<<2) +#define D3DCOLORWRITEENABLE_ALPHA (1L<<3) + +/* + * State enumerants for per-stage texture processing. + */ +typedef enum _D3DTEXTURESTAGESTATETYPE +{ + D3DTSS_COLOROP = 1, /* D3DTEXTUREOP - per-stage blending controls for color channels */ + D3DTSS_COLORARG1 = 2, /* D3DTA_* (texture arg) */ + D3DTSS_COLORARG2 = 3, /* D3DTA_* (texture arg) */ + D3DTSS_ALPHAOP = 4, /* D3DTEXTUREOP - per-stage blending controls for alpha channel */ + D3DTSS_ALPHAARG1 = 5, /* D3DTA_* (texture arg) */ + D3DTSS_ALPHAARG2 = 6, /* D3DTA_* (texture arg) */ + D3DTSS_BUMPENVMAT00 = 7, /* float (bump mapping matrix) */ + D3DTSS_BUMPENVMAT01 = 8, /* float (bump mapping matrix) */ + D3DTSS_BUMPENVMAT10 = 9, /* float (bump mapping matrix) */ + D3DTSS_BUMPENVMAT11 = 10, /* float (bump mapping matrix) */ + D3DTSS_TEXCOORDINDEX = 11, /* identifies which set of texture coordinates index this texture */ + D3DTSS_ADDRESSU = 13, /* D3DTEXTUREADDRESS for U coordinate */ + D3DTSS_ADDRESSV = 14, /* D3DTEXTUREADDRESS for V coordinate */ + D3DTSS_BORDERCOLOR = 15, /* D3DCOLOR */ + D3DTSS_MAGFILTER = 16, /* D3DTEXTUREFILTER filter to use for magnification */ + D3DTSS_MINFILTER = 17, /* D3DTEXTUREFILTER filter to use for minification */ + D3DTSS_MIPFILTER = 18, /* D3DTEXTUREFILTER filter to use between mipmaps during minification */ + D3DTSS_MIPMAPLODBIAS = 19, /* float Mipmap LOD bias */ + D3DTSS_MAXMIPLEVEL = 20, /* DWORD 0..(n-1) LOD index of largest map to use (0 == largest) */ + D3DTSS_MAXANISOTROPY = 21, /* DWORD maximum anisotropy */ + D3DTSS_BUMPENVLSCALE = 22, /* float scale for bump map luminance */ + D3DTSS_BUMPENVLOFFSET = 23, /* float offset for bump map luminance */ + D3DTSS_TEXTURETRANSFORMFLAGS = 24, /* D3DTEXTURETRANSFORMFLAGS controls texture transform */ + D3DTSS_ADDRESSW = 25, /* D3DTEXTUREADDRESS for W coordinate */ + D3DTSS_COLORARG0 = 26, /* D3DTA_* third arg for triadic ops */ + D3DTSS_ALPHAARG0 = 27, /* D3DTA_* third arg for triadic ops */ + D3DTSS_RESULTARG = 28, /* D3DTA_* arg for result (CURRENT or TEMP) */ + D3DTSS_FORCE_DWORD = 0x7fffffff, /* force 32-bit size enum */ +} D3DTEXTURESTAGESTATETYPE; + +// Values, used with D3DTSS_TEXCOORDINDEX, to specify that the vertex data(position +// and normal in the camera space) should be taken as texture coordinates +// Low 16 bits are used to specify texture coordinate index, to take the WRAP mode from +// +#define D3DTSS_TCI_PASSTHRU 0x00000000 +#define D3DTSS_TCI_CAMERASPACENORMAL 0x00010000 +#define D3DTSS_TCI_CAMERASPACEPOSITION 0x00020000 +#define D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR 0x00030000 + +/* + * Enumerations for COLOROP and ALPHAOP texture blending operations set in + * texture processing stage controls in D3DTSS. + */ +typedef enum _D3DTEXTUREOP +{ + // Control + D3DTOP_DISABLE = 1, // disables stage + D3DTOP_SELECTARG1 = 2, // the default + D3DTOP_SELECTARG2 = 3, + + // Modulate + D3DTOP_MODULATE = 4, // multiply args together + D3DTOP_MODULATE2X = 5, // multiply and 1 bit + D3DTOP_MODULATE4X = 6, // multiply and 2 bits + + // Add + D3DTOP_ADD = 7, // add arguments together + D3DTOP_ADDSIGNED = 8, // add with -0.5 bias + D3DTOP_ADDSIGNED2X = 9, // as above but left 1 bit + D3DTOP_SUBTRACT = 10, // Arg1 - Arg2, with no saturation + D3DTOP_ADDSMOOTH = 11, // add 2 args, subtract product + // Arg1 + Arg2 - Arg1*Arg2 + // = Arg1 + (1-Arg1)*Arg2 + + // Linear alpha blend: Arg1*(Alpha) + Arg2*(1-Alpha) + D3DTOP_BLENDDIFFUSEALPHA = 12, // iterated alpha + D3DTOP_BLENDTEXTUREALPHA = 13, // texture alpha + D3DTOP_BLENDFACTORALPHA = 14, // alpha from D3DRS_TEXTUREFACTOR + + // Linear alpha blend with pre-multiplied arg1 input: Arg1 + Arg2*(1-Alpha) + D3DTOP_BLENDTEXTUREALPHAPM = 15, // texture alpha + D3DTOP_BLENDCURRENTALPHA = 16, // by alpha of current color + + // Specular mapping + D3DTOP_PREMODULATE = 17, // modulate with next texture before use + D3DTOP_MODULATEALPHA_ADDCOLOR = 18, // Arg1.RGB + Arg1.A*Arg2.RGB + // COLOROP only + D3DTOP_MODULATECOLOR_ADDALPHA = 19, // Arg1.RGB*Arg2.RGB + Arg1.A + // COLOROP only + D3DTOP_MODULATEINVALPHA_ADDCOLOR = 20, // (1-Arg1.A)*Arg2.RGB + Arg1.RGB + // COLOROP only + D3DTOP_MODULATEINVCOLOR_ADDALPHA = 21, // (1-Arg1.RGB)*Arg2.RGB + Arg1.A + // COLOROP only + + // Bump mapping + D3DTOP_BUMPENVMAP = 22, // per pixel env map perturbation + D3DTOP_BUMPENVMAPLUMINANCE = 23, // with luminance channel + + // This can do either diffuse or specular bump mapping with correct input. + // Performs the function (Arg1.R*Arg2.R + Arg1.G*Arg2.G + Arg1.B*Arg2.B) + // where each component has been scaled and offset to make it signed. + // The result is replicated into all four (including alpha) channels. + // This is a valid COLOROP only. + D3DTOP_DOTPRODUCT3 = 24, + + // Triadic ops + D3DTOP_MULTIPLYADD = 25, // Arg0 + Arg1*Arg2 + D3DTOP_LERP = 26, // (Arg0)*Arg1 + (1-Arg0)*Arg2 + + D3DTOP_FORCE_DWORD = 0x7fffffff, +} D3DTEXTUREOP; + +/* + * Values for COLORARG0,1,2, ALPHAARG0,1,2, and RESULTARG texture blending + * operations set in texture processing stage controls in D3DRENDERSTATE. + */ +#define D3DTA_SELECTMASK 0x0000000f // mask for arg selector +#define D3DTA_DIFFUSE 0x00000000 // select diffuse color (read only) +#define D3DTA_CURRENT 0x00000001 // select stage destination register (read/write) +#define D3DTA_TEXTURE 0x00000002 // select texture color (read only) +#define D3DTA_TFACTOR 0x00000003 // select D3DRS_TEXTUREFACTOR (read only) +#define D3DTA_SPECULAR 0x00000004 // select specular color (read only) +#define D3DTA_TEMP 0x00000005 // select temporary register color (read/write) +#define D3DTA_COMPLEMENT 0x00000010 // take 1.0 - x (read modifier) +#define D3DTA_ALPHAREPLICATE 0x00000020 // replicate alpha to color components (read modifier) + +// +// Values for D3DTSS_***FILTER texture stage states +// +typedef enum _D3DTEXTUREFILTERTYPE +{ + D3DTEXF_NONE = 0, // filtering disabled (valid for mip filter only) + D3DTEXF_POINT = 1, // nearest + D3DTEXF_LINEAR = 2, // linear interpolation + D3DTEXF_ANISOTROPIC = 3, // anisotropic + D3DTEXF_FLATCUBIC = 4, // cubic + D3DTEXF_GAUSSIANCUBIC = 5, // different cubic kernel + D3DTEXF_FORCE_DWORD = 0x7fffffff, // force 32-bit size enum +} D3DTEXTUREFILTERTYPE; + +/* Bits for Flags in ProcessVertices call */ + +#define D3DPV_DONOTCOPYDATA (1 << 0) + +//------------------------------------------------------------------- + +// Flexible vertex format bits +// +#define D3DFVF_RESERVED0 0x001 +#define D3DFVF_POSITION_MASK 0x00E +#define D3DFVF_XYZ 0x002 +#define D3DFVF_XYZRHW 0x004 +#define D3DFVF_XYZB1 0x006 +#define D3DFVF_XYZB2 0x008 +#define D3DFVF_XYZB3 0x00a +#define D3DFVF_XYZB4 0x00c +#define D3DFVF_XYZB5 0x00e + +#define D3DFVF_NORMAL 0x010 +#define D3DFVF_PSIZE 0x020 +#define D3DFVF_DIFFUSE 0x040 +#define D3DFVF_SPECULAR 0x080 + +#define D3DFVF_TEXCOUNT_MASK 0xf00 +#define D3DFVF_TEXCOUNT_SHIFT 8 +#define D3DFVF_TEX0 0x000 +#define D3DFVF_TEX1 0x100 +#define D3DFVF_TEX2 0x200 +#define D3DFVF_TEX3 0x300 +#define D3DFVF_TEX4 0x400 +#define D3DFVF_TEX5 0x500 +#define D3DFVF_TEX6 0x600 +#define D3DFVF_TEX7 0x700 +#define D3DFVF_TEX8 0x800 + +#define D3DFVF_LASTBETA_UBYTE4 0x1000 + +#define D3DFVF_RESERVED2 0xE000 // 4 reserved bits + +//--------------------------------------------------------------------- +// Vertex Shaders +// + +/* + +Vertex Shader Declaration + +The declaration portion of a vertex shader defines the static external +interface of the shader. The information in the declaration includes: + +- Assignments of vertex shader input registers to data streams. These +assignments bind a specific vertex register to a single component within a +vertex stream. A vertex stream element is identified by a byte offset +within the stream and a type. The type specifies the arithmetic data type +plus the dimensionality (1, 2, 3, or 4 values). Stream data which is +less than 4 values are always expanded out to 4 values with zero or more +0.F values and one 1.F value. + +- Assignment of vertex shader input registers to implicit data from the +primitive tessellator. This controls the loading of vertex data which is +not loaded from a stream, but rather is generated during primitive +tessellation prior to the vertex shader. + +- Loading data into the constant memory at the time a shader is set as the +current shader. Each token specifies values for one or more contiguous 4 +DWORD constant registers. This allows the shader to update an arbitrary +subset of the constant memory, overwriting the device state (which +contains the current values of the constant memory). Note that these +values can be subsequently overwritten (between DrawPrimitive calls) +during the time a shader is bound to a device via the +SetVertexShaderConstant method. + + +Declaration arrays are single-dimensional arrays of DWORDs composed of +multiple tokens each of which is one or more DWORDs. The single-DWORD +token value 0xFFFFFFFF is a special token used to indicate the end of the +declaration array. The single DWORD token value 0x00000000 is a NOP token +with is ignored during the declaration parsing. Note that 0x00000000 is a +valid value for DWORDs following the first DWORD for multiple word tokens. + +[31:29] TokenType + 0x0 - NOP (requires all DWORD bits to be zero) + 0x1 - stream selector + 0x2 - stream data definition (map to vertex input memory) + 0x3 - vertex input memory from tessellator + 0x4 - constant memory from shader + 0x5 - extension + 0x6 - reserved + 0x7 - end-of-array (requires all DWORD bits to be 1) + +NOP Token (single DWORD token) + [31:29] 0x0 + [28:00] 0x0 + +Stream Selector (single DWORD token) + [31:29] 0x1 + [28] indicates whether this is a tessellator stream + [27:04] 0x0 + [03:00] stream selector (0..15) + +Stream Data Definition (single DWORD token) + Vertex Input Register Load + [31:29] 0x2 + [28] 0x0 + [27:20] 0x0 + [19:16] type (dimensionality and data type) + [15:04] 0x0 + [03:00] vertex register address (0..15) + Data Skip (no register load) + [31:29] 0x2 + [28] 0x1 + [27:20] 0x0 + [19:16] count of DWORDS to skip over (0..15) + [15:00] 0x0 + Vertex Input Memory from Tessellator Data (single DWORD token) + [31:29] 0x3 + [28] indicates whether data is normals or u/v + [27:24] 0x0 + [23:20] vertex register address (0..15) + [19:16] type (dimensionality) + [15:04] 0x0 + [03:00] vertex register address (0..15) + +Constant Memory from Shader (multiple DWORD token) + [31:29] 0x4 + [28:25] count of 4*DWORD constants to load (0..15) + [24:07] 0x0 + [06:00] constant memory address (0..95) + +Extension Token (single or multiple DWORD token) + [31:29] 0x5 + [28:24] count of additional DWORDs in token (0..31) + [23:00] extension-specific information + +End-of-array token (single DWORD token) + [31:29] 0x7 + [28:00] 0x1fffffff + +The stream selector token must be immediately followed by a contiguous set of stream data definition tokens. This token sequence fully defines that stream, including the set of elements within the stream, the order in which the elements appear, the type of each element, and the vertex register into which to load an element. +Streams are allowed to include data which is not loaded into a vertex register, thus allowing data which is not used for this shader to exist in the vertex stream. This skipped data is defined only by a count of DWORDs to skip over, since the type information is irrelevant. +The token sequence: +Stream Select: stream=0 +Stream Data Definition (Load): type=FLOAT3; register=3 +Stream Data Definition (Load): type=FLOAT3; register=4 +Stream Data Definition (Skip): count=2 +Stream Data Definition (Load): type=FLOAT2; register=7 + +defines stream zero to consist of 4 elements, 3 of which are loaded into registers and the fourth skipped over. Register 3 is loaded with the first three DWORDs in each vertex interpreted as FLOAT data. Register 4 is loaded with the 4th, 5th, and 6th DWORDs interpreted as FLOAT data. The next two DWORDs (7th and 8th) are skipped over and not loaded into any vertex input register. Register 7 is loaded with the 9th and 10th DWORDS interpreted as FLOAT data. +Placing of tokens other than NOPs between the Stream Selector and Stream Data Definition tokens is disallowed. + +*/ + +typedef enum _D3DVSD_TOKENTYPE +{ + D3DVSD_TOKEN_NOP = 0, // NOP or extension + D3DVSD_TOKEN_STREAM, // stream selector + D3DVSD_TOKEN_STREAMDATA, // stream data definition (map to vertex input memory) + D3DVSD_TOKEN_TESSELLATOR, // vertex input memory from tessellator + D3DVSD_TOKEN_CONSTMEM, // constant memory from shader + D3DVSD_TOKEN_EXT, // extension + D3DVSD_TOKEN_END = 7, // end-of-array (requires all DWORD bits to be 1) + D3DVSD_FORCE_DWORD = 0x7fffffff,// force 32-bit size enum +} D3DVSD_TOKENTYPE; + +#define D3DVSD_TOKENTYPESHIFT 29 +#define D3DVSD_TOKENTYPEMASK (7 << D3DVSD_TOKENTYPESHIFT) + +#define D3DVSD_STREAMNUMBERSHIFT 0 +#define D3DVSD_STREAMNUMBERMASK (0xF << D3DVSD_STREAMNUMBERSHIFT) + +#define D3DVSD_DATALOADTYPESHIFT 28 +#define D3DVSD_DATALOADTYPEMASK (0x1 << D3DVSD_DATALOADTYPESHIFT) + +#define D3DVSD_DATATYPESHIFT 16 +#define D3DVSD_DATATYPEMASK (0xF << D3DVSD_DATATYPESHIFT) + +#define D3DVSD_SKIPCOUNTSHIFT 16 +#define D3DVSD_SKIPCOUNTMASK (0xF << D3DVSD_SKIPCOUNTSHIFT) + +#define D3DVSD_VERTEXREGSHIFT 0 +#define D3DVSD_VERTEXREGMASK (0x1F << D3DVSD_VERTEXREGSHIFT) + +#define D3DVSD_VERTEXREGINSHIFT 20 +#define D3DVSD_VERTEXREGINMASK (0xF << D3DVSD_VERTEXREGINSHIFT) + +#define D3DVSD_CONSTCOUNTSHIFT 25 +#define D3DVSD_CONSTCOUNTMASK (0xF << D3DVSD_CONSTCOUNTSHIFT) + +#define D3DVSD_CONSTADDRESSSHIFT 0 +#define D3DVSD_CONSTADDRESSMASK (0x7F << D3DVSD_CONSTADDRESSSHIFT) + +#define D3DVSD_CONSTRSSHIFT 16 +#define D3DVSD_CONSTRSMASK (0x1FFF << D3DVSD_CONSTRSSHIFT) + +#define D3DVSD_EXTCOUNTSHIFT 24 +#define D3DVSD_EXTCOUNTMASK (0x1F << D3DVSD_EXTCOUNTSHIFT) + +#define D3DVSD_EXTINFOSHIFT 0 +#define D3DVSD_EXTINFOMASK (0xFFFFFF << D3DVSD_EXTINFOSHIFT) + +#define D3DVSD_MAKETOKENTYPE(tokenType) ((tokenType << D3DVSD_TOKENTYPESHIFT) & D3DVSD_TOKENTYPEMASK) + +// macros for generation of CreateVertexShader Declaration token array + +// Set current stream +// _StreamNumber [0..(MaxStreams-1)] stream to get data from +// +#define D3DVSD_STREAM( _StreamNumber ) \ + (D3DVSD_MAKETOKENTYPE(D3DVSD_TOKEN_STREAM) | (_StreamNumber)) + +// Set tessellator stream +// +#define D3DVSD_STREAMTESSSHIFT 28 +#define D3DVSD_STREAMTESSMASK (1 << D3DVSD_STREAMTESSSHIFT) +#define D3DVSD_STREAM_TESS( ) \ + (D3DVSD_MAKETOKENTYPE(D3DVSD_TOKEN_STREAM) | (D3DVSD_STREAMTESSMASK)) + +// bind single vertex register to vertex element from vertex stream +// +// _VertexRegister [0..15] address of the vertex register +// _Type [D3DVSDT_*] dimensionality and arithmetic data type + +#define D3DVSD_REG( _VertexRegister, _Type ) \ + (D3DVSD_MAKETOKENTYPE(D3DVSD_TOKEN_STREAMDATA) | \ + ((_Type) << D3DVSD_DATATYPESHIFT) | (_VertexRegister)) + +// Skip _DWORDCount DWORDs in vertex +// +#define D3DVSD_SKIP( _DWORDCount ) \ + (D3DVSD_MAKETOKENTYPE(D3DVSD_TOKEN_STREAMDATA) | 0x10000000 | \ + ((_DWORDCount) << D3DVSD_SKIPCOUNTSHIFT)) + +// load data into vertex shader constant memory +// +// _ConstantAddress [0..95] - address of constant array to begin filling data +// _Count [0..15] - number of constant vectors to load (4 DWORDs each) +// followed by 4*_Count DWORDS of data +// +#define D3DVSD_CONST( _ConstantAddress, _Count ) \ + (D3DVSD_MAKETOKENTYPE(D3DVSD_TOKEN_CONSTMEM) | \ + ((_Count) << D3DVSD_CONSTCOUNTSHIFT) | (_ConstantAddress)) + +// enable tessellator generated normals +// +// _VertexRegisterIn [0..15] address of vertex register whose input stream +// will be used in normal computation +// _VertexRegisterOut [0..15] address of vertex register to output the normal to +// +#define D3DVSD_TESSNORMAL( _VertexRegisterIn, _VertexRegisterOut ) \ + (D3DVSD_MAKETOKENTYPE(D3DVSD_TOKEN_TESSELLATOR) | \ + ((_VertexRegisterIn) << D3DVSD_VERTEXREGINSHIFT) | \ + ((0x02) << D3DVSD_DATATYPESHIFT) | (_VertexRegisterOut)) + +// enable tessellator generated surface parameters +// +// _VertexRegister [0..15] address of vertex register to output parameters +// +#define D3DVSD_TESSUV( _VertexRegister ) \ + (D3DVSD_MAKETOKENTYPE(D3DVSD_TOKEN_TESSELLATOR) | 0x10000000 | \ + ((0x01) << D3DVSD_DATATYPESHIFT) | (_VertexRegister)) + +// Generates END token +// +#define D3DVSD_END() 0xFFFFFFFF + +// Generates NOP token +#define D3DVSD_NOP() 0x00000000 + +// bit declarations for _Type fields +#define D3DVSDT_FLOAT1 0x00 // 1D float expanded to (value, 0., 0., 1.) +#define D3DVSDT_FLOAT2 0x01 // 2D float expanded to (value, value, 0., 1.) +#define D3DVSDT_FLOAT3 0x02 // 3D float expanded to (value, value, value, 1.) +#define D3DVSDT_FLOAT4 0x03 // 4D float +#define D3DVSDT_D3DCOLOR 0x04 // 4D packed unsigned bytes mapped to 0. to 1. range + // Input is in D3DCOLOR format (ARGB) expanded to (R, G, B, A) +#define D3DVSDT_UBYTE4 0x05 // 4D unsigned byte +#define D3DVSDT_SHORT2 0x06 // 2D signed short expanded to (value, value, 0., 1.) +#define D3DVSDT_SHORT4 0x07 // 4D signed short + +// assignments of vertex input registers for fixed function vertex shader +// +#define D3DVSDE_POSITION 0 +#define D3DVSDE_BLENDWEIGHT 1 +#define D3DVSDE_BLENDINDICES 2 +#define D3DVSDE_NORMAL 3 +#define D3DVSDE_PSIZE 4 +#define D3DVSDE_DIFFUSE 5 +#define D3DVSDE_SPECULAR 6 +#define D3DVSDE_TEXCOORD0 7 +#define D3DVSDE_TEXCOORD1 8 +#define D3DVSDE_TEXCOORD2 9 +#define D3DVSDE_TEXCOORD3 10 +#define D3DVSDE_TEXCOORD4 11 +#define D3DVSDE_TEXCOORD5 12 +#define D3DVSDE_TEXCOORD6 13 +#define D3DVSDE_TEXCOORD7 14 +#define D3DVSDE_POSITION2 15 +#define D3DVSDE_NORMAL2 16 + +// Maximum supported number of texture coordinate sets +#define D3DDP_MAXTEXCOORD 8 + + +// +// Instruction Token Bit Definitions +// +#define D3DSI_OPCODE_MASK 0x0000FFFF + +typedef enum _D3DSHADER_INSTRUCTION_OPCODE_TYPE +{ + D3DSIO_NOP = 0, // PS/VS + D3DSIO_MOV , // PS/VS + D3DSIO_ADD , // PS/VS + D3DSIO_SUB , // PS + D3DSIO_MAD , // PS/VS + D3DSIO_MUL , // PS/VS + D3DSIO_RCP , // VS + D3DSIO_RSQ , // VS + D3DSIO_DP3 , // PS/VS + D3DSIO_DP4 , // PS/VS + D3DSIO_MIN , // VS + D3DSIO_MAX , // VS + D3DSIO_SLT , // VS + D3DSIO_SGE , // VS + D3DSIO_EXP , // VS + D3DSIO_LOG , // VS + D3DSIO_LIT , // VS + D3DSIO_DST , // VS + D3DSIO_LRP , // PS + D3DSIO_FRC , // VS + D3DSIO_M4x4 , // VS + D3DSIO_M4x3 , // VS + D3DSIO_M3x4 , // VS + D3DSIO_M3x3 , // VS + D3DSIO_M3x2 , // VS + + D3DSIO_TEXCOORD = 64, // PS + D3DSIO_TEXKILL , // PS + D3DSIO_TEX , // PS + D3DSIO_TEXBEM , // PS + D3DSIO_TEXBEML , // PS + D3DSIO_TEXREG2AR , // PS + D3DSIO_TEXREG2GB , // PS + D3DSIO_TEXM3x2PAD , // PS + D3DSIO_TEXM3x2TEX , // PS + D3DSIO_TEXM3x3PAD , // PS + D3DSIO_TEXM3x3TEX , // PS + D3DSIO_TEXM3x3DIFF , // PS + D3DSIO_TEXM3x3SPEC , // PS + D3DSIO_TEXM3x3VSPEC , // PS + D3DSIO_EXPP , // VS + D3DSIO_LOGP , // VS + D3DSIO_CND , // PS + D3DSIO_DEF , // PS + D3DSIO_TEXREG2RGB , // PS + D3DSIO_TEXDP3TEX , // PS + D3DSIO_TEXM3x2DEPTH , // PS + D3DSIO_TEXDP3 , // PS + D3DSIO_TEXM3x3 , // PS + D3DSIO_TEXDEPTH , // PS + D3DSIO_CMP , // PS + D3DSIO_BEM , // PS + + D3DSIO_PHASE = 0xFFFD, + D3DSIO_COMMENT = 0xFFFE, + D3DSIO_END = 0xFFFF, + + D3DSIO_FORCE_DWORD = 0x7fffffff, // force 32-bit size enum +} D3DSHADER_INSTRUCTION_OPCODE_TYPE; + +// +// Co-Issue Instruction Modifier - if set then this instruction is to be +// issued in parallel with the previous instruction(s) for which this bit +// is not set. +// +#define D3DSI_COISSUE 0x40000000 + +// +// Parameter Token Bit Definitions +// +#define D3DSP_REGNUM_MASK 0x00001FFF + +// destination parameter write mask +#define D3DSP_WRITEMASK_0 0x00010000 // Component 0 (X;Red) +#define D3DSP_WRITEMASK_1 0x00020000 // Component 1 (Y;Green) +#define D3DSP_WRITEMASK_2 0x00040000 // Component 2 (Z;Blue) +#define D3DSP_WRITEMASK_3 0x00080000 // Component 3 (W;Alpha) +#define D3DSP_WRITEMASK_ALL 0x000F0000 // All Components + +// destination parameter modifiers +#define D3DSP_DSTMOD_SHIFT 20 +#define D3DSP_DSTMOD_MASK 0x00F00000 + +typedef enum _D3DSHADER_PARAM_DSTMOD_TYPE +{ + D3DSPDM_NONE = 0<>8)&0xFF) +#define D3DSHADER_VERSION_MINOR(_Version) (((_Version)>>0)&0xFF) + +// destination/source parameter register type +#define D3DSI_COMMENTSIZE_SHIFT 16 +#define D3DSI_COMMENTSIZE_MASK 0x7FFF0000 +#define D3DSHADER_COMMENT(_DWordSize) \ + ((((_DWordSize)<= 0x0800) */ +#endif /* _D3D8TYPES(P)_H_ */ + diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8standalonetypes.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8standalonetypes.h new file mode 100644 index 00000000000..88f02a6f69f --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8standalonetypes.h @@ -0,0 +1,755 @@ +/* +** 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 + +#if !defined(GGC_RENDER_BACKEND_BGFX) +#error dx8standalonetypes.h is only for the standalone bgfx compatibility build. +#endif + +#include "WWLib/win.h" +#include "WWMath/legacyd3dmatrix.h" + +#include + +struct D3DSURFACE_DESC; +struct D3DLOCKED_RECT; +struct IDirect3DSurface8; + +struct IDirect3D8; +struct IDirect3DDevice8; +struct IDirect3DBaseTexture8 +{ + ULONG AddRef(); + ULONG Release(); +}; +struct IDirect3DTexture8 : IDirect3DBaseTexture8 +{ + HRESULT GetSurfaceLevel(UINT level, IDirect3DSurface8 **surface); +}; +struct IDirect3DCubeTexture8 : IDirect3DBaseTexture8 {}; +struct IDirect3DVolumeTexture8 : IDirect3DBaseTexture8 {}; +struct IDirect3DSurface8 +{ + ULONG AddRef(); + ULONG Release(); + HRESULT GetDesc(D3DSURFACE_DESC *desc); + HRESULT LockRect(D3DLOCKED_RECT *locked_rect, const RECT *rect, DWORD flags); + HRESULT UnlockRect(); +}; +struct IDirect3DSwapChain8; +struct IDirect3DVertexBuffer8; +struct IDirect3DIndexBuffer8; + +#ifndef MAKEFOURCC +#define MAKEFOURCC(ch0, ch1, ch2, ch3) \ + ((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | ((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24)) +#endif + +using D3DCOLOR = DWORD; +#define D3DCOLOR_ARGB(a, r, g, b) ((D3DCOLOR)((((a) & 0xff) << 24) | (((r) & 0xff) << 16) | (((g) & 0xff) << 8) | ((b) & 0xff))) +#define D3DCOLOR_RGBA(r, g, b, a) D3DCOLOR_ARGB(a, r, g, b) +#define D3DCOLOR_COLORVALUE(r, g, b, a) D3DCOLOR_RGBA((DWORD)((r) * 255.f), (DWORD)((g) * 255.f), (DWORD)((b) * 255.f), (DWORD)((a) * 255.f)) + +struct D3DVECTOR +{ + float x; + float y; + float z; +}; + +struct D3DCOLORVALUE +{ + float r; + float g; + float b; + float a; +}; + +struct D3DVIEWPORT8 +{ + DWORD X; + DWORD Y; + DWORD Width; + DWORD Height; + float MinZ; + float MaxZ; +}; + +struct D3DMATERIAL8 +{ + D3DCOLORVALUE Diffuse; + D3DCOLORVALUE Ambient; + D3DCOLORVALUE Specular; + D3DCOLORVALUE Emissive; + float Power; +}; + +enum D3DLIGHTTYPE +{ + D3DLIGHT_POINT = 1, + D3DLIGHT_SPOT = 2, + D3DLIGHT_DIRECTIONAL = 3, + D3DLIGHT_FORCE_DWORD = 0x7fffffff +}; + +struct D3DLIGHT8 +{ + D3DLIGHTTYPE Type; + D3DCOLORVALUE Diffuse; + D3DCOLORVALUE Specular; + D3DCOLORVALUE Ambient; + D3DVECTOR Position; + D3DVECTOR Direction; + float Range; + float Falloff; + float Attenuation0; + float Attenuation1; + float Attenuation2; + float Theta; + float Phi; +}; + +enum D3DDEVTYPE +{ + D3DDEVTYPE_HAL = 1, + D3DDEVTYPE_REF = 2, + D3DDEVTYPE_SW = 3, + D3DDEVTYPE_FORCE_DWORD = 0x7fffffff +}; + +enum D3DRESOURCETYPE +{ + D3DRTYPE_SURFACE = 1, + D3DRTYPE_VOLUME = 2, + D3DRTYPE_TEXTURE = 3, + D3DRTYPE_VOLUMETEXTURE = 4, + D3DRTYPE_CUBETEXTURE = 5, + D3DRTYPE_VERTEXBUFFER = 6, + D3DRTYPE_INDEXBUFFER = 7, + D3DRTYPE_FORCE_DWORD = 0x7fffffff +}; + +enum D3DPOOL +{ + D3DPOOL_DEFAULT = 0, + D3DPOOL_MANAGED = 1, + D3DPOOL_SYSTEMMEM = 2, + D3DPOOL_SCRATCH = 3, + D3DPOOL_FORCE_DWORD = 0x7fffffff +}; + +enum D3DMULTISAMPLE_TYPE +{ + D3DMULTISAMPLE_NONE = 0, + D3DMULTISAMPLE_2_SAMPLES = 2, + D3DMULTISAMPLE_4_SAMPLES = 4, + D3DMULTISAMPLE_8_SAMPLES = 8, + D3DMULTISAMPLE_FORCE_DWORD = 0x7fffffff +}; + +enum D3DSWAPEFFECT +{ + D3DSWAPEFFECT_DISCARD = 1, + D3DSWAPEFFECT_FLIP = 2, + D3DSWAPEFFECT_COPY = 3, + D3DSWAPEFFECT_COPY_VSYNC = 4, + D3DSWAPEFFECT_FORCE_DWORD = 0x7fffffff +}; + +enum D3DBACKBUFFER_TYPE +{ + D3DBACKBUFFER_TYPE_MONO = 0, + D3DBACKBUFFER_TYPE_FORCE_DWORD = 0x7fffffff +}; + +enum D3DFORMAT +{ + D3DFMT_UNKNOWN = 0, + D3DFMT_R8G8B8 = 20, + D3DFMT_A8R8G8B8 = 21, + D3DFMT_X8R8G8B8 = 22, + D3DFMT_R5G6B5 = 23, + D3DFMT_X1R5G5B5 = 24, + D3DFMT_A1R5G5B5 = 25, + D3DFMT_A4R4G4B4 = 26, + D3DFMT_R3G3B2 = 27, + D3DFMT_A8 = 28, + D3DFMT_A8R3G3B2 = 29, + D3DFMT_X4R4G4B4 = 30, + D3DFMT_A8P8 = 40, + D3DFMT_P8 = 41, + D3DFMT_L8 = 50, + D3DFMT_A8L8 = 51, + D3DFMT_A4L4 = 52, + D3DFMT_V8U8 = 60, + D3DFMT_L6V5U5 = 61, + D3DFMT_X8L8V8U8 = 62, + D3DFMT_DXT1 = MAKEFOURCC('D', 'X', 'T', '1'), + D3DFMT_DXT2 = MAKEFOURCC('D', 'X', 'T', '2'), + D3DFMT_DXT3 = MAKEFOURCC('D', 'X', 'T', '3'), + D3DFMT_DXT4 = MAKEFOURCC('D', 'X', 'T', '4'), + D3DFMT_DXT5 = MAKEFOURCC('D', 'X', 'T', '5'), + D3DFMT_D16_LOCKABLE = 70, + D3DFMT_D32 = 71, + D3DFMT_D15S1 = 73, + D3DFMT_D24S8 = 75, + D3DFMT_D24X8 = 77, + D3DFMT_D24X4S4 = 79, + D3DFMT_D16 = 80, + D3DFMT_FORCE_DWORD = 0x7fffffff +}; + +struct D3DDISPLAYMODE +{ + UINT Width; + UINT Height; + UINT RefreshRate; + D3DFORMAT Format; +}; + +struct D3DPRESENT_PARAMETERS +{ + UINT BackBufferWidth; + UINT BackBufferHeight; + D3DFORMAT BackBufferFormat; + UINT BackBufferCount; + D3DMULTISAMPLE_TYPE MultiSampleType; + D3DSWAPEFFECT SwapEffect; + HWND hDeviceWindow; + BOOL Windowed; + BOOL EnableAutoDepthStencil; + D3DFORMAT AutoDepthStencilFormat; + DWORD Flags; + UINT FullScreen_RefreshRateInHz; + UINT FullScreen_PresentationInterval; +}; + +struct D3DSURFACE_DESC +{ + D3DFORMAT Format; + D3DRESOURCETYPE Type; + DWORD Usage; + D3DPOOL Pool; + UINT Size; + D3DMULTISAMPLE_TYPE MultiSampleType; + UINT Width; + UINT Height; +}; + +struct D3DVOLUME_DESC +{ + D3DFORMAT Format; + D3DRESOURCETYPE Type; + DWORD Usage; + D3DPOOL Pool; + UINT Size; + UINT Width; + UINT Height; + UINT Depth; +}; + +struct D3DLOCKED_RECT +{ + INT Pitch; + void *pBits; +}; + +struct D3DLOCKED_BOX +{ + INT RowPitch; + INT SlicePitch; + void *pBits; +}; + +enum D3DCUBEMAP_FACES +{ + D3DCUBEMAP_FACE_POSITIVE_X = 0, + D3DCUBEMAP_FACE_NEGATIVE_X = 1, + D3DCUBEMAP_FACE_POSITIVE_Y = 2, + D3DCUBEMAP_FACE_NEGATIVE_Y = 3, + D3DCUBEMAP_FACE_POSITIVE_Z = 4, + D3DCUBEMAP_FACE_NEGATIVE_Z = 5, + D3DCUBEMAP_FACE_FORCE_DWORD = 0x7fffffff +}; + +struct D3DADAPTER_IDENTIFIER8 +{ + char Driver[512]; + char Description[512]; +#ifdef _WIN32 + LARGE_INTEGER DriverVersion; +#else + DWORD DriverVersionLowPart; + DWORD DriverVersionHighPart; +#endif + DWORD VendorId; + DWORD DeviceId; + DWORD SubSysId; + DWORD Revision; + GUID DeviceIdentifier; + DWORD WHQLLevel; +}; + +enum D3DTRANSFORMSTATETYPE +{ + D3DTS_VIEW = 2, + D3DTS_PROJECTION = 3, + D3DTS_FORCE_DWORD = 0x7fffffff +}; + +#define D3DTS_WORLDMATRIX(index) (D3DTRANSFORMSTATETYPE)((index) + 256) +#define D3DTS_WORLD D3DTS_WORLDMATRIX(0) + +enum D3DRENDERSTATETYPE +{ + D3DRS_ZENABLE = 7, + D3DRS_FILLMODE = 8, + D3DRS_SHADEMODE = 9, + D3DRS_ZWRITEENABLE = 14, + D3DRS_ALPHATESTENABLE = 15, + D3DRS_SRCBLEND = 19, + D3DRS_DESTBLEND = 20, + D3DRS_CULLMODE = 22, + D3DRS_ZFUNC = 23, + D3DRS_ALPHAREF = 24, + D3DRS_ALPHAFUNC = 25, + D3DRS_ALPHABLENDENABLE = 27, + D3DRS_FOGENABLE = 28, + D3DRS_SPECULARENABLE = 29, + D3DRS_FOGCOLOR = 34, + D3DRS_FOGSTART = 36, + D3DRS_FOGEND = 37, + D3DRS_ZBIAS = 47, + D3DRS_STENCILENABLE = 52, + D3DRS_STENCILFAIL = 53, + D3DRS_STENCILZFAIL = 54, + D3DRS_STENCILPASS = 55, + D3DRS_STENCILFUNC = 56, + D3DRS_STENCILREF = 57, + D3DRS_STENCILMASK = 58, + D3DRS_STENCILWRITEMASK = 59, + D3DRS_TEXTUREFACTOR = 60, + D3DRS_WRAP0 = 128, + D3DRS_WRAP1 = 129, + D3DRS_WRAP2 = 130, + D3DRS_WRAP3 = 131, + D3DRS_WRAP4 = 132, + D3DRS_WRAP5 = 133, + D3DRS_WRAP6 = 134, + D3DRS_WRAP7 = 135, + D3DRS_LIGHTING = 137, + D3DRS_AMBIENT = 139, + D3DRS_NORMALIZENORMALS = 143, + D3DRS_DIFFUSEMATERIALSOURCE = 145, + D3DRS_SPECULARMATERIALSOURCE = 146, + D3DRS_AMBIENTMATERIALSOURCE = 147, + D3DRS_EMISSIVEMATERIALSOURCE = 148, + D3DRS_VERTEXBLEND = 151, + D3DRS_POINTSIZE = 154, + D3DRS_POINTSIZE_MIN = 155, + D3DRS_POINTSPRITEENABLE = 156, + D3DRS_POINTSCALEENABLE = 157, + D3DRS_POINTSCALE_A = 158, + D3DRS_POINTSCALE_B = 159, + D3DRS_POINTSCALE_C = 160, + D3DRS_PATCHEDGESTYLE = 163, + D3DRS_PATCHSEGMENTS = 164, + D3DRS_DEBUGMONITORTOKEN = 165, + D3DRS_POINTSIZE_MAX = 166, + D3DRS_COLORWRITEENABLE = 168, + D3DRS_BLENDOP = 171, + D3DRS_FORCE_DWORD = 0x7fffffff +}; + +enum D3DSHADEMODE { D3DSHADE_FLAT = 1, D3DSHADE_GOURAUD = 2, D3DSHADE_PHONG = 3 }; +enum D3DFILLMODE { D3DFILL_POINT = 1, D3DFILL_WIREFRAME = 2, D3DFILL_SOLID = 3 }; +enum D3DBLEND { D3DBLEND_ZERO = 1, D3DBLEND_ONE = 2, D3DBLEND_SRCCOLOR = 3, D3DBLEND_INVSRCCOLOR = 4, D3DBLEND_SRCALPHA = 5, D3DBLEND_INVSRCALPHA = 6, D3DBLEND_DESTALPHA = 7, D3DBLEND_INVDESTALPHA = 8, D3DBLEND_DESTCOLOR = 9, D3DBLEND_INVDESTCOLOR = 10, D3DBLEND_SRCALPHASAT = 11, D3DBLEND_BOTHSRCALPHA = 12, D3DBLEND_BOTHINVSRCALPHA = 13 }; +enum D3DBLENDOP { D3DBLENDOP_ADD = 1, D3DBLENDOP_SUBTRACT = 2, D3DBLENDOP_REVSUBTRACT = 3, D3DBLENDOP_MIN = 4, D3DBLENDOP_MAX = 5 }; +enum D3DTEXTUREADDRESS { D3DTADDRESS_WRAP = 1, D3DTADDRESS_MIRROR = 2, D3DTADDRESS_CLAMP = 3, D3DTADDRESS_BORDER = 4, D3DTADDRESS_MIRRORONCE = 5 }; +enum D3DCULL { D3DCULL_NONE = 1, D3DCULL_CW = 2, D3DCULL_CCW = 3 }; +enum D3DCMPFUNC { D3DCMP_NEVER = 1, D3DCMP_LESS = 2, D3DCMP_EQUAL = 3, D3DCMP_LESSEQUAL = 4, D3DCMP_GREATER = 5, D3DCMP_NOTEQUAL = 6, D3DCMP_GREATEREQUAL = 7, D3DCMP_ALWAYS = 8 }; +enum D3DZBUFFERTYPE { D3DZB_FALSE = 0, D3DZB_TRUE = 1, D3DZB_USEW = 2 }; +enum D3DFOGMODE { D3DFOG_NONE = 0, D3DFOG_EXP = 1, D3DFOG_EXP2 = 2, D3DFOG_LINEAR = 3 }; +enum D3DSTENCILOP { D3DSTENCILOP_KEEP = 1, D3DSTENCILOP_ZERO = 2, D3DSTENCILOP_REPLACE = 3, D3DSTENCILOP_INCRSAT = 4, D3DSTENCILOP_DECRSAT = 5, D3DSTENCILOP_INVERT = 6, D3DSTENCILOP_INCR = 7, D3DSTENCILOP_DECR = 8 }; +enum D3DMATERIALCOLORSOURCE { D3DMCS_MATERIAL = 0, D3DMCS_COLOR1 = 1, D3DMCS_COLOR2 = 2 }; +enum D3DVERTEXBLENDFLAGS { D3DVBF_DISABLE = 0, D3DVBF_1WEIGHTS = 1, D3DVBF_2WEIGHTS = 2, D3DVBF_3WEIGHTS = 3, D3DVBF_TWEENING = 255, D3DVBF_0WEIGHTS = 256 }; +enum D3DPATCHEDGESTYLE { D3DPATCHEDGE_DISCRETE = 0, D3DPATCHEDGE_CONTINUOUS = 1 }; + +#define D3DWRAP_U 0x00000001L +#define D3DWRAP_V 0x00000002L +#define D3DWRAP_W 0x00000004L +#define D3DCOLORWRITEENABLE_RED (1L << 0) +#define D3DCOLORWRITEENABLE_GREEN (1L << 1) +#define D3DCOLORWRITEENABLE_BLUE (1L << 2) +#define D3DCOLORWRITEENABLE_ALPHA (1L << 3) + +enum D3DTEXTURESTAGESTATETYPE +{ + D3DTSS_COLOROP = 1, + D3DTSS_COLORARG1 = 2, + D3DTSS_COLORARG2 = 3, + D3DTSS_ALPHAOP = 4, + D3DTSS_ALPHAARG1 = 5, + D3DTSS_ALPHAARG2 = 6, + D3DTSS_BUMPENVMAT00 = 7, + D3DTSS_BUMPENVMAT01 = 8, + D3DTSS_BUMPENVMAT10 = 9, + D3DTSS_BUMPENVMAT11 = 10, + D3DTSS_TEXCOORDINDEX = 11, + D3DTSS_ADDRESSU = 13, + D3DTSS_ADDRESSV = 14, + D3DTSS_BORDERCOLOR = 15, + D3DTSS_MAGFILTER = 16, + D3DTSS_MINFILTER = 17, + D3DTSS_MIPFILTER = 18, + D3DTSS_MIPMAPLODBIAS = 19, + D3DTSS_MAXMIPLEVEL = 20, + D3DTSS_MAXANISOTROPY = 21, + D3DTSS_BUMPENVLSCALE = 22, + D3DTSS_BUMPENVLOFFSET = 23, + D3DTSS_TEXTURETRANSFORMFLAGS = 24, + D3DTSS_ADDRESSW = 25, + D3DTSS_COLORARG0 = 26, + D3DTSS_ALPHAARG0 = 27, + D3DTSS_RESULTARG = 28, + D3DTSS_FORCE_DWORD = 0x7fffffff +}; + +#define D3DTSS_TCI_CAMERASPACENORMAL 0x00010000 +#define D3DTSS_TCI_CAMERASPACEPOSITION 0x00020000 +#define D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR 0x00030000 + +enum D3DTEXTUREOP +{ + D3DTOP_DISABLE = 1, + D3DTOP_SELECTARG1 = 2, + D3DTOP_SELECTARG2 = 3, + D3DTOP_MODULATE = 4, + D3DTOP_MODULATE2X = 5, + D3DTOP_MODULATE4X = 6, + D3DTOP_ADD = 7, + D3DTOP_ADDSIGNED = 8, + D3DTOP_ADDSIGNED2X = 9, + D3DTOP_SUBTRACT = 10, + D3DTOP_ADDSMOOTH = 11, + D3DTOP_BLENDDIFFUSEALPHA = 12, + D3DTOP_BLENDTEXTUREALPHA = 13, + D3DTOP_BLENDFACTORALPHA = 14, + D3DTOP_BLENDTEXTUREALPHAPM = 15, + D3DTOP_BLENDCURRENTALPHA = 16, + D3DTOP_PREMODULATE = 17, + D3DTOP_MODULATEALPHA_ADDCOLOR = 18, + D3DTOP_MODULATECOLOR_ADDALPHA = 19, + D3DTOP_MODULATEINVALPHA_ADDCOLOR = 20, + D3DTOP_MODULATEINVCOLOR_ADDALPHA = 21, + D3DTOP_BUMPENVMAP = 22, + D3DTOP_BUMPENVMAPLUMINANCE = 23, + D3DTOP_DOTPRODUCT3 = 24, + D3DTOP_MULTIPLYADD = 25, + D3DTOP_LERP = 26 +}; + +#define D3DTA_SELECTMASK 0x0000000f +#define D3DTA_DIFFUSE 0x00000000 +#define D3DTA_CURRENT 0x00000001 +#define D3DTA_TEXTURE 0x00000002 +#define D3DTA_TFACTOR 0x00000003 +#define D3DTA_SPECULAR 0x00000004 +#define D3DTA_TEMP 0x00000005 +#define D3DTA_COMPLEMENT 0x00000010 +#define D3DTA_ALPHAREPLICATE 0x00000020 + +enum D3DTEXTUREFILTERTYPE +{ + D3DTEXF_NONE = 0, + D3DTEXF_POINT = 1, + D3DTEXF_LINEAR = 2, + D3DTEXF_ANISOTROPIC = 3, + D3DTEXF_FLATCUBIC = 4, + D3DTEXF_GAUSSIANCUBIC = 5 +}; + +enum D3DTEXTURETRANSFORMFLAGS +{ + D3DTTFF_DISABLE = 0, + D3DTTFF_COUNT1 = 1, + D3DTTFF_COUNT2 = 2, + D3DTTFF_COUNT3 = 3, + D3DTTFF_COUNT4 = 4, + D3DTTFF_PROJECTED = 256 +}; + +struct D3DCAPS8 +{ + D3DDEVTYPE DeviceType; + UINT AdapterOrdinal; + DWORD Caps; + DWORD Caps2; + DWORD Caps3; + DWORD PresentationIntervals; + DWORD CursorCaps; + DWORD DevCaps; + DWORD PrimitiveMiscCaps; + DWORD RasterCaps; + DWORD ZCmpCaps; + DWORD SrcBlendCaps; + DWORD DestBlendCaps; + DWORD AlphaCmpCaps; + DWORD ShadeCaps; + DWORD TextureCaps; + DWORD TextureFilterCaps; + DWORD CubeTextureFilterCaps; + DWORD VolumeTextureFilterCaps; + DWORD TextureAddressCaps; + DWORD VolumeTextureAddressCaps; + DWORD LineCaps; + DWORD MaxTextureWidth; + DWORD MaxTextureHeight; + DWORD MaxVolumeExtent; + DWORD MaxTextureRepeat; + DWORD MaxTextureAspectRatio; + DWORD MaxAnisotropy; + float MaxVertexW; + float GuardBandLeft; + float GuardBandTop; + float GuardBandRight; + float GuardBandBottom; + float ExtentsAdjust; + DWORD StencilCaps; + DWORD FVFCaps; + DWORD TextureOpCaps; + DWORD MaxTextureBlendStages; + DWORD MaxSimultaneousTextures; + DWORD VertexProcessingCaps; + DWORD MaxActiveLights; + DWORD MaxUserClipPlanes; + DWORD MaxVertexBlendMatrices; + DWORD MaxVertexBlendMatrixIndex; + float MaxPointSize; + DWORD MaxPrimitiveCount; + DWORD MaxVertexIndex; + DWORD MaxStreams; + DWORD MaxStreamStride; + DWORD VertexShaderVersion; + DWORD MaxVertexShaderConst; + DWORD PixelShaderVersion; + float MaxPixelShaderValue; +}; + +#define D3DCAPS2_FULLSCREENGAMMA 0x00020000L +#define D3DCAPS2_CANRENDERWINDOWED 0x00080000L +#define D3DCAPS2_CANCALIBRATEGAMMA 0x00100000L +#define D3DCAPS2_DYNAMICTEXTURES 0x20000000L +#define D3DPRESENT_INTERVAL_DEFAULT 0x00000000L +#define D3DPRESENT_INTERVAL_ONE 0x00000001L +#define D3DPRESENT_INTERVAL_IMMEDIATE 0x80000000L +#define D3DPRESENT_RATE_DEFAULT 0x00000000L +#define D3DCURSORCAPS_COLOR 0x00000001L +#define D3DCURSORCAPS_LOWRES 0x00000002L +#define D3DDEVCAPS_EXECUTESYSTEMMEMORY 0x00000010L +#define D3DDEVCAPS_EXECUTEVIDEOMEMORY 0x00000020L +#define D3DDEVCAPS_TLVERTEXSYSTEMMEMORY 0x00000040L +#define D3DDEVCAPS_TLVERTEXVIDEOMEMORY 0x00000080L +#define D3DDEVCAPS_TEXTURESYSTEMMEMORY 0x00000100L +#define D3DDEVCAPS_TEXTUREVIDEOMEMORY 0x00000200L +#define D3DDEVCAPS_DRAWPRIMTLVERTEX 0x00000400L +#define D3DDEVCAPS_CANRENDERAFTERFLIP 0x00000800L +#define D3DDEVCAPS_TEXTURENONLOCALVIDMEM 0x00001000L +#define D3DDEVCAPS_DRAWPRIMITIVES2 0x00002000L +#define D3DDEVCAPS_DRAWPRIMITIVES2EX 0x00008000L +#define D3DDEVCAPS_HWTRANSFORMANDLIGHT 0x00010000L +#define D3DDEVCAPS_HWRASTERIZATION 0x00080000L +#define D3DDEVCAPS_PUREDEVICE 0x00100000L +#define D3DDEVCAPS_NPATCHES 0x01000000L +#define D3DPMISCCAPS_MASKZ 0x00000002L +#define D3DPMISCCAPS_LINEPATTERNREP 0x00000004L +#define D3DPMISCCAPS_CULLNONE 0x00000010L +#define D3DPMISCCAPS_CULLCW 0x00000020L +#define D3DPMISCCAPS_CULLCCW 0x00000040L +#define D3DPMISCCAPS_COLORWRITEENABLE 0x00000080L +#define D3DPMISCCAPS_CLIPTLVERTS 0x00000200L +#define D3DPMISCCAPS_TSSARGTEMP 0x00000400L +#define D3DPMISCCAPS_BLENDOP 0x00000800L +#define D3DLINECAPS_TEXTURE 0x00000001L +#define D3DLINECAPS_ZTEST 0x00000002L +#define D3DLINECAPS_BLEND 0x00000004L +#define D3DLINECAPS_ALPHACMP 0x00000008L +#define D3DLINECAPS_FOG 0x00000010L +#define D3DPRASTERCAPS_DITHER 0x00000001L +#define D3DPRASTERCAPS_ZTEST 0x00000010L +#define D3DPRASTERCAPS_FOGVERTEX 0x00000080L +#define D3DPRASTERCAPS_FOGTABLE 0x00000100L +#define D3DPRASTERCAPS_MIPMAPLODBIAS 0x00002000L +#define D3DPRASTERCAPS_ZBIAS 0x00004000L +#define D3DPRASTERCAPS_FOGRANGE 0x00010000L +#define D3DPRASTERCAPS_ANISOTROPY 0x00020000L +#define D3DPRASTERCAPS_WFOG 0x00100000L +#define D3DPRASTERCAPS_ZFOG 0x00200000L +#define D3DPRASTERCAPS_COLORPERSPECTIVE 0x00400000L +#define D3DPCMPCAPS_NEVER 0x00000001L +#define D3DPCMPCAPS_LESS 0x00000002L +#define D3DPCMPCAPS_EQUAL 0x00000004L +#define D3DPCMPCAPS_LESSEQUAL 0x00000008L +#define D3DPCMPCAPS_GREATER 0x00000010L +#define D3DPCMPCAPS_NOTEQUAL 0x00000020L +#define D3DPCMPCAPS_GREATEREQUAL 0x00000040L +#define D3DPCMPCAPS_ALWAYS 0x00000080L +#define D3DPBLENDCAPS_ZERO 0x00000001L +#define D3DPBLENDCAPS_ONE 0x00000002L +#define D3DPBLENDCAPS_SRCCOLOR 0x00000004L +#define D3DPBLENDCAPS_INVSRCCOLOR 0x00000008L +#define D3DPBLENDCAPS_SRCALPHA 0x00000010L +#define D3DPBLENDCAPS_INVSRCALPHA 0x00000020L +#define D3DPBLENDCAPS_DESTALPHA 0x00000040L +#define D3DPBLENDCAPS_INVDESTALPHA 0x00000080L +#define D3DPBLENDCAPS_DESTCOLOR 0x00000100L +#define D3DPBLENDCAPS_INVDESTCOLOR 0x00000200L +#define D3DPBLENDCAPS_SRCALPHASAT 0x00000400L +#define D3DPBLENDCAPS_BOTHSRCALPHA 0x00000800L +#define D3DPBLENDCAPS_BOTHINVSRCALPHA 0x00001000L +#define D3DPSHADECAPS_COLORGOURAUDRGB 0x00000008L +#define D3DPSHADECAPS_SPECULARGOURAUDRGB 0x00000200L +#define D3DPSHADECAPS_ALPHAGOURAUDBLEND 0x00004000L +#define D3DPSHADECAPS_FOGGOURAUD 0x00080000L +#define D3DPTEXTURECAPS_PERSPECTIVE 0x00000001L +#define D3DPTEXTURECAPS_ALPHA 0x00000004L +#define D3DPTEXTURECAPS_TEXREPEATNOTSCALEDBYSIZE 0x00000040L +#define D3DPTEXTURECAPS_ALPHAPALETTE 0x00000080L +#define D3DPTEXTURECAPS_PROJECTED 0x00000400L +#define D3DPTEXTURECAPS_CUBEMAP 0x00000800L +#define D3DPTEXTURECAPS_VOLUMEMAP 0x00002000L +#define D3DPTEXTURECAPS_MIPMAP 0x00004000L +#define D3DPTEXTURECAPS_MIPVOLUMEMAP 0x00008000L +#define D3DPTEXTURECAPS_MIPCUBEMAP 0x00010000L +#define D3DPTFILTERCAPS_MINFPOINT 0x00000100L +#define D3DPTFILTERCAPS_MINFLINEAR 0x00000200L +#define D3DPTFILTERCAPS_MINFANISOTROPIC 0x00000400L +#define D3DPTFILTERCAPS_MIPFPOINT 0x00010000L +#define D3DPTFILTERCAPS_MIPFLINEAR 0x00020000L +#define D3DPTFILTERCAPS_MAGFPOINT 0x01000000L +#define D3DPTFILTERCAPS_MAGFLINEAR 0x02000000L +#define D3DPTFILTERCAPS_MAGFANISOTROPIC 0x04000000L +#define D3DPTADDRESSCAPS_WRAP 0x00000001L +#define D3DPTADDRESSCAPS_MIRROR 0x00000002L +#define D3DPTADDRESSCAPS_CLAMP 0x00000004L +#define D3DPTADDRESSCAPS_BORDER 0x00000008L +#define D3DPTADDRESSCAPS_INDEPENDENTUV 0x00000010L +#define D3DPTADDRESSCAPS_MIRRORONCE 0x00000020L +#define D3DSTENCILCAPS_KEEP 0x00000001L +#define D3DSTENCILCAPS_ZERO 0x00000002L +#define D3DSTENCILCAPS_REPLACE 0x00000004L +#define D3DSTENCILCAPS_INCRSAT 0x00000008L +#define D3DSTENCILCAPS_DECRSAT 0x00000010L +#define D3DSTENCILCAPS_INVERT 0x00000020L +#define D3DSTENCILCAPS_INCR 0x00000040L +#define D3DSTENCILCAPS_DECR 0x00000080L +#define D3DTEXOPCAPS_DISABLE 0x00000001L +#define D3DTEXOPCAPS_SELECTARG1 0x00000002L +#define D3DTEXOPCAPS_SELECTARG2 0x00000004L +#define D3DTEXOPCAPS_MODULATE 0x00000008L +#define D3DTEXOPCAPS_MODULATE2X 0x00000010L +#define D3DTEXOPCAPS_MODULATE4X 0x00000020L +#define D3DTEXOPCAPS_ADD 0x00000040L +#define D3DTEXOPCAPS_ADDSIGNED 0x00000080L +#define D3DTEXOPCAPS_ADDSIGNED2X 0x00000100L +#define D3DTEXOPCAPS_SUBTRACT 0x00000200L +#define D3DTEXOPCAPS_ADDSMOOTH 0x00000400L +#define D3DTEXOPCAPS_BLENDDIFFUSEALPHA 0x00000800L +#define D3DTEXOPCAPS_BLENDTEXTUREALPHA 0x00001000L +#define D3DTEXOPCAPS_BLENDFACTORALPHA 0x00002000L +#define D3DTEXOPCAPS_BLENDTEXTUREALPHAPM 0x00004000L +#define D3DTEXOPCAPS_BLENDCURRENTALPHA 0x00008000L +#define D3DTEXOPCAPS_PREMODULATE 0x00010000L +#define D3DTEXOPCAPS_MODULATEALPHA_ADDCOLOR 0x00020000L +#define D3DTEXOPCAPS_MODULATECOLOR_ADDALPHA 0x00040000L +#define D3DTEXOPCAPS_MODULATEINVALPHA_ADDCOLOR 0x00080000L +#define D3DTEXOPCAPS_MODULATEINVCOLOR_ADDALPHA 0x00100000L +#define D3DTEXOPCAPS_BUMPENVMAP 0x00200000L +#define D3DTEXOPCAPS_BUMPENVMAPLUMINANCE 0x00400000L +#define D3DTEXOPCAPS_DOTPRODUCT3 0x00800000L +#define D3DTEXOPCAPS_MULTIPLYADD 0x01000000L +#define D3DTEXOPCAPS_LERP 0x02000000L +#define D3DFVFCAPS_PSIZE 0x00100000L +#define D3DVTXPCAPS_TEXGEN 0x00000001L +#define D3DVTXPCAPS_MATERIALSOURCE7 0x00000002L +#define D3DVTXPCAPS_DIRECTIONALLIGHTS 0x00000008L +#define D3DVTXPCAPS_POSITIONALLIGHTS 0x00000010L +#define D3DVTXPCAPS_LOCALVIEWER 0x00000020L +#define D3DVTXPCAPS_TWEENING 0x00000040L + +#define D3DUSAGE_RENDERTARGET 0x00000001L +#define D3DUSAGE_DEPTHSTENCIL 0x00000002L +#define STANDALONE_FACD3D 0x876 +#define STANDALONE_MAKE_D3DHRESULT(code) MAKE_HRESULT(1, STANDALONE_FACD3D, code) +#define D3D_OK S_OK +#define D3DERR_INVALIDCALL STANDALONE_MAKE_D3DHRESULT(2156) +#define D3DERR_NOTAVAILABLE STANDALONE_MAKE_D3DHRESULT(2154) +#define D3DERR_OUTOFVIDEOMEMORY STANDALONE_MAKE_D3DHRESULT(380) +#define D3DVS_VERSION(major, minor) (0xFFFE0000 | ((major) << 8) | (minor)) +#define D3DPS_VERSION(major, minor) (0xFFFF0000 | ((major) << 8) | (minor)) + +inline ULONG IDirect3DSurface8::AddRef() +{ + return 1; +} + +inline ULONG IDirect3DBaseTexture8::AddRef() +{ + return 1; +} + +inline ULONG IDirect3DSurface8::Release() +{ + return 1; +} + +inline ULONG IDirect3DBaseTexture8::Release() +{ + return 1; +} + +inline HRESULT IDirect3DSurface8::GetDesc(D3DSURFACE_DESC *desc) +{ + if (desc != nullptr) { + ZeroMemory(desc, sizeof(*desc)); + } + return D3DERR_INVALIDCALL; +} + +inline HRESULT IDirect3DSurface8::LockRect(D3DLOCKED_RECT *locked_rect, const RECT *rect, DWORD flags) +{ + (void)locked_rect; + (void)rect; + (void)flags; + return D3DERR_INVALIDCALL; +} + +inline HRESULT IDirect3DSurface8::UnlockRect() +{ + return D3DERR_INVALIDCALL; +} + +inline HRESULT IDirect3DTexture8::GetSurfaceLevel(UINT level, IDirect3DSurface8 **surface) +{ + (void)level; + if (surface != nullptr) { + *surface = nullptr; + } + return D3DERR_INVALIDCALL; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8texman.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8texman.cpp index d92e5b9f71f..f290edfd206 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8texman.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8texman.cpp @@ -38,7 +38,7 @@ * DX8TextureManagerClass::Shutdown -- Shuts down the texture manager * * DX8TextureManagerClass::Add -- Adds a texture to be managed * * DX8TextureManagerClass::Remove -- Removes a texture from being managed * - * DX8TextureManagerClass::Release_Textures -- Releases the internal d3d texture * + * DX8TextureManagerClass::Release_Textures -- Releases the internal legacy texture * * DX8TextureManagerClass::Recreate_Textures -- Reallocates lost textures * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ @@ -51,148 +51,53 @@ // destructor #include "dx8texman.h" +#include "texturecompatibilityinterop.h" +#include "dx8wrapper.h" -TextureTrackerList DX8TextureManagerClass::Managed_Textures; - - -/*********************************************************************************************** - * DX8TextureManagerClass::Shutdown -- Shuts down the texture manager * - * * - * * - * * - * * - * INPUT: * - * * - * OUTPUT: * - * * - * WARNINGS: * - * * - * HISTORY: * - * 4/25/2001 hy : Created. * - * 5/16/2002 km : Added depth stencil texture tracking and abstraction * - *=============================================================================================*/ -void DX8TextureManagerClass::Shutdown() +#if !defined(GGC_RENDER_BACKEND_BGFX) +namespace { - while (!Managed_Textures.Is_Empty()) - { - TextureTrackerClass *track=Managed_Textures.Remove_Head(); - delete track; - } + constexpr auto kLegacyDefaultPool = D3DPOOL_DEFAULT; } -/*********************************************************************************************** - * DX8TextureManagerClass::Add -- Adds a texture to be managed * - * * - * * - * * - * * - * INPUT: * - * * - * OUTPUT: * - * * - * WARNINGS: * - * * - * HISTORY: * - * 4/25/2001 hy : Created. * - * 5/16/2002 km : Added depth stencil texture tracking and abstraction * - *=============================================================================================*/ -void DX8TextureManagerClass::Add(TextureTrackerClass *track) +void DX8TextureTrackerClass::Recreate() const { - // this function should only be called by the texture constructor - Managed_Textures.Add(track); + WWASSERT(Peek_Legacy_Base_Texture(*Texture)==nullptr); + Poke_Legacy_Texture(*Texture, + DX8Wrapper::_Create_DX8_Texture + ( + Width, + Height, + Format, + Mip_level_count, + kLegacyDefaultPool, + RenderTarget + ) + ); } - -/*********************************************************************************************** - * DX8TextureManagerClass::Remove -- Removes a texture from being managed * - * * - * * - * * - * * - * INPUT: * - * * - * OUTPUT: * - * * - * WARNINGS: * - * * - * HISTORY: * - * 4/25/2001 hy : Created. * - * 5/16/2002 km : Added depth stencil texture tracking and abstraction * - *=============================================================================================*/ -void DX8TextureManagerClass::Remove(TextureBaseClass *tex) +void DX8TextureTrackerClass::Release() const { - // this function should only be called by the texture destructor - TextureTrackerListIterator it(&Managed_Textures); - - while (!it.Is_Done()) - { - TextureTrackerClass *track=it.Peek_Obj(); - if (track->Get_Texture()==tex) - { - it.Remove_Current_Object(); - delete track; - break; - } - it.Next(); - } + Set_Legacy_Base_Texture(*Texture, nullptr); } - -/*********************************************************************************************** - * DX8TextureManagerClass::Release_Textures -- Releases the internal d3d texture * - * * - * * - * * - * * - * INPUT: * - * * - * OUTPUT: * - * * - * WARNINGS: * - * * - * HISTORY: * - * 4/25/2001 hy : Created. * - * 5/16/2002 km : Added depth stencil texture tracking and abstraction * - *=============================================================================================*/ -void DX8TextureManagerClass::Release_Textures() +void DX8ZTextureTrackerClass::Recreate() const { - TextureTrackerListIterator it(&Managed_Textures); - - while (!it.Is_Done()) - { - TextureTrackerClass *track=it.Peek_Obj(); - track->Release(); - it.Next(); - } + WWASSERT(Peek_Legacy_Base_Texture(*Texture)==nullptr); + Poke_Legacy_Texture(*Texture, + DX8Wrapper::_Create_DX8_ZTexture + ( + Width, + Height, + ZFormat, + Mip_level_count, + kLegacyDefaultPool + ) + ); } - -/*********************************************************************************************** - * DX8TextureManagerClass::Recreate_Textures -- Reallocates lost textures * - * * - * * - * * - * * - * INPUT: * - * * - * OUTPUT: * - * * - * WARNINGS: * - * * - * HISTORY: * - * 4/25/2001 hy : Created. * - * 5/16/2002 km : Added depth stencil texture tracking and abstraction * - *=============================================================================================*/ -void DX8TextureManagerClass::Recreate_Textures() +void DX8ZTextureTrackerClass::Release() const { - TextureTrackerListIterator it(&Managed_Textures); - - while (!it.Is_Done()) - { - TextureTrackerClass *track=it.Peek_Obj(); - track->Recreate(); - track->Get_Texture()->Set_Dirty(); - it.Next(); - } + Set_Legacy_Base_Texture(*Texture, nullptr); } - +#endif diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8texman.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8texman.h index c01001cb074..b4274ed0094 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8texman.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8texman.h @@ -39,49 +39,8 @@ #pragma once -#include "WWLib/always.h" -#include "texture.h" -#include "dx8wrapper.h" -#include "ww3dformat.h" -#include "dx8list.h" -#include "WWLib/multilist.h" - -class DX8TextureManagerClass; - -class TextureTrackerClass : public MultiListObjectClass -{ -public: - TextureTrackerClass - ( - unsigned int w, - unsigned int h, - MipCountType count, - TextureBaseClass *tex - ) - : Width(w), - Height(h), - Mip_level_count(count), - Texture(tex) - { - } - - virtual void Recreate() const =0; - - void Release() - { - Texture->Set_D3D_Base_Texture(nullptr); - } - - TextureBaseClass* Get_Texture() const { return Texture; } - - -protected: - - unsigned int Width; - unsigned int Height; - MipCountType Mip_level_count; - TextureBaseClass *Texture; -}; +#include "WW3D2/TextureResourceManager.h" +#include "WW3D2/ww3dformat.h" class DX8TextureTrackerClass : public TextureTrackerClass { @@ -99,22 +58,8 @@ class DX8TextureTrackerClass : public TextureTrackerClass { } - virtual void Recreate() const override - { - WWASSERT(Texture->Peek_D3D_Base_Texture()==nullptr); - Texture->Poke_Texture - ( - DX8Wrapper::_Create_DX8_Texture - ( - Width, - Height, - Format, - Mip_level_count, - D3DPOOL_DEFAULT, - RenderTarget - ) - ); - } + virtual void Release() const override; + virtual void Recreate() const override; private: WW3DFormat Format; @@ -136,36 +81,11 @@ class DX8ZTextureTrackerClass : public TextureTrackerClass { } - virtual void Recreate() const override - { - WWASSERT(Texture->Peek_D3D_Base_Texture()==nullptr); - Texture->Poke_Texture - ( - DX8Wrapper::_Create_DX8_ZTexture - ( - Width, - Height, - ZFormat, - Mip_level_count, - D3DPOOL_DEFAULT - ) - ); - } - + virtual void Release() const override; + virtual void Recreate() const override; private: WW3DZFormat ZFormat; }; - -class DX8TextureManagerClass -{ -public: - static void Shutdown(); - static void Add(TextureTrackerClass *track); - static void Remove(TextureBaseClass *tex); - static void Release_Textures(); - static void Recreate_Textures(); -private: - static TextureTrackerList Managed_Textures; -}; +using DX8TextureManagerClass = TextureResourceManagerClass; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8texturelegacyd3dtypes.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8texturelegacyd3dtypes.h new file mode 100644 index 00000000000..134674c8421 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8texturelegacyd3dtypes.h @@ -0,0 +1,40 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** 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 + +#if defined(GGC_RENDER_BACKEND_BGFX) +#error dx8texturelegacyd3dtypes.h is only for the native DX8 texture compatibility path. +#endif + +#include "d3d8.h" + +using LegacyBaseTexture = IDirect3DBaseTexture8; +using LegacySurface = IDirect3DSurface8; +using NativeCompatibilityTextureSurface = IDirect3DSurface8; +using LegacySurfaceDesc = D3DSURFACE_DESC; +using LegacyVolumeDesc = D3DVOLUME_DESC; +using LegacyLockedRect = D3DLOCKED_RECT; + +using LegacyLoaderTexture = IDirect3DTexture8; +using LegacyLoaderSurface = IDirect3DSurface8; +using LegacyLoaderCubeTexture = IDirect3DCubeTexture8; +using LegacyLoaderVolumeTexture = IDirect3DVolumeTexture8; +using LegacyLoaderLockedRect = D3DLOCKED_RECT; +using LegacyLoaderLockedBox = D3DLOCKED_BOX; +using LegacyLoaderCubeFace = D3DCUBEMAP_FACES; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8texturelegacytypes.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8texturelegacytypes.h new file mode 100644 index 00000000000..1ce17998d70 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8texturelegacytypes.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 . +*/ + +#pragma once + +#if defined(GGC_RENDER_BACKEND_BGFX) +#include "texturecompatibilitytypes.h" +#else +#include "dx8texturelegacyd3dtypes.h" +#endif + +#if defined(GGC_RENDER_BACKEND_BGFX) +using LegacyBaseTexture = NativeCompatibilityBaseTexture; +using LegacySurface = NativeCompatibilitySurface; +using NativeCompatibilityTextureSurface = NativeCompatibilitySurface; +using LegacySurfaceDesc = NativeCompatibilitySurfaceDesc; +using LegacyVolumeDesc = NativeCompatibilityVolumeDesc; +using LegacyLockedRect = NativeCompatibilityLockedRect; + +using LegacyLoaderTexture = NativeCompatibilityTexture2D; +using LegacyLoaderSurface = NativeCompatibilitySurface; +using LegacyLoaderCubeTexture = NativeCompatibilityCubeTexture; +using LegacyLoaderVolumeTexture = NativeCompatibilityVolumeTexture; +using LegacyLoaderLockedRect = NativeCompatibilityLockedRect; +using LegacyLoaderLockedBox = NativeCompatibilityLockedBox; +using LegacyLoaderCubeFace = NativeCompatibilityCubeFace; +#endif diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.h index 08ec118931b..a278208da34 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.h @@ -42,8 +42,7 @@ #include "WWLib/always.h" #include "WWDebug/wwdebug.h" #include "dx8fvf.h" - -const unsigned dynamic_fvf_type=D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX2|D3DFVF_DIFFUSE; +#include "vertexbuffer.h" class DX8Wrapper; class SortingRendererClass; @@ -51,145 +50,10 @@ class Vector2; class Vector3; class Vector4; class StringClass; -class DX8VertexBufferClass; class FVFInfoClass; -struct IDirect3DVertexBuffer8; -class VertexBufferClass; -struct VertexFormatXYZNDUV2; - -class VertexBufferLockClass -{ -protected: - VertexBufferClass* VertexBuffer; - void* Vertices; - - // This class can't be used directly, so constructor as to be protected - VertexBufferLockClass(VertexBufferClass* vertex_buffer_) : VertexBuffer(vertex_buffer_) {} -public: - void* Get_Vertex_Array() { return Vertices; } -}; - -/** -** DX8VertexBufferClass -** This class wraps a DX8 vertex buffer. Use the lock objects to modify or append to the vertex buffer. -*/ -class VertexBufferClass : public RefCountClass -{ -protected: - VertexBufferClass(unsigned type, unsigned FVF, unsigned short VertexCount); - virtual ~VertexBufferClass() override; -public: - const FVFInfoClass& FVF_Info() const { return *fvf_info; } - unsigned short Get_Vertex_Count() const { return VertexCount; } - unsigned Type() const { return type; } - - void Add_Engine_Ref() const; - void Release_Engine_Ref() const; - unsigned Engine_Refs() const { return engine_refs; } - - class WriteLockClass : public VertexBufferLockClass - { - public: - WriteLockClass(VertexBufferClass* vertex_buffer, int flags=0); - ~WriteLockClass(); - }; - - class AppendLockClass : public VertexBufferLockClass - { - public: - AppendLockClass(VertexBufferClass* vertex_buffer,unsigned start_index, unsigned index_range); - ~AppendLockClass(); - }; - - static unsigned Get_Total_Buffer_Count(); - static unsigned Get_Total_Allocated_Vertices(); - static unsigned Get_Total_Allocated_Memory(); - -protected: - unsigned type; - unsigned short VertexCount; - mutable int engine_refs; - FVFInfoClass* fvf_info; -}; - - - -/** -** Dynamic vertex buffer access is a wrapper to a single cycled dynamic vertex -** buffer. -** DynamicVBAccess gains an access to the dynamic vertex buffer and only -** only of these are allowed at any one time. -** -** The dynamic fvf buffers are always of the same type. -** -** NOTE: Dynamic vertex buffers accessors should only be used locally! -** -*/ - -class DynamicVBAccessClass -{ - friend DX8Wrapper; - friend SortingRendererClass; - - const FVFInfoClass& FVFInfo; - unsigned Type; - unsigned short VertexCount; - unsigned short VertexBufferOffset; - VertexBufferClass* VertexBuffer; -// static VertexFormatXYZNDUV2* _Get_Sorting_Vertex_Array(); - - void Allocate_Sorting_Dynamic_Buffer(); - void Allocate_DX8_Dynamic_Buffer(); -public: - // Type parameter can be either BUFFER_TYPE_DYNAMIC_DX8 or BUFFER_TYPE_DYNAMIC_SORTING. - - // Note: Even though the constructor takes fvf as a parameter, currently the - // only acceptable parameter is "dynamic_fvf_type". Any other type will - // result to an assert. - DynamicVBAccessClass(unsigned type,unsigned fvf,unsigned short vertex_count); - ~DynamicVBAccessClass(); - - // Access fvf - const FVFInfoClass& FVF_Info() const { return FVFInfo; } - unsigned Get_Type() const { return Type; } - unsigned short Get_Vertex_Count() const { return VertexCount; } - - // Call at the end of the execution, or at whatever time you wish to release - // the recycled dynamic vertex buffer. - static void _Deinit(); - static void _Reset(bool frame_changed); - static unsigned short Get_Default_Vertex_Count(); ///VertexBuffer->FVF_Info().Get_FVF() == (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX2|D3DFVF_DIFFUSE)); - return Vertices; -} - -// ---------------------------------------------------------------------------- +#if !defined(GGC_RENDER_BACKEND_BGFX) +class DX8VertexBufferClass; /** ** DX8VertexBufferClass @@ -214,7 +78,9 @@ class DX8VertexBufferClass : public VertexBufferClass DX8VertexBufferClass(const Vector3* vertices, const Vector4* diffuse, const Vector2* tex_coords, unsigned short VertexCount,UsageType usage=USAGE_DEFAULT); DX8VertexBufferClass(const Vector3* vertices, const Vector2* tex_coords, unsigned short VertexCount,UsageType usage=USAGE_DEFAULT); - IDirect3DVertexBuffer8* Get_DX8_Vertex_Buffer() { return VertexBuffer; } +#if !defined(GGC_RENDER_BACKEND_BGFX) + void *Get_Legacy_Vertex_Buffer() { return VertexBuffer; } +#endif void Copy(const Vector3* loc, unsigned first_vertex, unsigned count); void Copy(const Vector3* loc, const Vector2* uv, unsigned first_vertex, unsigned count); @@ -224,30 +90,8 @@ class DX8VertexBufferClass : public VertexBufferClass void Copy(const Vector3* loc, const Vector2* uv, const Vector4* diffuse, unsigned first_vertex, unsigned count); protected: - IDirect3DVertexBuffer8* VertexBuffer; + void *VertexBuffer; void Create_Vertex_Buffer(UsageType usage); }; - - -/** -** SortingVertexBufferClass -** This class acts as a vertex buffer for the vertices that need to be passed to alpha renderer. -*/ -class SortingVertexBufferClass : public VertexBufferClass -{ - W3DMPO_CODE(SortingVertexBufferClass) - - friend DX8Wrapper; - friend SortingRendererClass; - friend VertexBufferClass::WriteLockClass; - friend VertexBufferClass::AppendLockClass; - friend DynamicVBAccessClass::WriteLockClass; - - VertexFormatXYZNDUV2* VertexBuffer; - -protected: - virtual ~SortingVertexBufferClass() override; -public: - SortingVertexBufferClass(unsigned short VertexCount); -}; +#endif diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8webbrowser.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8webbrowser.cpp index ab9bcdce743..f249341910d 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8webbrowser.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8webbrowser.cpp @@ -32,10 +32,11 @@ #include "dx8webbrowser.h" #include "WW3D2/ww3d.h" -#include "dx8wrapper.h" #if ENABLE_EMBEDDED_BROWSER +#include "dx8wrapper.h" + #if defined(_MSC_VER) && _MSC_VER < 1300 // Import the Browser Type Library diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8webbrowser.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8webbrowser.h index f3170e79804..b1d5dc75e4d 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8webbrowser.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8webbrowser.h @@ -33,22 +33,25 @@ #pragma once #include -#include "d3d8.h" // *********************************** // Set this to 0 to remove all embedded browser code. // +#if defined(_WIN32) && !defined(GGC_RENDER_BACKEND_BGFX) #define ENABLE_EMBEDDED_BROWSER 1 +#else +#define ENABLE_EMBEDDED_BROWSER 0 +#endif // // *********************************** -#if ENABLE_EMBEDDED_BROWSER - // These options must match the browser option bits defined in the BrowserEngine code. // Look in febrowserengine.h #define BROWSEROPTION_SCROLLBARS 0x0001 #define BROWSEROPTION_3DBORDER 0x0002 +#if ENABLE_EMBEDDED_BROWSER + struct IDirect3DDevice8; /** @@ -87,4 +90,19 @@ class DX8WebBrowser static HWND hWnd; }; +#else + +class DX8WebBrowser +{ +public: + static bool Initialize(const char* = 0, const char* = 0, const char* = 0, const char* = 0) { return false; } + static void Shutdown() {} + static void Update() {} + static void Render(int) {} + static void CreateBrowser(const char*, const char*, int, int, int, int, int = 0, LONG = BROWSEROPTION_SCROLLBARS | BROWSEROPTION_3DBORDER, void* = nullptr) {} + static void DestroyBrowser(const char*) {} + static bool Is_Browser_Open(const char*) { return false; } + static void Navigate(const char*, const char*) {} +}; + #endif diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp index bc2a6eeeeca..a502f89d1f3 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp @@ -43,7 +43,7 @@ //#define CREATE_DX8_MULTI_THREADED //#define CREATE_DX8_FPU_PRESERVE -#define WW3D_DEVTYPE D3DDEVTYPE_HAL +#define WW3D_DEVTYPE Legacy_Device_Type(1) #if !defined(WINVER) || WINVER < 0x0500 #undef WINVER @@ -51,11 +51,23 @@ #endif #include "dx8wrapper.h" +#include "WW3DDeviceInit.h" +#include "texturecompatibilityinterop.h" +#include "DrawCallLog.h" +#include "GgcRuntimeFlags.h" +#include "RenderStateDefs.h" #include "dx8webbrowser.h" #include "dx8fvf.h" #include "dx8vertexbuffer.h" #include "dx8indexbuffer.h" #include "dx8renderer.h" +#include "RenderBackend.h" +#include "WW3D2/IRenderBackend.h" +#if defined(GGC_RENDER_BACKEND_BGFX) +// TheSuperHackers @refactor bobtista 08/06/2026 On bgfx builds g_device owns the windowed/bit-depth +// device state; Set_Render_Device/Init mirror DX8Wrapper's IsWindowed/BitDepth into it. +#include "WW3D2/BgfxBackendState.h" +#endif #include "WW3D2/ww3d.h" #include "WW3D2/camera.h" #include "WWLib/wwstring.h" @@ -75,27 +87,157 @@ #include "textureloader.h" #include "missingtexture.h" #include "WWLib/thread.h" +#if !defined(GGC_RENDER_BACKEND_BGFX) #include +#endif #include "WWMath/pot.h" #include "WWDebug/wwprofile.h" #include "WWLib/ffactory.h" #include "dx8caps.h" -#include "formconv.h" -#include "dx8texman.h" +#include "WW3D2/dx8formatconv.h" +#include "WW3D2/TextureResourceManager.h" #include "WWLib/bound.h" #include "WWLib/DbgHelpGuard.h" #include "shdlib.h" +#include +#include + +#if defined(GGC_RENDER_BACKEND_BGFX) +#include "WWLib/TARGA.h" +#include "WW3D2/ww3dformat.h" + +HRESULT Standalone_Filter_Legacy_Texture_Mips(IDirect3DBaseTexture8 *base_texture, unsigned int src_level); +HRESULT Standalone_Copy_Legacy_Surface( + IDirect3DSurface8 *destination, + const RECT *destination_rect, + IDirect3DSurface8 *source, + const RECT *source_rect); +#endif + +static D3DDEVTYPE Legacy_Device_Type(unsigned value) { return static_cast(value); } +static D3DRESOURCETYPE Legacy_Resource_Type(unsigned value) { return static_cast(value); } + +#if !defined(GGC_RENDER_BACKEND_BGFX) +static IDirect3DVertexBuffer8 *Legacy_Vertex_Buffer(VertexBufferClass *buffer) +{ + return static_cast( + static_cast(buffer)->Get_Legacy_Vertex_Buffer()); +} + +static IDirect3DIndexBuffer8 *Legacy_Index_Buffer(IndexBufferClass *buffer) +{ + return static_cast( + static_cast(buffer)->Get_Legacy_Index_Buffer()); +} +#endif + +static LegacyFixedFunctionColor To_Legacy_Color(const D3DCOLORVALUE &color) +{ + LegacyFixedFunctionColor result; + result.r = color.r; + result.g = color.g; + result.b = color.b; + result.a = color.a; + return result; +} + +static D3DCOLORVALUE To_D3D_Color(const LegacyFixedFunctionColor &color) +{ + D3DCOLORVALUE result; + result.r = color.r; + result.g = color.g; + result.b = color.b; + result.a = color.a; + return result; +} + +static LegacyFixedFunctionVector3 To_Legacy_Vector(const D3DVECTOR &vector) +{ + LegacyFixedFunctionVector3 result; + result.x = vector.x; + result.y = vector.y; + result.z = vector.z; + return result; +} + +static D3DVECTOR To_D3D_Vector(const LegacyFixedFunctionVector3 &vector) +{ + D3DVECTOR result; + result.x = vector.x; + result.y = vector.y; + result.z = vector.z; + return result; +} + +static LegacyFixedFunctionLight To_Legacy_Light(const D3DLIGHT8 &light) +{ + LegacyFixedFunctionLight result; + result.Type = static_cast(light.Type); + result.Diffuse = To_Legacy_Color(light.Diffuse); + result.Specular = To_Legacy_Color(light.Specular); + result.Ambient = To_Legacy_Color(light.Ambient); + result.Position = To_Legacy_Vector(light.Position); + result.Direction = To_Legacy_Vector(light.Direction); + result.Range = light.Range; + result.Falloff = light.Falloff; + result.Attenuation0 = light.Attenuation0; + result.Attenuation1 = light.Attenuation1; + result.Attenuation2 = light.Attenuation2; + result.Theta = light.Theta; + result.Phi = light.Phi; + return result; +} + +static D3DLIGHT8 To_D3D_Light(const LegacyFixedFunctionLight &light) +{ + D3DLIGHT8 result; + result.Type = static_cast(light.Type); + result.Diffuse = To_D3D_Color(light.Diffuse); + result.Specular = To_D3D_Color(light.Specular); + result.Ambient = To_D3D_Color(light.Ambient); + result.Position = To_D3D_Vector(light.Position); + result.Direction = To_D3D_Vector(light.Direction); + result.Range = light.Range; + result.Falloff = light.Falloff; + result.Attenuation0 = light.Attenuation0; + result.Attenuation1 = light.Attenuation1; + result.Attenuation2 = light.Attenuation2; + result.Theta = light.Theta; + result.Phi = light.Phi; + return result; +} + const int DEFAULT_RESOLUTION_WIDTH = 640; const int DEFAULT_RESOLUTION_HEIGHT = 480; const int DEFAULT_BIT_DEPTH = 32; const int DEFAULT_TEXTURE_BIT_DEPTH = 16; -const D3DMULTISAMPLE_TYPE DEFAULT_MSAA = D3DMULTISAMPLE_NONE; +const unsigned DEFAULT_MSAA = 0; +const DWORD LEGACY_NO_WHQL_LEVEL = 0x00000002L; +const DWORD LEGACY_CAP_HW_TRANSFORM_AND_LIGHT = 0x00010000L; +const DWORD LEGACY_CREATE_FPU_PRESERVE = 0x00000002L; +const DWORD LEGACY_CREATE_MULTITHREADED = 0x00000004L; +const DWORD LEGACY_CREATE_SOFTWARE_VERTEXPROCESSING = 0x00000020L; +const DWORD LEGACY_CREATE_MIXED_VERTEXPROCESSING = 0x00000080L; DX8FrameStatistics DX8Wrapper::FrameStatistics; static DX8FrameStatistics LastFrameStatistics; +static void Log_Missing_Texture_File(const char *reason, const char *filename) +{ + char message[512]; + snprintf( + message, + sizeof(message), + "Missing texture %s: %s\n", + reason ? reason : "load failed", + filename ? filename : "(null)"); + fprintf(stderr, "%s", message); + fflush(stderr); + OutputDebugString(message); +} + bool DX8Wrapper_IsWindowed = true; // FPU_PRESERVE @@ -117,8 +259,8 @@ int DX8Wrapper::ResolutionHeight = DEFAULT_RESOLUTION_HEIGHT; int DX8Wrapper::BitDepth = DEFAULT_BIT_DEPTH; int DX8Wrapper::TextureBitDepth = DEFAULT_TEXTURE_BIT_DEPTH; bool DX8Wrapper::IsWindowed = false; -D3DFORMAT DX8Wrapper::DisplayFormat = D3DFMT_UNKNOWN; -D3DMULTISAMPLE_TYPE DX8Wrapper::MultiSampleAntiAliasing = DEFAULT_MSAA; +unsigned DX8Wrapper::DisplayFormat = 0; +unsigned DX8Wrapper::MultiSampleAntiAliasing = DEFAULT_MSAA; // shader system additions KJM v DWORD DX8Wrapper::Vertex_Shader = 0; @@ -136,21 +278,26 @@ Vector3 DX8Wrapper::Ambient_Color; // shader system additions KJM ^ bool DX8Wrapper::world_identity; -unsigned DX8Wrapper::RenderStates[256]; -unsigned DX8Wrapper::TextureStageStates[MAX_TEXTURE_STAGES][32]; -IDirect3DBaseTexture8 * DX8Wrapper::Textures[MAX_TEXTURE_STAGES]; -RenderStateStruct DX8Wrapper::render_state; -unsigned DX8Wrapper::render_state_changed; bool DX8Wrapper::FogEnable = false; -D3DCOLOR DX8Wrapper::FogColor = 0; - -IDirect3D8 * DX8Wrapper::D3DInterface = nullptr; -IDirect3DDevice8 * DX8Wrapper::D3DDevice = nullptr; -IDirect3DSurface8 * DX8Wrapper::CurrentRenderTarget = nullptr; -IDirect3DSurface8 * DX8Wrapper::CurrentDepthBuffer = nullptr; -IDirect3DSurface8 * DX8Wrapper::DefaultRenderTarget = nullptr; -IDirect3DSurface8 * DX8Wrapper::DefaultDepthBuffer = nullptr; +unsigned DX8Wrapper::FogColor = 0; + +static IDirect3D8 * D3DInterface = nullptr; +static IDirect3DDevice8 * D3DDevice = nullptr; + +// TheSuperHackers @build bobtista 01/06/2026 Out-of-line getters for the +// file-static D3D8 device + interface pointers above. dx8wrapper.h forward- +// declares these; defining them inline there would expose the file-static +// pointers to every TU that includes the header, which fails to compile. +#if !defined(GGC_RENDER_BACKEND_BGFX) +IDirect3DDevice8 * DX8Wrapper::_Get_D3D_Device8() { return D3DDevice; } +IDirect3D8 * DX8Wrapper::_Get_D3D8() { return D3DInterface; } +#endif + +static IDirect3DSurface8 * CurrentRenderTarget = nullptr; +static IDirect3DSurface8 * CurrentDepthBuffer = nullptr; +static IDirect3DSurface8 * DefaultRenderTarget = nullptr; +static IDirect3DSurface8 * DefaultDepthBuffer = nullptr; bool DX8Wrapper::IsRenderToTexture = false; unsigned DX8Wrapper::_MainThreadID = 0; @@ -159,33 +306,206 @@ bool DX8Wrapper::IsDeviceLost; int DX8Wrapper::ZBias; float DX8Wrapper::ZNear; float DX8Wrapper::ZFar; +#if !defined(GGC_RENDER_BACKEND_BGFX) D3DMATRIX DX8Wrapper::ProjectionMatrix; -D3DMATRIX DX8Wrapper::DX8Transforms[D3DTS_WORLD+1]; - +#endif DX8Caps* DX8Wrapper::CurrentCaps = nullptr; // Hack test... this disables rendering of batches of too few polygons. unsigned DX8Wrapper::DrawPolygonLowBoundLimit=0; -D3DADAPTER_IDENTIFIER8 DX8Wrapper::CurrentAdapterIdentifier; +static D3DADAPTER_IDENTIFIER8 CurrentAdapterIdentifier; unsigned long DX8Wrapper::FrameCount = 0; bool _DX8SingleThreaded = false; static D3DPRESENT_PARAMETERS _PresentParameters; + +#if defined(GGC_RENDER_BACKEND_BGFX) +static bool StandaloneDeviceCreated = false; + +static void Fill_Standalone_DX8_Caps(D3DCAPS8 &caps) +{ + ::ZeroMemory(&caps, sizeof(caps)); + caps.DeviceType = D3DDEVTYPE_HAL; + caps.AdapterOrdinal = 0; + caps.Caps = 0; + caps.Caps2 = D3DCAPS2_CANRENDERWINDOWED | D3DCAPS2_DYNAMICTEXTURES | D3DCAPS2_FULLSCREENGAMMA | D3DCAPS2_CANCALIBRATEGAMMA; + caps.Caps3 = 0; + caps.PresentationIntervals = D3DPRESENT_INTERVAL_DEFAULT | D3DPRESENT_INTERVAL_IMMEDIATE | D3DPRESENT_INTERVAL_ONE; + caps.CursorCaps = D3DCURSORCAPS_COLOR | D3DCURSORCAPS_LOWRES; + caps.DevCaps = D3DDEVCAPS_HWTRANSFORMANDLIGHT | D3DDEVCAPS_PUREDEVICE | D3DDEVCAPS_DRAWPRIMTLVERTEX + | D3DDEVCAPS_EXECUTESYSTEMMEMORY | D3DDEVCAPS_EXECUTEVIDEOMEMORY + | D3DDEVCAPS_TLVERTEXSYSTEMMEMORY | D3DDEVCAPS_TLVERTEXVIDEOMEMORY + | D3DDEVCAPS_TEXTURESYSTEMMEMORY | D3DDEVCAPS_TEXTUREVIDEOMEMORY + | D3DDEVCAPS_CANRENDERAFTERFLIP | D3DDEVCAPS_TEXTURENONLOCALVIDMEM + | D3DDEVCAPS_DRAWPRIMITIVES2 | D3DDEVCAPS_DRAWPRIMITIVES2EX + | D3DDEVCAPS_HWRASTERIZATION; + caps.PrimitiveMiscCaps = D3DPMISCCAPS_MASKZ | D3DPMISCCAPS_LINEPATTERNREP + | D3DPMISCCAPS_CULLNONE | D3DPMISCCAPS_CULLCW | D3DPMISCCAPS_CULLCCW + | D3DPMISCCAPS_COLORWRITEENABLE | D3DPMISCCAPS_CLIPTLVERTS + | D3DPMISCCAPS_TSSARGTEMP | D3DPMISCCAPS_BLENDOP; + caps.RasterCaps = D3DPRASTERCAPS_DITHER | D3DPRASTERCAPS_ZTEST + | D3DPRASTERCAPS_FOGVERTEX | D3DPRASTERCAPS_FOGTABLE + | D3DPRASTERCAPS_MIPMAPLODBIAS | D3DPRASTERCAPS_ZBIAS + | D3DPRASTERCAPS_ANISOTROPY | D3DPRASTERCAPS_WFOG | D3DPRASTERCAPS_ZFOG + | D3DPRASTERCAPS_COLORPERSPECTIVE; + caps.ZCmpCaps = D3DPCMPCAPS_NEVER | D3DPCMPCAPS_LESS | D3DPCMPCAPS_EQUAL + | D3DPCMPCAPS_LESSEQUAL | D3DPCMPCAPS_GREATER | D3DPCMPCAPS_NOTEQUAL + | D3DPCMPCAPS_GREATEREQUAL | D3DPCMPCAPS_ALWAYS; + caps.SrcBlendCaps = D3DPBLENDCAPS_ZERO | D3DPBLENDCAPS_ONE + | D3DPBLENDCAPS_SRCCOLOR | D3DPBLENDCAPS_INVSRCCOLOR + | D3DPBLENDCAPS_SRCALPHA | D3DPBLENDCAPS_INVSRCALPHA + | D3DPBLENDCAPS_DESTALPHA | D3DPBLENDCAPS_INVDESTALPHA + | D3DPBLENDCAPS_DESTCOLOR | D3DPBLENDCAPS_INVDESTCOLOR + | D3DPBLENDCAPS_SRCALPHASAT | D3DPBLENDCAPS_BOTHSRCALPHA + | D3DPBLENDCAPS_BOTHINVSRCALPHA; + caps.DestBlendCaps = caps.SrcBlendCaps; + caps.AlphaCmpCaps = caps.ZCmpCaps; + caps.ShadeCaps = D3DPSHADECAPS_COLORGOURAUDRGB | D3DPSHADECAPS_SPECULARGOURAUDRGB + | D3DPSHADECAPS_ALPHAGOURAUDBLEND | D3DPSHADECAPS_FOGGOURAUD; + caps.TextureCaps = D3DPTEXTURECAPS_PERSPECTIVE | D3DPTEXTURECAPS_ALPHA + | D3DPTEXTURECAPS_TEXREPEATNOTSCALEDBYSIZE | D3DPTEXTURECAPS_ALPHAPALETTE + | D3DPTEXTURECAPS_PROJECTED | D3DPTEXTURECAPS_CUBEMAP + | D3DPTEXTURECAPS_VOLUMEMAP | D3DPTEXTURECAPS_MIPMAP + | D3DPTEXTURECAPS_MIPVOLUMEMAP | D3DPTEXTURECAPS_MIPCUBEMAP; + caps.TextureFilterCaps = D3DPTFILTERCAPS_MINFPOINT | D3DPTFILTERCAPS_MINFLINEAR + | D3DPTFILTERCAPS_MINFANISOTROPIC | D3DPTFILTERCAPS_MIPFPOINT + | D3DPTFILTERCAPS_MIPFLINEAR | D3DPTFILTERCAPS_MAGFPOINT + | D3DPTFILTERCAPS_MAGFLINEAR | D3DPTFILTERCAPS_MAGFANISOTROPIC; + caps.CubeTextureFilterCaps = caps.TextureFilterCaps; + caps.VolumeTextureFilterCaps = caps.TextureFilterCaps; + caps.TextureAddressCaps = D3DPTADDRESSCAPS_WRAP | D3DPTADDRESSCAPS_MIRROR + | D3DPTADDRESSCAPS_CLAMP | D3DPTADDRESSCAPS_BORDER + | D3DPTADDRESSCAPS_INDEPENDENTUV | D3DPTADDRESSCAPS_MIRRORONCE; + caps.VolumeTextureAddressCaps = caps.TextureAddressCaps; + caps.LineCaps = D3DLINECAPS_TEXTURE | D3DLINECAPS_ZTEST | D3DLINECAPS_BLEND | D3DLINECAPS_ALPHACMP | D3DLINECAPS_FOG; + caps.MaxTextureWidth = 4096; + caps.MaxTextureHeight = 4096; + caps.MaxVolumeExtent = 256; + caps.MaxTextureRepeat = 8192; + caps.MaxTextureAspectRatio = 0; + caps.MaxAnisotropy = 16; + caps.MaxVertexW = 1e10f; + caps.GuardBandLeft = -32768.0f; + caps.GuardBandTop = -32768.0f; + caps.GuardBandRight = 32768.0f; + caps.GuardBandBottom = 32768.0f; + caps.ExtentsAdjust = 0.0f; + caps.StencilCaps = D3DSTENCILCAPS_KEEP | D3DSTENCILCAPS_ZERO | D3DSTENCILCAPS_REPLACE + | D3DSTENCILCAPS_INCRSAT | D3DSTENCILCAPS_DECRSAT | D3DSTENCILCAPS_INVERT + | D3DSTENCILCAPS_INCR | D3DSTENCILCAPS_DECR; + caps.FVFCaps = 8 | D3DFVFCAPS_PSIZE; + caps.TextureOpCaps = D3DTEXOPCAPS_DISABLE | D3DTEXOPCAPS_SELECTARG1 | D3DTEXOPCAPS_SELECTARG2 + | D3DTEXOPCAPS_MODULATE | D3DTEXOPCAPS_MODULATE2X | D3DTEXOPCAPS_MODULATE4X + | D3DTEXOPCAPS_ADD | D3DTEXOPCAPS_ADDSIGNED | D3DTEXOPCAPS_ADDSIGNED2X + | D3DTEXOPCAPS_SUBTRACT | D3DTEXOPCAPS_ADDSMOOTH + | D3DTEXOPCAPS_BLENDDIFFUSEALPHA | D3DTEXOPCAPS_BLENDTEXTUREALPHA + | D3DTEXOPCAPS_BLENDFACTORALPHA | D3DTEXOPCAPS_BLENDTEXTUREALPHAPM + | D3DTEXOPCAPS_BLENDCURRENTALPHA | D3DTEXOPCAPS_PREMODULATE + | D3DTEXOPCAPS_MODULATEALPHA_ADDCOLOR | D3DTEXOPCAPS_MODULATECOLOR_ADDALPHA + | D3DTEXOPCAPS_MODULATEINVALPHA_ADDCOLOR | D3DTEXOPCAPS_MODULATEINVCOLOR_ADDALPHA + | D3DTEXOPCAPS_BUMPENVMAP | D3DTEXOPCAPS_BUMPENVMAPLUMINANCE + | D3DTEXOPCAPS_DOTPRODUCT3 | D3DTEXOPCAPS_MULTIPLYADD | D3DTEXOPCAPS_LERP; + caps.MaxTextureBlendStages = 8; + caps.MaxSimultaneousTextures = 4; + caps.VertexProcessingCaps = D3DVTXPCAPS_TEXGEN | D3DVTXPCAPS_MATERIALSOURCE7 + | D3DVTXPCAPS_DIRECTIONALLIGHTS | D3DVTXPCAPS_POSITIONALLIGHTS + | D3DVTXPCAPS_LOCALVIEWER | D3DVTXPCAPS_TWEENING; + caps.MaxActiveLights = 8; + caps.MaxUserClipPlanes = 6; + caps.MaxVertexBlendMatrices = 4; + caps.MaxVertexBlendMatrixIndex = 0; + caps.MaxPointSize = 256.0f; + caps.MaxPrimitiveCount = 65535; + caps.MaxVertexIndex = 65535; + caps.MaxStreams = 8; + caps.MaxStreamStride = 256; + caps.VertexShaderVersion = D3DVS_VERSION(1, 1); + caps.MaxVertexShaderConst = 256; + caps.PixelShaderVersion = D3DPS_VERSION(1, 1); + caps.MaxPixelShaderValue = 1.0f; +} + +static void Fill_Standalone_Adapter_Identifier(D3DADAPTER_IDENTIFIER8 &identifier) +{ + ::ZeroMemory(&identifier, sizeof(identifier)); + std::snprintf(identifier.Driver, sizeof(identifier.Driver), "%s", "bgfx"); + std::snprintf(identifier.Description, sizeof(identifier.Description), "%s", "Generals bgfx standalone"); +#ifdef _WIN32 + identifier.DriverVersion.QuadPart = 0; +#else + identifier.DriverVersionHighPart = 0; + identifier.DriverVersionLowPart = 0; +#endif +} + +#endif + +template +static T Legacy_Value(unsigned value) +{ + return static_cast(value); +} + +static auto Legacy_Format(unsigned value) { return Legacy_Value(value); } +static auto Legacy_Multisample_Type(unsigned value) { return Legacy_Value(value); } +static auto Legacy_Swap_Effect(unsigned value) { return Legacy_Value(value); } static DynamicVectorClass _RenderDeviceNameTable; static DynamicVectorClass _RenderDeviceShortNameTable; static DynamicVectorClass _RenderDeviceDescriptionTable; +static HRESULT Copy_Legacy_Surface_Compat( + IDirect3DSurface8 *destination, + const RECT *destination_rect, + IDirect3DSurface8 *source, + const RECT *source_rect, + unsigned int filter) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)filter; + return Standalone_Copy_Legacy_Surface(destination, destination_rect, source, source_rect); +#else + return D3DXLoadSurfaceFromSurface( + destination, + nullptr, + destination_rect, + source, + nullptr, + source_rect, + filter, + 0); +#endif +} + +static HRESULT Filter_Legacy_Texture_Mips_Compat(IDirect3DBaseTexture8 *base_texture, unsigned int src_level) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + return Standalone_Filter_Legacy_Texture_Mips(base_texture, src_level); +#else + return D3DXFilterTexture(base_texture, nullptr, src_level, D3DX_FILTER_BOX); +#endif +} + +IDirect3DDevice8* DX8_Call_Device() +{ + return D3DDevice; +} + +IDirect3D8* DX8_Call_Interface() +{ + return D3DInterface; +} + typedef IDirect3D8* (WINAPI *Direct3DCreate8Type) (UINT SDKVersion); Direct3DCreate8Type Direct3DCreate8Ptr = nullptr; HINSTANCE D3D8Lib = nullptr; -DX8_CleanupHook *DX8Wrapper::m_pCleanupHook=nullptr; +RenderDeviceCleanupHook *DX8Wrapper::m_pCleanupHook=nullptr; #ifdef EXTENDED_STATS -DX8_Stats DX8Wrapper::stats; +RenderDebugStats &DX8Wrapper::stats = g_renderDebugStats; #endif /*********************************************************************************** ** @@ -193,16 +513,42 @@ DX8_Stats DX8Wrapper::stats; ** ***********************************************************************************/ +static HRESULT Get_DX8_Error_String(unsigned res, char *buffer, size_t buffer_size) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + if (buffer == nullptr || buffer_size == 0) + { + return D3D_OK; + } + const char *message = nullptr; + switch (res) + { + case D3D_OK: message = "D3D_OK"; break; + case D3DERR_INVALIDCALL: message = "D3DERR_INVALIDCALL"; break; + case D3DERR_NOTAVAILABLE: message = "D3DERR_NOTAVAILABLE"; break; + case D3DERR_OUTOFVIDEOMEMORY: message = "D3DERR_OUTOFVIDEOMEMORY"; break; + case E_OUTOFMEMORY: message = "E_OUTOFMEMORY"; break; + case E_NOTIMPL: message = "E_NOTIMPL"; break; + case E_FAIL: message = "E_FAIL"; break; + default: message = "D3D unknown error"; break; + } + std::snprintf(buffer, buffer_size, "%s", message); + return D3D_OK; +#else + return D3DXGetErrorStringA(res, buffer, static_cast(buffer_size)); +#endif +} + void Log_DX8_ErrorCode(unsigned res) { char tmp[256]=""; - HRESULT new_res=D3DXGetErrorStringA( + HRESULT new_res=Get_DX8_Error_String( res, tmp, sizeof(tmp)); - if (new_res==D3D_OK) { + if (new_res==S_OK) { WWDEBUG_SAY((tmp)); } @@ -213,12 +559,12 @@ void Non_Fatal_Log_DX8_ErrorCode(unsigned res,const char * file,int line) { char tmp[256]=""; - HRESULT new_res=D3DXGetErrorStringA( + HRESULT new_res=Get_DX8_Error_String( res, tmp, sizeof(tmp)); - if (new_res==D3D_OK) { + if (new_res==S_OK) { WWDEBUG_SAY(("DX8 Error: %s, File: %s, Line: %d",tmp,file,line)); } } @@ -251,17 +597,16 @@ bool DX8Wrapper::Init(void * hwnd, bool lite) WWASSERT(!IsInitted); // zero memory - memset(Textures,0,sizeof(IDirect3DBaseTexture8*)*MAX_TEXTURE_STAGES); - memset(RenderStates,0,sizeof(unsigned)*256); - memset(TextureStageStates,0,sizeof(unsigned)*32*MAX_TEXTURE_STAGES); + FixedFunctionState::Clear_Cached_State(); memset(Vertex_Shader_Constants,0,sizeof(Vector4)*MAX_VERTEX_SHADER_CONSTANTS); memset(Pixel_Shader_Constants,0,sizeof(Vector4)*MAX_PIXEL_SHADER_CONSTANTS); - memset(&render_state,0,sizeof(RenderStateStruct)); + FixedFunctionState::Clear_Raw(); memset(Shadow_Map,0,sizeof(ZTextureClass*)*MAX_SHADOW_MAPS); /* ** Initialize all variables! */ + _Hwnd = (HWND)hwnd; _MainThreadID=ThreadClass::_Get_Current_Thread_ID(); WWDEBUG_SAY(("DX8Wrapper main thread: 0x%x",_MainThreadID)); @@ -273,6 +618,10 @@ bool DX8Wrapper::Init(void * hwnd, bool lite) BitDepth = DEFAULT_BIT_DEPTH; IsWindowed = false; DX8Wrapper_IsWindowed = false; +#if defined(GGC_RENDER_BACKEND_BGFX) + g_device.windowed = IsWindowed; + g_device.bits = BitDepth; +#endif for (int light=0;light<4;++light) CurrentDX8LightEnables[light]=false; @@ -292,6 +641,12 @@ bool DX8Wrapper::Init(void * hwnd, bool lite) Invalidate_Cached_Render_States(); if (!lite) { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWDEBUG_SAY(("Using standalone bgfx device metadata")); + IsInitted = true; + Enumerate_Devices(); + WWDEBUG_SAY(("DX8Wrapper Init completed (standalone bgfx)")); +#else D3D8Lib = LoadLibrary("D3D8.DLL"); if (D3D8Lib == nullptr) return false; // Return false at this point if init failed @@ -321,6 +676,7 @@ bool DX8Wrapper::Init(void * hwnd, bool lite) WWDEBUG_SAY(("Enumerate devices")); Enumerate_Devices(); WWDEBUG_SAY(("DX8Wrapper Init completed")); +#endif } return(true); @@ -328,29 +684,29 @@ bool DX8Wrapper::Init(void * hwnd, bool lite) void DX8Wrapper::Shutdown() { - if (D3DDevice) { + if (D3DDevice +#if defined(GGC_RENDER_BACKEND_BGFX) + || StandaloneDeviceCreated +#endif + ) { +#if !defined(GGC_RENDER_BACKEND_BGFX) Set_Render_Target ((IDirect3DSurface8 *)nullptr); +#endif Release_Device(); } +#if !defined(GGC_RENDER_BACKEND_BGFX) if (D3DInterface) { D3DInterface->Release(); D3DInterface=nullptr; } +#endif if (CurrentCaps) { - int max=CurrentCaps->Get_Max_Textures_Per_Pass(); - for (int i = 0; i < max; i++) - { - if (Textures[i]) - { - Textures[i]->Release(); - Textures[i] = nullptr; - } - } + FixedFunctionState::Release_Raw_Textures(); } if (D3D8Lib) { @@ -371,23 +727,17 @@ void DX8Wrapper::Do_Onetime_Device_Dependent_Inits() /* ** Set Global render states (some of which depend on caps) */ - Compute_Caps(D3DFormat_To_WW3DFormat(DisplayFormat)); + Compute_Caps(D3DFormat_To_WW3DFormat(Legacy_Format(DisplayFormat))); + + // TheSuperHackers @refactor bobtista 11/04/2026 Initialize the render backend's per-window + // context BEFORE the subsystem _Init() calls below: their static-buffer Write locks mirror + // data into the backend caches, which requires a fully initialized backend. + g_renderBackend->Initialize(_Hwnd, ResolutionWidth, ResolutionHeight); /* ** Initialize any other subsystems inside of WW3D */ - MissingTexture::_Init(); - TextureFilterClass::_Init_Filters( - (TextureFilterClass::TextureFilterMode)WW3D::Get_Texture_Filter(), - (TextureFilterClass::AnisotropicFilterMode)WW3D::Get_Anisotropy_Level() - ); - TheDX8MeshRenderer.Init(); - SHD_INIT; - BoxRenderObjClass::Init(); - VertexMaterialClass::Init(); - PointGroupClass::_Init(); // This needs the VertexMaterialClass to be initted - ShatterSystem::Init(); - TextureLoader::Init(); + WW3DDeviceInit::Init_Subsystems(); Set_Default_Global_Render_States(); } @@ -396,85 +746,66 @@ inline DWORD F2DW(float f) { return *((unsigned*)&f); } void DX8Wrapper::Set_Default_Global_Render_States() { DX8_THREAD_ASSERT(); - const D3DCAPS8 &caps = Get_Current_Caps()->Get_DX8_Caps(); - - Set_DX8_Render_State(D3DRS_RANGEFOGENABLE, (caps.RasterCaps & D3DPRASTERCAPS_FOGRANGE) ? TRUE : FALSE); - Set_DX8_Render_State(D3DRS_FOGTABLEMODE, D3DFOG_NONE); - Set_DX8_Render_State(D3DRS_FOGVERTEXMODE, D3DFOG_LINEAR); - Set_DX8_Render_State(D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL); - Set_DX8_Render_State(D3DRS_COLORVERTEX, TRUE); - Set_DX8_Render_State(D3DRS_ZBIAS,0); - Set_DX8_Texture_Stage_State(1, D3DTSS_BUMPENVLSCALE, F2DW(1.0f)); - Set_DX8_Texture_Stage_State(1, D3DTSS_BUMPENVLOFFSET, F2DW(0.0f)); - Set_DX8_Texture_Stage_State(0, D3DTSS_BUMPENVMAT00,F2DW(1.0f)); - Set_DX8_Texture_Stage_State(0, D3DTSS_BUMPENVMAT01,F2DW(0.0f)); - Set_DX8_Texture_Stage_State(0, D3DTSS_BUMPENVMAT10,F2DW(0.0f)); - Set_DX8_Texture_Stage_State(0, D3DTSS_BUMPENVMAT11,F2DW(1.0f)); - -// Set_DX8_Render_State(D3DRS_CULLMODE, D3DCULL_CW); + + Commit_Fixed_Function_Render_Value(48 /* D3DRS_RANGEFOGENABLE */, Get_Current_Caps()->Support_Range_Fog() ? TRUE : FALSE); + Commit_Fixed_Function_Render_Value(35 /* D3DRS_FOGTABLEMODE */, 0); + Commit_Fixed_Function_Render_Value(140 /* D3DRS_FOGVERTEXMODE */, 3); + Commit_Fixed_Function_Render_Value(146 /* D3DRS_SPECULARMATERIALSOURCE */, 0); + Commit_Fixed_Function_Render_Value(141 /* D3DRS_COLORVERTEX */, TRUE); + Commit_Fixed_Function_Render_Value(47 /* D3DRS_ZBIAS */,0); + Commit_Fixed_Function_Texture_Stage_Value(1, 22 /* D3DTSS_BUMPENVLSCALE */, F2DW(1.0f)); + Commit_Fixed_Function_Texture_Stage_Value(1, 23 /* D3DTSS_BUMPENVLOFFSET */, F2DW(0.0f)); + Commit_Fixed_Function_Texture_Stage_Value(0, 7 /* D3DTSS_BUMPENVMAT00 */,F2DW(1.0f)); + Commit_Fixed_Function_Texture_Stage_Value(0, 8 /* D3DTSS_BUMPENVMAT01 */,F2DW(0.0f)); + Commit_Fixed_Function_Texture_Stage_Value(0, 9 /* D3DTSS_BUMPENVMAT10 */,F2DW(0.0f)); + Commit_Fixed_Function_Texture_Stage_Value(0, 10 /* D3DTSS_BUMPENVMAT11 */,F2DW(1.0f)); + +// Commit_Fixed_Function_Render_Value(22 /* D3DRS_CULLMODE */, 1); // Set dither mode here? } void DX8Wrapper::Invalidate_Cached_Render_States() { - render_state_changed=0; + FixedFunctionState::Changed_Mask()=0; + FixedFunctionState::Invalidate_Cached_State(); +#if !defined(GGC_RENDER_BACKEND_BGFX) int a; - for (a=0;aSetTexture(a,nullptr); - if (Textures[a] != nullptr) { - Textures[a]->Release(); - } - Textures[a]=nullptr; } +#endif + FixedFunctionState::Release_Raw_Textures(); ShaderClass::Invalidate(); - //Need to explicitly set render_state texture pointers to null. MW + //Need to explicitly set render-state texture pointers to null. MW Release_Render_State(); - // (gth) clear the matrix shadows too - memset(&DX8Transforms, 0, sizeof(DX8Transforms)); } 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. + if (g_renderBackend != nullptr) + { + // Symmetric counterpart to the Initialize call in + // Do_Onetime_Device_Dependent_Inits. The backend object outlives + // this teardown; it is destroyed in WW3D::Shutdown via + // Shutdown_Render_Backend. + g_renderBackend->Shutdown(); + } + /* ** Shutdown ww3d systems */ - int i; - for (i=0;iRelease_Engine_Ref(); - REF_PTR_RELEASE(render_state.vertex_buffers[i]); - } - if (render_state.index_buffer) render_state.index_buffer->Release_Engine_Ref(); - REF_PTR_RELEASE(render_state.index_buffer); - REF_PTR_RELEASE(render_state.material); - for (i=0;iGet_Max_Textures_Per_Pass();++i) REF_PTR_RELEASE(render_state.Textures[i]); - - - TextureLoader::Deinit(); - SortingRendererClass::Deinit(); - DynamicVBAccessClass::_Deinit(); - DynamicIBAccessClass::_Deinit(); - ShatterSystem::Shutdown(); - PointGroupClass::_Shutdown(); - VertexMaterialClass::Shutdown(); - BoxRenderObjClass::Shutdown(); - SHD_SHUTDOWN; - TheDX8MeshRenderer.Shutdown(); - MissingTexture::_Deinit(); + WW3DDeviceInit::Shutdown_Subsystems(); delete CurrentCaps; CurrentCaps=nullptr; @@ -484,6 +815,34 @@ void DX8Wrapper::Do_Onetime_Device_Dependent_Shutdowns() bool DX8Wrapper::Create_Device() { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT(!StandaloneDeviceCreated); + + D3DCAPS8 caps; + Fill_Standalone_DX8_Caps(caps); + Fill_Standalone_Adapter_Identifier(CurrentAdapterIdentifier); + + Vertex_Processing_Behavior=(caps.DevCaps&LEGACY_CAP_HW_TRANSFORM_AND_LIGHT) ? + LEGACY_CREATE_MIXED_VERTEXPROCESSING : LEGACY_CREATE_SOFTWARE_VERTEXPROCESSING; + +#ifdef CREATE_DX8_MULTI_THREADED + Vertex_Processing_Behavior|=LEGACY_CREATE_MULTITHREADED; + _DX8SingleThreaded=false; +#else + _DX8SingleThreaded=true; +#endif + + if (DX8Wrapper_PreserveFPU) + Vertex_Processing_Behavior |= LEGACY_CREATE_FPU_PRESERVE; + +#ifdef CREATE_DX8_FPU_PRESERVE + Vertex_Processing_Behavior|=LEGACY_CREATE_FPU_PRESERVE; +#endif + + StandaloneDeviceCreated = true; + Do_Onetime_Device_Dependent_Inits(); + return true; +#else WWASSERT(D3DDevice==nullptr); // for now, once you've created a device, you're stuck with it! D3DCAPS8 caps; @@ -503,7 +862,7 @@ bool DX8Wrapper::Create_Device() return false; } - ::ZeroMemory(&CurrentAdapterIdentifier, sizeof(D3DADAPTER_IDENTIFIER8)); + ::ZeroMemory(&CurrentAdapterIdentifier, sizeof(CurrentAdapterIdentifier)); if ( @@ -512,7 +871,7 @@ bool DX8Wrapper::Create_Device() D3DInterface->GetAdapterIdentifier ( CurRenderDevice, - D3DENUM_NO_WHQL_LEVEL, + LEGACY_NO_WHQL_LEVEL, &CurrentAdapterIdentifier ) ) @@ -521,27 +880,27 @@ bool DX8Wrapper::Create_Device() return false; } - Vertex_Processing_Behavior=(caps.DevCaps&D3DDEVCAPS_HWTRANSFORMANDLIGHT) ? - D3DCREATE_MIXED_VERTEXPROCESSING : D3DCREATE_SOFTWARE_VERTEXPROCESSING; + Vertex_Processing_Behavior=(caps.DevCaps&LEGACY_CAP_HW_TRANSFORM_AND_LIGHT) ? + LEGACY_CREATE_MIXED_VERTEXPROCESSING : LEGACY_CREATE_SOFTWARE_VERTEXPROCESSING; // enable this when all 'get' dx calls are removed KJM - /*if (caps.DevCaps&D3DDEVCAPS_PUREDEVICE) + /*if (caps.DevCaps&LEGACY_CAP_PURE_DEVICE) { - Vertex_Processing_Behavior|=D3DCREATE_PUREDEVICE; + Vertex_Processing_Behavior|=LEGACY_CREATE_PUREDEVICE; }*/ #ifdef CREATE_DX8_MULTI_THREADED - Vertex_Processing_Behavior|=D3DCREATE_MULTITHREADED; + Vertex_Processing_Behavior|=LEGACY_CREATE_MULTITHREADED; _DX8SingleThreaded=false; #else _DX8SingleThreaded=true; #endif if (DX8Wrapper_PreserveFPU) - Vertex_Processing_Behavior |= D3DCREATE_FPU_PRESERVE; + Vertex_Processing_Behavior |= LEGACY_CREATE_FPU_PRESERVE; #ifdef CREATE_DX8_FPU_PRESERVE - Vertex_Processing_Behavior|=D3DCREATE_FPU_PRESERVE; + Vertex_Processing_Behavior|=LEGACY_CREATE_FPU_PRESERVE; #endif // TheSuperHackers @bugfix xezon 13/06/2025 Front load the system dbghelp.dll to prevent @@ -563,14 +922,16 @@ bool DX8Wrapper::Create_Device() // The device selection may fail because the device lied that it supports 32 bit zbuffer with 16 bit // display. This happens at least on Voodoo2. - if ((_PresentParameters.BackBufferFormat==D3DFMT_R5G6B5 || - _PresentParameters.BackBufferFormat==D3DFMT_X1R5G5B5 || - _PresentParameters.BackBufferFormat==D3DFMT_A1R5G5B5) && - (_PresentParameters.AutoDepthStencilFormat==D3DFMT_D32 || - _PresentParameters.AutoDepthStencilFormat==D3DFMT_D24S8 || - _PresentParameters.AutoDepthStencilFormat==D3DFMT_D24X8)) + const unsigned backbuffer_format = static_cast(_PresentParameters.BackBufferFormat); + const unsigned depth_format = static_cast(_PresentParameters.AutoDepthStencilFormat); + if ((backbuffer_format==23 || + backbuffer_format==24 || + backbuffer_format==25) && + (depth_format==71 || + depth_format==75 || + depth_format==77)) { - _PresentParameters.AutoDepthStencilFormat=D3DFMT_D16; + _PresentParameters.AutoDepthStencilFormat=Legacy_Format(80); hr = D3DInterface->CreateDevice ( CurRenderDevice, @@ -599,13 +960,18 @@ bool DX8Wrapper::Create_Device() */ Do_Onetime_Device_Dependent_Inits(); return true; +#endif } bool DX8Wrapper::Reset_Device(bool reload_assets) { WWDEBUG_SAY(("Resetting device.")); DX8_THREAD_ASSERT(); - if ((IsInitted) && (D3DDevice != nullptr)) { + if ((IsInitted) && (D3DDevice != nullptr +#if defined(GGC_RENDER_BACKEND_BGFX) + || StandaloneDeviceCreated +#endif + )) { // Release all non-MANAGED stuff WW3D::_Invalidate_Textures(); @@ -619,7 +985,7 @@ bool DX8Wrapper::Reset_Device(bool reload_assets) } DynamicVBAccessClass::_Deinit(); DynamicIBAccessClass::_Deinit(); - DX8TextureManagerClass::Release_Textures(); + TextureResourceManagerClass::Release_Textures(); SHD_SHUTDOWN_SHADERS; // Reset frame count to reflect the flipping chain being reset by Reset() @@ -628,6 +994,7 @@ bool DX8Wrapper::Reset_Device(bool reload_assets) memset(Vertex_Shader_Constants,0,sizeof(Vector4)*MAX_VERTEX_SHADER_CONSTANTS); memset(Pixel_Shader_Constants,0,sizeof(Vector4)*MAX_PIXEL_SHADER_CONSTANTS); +#if !defined(GGC_RENDER_BACKEND_BGFX) HRESULT hr=_Get_D3D_Device8()->TestCooperativeLevel(); if (hr != D3DERR_DEVICELOST ) { DX8CALL_HRES(Reset(&_PresentParameters),hr) @@ -636,10 +1003,11 @@ bool DX8Wrapper::Reset_Device(bool reload_assets) } else return false; //device is lost and can't be reset. +#endif if (reload_assets) { - DX8TextureManagerClass::Recreate_Textures(); + TextureResourceManagerClass::Recreate_Textures(); if (m_pCleanupHook) { m_pCleanupHook->ReAcquireResources(); } @@ -656,12 +1024,29 @@ bool DX8Wrapper::Reset_Device(bool reload_assets) void DX8Wrapper::Release_Device() { +#if defined(GGC_RENDER_BACKEND_BGFX) + if (StandaloneDeviceCreated) { + FixedFunctionState::Release_Raw_Textures(); + + for (unsigned i=0;iRelease_Engine_Ref(); + REF_PTR_RELEASE(FixedFunctionState::Render_State().vertex_buffers[i]); + } + if (FixedFunctionState::Render_State().index_buffer) FixedFunctionState::Render_State().index_buffer->Release_Engine_Ref(); + REF_PTR_RELEASE(FixedFunctionState::Render_State().index_buffer); + + Do_Onetime_Device_Dependent_Shutdowns(); + StandaloneDeviceCreated = false; + } +#else if (D3DDevice) { for (int a=0;aRelease_Engine_Ref(); - REF_PTR_RELEASE(render_state.vertex_buffers[i]); + if (FixedFunctionState::Render_State().vertex_buffers[i]) FixedFunctionState::Render_State().vertex_buffers[i]->Release_Engine_Ref(); + REF_PTR_RELEASE(FixedFunctionState::Render_State().vertex_buffers[i]); } - if (render_state.index_buffer) render_state.index_buffer->Release_Engine_Ref(); - REF_PTR_RELEASE(render_state.index_buffer); + if (FixedFunctionState::Render_State().index_buffer) FixedFunctionState::Render_State().index_buffer->Release_Engine_Ref(); + REF_PTR_RELEASE(FixedFunctionState::Render_State().index_buffer); /* ** Shutdown all subsystems @@ -690,20 +1075,42 @@ void DX8Wrapper::Release_Device() D3DDevice->Release(); D3DDevice=nullptr; } +#endif } void DX8Wrapper::Enumerate_Devices() { DX8_Assert(); +#if defined(GGC_RENDER_BACKEND_BGFX) + D3DADAPTER_IDENTIFIER8 id; + Fill_Standalone_Adapter_Identifier(id); + + RenderDeviceDescClass desc; + desc.set_device_name(id.Description); + desc.set_driver_name(id.Driver); + desc.set_driver_version("0.0.0.0"); + desc.reset_resolution_list(); + desc.add_resolution(640, 480, 32); + desc.add_resolution(800, 600, 32); + desc.add_resolution(1024, 768, 32); + desc.add_resolution(1280, 720, 32); + desc.add_resolution(1280, 1024, 32); + desc.add_resolution(1920, 1080, 32); + + StringClass device_name(id.Description, true); + _RenderDeviceNameTable.Add(device_name); + _RenderDeviceShortNameTable.Add(device_name); + _RenderDeviceDescriptionTable.Add(desc); +#else int adapter_count = D3DInterface->GetAdapterCount(); for (int adapter_index=0; adapter_indexGetAdapterIdentifier(adapter_index,D3DENUM_NO_WHQL_LEVEL,&id); + ::ZeroMemory(&id, sizeof(id)); + HRESULT res = D3DInterface->GetAdapterIdentifier(adapter_index,LEGACY_NO_WHQL_LEVEL,&id); - if (res == D3D_OK) { + if (res == S_OK) { /* ** Set up the render device description @@ -714,18 +1121,30 @@ void DX8Wrapper::Enumerate_Devices() desc.set_driver_name(id.Driver); char buf[64]; +#ifdef _WIN32 sprintf(buf,"%d.%d.%d.%d", //"%04x.%04x.%04x.%04x", HIWORD(id.DriverVersion.HighPart), LOWORD(id.DriverVersion.HighPart), HIWORD(id.DriverVersion.LowPart), LOWORD(id.DriverVersion.LowPart)); +#else + sprintf(buf,"%d.%d.%d.%d", //"%04x.%04x.%04x.%04x", + HIWORD(id.DriverVersionHighPart), + LOWORD(id.DriverVersionHighPart), + HIWORD(id.DriverVersionLowPart), + LOWORD(id.DriverVersionLowPart)); +#endif desc.set_driver_version(buf); - D3DInterface->GetDeviceCaps(adapter_index,WW3D_DEVTYPE,&desc.Caps); - D3DInterface->GetAdapterIdentifier(adapter_index,D3DENUM_NO_WHQL_LEVEL,&desc.AdapterIdentifier); + D3DCAPS8 caps; + ::ZeroMemory(&caps, sizeof(caps)); + D3DADAPTER_IDENTIFIER8 adapter_identifier; + ::ZeroMemory(&adapter_identifier, sizeof(adapter_identifier)); + D3DInterface->GetDeviceCaps(adapter_index,WW3D_DEVTYPE,&caps); + D3DInterface->GetAdapterIdentifier(adapter_index,LEGACY_NO_WHQL_LEVEL,&adapter_identifier); - DX8Caps dx8caps(D3DInterface,desc.Caps,WW3D_FORMAT_UNKNOWN,desc.AdapterIdentifier); + DX8Caps dx8caps(D3DInterface,static_cast(&caps),WW3D_FORMAT_UNKNOWN,&adapter_identifier); /* ** Enumerate the resolutions @@ -734,19 +1153,19 @@ void DX8Wrapper::Enumerate_Devices() int mode_count = D3DInterface->GetAdapterModeCount(adapter_index); for (int mode_index=0; mode_indexEnumAdapterModes(adapter_index,mode_index,&d3dmode); - if (res == D3D_OK) { + if (res == S_OK) { int bits = 0; - switch (d3dmode.Format) + switch (static_cast(d3dmode.Format)) { - case D3DFMT_R8G8B8: - case D3DFMT_A8R8G8B8: - case D3DFMT_X8R8G8B8: bits = 32; break; + case 20: + case 21: + case 22: bits = 32; break; - case D3DFMT_R5G6B5: - case D3DFMT_X1R5G5B5: bits = 16; break; + case 23: + case 24: bits = 16; break; } // Some cards fail in certain modes, DX8Caps keeps list of those. @@ -782,6 +1201,7 @@ void DX8Wrapper::Enumerate_Devices() } } } +#endif } bool DX8Wrapper::Set_Any_Render_Device() @@ -826,6 +1246,7 @@ bool DX8Wrapper::Set_Render_Device return false; } +#if !defined(GGC_RENDER_BACKEND_BGFX) void DX8Wrapper::Get_Format_Name(unsigned int format, StringClass *tex_format) { *tex_format="Unknown"; @@ -869,9 +1290,17 @@ void DX8Wrapper::Get_Format_Name(unsigned int format, StringClass *tex_format) default: break; } } +#endif void DX8Wrapper::Resize_And_Position_Window() { +#if defined(SAGE_USE_SDL3) + // TheSuperHackers @bugfix bobtista 07/06/2026 SDL3 owns window sizing, positioning and + // fullscreen (SDL3Main and W3DDisplay::setDisplayMode). The legacy Win32 SetWindowPos path + // below fights it: with the windowed/fullscreen choice now honored, the !IsWindowed branch + // shrinks the SDL fullscreen window to the logical resolution at the top-left corner. Skip it. + return; +#else // Get the current dimensions of the 'render area' of the window RECT rect = { 0 }; ::GetClientRect (_Hwnd, &rect); @@ -922,6 +1351,7 @@ 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)); } } +#endif } bool DX8Wrapper::Set_Render_Device(int dev, int width, int height, int bits, int windowed, @@ -966,27 +1396,31 @@ bool DX8Wrapper::Set_Render_Device(int dev, int width, int height, int bits, int } #endif //must be either resetting existing device or creating a new one. +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT(reset_device || !StandaloneDeviceCreated); +#else WWASSERT(reset_device || D3DDevice == nullptr); +#endif /* - ** Initialize values for D3DPRESENT_PARAMETERS members. + ** Initialize values for present parameters. */ - ::ZeroMemory(&_PresentParameters, sizeof(D3DPRESENT_PARAMETERS)); + ::ZeroMemory(&_PresentParameters, sizeof(_PresentParameters)); _PresentParameters.BackBufferWidth = ResolutionWidth; _PresentParameters.BackBufferHeight = ResolutionHeight; _PresentParameters.BackBufferCount = IsWindowed ? 1 : 2; //I changed this to discard all the time (even when full-screen) since that the most efficient. 07-16-03 MW: - _PresentParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;//IsWindowed ? D3DSWAPEFFECT_DISCARD : D3DSWAPEFFECT_FLIP; // Shouldn't this be D3DSWAPEFFECT_FLIP? + _PresentParameters.SwapEffect = Legacy_Swap_Effect(1);// Discard in windowed and full-screen modes. _PresentParameters.hDeviceWindow = _Hwnd; _PresentParameters.Windowed = IsWindowed; _PresentParameters.EnableAutoDepthStencil = TRUE; // Driver will attempt to match Z-buffer depth _PresentParameters.Flags=0; // We're not going to lock the backbuffer - _PresentParameters.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; - _PresentParameters.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; + _PresentParameters.FullScreen_PresentationInterval = 0; + _PresentParameters.FullScreen_RefreshRateInHz = 0; /* ** Set up the buffer formats. Several issues here: @@ -996,101 +1430,131 @@ bool DX8Wrapper::Set_Render_Device(int dev, int width, int height, int bits, int if (IsWindowed) { D3DDISPLAYMODE desktop_mode; - ::ZeroMemory(&desktop_mode, sizeof(D3DDISPLAYMODE)); + ::ZeroMemory(&desktop_mode, sizeof(desktop_mode)); +#if defined(GGC_RENDER_BACKEND_BGFX) + desktop_mode.Width = ResolutionWidth; + desktop_mode.Height = ResolutionHeight; + desktop_mode.RefreshRate = 60; + desktop_mode.Format = D3DFMT_A8R8G8B8; +#else D3DInterface->GetAdapterDisplayMode( CurRenderDevice, &desktop_mode ); +#endif - DisplayFormat=_PresentParameters.BackBufferFormat = desktop_mode.Format; + _PresentParameters.BackBufferFormat = desktop_mode.Format; + DisplayFormat=static_cast(desktop_mode.Format); // In windowed mode, define the bitdepth from desktop mode (as it can't be changed) - switch (_PresentParameters.BackBufferFormat) { - case D3DFMT_X8R8G8B8: - case D3DFMT_A8R8G8B8: - case D3DFMT_R8G8B8: BitDepth=32; break; - case D3DFMT_A4R4G4B4: - case D3DFMT_A1R5G5B5: - case D3DFMT_R5G6B5: BitDepth=16; break; - case D3DFMT_L8: - case D3DFMT_A8: - case D3DFMT_P8: BitDepth=8; break; + switch (static_cast(_PresentParameters.BackBufferFormat)) { + case 20: + case 21: + case 22: BitDepth=32; break; + case 23: + case 25: + case 26: BitDepth=16; break; + case 28: + case 41: + case 50: BitDepth=8; break; default: // Unknown backbuffer format probably means the device can't do windowed return false; } - if (BitDepth==32 && D3DInterface->CheckDeviceType(0,D3DDEVTYPE_HAL,desktop_mode.Format,D3DFMT_A8R8G8B8, TRUE) == D3D_OK) +#if defined(GGC_RENDER_BACKEND_BGFX) + _PresentParameters.AutoDepthStencilFormat = D3DFMT_D24S8; +#else + if (BitDepth==32 && D3DInterface->CheckDeviceType(0,WW3D_DEVTYPE,desktop_mode.Format,Legacy_Format(21), TRUE) == S_OK) { //promote 32-bit modes to include destination alpha - _PresentParameters.BackBufferFormat = D3DFMT_A8R8G8B8; + _PresentParameters.BackBufferFormat = Legacy_Format(21); } /* ** Find a appropriate Z buffer */ - if (!Find_Z_Mode(DisplayFormat,_PresentParameters.BackBufferFormat,&_PresentParameters.AutoDepthStencilFormat)) + unsigned z_format = static_cast(_PresentParameters.AutoDepthStencilFormat); + if (Find_Z_Mode(DisplayFormat,static_cast(_PresentParameters.BackBufferFormat),&z_format)) + { + _PresentParameters.AutoDepthStencilFormat=Legacy_Format(z_format); + } + else { // If opening 32 bit mode failed, try 16 bit, even if the desktop happens to be 32 bit if (BitDepth==32) { BitDepth=16; - _PresentParameters.BackBufferFormat=D3DFMT_R5G6B5; - if (!Find_Z_Mode(_PresentParameters.BackBufferFormat,_PresentParameters.BackBufferFormat,&_PresentParameters.AutoDepthStencilFormat)) { - _PresentParameters.AutoDepthStencilFormat=D3DFMT_UNKNOWN; + _PresentParameters.BackBufferFormat=Legacy_Format(23); + z_format = static_cast(_PresentParameters.AutoDepthStencilFormat); + if (!Find_Z_Mode(static_cast(_PresentParameters.BackBufferFormat),static_cast(_PresentParameters.BackBufferFormat),&z_format)) { + _PresentParameters.AutoDepthStencilFormat=Legacy_Format(0); + } else { + _PresentParameters.AutoDepthStencilFormat=Legacy_Format(z_format); } } else { - _PresentParameters.AutoDepthStencilFormat=D3DFMT_UNKNOWN; + _PresentParameters.AutoDepthStencilFormat=Legacy_Format(0); } } +#endif } else { /* ** Try to find a mode that matches the user's desired bit-depth. */ - Find_Color_And_Z_Mode(ResolutionWidth,ResolutionHeight,BitDepth,&DisplayFormat, - &_PresentParameters.BackBufferFormat,&_PresentParameters.AutoDepthStencilFormat); + unsigned display_format = DisplayFormat; + unsigned backbuffer_format = static_cast(_PresentParameters.BackBufferFormat); + unsigned depth_format = static_cast(_PresentParameters.AutoDepthStencilFormat); + Find_Color_And_Z_Mode(ResolutionWidth,ResolutionHeight,BitDepth,&display_format, + &backbuffer_format,&depth_format); + DisplayFormat = display_format; + _PresentParameters.BackBufferFormat = Legacy_Format(backbuffer_format); + _PresentParameters.AutoDepthStencilFormat = Legacy_Format(depth_format); } /* ** Set default for depth stencil format if auto Z buffer failed. */ - if (_PresentParameters.AutoDepthStencilFormat==D3DFMT_UNKNOWN) { + if (static_cast(_PresentParameters.AutoDepthStencilFormat)==0) { if (BitDepth==32) { - _PresentParameters.AutoDepthStencilFormat=D3DFMT_D32; + _PresentParameters.AutoDepthStencilFormat=Legacy_Format(71); } else { - _PresentParameters.AutoDepthStencilFormat=D3DFMT_D16; + _PresentParameters.AutoDepthStencilFormat=Legacy_Format(80); } } /* ** Check the devices support for the requested MSAA mode then setup the multi sample type */ - if (MultiSampleAntiAliasing > D3DMULTISAMPLE_NONE) { + if (MultiSampleAntiAliasing > 0) { +#if defined(GGC_RENDER_BACKEND_BGFX) + _PresentParameters.MultiSampleType = Legacy_Multisample_Type(MultiSampleAntiAliasing); +#else HRESULT hrBack = D3DInterface->CheckDeviceMultiSampleType( CurRenderDevice, - D3DDEVTYPE_HAL, + WW3D_DEVTYPE, _PresentParameters.BackBufferFormat, IsWindowed, - MultiSampleAntiAliasing + Legacy_Multisample_Type(MultiSampleAntiAliasing) ); HRESULT hrDepth = D3DInterface->CheckDeviceMultiSampleType( CurRenderDevice, - D3DDEVTYPE_HAL, + WW3D_DEVTYPE, _PresentParameters.AutoDepthStencilFormat, IsWindowed, - MultiSampleAntiAliasing + Legacy_Multisample_Type(MultiSampleAntiAliasing) ); if (FAILED(hrBack) || FAILED(hrDepth)) { // IF we fail then disable MSAA entirely. // External code needs to retrieve the configured MSAA mode after device creation WWDEBUG_SAY(("Requested MSAA Mode Not Supported")); - MultiSampleAntiAliasing = D3DMULTISAMPLE_NONE; + MultiSampleAntiAliasing = 0; } +#endif } - _PresentParameters.MultiSampleType = MultiSampleAntiAliasing; + _PresentParameters.MultiSampleType = Legacy_Multisample_Type(MultiSampleAntiAliasing); /* ** Time to actually create the device. @@ -1098,8 +1562,13 @@ bool DX8Wrapper::Set_Render_Device(int dev, int width, int height, int bits, int StringClass displayFormat; StringClass backbufferFormat; - Get_Format_Name(DisplayFormat,&displayFormat); +#if !defined(GGC_RENDER_BACKEND_BGFX) + Get_Format_Name(Legacy_Format(DisplayFormat),&displayFormat); Get_Format_Name(_PresentParameters.BackBufferFormat,&backbufferFormat); +#else + displayFormat.Format("%u", DisplayFormat); + backbufferFormat.Format("%u", static_cast(_PresentParameters.BackBufferFormat)); +#endif WWDEBUG_SAY(("Using Display/BackBuffer Formats: %s/%s",displayFormat.str(),backbufferFormat.str())); @@ -1120,6 +1589,14 @@ bool DX8Wrapper::Set_Render_Device(int dev, int width, int height, int bits, int Render2DClass::Set_Screen_Resolution( RectClass( 0, 0, ResolutionWidth, ResolutionHeight ) ); } +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @refactor bobtista 08/06/2026 Mirror the final windowed/bit-depth into g_device, + // which owns this state on bgfx builds. This is the single authoritative mutation point: direct + // callers and Registry_Load_Render_Device (which loops through this function) all pass here. + g_device.windowed = IsWindowed; + g_device.bits = BitDepth; +#endif + return ret; } @@ -1179,11 +1656,11 @@ bool DX8Wrapper::Toggle_Windowed() void DX8Wrapper::Set_Swap_Interval(int swap) { switch (swap) { - case 0: _PresentParameters.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; break; - case 1: _PresentParameters.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_ONE ; break; - case 2: _PresentParameters.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_TWO; break; - case 3: _PresentParameters.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_THREE; break; - default: _PresentParameters.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_ONE ; break; + case 0: _PresentParameters.FullScreen_PresentationInterval = 0x80000000L; break; + case 1: _PresentParameters.FullScreen_PresentationInterval = 0x00000001L; break; + case 2: _PresentParameters.FullScreen_PresentationInterval = 0x00000002L; break; + case 3: _PresentParameters.FullScreen_PresentationInterval = 0x00000004L; break; + default: _PresentParameters.FullScreen_PresentationInterval = 0x00000001L; break; } WWDEBUG_SAY(("DX8Wrapper::Set_Swap_Interval is resetting the device.")); @@ -1197,8 +1674,9 @@ int DX8Wrapper::Get_Swap_Interval() bool DX8Wrapper::Has_Stencil() { - bool has_stencil = (_PresentParameters.AutoDepthStencilFormat == D3DFMT_D24S8 || - _PresentParameters.AutoDepthStencilFormat == D3DFMT_D24X4S4); + const unsigned depth_format = static_cast(_PresentParameters.AutoDepthStencilFormat); + bool has_stencil = (depth_format == 75 || + depth_format == 79); return has_stencil; } @@ -1243,7 +1721,11 @@ const char * DX8Wrapper::Get_Render_Device_Name(int device_index) bool DX8Wrapper::Set_Device_Resolution(int width,int height,int bits,int windowed, bool resize_window) { - if (D3DDevice != nullptr) { + if (D3DDevice != nullptr +#if defined(GGC_RENDER_BACKEND_BGFX) + || StandaloneDeviceCreated +#endif + ) { if (width != -1) { _PresentParameters.BackBufferWidth = ResolutionWidth = width; @@ -1277,6 +1759,9 @@ void DX8Wrapper::Get_Render_Target_Resolution(int & set_w,int & set_h,int & set_ { WWASSERT(IsInitted); +#if defined(GGC_RENDER_BACKEND_BGFX) + Get_Device_Resolution (set_w, set_h, set_bits, set_windowed); +#else if (CurrentRenderTarget != nullptr) { D3DSURFACE_DESC info; CurrentRenderTarget->GetDesc (&info); @@ -1289,6 +1774,7 @@ void DX8Wrapper::Get_Render_Target_Resolution(int & set_w,int & set_h,int & set_ } else { Get_Device_Resolution (set_w, set_h, set_bits, set_windowed); } +#endif } bool DX8Wrapper::Registry_Save_Render_Device( const char * sub_key ) @@ -1434,34 +1920,43 @@ bool DX8Wrapper::Registry_Load_Render_Device( const char * sub_key, char *device } -bool DX8Wrapper::Find_Color_And_Z_Mode(int resx,int resy,int bitdepth,D3DFORMAT * set_colorbuffer,D3DFORMAT * set_backbuffer,D3DFORMAT * set_zmode) +bool DX8Wrapper::Find_Color_And_Z_Mode(int resx,int resy,int bitdepth,unsigned * set_colorbuffer,unsigned * set_backbuffer,unsigned * set_zmode) { - static D3DFORMAT _formats16[] = +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)resx; + (void)resy; + const unsigned color_format = bitdepth == 16 ? D3DFMT_R5G6B5 : D3DFMT_A8R8G8B8; + *set_colorbuffer = color_format; + *set_backbuffer = color_format; + *set_zmode = bitdepth == 16 ? D3DFMT_D16 : D3DFMT_D24S8; + return true; +#else + static unsigned _formats16[] = { - D3DFMT_R5G6B5, - D3DFMT_X1R5G5B5, - D3DFMT_A1R5G5B5 + 23, + 24, + 25 }; - static D3DFORMAT _formats32[] = + static unsigned _formats32[] = { - D3DFMT_A8R8G8B8, - D3DFMT_X8R8G8B8, - D3DFMT_R8G8B8, + 21, + 22, + 20, }; /* ** Select the table that we're going to use to search for a valid backbuffer format */ - D3DFORMAT * format_table = nullptr; + unsigned * format_table = nullptr; int format_count = 0; if (BitDepth == 16) { format_table = _formats16; - format_count = sizeof(_formats16) / sizeof(D3DFORMAT); + format_count = sizeof(_formats16) / sizeof(unsigned); } else { format_table = _formats32; - format_count = sizeof(_formats32) / sizeof(D3DFORMAT); + format_count = sizeof(_formats32) / sizeof(unsigned); } /* @@ -1482,40 +1977,48 @@ bool DX8Wrapper::Find_Color_And_Z_Mode(int resx,int resy,int bitdepth,D3DFORMAT *set_backbuffer=*set_colorbuffer = format_table[format_index]; } - if (bitdepth==32 && *set_colorbuffer == D3DFMT_X8R8G8B8 && D3DInterface->CheckDeviceType(0,D3DDEVTYPE_HAL,*set_colorbuffer,D3DFMT_A8R8G8B8, TRUE) == D3D_OK) + if (bitdepth==32 && *set_colorbuffer == 22 && D3DInterface->CheckDeviceType(0,WW3D_DEVTYPE,Legacy_Format(*set_colorbuffer),Legacy_Format(21), TRUE) == S_OK) { //promote 32-bit modes to include destination alpha when supported - *set_backbuffer = D3DFMT_A8R8G8B8; + *set_backbuffer = 21; } /* ** We found a backbuffer format, now find a zbuffer format */ return Find_Z_Mode(*set_colorbuffer,*set_backbuffer, set_zmode); +#endif }; // find the resolution mode with at least resx,resy with the highest supported // refresh rate -bool DX8Wrapper::Find_Color_Mode(D3DFORMAT colorbuffer, int resx, int resy, UINT *mode) +bool DX8Wrapper::Find_Color_Mode(unsigned colorbuffer, int resx, int resy, UINT *mode) { +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)colorbuffer; + (void)resx; + (void)resy; + *mode = 0; + return true; +#else UINT i,j,modemax; UINT rx,ry; D3DDISPLAYMODE dmode; - ::ZeroMemory(&dmode, sizeof(D3DDISPLAYMODE)); + ::ZeroMemory(&dmode, sizeof(dmode)); rx=(unsigned int) resx; ry=(unsigned int) resy; bool found=false; - modemax=D3DInterface->GetAdapterModeCount(D3DADAPTER_DEFAULT); + modemax=D3DInterface->GetAdapterModeCount(0); i=0; while (iEnumAdapterModes(D3DADAPTER_DEFAULT, i, &dmode); - if (dmode.Width==rx && dmode.Height==ry && dmode.Format==colorbuffer) { + D3DInterface->EnumAdapterModes(0, i, &dmode); + if (dmode.Width==rx && dmode.Height==ry && static_cast(dmode.Format)==colorbuffer) { WWDEBUG_SAY(("Found valid color mode. Width = %d Height = %d Format = %d",dmode.Width,dmode.Height,dmode.Format)); found=true; } @@ -1536,8 +2039,8 @@ bool DX8Wrapper::Find_Color_Mode(D3DFORMAT colorbuffer, int resx, int resy, UINT j=i; while (jEnumAdapterModes(D3DADAPTER_DEFAULT, j, &dmode); - if (dmode.Width==rx && dmode.Height==ry && dmode.Format==colorbuffer) + D3DInterface->EnumAdapterModes(0, j, &dmode); + if (dmode.Width==rx && dmode.Height==ry && static_cast(dmode.Format)==colorbuffer) stillok=true; else stillok=false; j++; } @@ -1546,52 +2049,53 @@ bool DX8Wrapper::Find_Color_Mode(D3DFORMAT colorbuffer, int resx, int resy, UINT else *mode=i; return true; +#endif } // Helper function to find a Z buffer mode for the colorbuffer // Will look for greatest Z precision -bool DX8Wrapper::Find_Z_Mode(D3DFORMAT colorbuffer,D3DFORMAT backbuffer, D3DFORMAT *zmode) +bool DX8Wrapper::Find_Z_Mode(unsigned colorbuffer,unsigned backbuffer, unsigned *zmode) { //MW: Swapped the next 2 tests so that Stencil modes get tested first. - if (Test_Z_Mode(colorbuffer,backbuffer,D3DFMT_D24S8)) + if (Test_Z_Mode(colorbuffer,backbuffer,75)) { - *zmode=D3DFMT_D24S8; - WWDEBUG_SAY(("Found zbuffer mode D3DFMT_D24S8")); + *zmode=75; + WWDEBUG_SAY(("Found zbuffer mode 75")); return true; } - if (Test_Z_Mode(colorbuffer,backbuffer,D3DFMT_D32)) + if (Test_Z_Mode(colorbuffer,backbuffer,71)) { - *zmode=D3DFMT_D32; - WWDEBUG_SAY(("Found zbuffer mode D3DFMT_D32")); + *zmode=71; + WWDEBUG_SAY(("Found zbuffer mode 71")); return true; } - if (Test_Z_Mode(colorbuffer,backbuffer,D3DFMT_D24X8)) + if (Test_Z_Mode(colorbuffer,backbuffer,77)) { - *zmode=D3DFMT_D24X8; - WWDEBUG_SAY(("Found zbuffer mode D3DFMT_D24X8")); + *zmode=77; + WWDEBUG_SAY(("Found zbuffer mode 77")); return true; } - if (Test_Z_Mode(colorbuffer,backbuffer,D3DFMT_D24X4S4)) + if (Test_Z_Mode(colorbuffer,backbuffer,79)) { - *zmode=D3DFMT_D24X4S4; - WWDEBUG_SAY(("Found zbuffer mode D3DFMT_D24X4S4")); + *zmode=79; + WWDEBUG_SAY(("Found zbuffer mode 79")); return true; } - if (Test_Z_Mode(colorbuffer,backbuffer,D3DFMT_D16)) + if (Test_Z_Mode(colorbuffer,backbuffer,80)) { - *zmode=D3DFMT_D16; - WWDEBUG_SAY(("Found zbuffer mode D3DFMT_D16")); + *zmode=80; + WWDEBUG_SAY(("Found zbuffer mode 80")); return true; } - if (Test_Z_Mode(colorbuffer,backbuffer,D3DFMT_D15S1)) + if (Test_Z_Mode(colorbuffer,backbuffer,73)) { - *zmode=D3DFMT_D15S1; - WWDEBUG_SAY(("Found zbuffer mode D3DFMT_D15S1")); + *zmode=73; + WWDEBUG_SAY(("Found zbuffer mode 73")); return true; } @@ -1600,24 +2104,31 @@ bool DX8Wrapper::Find_Z_Mode(D3DFORMAT colorbuffer,D3DFORMAT backbuffer, D3DFORM return false; } -bool DX8Wrapper::Test_Z_Mode(D3DFORMAT colorbuffer,D3DFORMAT backbuffer, D3DFORMAT zmode) +bool DX8Wrapper::Test_Z_Mode(unsigned colorbuffer,unsigned backbuffer, unsigned zmode) { +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)colorbuffer; + (void)backbuffer; + (void)zmode; + return true; +#else // See if we have this mode first - if (FAILED(D3DInterface->CheckDeviceFormat(D3DADAPTER_DEFAULT,WW3D_DEVTYPE, - colorbuffer,D3DUSAGE_DEPTHSTENCIL,D3DRTYPE_SURFACE,zmode))) + if (FAILED(D3DInterface->CheckDeviceFormat(0,WW3D_DEVTYPE, + Legacy_Format(colorbuffer),2,Legacy_Resource_Type(1),Legacy_Format(zmode)))) { WWDEBUG_SAY(("CheckDeviceFormat failed. Colorbuffer format = %d Zbufferformat = %d",colorbuffer,zmode)); return false; } // Then see if it matches the color buffer - if(FAILED(D3DInterface->CheckDepthStencilMatch(D3DADAPTER_DEFAULT, WW3D_DEVTYPE, - colorbuffer,backbuffer,zmode))) + if(FAILED(D3DInterface->CheckDepthStencilMatch(0, WW3D_DEVTYPE, + Legacy_Format(colorbuffer),Legacy_Format(backbuffer),Legacy_Format(zmode)))) { WWDEBUG_SAY(("CheckDepthStencilMatch failed. Colorbuffer format = %d Backbuffer format = %d Zbufferformat = %d",colorbuffer,backbuffer,zmode)); return false; } return true; +#endif } @@ -1646,7 +2157,9 @@ unsigned long DX8Wrapper::Get_FrameCount() {return FrameCount;} void DX8_Assert() { +#if !defined(GGC_RENDER_BACKEND_BGFX) WWASSERT(DX8Wrapper::_Get_D3D8()); +#endif DX8_THREAD_ASSERT(); } @@ -1654,6 +2167,7 @@ void DX8Wrapper::Begin_Scene() { DX8_THREAD_ASSERT(); +#if !defined(GGC_RENDER_BACKEND_BGFX) #if ENABLE_EMBEDDED_BROWSER DX8WebBrowser::Update(); #endif @@ -1661,11 +2175,13 @@ void DX8Wrapper::Begin_Scene() DX8CALL(BeginScene()); DX8WebBrowser::Update(); +#endif } void DX8Wrapper::End_Scene(bool flip_frames) { DX8_THREAD_ASSERT(); +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8CALL(EndScene()); DX8WebBrowser::Render(0); @@ -1709,17 +2225,19 @@ void DX8Wrapper::End_Scene(bool flip_frames) DX8_ErrorCode(hr); } } +#endif // Each frame, release all of the buffers and textures. Set_Vertex_Buffer(nullptr); Set_Index_Buffer(nullptr,0); - for (int i=0;iGet_Max_Textures_Per_Pass();++i) Set_Texture(i,nullptr); - Set_Material(nullptr); + for (int i=0;iGet_Max_Textures_Per_Pass();++i) Commit_Fixed_Function_Texture(i,nullptr); + FixedFunctionState::Set_Material(nullptr); } void DX8Wrapper::Flip_To_Primary() { +#if !defined(GGC_RENDER_BACKEND_BGFX) // If we are fullscreen and the current frame is odd then we need // to force a page flip to ensure that the first buffer in the flipping // chain is the one visible. @@ -1766,6 +2284,7 @@ void DX8Wrapper::Flip_To_Primary() --flipCount; } } +#endif } @@ -1778,6 +2297,7 @@ void DX8Wrapper::Clear(bool clear_color, bool clear_z_stencil, const Vector3 &co { DX8_THREAD_ASSERT(); +#if !defined(GGC_RENDER_BACKEND_BGFX) // If we try to clear a stencil buffer which is not there, the entire call will fail // KJM fixed this to get format from back buffer (incase render to texture is used) /*bool has_stencil = ( _PresentParameters.AutoDepthStencilFormat == D3DFMT_D15S1 || @@ -1812,13 +2332,33 @@ void DX8Wrapper::Clear(bool clear_color, bool clear_z_stencil, const Vector3 &co { DX8CALL(Clear(0, nullptr, flags, Convert_Color(color,dest_alpha), z, stencil)); } +#endif } +#if !defined(GGC_RENDER_BACKEND_BGFX) void DX8Wrapper::Set_Viewport(CONST D3DVIEWPORT8* pViewport) { DX8_THREAD_ASSERT(); DX8CALL(SetViewport(pViewport)); + +#if defined(GGC_RENDER_BACKEND_BGFX) + // 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); + } +#endif } +#endif // ---------------------------------------------------------------------------- // @@ -1830,20 +2370,7 @@ void DX8Wrapper::Set_Viewport(CONST D3DVIEWPORT8* pViewport) void DX8Wrapper::Set_Vertex_Buffer(const VertexBufferClass* vb, unsigned stream) { - render_state.vba_offset=0; - render_state.vba_count=0; - if (render_state.vertex_buffers[stream]) { - render_state.vertex_buffers[stream]->Release_Engine_Ref(); - } - REF_PTR_SET(render_state.vertex_buffers[stream],const_cast(vb)); - if (vb) { - vb->Add_Engine_Ref(); - render_state.vertex_buffer_types[stream]=vb->Type(); - } - else { - render_state.vertex_buffer_types[stream]=BUFFER_TYPE_INVALID; - } - render_state_changed|=VERTEX_BUFFER_CHANGED; + FixedFunctionState::Set_Vertex_Buffer(vb, stream); } // ---------------------------------------------------------------------------- @@ -1856,20 +2383,7 @@ void DX8Wrapper::Set_Vertex_Buffer(const VertexBufferClass* vb, unsigned stream) void DX8Wrapper::Set_Index_Buffer(const IndexBufferClass* ib,unsigned short index_base_offset) { - render_state.iba_offset=0; - if (render_state.index_buffer) { - render_state.index_buffer->Release_Engine_Ref(); - } - REF_PTR_SET(render_state.index_buffer,const_cast(ib)); - render_state.index_base_offset=index_base_offset; - if (ib) { - ib->Add_Engine_Ref(); - render_state.index_buffer_type=ib->Type(); - } - else { - render_state.index_buffer_type=BUFFER_TYPE_INVALID; - } - render_state_changed|=INDEX_BUFFER_CHANGED; + FixedFunctionState::Set_Index_Buffer(ib, index_base_offset); } // ---------------------------------------------------------------------------- @@ -1880,20 +2394,7 @@ void DX8Wrapper::Set_Index_Buffer(const IndexBufferClass* ib,unsigned short inde void DX8Wrapper::Set_Vertex_Buffer(const DynamicVBAccessClass& vba_) { - // Release all streams (only one stream allowed in the legacy pipeline) - for (int i=1;iRelease_Engine_Ref(); - DynamicVBAccessClass& vba=const_cast(vba_); - render_state.vertex_buffer_types[0]=vba.Get_Type(); - render_state.vba_offset=vba.VertexBufferOffset; - render_state.vba_count=vba.Get_Vertex_Count(); - REF_PTR_SET(render_state.vertex_buffers[0],vba.VertexBuffer); - render_state.vertex_buffers[0]->Add_Engine_Ref(); - render_state_changed|=VERTEX_BUFFER_CHANGED; - render_state_changed|=INDEX_BUFFER_CHANGED; // vba_offset changes so index buffer needs to be reset as well. + FixedFunctionState::Set_Vertex_Buffer(vba_); } // ---------------------------------------------------------------------------- @@ -1904,15 +2405,7 @@ void DX8Wrapper::Set_Vertex_Buffer(const DynamicVBAccessClass& vba_) void DX8Wrapper::Set_Index_Buffer(const DynamicIBAccessClass& iba_,unsigned short index_base_offset) { - if (render_state.index_buffer) render_state.index_buffer->Release_Engine_Ref(); - - DynamicIBAccessClass& iba=const_cast(iba_); - render_state.index_base_offset=index_base_offset; - render_state.index_buffer_type=iba.Get_Type(); - render_state.iba_offset=iba.IndexBufferOffset; - REF_PTR_SET(render_state.index_buffer,iba.IndexBuffer); - render_state.index_buffer->Add_Engine_Ref(); - render_state_changed|=INDEX_BUFFER_CHANGED; + FixedFunctionState::Set_Index_Buffer(iba_, index_base_offset); } // ---------------------------------------------------------------------------- @@ -1929,16 +2422,16 @@ void DX8Wrapper::Draw_Sorting_IB_VB( unsigned short min_vertex_index, unsigned short vertex_count) { - WWASSERT(render_state.vertex_buffer_types[0]==BUFFER_TYPE_SORTING || render_state.vertex_buffer_types[0]==BUFFER_TYPE_DYNAMIC_SORTING); - WWASSERT(render_state.index_buffer_type==BUFFER_TYPE_SORTING || render_state.index_buffer_type==BUFFER_TYPE_DYNAMIC_SORTING); + WWASSERT(FixedFunctionState::Render_State().vertex_buffer_types[0]==BUFFER_TYPE_SORTING || FixedFunctionState::Render_State().vertex_buffer_types[0]==BUFFER_TYPE_DYNAMIC_SORTING); + WWASSERT(FixedFunctionState::Render_State().index_buffer_type==BUFFER_TYPE_SORTING || FixedFunctionState::Render_State().index_buffer_type==BUFFER_TYPE_DYNAMIC_SORTING); // Fill dynamic vertex buffer with sorting vertex buffer vertices - DynamicVBAccessClass dyn_vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,vertex_count); + DynamicVBAccessClass dyn_vb_access(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,vertex_count); { DynamicVBAccessClass::WriteLockClass lock(&dyn_vb_access); - VertexFormatXYZNDUV2* src = static_cast(render_state.vertex_buffers[0])->VertexBuffer; + VertexFormatXYZNDUV2* src = static_cast(FixedFunctionState::Render_State().vertex_buffers[0])->VertexBuffer; VertexFormatXYZNDUV2* dest= lock.Get_Formatted_Vertex_Array(); - src += render_state.vba_offset + render_state.index_base_offset + min_vertex_index; + src += FixedFunctionState::Render_State().vba_offset + FixedFunctionState::Render_State().index_base_offset + min_vertex_index; unsigned size = dyn_vb_access.FVF_Info().Get_FVF_Size()*vertex_count/sizeof(unsigned); unsigned *dest_u =(unsigned*) dest; unsigned *src_u = (unsigned*) src; @@ -1948,33 +2441,36 @@ void DX8Wrapper::Draw_Sorting_IB_VB( } } +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8CALL(SetStreamSource( 0, - static_cast(dyn_vb_access.VertexBuffer)->Get_DX8_Vertex_Buffer(), + Legacy_Vertex_Buffer(dyn_vb_access.VertexBuffer), dyn_vb_access.FVF_Info().Get_FVF_Size())); - // If using FVF format VB, set the FVF as vertex shader (may not be needed here KM) +#endif +#if !defined(GGC_RENDER_BACKEND_BGFX) unsigned fvf=dyn_vb_access.FVF_Info().Get_FVF(); if (fvf!=0) { DX8CALL(SetVertexShader(fvf)); } +#endif DX8_RECORD_VERTEX_BUFFER_CHANGE(); unsigned index_count=0; switch (primitive_type) { - case D3DPT_TRIANGLELIST: index_count=polygon_count*3; break; - case D3DPT_TRIANGLESTRIP: index_count=polygon_count+2; break; - case D3DPT_TRIANGLEFAN: index_count=polygon_count+2; break; + case 4: index_count=polygon_count*3; break; + case 5: index_count=polygon_count+2; break; + case 6: index_count=polygon_count+2; break; default: WWASSERT(0); break; // Unsupported primitive type } // Fill dynamic index buffer with sorting index buffer vertices - DynamicIBAccessClass dyn_ib_access(BUFFER_TYPE_DYNAMIC_DX8,index_count); + DynamicIBAccessClass dyn_ib_access(BUFFER_TYPE_DYNAMIC,index_count); { DynamicIBAccessClass::WriteLockClass lock(&dyn_ib_access); unsigned short* dest=lock.Get_Index_Array(); unsigned short* src=nullptr; - src=static_cast(render_state.index_buffer)->index_buffer; - src+=render_state.iba_offset+start_index; + src=static_cast(FixedFunctionState::Render_State().index_buffer)->index_buffer; + src+=FixedFunctionState::Render_State().iba_offset+start_index; for (unsigned short i=0;i(dyn_ib_access.IndexBuffer)->Get_DX8_Index_Buffer(), + Legacy_Index_Buffer(dyn_ib_access.IndexBuffer), dyn_vb_access.VertexBufferOffset)); +#endif DX8_RECORD_INDEX_BUFFER_CHANGE(); DX8_RECORD_DRAW_CALLS(); +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8CALL(DrawIndexedPrimitive( - D3DPT_TRIANGLELIST, + static_cast(4), 0, // start vertex vertex_count, dyn_ib_access.IndexBufferOffset, polygon_count)); +#endif - DX8_RECORD_RENDER(polygon_count,vertex_count,render_state.shader); + DX8_RECORD_RENDER(polygon_count,vertex_count,FixedFunctionState::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); + } } // ---------------------------------------------------------------------------- @@ -2018,14 +2532,14 @@ void DX8Wrapper::Draw( DX8_THREAD_ASSERT(); SNAPSHOT_SAY(("DX8 - draw")); - Apply_Render_State_Changes(); + Commit_Deferred_Render_State_Changes(); // Debug feature to disable triangle drawing... if (!_Is_Triangle_Draw_Enabled()) return; -#ifdef MESH_RENDER_SNAPSHOT_ENABLED +#if defined(MESH_RENDER_SNAPSHOT_ENABLED) && !defined(GGC_RENDER_BACKEND_BGFX) if (WW3D::Is_Snapshot_Activated()) { - unsigned long passes=0; + DWORD passes=0; SNAPSHOT_SAY(("ValidateDevice:")); HRESULT res=D3DDevice->ValidateDevice(&passes); switch (res) { @@ -2078,38 +2592,54 @@ void DX8Wrapper::Draw( if (vertex_count<3) { min_vertex_index=0; - switch (render_state.vertex_buffer_types[0]) { - case BUFFER_TYPE_DX8: + switch (FixedFunctionState::Render_State().vertex_buffer_types[0]) { + case BUFFER_TYPE_STATIC: case BUFFER_TYPE_SORTING: - vertex_count=render_state.vertex_buffers[0]->Get_Vertex_Count()-render_state.index_base_offset-render_state.vba_offset-min_vertex_index; + vertex_count=FixedFunctionState::Render_State().vertex_buffers[0]->Get_Vertex_Count()-FixedFunctionState::Render_State().index_base_offset-FixedFunctionState::Render_State().vba_offset-min_vertex_index; break; - case BUFFER_TYPE_DYNAMIC_DX8: + case BUFFER_TYPE_DYNAMIC: case BUFFER_TYPE_DYNAMIC_SORTING: - vertex_count=render_state.vba_count; + vertex_count=FixedFunctionState::Render_State().vba_count; break; } } - switch (render_state.vertex_buffer_types[0]) { - case BUFFER_TYPE_DX8: - case BUFFER_TYPE_DYNAMIC_DX8: - switch (render_state.index_buffer_type) { - case BUFFER_TYPE_DX8: - case BUFFER_TYPE_DYNAMIC_DX8: + if (DrawCallLog_Is_Active()) { + const TextureBaseClass * tex0 = FixedFunctionState::Render_State().Textures[0]; + const char * tex_name = (tex0 != nullptr) ? tex0->Get_Texture_Name().str() : ""; + DrawCallLog_Record( + primitive_type, + polygon_count, + vertex_count, + FixedFunctionState::Render_State().vertex_buffer_types[0], + FixedFunctionState::Render_State().index_buffer_type, + FixedFunctionState::Render_State().shader.Get_Bits(), + FixedFunctionState::Render_State().sorted_draw_flags, + tex_name); + } + + switch (FixedFunctionState::Render_State().vertex_buffer_types[0]) { + case BUFFER_TYPE_STATIC: + case BUFFER_TYPE_DYNAMIC: + switch (FixedFunctionState::Render_State().index_buffer_type) { + case BUFFER_TYPE_STATIC: + case BUFFER_TYPE_DYNAMIC: { -/* if ((start_index+render_state.iba_offset+polygon_count*3) > render_state.index_buffer->Get_Index_Count()) +/* if ((start_index+FixedFunctionState::Render_State().iba_offset+polygon_count*3) > FixedFunctionState::Render_State().index_buffer->Get_Index_Count()) { WWASSERT_PRINT(0,"OVERFLOWING INDEX BUFFER"); ///@todo: MUST FIND OUT WHY THIS HAPPENS WITH LOTS OF PARTICLES ON BIG FIGHT! -MW break; }*/ - DX8_RECORD_RENDER(polygon_count,vertex_count,render_state.shader); + DX8_RECORD_RENDER(polygon_count,vertex_count,FixedFunctionState::Render_State().shader); DX8_RECORD_DRAW_CALLS(); +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8CALL(DrawIndexedPrimitive( (D3DPRIMITIVETYPE)primitive_type, min_vertex_index, vertex_count, - start_index+render_state.iba_offset, + start_index+FixedFunctionState::Render_State().iba_offset, polygon_count)); +#endif } break; case BUFFER_TYPE_SORTING: @@ -2123,9 +2653,9 @@ void DX8Wrapper::Draw( break; case BUFFER_TYPE_SORTING: case BUFFER_TYPE_DYNAMIC_SORTING: - switch (render_state.index_buffer_type) { - case BUFFER_TYPE_DX8: - case BUFFER_TYPE_DYNAMIC_DX8: + switch (FixedFunctionState::Render_State().index_buffer_type) { + case BUFFER_TYPE_STATIC: + case BUFFER_TYPE_DYNAMIC: WWASSERT_PRINT(0,"VB and IB must of same type (sorting or dx8)"); break; case BUFFER_TYPE_SORTING: @@ -2160,7 +2690,7 @@ void DX8Wrapper::Draw_Triangles( SortingRendererClass::Insert_Triangles(start_index,polygon_count,min_vertex_index,vertex_count); } else { - Draw(D3DPT_TRIANGLELIST,start_index,polygon_count,min_vertex_index,vertex_count); + Draw(4,start_index,polygon_count,min_vertex_index,vertex_count); } } @@ -2176,7 +2706,7 @@ void DX8Wrapper::Draw_Triangles( unsigned short min_vertex_index, unsigned short vertex_count) { - Draw(D3DPT_TRIANGLELIST,start_index,polygon_count,min_vertex_index,vertex_count); + Draw(4,start_index,polygon_count,min_vertex_index,vertex_count); } // ---------------------------------------------------------------------------- @@ -2191,7 +2721,7 @@ void DX8Wrapper::Draw_Strip( unsigned short min_vertex_index, unsigned short vertex_count) { - Draw(D3DPT_TRIANGLESTRIP,start_index,polygon_count,min_vertex_index,vertex_count); + Draw(5,start_index,polygon_count,min_vertex_index,vertex_count); } // ---------------------------------------------------------------------------- @@ -2200,27 +2730,27 @@ void DX8Wrapper::Draw_Strip( // // ---------------------------------------------------------------------------- -void DX8Wrapper::Apply_Render_State_Changes() +void DX8Wrapper::Commit_Deferred_Render_State_Changes() { - SNAPSHOT_SAY(("DX8Wrapper::Apply_Render_State_Changes()")); + SNAPSHOT_SAY(("DX8Wrapper::Commit_Deferred_Render_State_Changes()")); - if (!render_state_changed) return; - if (render_state_changed&SHADER_CHANGED) { + if (!FixedFunctionState::Changed_Mask()) return; + if (FixedFunctionState::Changed_Mask()&SHADER_CHANGED) { SNAPSHOT_SAY(("DX8 - apply shader")); - render_state.shader.Apply(); + FixedFunctionState::Render_State().shader.Apply(); } unsigned mask=TEXTURE0_CHANGED; int i=0; for (;iGet_Max_Textures_Per_Pass();++i,mask<<=1) { - if (render_state_changed&mask) + if (FixedFunctionState::Changed_Mask()&mask) { - SNAPSHOT_SAY(("DX8 - apply texture %d (%s)",i,render_state.Textures[i] ? render_state.Textures[i]->Get_Full_Path().str() : "null")); + SNAPSHOT_SAY(("DX8 - apply texture %d (%s)",i,FixedFunctionState::Render_State().Textures[i] ? FixedFunctionState::Render_State().Textures[i]->Get_Full_Path().str() : "null")); - if (render_state.Textures[i]) + if (FixedFunctionState::Render_State().Textures[i]) { - render_state.Textures[i]->Apply(i); + FixedFunctionState::Render_State().Textures[i]->Apply(i); } else { @@ -2229,10 +2759,11 @@ void DX8Wrapper::Apply_Render_State_Changes() } } - if (render_state_changed&MATERIAL_CHANGED) +#if !defined(GGC_RENDER_BACKEND_BGFX) + if (FixedFunctionState::Changed_Mask()&MATERIAL_CHANGED) { SNAPSHOT_SAY(("DX8 - apply material")); - VertexMaterialClass* material=const_cast(render_state.material); + VertexMaterialClass* material=const_cast(FixedFunctionState::Render_State().material); if (material) { material->Apply(); @@ -2240,16 +2771,16 @@ void DX8Wrapper::Apply_Render_State_Changes() else VertexMaterialClass::Apply_Null(); } - if (render_state_changed&LIGHTS_CHANGED) + if (FixedFunctionState::Changed_Mask()&LIGHTS_CHANGED) { unsigned mask=LIGHT0_CHANGED; for (unsigned index=0;index<4;++index,mask<<=1) { - if (render_state_changed&mask) { + if (FixedFunctionState::Changed_Mask()&mask) { SNAPSHOT_SAY(("DX8 - apply light %d",index)); - if (render_state.LightEnable[index]) { + if (FixedFunctionState::Render_State().LightEnable[index]) { #ifdef MESH_RENDER_SNAPSHOT_ENABLED if ( WW3D::Is_Snapshot_Activated() ) { - D3DLIGHT8 * light = &(render_state.Lights[index]); + const LegacyFixedFunctionLight * light = &(FixedFunctionState::Render_State().Lights[index]); static const char * _light_types[] = { "Unknown", "Point","Spot", "Directional" }; WWASSERT((light->Type >= 0) && (light->Type <= 3)); @@ -2264,7 +2795,8 @@ void DX8Wrapper::Apply_Render_State_Changes() } #endif - Set_DX8_Light(index,&render_state.Lights[index]); + D3DLIGHT8 light = To_D3D_Light(FixedFunctionState::Render_State().Lights[index]); + Set_DX8_Light(index,&light); } else { Set_DX8_Light(index,nullptr); @@ -2273,32 +2805,35 @@ void DX8Wrapper::Apply_Render_State_Changes() } } } +#endif - if (render_state_changed&WORLD_CHANGED) { + if (FixedFunctionState::Changed_Mask()&WORLD_CHANGED) { SNAPSHOT_SAY(("DX8 - apply world matrix")); - _Set_DX8_Transform(D3DTS_WORLD,render_state.world); + Commit_Fixed_Function_Transform(256,FixedFunctionState::Render_State().world); } - if (render_state_changed&VIEW_CHANGED) { + if (FixedFunctionState::Changed_Mask()&VIEW_CHANGED) { SNAPSHOT_SAY(("DX8 - apply view matrix")); - _Set_DX8_Transform(D3DTS_VIEW,render_state.view); + Commit_Fixed_Function_Transform(2,FixedFunctionState::Render_State().view); } - if (render_state_changed&VERTEX_BUFFER_CHANGED) { + if (FixedFunctionState::Changed_Mask()&VERTEX_BUFFER_CHANGED) { SNAPSHOT_SAY(("DX8 - apply vb change")); for (i=0;iType()) { - case BUFFER_TYPE_DX8: - case BUFFER_TYPE_DYNAMIC_DX8: + if (FixedFunctionState::Render_State().vertex_buffers[i]) { + switch (FixedFunctionState::Render_State().vertex_buffer_types[i]) {//->Type()) { + case BUFFER_TYPE_STATIC: + case BUFFER_TYPE_DYNAMIC: +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8CALL(SetStreamSource( i, - static_cast(render_state.vertex_buffers[i])->Get_DX8_Vertex_Buffer(), - render_state.vertex_buffers[i]->FVF_Info().Get_FVF_Size())); + Legacy_Vertex_Buffer(FixedFunctionState::Render_State().vertex_buffers[i]), + FixedFunctionState::Render_State().vertex_buffers[i]->FVF_Info().Get_FVF_Size())); +#endif DX8_RECORD_VERTEX_BUFFER_CHANGE(); { // If the VB format is FVF, set the FVF as a vertex shader - unsigned fvf=render_state.vertex_buffers[i]->FVF_Info().Get_FVF(); + unsigned fvf=FixedFunctionState::Render_State().vertex_buffers[i]->FVF_Info().Get_FVF(); if (fvf!=0) { - Set_Vertex_Shader(fvf); + Commit_Vertex_Shader_Value(fvf); } } break; @@ -2309,20 +2844,24 @@ void DX8Wrapper::Apply_Render_State_Changes() WWASSERT(0); } } else { +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8CALL(SetStreamSource(i,nullptr,0)); +#endif DX8_RECORD_VERTEX_BUFFER_CHANGE(); } } } - if (render_state_changed&INDEX_BUFFER_CHANGED) { + if (FixedFunctionState::Changed_Mask()&INDEX_BUFFER_CHANGED) { SNAPSHOT_SAY(("DX8 - apply ib change")); - if (render_state.index_buffer) { - switch (render_state.index_buffer_type) {//->Type()) { - case BUFFER_TYPE_DX8: - case BUFFER_TYPE_DYNAMIC_DX8: + if (FixedFunctionState::Render_State().index_buffer) { + switch (FixedFunctionState::Render_State().index_buffer_type) {//->Type()) { + case BUFFER_TYPE_STATIC: + case BUFFER_TYPE_DYNAMIC: +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8CALL(SetIndices( - static_cast(render_state.index_buffer)->Get_DX8_Index_Buffer(), - render_state.index_base_offset+render_state.vba_offset)); + Legacy_Index_Buffer(FixedFunctionState::Render_State().index_buffer), + FixedFunctionState::Render_State().index_base_offset+FixedFunctionState::Render_State().vba_offset)); +#endif DX8_RECORD_INDEX_BUFFER_CHANGE(); break; case BUFFER_TYPE_SORTING: @@ -2333,18 +2872,28 @@ void DX8Wrapper::Apply_Render_State_Changes() } } else { +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8CALL(SetIndices( nullptr, 0)); +#endif DX8_RECORD_INDEX_BUFFER_CHANGE(); } } - render_state_changed&=((unsigned)WORLD_IDENTITY|(unsigned)VIEW_IDENTITY); + FixedFunctionState::Changed_Mask()&=((unsigned)WORLD_IDENTITY|(unsigned)VIEW_IDENTITY); - SNAPSHOT_SAY(("DX8Wrapper::Apply_Render_State_Changes() - finished")); + SNAPSHOT_SAY(("DX8Wrapper::Commit_Deferred_Render_State_Changes() - finished")); } +#if !defined(GGC_RENDER_BACKEND_BGFX) +void DX8Wrapper::Apply_Render_State_Changes() +{ + Commit_Deferred_Render_State_Changes(); +} +#endif + +#if !defined(GGC_RENDER_BACKEND_BGFX) IDirect3DTexture8 * DX8Wrapper::_Create_DX8_Texture ( unsigned int width, @@ -2466,6 +3015,241 @@ IDirect3DTexture8 * DX8Wrapper::_Create_DX8_Texture return texture; } +#if defined(GGC_RENDER_BACKEND_BGFX) +// TheSuperHackers @refactor bobtista 22/04/2026 In standalone we replace +// D3DXCreateTextureFromFileExA with a direct +// Targa decoder + stub-device CreateTexture + LockRect write. The goal +// is to (a) remove D3DX as a black-box in the standalone pixel path so +// remaining visual bugs don't depend on D3DX internals interacting with +// our stub and (b) start the work of dropping d3dx8.lib from the link. +// The bgfx ownership path is blocked before reaching this helper. +static IDirect3DTexture8 * LoadTextureStandalone_TGA( + const char * filename, + MipCountType mip_level_count) +{ + Targa targa; + if (targa.Open(filename, TGA_READMODE) != 0) + return nullptr; + + // W3D uses Y-flipped TGA (D3D texels top-down). + targa.Header.ImageDescriptor ^= TGAIDF_YORIGIN; + + WW3DFormat src_format = WW3D_FORMAT_UNKNOWN; + unsigned src_bpp = 0; + Get_WW3D_Format(src_format, src_bpp, targa); + if (src_format == WW3D_FORMAT_UNKNOWN) + return nullptr; + + const unsigned src_w = targa.Header.Width; + const unsigned src_h = targa.Header.Height; + if (src_w == 0 || src_h == 0) + return nullptr; + + // Decide destination format: 32-bit TGA gets A8R8G8B8, 24-bit gets X8R8G8B8. + // All other cases up-convert to A8R8G8B8 so the stub scratch layout (width*4) + // matches what EnsureBgfxTexture expects. + const bool has_alpha = (targa.Header.PixelDepth == 32) + || (src_format == WW3D_FORMAT_A8R8G8B8) + || (src_format == WW3D_FORMAT_A4R4G4B4) + || (src_format == WW3D_FORMAT_A1R5G5B5); + const D3DFORMAT d3d_fmt = has_alpha ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8; + + // How many mip levels do we actually produce? If caller asked for + // MIP_LEVELS_1, just one; otherwise full chain down to 1x1. + DWORD levels = 1; + if (mip_level_count != MIP_LEVELS_1) + { + unsigned m = src_w > src_h ? src_w : src_h; + while (m > 1) { m >>= 1; ++levels; } + } + + IDirect3DTexture8 * texture = nullptr; + HRESULT hr = DX8Wrapper::_Get_D3D_Device8()->CreateTexture( + src_w, src_h, levels, 0, d3d_fmt, D3DPOOL_MANAGED, &texture); + if (hr != D3D_OK || texture == nullptr) + return nullptr; + + // Decode file into an internally-allocated buffer owned by targa. + // TGA class flips Y-orientation itself based on ImageDescriptor. + if (targa.Load(filename, TGAF_IMAGE, false) != 0) + { + texture->Release(); + return nullptr; + } + + const uint8_t * src = reinterpret_cast(targa.GetImage()); + if (src == nullptr) + { + texture->Release(); + return nullptr; + } + + D3DLOCKED_RECT locked = { 0 }; + if (FAILED(texture->LockRect(0, &locked, nullptr, 0))) + { + texture->Release(); + return nullptr; + } + + // Convert/copy source pixels into the texture's level-0 scratch. + // Targa memory layout is the same little-endian BGRA byte order as + // D3D8 A8R8G8B8/X8R8G8B8 so we can straight-copy for 32-bit and + // fill alpha=0xFF for 24-bit. + const unsigned dst_pitch = static_cast(locked.Pitch); + uint8_t * dst = static_cast(locked.pBits); + if (src_bpp == 4) + { + for (unsigned y = 0; y < src_h; ++y) + { + std::memcpy(dst + y * dst_pitch, src + y * src_w * 4, src_w * 4); + } + } + else if (src_bpp == 3) + { + for (unsigned y = 0; y < src_h; ++y) + { + const uint8_t * s = src + y * src_w * 3; + uint8_t * d = dst + y * dst_pitch; + for (unsigned x = 0; x < src_w; ++x) + { + d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; d[3] = 0xFF; + s += 3; d += 4; + } + } + } + else + { + // Other bit depths (16-bit, paletted) — reject; D3DX fallback + // will handle these rarer cases. + texture->UnlockRect(0); + texture->Release(); + return nullptr; + } + texture->UnlockRect(0); + + // Generate mip levels via 2x2 box filter (per channel independent). + UINT prev_w = src_w; + UINT prev_h = src_h; + for (DWORD level = 1; level < levels; ++level) + { + UINT lw = prev_w >> 1; if (lw == 0) lw = 1; + UINT lh = prev_h >> 1; if (lh == 0) lh = 1; + + D3DLOCKED_RECT src_l = { 0 }; + D3DLOCKED_RECT dst_l = { 0 }; + if (FAILED(texture->LockRect(level - 1, &src_l, nullptr, 0))) break; + if (FAILED(texture->LockRect(level, &dst_l, nullptr, 0))) + { + texture->UnlockRect(level - 1); + break; + } + + // Clamp the second sample coordinate so 1D parent mips (width==1 + // or height==1) don't read past their row/column. Matches the + // edge-aware box filter in StandaloneLegacyTextureOps.cpp. + const UINT parent_w = prev_w; + const UINT parent_h = prev_h; + const uint8_t * spx = static_cast(src_l.pBits); + uint8_t * dpx = static_cast(dst_l.pBits); + for (UINT y = 0; y < lh; ++y) + { + const UINT y0 = 2 * y; + const UINT y1 = (y0 + 1 < parent_h) ? (y0 + 1) : y0; + for (UINT x = 0; x < lw; ++x) + { + const UINT x0 = 2 * x; + const UINT x1 = (x0 + 1 < parent_w) ? (x0 + 1) : x0; + const uint8_t * p00 = spx + y0 * src_l.Pitch + x0 * 4; + const uint8_t * p10 = spx + y0 * src_l.Pitch + x1 * 4; + const uint8_t * p01 = spx + y1 * src_l.Pitch + x0 * 4; + const uint8_t * p11 = spx + y1 * src_l.Pitch + x1 * 4; + uint8_t * d = dpx + y * dst_l.Pitch + x * 4; + d[0] = static_cast((p00[0] + p10[0] + p01[0] + p11[0] + 2) >> 2); + d[1] = static_cast((p00[1] + p10[1] + p01[1] + p11[1] + 2) >> 2); + d[2] = static_cast((p00[2] + p10[2] + p01[2] + p11[2] + 2) >> 2); + d[3] = static_cast((p00[3] + p10[3] + p01[3] + p11[3] + 2) >> 2); + } + } + texture->UnlockRect(level); + texture->UnlockRect(level - 1); + prev_w = lw; + prev_h = lh; + } + + return texture; +} + +static bool HasTgaExtension(const char * filename) +{ + if (filename == nullptr) return false; + const size_t n = std::strlen(filename); + if (n < 4) return false; + const char * ext = filename + n - 4; + return (ext[0] == '.') && + (ext[1] == 't' || ext[1] == 'T') && + (ext[2] == 'g' || ext[2] == 'G') && + (ext[3] == 'a' || ext[3] == 'A'); +} +#endif // GGC_RENDER_BACKEND_BGFX + +static HRESULT Create_Legacy_Cube_Texture_Compat( + LPDIRECT3DDEVICE8 device, + UINT size, + UINT mip_level_count, + DWORD usage, + D3DFORMAT format, + D3DPOOL pool, + LPDIRECT3DCUBETEXTURE8 *out_texture) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + if (device == nullptr || out_texture == nullptr) + { + return E_POINTER; + } + if (mip_level_count == D3DX_DEFAULT) + { + mip_level_count = 0; + } + if (format == D3DFMT_UNKNOWN) + { + format = D3DFMT_A8R8G8B8; + } + return device->CreateCubeTexture(size, mip_level_count, usage, format, pool, out_texture); +#else + return D3DXCreateCubeTexture(device, size, mip_level_count, usage, format, pool, out_texture); +#endif +} + +static HRESULT Create_Legacy_Volume_Texture_Compat( + LPDIRECT3DDEVICE8 device, + UINT width, + UINT height, + UINT depth, + UINT mip_level_count, + DWORD usage, + D3DFORMAT format, + D3DPOOL pool, + LPDIRECT3DVOLUMETEXTURE8 *out_texture) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + if (device == nullptr || out_texture == nullptr) + { + return E_POINTER; + } + if (mip_level_count == D3DX_DEFAULT) + { + mip_level_count = 0; + } + if (format == D3DFMT_UNKNOWN) + { + format = D3DFMT_A8R8G8B8; + } + return device->CreateVolumeTexture(width, height, depth, mip_level_count, usage, format, pool, out_texture); +#else + return D3DXCreateVolumeTexture(device, width, height, depth, mip_level_count, usage, format, pool, out_texture); +#endif +} + IDirect3DTexture8 * DX8Wrapper::_Create_DX8_Texture ( const char *filename, @@ -2476,6 +3260,34 @@ IDirect3DTexture8 * DX8Wrapper::_Create_DX8_Texture DX8_Assert(); IDirect3DTexture8 *texture = nullptr; +#if defined(GGC_RENDER_BACKEND_BGFX) + // Bypass D3DX for TGA files in standalone. The D3DX upload path + // occasionally produced bad pixel data against our stub device + // (unknown internal cause) which showed as dark bands / black + // regions on terrain. Direct TGA -> stub LockRect is deterministic. + if (HasTgaExtension(filename)) + { + texture = LoadTextureStandalone_TGA(filename, mip_level_count); + if (texture != nullptr) + { + D3DSURFACE_DESC desc; + texture->GetLevelDesc(0, &desc); + if (desc.Format == D3DFMT_P8) { + Log_Missing_Texture_File("paletted TGA", filename); + texture->Release(); + return Get_Legacy_Missing_Texture(); + } + return texture; + } + } + + WWASSERT_PRINT( + false, + "DX8Wrapper::_Create_DX8_Texture(file): standalone bgfx legacy texture path cannot load this file; no D3DX fallback is available"); + Log_Missing_Texture_File("standalone legacy texture loader", filename); + return Get_Legacy_Missing_Texture(); +#else + // NOTE: If the original image format is not supported as a texture format, it will // automatically be converted to an appropriate format. // NOTE: It is possible to get the size and format of the original image file from this @@ -2498,17 +3310,20 @@ IDirect3DTexture8 * DX8Wrapper::_Create_DX8_Texture &texture); if (result != D3D_OK) { - return MissingTexture::_Get_Missing_Texture(); + Log_Missing_Texture_File("D3DX fallback", filename); + return Get_Legacy_Missing_Texture(); } // Make sure texture wasn't paletted! D3DSURFACE_DESC desc; texture->GetLevelDesc(0,&desc); if (desc.Format==D3DFMT_P8) { + Log_Missing_Texture_File("paletted D3DX", filename); texture->Release(); - return MissingTexture::_Get_Missing_Texture(); + return Get_Legacy_Missing_Texture(); } return texture; +#endif } IDirect3DTexture8 * DX8Wrapper::_Create_DX8_Texture @@ -2533,13 +3348,13 @@ IDirect3DTexture8 * DX8Wrapper::_Create_DX8_Texture // Copy the surface to the texture IDirect3DSurface8 *tex_surface = nullptr; texture->GetSurfaceLevel(0, &tex_surface); - DX8_ErrorCode(D3DXLoadSurfaceFromSurface(tex_surface, nullptr, nullptr, surface, nullptr, nullptr, D3DX_FILTER_BOX, 0)); + DX8_ErrorCode(Copy_Legacy_Surface_Compat(tex_surface, nullptr, surface, nullptr, D3DX_FILTER_BOX)); tex_surface->Release(); // Create mipmaps if needed if (mip_level_count!=MIP_LEVELS_1) { - DX8_ErrorCode(D3DXFilterTexture(texture, nullptr, 0, D3DX_FILTER_BOX)); + DX8_ErrorCode(Filter_Legacy_Texture_Mips_Compat(texture, 0)); } return texture; @@ -2645,6 +3460,16 @@ IDirect3DCubeTexture8* DX8Wrapper::_Create_DX8_Cube_Texture DX8_Assert(); IDirect3DCubeTexture8* texture=nullptr; +#if defined(GGC_RENDER_BACKEND_BGFX) + if (Blocks_Legacy_Texture_Create()) + { + WWASSERT_PRINT( + false, + "DX8Wrapper::_Create_DX8_Cube_Texture: BGFX texture ownership is enabled; no fake-D3D cube texture fallback is allowed"); + return nullptr; + } +#endif + // Paletted textures not supported! WWASSERT(format!=D3DFMT_P8); @@ -2655,8 +3480,7 @@ IDirect3DCubeTexture8* DX8Wrapper::_Create_DX8_Cube_Texture // which case we return null. if (rendertarget) { - unsigned ret=D3DXCreateCubeTexture - ( + unsigned ret=Create_Legacy_Cube_Texture_Compat( DX8Wrapper::_Get_D3D_Device8(), width, mip_level_count, @@ -2682,8 +3506,7 @@ IDirect3DCubeTexture8* DX8Wrapper::_Create_DX8_Cube_Texture // Invalidate the mesh cache WW3D::_Invalidate_Mesh_Cache(); - ret=D3DXCreateCubeTexture - ( + ret=Create_Legacy_Cube_Texture_Compat( DX8Wrapper::_Get_D3D_Device8(), width, mip_level_count, @@ -2717,8 +3540,7 @@ IDirect3DCubeTexture8* DX8Wrapper::_Create_DX8_Cube_Texture // We should never run out of video memory when allocating a non-rendertarget texture. // However, it seems to happen sometimes when there are a lot of textures in memory and so // if it happens we'll release assets and try again (anything is better than crashing). - unsigned ret=D3DXCreateCubeTexture - ( + unsigned ret=Create_Legacy_Cube_Texture_Compat( DX8Wrapper::_Get_D3D_Device8(), width, mip_level_count, @@ -2738,8 +3560,7 @@ IDirect3DCubeTexture8* DX8Wrapper::_Create_DX8_Cube_Texture // Invalidate the mesh cache WW3D::_Invalidate_Mesh_Cache(); - ret=D3DXCreateCubeTexture - ( + ret=Create_Legacy_Cube_Texture_Compat( DX8Wrapper::_Get_D3D_Device8(), width, mip_level_count, @@ -2782,6 +3603,16 @@ IDirect3DVolumeTexture8* DX8Wrapper::_Create_DX8_Volume_Texture DX8_Assert(); IDirect3DVolumeTexture8* texture=nullptr; +#if defined(GGC_RENDER_BACKEND_BGFX) + if (Blocks_Legacy_Texture_Create()) + { + WWASSERT_PRINT( + false, + "DX8Wrapper::_Create_DX8_Volume_Texture: BGFX texture ownership is enabled; no fake-D3D volume texture fallback is allowed"); + return nullptr; + } +#endif + // Paletted textures not supported! WWASSERT(format!=D3DFMT_P8); @@ -2792,8 +3623,7 @@ IDirect3DVolumeTexture8* DX8Wrapper::_Create_DX8_Volume_Texture // We should never run out of video memory when allocating a non-rendertarget texture. // However, it seems to happen sometimes when there are a lot of textures in memory and so // if it happens we'll release assets and try again (anything is better than crashing). - unsigned ret=D3DXCreateVolumeTexture - ( + unsigned ret=Create_Legacy_Volume_Texture_Compat( DX8Wrapper::_Get_D3D_Device8(), width, height, @@ -2815,8 +3645,7 @@ IDirect3DVolumeTexture8* DX8Wrapper::_Create_DX8_Volume_Texture // Invalidate the mesh cache WW3D::_Invalidate_Mesh_Cache(); - ret=D3DXCreateVolumeTexture - ( + ret=Create_Legacy_Volume_Texture_Compat( DX8Wrapper::_Get_D3D_Device8(), width, height, @@ -2896,18 +3725,21 @@ IDirect3DSurface8 * DX8Wrapper::_Create_DX8_Surface(const char *filename_) ext[3]='s'; } file_auto_ptr myfile2(_TheFileFactory,compressed_name); - if (!myfile2->Is_Available()) - return MissingTexture::_Create_Missing_Surface(); + if (!myfile2->Is_Available()) { + Log_Missing_Texture_File("surface file", filename_); + return Create_Legacy_Missing_Surface(); + } } } StringClass filename_string(filename_,true); - surface=TextureLoader::Load_Surface_Immediate( + surface=Load_Legacy_Surface_Immediate( filename_string, WW3D_FORMAT_UNKNOWN, true); return surface; } +#endif /*********************************************************************************************** @@ -2925,34 +3757,42 @@ IDirect3DSurface8 * DX8Wrapper::_Create_DX8_Surface(const char *filename_) * HISTORY: * * 4/26/2001 hy : Created. * *=============================================================================================*/ +#if !defined(GGC_RENDER_BACKEND_BGFX) void DX8Wrapper::_Update_Texture(TextureClass *system, TextureClass *video) { WWASSERT(system); WWASSERT(video); WWASSERT(system->Get_Pool()==TextureClass::POOL_SYSTEMMEM); WWASSERT(video->Get_Pool()==TextureClass::POOL_DEFAULT); - DX8CALL(UpdateTexture(system->Peek_D3D_Base_Texture(),video->Peek_D3D_Base_Texture())); + DX8CALL(UpdateTexture(Peek_Legacy_Base_Texture(*system),Peek_Legacy_Base_Texture(*video))); } +#endif void DX8Wrapper::Compute_Caps(WW3DFormat display_format) { DX8_THREAD_ASSERT(); DX8_Assert(); delete CurrentCaps; - CurrentCaps=new DX8Caps(_Get_D3D8(),D3DDevice,display_format,Get_Current_Adapter_Identifier()); +#if defined(GGC_RENDER_BACKEND_BGFX) + D3DCAPS8 caps; + Fill_Standalone_DX8_Caps(caps); + CurrentCaps=new DX8Caps(nullptr,static_cast(&caps),display_format,&CurrentAdapterIdentifier); +#else + CurrentCaps=new DX8Caps(D3DInterface,D3DDevice,display_format,&CurrentAdapterIdentifier); +#endif } - +#if !defined(GGC_RENDER_BACKEND_BGFX) void DX8Wrapper::Set_Light(unsigned index, const D3DLIGHT8* light) { if (light) { - render_state.Lights[index]=*light; - render_state.LightEnable[index]=true; + FixedFunctionState::Render_State().Lights[index]=To_Legacy_Light(*light); + FixedFunctionState::Render_State().LightEnable[index]=true; } else { - render_state.LightEnable[index]=false; + FixedFunctionState::Render_State().LightEnable[index]=false; } - render_state_changed|=(LIGHT0_CHANGED<Get_Equivalent_Ambient(); + std::fprintf(stderr, "LIGHT_ENV: eqAmb=[%.3f %.3f %.3f] lights=%d\n", + eqa.X, eqa.Y, eqa.Z, light_env->Get_Light_Count()); + } + } int light_count = light_env->Get_Light_Count(); unsigned int color=Convert_Color(light_env->Get_Equivalent_Ambient(),0.0f); - if (RenderStates[D3DRS_AMBIENT]!=color) + if (FixedFunctionState::Cached_Render_State(RS::AMBIENT)!=color) { - Set_DX8_Render_State(D3DRS_AMBIENT,color); + Commit_Fixed_Function_Render_Value(RS::AMBIENT,color); //buggy Radeon 9700 driver doesn't apply new ambient unless the material also changes. #if 1 - render_state_changed|=MATERIAL_CHANGED; + FixedFunctionState::Changed_Mask()|=MATERIAL_CHANGED; #endif } @@ -3109,7 +3960,9 @@ void DX8Wrapper::Set_Light_Environment(LightEnvironmentClass* light_env) } */ } +#endif +#if !defined(GGC_RENDER_BACKEND_BGFX) IDirect3DSurface8 * DX8Wrapper::_Get_DX8_Front_Buffer() { DX8_THREAD_ASSERT(); @@ -3134,7 +3987,7 @@ SurfaceClass * DX8Wrapper::_Get_DX8_Back_Buffer(unsigned int num) DX8CALL(GetBackBuffer(num,D3DBACKBUFFER_TYPE_MONO,&bb)); if (bb) { - surf=NEW_REF(SurfaceClass,(bb)); + surf=Create_Legacy_Surface_Wrapper(bb); bb->Release(); } @@ -3165,18 +4018,18 @@ DX8Wrapper::Create_Render_Target (int width, int height, WW3DFormat format) // // Note: We're going to force the width and height to be powers of two and equal // - const D3DCAPS8& dx8caps=Get_Current_Caps()->Get_DX8_Caps(); + const DX8Caps* dx8caps=Get_Current_Caps(); float poweroftwosize = width; if (height > 0 && height < width) { poweroftwosize = height; } poweroftwosize = ::Find_POT (poweroftwosize); - if (poweroftwosize>dx8caps.MaxTextureWidth) { - poweroftwosize=dx8caps.MaxTextureWidth; + if (poweroftwosize>dx8caps->Get_Max_Texture_Width()) { + poweroftwosize=dx8caps->Get_Max_Texture_Width(); } - if (poweroftwosize>dx8caps.MaxTextureHeight) { - poweroftwosize=dx8caps.MaxTextureHeight; + if (poweroftwosize>dx8caps->Get_Max_Texture_Height()) { + poweroftwosize=dx8caps->Get_Max_Texture_Height(); } width = height = poweroftwosize; @@ -3188,7 +4041,7 @@ DX8Wrapper::Create_Render_Target (int width, int height, WW3DFormat format) // 3dfx drivers are lying in the CheckDeviceFormat call and claiming // that they support render targets! - if (tex->Peek_D3D_Base_Texture() == nullptr) + if (Peek_Legacy_Base_Texture(*tex) == nullptr) { WWDEBUG_SAY(("DX8Wrapper - Render target creation failed!")); REF_PTR_RELEASE(tex); @@ -3235,7 +4088,7 @@ void DX8Wrapper::Create_Render_Target } // Note: We're going to force the width and height to be powers of two and equal - const D3DCAPS8& dx8caps=Get_Current_Caps()->Get_DX8_Caps(); + const DX8Caps* dx8caps=Get_Current_Caps(); float poweroftwosize = width; if (height > 0 && height < width) { @@ -3243,14 +4096,14 @@ void DX8Wrapper::Create_Render_Target } poweroftwosize = ::Find_POT (poweroftwosize); - if (poweroftwosize>dx8caps.MaxTextureWidth) + if (poweroftwosize>dx8caps->Get_Max_Texture_Width()) { - poweroftwosize=dx8caps.MaxTextureWidth; + poweroftwosize=dx8caps->Get_Max_Texture_Width(); } - if (poweroftwosize>dx8caps.MaxTextureHeight) + if (poweroftwosize>dx8caps->Get_Max_Texture_Height()) { - poweroftwosize=dx8caps.MaxTextureHeight; + poweroftwosize=dx8caps->Get_Max_Texture_Height(); } width = height = poweroftwosize; @@ -3260,7 +4113,7 @@ void DX8Wrapper::Create_Render_Target // 3dfx drivers are lying in the CheckDeviceFormat call and claiming // that they support render targets! - if (tex->Peek_D3D_Base_Texture() == nullptr) + if (Peek_Legacy_Base_Texture(*tex) == nullptr) { WWDEBUG_SAY(("DX8Wrapper - Render target creation failed!")); REF_PTR_RELEASE(tex); @@ -3293,14 +4146,14 @@ void DX8Wrapper::Set_Render_Target_With_Z ) { WWASSERT(texture!=nullptr); - IDirect3DSurface8 * d3d_surf = texture->Get_D3D_Surface_Level(); + IDirect3DSurface8 * d3d_surf = Get_Native_Compatibility_Surface_Level(*texture); WWASSERT(d3d_surf != nullptr); IDirect3DSurface8* d3d_zbuf=nullptr; if (ztexture!=nullptr) { - d3d_zbuf=ztexture->Get_D3D_Surface_Level(); + d3d_zbuf=Get_Native_Compatibility_Surface_Level(*ztexture); WWASSERT(d3d_zbuf!=nullptr); Set_Render_Target(d3d_surf,d3d_zbuf); d3d_zbuf->Release(); @@ -3573,35 +4426,6 @@ void DX8Wrapper::Set_Render_Target } -IDirect3DSwapChain8 * -DX8Wrapper::Create_Additional_Swap_Chain (HWND render_window) -{ - DX8_Assert(); - - // - // Configure the presentation parameters for a windowed render target - // - D3DPRESENT_PARAMETERS params = { 0 }; - params.BackBufferFormat = _PresentParameters.BackBufferFormat; - params.BackBufferCount = 1; - params.MultiSampleType = D3DMULTISAMPLE_NONE; - params.SwapEffect = D3DSWAPEFFECT_COPY_VSYNC; - params.hDeviceWindow = render_window; - params.Windowed = TRUE; - params.EnableAutoDepthStencil = TRUE; - params.AutoDepthStencilFormat = _PresentParameters.AutoDepthStencilFormat; - params.Flags = 0; - params.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; - params.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; - - // - // Create the swap chain - // - IDirect3DSwapChain8 *swap_chain = nullptr; - DX8CALL(CreateAdditionalSwapChain(¶ms, &swap_chain)); - return swap_chain; -} - void DX8Wrapper::Flush_DX8_Resource_Manager(unsigned int bytes) { DX8_Assert(); @@ -3614,6 +4438,7 @@ unsigned int DX8Wrapper::Get_Free_Texture_RAM() DX8_RECORD_DX8_CALLS(); return DX8Wrapper::_Get_D3D_Device8()->GetAvailableTextureMem(); } +#endif // Converts a linear gamma ramp to one that is controlled by: // Gamma - controls the curvature of the middle of the curve @@ -3621,6 +4446,7 @@ unsigned int DX8Wrapper::Get_Free_Texture_RAM() // Contrast - controls the difference between the maximum and the minimum of the curve void DX8Wrapper::Set_Gamma(float gamma,float bright,float contrast,bool calibrate,bool uselimit) { +#if !defined(GGC_RENDER_BACKEND_BGFX) gamma=Bound(gamma,0.6f,6.0f); bright=Bound(bright,-0.5f,0.5f); contrast=Bound(contrast,0.5f,2.0f); @@ -3666,34 +4492,17 @@ void DX8Wrapper::Set_Gamma(float gamma,float bright,float contrast,bool calibrat ReleaseDC (hwnd, hdc); } } +#endif } -namespace wrapper -{ -void D3DMatrixIdentity(D3DMATRIX* dxm) -{ - memset(dxm, 0, sizeof(*dxm)); - dxm->_11 = 1.0f; - dxm->_22 = 1.0f; - dxm->_33 = 1.0f; - dxm->_44 = 1.0f; -} -} // namespace wrapper - void DX8Wrapper::Set_World_Identity() { - if (render_state_changed&(unsigned)WORLD_IDENTITY) - return; - wrapper::D3DMatrixIdentity(&render_state.world); - render_state_changed|=(unsigned)WORLD_CHANGED|(unsigned)WORLD_IDENTITY; + FixedFunctionState::Set_World_Identity(); } void DX8Wrapper::Set_View_Identity() { - if (render_state_changed&(unsigned)VIEW_IDENTITY) - return; - wrapper::D3DMatrixIdentity(&render_state.view); - render_state_changed|=(unsigned)VIEW_CHANGED|(unsigned)VIEW_IDENTITY; + FixedFunctionState::Set_View_Identity(); } //********************************************************************************************** @@ -3705,148 +4514,84 @@ void DX8Wrapper::Apply_Default_State() SNAPSHOT_SAY(("DX8Wrapper::Apply_Default_State()")); // only set states used in game - Set_DX8_Render_State(D3DRS_ZENABLE, TRUE); -// Set_DX8_Render_State(D3DRS_FILLMODE, D3DFILL_SOLID); - Set_DX8_Render_State(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); - //Set_DX8_Render_State(D3DRS_LINEPATTERN, 0); - Set_DX8_Render_State(D3DRS_ZWRITEENABLE, TRUE); - Set_DX8_Render_State(D3DRS_ALPHATESTENABLE, FALSE); - //Set_DX8_Render_State(D3DRS_LASTPIXEL, FALSE); - Set_DX8_Render_State(D3DRS_SRCBLEND, D3DBLEND_ONE); - Set_DX8_Render_State(D3DRS_DESTBLEND, D3DBLEND_ZERO); - Set_DX8_Render_State(D3DRS_CULLMODE, D3DCULL_CW); - Set_DX8_Render_State(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); - Set_DX8_Render_State(D3DRS_ALPHAREF, 0); - Set_DX8_Render_State(D3DRS_ALPHAFUNC, D3DCMP_LESSEQUAL); - Set_DX8_Render_State(D3DRS_DITHERENABLE, FALSE); - Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE, FALSE); - Set_DX8_Render_State(D3DRS_FOGENABLE, FALSE); - Set_DX8_Render_State(D3DRS_SPECULARENABLE, FALSE); -// Set_DX8_Render_State(D3DRS_ZVISIBLE, FALSE); -// Set_DX8_Render_State(D3DRS_FOGCOLOR, 0); -// Set_DX8_Render_State(D3DRS_FOGTABLEMODE, D3DFOG_NONE); -// Set_DX8_Render_State(D3DRS_FOGSTART, 0); - -// Set_DX8_Render_State(D3DRS_FOGEND, WWMath::Float_As_Int(1.0f)); -// Set_DX8_Render_State(D3DRS_FOGDENSITY, WWMath::Float_As_Int(1.0f)); - - //Set_DX8_Render_State(D3DRS_EDGEANTIALIAS, FALSE); - Set_DX8_Render_State(D3DRS_ZBIAS, 0); -// Set_DX8_Render_State(D3DRS_RANGEFOGENABLE, FALSE); - Set_DX8_Render_State(D3DRS_STENCILENABLE, FALSE); - Set_DX8_Render_State(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP); - Set_DX8_Render_State(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP); - Set_DX8_Render_State(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP); - Set_DX8_Render_State(D3DRS_STENCILFUNC, D3DCMP_ALWAYS); - Set_DX8_Render_State(D3DRS_STENCILREF, 0); - Set_DX8_Render_State(D3DRS_STENCILMASK, 0xffffffff); - Set_DX8_Render_State(D3DRS_STENCILWRITEMASK, 0xffffffff); - Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, 0); -/* Set_DX8_Render_State(D3DRS_WRAP0, D3DWRAP_U| D3DWRAP_V); - Set_DX8_Render_State(D3DRS_WRAP1, D3DWRAP_U| D3DWRAP_V); - Set_DX8_Render_State(D3DRS_WRAP2, D3DWRAP_U| D3DWRAP_V); - Set_DX8_Render_State(D3DRS_WRAP3, D3DWRAP_U| D3DWRAP_V); - Set_DX8_Render_State(D3DRS_WRAP4, D3DWRAP_U| D3DWRAP_V); - Set_DX8_Render_State(D3DRS_WRAP5, D3DWRAP_U| D3DWRAP_V); - Set_DX8_Render_State(D3DRS_WRAP6, D3DWRAP_U| D3DWRAP_V); - Set_DX8_Render_State(D3DRS_WRAP7, D3DWRAP_U| D3DWRAP_V);*/ - Set_DX8_Render_State(D3DRS_CLIPPING, TRUE); - Set_DX8_Render_State(D3DRS_LIGHTING, FALSE); - //Set_DX8_Render_State(D3DRS_AMBIENT, 0); -// Set_DX8_Render_State(D3DRS_FOGVERTEXMODE, D3DFOG_NONE); - Set_DX8_Render_State(D3DRS_COLORVERTEX, TRUE); -/* Set_DX8_Render_State(D3DRS_LOCALVIEWER, TRUE); - Set_DX8_Render_State(D3DRS_NORMALIZENORMALS, FALSE); - Set_DX8_Render_State(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1); - Set_DX8_Render_State(D3DRS_SPECULARMATERIALSOURCE, D3DMCS_COLOR2); - Set_DX8_Render_State(D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL); - Set_DX8_Render_State(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL); - Set_DX8_Render_State(D3DRS_VERTEXBLEND, D3DVBF_DISABLE);*/ - //Set_DX8_Render_State(D3DRS_CLIPPLANEENABLE, 0); - Set_DX8_Render_State(D3DRS_SOFTWAREVERTEXPROCESSING, FALSE); - //Set_DX8_Render_State(D3DRS_POINTSIZE, 0x3f800000); - //Set_DX8_Render_State(D3DRS_POINTSIZE_MIN, 0); - //Set_DX8_Render_State(D3DRS_POINTSPRITEENABLE, FALSE); - //Set_DX8_Render_State(D3DRS_POINTSCALEENABLE, FALSE); - //Set_DX8_Render_State(D3DRS_POINTSCALE_A, 0); - //Set_DX8_Render_State(D3DRS_POINTSCALE_B, 0); - //Set_DX8_Render_State(D3DRS_POINTSCALE_C, 0); - //Set_DX8_Render_State(D3DRS_MULTISAMPLEANTIALIAS, TRUE); - //Set_DX8_Render_State(D3DRS_MULTISAMPLEMASK, 0xffffffff); - //Set_DX8_Render_State(D3DRS_PATCHEDGESTYLE, D3DPATCHEDGE_DISCRETE); - //Set_DX8_Render_State(D3DRS_PATCHSEGMENTS, 0x3f800000); - //Set_DX8_Render_State(D3DRS_DEBUGMONITORTOKEN, D3DDMT_ENABLE); - //Set_DX8_Render_State(D3DRS_POINTSIZE_MAX, Float_At_Int(64.0f)); - //Set_DX8_Render_State(D3DRS_INDEXEDVERTEXBLENDENABLE, FALSE); - Set_DX8_Render_State(D3DRS_COLORWRITEENABLE, 0x0000000f); - //Set_DX8_Render_State(D3DRS_TWEENFACTOR, 0); - Set_DX8_Render_State(D3DRS_BLENDOP, D3DBLENDOP_ADD); - //Set_DX8_Render_State(D3DRS_POSITIONORDER, D3DORDER_CUBIC); - //Set_DX8_Render_State(D3DRS_NORMALORDER, D3DORDER_LINEAR); + Commit_Fixed_Function_Render_Value(7 /* D3DRS_ZENABLE */, TRUE); + Commit_Fixed_Function_Render_Value(9 /* D3DRS_SHADEMODE */, 2); + Commit_Fixed_Function_Render_Value(14 /* D3DRS_ZWRITEENABLE */, TRUE); + Commit_Fixed_Function_Render_Value(15 /* D3DRS_ALPHATESTENABLE */, FALSE); + Commit_Fixed_Function_Render_Value(19 /* D3DRS_SRCBLEND */, 2); + Commit_Fixed_Function_Render_Value(20 /* D3DRS_DESTBLEND */, 1); + Commit_Fixed_Function_Render_Value(22 /* D3DRS_CULLMODE */, 2); + Commit_Fixed_Function_Render_Value(23 /* D3DRS_ZFUNC */, 4); + Commit_Fixed_Function_Render_Value(24 /* D3DRS_ALPHAREF */, 0); + Commit_Fixed_Function_Render_Value(25 /* D3DRS_ALPHAFUNC */, 4); + Commit_Fixed_Function_Render_Value(26 /* D3DRS_DITHERENABLE */, FALSE); + Commit_Fixed_Function_Render_Value(27 /* D3DRS_ALPHABLENDENABLE */, FALSE); + Commit_Fixed_Function_Render_Value(28 /* D3DRS_FOGENABLE */, FALSE); + Commit_Fixed_Function_Render_Value(29 /* D3DRS_SPECULARENABLE */, FALSE); + Commit_Fixed_Function_Render_Value(47 /* D3DRS_ZBIAS */, 0); + Commit_Fixed_Function_Render_Value(52 /* D3DRS_STENCILENABLE */, FALSE); + Commit_Fixed_Function_Render_Value(53 /* D3DRS_STENCILFAIL */, 1); + Commit_Fixed_Function_Render_Value(54 /* D3DRS_STENCILZFAIL */, 1); + Commit_Fixed_Function_Render_Value(55 /* D3DRS_STENCILPASS */, 1); + Commit_Fixed_Function_Render_Value(56 /* D3DRS_STENCILFUNC */, 8); + Commit_Fixed_Function_Render_Value(57 /* D3DRS_STENCILREF */, 0); + Commit_Fixed_Function_Render_Value(58 /* D3DRS_STENCILMASK */, 0xffffffff); + Commit_Fixed_Function_Render_Value(59 /* D3DRS_STENCILWRITEMASK */, 0xffffffff); + Commit_Fixed_Function_Render_Value(60 /* D3DRS_TEXTUREFACTOR */, 0); + Commit_Fixed_Function_Render_Value(136 /* D3DRS_CLIPPING */, TRUE); + Commit_Fixed_Function_Render_Value(137 /* D3DRS_LIGHTING */, FALSE); + Commit_Fixed_Function_Render_Value(141 /* D3DRS_COLORVERTEX */, TRUE); + Commit_Fixed_Function_Render_Value(153 /* D3DRS_SOFTWAREVERTEXPROCESSING */, FALSE); + Commit_Fixed_Function_Render_Value(168 /* D3DRS_COLORWRITEENABLE */, 0x0000000f); + Commit_Fixed_Function_Render_Value(171 /* D3DRS_BLENDOP */, 1); // disable TSS stages int i; for (i=0; iGet_Max_Textures_Per_Pass(); i++) { - Set_DX8_Texture_Stage_State(i, D3DTSS_COLOROP, D3DTOP_DISABLE); - Set_DX8_Texture_Stage_State(i, D3DTSS_COLORARG1, D3DTA_TEXTURE); - Set_DX8_Texture_Stage_State(i, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - - Set_DX8_Texture_Stage_State(i, D3DTSS_ALPHAOP, D3DTOP_DISABLE); - Set_DX8_Texture_Stage_State(i, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); - Set_DX8_Texture_Stage_State(i, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); - - /*Set_DX8_Texture_Stage_State(i, D3DTSS_BUMPENVMAT00, 0); - Set_DX8_Texture_Stage_State(i, D3DTSS_BUMPENVMAT01, 0); - Set_DX8_Texture_Stage_State(i, D3DTSS_BUMPENVMAT10, 0); - Set_DX8_Texture_Stage_State(i, D3DTSS_BUMPENVMAT11, 0); - Set_DX8_Texture_Stage_State(i, D3DTSS_BUMPENVLSCALE, 0); - Set_DX8_Texture_Stage_State(i, D3DTSS_BUMPENVLOFFSET, 0);*/ - - Set_DX8_Texture_Stage_State(i, D3DTSS_TEXCOORDINDEX, i); - - - Set_DX8_Texture_Stage_State(i, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - Set_DX8_Texture_Stage_State(i, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); - Set_DX8_Texture_Stage_State(i, D3DTSS_BORDERCOLOR, 0); -// Set_DX8_Texture_Stage_State(i, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); -// Set_DX8_Texture_Stage_State(i, D3DTSS_MINFILTER, D3DTEXF_LINEAR); -// Set_DX8_Texture_Stage_State(i, D3DTSS_MIPFILTER, D3DTEXF_LINEAR); -// Set_DX8_Texture_Stage_State(i, D3DTSS_MIPMAPLODBIAS, 0); -// Set_DX8_Texture_Stage_State(i, D3DTSS_MAXMIPLEVEL, 0); -// Set_DX8_Texture_Stage_State(i, D3DTSS_MAXANISOTROPY, 1); - //Set_DX8_Texture_Stage_State(i, D3DTSS_ADDRESSW, D3DTADDRESS_WRAP); - //Set_DX8_Texture_Stage_State(i, D3DTSS_COLORARG0, D3DTA_CURRENT); - //Set_DX8_Texture_Stage_State(i, D3DTSS_ALPHAARG0, D3DTA_CURRENT); - //Set_DX8_Texture_Stage_State(i, D3DTSS_RESULTARG, D3DTA_CURRENT); - - Set_DX8_Texture_Stage_State(i, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE); - Set_Texture(i,nullptr); - } - -// DX8Wrapper::Set_Material(nullptr); + Commit_Fixed_Function_Texture_Stage_Value(i, 1 /* D3DTSS_COLOROP */, 1); + Commit_Fixed_Function_Texture_Stage_Value(i, 2 /* D3DTSS_COLORARG1 */, 2); + Commit_Fixed_Function_Texture_Stage_Value(i, 3 /* D3DTSS_COLORARG2 */, 0); + + Commit_Fixed_Function_Texture_Stage_Value(i, 4 /* D3DTSS_ALPHAOP */, 1); + Commit_Fixed_Function_Texture_Stage_Value(i, 5 /* D3DTSS_ALPHAARG1 */, 2); + Commit_Fixed_Function_Texture_Stage_Value(i, 6 /* D3DTSS_ALPHAARG2 */, 0); + + Commit_Fixed_Function_Texture_Stage_Value(i, 11 /* D3DTSS_TEXCOORDINDEX */, i); + + Commit_Fixed_Function_Texture_Stage_Value(i, 13 /* D3DTSS_ADDRESSU */, 1); + Commit_Fixed_Function_Texture_Stage_Value(i, 14 /* D3DTSS_ADDRESSV */, 1); + Commit_Fixed_Function_Texture_Stage_Value(i, 15 /* D3DTSS_BORDERCOLOR */, 0); + + Commit_Fixed_Function_Texture_Stage_Value(i, 24 /* D3DTSS_TEXTURETRANSFORMFLAGS */, 0); + Commit_Fixed_Function_Texture(i,nullptr); + } + VertexMaterialClass::Apply_Null(); +#if !defined(GGC_RENDER_BACKEND_BGFX) for (unsigned index=0;index<4;++index) { SNAPSHOT_SAY(("Clearing light %d to null",index)); Set_DX8_Light(index,nullptr); } +#endif // set up simple default TSS Vector4 vconst[MAX_VERTEX_SHADER_CONSTANTS]; memset(vconst,0,sizeof(Vector4)*MAX_VERTEX_SHADER_CONSTANTS); - Set_Vertex_Shader_Constant(0, vconst, MAX_VERTEX_SHADER_CONSTANTS); + Commit_Vertex_Shader_Constants(0, vconst, MAX_VERTEX_SHADER_CONSTANTS); Vector4 pconst[MAX_PIXEL_SHADER_CONSTANTS]; memset(pconst,0,sizeof(Vector4)*MAX_PIXEL_SHADER_CONSTANTS); - Set_Pixel_Shader_Constant(0, pconst, MAX_PIXEL_SHADER_CONSTANTS); + Commit_Pixel_Shader_Constants(0, pconst, MAX_PIXEL_SHADER_CONSTANTS); - Set_Vertex_Shader(DX8_FVF_XYZNDUV2); - Set_Pixel_Shader(0); + Commit_Vertex_Shader_Value(DX8_FVF_XYZNDUV2); + Commit_Pixel_Shader_Value(0); ShaderClass::Invalidate(); } +#if !defined(GGC_RENDER_BACKEND_BGFX) const char* DX8Wrapper::Get_DX8_Render_State_Name(D3DRENDERSTATETYPE state) { switch (state) { @@ -4425,9 +5170,10 @@ const char* DX8Wrapper::Get_DX8_Blend_Op_Name(unsigned value) case D3DBLENDOP_REVSUBTRACT: return "D3DBLENDOP_REVSUBTRACT"; case D3DBLENDOP_MIN : return "D3DBLENDOP_MIN"; case D3DBLENDOP_MAX : return "D3DBLENDOP_MAX"; - default : return "UNKNOWN"; + default : return "UNKNOWN"; } } +#endif //============================================================================ diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h index 8a6f3d26495..98be53084c8 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h @@ -43,7 +43,11 @@ #include "WWLib/always.h" #include "dllist.h" +#if defined(GGC_RENDER_BACKEND_BGFX) +#include "dx8standalonetypes.h" +#else #include "d3d8.h" +#endif #include "WWMath/matrix4.h" #include "statistics.h" #include "WWLib/wwstring.h" @@ -52,11 +56,27 @@ #include "WWMath/vector4.h" #include "WWLib/cpudetect.h" #include "dx8caps.h" +#include "dx8deviceinterop.h" +#include "RenderBufferTypes.h" +#include "RenderDeviceCleanupHook.h" #include "texture.h" -#include "dx8vertexbuffer.h" -#include "dx8indexbuffer.h" +#include "WW3D2/vertexbuffer.h" +#include "WW3D2/indexbuffer.h" #include "WW3D2/vertmaterial.h" +#include "WW3D2/FixedFunctionState.h" +#include "WW3D2/RenderStateDefs.h" + +// TheSuperHackers @refactor bobtista 10/04/2026 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 @@ -68,20 +88,10 @@ #define VALUE_NAME_RENDER_DEVICE_WINDOWED "RenderDeviceWindowed" #define VALUE_NAME_RENDER_DEVICE_TEXTURE_DEPTH "RenderDeviceTextureDepth" -const unsigned MAX_TEXTURE_STAGES=8; -const unsigned MAX_VERTEX_STREAMS=2; const unsigned MAX_VERTEX_SHADER_CONSTANTS=96; const unsigned MAX_PIXEL_SHADER_CONSTANTS=8; const unsigned MAX_SHADOW_MAPS=1; -enum { - BUFFER_TYPE_DX8, - BUFFER_TYPE_SORTING, - BUFFER_TYPE_DYNAMIC_DX8, - BUFFER_TYPE_DYNAMIC_SORTING, - BUFFER_TYPE_INVALID -}; - class VertexMaterialClass; class CameraClass; class LightEnvironmentClass; @@ -90,6 +100,8 @@ class VertexBufferClass; class DynamicVBAccessClass; class IndexBufferClass; class DynamicIBAccessClass; +class DX8VertexBufferClass; +class DX8IndexBufferClass; class TextureClass; class LightClass; class SurfaceClass; @@ -135,120 +147,69 @@ struct DX8FrameStatistics extern bool _DX8SingleThreaded; -void DX8_Assert(); -void Log_DX8_ErrorCode(unsigned res); - WWINLINE void DX8_ErrorCode(unsigned res) { - if (res==D3D_OK) return; + if (res==S_OK) return; Log_DX8_ErrorCode(res); } #ifdef WWDEBUG -#define DX8CALL_HRES(x,res) DX8_Assert(); res = DX8Wrapper::_Get_D3D_Device8()->x; DX8_ErrorCode(res); DX8Wrapper::Increment_DX8_CallCount(); -#define DX8CALL(x) DX8_Assert(); DX8_ErrorCode(DX8Wrapper::_Get_D3D_Device8()->x); DX8Wrapper::Increment_DX8_CallCount(); -#define DX8CALL_D3D(x) DX8_Assert(); DX8_ErrorCode(DX8Wrapper::_Get_D3D8()->x); DX8Wrapper::Increment_DX8_CallCount(); #define DX8_THREAD_ASSERT() if (_DX8SingleThreaded) { WWASSERT_PRINT(DX8Wrapper::_Get_Main_Thread_ID()==ThreadClass::_Get_Current_Thread_ID(),"DX8Wrapper::DX8 calls must be called from the main thread!"); } #else -#define DX8CALL_HRES(x,res) res = DX8Wrapper::_Get_D3D_Device8()->x; DX8Wrapper::Increment_DX8_CallCount(); -#define DX8CALL(x) DX8Wrapper::_Get_D3D_Device8()->x; DX8Wrapper::Increment_DX8_CallCount(); -#define DX8CALL_D3D(x) DX8Wrapper::_Get_D3D8()->x; DX8Wrapper::Increment_DX8_CallCount(); #define DX8_THREAD_ASSERT() ; #endif +#if !defined(GGC_RENDER_BACKEND_BGFX) +#ifdef WWDEBUG +#define DX8CALL_HRES(x,res) DX8_Assert(); res = DX8_Call_Device()->x; DX8_ErrorCode(res); DX8Wrapper::Increment_DX8_CallCount(); +#define DX8CALL(x) DX8_Assert(); DX8_ErrorCode(DX8_Call_Device()->x); DX8Wrapper::Increment_DX8_CallCount(); +#define DX8CALL_D3D(x) DX8_Assert(); DX8_ErrorCode(DX8_Call_Interface()->x); DX8Wrapper::Increment_DX8_CallCount(); +#else +#define DX8CALL_HRES(x,res) res = DX8_Call_Device()->x; DX8Wrapper::Increment_DX8_CallCount(); +#define DX8CALL(x) DX8_Call_Device()->x; DX8Wrapper::Increment_DX8_CallCount(); +#define DX8CALL_D3D(x) DX8_Call_Interface()->x; DX8Wrapper::Increment_DX8_CallCount(); +#endif +#endif + #define no_EXTENDED_STATS // EXTENDED_STATS collects additional timing statistics by turning off parts // of the 3D drawing system (terrain, objects, etc.) #ifdef EXTENDED_STATS -class DX8_Stats -{ -public: - bool m_showingStats; - bool m_disableTerrain; - bool m_disableWater; - bool m_disableObjects; - bool m_disableOverhead; - bool m_disableConsole; - int m_debugLinesToShow; - int m_sleepTime; -public: - DX8_Stats::DX8_Stats() { - m_disableConsole = m_showingStats = m_disableTerrain = m_disableWater = m_disableOverhead = m_disableObjects = false; - m_sleepTime = 0; - m_debugLinesToShow = -1; // -1 means show all expected lines of output - } -}; +#include "renderdebugstats.h" #endif -// This virtual interface was added for the Generals RTS. -// It is called before resetting the dx8 device to ensure -// that all dx8 resources are released. Otherwise reset fails. jba. -class DX8_CleanupHook -{ -public: - virtual void ReleaseResources()=0; - virtual void ReAcquireResources()=0; -}; - - -struct RenderStateStruct -{ - ShaderClass shader; - VertexMaterialClass* material; - TextureBaseClass * Textures[MAX_TEXTURE_STAGES]; - D3DLIGHT8 Lights[4]; - bool LightEnable[4]; - D3DMATRIX world; - D3DMATRIX view; - unsigned vertex_buffer_types[MAX_VERTEX_STREAMS]; - unsigned index_buffer_type; - unsigned short vba_offset; - unsigned short vba_count; - unsigned short iba_offset; - VertexBufferClass* vertex_buffers[MAX_VERTEX_STREAMS]; - IndexBufferClass* index_buffer; - unsigned short index_base_offset; - - RenderStateStruct(); - ~RenderStateStruct(); - - RenderStateStruct& operator= (const RenderStateStruct& src); -}; - /** ** DX8Wrapper ** ** DX8 interface wrapper class. This encapsulates the DX8 interface; adding redundant state ** detection, stat tracking, etc etc. In general, we will wrap all DX8 calls with at least ** an WWINLINE function so that we can add stat tracking, etc if needed. Direct access to the -** D3D device will require "friend" status and should be granted only in extreme circumstances :-) +** legacy device will require "friend" status and should be granted only in extreme circumstances :-) */ class DX8Wrapper { enum ChangedStates { - WORLD_CHANGED = 1<<0, - VIEW_CHANGED = 1<<1, - LIGHT0_CHANGED = 1<<2, - LIGHT1_CHANGED = 1<<3, - LIGHT2_CHANGED = 1<<4, - LIGHT3_CHANGED = 1<<5, - TEXTURE0_CHANGED= 1<<6, - TEXTURE1_CHANGED= 1<<7, - TEXTURE2_CHANGED= 1<<8, - TEXTURE3_CHANGED= 1<<9, - MATERIAL_CHANGED= 1<<14, - SHADER_CHANGED = 1<<15, - VERTEX_BUFFER_CHANGED = 1<<16, - INDEX_BUFFER_CHANGED = 1 << 17, - WORLD_IDENTITY= 1<<18, - VIEW_IDENTITY= 1<<19, - - TEXTURES_CHANGED= - TEXTURE0_CHANGED|TEXTURE1_CHANGED|TEXTURE2_CHANGED|TEXTURE3_CHANGED, - LIGHTS_CHANGED= - LIGHT0_CHANGED|LIGHT1_CHANGED|LIGHT2_CHANGED|LIGHT3_CHANGED, + WORLD_CHANGED = FixedFunctionState::WORLD_CHANGED, + VIEW_CHANGED = FixedFunctionState::VIEW_CHANGED, + LIGHT0_CHANGED = FixedFunctionState::LIGHT0_CHANGED, + LIGHT1_CHANGED = FixedFunctionState::LIGHT1_CHANGED, + LIGHT2_CHANGED = FixedFunctionState::LIGHT2_CHANGED, + LIGHT3_CHANGED = FixedFunctionState::LIGHT3_CHANGED, + TEXTURE0_CHANGED = FixedFunctionState::TEXTURE0_CHANGED, + TEXTURE1_CHANGED = FixedFunctionState::TEXTURE1_CHANGED, + TEXTURE2_CHANGED = FixedFunctionState::TEXTURE2_CHANGED, + TEXTURE3_CHANGED = FixedFunctionState::TEXTURE3_CHANGED, + MATERIAL_CHANGED = FixedFunctionState::MATERIAL_CHANGED, + SHADER_CHANGED = FixedFunctionState::SHADER_CHANGED, + VERTEX_BUFFER_CHANGED = FixedFunctionState::VERTEX_BUFFER_CHANGED, + INDEX_BUFFER_CHANGED = FixedFunctionState::INDEX_BUFFER_CHANGED, + WORLD_IDENTITY = FixedFunctionState::WORLD_IDENTITY, + VIEW_IDENTITY = FixedFunctionState::VIEW_IDENTITY, + + TEXTURES_CHANGED = FixedFunctionState::TEXTURES_CHANGED, + LIGHTS_CHANGED = FixedFunctionState::LIGHTS_CHANGED, }; static void Draw_Sorting_IB_VB( @@ -267,13 +228,13 @@ class DX8Wrapper public: #ifdef EXTENDED_STATS - static DX8_Stats stats; + static RenderDebugStats &stats; #endif static bool Init(void * hwnd, bool lite = false); static void Shutdown(); - static void SetCleanupHook(DX8_CleanupHook *pCleanupHook) {m_pCleanupHook = pCleanupHook;}; + static void SetCleanupHook(RenderDeviceCleanupHook *pCleanupHook) {m_pCleanupHook = pCleanupHook;}; /* ** Some WW3D sub-systems need to be initialized after the device is created and shutdown ** before the device is released. @@ -281,88 +242,129 @@ 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); +#if !defined(GGC_RENDER_BACKEND_BGFX) + GGC_RB_DEPRECATED static void Set_Viewport(CONST D3DVIEWPORT8* pViewport); +#endif - 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); +#if !defined(GGC_RENDER_BACKEND_BGFX) static void Set_Render_State(const RenderStateStruct& state); +#endif static void Release_Render_State(); - + // TheSuperHackers @perf bobtista 28/04/2026 Const-ref peek avoids the + // RenderStateStruct copy assignment, which does REF_PTR_SET on material, + // MAX_VERTEX_STREAMS vertex buffers, the index buffer, and every entry + // of Textures[MAX_TEXTURE_STAGES]. Read-only callers (e.g. per-draw + // light/texture sync in BgfxBackend) should use this instead. + static const RenderStateStruct & Peek_Render_State() { return FixedFunctionState::Peek_Render_State(); } + +#if !defined(GGC_RENDER_BACKEND_BGFX) static void Set_DX8_Material(const D3DMATERIAL8* mat); +#endif - 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(); +#if !defined(GGC_RENDER_BACKEND_BGFX) + 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); +#endif + 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. + static void Commit_Fixed_Function_Transform(unsigned transform, const LegacyTransformMatrix& m); +#if !defined(GGC_RENDER_BACKEND_BGFX) static void _Set_DX8_Transform(D3DTRANSFORMSTATETYPE transform, const D3DMATRIX& m); +#endif +#if !defined(GGC_RENDER_BACKEND_BGFX) static void _Get_DX8_Transform(D3DTRANSFORMSTATETYPE transform, D3DMATRIX& m); +#endif +#if !defined(GGC_RENDER_BACKEND_BGFX) static void Set_DX8_Light(int index,D3DLIGHT8* light); +#endif + static void Commit_Fixed_Function_Render_Value(unsigned state, unsigned value); +#if !defined(GGC_RENDER_BACKEND_BGFX) static void Set_DX8_Render_State(D3DRENDERSTATETYPE state, unsigned value); +#endif +#if !defined(GGC_RENDER_BACKEND_BGFX) static void Set_DX8_Clip_Plane(DWORD Index, CONST float* pPlane); +#endif + static void Commit_Fixed_Function_Texture_Stage_Value(unsigned stage, unsigned state, unsigned value); +#if !defined(GGC_RENDER_BACKEND_BGFX) 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); +#endif +#if !defined(GGC_RENDER_BACKEND_BGFX) + GGC_RB_DEPRECATED static void Set_Light_Environment(LightEnvironmentClass* light_env); +#endif + GGC_RB_DEPRECATED static LightEnvironmentClass* Get_Light_Environment() { return Light_Environment; } +#if !defined(GGC_RENDER_BACKEND_BGFX) + GGC_RB_DEPRECATED static void Set_Fog(bool enable, const Vector3 &color, float start, float end); +#endif // 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); +#if !defined(GGC_RENDER_BACKEND_BGFX) + GGC_RB_DEPRECATED static void Set_Shader(const ShaderClass& shader); +#endif + GGC_RB_DEPRECATED static void Get_Shader(ShaderClass& shader); +#if !defined(GGC_RENDER_BACKEND_BGFX) + 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); +#endif + static void Commit_Fixed_Function_Texture(unsigned stage,TextureBaseClass* texture); + static void Commit_Deferred_Render_State_Changes(); - static void Apply_Render_State_Changes(); // Apply deferred render state changes (will be called automatically by Draw...) +#if !defined(GGC_RENDER_BACKEND_BGFX) + GGC_RB_DEPRECATED static void Apply_Render_State_Changes(); // Apply deferred render state changes (will be called automatically by Draw...) +#endif - 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, @@ -372,6 +374,7 @@ class DX8Wrapper ** Resources */ +#if !defined(GGC_RENDER_BACKEND_BGFX) static IDirect3DVolumeTexture8* _Create_DX8_Volume_Texture ( unsigned int width, @@ -431,9 +434,9 @@ class DX8Wrapper static void _Update_Texture(TextureClass *system, TextureClass *video); static void Flush_DX8_Resource_Manager(unsigned int bytes=0); static unsigned int Get_Free_Texture_RAM(); +#endif static unsigned _Get_Main_Thread_ID() { return _MainThreadID; } - static const D3DADAPTER_IDENTIFIER8& Get_Current_Adapter_Identifier() { return CurrentAdapterIdentifier; } /* ** Statistics @@ -446,7 +449,7 @@ class DX8Wrapper // Needed by shader class static bool Get_Fog_Enable() { return FogEnable; } - static D3DCOLOR Get_Fog_Color() { return FogColor; } + static unsigned Get_Fog_Color() { return FogColor; } // Utilities static Vector4 Convert_Color(unsigned color); @@ -460,36 +463,18 @@ class DX8Wrapper static void _Enable_Triangle_Draw(bool enable) { _EnableTriangleDraw=enable; } static bool _Is_Triangle_Draw_Enabled() { return _EnableTriangleDraw; } - /* - ** Additional swap chain interface - ** - ** Use this interface to render to multiple windows (in windowed mode). - ** To render to an additional window, the sequence of calls should look - ** something like this: - ** - ** DX8Wrapper::Set_Render_Target (swap_chain_ptr); - ** - ** WW3D::Begin_Render (true, true, Vector3 (0, 0, 0)); - ** WW3D::Render (scene, camera, FALSE, FALSE); - ** WW3D::End_Render (); - ** - ** swap_chain_ptr->Present (nullptr, nullptr, nullptr, nullptr); - ** - ** DX8Wrapper::Set_Render_Target ((IDirect3DSurface8 *)nullptr); - ** - */ - static IDirect3DSwapChain8 * Create_Additional_Swap_Chain (HWND render_window); +#if !defined(GGC_RENDER_BACKEND_BGFX) /* ** 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,33 +486,48 @@ 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); +#endif - 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); +#if !defined(GGC_RENDER_BACKEND_BGFX) + 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); +#endif + static void Commit_Vertex_Shader_Value(DWORD vertex_shader); + static void Commit_Pixel_Shader_Value(DWORD pixel_shader); + static void Commit_Vertex_Shader_Constants(int reg, const void* data, int count); + static void Commit_Pixel_Shader_Constants(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; } +#if !defined(GGC_RENDER_BACKEND_BGFX) + GGC_RB_DEPRECATED static void Set_Ambient(const Vector3& color); +#endif + GGC_RB_DEPRECATED static const Vector3& Get_Ambient() { return Ambient_Color; } // shader system updates KJM ^ - static IDirect3DDevice8* _Get_D3D_Device8() { return D3DDevice; } - static IDirect3D8* _Get_D3D8() { return D3DInterface; } +#if !defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @build bobtista 01/06/2026 Out-of-line getters; the + // header cannot reference the file-static D3DDevice / D3DInterface + // pointers (defined in dx8wrapper.cpp) from inline bodies -- every TU + // including this header would otherwise fail to compile. + static IDirect3DDevice8* _Get_D3D_Device8(); + static IDirect3D8* _Get_D3D8(); +#endif /// Returns the display format - added by TR for video playback - not part of W3D static WW3DFormat getBackBufferFormat(); static bool Reset_Device(bool reload_assets=true); @@ -537,9 +537,11 @@ class DX8Wrapper static bool Registry_Save_Render_Device( const char * sub_key ); static bool Registry_Load_Render_Device( const char * sub_key, bool resize_window ); +#if !defined(GGC_RENDER_BACKEND_BGFX) 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_Render_State(D3DRENDERSTATETYPE state) { return FixedFunctionState::Cached_Render_State((unsigned)state); } + static unsigned Get_DX8_Texture_Stage_State(unsigned stage, D3DTEXTURESTAGESTATETYPE state) { return FixedFunctionState::Cached_Texture_Stage_State(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); @@ -563,8 +565,9 @@ class DX8Wrapper static const char* Get_DX8_Patch_Edge_Style_Name(unsigned value); static const char* Get_DX8_Debug_Monitor_Token_Name(unsigned value); static const char* Get_DX8_Blend_Op_Name(unsigned value); +#endif - 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; } @@ -605,8 +608,10 @@ class DX8Wrapper static void Set_Texture_Bitdepth(int depth) { WWASSERT(depth==16 || depth==32); TextureBitDepth = depth; } static int Get_Texture_Bitdepth() { return TextureBitDepth; } - static void Set_MSAA_Mode(D3DMULTISAMPLE_TYPE mode) { MultiSampleAntiAliasing = mode; } - static D3DMULTISAMPLE_TYPE Get_MSAA_Mode() { return MultiSampleAntiAliasing; } +#if !defined(GGC_RENDER_BACKEND_BGFX) + static void Set_MSAA_Mode(D3DMULTISAMPLE_TYPE mode) { MultiSampleAntiAliasing = static_cast(mode); } + static D3DMULTISAMPLE_TYPE Get_MSAA_Mode() { return static_cast(MultiSampleAntiAliasing); } +#endif static void Set_Swap_Interval(int swap); static int Get_Swap_Interval(); @@ -616,21 +621,17 @@ class DX8Wrapper ** Internal functions */ static void Resize_And_Position_Window(); - static bool Find_Color_And_Z_Mode(int resx,int resy,int bitdepth,D3DFORMAT * set_colorbuffer,D3DFORMAT * set_backbuffer, D3DFORMAT * set_zmode); - static bool Find_Color_Mode(D3DFORMAT colorbuffer, int resx, int resy, UINT *mode); - static bool Find_Z_Mode(D3DFORMAT colorbuffer,D3DFORMAT backbuffer, D3DFORMAT *zmode); - static bool Test_Z_Mode(D3DFORMAT colorbuffer,D3DFORMAT backbuffer, D3DFORMAT zmode); + static bool Find_Color_And_Z_Mode(int resx,int resy,int bitdepth,unsigned * set_colorbuffer,unsigned * set_backbuffer, unsigned * set_zmode); + static bool Find_Color_Mode(unsigned colorbuffer, int resx, int resy, UINT *mode); + static bool Find_Z_Mode(unsigned colorbuffer,unsigned backbuffer, unsigned *zmode); + static bool Test_Z_Mode(unsigned colorbuffer,unsigned backbuffer, unsigned zmode); static void Compute_Caps(WW3DFormat display_format); /* ** Protected Member Variables */ - static DX8_CleanupHook *m_pCleanupHook; - - static RenderStateStruct render_state; - static unsigned render_state_changed; - static D3DMATRIX DX8Transforms[D3DTS_WORLD+1]; + static RenderDeviceCleanupHook *m_pCleanupHook; static bool IsInitted; static bool IsDeviceLost; @@ -645,8 +646,8 @@ class DX8Wrapper static int BitDepth; static int TextureBitDepth; static bool IsWindowed; - static D3DFORMAT DisplayFormat; - static D3DMULTISAMPLE_TYPE MultiSampleAntiAliasing; + static unsigned DisplayFormat; + static unsigned MultiSampleAntiAliasing; // shader system updates KJM v @@ -666,14 +667,11 @@ class DX8Wrapper // shader system updates KJM ^ static bool world_identity; - static unsigned RenderStates[256]; - static unsigned TextureStageStates[MAX_TEXTURE_STAGES][32]; - static IDirect3DBaseTexture8 * Textures[MAX_TEXTURE_STAGES]; // These fog settings are constant for all objects in a given scene, // unlike the matching renderstates which vary based on shader settings. static bool FogEnable; - static D3DCOLOR FogColor; + static unsigned FogColor; static DX8FrameStatistics FrameStatistics; static bool CurrentDX8LightEnables[4]; @@ -682,16 +680,6 @@ class DX8Wrapper static DX8Caps* CurrentCaps; - static D3DADAPTER_IDENTIFIER8 CurrentAdapterIdentifier; - - static IDirect3D8 * D3DInterface; //d3d8; - static IDirect3DDevice8 * D3DDevice; //d3ddevice8; - - static IDirect3DSurface8 * CurrentRenderTarget; - static IDirect3DSurface8 * CurrentDepthBuffer; - static IDirect3DSurface8 * DefaultRenderTarget; - static IDirect3DSurface8 * DefaultDepthBuffer; - static unsigned DrawPolygonLowBoundLimit; static bool IsRenderToTexture; @@ -699,16 +687,26 @@ class DX8Wrapper static int ZBias; static float ZNear; static float ZFar; +#if !defined(GGC_RENDER_BACKEND_BGFX) static D3DMATRIX ProjectionMatrix; +#endif friend void DX8_Assert(); friend class WW3D; friend class DX8IndexBufferClass; friend class DX8VertexBufferClass; + // TheSuperHackers @build bobtista 01/06/2026 DX8Backend is the + // reference adapter that bridges IRenderBackend to DX8Wrapper. It needs + // access to the protected MSAA / texture-bitdepth accessors. + friend class DX8Backend; + // TheSuperHackers @build bobtista 05/06/2026 BgfxBackend forwards the + // device-enumeration / display-mode facade to DX8Wrapper on bgfx builds + // and needs the same protected access as DX8Backend. + friend class BgfxBackend; }; // shader system updates KJM v -WWINLINE void DX8Wrapper::Set_Vertex_Shader(DWORD vertex_shader) +WWINLINE void DX8Wrapper::Commit_Vertex_Shader_Value(DWORD vertex_shader) { #if 0 //(gth) some code is bypassing this accessor function so we can't count on this variable... // may be incorrect if shaders are created and destroyed dynamically @@ -716,19 +714,37 @@ WWINLINE void DX8Wrapper::Set_Vertex_Shader(DWORD vertex_shader) #endif Vertex_Shader=vertex_shader; +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8CALL(SetVertexShader(Vertex_Shader)); +#endif } -WWINLINE void DX8Wrapper::Set_Pixel_Shader(DWORD pixel_shader) +#if !defined(GGC_RENDER_BACKEND_BGFX) +WWINLINE void DX8Wrapper::Set_Vertex_Shader(DWORD vertex_shader) +{ + Commit_Vertex_Shader_Value(vertex_shader); +} +#endif + +WWINLINE void DX8Wrapper::Commit_Pixel_Shader_Value(DWORD pixel_shader) { // may be incorrect if shaders are created and destroyed dynamically if (Pixel_Shader==pixel_shader) return; Pixel_Shader=pixel_shader; +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8CALL(SetPixelShader(Pixel_Shader)); +#endif } -WWINLINE void DX8Wrapper::Set_Vertex_Shader_Constant(int reg, const void* data, int count) +#if !defined(GGC_RENDER_BACKEND_BGFX) +WWINLINE void DX8Wrapper::Set_Pixel_Shader(DWORD pixel_shader) +{ + Commit_Pixel_Shader_Value(pixel_shader); +} +#endif + +WWINLINE void DX8Wrapper::Commit_Vertex_Shader_Constants(int reg, const void* data, int count) { int memsize=sizeof(Vector4)*count; @@ -736,10 +752,19 @@ WWINLINE void DX8Wrapper::Set_Vertex_Shader_Constant(int reg, const void* data, if (memcmp(data, &Vertex_Shader_Constants[reg],memsize)==0) return; memcpy(&Vertex_Shader_Constants[reg],data,memsize); +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8CALL(SetVertexShaderConstant(reg,data,count)); +#endif } -WWINLINE void DX8Wrapper::Set_Pixel_Shader_Constant(int reg, const void* data, int count) +#if !defined(GGC_RENDER_BACKEND_BGFX) +WWINLINE void DX8Wrapper::Set_Vertex_Shader_Constant(int reg, const void* data, int count) +{ + Commit_Vertex_Shader_Constants(reg, data, count); +} +#endif + +WWINLINE void DX8Wrapper::Commit_Pixel_Shader_Constants(int reg, const void* data, int count) { int memsize=sizeof(Vector4)*count; @@ -747,32 +772,48 @@ WWINLINE void DX8Wrapper::Set_Pixel_Shader_Constant(int reg, const void* data, i if (memcmp(data, &Pixel_Shader_Constants[reg],memsize)==0) return; memcpy(&Pixel_Shader_Constants[reg],data,memsize); +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8CALL(SetPixelShaderConstant(reg,data,count)); +#endif } -// shader system updates KJM ^ -WWINLINE void DX8Wrapper::_Set_DX8_Transform(D3DTRANSFORMSTATETYPE transform, const D3DMATRIX& m) +#if !defined(GGC_RENDER_BACKEND_BGFX) +WWINLINE void DX8Wrapper::Set_Pixel_Shader_Constant(int reg, const void* data, int count) { - WWASSERT(transform<=D3DTS_WORLD); -#if 0 // (gth) this optimization is breaking generals because they set the transform behind our backs. - if (mtx!=DX8Transforms[transform]) + Commit_Pixel_Shader_Constants(reg, data, count); +} #endif +// shader system updates KJM ^ + +WWINLINE void DX8Wrapper::Commit_Fixed_Function_Transform(unsigned transform, const LegacyTransformMatrix& m) +{ { - DX8Transforms[transform]=m; + FixedFunctionState::Set_Cached_Transform(transform,m); SNAPSHOT_SAY(("DX8 - SetTransform %d [%f,%f,%f,%f][%f,%f,%f,%f][%f,%f,%f,%f]", transform, m.m[0][0],m.m[0][1],m.m[0][2],m.m[0][3], m.m[1][0],m.m[1][1],m.m[1][2],m.m[1][3], m.m[2][0],m.m[2][1],m.m[2][2],m.m[2][3])); DX8_RECORD_MATRIX_CHANGE(); - DX8CALL(SetTransform(transform,&m)); +#if !defined(GGC_RENDER_BACKEND_BGFX) + DX8CALL(SetTransform(static_cast(transform),&m)); +#endif } } +#if !defined(GGC_RENDER_BACKEND_BGFX) +WWINLINE void DX8Wrapper::_Set_DX8_Transform(D3DTRANSFORMSTATETYPE transform, const D3DMATRIX& m) +{ + Commit_Fixed_Function_Transform(transform, m); +} +#endif + +#if !defined(GGC_RENDER_BACKEND_BGFX) WWINLINE void DX8Wrapper::_Get_DX8_Transform(D3DTRANSFORMSTATETYPE transform, D3DMATRIX& m) { DX8CALL(GetTransform(transform,&m)); } +#endif // ---------------------------------------------------------------------------- // @@ -782,9 +823,9 @@ WWINLINE void DX8Wrapper::_Get_DX8_Transform(D3DTRANSFORMSTATETYPE transform, D3 WWINLINE void DX8Wrapper::Set_Index_Buffer_Index_Offset(unsigned offset) { - if (render_state.index_base_offset==offset) return; - render_state.index_base_offset=offset; - render_state_changed|=INDEX_BUFFER_CHANGED; + if (FixedFunctionState::Render_State().index_base_offset==offset) return; + FixedFunctionState::Render_State().index_base_offset=offset; + FixedFunctionState::Changed_Mask()|=INDEX_BUFFER_CHANGED; } // ---------------------------------------------------------------------------- @@ -795,6 +836,7 @@ WWINLINE void DX8Wrapper::Set_Index_Buffer_Index_Offset(unsigned offset) // This function should be called rarely - once per scene would be appropriate. // ---------------------------------------------------------------------------- +#if !defined(GGC_RENDER_BACKEND_BGFX) WWINLINE void DX8Wrapper::Set_Fog(bool enable, const Vector3 &color, float start, float end) { // Set global states @@ -806,16 +848,17 @@ WWINLINE void DX8Wrapper::Set_Fog(bool enable, const Vector3 &color, float start ShaderClass::Invalidate(); // Set renderstates which are not affected by the shader - Set_DX8_Render_State(D3DRS_FOGSTART, *(DWORD *)(&start)); - Set_DX8_Render_State(D3DRS_FOGEND, *(DWORD *)(&end)); + Commit_Fixed_Function_Render_Value(D3DRS_FOGSTART, *(DWORD *)(&start)); + Commit_Fixed_Function_Render_Value(D3DRS_FOGEND, *(DWORD *)(&end)); } WWINLINE void DX8Wrapper::Set_Ambient(const Vector3& color) { Ambient_Color=color; - Set_DX8_Render_State(D3DRS_AMBIENT, DX8Wrapper::Convert_Color(color,0.0f)); + Commit_Fixed_Function_Render_Value(D3DRS_AMBIENT, DX8Wrapper::Convert_Color(color,0.0f)); } +#endif // ---------------------------------------------------------------------------- // @@ -825,6 +868,7 @@ WWINLINE void DX8Wrapper::Set_Ambient(const Vector3& color) // // ---------------------------------------------------------------------------- +#if !defined(GGC_RENDER_BACKEND_BGFX) WWINLINE void DX8Wrapper::Set_DX8_Material(const D3DMATERIAL8* mat) { DX8_RECORD_MATERIAL_CHANGE(); @@ -849,57 +893,83 @@ WWINLINE void DX8Wrapper::Set_DX8_Light(int index, D3DLIGHT8* light) SNAPSHOT_SAY(("DX8 - DisableLight %d",index)); } } +#endif -WWINLINE void DX8Wrapper::Set_DX8_Render_State(D3DRENDERSTATETYPE state, unsigned value) +WWINLINE void DX8Wrapper::Commit_Fixed_Function_Render_Value(unsigned state, unsigned value) { // Can't monitor state changes because setShader call to GERD may change the states! - if (RenderStates[state]==value) return; + if (FixedFunctionState::Cached_Render_State(state)==value) return; -#ifdef MESH_RENDER_SNAPSHOT_ENABLED +#if defined(MESH_RENDER_SNAPSHOT_ENABLED) && !defined(GGC_RENDER_BACKEND_BGFX) if (WW3D::Is_Snapshot_Activated()) { StringClass value_name(0,true); - Get_DX8_Render_State_Value_Name(value_name,state,value); + const auto legacy_state = static_cast(state); + Get_DX8_Render_State_Value_Name(value_name,legacy_state,value); SNAPSHOT_SAY(("DX8 - SetRenderState(state: %s, value: %s)", - Get_DX8_Render_State_Name(state), + Get_DX8_Render_State_Name(legacy_state), value_name.str())); } #endif - RenderStates[state]=value; - DX8CALL(SetRenderState( state, value )); + FixedFunctionState::Set_Cached_Render_State(state,value); +#if !defined(GGC_RENDER_BACKEND_BGFX) + DX8CALL(SetRenderState( static_cast(state), value )); +#endif DX8_RECORD_RENDER_STATE_CHANGE(); } +#if !defined(GGC_RENDER_BACKEND_BGFX) +WWINLINE void DX8Wrapper::Set_DX8_Render_State(D3DRENDERSTATETYPE state, unsigned value) +{ + Commit_Fixed_Function_Render_Value(state, value); +} +#endif + +#if !defined(GGC_RENDER_BACKEND_BGFX) WWINLINE void DX8Wrapper::Set_DX8_Clip_Plane(DWORD Index, CONST float* pPlane) { DX8CALL(SetClipPlane( Index, pPlane )); } +#endif -WWINLINE void DX8Wrapper::Set_DX8_Texture_Stage_State(unsigned stage, D3DTEXTURESTAGESTATETYPE state, unsigned value) +WWINLINE void DX8Wrapper::Commit_Fixed_Function_Texture_Stage_Value(unsigned stage, unsigned state, unsigned value) { - if (stage >= MAX_TEXTURE_STAGES) - { DX8CALL(SetTextureStageState( stage, state, value )); + if (stage >= MAX_TEXTURE_STAGES) + { +#if !defined(GGC_RENDER_BACKEND_BGFX) + DX8CALL(SetTextureStageState( stage, static_cast(state), value )); + DX8_RECORD_TEXTURE_STAGE_STATE_CHANGE(); +#endif return; } // Can't monitor state changes because setShader call to GERD may change the states! - if (TextureStageStates[stage][(unsigned int)state]==value) return; -#ifdef MESH_RENDER_SNAPSHOT_ENABLED + if (FixedFunctionState::Cached_Texture_Stage_State(stage,state)==value) return; +#if defined(MESH_RENDER_SNAPSHOT_ENABLED) && !defined(GGC_RENDER_BACKEND_BGFX) if (WW3D::Is_Snapshot_Activated()) { StringClass value_name(0,true); - Get_DX8_Texture_Stage_State_Value_Name(value_name,state,value); + const auto legacy_state = static_cast(state); + Get_DX8_Texture_Stage_State_Value_Name(value_name,legacy_state,value); SNAPSHOT_SAY(("DX8 - SetTextureStageState(stage: %d, state: %s, value: %s)", stage, - Get_DX8_Texture_Stage_State_Name(state), + Get_DX8_Texture_Stage_State_Name(legacy_state), value_name.str())); } #endif - TextureStageStates[stage][(unsigned int)state]=value; - DX8CALL(SetTextureStageState( stage, state, value )); + FixedFunctionState::Set_Cached_Texture_Stage_State(stage,state,value); +#if !defined(GGC_RENDER_BACKEND_BGFX) + DX8CALL(SetTextureStageState( stage, static_cast(state), value )); +#endif DX8_RECORD_TEXTURE_STAGE_STATE_CHANGE(); } +#if !defined(GGC_RENDER_BACKEND_BGFX) +WWINLINE void DX8Wrapper::Set_DX8_Texture_Stage_State(unsigned stage, D3DTEXTURESTAGESTATETYPE state, unsigned value) +{ + Commit_Fixed_Function_Texture_Stage_Value(stage, state, value); +} + WWINLINE void DX8Wrapper::Set_DX8_Texture(unsigned int stage, IDirect3DBaseTexture8* texture) { if (stage >= MAX_TEXTURE_STAGES) @@ -907,17 +977,17 @@ WWINLINE void DX8Wrapper::Set_DX8_Texture(unsigned int stage, IDirect3DBaseTextu return; } - if (Textures[stage]==texture) return; + if (FixedFunctionState::Raw_Texture(stage)==texture) return; SNAPSHOT_SAY(("DX8 - SetTexture(%x) ",texture)); - if (Textures[stage]) Textures[stage]->Release(); - Textures[stage] = texture; - if (Textures[stage]) Textures[stage]->AddRef(); + FixedFunctionState::Set_Raw_Texture(stage, texture); DX8CALL(SetTexture(stage, texture)); DX8_RECORD_TEXTURE_CHANGE(); } +#endif +#if !defined(GGC_RENDER_BACKEND_BGFX) WWINLINE void DX8Wrapper::_Copy_DX8_Rects( IDirect3DSurface8* pSourceSurface, CONST RECT* pSourceRectsArray, @@ -931,8 +1001,9 @@ WWINLINE void DX8Wrapper::_Copy_DX8_Rects( pSourceRectsArray, cRects, pDestinationSurface, - pDestPointsArray)); + pDestPointsArray)); } +#endif WWINLINE Vector4 DX8Wrapper::Convert_Color(unsigned color) { @@ -1147,45 +1218,46 @@ WWINLINE void DX8Wrapper::Set_Alpha (const float alpha, unsigned int &color) WWINLINE void DX8Wrapper::Get_Render_State(RenderStateStruct& state) { - state=render_state; + FixedFunctionState::Capture_Render_State(state); } WWINLINE void DX8Wrapper::Get_Shader(ShaderClass& shader) { - shader=render_state.shader; + shader=FixedFunctionState::Render_State().shader; } -WWINLINE void DX8Wrapper::Set_Texture(unsigned stage,TextureBaseClass* texture) +WWINLINE void DX8Wrapper::Commit_Fixed_Function_Texture(unsigned stage,TextureBaseClass* texture) { WWASSERT(stage<(unsigned int)CurrentCaps->Get_Max_Textures_Per_Pass()); - if (texture==render_state.Textures[stage]) return; - REF_PTR_SET(render_state.Textures[stage],texture); - render_state_changed|=(TEXTURE0_CHANGED<Get_Name(),render_state.material->Get_Name())) { - material->Get_CRC()!=render_state.material->Get_CRC()) { +/* if (material && FixedFunctionState::Render_State().material && + // !stricmp(material->Get_Name(),FixedFunctionState::Render_State().material->Get_Name())) { + material->Get_CRC()!=FixedFunctionState::Render_State().material->Get_CRC()) { return; } */ -// if (material==render_state.material) { +// if (material==FixedFunctionState::Render_State().material) { // return; // } - REF_PTR_SET(render_state.material,const_cast(material)); - render_state_changed|=MATERIAL_CHANGED; + FixedFunctionState::Set_Material(material); SNAPSHOT_SAY(("DX8Wrapper::Set_Material(%s)",material ? material->Get_Name() : "null")); } WWINLINE void DX8Wrapper::Set_Shader(const ShaderClass& shader) { - if (!ShaderClass::ShaderDirty && ((unsigned&)shader==(unsigned&)render_state.shader)) { + if (!FixedFunctionState::Set_Shader(shader, ShaderClass::ShaderDirty)) { return; } - render_state.shader=shader; - render_state_changed|=SHADER_CHANGED; #ifdef MESH_RENDER_SNAPSHOT_ENABLED StringClass str; #endif @@ -1215,14 +1287,14 @@ WWINLINE void DX8Wrapper::Set_Transform(D3DTRANSFORMSTATETYPE transform,const Ma { switch ((int)transform) { case D3DTS_WORLD: - render_state.world=To_D3DMATRIX(m); - render_state_changed|=(unsigned)WORLD_CHANGED; - render_state_changed&=~(unsigned)WORLD_IDENTITY; + FixedFunctionState::Render_State().world=To_D3DMATRIX(m); + FixedFunctionState::Changed_Mask()|=(unsigned)WORLD_CHANGED; + FixedFunctionState::Changed_Mask()&=~(unsigned)WORLD_IDENTITY; break; case D3DTS_VIEW: - render_state.view=To_D3DMATRIX(m); - render_state_changed|=(unsigned)VIEW_CHANGED; - render_state_changed&=~(unsigned)VIEW_IDENTITY; + FixedFunctionState::Render_State().view=To_D3DMATRIX(m); + FixedFunctionState::Changed_Mask()|=(unsigned)VIEW_CHANGED; + FixedFunctionState::Changed_Mask()&=~(unsigned)VIEW_IDENTITY; break; case D3DTS_PROJECTION: { @@ -1244,14 +1316,14 @@ WWINLINE void DX8Wrapper::Set_Transform(D3DTRANSFORMSTATETYPE transform,const Ma { switch ((int)transform) { case D3DTS_WORLD: - render_state.world=To_D3DMATRIX(m); - render_state_changed|=(unsigned)WORLD_CHANGED; - render_state_changed&=~(unsigned)WORLD_IDENTITY; + FixedFunctionState::Render_State().world=To_D3DMATRIX(m); + FixedFunctionState::Changed_Mask()|=(unsigned)WORLD_CHANGED; + FixedFunctionState::Changed_Mask()&=~(unsigned)WORLD_IDENTITY; break; case D3DTS_VIEW: - render_state.view=To_D3DMATRIX(m); - render_state_changed|=(unsigned)VIEW_CHANGED; - render_state_changed&=~(unsigned)VIEW_IDENTITY; + FixedFunctionState::Render_State().view=To_D3DMATRIX(m); + FixedFunctionState::Changed_Mask()|=(unsigned)VIEW_CHANGED; + FixedFunctionState::Changed_Mask()&=~(unsigned)VIEW_IDENTITY; break; default: DX8_RECORD_MATRIX_CHANGE(); @@ -1263,24 +1335,24 @@ WWINLINE void DX8Wrapper::Set_Transform(D3DTRANSFORMSTATETYPE transform,const Ma WWINLINE bool DX8Wrapper::Is_World_Identity() { - return !!(render_state_changed&(unsigned)WORLD_IDENTITY); + return FixedFunctionState::Is_World_Identity(); } WWINLINE bool DX8Wrapper::Is_View_Identity() { - return !!(render_state_changed&(unsigned)VIEW_IDENTITY); + return FixedFunctionState::Is_View_Identity(); } WWINLINE void DX8Wrapper::Get_Transform(D3DTRANSFORMSTATETYPE transform, Matrix4x4& m) { switch ((int)transform) { case D3DTS_WORLD: - if (render_state_changed&WORLD_IDENTITY) m.Make_Identity(); - else m=To_Matrix4x4(render_state.world); + if (FixedFunctionState::Changed_Mask()&WORLD_IDENTITY) m.Make_Identity(); + else m=To_Matrix4x4(FixedFunctionState::Render_State().world); break; case D3DTS_VIEW: - if (render_state_changed&VIEW_IDENTITY) m.Make_Identity(); - else m=To_Matrix4x4(render_state.view); + if (FixedFunctionState::Changed_Mask()&VIEW_IDENTITY) m.Make_Identity(); + else m=To_Matrix4x4(FixedFunctionState::Render_State().view); break; default: D3DMATRIX dxm; @@ -1292,132 +1364,11 @@ WWINLINE void DX8Wrapper::Get_Transform(D3DTRANSFORMSTATETYPE transform, Matrix4 WWINLINE void DX8Wrapper::Set_Render_State(const RenderStateStruct& state) { - int i; - - if (render_state.index_buffer) { - render_state.index_buffer->Release_Engine_Ref(); - } - - for (i=0;iRelease_Engine_Ref(); - } - } - - render_state=state; - render_state_changed=0xffffffff; - - if (render_state.index_buffer) { - render_state.index_buffer->Add_Engine_Ref(); - } - - for (i=0;iAdd_Engine_Ref(); - } - } + FixedFunctionState::Restore_Render_State(state); } +#endif WWINLINE void DX8Wrapper::Release_Render_State() { - int i; - - if (render_state.index_buffer) { - render_state.index_buffer->Release_Engine_Ref(); - } - - for (i=0;iRelease_Engine_Ref(); - } - } - - for (i=0;iProcess_Texture_Reduction(); - unsigned buffer_type=(Get_Flag(MeshGeometryClass::SORT)&& WW3D::Is_Sorting_Enabled()) ? BUFFER_TYPE_DYNAMIC_SORTING : BUFFER_TYPE_DYNAMIC_DX8; + unsigned buffer_type=(Get_Flag(MeshGeometryClass::SORT)&& WW3D::Is_Sorting_Enabled()) ? BUFFER_TYPE_DYNAMIC_SORTING : BUFFER_TYPE_DYNAMIC; /* ** Write the vertex data to the vertex buffer. We assume the FVF contains positions, normals, @@ -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); } } @@ -461,7 +463,7 @@ bool DynamicMeshClass::End_Vertex() // color->Z = CurVertexColor[color_array_index].Z; // color->W = CurVertexColor[color_array_index].W; unsigned * color = &((Model->Get_Color_Array(color_array_index))[VertCount]); - *color=DX8Wrapper::Convert_Color_Clamp(CurVertexColor[color_array_index]); + *color=WW3DColor::To_ARGB_Clamp(CurVertexColor[color_array_index]); } } @@ -827,5 +829,3 @@ void DynamicScreenMeshClass::Reset() Reset_Flags(); Reset_Mesh_Counters(); } - - diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dynamesh.h b/Core/Libraries/Source/WWVegas/WW3D2/dynamesh.h index 0fcd9c29554..1c08647966f 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dynamesh.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dynamesh.h @@ -39,7 +39,7 @@ #include "matinfo.h" #include "rendobj.h" #include "polyinfo.h" -#include "dx8wrapper.h" +#include "ww3dcolor.h" class ShaderClass; class IntersectionClass; @@ -278,7 +278,7 @@ class DynamicMeshClass : public RenderObjClass { unsigned * color = Model->Get_Color_Array(color_array_index); assert(color); - color[VertCount]=DX8Wrapper::Convert_Color_Clamp(Vector4(r,g,b,a)); + color[VertCount]=WW3DColor::To_ARGB_Clamp(Vector4(r,g,b,a)); // color[VertCount].X = r; // color[VertCount].Y = g; // color[VertCount].Z = b; @@ -361,7 +361,7 @@ class DynamicMeshClass : public RenderObjClass { CurVertexColor[color_array_index].W = color.W; // Vector4 * color_list = Model->Get_Color_Array(color_array_index); unsigned * color_list = Model->Get_Color_Array(color_array_index); - color_list[index] = DX8Wrapper::Convert_Color_Clamp(color); + color_list[index] = WW3DColor::To_ARGB_Clamp(color); } @@ -485,7 +485,7 @@ void DynamicMeshClass::Switch_To_Multi_Vertex_Color(int color_array_index) */ unsigned * color_list = Model->Get_Color_Array(color_array_index); // set the proper color for all the existing vertices - unsigned vertex_color=DX8Wrapper::Convert_Color_Clamp(CurVertexColor[color_array_index]); + unsigned vertex_color=WW3DColor::To_ARGB_Clamp(CurVertexColor[color_array_index]); for (int lp = 0; lp < VertCount; lp++) { color_list[lp]=vertex_color; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/fixedfunctionlegacytypes.h b/Core/Libraries/Source/WWVegas/WW3D2/fixedfunctionlegacytypes.h new file mode 100644 index 00000000000..607dd36b331 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/fixedfunctionlegacytypes.h @@ -0,0 +1,67 @@ +/* +** 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 "WWMath/legacyd3dmatrix.h" + +#if !defined(GGC_RENDER_BACKEND_BGFX) +struct IDirect3DBaseTexture8; +#endif + +struct LegacyFixedFunctionColor +{ + float r; + float g; + float b; + float a; +}; + +struct LegacyFixedFunctionVector3 +{ + float x; + float y; + float z; +}; + +struct LegacyFixedFunctionLight +{ + unsigned int Type; + LegacyFixedFunctionColor Diffuse; + LegacyFixedFunctionColor Specular; + LegacyFixedFunctionColor Ambient; + LegacyFixedFunctionVector3 Position; + LegacyFixedFunctionVector3 Direction; + float Range; + float Falloff; + float Attenuation0; + float Attenuation1; + float Attenuation2; + float Theta; + float Phi; +}; + +#if defined(GGC_RENDER_BACKEND_BGFX) +using LegacyRawTexture = void; +#else +using LegacyRawTexture = IDirect3DBaseTexture8; +#endif +using LegacyTransformMatrix = D3DMATRIX; + +constexpr unsigned LEGACY_D3DTS_WORLD = 256; +constexpr unsigned LEGACY_FIXED_FUNCTION_TRANSFORM_COUNT = LEGACY_D3DTS_WORLD + 1; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/formconv.cpp b/Core/Libraries/Source/WWVegas/WW3D2/formconv.cpp index 9ad461efa42..909a44f2711 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/formconv.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/formconv.cpp @@ -36,7 +36,7 @@ *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#include "formconv.h" +#include "dx8formatconv.h" D3DFORMAT WW3DFormatToD3DFormatConversionArray[WW3D_FORMAT_COUNT] = { D3DFMT_UNKNOWN, diff --git a/Core/Libraries/Source/WWVegas/WW3D2/formconv.h b/Core/Libraries/Source/WWVegas/WW3D2/formconv.h index 55fbd189745..16b9baaa608 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/formconv.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/formconv.h @@ -40,16 +40,9 @@ #pragma once #include "ww3dformat.h" -#include /* -** This file is used for conversions between D3DFORMAT and WW3DFormat. +** Neutral include kept for source compatibility. Legacy format conversion +** declarations live in dx8formatconv.h and should only be included by +** compatibility implementation files. */ - -D3DFORMAT WW3DFormat_To_D3DFormat(WW3DFormat ww3d_format); -WW3DFormat D3DFormat_To_WW3DFormat(D3DFORMAT d3d_format); - -D3DFORMAT WW3DZFormat_To_D3DFormat(WW3DZFormat ww3d_zformat); -WW3DZFormat D3DFormat_To_WW3DZFormat(D3DFORMAT d3d_format); - -void Init_D3D_To_WW3_Conversion(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/framgrab.h b/Core/Libraries/Source/WWVegas/WW3D2/framgrab.h index d5d48188d73..9611811f60c 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/framgrab.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/framgrab.h @@ -42,9 +42,11 @@ #pragma warning (push, 3) // (gth) system headers complain at warning level 4... #endif +#ifdef _WIN32 #include "windows.h" #include "windowsx.h" #include "vfw.h" +#endif #if defined (_MSC_VER) #pragma warning (pop) @@ -80,6 +82,7 @@ class FrameGrabClass MODE Mode; long Counter; // used for incrementing filename cunter, etc. +#ifdef _WIN32 void GrabAVI(void *BitmapPointer); void GrabRawFrame(void *BitmapPointer); @@ -95,5 +98,39 @@ class FrameGrabClass // convert the SR image into AVI byte ordering void ConvertFrame(void *BitmapPointer); +#else + void GrabAVI(void * /*BitmapPointer*/) {} + void GrabRawFrame(void * /*BitmapPointer*/) {} + void CleanupAVI() {} + void ConvertFrame(void * /*BitmapPointer*/) {} + + long *Bitmap = nullptr; +#endif }; + +#ifndef _WIN32 +inline FrameGrabClass::FrameGrabClass(const char *filename, MODE mode, int width, int height, int bitdepth, float framerate) + : Filename(filename), FrameRate(framerate), Mode(mode), Counter(0) +{ + (void)bitdepth; + const size_t pixel_count = static_cast(width) * static_cast(height); + Bitmap = (pixel_count > 0) ? new long[pixel_count]{} : nullptr; +} + +inline FrameGrabClass::~FrameGrabClass() +{ + delete[] Bitmap; + Bitmap = nullptr; +} + +inline void FrameGrabClass::ConvertGrab(void *BitmapPointer) +{ + (void)BitmapPointer; +} + +inline void FrameGrabClass::Grab(void *BitmapPointer) +{ + (void)BitmapPointer; +} +#endif diff --git a/Core/Libraries/Source/WWVegas/WW3D2/hanim.cpp b/Core/Libraries/Source/WWVegas/WW3D2/hanim.cpp index 3770dc0ba95..af7c3667fbe 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/hanim.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/hanim.cpp @@ -304,7 +304,7 @@ bool HAnimComboClass::Normalize_Weights() } // weight_total should be very close to 1. If not, normalize this pivot's weights - if (weight_total != 0.0 && WWMath::Fabs( weight_total - 1.0 ) > WWMATH_EPSILON) { + if (weight_total != 0.0 && WWMath::Fabsf_Legacy( weight_total - 1.0 ) > WWMATH_EPSILON) { float oo_total = 1.0f / weight_total; for (anim_idx = 0; anim_idx < anim_count; anim_idx++ ) { if (Peek_Motion(anim_idx) != nullptr ) { @@ -329,7 +329,7 @@ bool HAnimComboClass::Normalize_Weights() } // weight_total should be very close to 1. If not, normalize this pivot's weights - if (weight_total != 0.0 && WWMath::Fabs( weight_total - 1.0 ) > WWMATH_EPSILON) { + if (weight_total != 0.0 && WWMath::Fabsf_Legacy( weight_total - 1.0 ) > WWMATH_EPSILON) { float oo_total = 1.0f / weight_total; for (anim_idx = 0; anim_idx < anim_count; anim_idx++ ) { if (Peek_Motion(anim_idx) != nullptr ) { diff --git a/Core/Libraries/Source/WWVegas/WW3D2/htree.cpp b/Core/Libraries/Source/WWVegas/WW3D2/htree.cpp index 1f04fa29ba0..3167017d3c5 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/htree.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/htree.cpp @@ -866,7 +866,7 @@ void HTreeClass::Combo_Update if (weight_total != 0.0f ) { // SKB: Removed assert because I have a case where I don't want normalization. // One anim moves X, the other moves Y. Assert was just in to warn programmers. -// WWASSERT(WWMath::Fabs( weight_total - 1.0 ) < WWMATH_EPSILON); +// WWASSERT(WWMath::Fabsf( weight_total - 1.0 ) < WWMATH_EPSILON); pivot->Transform.Translate(trans); #ifdef ALLOW_TEMPORARIES @@ -1204,8 +1204,8 @@ HTreeClass * HTreeClass::Create_Interpolated(const HTreeClass * tree_base, // Clone the first one, HTreeClass * new_tree = W3DNEW HTreeClass( *tree_base ); - float a_scale_abs = WWMath::Fabs( a_scale ); - float b_scale_abs = WWMath::Fabs( b_scale ); + float a_scale_abs = WWMath::Fabsf_Legacy( a_scale ); + float b_scale_abs = WWMath::Fabsf_Legacy( b_scale ); if ( a_scale_abs + b_scale_abs > 0 ) { diff --git a/Core/Libraries/Source/WWVegas/WW3D2/indexbuffer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/indexbuffer.cpp new file mode 100644 index 00000000000..311effb536a --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/indexbuffer.cpp @@ -0,0 +1,791 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** 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 . +*/ + +/*********************************************************************************************** + *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** + *********************************************************************************************** + * * + * Project Name : ww3d * + * * + * $Archive:: /Commando/Code/ww3d2/indexbuffer.cpp $* + * * + * Original Author:: Jani Penttinen * + * * + * $Author:: Jani_p $* + * * + * $Modtime:: 11/09/01 3:12p $* + * * + * $Revision:: 26 $* + * * + *---------------------------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +//#define INDEX_BUFFER_LOG + +#include "indexbuffer.h" +#if !defined(GGC_RENDER_BACKEND_BGFX) +#include "dx8indexbuffer.h" +#include "dx8wrapper.h" +#endif +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/renderbufferclasses.h" +#include "WWMath/sphere.h" +#include "WWLib/thread.h" +#include "WWDebug/wwmemlog.h" +#include + +#if defined(GGC_RENDER_BACKEND_BGFX) +#define RENDER_BUFFER_THREAD_ASSERT() +#else +#define RENDER_BUFFER_THREAD_ASSERT() DX8_THREAD_ASSERT() +#endif + +// TheSuperHackers @refactor bobtista 11/04/2026 capture index +// data into the active render backend at write-lock time. See the +// matching comment in vertexbuffer.cpp. + +static constexpr unsigned short kDefaultDynamicIndexBufferSize = 5000; + +static bool _DynamicSortingIndexArrayInUse=false; +static SortingIndexBufferClass* _DynamicSortingIndexArray; +static unsigned short _DynamicSortingIndexArraySize=0; +static unsigned short _DynamicSortingIndexArrayOffset=0; + +static bool _DynamicBackendIndexBufferInUse=false; +static RenderIndexBufferClass* _DynamicBackendIndexBuffer=nullptr; +static unsigned short _DynamicBackendIndexBufferSize=kDefaultDynamicIndexBufferSize; +static unsigned short _DynamicBackendIndexBufferOffset=0; + +static int _IndexBufferCount; +static int _IndexBufferTotalIndices; +static int _IndexBufferTotalSize; + +#if !defined(GGC_RENDER_BACKEND_BGFX) +using LegacyIndexBuffer = IDirect3DIndexBuffer8; + +constexpr unsigned kLegacyBufferUsageWriteOnly = D3DUSAGE_WRITEONLY, kLegacyBufferUsageDynamic = D3DUSAGE_DYNAMIC, kLegacyBufferUsageNPatches = D3DUSAGE_NPATCHES, kLegacyBufferUsageSoftwareProcessing = D3DUSAGE_SOFTWAREPROCESSING; +constexpr auto kLegacyIndexFormat = D3DFMT_INDEX16; + +static unsigned BuildLegacyBufferUsage(DX8IndexBufferClass::UsageType usage) +{ + return kLegacyBufferUsageWriteOnly | + ((usage&DX8IndexBufferClass::USAGE_DYNAMIC) ? kLegacyBufferUsageDynamic : 0) | + ((usage&DX8IndexBufferClass::USAGE_NPATCHES) ? kLegacyBufferUsageNPatches : 0) | + ((usage&DX8IndexBufferClass::USAGE_SOFTWAREPROCESSING) ? kLegacyBufferUsageSoftwareProcessing : 0); +} + +static auto GetLegacyBufferPool(DX8IndexBufferClass::UsageType usage) +{ + return (usage&DX8IndexBufferClass::USAGE_DYNAMIC) ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED; +} + +static auto Legacy_Device() +{ + return DX8Wrapper::_Get_D3D_Device8(); +} + +static LegacyIndexBuffer *Legacy_Index_Buffer(DX8IndexBufferClass *index_buffer) +{ + return static_cast(index_buffer->Get_Legacy_Index_Buffer()); +} +#endif + +// ---------------------------------------------------------------------------- +// +// +// +// ---------------------------------------------------------------------------- + +IndexBufferClass::IndexBufferClass(unsigned type_, unsigned short index_count_) + : + index_count(index_count_), + type(type_), + engine_refs(0), + CPUBufferData(nullptr), + CPUBufferSize(0), + CPUBufferValid(false), + m_backendStaticEligible(false) +{ + m_backendHandle = kInvalidRenderResource; + WWASSERT(type==BUFFER_TYPE_STATIC || type==BUFFER_TYPE_SORTING); + WWASSERT(index_count); + + _IndexBufferCount++; + _IndexBufferTotalIndices+=index_count; + _IndexBufferTotalSize+=index_count*sizeof(unsigned short); +#ifdef VERTEX_BUFFER_LOG + WWDEBUG_SAY(("New IB, %d indices, size %d bytes",index_count,index_count*sizeof(unsigned short))); + WWDEBUG_SAY(("Total IB count: %d, total %d indices, total size %d bytes", + _IndexBufferCount, + _IndexBufferTotalIndices, + _IndexBufferTotalSize)); +#endif +} + +IndexBufferClass::~IndexBufferClass() +{ + _IndexBufferCount--; + _IndexBufferTotalIndices-=index_count; + _IndexBufferTotalSize-=index_count*sizeof(unsigned short); + #ifdef VERTEX_BUFFER_LOG + WWDEBUG_SAY(("Delete IB, %d indices, size %d bytes",index_count,index_count*sizeof(unsigned short))); + WWDEBUG_SAY(("Total IB count: %d, total %d indices, total size %d bytes", + _IndexBufferCount, + _IndexBufferTotalIndices, + _IndexBufferTotalSize)); + #endif + delete[] CPUBufferData; +} + +unsigned IndexBufferClass::Get_Total_Buffer_Count() +{ + return _IndexBufferCount; +} + +unsigned IndexBufferClass::Get_Total_Allocated_Indices() +{ + return _IndexBufferTotalIndices; +} + +unsigned IndexBufferClass::Get_Total_Allocated_Memory() +{ + return _IndexBufferTotalSize; +} + +void *IndexBufferClass::Lock_CPU_Buffer_Data(unsigned byte_offset, unsigned size) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + if (type == BUFFER_TYPE_STATIC && m_backendHandle == kInvalidRenderResource) { + WWASSERT_PRINT( + false, + "IndexBufferClass::Lock_CPU_Buffer_Data: standalone bgfx static index buffers require a backend resource"); + return nullptr; + } +#endif + const unsigned total_size = index_count * sizeof(unsigned short); + if (byte_offset > total_size || size > total_size - byte_offset) { + WWASSERT(0); + return nullptr; + } + + if (CPUBufferData == nullptr) { + CPUBufferData = W3DNEWARRAY unsigned char[total_size]; + std::memset(CPUBufferData, 0, total_size); + CPUBufferSize = total_size; + } + CPUBufferValid = true; + return CPUBufferData + byte_offset; +} + +void IndexBufferClass::Update_CPU_Buffer_Data(unsigned byte_offset, const void * data, unsigned size) +{ + if (data == nullptr || size == 0) { + return; + } + +#if defined(GGC_RENDER_BACKEND_BGFX) + if (type == BUFFER_TYPE_STATIC && m_backendHandle == kInvalidRenderResource) { + WWASSERT_PRINT( + false, + "IndexBufferClass::Update_CPU_Buffer_Data: standalone bgfx static index buffers require a backend resource"); + return; + } +#endif + const unsigned total_size = index_count * sizeof(unsigned short); + if (byte_offset > total_size || size > total_size - byte_offset) { + WWASSERT(0); + return; + } + + if (CPUBufferData == nullptr) { + CPUBufferData = W3DNEWARRAY unsigned char[total_size]; + std::memset(CPUBufferData, 0, total_size); + CPUBufferSize = total_size; + } + + std::memcpy(CPUBufferData + byte_offset, data, size); + CPUBufferValid = true; +} + +void IndexBufferClass::Add_Engine_Ref() const +{ + engine_refs++; +} + +void IndexBufferClass::Release_Engine_Ref() const +{ + engine_refs--; + WWASSERT(engine_refs>=0); +} + +// ---------------------------------------------------------------------------- +// +// +// +// ---------------------------------------------------------------------------- + +void IndexBufferClass::Copy(unsigned int* indices,unsigned first_index,unsigned count) +{ + WWASSERT(indices); + + if (first_index) { + IndexBufferClass::AppendLockClass l(this,first_index,count); + unsigned short* inds=l.Get_Index_Array(); + for (unsigned v=0;vEngine_Refs()); + index_buffer->Add_Ref(); + switch (index_buffer->Type()) { + case BUFFER_TYPE_STATIC: +#if !defined(GGC_RENDER_BACKEND_BGFX) + DX8_Assert(); + if (LegacyIndexBuffer *legacy = Legacy_Index_Buffer(static_cast(index_buffer))) { + DX8_ErrorCode(legacy->Lock( + 0, + index_buffer->Get_Index_Count()*sizeof(WORD), + (unsigned char**)&indices, + flags)); + } else +#endif + { + indices = static_cast(index_buffer->Lock_CPU_Buffer_Data( + 0, + index_buffer->Get_Index_Count()*sizeof(WORD))); + } + break; + case BUFFER_TYPE_SORTING: + indices=static_cast(index_buffer)->index_buffer; + break; + default: + WWASSERT(0); + break; + } +} + +// ---------------------------------------------------------------------------- +// +// +// ---------------------------------------------------------------------------- + +IndexBufferClass::WriteLockClass::~WriteLockClass() +{ + RENDER_BUFFER_THREAD_ASSERT(); + // TheSuperHackers @refactor bobtista 11/04/2026 Capture index data (including + // BUFFER_TYPE_SORTING) into the render backend before Unlock invalidates the pointer. + if (indices != NULL && + (index_buffer->Type() == BUFFER_TYPE_STATIC || index_buffer->Type() == BUFFER_TYPE_SORTING)) { + const unsigned int total_bytes = index_buffer->Get_Index_Count() * sizeof(unsigned short); + index_buffer->Update_CPU_Buffer_Data(0, indices, total_bytes); + if (g_renderBackend != NULL) { + g_renderBackend->Upload_Index_Buffer_Data(index_buffer, indices, total_bytes); + } + } + switch (index_buffer->Type()) { + case BUFFER_TYPE_STATIC: +#if !defined(GGC_RENDER_BACKEND_BGFX) + DX8_Assert(); + if (LegacyIndexBuffer *legacy = Legacy_Index_Buffer(static_cast(index_buffer))) { + DX8_ErrorCode(legacy->Unlock()); + } +#endif + break; + case BUFFER_TYPE_SORTING: + break; + default: + WWASSERT(0); + break; + } + index_buffer->Release_Ref(); +} + +// ---------------------------------------------------------------------------- + +IndexBufferClass::AppendLockClass::AppendLockClass(IndexBufferClass* index_buffer_,unsigned start_index, unsigned index_range, unsigned flags) + : + index_buffer(index_buffer_), + AppendStartIndex(start_index), + AppendIndexRange(index_range) +{ + RENDER_BUFFER_THREAD_ASSERT(); + WWASSERT(start_index+index_range<=index_buffer->Get_Index_Count()); + WWASSERT(index_buffer); + WWASSERT(!index_buffer->Engine_Refs()); + index_buffer->Add_Ref(); + switch (index_buffer->Type()) { + case BUFFER_TYPE_STATIC: +#if !defined(GGC_RENDER_BACKEND_BGFX) + DX8_Assert(); + if (LegacyIndexBuffer *legacy = Legacy_Index_Buffer(static_cast(index_buffer))) { + DX8_ErrorCode(legacy->Lock( + start_index*sizeof(unsigned short), + index_range*sizeof(unsigned short), + (unsigned char**)&indices, + flags)); + } else +#endif + { + indices = static_cast(index_buffer->Lock_CPU_Buffer_Data( + start_index*sizeof(unsigned short), + index_range*sizeof(unsigned short))); + } + break; + case BUFFER_TYPE_SORTING: + indices=static_cast(index_buffer)->index_buffer+start_index; + break; + default: + WWASSERT(0); + break; + } +} + +// ---------------------------------------------------------------------------- + +IndexBufferClass::AppendLockClass::~AppendLockClass() +{ + RENDER_BUFFER_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 (indices != NULL && + (index_buffer->Type() == BUFFER_TYPE_STATIC || index_buffer->Type() == BUFFER_TYPE_SORTING)) { + const unsigned int size_bytes = AppendIndexRange * sizeof(unsigned short); + index_buffer->Update_CPU_Buffer_Data(AppendStartIndex * sizeof(unsigned short), indices, size_bytes); + if (g_renderBackend != NULL) { + g_renderBackend->Upload_Index_Buffer_Sub_Range(index_buffer, indices, AppendStartIndex, size_bytes); + } + } + switch (index_buffer->Type()) { + case BUFFER_TYPE_STATIC: +#if !defined(GGC_RENDER_BACKEND_BGFX) + DX8_Assert(); + if (LegacyIndexBuffer *legacy = Legacy_Index_Buffer(static_cast(index_buffer))) { + DX8_ErrorCode(legacy->Unlock()); + } +#endif + break; + case BUFFER_TYPE_SORTING: + break; + default: + WWASSERT(0); + break; + } + index_buffer->Release_Ref(); +} + +// ---------------------------------------------------------------------------- +// +// +// +// ---------------------------------------------------------------------------- + +#if !defined(GGC_RENDER_BACKEND_BGFX) +DX8IndexBufferClass::DX8IndexBufferClass(unsigned short index_count_,UsageType usage) + : + IndexBufferClass(BUFFER_TYPE_STATIC,index_count_) +{ + RENDER_BUFFER_THREAD_ASSERT(); + WWASSERT(index_count); + Set_Backend_Static_Eligible((usage & USAGE_DYNAMIC) == 0); + if (g_renderBackend != nullptr && !g_renderBackend->Requires_Legacy_Buffer_Resources()) { + m_backendHandle = g_renderBackend->Register_Index_Buffer_Resource(this); + return; + } + +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT(0); + return; +#else + unsigned usage_flags=BuildLegacyBufferUsage(usage); + if (!g_renderBackend || !g_renderBackend->Supports_Hardware_Transform_And_Lighting()) { + usage_flags|=kLegacyBufferUsageSoftwareProcessing; + } + + LegacyIndexBuffer *new_index_buffer = nullptr; + HRESULT ret=Legacy_Device()->CreateIndexBuffer( + sizeof(WORD)*index_count, + usage_flags, + kLegacyIndexFormat, + GetLegacyBufferPool(usage), + &new_index_buffer); + index_buffer = new_index_buffer; + + if (SUCCEEDED(ret)) { + //: populate backend-neutral handle. + if (g_renderBackend != nullptr) { + m_backendHandle = g_renderBackend->Register_Index_Buffer_Resource(this); + } + return; + } + + WWDEBUG_SAY(("Index buffer creation failed, trying to release assets...")); + + // Index buffer creation failed, so try releasing least used textures and flushing the mesh cache. + + // Free all textures that haven't been used in the last 5 seconds + TextureClass::Invalidate_Old_Unused_Textures(5000); + + // Invalidate the mesh cache + WW3D::_Invalidate_Mesh_Cache(); + + // Try again... + new_index_buffer = nullptr; + ret=Legacy_Device()->CreateIndexBuffer( + sizeof(WORD)*index_count, + usage_flags, + kLegacyIndexFormat, + GetLegacyBufferPool(usage), + &new_index_buffer); + index_buffer = new_index_buffer; + + if (SUCCEEDED(ret)) { + WWDEBUG_SAY(("...Index buffer creation successful")); + if (g_renderBackend != nullptr) { + m_backendHandle = g_renderBackend->Register_Index_Buffer_Resource(this); + } + } + + // If it still fails it is fatal + DX8_ErrorCode(ret); +#endif +} + +// ---------------------------------------------------------------------------- + +DX8IndexBufferClass::~DX8IndexBufferClass() +{ + //: release backend-neutral handle before the legacy resource. + if (m_backendHandle != kInvalidRenderResource && g_renderBackend != nullptr) { + g_renderBackend->Destroy_Resource(m_backendHandle); + m_backendHandle = kInvalidRenderResource; + } + if (LegacyIndexBuffer *legacy = Legacy_Index_Buffer(this)) { + legacy->Release(); + } +} +#endif + +// ---------------------------------------------------------------------------- + +#if defined(GGC_RENDER_BACKEND_BGFX) +RenderIndexBufferClass::RenderIndexBufferClass(unsigned short index_count_, UsageType usage) + : + IndexBufferClass(BUFFER_TYPE_STATIC, index_count_) +{ + RENDER_BUFFER_THREAD_ASSERT(); + WWASSERT(index_count); + Set_Backend_Static_Eligible((usage & USAGE_DYNAMIC) == 0); + if (g_renderBackend != nullptr) { + m_backendHandle = g_renderBackend->Register_Index_Buffer_Resource(this); + } +} + +RenderIndexBufferClass::~RenderIndexBufferClass() +{ + if (m_backendHandle != kInvalidRenderResource && g_renderBackend != nullptr) { + g_renderBackend->Destroy_Resource(m_backendHandle); + m_backendHandle = kInvalidRenderResource; + } +} +#endif + +// ---------------------------------------------------------------------------- +// +// +// +// ---------------------------------------------------------------------------- + +SortingIndexBufferClass::SortingIndexBufferClass(unsigned short index_count_) + : + IndexBufferClass(BUFFER_TYPE_SORTING,index_count_) +{ + WWMEMLOG(MEM_RENDERER); + WWASSERT(index_count); + + index_buffer=W3DNEWARRAY unsigned short[index_count]; +} + +// ---------------------------------------------------------------------------- + +SortingIndexBufferClass::~SortingIndexBufferClass() +{ + delete[] index_buffer; +} + +// ---------------------------------------------------------------------------- +// +// +// +// ---------------------------------------------------------------------------- + +DynamicIBAccessClass::DynamicIBAccessClass(unsigned short type_, unsigned short index_count_) + : + IndexCount(index_count_), + IndexBuffer(nullptr), + Type(type_) +{ + WWASSERT(Type==BUFFER_TYPE_DYNAMIC || Type==BUFFER_TYPE_DYNAMIC_SORTING); + if (Type==BUFFER_TYPE_DYNAMIC) { + Allocate_Backend_Dynamic_Buffer(); + } + else { + Allocate_Sorting_Dynamic_Buffer(); + } +} + +DynamicIBAccessClass::~DynamicIBAccessClass() +{ + REF_PTR_RELEASE(IndexBuffer); + if (Type==BUFFER_TYPE_DYNAMIC) { + _DynamicBackendIndexBufferInUse=false; + _DynamicBackendIndexBufferOffset+=IndexCount; + } + else { + _DynamicSortingIndexArrayInUse=false; + _DynamicSortingIndexArrayOffset+=IndexCount; + } +} + +void DynamicIBAccessClass::_Deinit() +{ + WWASSERT ((_DynamicBackendIndexBuffer == nullptr) || (_DynamicBackendIndexBuffer->Num_Refs() == 1)); + REF_PTR_RELEASE(_DynamicBackendIndexBuffer); + _DynamicBackendIndexBufferInUse=false; + _DynamicBackendIndexBufferSize=kDefaultDynamicIndexBufferSize; + _DynamicBackendIndexBufferOffset=0; + + WWASSERT ((_DynamicSortingIndexArray == nullptr) || (_DynamicSortingIndexArray->Num_Refs() == 1)); + REF_PTR_RELEASE(_DynamicSortingIndexArray); + _DynamicSortingIndexArrayInUse=false; + _DynamicSortingIndexArraySize=0; + _DynamicSortingIndexArrayOffset=0; +} + +// ---------------------------------------------------------------------------- +// +// +// +// ---------------------------------------------------------------------------- + +DynamicIBAccessClass::WriteLockClass::WriteLockClass(DynamicIBAccessClass* ib_access_) + : + DynamicIBAccess(ib_access_), + Indices(NULL), + DirectBackendWrite(false) +{ + RENDER_BUFFER_THREAD_ASSERT(); + DynamicIBAccess->IndexBuffer->Add_Ref(); + switch (DynamicIBAccess->Get_Type()) { + case BUFFER_TYPE_DYNAMIC: + WWASSERT(DynamicIBAccess); +// WWASSERT(!dynamic_dx8_index_buffer->Engine_Refs()); +#if !defined(GGC_RENDER_BACKEND_BGFX) + DX8_Assert(); + if (LegacyIndexBuffer *legacy = Legacy_Index_Buffer(static_cast(DynamicIBAccess->IndexBuffer))) { + DX8_ErrorCode(legacy->Lock( + DynamicIBAccess->IndexBufferOffset*sizeof(WORD), + DynamicIBAccess->Get_Index_Count()*sizeof(WORD), + (unsigned char**)&Indices, + !DynamicIBAccess->IndexBufferOffset ? RB_LOCK_DISCARD : RB_LOCK_NOOVERWRITE)); + } else +#endif + { + const unsigned int ib_bytes = DynamicIBAccess->Get_Index_Count() * sizeof(unsigned short); + if (g_renderBackend != NULL) { + Indices = static_cast( + g_renderBackend->Begin_Dynamic_Index_Write(DynamicIBAccess, ib_bytes)); + } + if (Indices != NULL) { + DirectBackendWrite = true; + } else { + Indices = static_cast(DynamicIBAccess->IndexBuffer->Lock_CPU_Buffer_Data( + DynamicIBAccess->IndexBufferOffset*sizeof(WORD), + ib_bytes)); + } + } + break; + case BUFFER_TYPE_DYNAMIC_SORTING: + Indices=static_cast(DynamicIBAccess->IndexBuffer)->index_buffer; + Indices+=DynamicIBAccess->IndexBufferOffset; + break; + default: + WWASSERT(0); + break; + } +} + +DynamicIBAccessClass::WriteLockClass::~WriteLockClass() +{ + RENDER_BUFFER_THREAD_ASSERT(); + switch (DynamicIBAccess->Get_Type()) { + case BUFFER_TYPE_DYNAMIC: + // 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); + if (DirectBackendWrite) { + g_renderBackend->End_Dynamic_Index_Write(DynamicIBAccess, Indices, total_bytes); + } else { + g_renderBackend->Capture_Dynamic_Index_Data(DynamicIBAccess, Indices, total_bytes); + } + } +#if !defined(GGC_RENDER_BACKEND_BGFX) + DX8_Assert(); + if (LegacyIndexBuffer *legacy = Legacy_Index_Buffer(static_cast(DynamicIBAccess->IndexBuffer))) { + DX8_ErrorCode(legacy->Unlock()); + } +#endif + break; + case BUFFER_TYPE_DYNAMIC_SORTING: + break; + default: + WWASSERT(0); + break; + } + DynamicIBAccess->IndexBuffer->Release_Ref(); +} + +// ---------------------------------------------------------------------------- +// +// +// +// ---------------------------------------------------------------------------- + +void DynamicIBAccessClass::Allocate_Backend_Dynamic_Buffer() +{ + WWMEMLOG(MEM_RENDERER); + WWASSERT(!_DynamicBackendIndexBufferInUse); + _DynamicBackendIndexBufferInUse=true; + + // If requesting more indices than dynamic index buffer can fit, delete the ib + // and adjust the size to the new count. + if (IndexCount>_DynamicBackendIndexBufferSize) { + REF_PTR_RELEASE(_DynamicBackendIndexBuffer); + _DynamicBackendIndexBufferSize=IndexCount; + if (_DynamicBackendIndexBufferSizeSupports_NPatches()) { + usage|=RenderIndexBufferClass::USAGE_NPATCHES; + } + + _DynamicBackendIndexBuffer=NEW_REF(RenderIndexBufferClass,( + _DynamicBackendIndexBufferSize, + (RenderIndexBufferClass::UsageType)usage)); + _DynamicBackendIndexBufferOffset=0; + } + + // Any room at the end of the buffer? + if (((unsigned)IndexCount+_DynamicBackendIndexBufferOffset)>_DynamicBackendIndexBufferSize) { + _DynamicBackendIndexBufferOffset=0; + } + + REF_PTR_SET(IndexBuffer,_DynamicBackendIndexBuffer); + IndexBufferOffset=_DynamicBackendIndexBufferOffset; +} + +void DynamicIBAccessClass::Allocate_Sorting_Dynamic_Buffer() +{ + WWMEMLOG(MEM_RENDERER); + WWASSERT(!_DynamicSortingIndexArrayInUse); + _DynamicSortingIndexArrayInUse=true; + + unsigned new_index_count=(unsigned)_DynamicSortingIndexArrayOffset+IndexCount; + // TheSuperHackers @bugfix bobtista 13/07/2026 Start a fresh buffer when the request would + // cross the 65535 index capacity of SortingIndexBufferClass. The size was silently + // truncated to 16 bits, so the subsequent index writes overflowed the allocation. + // Draws queued earlier hold their own reference to the old buffer, so their data stays valid. + if (new_index_count>65535) { + REF_PTR_RELEASE(_DynamicSortingIndexArray); + _DynamicSortingIndexArraySize=IndexCount; + if (_DynamicSortingIndexArraySize_DynamicSortingIndexArraySize) { + REF_PTR_RELEASE(_DynamicSortingIndexArray); + _DynamicSortingIndexArraySize=new_index_count; + if (_DynamicSortingIndexArraySize r.X) return true; - if (WWMath::Fabs(dc.Y) > r.Y) return true; - if (WWMath::Fabs(dc.Z) > r.Z) return true; + if (WWMath::Fabsf_Legacy(dc.X) > r.X) return true; + if (WWMath::Fabsf_Legacy(dc.Y) > r.Y) return true; + if (WWMath::Fabsf_Legacy(dc.Z) > r.Z) return true; return false; } @@ -228,9 +228,9 @@ inline bool OBBoxIntersectionTestClass::Cull(const AABoxClass & cull_box) Vector3::Subtract(cull_box.Center,BoundingBox.Center,&dc); Vector3::Add(cull_box.Extent,BoundingBox.Extent,&r); - if (WWMath::Fabs(dc.X) > r.X) return true; - if (WWMath::Fabs(dc.Y) > r.Y) return true; - if (WWMath::Fabs(dc.Z) > r.Z) return true; + if (WWMath::Fabsf_Legacy(dc.X) > r.X) return true; + if (WWMath::Fabsf_Legacy(dc.Y) > r.Y) return true; + if (WWMath::Fabsf_Legacy(dc.Z) > r.Z) return true; return false; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/line3d.cpp b/Core/Libraries/Source/WWVegas/WW3D2/line3d.cpp index 0bfaa121587..d9ec4dcaf2c 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/line3d.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/line3d.cpp @@ -53,10 +53,12 @@ #include "WWDebug/wwdebug.h" #include "WW3D2/ww3d.h" #include "WW3D2/rinfo.h" -#include "dx8wrapper.h" -#include "dx8vertexbuffer.h" -#include "dx8indexbuffer.h" +#include "WW3D2/ww3dcolor.h" +#include "WW3D2/vertexbuffer.h" +#include "WW3D2/indexbuffer.h" #include "dx8fvf.h" +#include "RenderBackend.h" +#include "IRenderBackend.h" // 12 Triangles for index buffer const unsigned short Indices[]= @@ -267,21 +269,21 @@ 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); + DynamicVBAccessClass vb(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,8); { DynamicVBAccessClass::WriteLockClass Lock(&vb); const FVFInfoClass &fi=vb.FVF_Info(); unsigned char *vb=(unsigned char*)Lock.Get_Formatted_Vertex_Array(); int i; - unsigned int color=DX8Wrapper::Convert_Color(Color); + unsigned int color=WW3DColor::To_ARGB(Color); for (i=0; i<8; i++) { @@ -291,7 +293,7 @@ void Line3DClass::Render(RenderInfoClass & rinfo) } } - DynamicIBAccessClass ib(BUFFER_TYPE_DYNAMIC_DX8,36); + DynamicIBAccessClass ib(BUFFER_TYPE_DYNAMIC,36); { DynamicIBAccessClass::WriteLockClass Lock(&ib); unsigned short *mem=Lock.Get_Index_Array(); @@ -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); } /************************************************************************** @@ -511,4 +513,3 @@ int Line3DClass::Get_Num_Polys() const { return 12; } - diff --git a/Core/Libraries/Source/WWVegas/WW3D2/matpass.cpp b/Core/Libraries/Source/WWVegas/WW3D2/matpass.cpp index d300e5ffe36..61a1f32d882 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/matpass.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/matpass.cpp @@ -51,7 +51,8 @@ #include "WW3D2/shader.h" #include "texture.h" #include "statistics.h" -#include "dx8wrapper.h" +#include "RenderBackend.h" +#include "IRenderBackend.h" bool MaterialPassClass::EnablePerPolygonCulling = true; @@ -117,11 +118,11 @@ MaterialPassClass::~MaterialPassClass() *=============================================================================================*/ void MaterialPassClass::Install_Materials() const { - DX8Wrapper::Set_Material(Peek_Material()); - DX8Wrapper::Set_Shader(Peek_Shader()); - for (int i=0;iGet_Max_Textures_Per_Pass();++i) + g_renderBackend->Set_Material(Peek_Material()); + g_renderBackend->Set_Shader(Peek_Shader()); + for (int i=0;iGet_Max_Texture_Stages();++i) { - DX8Wrapper::Set_Texture(i,Peek_Texture(i)); + g_renderBackend->Set_Texture(i,Peek_Texture(i)); } } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/matpass.h b/Core/Libraries/Source/WWVegas/WW3D2/matpass.h index 53dd32afb62..a157cf8a83c 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/matpass.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/matpass.h @@ -70,6 +70,7 @@ class MaterialPassClass : public RefCountClass /// MW: Had to make this virtual so app can perform direct/custom D3D setup. virtual void Install_Materials() const; virtual void UnInstall_Materials() const { }; ///< reset/cleanup D3D states + virtual void Set_Context_Texture(TextureClass * Texture,int stage = 0) { }; void Set_Texture(TextureClass * Texture,int stage = 0); void Set_Shader(ShaderClass shader); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/metalmap.cpp b/Core/Libraries/Source/WWVegas/WW3D2/metalmap.cpp index ba9a32ca3f0..ac6939406c8 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/metalmap.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/metalmap.cpp @@ -306,9 +306,12 @@ void MetalMapManagerClass::Update_Textures() cur_params.AmbientColor.Z * CurrentAmbient.Z); Vector3 white(1.0f, 1.0f, 1.0f); - SurfaceClass * metal_map_surface = Textures[i]->Get_Surface_Level(0); - int pitch; - unsigned char *map=(unsigned char *) metal_map_surface->Lock(&pitch); + TextureClass::MutableTextureMipView mip = Textures[i]->Begin_Mip_Write(0); + if (!mip.Is_Valid()) { + continue; + } + + unsigned char *map = mip.Data; int idx=0; for (int y = 0; y < METALMAP_SIZE; y++) { for (int x = 0; x < METALMAP_SIZE; x++) { @@ -316,9 +319,9 @@ void MetalMapManagerClass::Update_Textures() result.Update_Min(white); // Clamp to white unsigned char b,g,r,a; - b= (unsigned char)WWMath::Floor(result.Z * 255.99f); // B - g= (unsigned char)WWMath::Floor(result.Y * 255.99f); // G - r= (unsigned char)WWMath::Floor(result.X * 255.99f); // R + b= (unsigned char)WWMath::Floorf(result.Z * 255.99f); // B + g= (unsigned char)WWMath::Floorf(result.Y * 255.99f); // G + r= (unsigned char)WWMath::Floorf(result.X * 255.99f); // R a= 0xFF; // A if (Use16Bit) { @@ -336,10 +339,9 @@ void MetalMapManagerClass::Update_Textures() } idx++; } - map+=pitch; + map+=mip.Pitch; } - metal_map_surface->Unlock(); - REF_PTR_RELEASE(metal_map_surface); + Textures[i]->End_Mip_Write(0); } } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/missingtexture.cpp b/Core/Libraries/Source/WWVegas/WW3D2/missingtexture.cpp index 0a2867b12b7..6f07f7ca924 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/missingtexture.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/missingtexture.cpp @@ -18,9 +18,10 @@ // 08/05/02 KM Texture class redesign #include "missingtexture.h" -#include "texture.h" -#include "dx8wrapper.h" -#include +#include "texturecompat.h" + +#include +#include static unsigned missing_image_width=128; static unsigned missing_image_height=128; @@ -29,94 +30,15 @@ static unsigned missing_image_depth=24; extern unsigned int missing_image_palette[]; extern unsigned int missing_image_pixels[]; -static IDirect3DTexture8 * _MissingTexture = nullptr; - -IDirect3DTexture8* MissingTexture::_Get_Missing_Texture() -{ - WWASSERT(_MissingTexture); - _MissingTexture->AddRef(); - return _MissingTexture; -} - -IDirect3DSurface8* MissingTexture::_Create_Missing_Surface() -{ - IDirect3DSurface8 *texture_surface = nullptr; - DX8_ErrorCode(_MissingTexture->GetSurfaceLevel(0, &texture_surface)); - D3DSURFACE_DESC texture_surface_desc; - ::ZeroMemory(&texture_surface_desc, sizeof(D3DSURFACE_DESC)); - DX8_ErrorCode(texture_surface->GetDesc(&texture_surface_desc)); - - IDirect3DSurface8 *surface = nullptr; - DX8CALL(CreateImageSurface( - texture_surface_desc.Width, - texture_surface_desc.Height, - texture_surface_desc.Format, - &surface)); - DX8CALL(CopyRects(texture_surface, nullptr, 0, surface, nullptr)); - texture_surface->Release(); - return surface; -} - void MissingTexture::_Init() { - WWASSERT(!_MissingTexture); - - IDirect3DTexture8* tex=DX8Wrapper::_Create_DX8_Texture - ( +#if defined(GGC_RENDER_BACKEND_BGFX) + return; +#endif + Init_Legacy_Missing_Texture( missing_image_width, missing_image_height, - WW3D_FORMAT_A8R8G8B8, - MIP_LEVELS_ALL - ); - - D3DLOCKED_RECT locked_rect; - RECT rect; - rect.left=0; - rect.right=missing_image_width; - rect.top=0; - rect.bottom=missing_image_height; - DX8_ErrorCode( - tex->LockRect( - 0, - &locked_rect, - &rect, - 0)); - - unsigned *buffer=(unsigned*)locked_rect.pBits; - unsigned char *pixels=(unsigned char *)missing_image_pixels; - for (unsigned y=0;yUnlockRect(0)); - - for (unsigned i=1;iGetLevelCount();++i) { - IDirect3DSurface8 *src,*dst; - DX8_ErrorCode(tex->GetSurfaceLevel(i-1,&src)); - DX8_ErrorCode(tex->GetSurfaceLevel(i,&dst)); - - DX8_ErrorCode(D3DXLoadSurfaceFromSurface( - dst, - nullptr, // palette - nullptr, // rect - src, - nullptr, // palette - nullptr, // rect - D3DX_FILTER_BOX, // box is good for 2:1 filtering - 0)); - - src->Release(); - dst->Release(); - } - - _MissingTexture=tex; + missing_image_pixels); /* //Load an 8-bit tga and generate text representation FILE *fp; @@ -158,8 +80,45 @@ void MissingTexture::_Init() void MissingTexture::_Deinit() { - _MissingTexture->Release(); - _MissingTexture=nullptr; + Release_Legacy_Missing_Texture(); +} + +void MissingTexture::Build_CPU_Texture_Mips(std::vector &mips) +{ + constexpr unsigned kMissingPixel = 0x7FFF00FF; + unsigned width = missing_image_width; + unsigned height = missing_image_height; + mips.clear(); + + while (width > 0 && height > 0) + { + TextureBaseClass::TextureMipSnapshot mip; + mip.Width = width; + mip.Height = height; + mip.Pitch = width * sizeof(kMissingPixel); + mip.Format = WW3D_FORMAT_A8R8G8B8; + mip.Data.resize(static_cast(mip.Pitch) * height); + + for (unsigned y = 0; y < height; ++y) + { + unsigned char *row = mip.Data.data() + static_cast(mip.Pitch) * y; + for (unsigned x = 0; x < width; ++x) + { + std::memcpy(row + x * sizeof(kMissingPixel), &kMissingPixel, sizeof(kMissingPixel)); + } + } + + mips.push_back(std::move(mip)); + if (width == 1 && height == 1) { + break; + } + if (width > 1) { + width >>= 1; + } + if (height > 1) { + height >>= 1; + } + } } unsigned int missing_image_palette[]={ diff --git a/Core/Libraries/Source/WWVegas/WW3D2/missingtexture.h b/Core/Libraries/Source/WWVegas/WW3D2/missingtexture.h index 56081d7a8aa..14bb213551e 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/missingtexture.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/missingtexture.h @@ -19,16 +19,14 @@ #pragma once #include "WWLib/always.h" +#include "WW3D2/texture.h" -struct IDirect3DTexture8; -struct IDirect3DSurface8; +#include class MissingTexture { public: static void _Init(); static void _Deinit(); - - static IDirect3DTexture8* _Get_Missing_Texture(); // Return a reference to missing texture - static IDirect3DSurface8* _Create_Missing_Surface(); // Create new surface which contain missing texture image + static void Build_CPU_Texture_Mips(std::vector &mips); }; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/pointgr.cpp b/Core/Libraries/Source/WWVegas/WW3D2/pointgr.cpp index 4bbbd6f0534..c864ea42da4 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/pointgr.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/pointgr.cpp @@ -80,14 +80,22 @@ #include "WWLib/Vector.h" #include "WWMath/vp.h" #include "WWMath/matrix4.h" -#include "dx8wrapper.h" -#include "dx8vertexbuffer.h" -#include "dx8indexbuffer.h" +#include "WW3D2/ww3dcolor.h" +#include "WW3D2/vertexbuffer.h" +#include "WW3D2/indexbuffer.h" #include "WW3D2/rinfo.h" #include "WW3D2/camera.h" #include "dx8fvf.h" -#include "d3dx8math.h" #include "sortingrenderer.h" +#include "RenderBackend.h" +#include "IRenderBackend.h" +#include "renderbufferclasses.h" +#include "BgfxRenderProfile.h" +#include "GgcRuntimeFlags.h" + +#include +#include +#include // Upgraded to DX8 2/2/01 HY @@ -126,14 +134,25 @@ VectorClass VertexLoc; // camera-space vertex locations VectorClass VertexDiffuse; // vertex diffuse/alpha colors VectorClass VertexUV; // vertex texture coords -// Some DX 8 variables +// TheSuperHackers @performance bobtista 03/06/2026 Scratch accumulators used to +// merge the per-depth-layer draws of one volume particle emitter into a single +// Draw_Triangles. Reused across calls (grow-only) to avoid per-frame allocation. +static VectorClass s_volMergeLoc; +static VectorClass s_volMergeUV; +static VectorClass s_volMergeColor; +static int s_volMergeCount = 0; +static int s_volMergeVerticesPerPrimitive = 3; +static bool s_volMergeSort = false; +static IndexBufferClass * s_volMergeIndexBuffer = nullptr; + +// Some render buffer constants #define MAX_VB_SIZE 2048 #define MAX_TRI_POINTS MAX_VB_SIZE/3 #define MAX_TRI_IB_SIZE 3*MAX_TRI_POINTS #define MAX_QUAD_POINTS MAX_VB_SIZE/4 #define MAX_QUAD_IB_SIZE 6*MAX_QUAD_POINTS -DX8IndexBufferClass *Tris, *Quads; // Index buffers. +RenderIndexBufferClass *Tris, *Quads; // Index buffers. SortingIndexBufferClass *SortingTris, *SortingQuads; // Sorting index buffers. /************************************************************************** @@ -819,6 +838,7 @@ void PointGroupClass::Render(RenderInfoClass &rinfo) // If there is an active point table, use it to compress the point // locations/colors/alphas/sizes/orientations/frames. if (APT) { + GGC_RPROFILE(POINTGROUP_COMPRESS); // Resize compressed result arrays if needed (2x guardband to prevent // frequent reallocations): @@ -880,7 +900,7 @@ void PointGroupClass::Render(RenderInfoClass &rinfo) // Get the world and view matrices Matrix4x4 view; - DX8Wrapper::Get_Transform(D3DTS_VIEW,view); + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, view); // Transform the point locations from worldspace to camera space if needed // (i.e. if they are not already in camera space): @@ -890,6 +910,7 @@ void PointGroupClass::Render(RenderInfoClass &rinfo) // (gth) changed this 'if' to use OR rather than AND... The way it was caused all emitters to break if (Get_Flag(TRANSFORM) && Billboard) { + GGC_RPROFILE(POINTGROUP_VIEW_XFORM); // Resize transformed location array if needed (2x guardband to prevent // frequent reallocations): if (transformed_loc.Length() < PointCount) { @@ -912,27 +933,67 @@ void PointGroupClass::Render(RenderInfoClass &rinfo) // Update the arrays with the offsets. int vnum, pnum; - Update_Arrays(current_loc, current_diffuse, current_size, current_orient, current_frame, - PointCount, PointLoc->Get_Count(), vnum, pnum); + { + GGC_RPROFILE(POINTGROUP_UPDATE_ARRAYS); + Update_Arrays(current_loc, current_diffuse, current_size, current_orient, current_frame, + PointCount, PointLoc->Get_Count(), vnum, pnum); + } + +// TheSuperHackers @bugfix bobtista 17/07/2026 The 28/05 ground-alignment fixup that + // re-built these vertices from the billboard orientation tables is removed: it overwrote + // Update_Arrays' retail ground geometry with half-size, opposite-spin, mirrored quads. + // Update_Arrays already outputs camera-space vertices for ground-aligned groups, and the + // backend's camera-space world fix reprojects them correctly on the engine view + // (kill switch GGC_BGFX_NO_CAMERA_SPACE_WORLD_FIX). // the locations are now in view space // 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. // This improves performance and resolves rendering artifacts caused by clipping between ground-aligned particles and billboard particles. + const bool isGroundAdditive = !Billboard + && Shader.Get_Dst_Blend_Func() == ShaderClass::DSTBLEND_ONE + && Shader.Get_Src_Blend_Func() == ShaderClass::SRCBLEND_ONE; const bool sort = Billboard && Shader.Get_Dst_Blend_Func() != ShaderClass::DSTBLEND_ZERO && Shader.Get_Alpha_Test() == ShaderClass::ALPHATEST_DISABLE && WW3D::Is_Sorting_Enabled(); + // TheSuperHackers @bugfix bobtista 28/05/2026 Cache the env probe once at startup; getenv() is not cheap to call every frame per particle group. + static const bool pointGroupDiag = GgcFlags::Enabled(GgcFlag_PointGroupDiag); + if (pointGroupDiag) + { + if (FILE *diag = std::fopen("ggc_pointgroup_diag.txt", "a")) + { + std::fprintf(diag, + "pointgroup texture=%s points=%d vnum=%d pnum=%d sort=%d billboard=%d mode=%d shader=0x%08x srcBlend=%d dstBlend=%d alphaTest=%d sortingEnabled=%d isGroundAdd=%d first=(%.2f,%.2f,%.2f)\n", + Texture != nullptr ? Texture->Get_Texture_Name().str() : "", + PointCount, + vnum, + pnum, + sort ? 1 : 0, + Billboard ? 1 : 0, + static_cast(PointMode), + Shader.Get_Bits(), + static_cast(Shader.Get_Src_Blend_Func()), + static_cast(Shader.Get_Dst_Blend_Func()), + static_cast(Shader.Get_Alpha_Test()), + WW3D::Is_Sorting_Enabled() ? 1 : 0, + isGroundAdditive ? 1 : 0, + current_loc != nullptr && PointCount > 0 ? current_loc[0].X : 0.0f, + current_loc != nullptr && PointCount > 0 ? current_loc[0].Y : 0.0f, + current_loc != nullptr && PointCount > 0 ? current_loc[0].Z : 0.0f); + std::fclose(diag); + } + } IndexBufferClass *indexbuffer; int verticesperprimitive;/// lorenzen fixed @@ -949,13 +1010,18 @@ void PointGroupClass::Render(RenderInfoClass &rinfo) } current = 0; + if (sort) + { + g_renderBackend->Set_Point_Group_Render_Active(true); + } while (currentSet_Index_Buffer (indexbuffer, 0); + g_renderBackend->Set_Vertex_Buffer (PointVerts); if ( sort ) { @@ -989,14 +1055,18 @@ 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; } + if (sort) + { + g_renderBackend->Set_Point_Group_Render_Active(false); + } // restore the matrices - DX8Wrapper::Set_Transform(D3DTS_VIEW,view); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW,view); } @@ -1208,18 +1278,26 @@ void PointGroupClass::Update_Arrays( Matrix4x4 view; Vector4 result; if (!Billboard) { - DX8Wrapper::Get_Transform(D3DTS_VIEW,view); + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, view); } // Scale vertex offsets and add them to point locations to get vertex locations - for (i = 0; i < active_points; i++) { - if (!Billboard) { - // If we're not billboarding, then the coordinate we have is in screen space. - Matrix4x4 rotMat; - D3DXMatrixRotationZ(&(D3DXMATRIX&) rotMat, ((float)point_orientation[i] / 255.0f * 2 * D3DX_PI)); - - Vector4 orientedVecX = rotMat * GroundMultiplierX; - Vector4 orientedVecY = rotMat * GroundMultiplierY; + for (i = 0; i < active_points; i++) { + if (!Billboard) { + // If we're not billboarding, then the coordinate we have is in screen space. + const float angle = static_cast(point_orientation[i]) / 255.0f * WWMATH_TWO_PI; + const float c = std::cos(angle); + const float s = std::sin(angle); + const Vector4 orientedVecX( + GroundMultiplierX.X * c + GroundMultiplierX.Y * s, + GroundMultiplierX.X * -s + GroundMultiplierX.Y * c, + GroundMultiplierX.Z, + 1.0f); + const Vector4 orientedVecY( + GroundMultiplierY.X * c + GroundMultiplierY.Y * s, + GroundMultiplierY.X * -s + GroundMultiplierY.Y * c, + GroundMultiplierY.Z, + 1.0f); vertex_loc[vert + 0].X = point_loc[i].X + (orientedVecX.X + orientedVecY.X) * point_size[i]; vertex_loc[vert + 0].Y = point_loc[i].Y + (orientedVecX.Y + orientedVecY.Y) * point_size[i]; @@ -1531,21 +1609,21 @@ void PointGroupClass::_Init() } // Create the IBs - Tris=NEW_REF(DX8IndexBufferClass,(MAX_TRI_IB_SIZE)); - Quads=NEW_REF(DX8IndexBufferClass,(MAX_QUAD_IB_SIZE)); + Tris=NEW_REF(RenderIndexBufferClass,(MAX_TRI_IB_SIZE)); + Quads=NEW_REF(RenderIndexBufferClass,(MAX_QUAD_IB_SIZE)); SortingTris=NEW_REF(SortingIndexBufferClass,(MAX_TRI_IB_SIZE)); SortingQuads=NEW_REF(SortingIndexBufferClass,(MAX_QUAD_IB_SIZE)); // Fill up the IBs { - DX8IndexBufferClass::WriteLockClass locktris(Tris); + RenderIndexBufferClass::WriteLockClass locktris(Tris); unsigned short *ib=locktris.Get_Index_Array(); for (i=0; iGet_Transform(RB_TRANSFORM_VIEW, view); + + // TheSuperHackers @performance bobtista 03/06/2026 On the bgfx shader + // pipeline, merge this emitter's depth layers into one draw instead of one + // per layer. GGC_NO_VOLUME_MERGE forces the legacy per-layer path. The DX8 + // fixed-function path keeps the original per-layer loop byte-identical. + static const bool s_volumeMergeDisabled = GgcFlags::Enabled(GgcFlag_NoVolumeMerge); + const bool mergeVolume = g_renderBackend->Has_Shader_Pipeline() && !s_volumeMergeDisabled; + if (mergeVolume) + { + s_volMergeCount = 0; + } //// VOLUME_PARTICLE LOOP /////////////// for ( unsigned int t = 0; t < depth; ++t ) @@ -1710,6 +1799,7 @@ void PointGroupClass::RenderVolumeParticle(RenderInfoClass &rinfo, unsigned int // If there is an active point table, use it to compress the point // locations/colors/alphas/sizes/orientations/frames. if (APT) { + GGC_RPROFILE(POINTGROUP_COMPRESS); // Resize compressed result arrays if needed (2x guardband to prevent // frequent reallocations): @@ -1779,6 +1869,7 @@ void PointGroupClass::RenderVolumeParticle(RenderInfoClass &rinfo, unsigned int // need to interrupt this processing. If we are not billboarding, then we need the actual position // of the vertice to lay it down flat. if (Get_Flag(TRANSFORM) && Billboard) { + GGC_RPROFILE(POINTGROUP_VIEW_XFORM); // Resize transformed location array if needed (2x guardband to prevent // frequent reallocations): if (transformed_loc.Length() < PointCount) { @@ -1826,19 +1917,22 @@ void PointGroupClass::RenderVolumeParticle(RenderInfoClass &rinfo, unsigned int //current_diffuse->Z *= attenuator; //current_diffuse->W *= attenuator; - Update_Arrays(current_loc, current_diffuse, current_size, current_orient, current_frame, - PointCount, PointLoc->Get_Count(), vnum, pnum); + { + GGC_RPROFILE(POINTGROUP_UPDATE_ARRAYS); + Update_Arrays(current_loc, current_diffuse, current_size, current_orient, current_frame, + PointCount, PointLoc->Get_Count(), vnum, pnum); + } // the locations are now in view space // 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 @@ -1862,14 +1956,49 @@ void PointGroupClass::RenderVolumeParticle(RenderInfoClass &rinfo, unsigned int float nudge = 0; + if (mergeVolume) + { + // Append this layer's vertices to the shared merge scratch in + // near-to-far t order. Submission order within the merged buffer + // equals the original per-layer order, so additive/alpha/multiply + // blending and the sort-pool z-ordering are unchanged. + const int mergeBase = s_volMergeCount; + const int mergeNeeded = mergeBase + vnum; + if (s_volMergeLoc.Length() < mergeNeeded) + { + s_volMergeLoc.Resize(mergeNeeded * 2); + s_volMergeUV.Resize(mergeNeeded * 2); + s_volMergeColor.Resize(mergeNeeded * 2); + } + const unsigned int defaultVolColor = WW3DColor::To_ARGB_Clamp( + Vector4(DefaultPointColor[0], DefaultPointColor[1], DefaultPointColor[2], DefaultPointAlpha)); + for (int mi = 0; mi < vnum; mi++) + { + s_volMergeLoc[mergeBase + mi] = VertexLoc[mi]; + s_volMergeUV[mergeBase + mi] = VertexUV[mi]; + s_volMergeColor[mergeBase + mi] = current_diffuse + ? WW3DColor::To_ARGB_Clamp(VertexDiffuse[mi]) : defaultVolColor; + } + s_volMergeCount = mergeNeeded; + s_volMergeVerticesPerPrimitive = verticesperprimitive; + s_volMergeSort = sort; + s_volMergeIndexBuffer = indexbuffer; + continue; + } + current = 0; + if (sort) + { + g_renderBackend->Set_Point_Group_Render_Active(true); + } while (currentSet_Index_Buffer (indexbuffer, 0); + g_renderBackend->Set_Vertex_Buffer (PointVerts); /// @todo lorenzen sez: precompute these params, above @@ -1905,19 +2034,72 @@ 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; } + if (sort) + { + g_renderBackend->Set_Point_Group_Render_Active(false); + } } - - + // TheSuperHackers @performance bobtista 03/06/2026 Emit the accumulated + // volume layers as a single Draw_Triangles (chunked only if the merged + // vertex count exceeds the dynamic VB cap), replacing the per-layer draws + // skipped above. Chunk size is aligned down to whole primitives. + if (mergeVolume && s_volMergeCount > 0) + { + const int vpp = s_volMergeVerticesPerPrimitive; + if (s_volMergeSort) + { + g_renderBackend->Set_Point_Group_Render_Active(true); + } + int mergedCurrent = 0; + while (mergedCurrent < s_volMergeCount) + { + int mergedDelta = MIN(s_volMergeCount - mergedCurrent, MAX_VB_SIZE); + mergedDelta -= (mergedDelta % vpp); + if (mergedDelta <= 0) + { + break; + } + DynamicVBAccessClass PointVerts(s_volMergeSort ? BUFFER_TYPE_DYNAMIC_SORTING : BUFFER_TYPE_DYNAMIC, dynamic_fvf_type, mergedDelta); + { + GGC_RPROFILE(POINTGROUP_VB_FILL); + DynamicVBAccessClass::WriteLockClass Lock(&PointVerts); + unsigned char *vb = (unsigned char*)Lock.Get_Formatted_Vertex_Array(); + const FVFInfoClass& fvfinfo = PointVerts.FVF_Info(); + for (int mi = mergedCurrent; mi < mergedCurrent + mergedDelta; mi++) + { + *(Vector3*)(vb + fvfinfo.Get_Location_Offset()) = s_volMergeLoc[mi]; + *(unsigned int*)(vb + fvfinfo.Get_Diffuse_Offset()) = s_volMergeColor[mi]; + *(Vector2*)(vb + fvfinfo.Get_Tex_Offset(0)) = s_volMergeUV[mi]; + vb += fvfinfo.Get_FVF_Size(); + } + } + g_renderBackend->Set_Index_Buffer(s_volMergeIndexBuffer, 0); + g_renderBackend->Set_Vertex_Buffer(PointVerts); + if (s_volMergeSort) + { + SortingRendererClass::Insert_Triangles(0, mergedDelta / vpp, 0, mergedDelta); + } + else + { + g_renderBackend->Draw_Triangles(0, mergedDelta / vpp, 0, mergedDelta); + } + mergedCurrent += mergedDelta; + } + if (s_volMergeSort) + { + g_renderBackend->Set_Point_Group_Render_Active(false); + } + } // 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/rddesc.h b/Core/Libraries/Source/WWVegas/WW3D2/rddesc.h index 1e4c2f6c78b..18ec699c367 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/rddesc.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/rddesc.h @@ -38,8 +38,10 @@ #include "WWLib/Vector.h" #include "WWLib/wwstring.h" +#if !defined(GGC_RENDER_BACKEND_BGFX) #include #include +#endif class ResolutionDescClass { @@ -82,8 +84,10 @@ class RenderDeviceDescClass set_hardware_name(src.Get_Hardware_Name()); set_hardware_vendor(src.Get_Hardware_Vendor()); set_hardware_chipset(src.Get_Hardware_Chipset()); +#if !defined(GGC_RENDER_BACKEND_BGFX) Caps=src.Caps; AdapterIdentifier=src.AdapterIdentifier; +#endif ResArray = src.ResArray; return *this; } @@ -104,8 +108,10 @@ class RenderDeviceDescClass const char * Get_Hardware_Chipset() const { return HardwareChipset; } const DynamicVectorClass & Enumerate_Resolutions() const { return ResArray; } +#if !defined(GGC_RENDER_BACKEND_BGFX) const D3DCAPS8& Get_Caps() const { return Caps; } const D3DADAPTER_IDENTIFIER8& Get_Adapter_Identifier() const { return AdapterIdentifier; } +#endif private: @@ -134,13 +140,18 @@ class RenderDeviceDescClass StringClass HardwareVendor; StringClass HardwareChipset; +#if !defined(GGC_RENDER_BACKEND_BGFX) D3DCAPS8 Caps; D3DADAPTER_IDENTIFIER8 AdapterIdentifier; +#endif DynamicVectorClass ResArray; friend class WW3D; friend class DX8Wrapper; + // TheSuperHackers @refactor bobtista 11/06/2026 BgfxBackend builds the synthetic device entry + // natively now that dx8wrapper.cpp is not compiled on bgfx builds. + friend class BgfxBackend; }; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp b/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp index 5fe9bf9a01a..470cd5c3ccc 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp @@ -39,7 +39,32 @@ #include "texture.h" #include "WWDebug/wwprofile.h" #include "WWDebug/wwmemlog.h" -#include "dx8wrapper.h" +#include "IRenderBackend.h" +#include "RenderBackend.h" +#if !defined(__APPLE__) +#include "WW3D2/ww3d.h" +#endif + +#if defined(__APPLE__) +#if defined(interface) +#define GGC_RESTORE_INTERFACE_MACRO +#undef interface +#endif +#include +#include +#if defined(GGC_RESTORE_INTERFACE_MACRO) +#define interface struct +#undef GGC_RESTORE_INTERFACE_MACRO +#endif +#include +#include +#endif + +#if !defined(_WIN32) && !defined(__APPLE__) +#include +#include FT_FREETYPE_H +#include +#endif //////////////////////////////////////////////////////////////////////////////////// @@ -48,6 +73,61 @@ #define no_TEST_PLACEMENT 1 // Shows alignment markers for text. #define TEXTURE_OFFSET 2 + +static TextureClass *Create_Writable_Sentence_Texture(unsigned width, unsigned height, WW3DFormat format) +{ + SurfaceClass *surface = NEW_REF(SurfaceClass, (width, height, format)); + TextureClass *texture = W3DNEW TextureClass(surface, MIP_LEVELS_1); + REF_PTR_RELEASE(surface); + return texture; +} + +#if defined(__APPLE__) +namespace +{ +int Apple_Font_Pixel_Size(int point_size) +{ + return max(1, MulDiv(point_size, 96, 72)); +} + +CFStringRef Create_Font_Name(const char *font_name) +{ + const char *resolved_name = (font_name != nullptr && strcmp(font_name, "Generals") == 0) ? "Arial" : font_name; + if (resolved_name == nullptr || resolved_name[0] == '\0') { + resolved_name = "Arial"; + } + + return CFStringCreateWithCString(kCFAllocatorDefault, resolved_name, kCFStringEncodingUTF8); +} + +CTFontRef Create_Apple_Font(const char *font_name, int point_size, bool is_bold) +{ + CFStringRef name = Create_Font_Name(font_name); + if (name == nullptr) { + return nullptr; + } + + CTFontRef base_font = CTFontCreateWithName(name, Apple_Font_Pixel_Size(point_size), nullptr); + CFRelease(name); + if (base_font == nullptr) { + return nullptr; + } + + if (!is_bold) { + return base_font; + } + + CTFontRef bold_font = CTFontCreateCopyWithSymbolicTraits( + base_font, + 0.0, + nullptr, + kCTFontBoldTrait, + kCTFontBoldTrait); + CFRelease(base_font); + return bold_font; +} +} +#endif //////////////////////////////////////////////////////////////////////////////////// // // Render2DSentenceClass @@ -358,8 +438,7 @@ Render2DSentenceClass::Build_Textures () // // Create the new texture // - TextureClass *new_texture = W3DNEW TextureClass (desc.Width, desc.Width, WW3D_FORMAT_A4R4G4B4, MIP_LEVELS_1); - SurfaceClass *texture_surface = new_texture->Get_Surface_Level (); + TextureClass *new_texture = Create_Writable_Sentence_Texture (desc.Width, desc.Width, WW3D_FORMAT_A4R4G4B4); new_texture->Get_Filter().Set_U_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); new_texture->Get_Filter().Set_V_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); @@ -370,8 +449,34 @@ Render2DSentenceClass::Build_Textures () // // Copy the contents of the texture from the surface // - DX8Wrapper::_Copy_DX8_Rects (curr_surface->Peek_D3D_Surface (), nullptr, 0, texture_surface->Peek_D3D_Surface (), nullptr); - REF_PTR_RELEASE (texture_surface); + TextureClass::MutableTextureMipView mip = new_texture->Begin_Mip_Write(0); + const unsigned bytes_per_pixel = ::Get_Bytes_Per_Pixel(desc.Format); + if (mip.Is_Valid() && bytes_per_pixel != 0) { + int source_pitch = 0; + const unsigned char *source_bits = static_cast(curr_surface->Lock(&source_pitch)); + if (source_bits != nullptr && source_pitch > 0) { + unsigned copy_width = desc.Width; + unsigned copy_height = desc.Height; + if (copy_width > mip.Width) { + copy_width = mip.Width; + } + if (copy_height > mip.Height) { + copy_height = mip.Height; + } + + const unsigned row_bytes = copy_width * bytes_per_pixel; + unsigned char *dest_bits = mip.Data; + for (unsigned row = 0; row < copy_height; ++row) { + ::memcpy(dest_bits, source_bits, row_bytes); + dest_bits += mip.Pitch; + source_bits += source_pitch; + } + } + if (source_bits != nullptr) { + curr_surface->Unlock(); + } + } + new_texture->End_Mip_Write(0); // // Assign this texture to any renderers that need it @@ -942,7 +1047,7 @@ void Render2DSentenceClass::Build_Sentence_Centered (const WCHAR *text, int *hkX if(hkX) *hkX = hotKeyPosX; - if(hkX) + if(hkY) *hkY = hotKeyPosY; } //////////////////////////////////////////////////////////////////////////////////// @@ -1125,7 +1230,7 @@ Vector2 Render2DSentenceClass::Build_Sentence_Not_Centered (const WCHAR *text, i if(hkX) *hkX = hotKeyPosX; - if(hkX) + if(hkY) *hkY = hotKeyPosY; return extent; @@ -1167,6 +1272,10 @@ FontCharsClass::FontCharsClass () : GDIBitmap( nullptr ), GDIBitmapBits ( nullptr ), MemDC( nullptr ), +#if !defined(_WIN32) && !defined(__APPLE__) + FTLibrary( nullptr ), + FTFace( nullptr ), +#endif CurrPixelOffset( 0 ), PointSize( 0 ), CharHeight( 0 ), @@ -1205,6 +1314,11 @@ FontCharsClass::~FontCharsClass () const FontCharsClassCharDataStruct * FontCharsClass::Get_Char_Data (WCHAR ch) { +#if defined(__APPLE__) + if (ch < 0 || ch > 0xFFFF) { + ch = '?'; + } +#endif const FontCharsClassCharDataStruct *retval = nullptr; if ( ch < 256 ) @@ -1278,7 +1392,10 @@ void FontCharsClass::Blit_Char (WCHAR ch, uint16 *dest_ptr, int dest_stride, int x, int y) { const FontCharsClassCharDataStruct * data = Get_Char_Data( ch ); - if ( data != nullptr && data->Width != 0 ) { + // TheSuperHackers @build bobtista 30/04/2026 Skip blit when Buffer is + // null — the non-Win Store_GDI_Char stub returns width-only entries with + // no rasterized glyph yet. + if ( data != nullptr && data->Width != 0 && data->Buffer != nullptr ) { // // Setup the src and destination pointers @@ -1313,6 +1430,175 @@ FontCharsClass::Blit_Char (WCHAR ch, uint16 *dest_ptr, int dest_stride, int x, i const FontCharsClassCharDataStruct * FontCharsClass::Store_GDI_Char (WCHAR ch) { +#if defined(__APPLE__) + CTFontRef font = Create_Apple_Font(GDIFontName.Peek_Buffer(), PointSize, IsBold); + if (font == nullptr) { + return nullptr; + } + + UniChar character = (ch >= 0 && ch <= 0xFFFF) ? static_cast(ch) : static_cast(0xFFFD); + CGGlyph glyph = 0; + if (!CTFontGetGlyphsForCharacters(font, &character, &glyph, 1) || glyph == 0) { + character = static_cast('?'); + CTFontGetGlyphsForCharacters(font, &character, &glyph, 1); + } + + CGSize advance = CGSizeZero; + CTFontGetAdvancesForGlyphs(font, kCTFontOrientationHorizontal, &glyph, &advance, 1); + CGRect bounds = CTFontGetBoundingRectsForGlyphs(font, kCTFontOrientationHorizontal, &glyph, nullptr, 1); + + int left_pad = 0; + if (bounds.origin.x < 0.0) { + left_pad = static_cast(std::ceil(-bounds.origin.x)); + } + int glyph_width = static_cast(std::ceil(bounds.size.width)) + left_pad + PixelOverlap + 1; + int advance_width = static_cast(std::ceil(advance.width)) + PixelOverlap; + int char_width = max(1, max(glyph_width, advance_width)); + int bitmap_height = max(1, CharHeight); + + std::vector glyph_bitmap(static_cast(char_width) * static_cast(bitmap_height), 0); + CGColorSpaceRef color_space = CGColorSpaceCreateDeviceGray(); + CGContextRef context = CGBitmapContextCreate( + glyph_bitmap.data(), + char_width, + bitmap_height, + 8, + char_width, + color_space, + kCGImageAlphaNone); + CGColorSpaceRelease(color_space); + + if (context != nullptr) { + CGContextSetGrayFillColor(context, 0.0, 1.0); + CGContextFillRect(context, CGRectMake(0, 0, char_width, bitmap_height)); + CGContextSetShouldAntialias(context, true); + CGContextSetAllowsAntialiasing(context, true); + CGContextSetGrayFillColor(context, 1.0, 1.0); + CGContextSetTextMatrix(context, CGAffineTransformIdentity); + CGPoint glyph_position = CGPointMake(left_pad, max(1, bitmap_height - CharAscent)); + CTFontDrawGlyphs(font, &glyph, &glyph_position, 1, context); + CGContextRelease(context); + } + + CFRelease(font); + + Update_Current_Buffer(char_width); + uint16 *curr_buffer_p = BufferList[BufferList.Count() - 1]->Buffer; + curr_buffer_p += CurrPixelOffset; + + // TheSuperHackers @bugfix bobtista 28/05/2026 Premultiply RGB by alpha + // so anti-aliased edges fade in colour instead of jumping from black to + // full white, and zero-fill the trailing rows between bitmap_height and + // CharHeight; the buffer stride below advances by CharHeight, and the + // previous code left those rows uninitialised, leaving stale words + // visible between glyphs on macOS. + for (int row = 0; row < bitmap_height; ++row) { + for (int col = 0; col < char_width; ++col) { + int source_row = row; + uint8 pixel_value = glyph_bitmap[static_cast(source_row) * static_cast(char_width) + col]; + uint8 rgb_value = (pixel_value >> 4) & 0xF; + uint16 pixel_color = static_cast(rgb_value) | (static_cast(rgb_value) << 4) | (static_cast(rgb_value) << 8); + uint8 alpha_value = ((pixel_value >> 4) & 0xF); + *curr_buffer_p++ = pixel_color | (alpha_value << 12); + } + } + for (int row = bitmap_height; row < CharHeight; ++row) { + for (int col = 0; col < char_width; ++col) { + *curr_buffer_p++ = 0; + } + } + + FontCharsClassCharDataStruct *char_data = W3DNEW FontCharsClassCharDataStruct; + char_data->Value = ch; + char_data->Width = static_cast(char_width); + char_data->Buffer = BufferList[BufferList.Count() - 1]->Buffer + CurrPixelOffset; + + if (ch < 256) { + ASCIICharArray[ch] = char_data; + } else { + Grow_Unicode_Array(ch); + UnicodeCharArray[ch - FirstUnicodeChar] = char_data; + } + + CurrPixelOffset += ((char_width + PixelOverlap) * CharHeight); + return char_data; +#elif !defined(_WIN32) + // TheSuperHackers @port bobtista 24/07/2026 Adapted from + // fbraz3/GeneralsX's FreeType font backend. + FT_ULong codepoint = static_cast(static_cast(ch)); + FT_Error error = FT_Load_Char(FTFace, codepoint, FT_LOAD_RENDER); + if (error != 0 && codepoint != static_cast('?')) { + error = FT_Load_Char(FTFace, static_cast('?'), FT_LOAD_RENDER); + } + if (error != 0) { + return nullptr; + } + + FT_GlyphSlot glyph = FTFace->glyph; + FT_Bitmap &bitmap = glyph->bitmap; + int x_origin = ch == 'W' ? 1 : 0; + int left_pad = glyph->bitmap_left < 0 ? -glyph->bitmap_left : 0; + int glyph_x = x_origin + left_pad + glyph->bitmap_left; + int glyph_right = glyph_x + static_cast(bitmap.width); + int advance_width = static_cast((glyph->advance.x + 63) >> 6); + int char_width = max(1, max(x_origin + left_pad + advance_width, glyph_right) + PixelOverlap); + + Update_Current_Buffer(char_width); + uint16 *char_buffer = BufferList[BufferList.Count() - 1]->Buffer + CurrPixelOffset; + ::memset(char_buffer, 0, static_cast(char_width) * static_cast(CharHeight) * sizeof(*char_buffer)); + + for (unsigned int row = 0; row < bitmap.rows; ++row) { + int dest_y = CharAscent - glyph->bitmap_top + static_cast(row); + if (dest_y < 0 || dest_y >= CharHeight) { + continue; + } + + const unsigned char *source_row = bitmap.pitch >= 0 + ? bitmap.buffer + static_cast(row) * bitmap.pitch + : bitmap.buffer + (static_cast(bitmap.rows) - 1 - static_cast(row)) * -bitmap.pitch; + for (unsigned int col = 0; col < bitmap.width; ++col) { + int dest_x = glyph_x + static_cast(col); + if (dest_x < 0 || dest_x >= char_width) { + continue; + } + + uint8 pixel_value = 0; + if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) { + pixel_value = source_row[col]; + if (bitmap.num_grays > 1 && bitmap.num_grays != 256) { + pixel_value = static_cast( + (static_cast(pixel_value) * 255U) / (bitmap.num_grays - 1)); + } + } else if (bitmap.pixel_mode == FT_PIXEL_MODE_MONO) { + pixel_value = (source_row[col >> 3] & (0x80U >> (col & 7))) != 0 ? 0xFF : 0; + } else { + continue; + } + + // Match the CoreText path: premultiplied A4R4G4B4. The existing + // sentence texture upload and blit code consume these words directly. + uint8 alpha_value = (pixel_value >> 4) & 0xF; + uint16 pixel_color = static_cast(alpha_value) + | (static_cast(alpha_value) << 4) + | (static_cast(alpha_value) << 8); + char_buffer[dest_y * char_width + dest_x] = pixel_color | (alpha_value << 12); + } + } + + FontCharsClassCharDataStruct *char_data = W3DNEW FontCharsClassCharDataStruct; + char_data->Value = ch; + char_data->Width = static_cast(char_width); + char_data->Buffer = char_buffer; + if (ch < 256) { + ASCIICharArray[ch] = char_data; + } else { + Grow_Unicode_Array(ch); + UnicodeCharArray[ch - FirstUnicodeChar] = char_data; + } + + CurrPixelOffset += char_width * CharHeight; + return char_data; +#else int width = PointSize * 2; int height = PointSize * 2; @@ -1430,6 +1716,7 @@ FontCharsClass::Store_GDI_Char (WCHAR ch) // Return the index of the entry we just added // return char_data; +#endif // _WIN32 } @@ -1475,6 +1762,104 @@ FontCharsClass::Update_Current_Buffer (int char_width) bool FontCharsClass::Create_GDI_Font (const char *font_name) { +#if defined(__APPLE__) + bool doingGenerals = font_name != nullptr && strcmp(font_name, "Generals") == 0; + int font_height = Apple_Font_Pixel_Size(PointSize); + + PixelOverlap = font_height / 8; + if (PixelOverlap < 0) PixelOverlap = 0; + if (PixelOverlap > 4) PixelOverlap = 4; + + CTFontRef font = Create_Apple_Font(font_name, PointSize, IsBold); + if (font == nullptr) { + return false; + } + + CharAscent = max(1, static_cast(std::ceil(CTFontGetAscent(font)))); + int descent = max(1, static_cast(std::ceil(CTFontGetDescent(font)))); + int leading = max(0, static_cast(std::ceil(CTFontGetLeading(font)))); + CharHeight = max(1, CharAscent + descent + leading); + CharOverhang = doingGenerals ? 0 : 0; + + CFRelease(font); + return true; +#elif !defined(_WIN32) + // TheSuperHackers @port bobtista 24/07/2026 Adapted from + // fbraz3/GeneralsX's FreeType and Fontconfig font backend. + if (FT_Init_FreeType(&FTLibrary) != 0) { + return false; + } + + bool doingGenerals = font_name != nullptr && strcmp(font_name, "Generals") == 0; + const char *resolved_name = doingGenerals ? "Arial" : font_name; + if (resolved_name == nullptr || resolved_name[0] == '\0') { + resolved_name = "Arial"; + } + + FcConfig *config = FcInitLoadConfigAndFonts(); + FcPattern *pattern = config != nullptr + ? FcNameParse(reinterpret_cast(resolved_name)) + : nullptr; + FcPattern *match = nullptr; + FcResult match_result = FcResultNoMatch; + if (pattern != nullptr) { + if (IsBold) { + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + } + FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); + FcConfigSubstitute(config, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + match = FcFontMatch(config, pattern, &match_result); + } + + FcChar8 *font_path = nullptr; + int face_index = 0; + bool found_font = match != nullptr + && match_result == FcResultMatch + && FcPatternGetString(match, FC_FILE, 0, &font_path) == FcResultMatch; + if (found_font) { + FcPatternGetInteger(match, FC_INDEX, 0, &face_index); + } + + FT_Error face_error = found_font + ? FT_New_Face(FTLibrary, reinterpret_cast(font_path), face_index, &FTFace) + : FT_Err_Cannot_Open_Resource; + + if (match != nullptr) { + FcPatternDestroy(match); + } + if (pattern != nullptr) { + FcPatternDestroy(pattern); + } + if (config != nullptr) { + FcConfigDestroy(config); + } + if (face_error != 0) { + FT_Done_FreeType(FTLibrary); + FTLibrary = nullptr; + return false; + } + + int font_height = max(1, static_cast(FT_MulDiv(PointSize, 96, 72))); + if (FT_Set_Pixel_Sizes(FTFace, 0, static_cast(font_height)) != 0) { + FT_Done_Face(FTFace); + FT_Done_FreeType(FTLibrary); + FTFace = nullptr; + FTLibrary = nullptr; + return false; + } + + PixelOverlap = font_height / 8; + if (PixelOverlap < 0) PixelOverlap = 0; + if (PixelOverlap > 4) PixelOverlap = 4; + + CharAscent = max(1, static_cast((FTFace->size->metrics.ascender + 63) >> 6)); + int descent = max(0, static_cast((-FTFace->size->metrics.descender + 63) >> 6)); + int metrics_height = max(1, static_cast((FTFace->size->metrics.height + 63) >> 6)); + CharHeight = max(CharAscent + descent, metrics_height); + CharOverhang = doingGenerals ? 0 : 0; + return true; +#else HDC screen_dc = ::GetDC ((HWND)WW3D::Get_Window()); const char *fontToUseForGenerals = "Arial"; @@ -1568,6 +1953,7 @@ FontCharsClass::Create_GDI_Font (const char *font_name) } return GDIFont != nullptr && GDIBitmap != nullptr; +#endif } @@ -1579,6 +1965,16 @@ FontCharsClass::Create_GDI_Font (const char *font_name) void FontCharsClass::Free_GDI_Font () { +#if !defined(_WIN32) && !defined(__APPLE__) + if (FTFace != nullptr) { + FT_Done_Face(FTFace); + FTFace = nullptr; + } + if (FTLibrary != nullptr) { + FT_Done_FreeType(FTLibrary); + FTLibrary = nullptr; + } +#else // // Select the old font back into the DC and delete // our font object @@ -1606,6 +2002,7 @@ FontCharsClass::Free_GDI_Font () ::DeleteDC( MemDC ); MemDC = nullptr; } +#endif } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.h b/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.h index 15426c1e950..64564d09fe4 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.h @@ -43,6 +43,16 @@ #include "WWLib/wwstring.h" #include "WWLib/win.h" +#if !defined(_WIN32) && !defined(__APPLE__) +// TheSuperHackers @build bobtista 24/07/2026 Forward-declare the FreeType handle +// types (both are pointers) so this public header does not pull in . +// Consumers of the WW3D2 headers (e.g. GameEngineDevice) do not link FreeType's +// include directory; the real FreeType headers are included in render2dsentence.cpp +// where the FT_Library / FT_Face objects are actually used. +typedef struct FT_LibraryRec_ *FT_Library; +typedef struct FT_FaceRec_ *FT_Face; +#endif + /* ** FontCharsClass */ @@ -126,6 +136,10 @@ class FontCharsClass : public RefCountClass HFONT GDIFont; uint8 * GDIBitmapBits; HDC MemDC; +#if !defined(_WIN32) && !defined(__APPLE__) + FT_Library FTLibrary; + FT_Face FTFace; +#endif FontCharsClassCharDataStruct * ASCIICharArray[256]; FontCharsClassCharDataStruct ** UnicodeCharArray; uint16 FirstUnicodeChar; @@ -264,6 +278,5 @@ class Render2DSentenceClass { uint16 * LockedPtr; int LockedStride; - TextureClass * CurTexture; ShaderClass Shader; }; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/renderbufferclasses.h b/Core/Libraries/Source/WWVegas/WW3D2/renderbufferclasses.h new file mode 100644 index 00000000000..cc8a5a62f65 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/renderbufferclasses.h @@ -0,0 +1,93 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** 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 "always.h" + +class DX8VertexBufferClass; + +#if defined(GGC_RENDER_BACKEND_BGFX) + +#include "indexbufferbase.h" +#include "vertexbufferbase.h" + +class RenderIndexBufferClass : public IndexBufferClass +{ + W3DMPO_CODE(RenderIndexBufferClass) + +public: + enum UsageType { + USAGE_DEFAULT = 0, + USAGE_DYNAMIC = 1, + USAGE_SOFTWAREPROCESSING = 2, + USAGE_NPATCHES = 4 + }; + + RenderIndexBufferClass(unsigned short index_count, UsageType usage = USAGE_DEFAULT); + virtual ~RenderIndexBufferClass() override; +}; + +class RenderVertexBufferClass : public VertexBufferClass +{ + W3DMPO_CODE(RenderVertexBufferClass) + +public: + enum UsageType { + USAGE_DEFAULT = 0, + USAGE_DYNAMIC = 1, + USAGE_SOFTWAREPROCESSING = 2, + USAGE_NPATCHES = 4 + }; + + RenderVertexBufferClass(unsigned FVF, unsigned short vertex_count, UsageType usage = USAGE_DEFAULT); + virtual ~RenderVertexBufferClass() override; +}; + +#else + +// TheSuperHackers @build bobtista 01/06/2026 Pull the complete DX8 buffer +// class definitions in here so the aliases below resolve to a complete type +// in every translation unit that includes this header. Forward declarations +// alone leave RenderIndexBufferClass / RenderVertexBufferClass usable only as +// pointer-to-incomplete in code shared with the bgfx backend (USAGE_* enums, +// WriteLockClass nested type, NEW_REF, static_cast to base IndexBufferClass / +// VertexBufferClass all require the full definition). +#include "dx8indexbuffer.h" +#include "dx8vertexbuffer.h" +using RenderIndexBufferClass = DX8IndexBufferClass; +using RenderVertexBufferClass = DX8VertexBufferClass; + +#endif + +template +constexpr typename BufferClass::UsageType Render_Buffer_Usage_Default() +{ + return BufferClass::USAGE_DEFAULT; +} + +template +constexpr typename BufferClass::UsageType Render_Buffer_Usage_Dynamic() +{ + return BufferClass::USAGE_DYNAMIC; +} + +// Transitional neutral names for runtime code that only needs WW3D render +// buffers, not raw Direct3D buffer objects. RenderVertexBufferClass and +// RenderIndexBufferClass have standalone bgfx implementations and alias the +// DX8 classes for DX8 reference builds. diff --git a/Core/Libraries/Source/WWVegas/WW3D2/renderdebugstats.cpp b/Core/Libraries/Source/WWVegas/WW3D2/renderdebugstats.cpp new file mode 100644 index 00000000000..8430a4c8481 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/renderdebugstats.cpp @@ -0,0 +1,25 @@ +/* +** 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 . +*/ + +#include "renderdebugstats.h" + +#ifdef EXTENDED_STATS + +RenderDebugStats g_renderDebugStats; + +#endif diff --git a/Core/Libraries/Source/WWVegas/WW3D2/renderdebugstats.h b/Core/Libraries/Source/WWVegas/WW3D2/renderdebugstats.h new file mode 100644 index 00000000000..e501b38ac55 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/renderdebugstats.h @@ -0,0 +1,49 @@ +/* +** 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 + +#ifdef EXTENDED_STATS + +struct RenderDebugStats +{ + RenderDebugStats() : + m_showingStats(false), + m_disableTerrain(false), + m_disableWater(false), + m_disableObjects(false), + m_disableOverhead(false), + m_disableConsole(false), + m_debugLinesToShow(-1), + m_sleepTime(0) + { + } + + bool m_showingStats; + bool m_disableTerrain; + bool m_disableWater; + bool m_disableObjects; + bool m_disableOverhead; + bool m_disableConsole; + int m_debugLinesToShow; + int m_sleepTime; +}; + +extern RenderDebugStats g_renderDebugStats; + +#endif diff --git a/Core/Libraries/Source/WWVegas/WW3D2/ringobj.cpp b/Core/Libraries/Source/WWVegas/WW3D2/ringobj.cpp index 7f246b51432..271d3d7a164 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/ringobj.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/ringobj.cpp @@ -88,12 +88,14 @@ #include "WW3D2/camera.h" #include "statistics.h" #include "predlod.h" -#include "dx8wrapper.h" -#include "dx8indexbuffer.h" -#include "dx8vertexbuffer.h" +#include "ww3dcolor.h" +#include "indexbuffer.h" +#include "vertexbuffer.h" #include "sortingrenderer.h" #include "WWMath/Vector3i.h" #include "visrasterizer.h" +#include "RenderBackend.h" +#include "IRenderBackend.h" static bool Ring_Array_Valid = false; @@ -531,13 +533,13 @@ 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()); - const unsigned int buffer_type = sort ? BUFFER_TYPE_DYNAMIC_SORTING : BUFFER_TYPE_DYNAMIC_DX8; + const unsigned int buffer_type = sort ? BUFFER_TYPE_DYNAMIC_SORTING : BUFFER_TYPE_DYNAMIC; DynamicVBAccessClass vb(buffer_type, dynamic_fvf_type, ring.Vertex_ct); { @@ -549,9 +551,9 @@ void RingRenderObjClass::render_ring(RenderInfoClass & rinfo,const Vector3 & cen // unsigned color; if (RingShader.Get_Dst_Blend_Func () == ShaderClass::DSTBLEND_ONE) { - color = DX8Wrapper::Convert_Color(Alpha * Color,1.0f); + color = WW3DColor::To_ARGB(Alpha * Color,1.0f); } else { - color = DX8Wrapper::Convert_Color(Color,Alpha); + color = WW3DColor::To_ARGB(Color,Alpha); } for (int i=0; iSet_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); } // @@ -1538,8 +1540,8 @@ void RingMeshClass::Generate(float radius, int slices) for (index = 0; index < Vertex_ct; index += 2) { - float x_pos = -WWMath::Sin (angle); - float y_pos = WWMath::Cos (angle); + float x_pos = -WWMath::Sinf_Legacy (angle); + float y_pos = WWMath::Cosf_Legacy (angle); // // Place the inner index @@ -1614,11 +1616,11 @@ RingMeshClass::~RingMeshClass() void RingMeshClass::Free() { - delete vtx; - delete orig_vtx; - delete vtx_normal; - delete vtx_uv; - delete tri_poly; + delete[] vtx; + delete[] orig_vtx; + delete[] vtx_normal; + delete[] vtx_uv; + delete[] tri_poly; vtx = nullptr; orig_vtx = nullptr; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/segline.cpp b/Core/Libraries/Source/WWVegas/WW3D2/segline.cpp index 4cec2125f36..3138ff3f3e6 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/segline.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/segline.cpp @@ -43,7 +43,6 @@ #include "coltest.h" #include "WW3D2/w3d_file.h" #include "texture.h" -#include "dx8wrapper.h" #include "WWMath/vp.h" #include "WWMath/Vector3i.h" #include "sortingrenderer.h" @@ -258,7 +257,7 @@ void SegmentedLineClass::Set_Opacity(float opacity) void SegmentedLineClass::Set_Noise_Amplitude(float amplitude) { - LineRenderer.Set_Noise_Amplitude(WWMath::Fabs(amplitude)); + LineRenderer.Set_Noise_Amplitude(WWMath::Fabsf_Legacy(amplitude)); Invalidate_Cached_Bounding_Volumes(); } @@ -587,5 +586,3 @@ bool SegmentedLineClass::Cast_Ray(RayCollisionTestClass & raytest) return retval; } - - diff --git a/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp index 0d96c6b3a70..f210a8ccd55 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp @@ -40,13 +40,24 @@ #include "seglinerenderer.h" #include "WW3D2/ww3d.h" #include "WW3D2/rinfo.h" -#include "dx8wrapper.h" +#include "WW3D2/ww3dcolor.h" #include "sortingrenderer.h" #include "WWMath/vp.h" #include "WWMath/Vector3i.h" #include "WWLib/RANDOM.h" #include "WWMath/v3_rnd.h" #include "WW3D2/meshgeometry.h" +#include "WW3D2/vertmaterial.h" +#include "WW3D2/vertexbuffer.h" +#include "WW3D2/indexbuffer.h" +#include "WW3D2/dx8fvf.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" +#include "GgcRuntimeFlags.h" +#include "WW3D2/w3d_file.h" + +#include +#include /* We have chunking logic which handles N segments at a time. To simplify the subdivision logic, @@ -65,9 +76,6 @@ // This macro depends on the assumption that each line segment is two polys. #define MAX_SEGLINE_POLY_BUFFER_SIZE (SEGLINE_CHUNK_SIZE * 2) - - - SegLineRendererClass::SegLineRendererClass() : Texture(nullptr), Shader(ShaderClass::_PresetAdditiveSpriteShader), @@ -217,12 +225,13 @@ void SegLineRendererClass::Render Vector4 * rgbas ) { + const bool diagSegline = GgcFlags::Enabled(GgcFlag_SeglineDiag); Matrix4x4 view; - DX8Wrapper::Get_Transform(D3DTS_VIEW,view); + g_renderBackend->Get_Transform(RB_TRANSFORM_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). @@ -289,6 +298,27 @@ void SegLineRendererClass::Render VectorProcessorClass::Transform(&xformed_pts[0], &points[chidx], modelview, point_cnt); + if (diagSegline && chidx == 0) + { + if (FILE *diag = std::fopen("ggc_segline_diag.txt", "a")) + { + const char *textureName = Texture != nullptr ? Texture->Get_Texture_Name().str() : ""; + std::fprintf(diag, + "segline texture=%s points=%u width=%.3f opacity=%.3f shader=0x%08x firstEye=(%.2f,%.2f,%.2f) lastEye=(%.2f,%.2f,%.2f)\n", + textureName, + point_cnt, + Width, + Opacity, + Shader.Get_Bits(), + xformed_pts[0].X, + xformed_pts[0].Y, + xformed_pts[0].Z, + xformed_pts[point_cnt - 1].X, + xformed_pts[point_cnt - 1].Y, + xformed_pts[point_cnt - 1].Z); + std::fclose(diag); + } + } /* @@ -942,14 +972,14 @@ void SegLineRendererClass::Render vArray[vidx].x = top.X; vArray[vidx].y = top.Y; vArray[vidx].z = top.Z; - vArray[vidx].diffuse = DX8Wrapper::Convert_Color(intersection[1][TOP_EDGE].RGBA); + vArray[vidx].diffuse = WW3DColor::To_ARGB(intersection[1][TOP_EDGE].RGBA); vArray[vidx].u1 = u_values[0] + uv_offset.X; vArray[vidx].v1 = intersection[1][TOP_EDGE].TexV + uv_offset.Y; vidx++; vArray[vidx].x = bottom.X; vArray[vidx].y = bottom.Y; vArray[vidx].z = bottom.Z; - vArray[vidx].diffuse = DX8Wrapper::Convert_Color(intersection[1][BOTTOM_EDGE].RGBA); + vArray[vidx].diffuse = WW3DColor::To_ARGB(intersection[1][BOTTOM_EDGE].RGBA); vArray[vidx].u1 = u_values[1] + uv_offset.X; vArray[vidx].v1 = intersection[1][BOTTOM_EDGE].TexV + uv_offset.Y; vidx++; @@ -1003,14 +1033,14 @@ void SegLineRendererClass::Render vArray[vidx].x = top.X; vArray[vidx].y = top.Y; vArray[vidx].z = top.Z; - vArray[vidx].diffuse = DX8Wrapper::Convert_Color(intersection[top_int_idx][TOP_EDGE].RGBA); + vArray[vidx].diffuse = WW3DColor::To_ARGB(intersection[top_int_idx][TOP_EDGE].RGBA); vArray[vidx].u1 = u_values[0] + uv_offset.X; vArray[vidx].v1 = intersection[top_int_idx][TOP_EDGE].TexV + uv_offset.Y; vidx++; vArray[vidx].x = bottom.X; vArray[vidx].y = bottom.Y; vArray[vidx].z = bottom.Z; - vArray[vidx].diffuse = DX8Wrapper::Convert_Color(intersection[bottom_int_idx][BOTTOM_EDGE].RGBA); + vArray[vidx].diffuse = WW3DColor::To_ARGB(intersection[bottom_int_idx][BOTTOM_EDGE].RGBA); vArray[vidx].u1 = u_values[1] + uv_offset.X; vArray[vidx].v1 = intersection[bottom_int_idx][BOTTOM_EDGE].TexV + uv_offset.Y; vidx++; @@ -1039,7 +1069,7 @@ void SegLineRendererClass::Render vArray[vidx].x = bottom.X; vArray[vidx].y = bottom.Y; vArray[vidx].z = bottom.Z; - vArray[vidx].diffuse = DX8Wrapper::Convert_Color(intersection[bottom_int_idx][BOTTOM_EDGE].RGBA); + vArray[vidx].diffuse = WW3DColor::To_ARGB(intersection[bottom_int_idx][BOTTOM_EDGE].RGBA); vArray[vidx].u1 = u_values[1] + uv_offset.X; vArray[vidx].v1 = intersection[bottom_int_idx][BOTTOM_EDGE].TexV + uv_offset.Y; vidx++; @@ -1068,7 +1098,7 @@ void SegLineRendererClass::Render vArray[vidx].x = top.X; vArray[vidx].y = top.Y; vArray[vidx].z = top.Z; - vArray[vidx].diffuse = DX8Wrapper::Convert_Color(intersection[top_int_idx][TOP_EDGE].RGBA); + vArray[vidx].diffuse = WW3DColor::To_ARGB(intersection[top_int_idx][TOP_EDGE].RGBA); vArray[vidx].u1 = u_values[0] + uv_offset.X; vArray[vidx].v1 = intersection[top_int_idx][TOP_EDGE].TexV + uv_offset.Y; vidx++; @@ -1099,11 +1129,28 @@ void SegLineRendererClass::Render // If color is not white or opacity not 100%, enable gradient in shader and in renderer - otherwise disable. unsigned int rgba; - rgba=DX8Wrapper::Convert_Color(Color,Opacity); + rgba=WW3DColor::To_ARGB(Color,Opacity); bool rgba_all=(rgba==0xFFFFFFFF); // Enable sorting if sorting has not been disabled and line is translucent and alpha testing is not enabled. bool sorting = (!Is_Sorting_Disabled()) && (Shader.Get_Dst_Blend_Func() != ShaderClass::DSTBLEND_ZERO && Shader.Get_Alpha_Test() == ShaderClass::ALPHATEST_DISABLE); + if (diagSegline) + { + if (FILE *diag = std::fopen("ggc_segline_diag.txt", "a")) + { + const char *textureName = Texture != nullptr ? Texture->Get_Texture_Name().str() : ""; + std::fprintf(diag, + "segline-submit texture=%s sorting=%d vnum=%u polys=%u rgbaAll=%d mapMode=%d disableSort=%d\n", + textureName, + sorting ? 1 : 0, + vnum, + tidx, + rgba_all ? 1 : 0, + static_cast(map_mode), + Is_Sorting_Disabled() ? 1 : 0); + std::fclose(diag); + } + } ShaderClass shader = Shader; shader.Set_Cull_Mode(ShaderClass::CULL_MODE_DISABLE); @@ -1132,7 +1179,7 @@ void SegLineRendererClass::Render ** Render */ - DynamicVBAccessClass Verts((sorting?BUFFER_TYPE_DYNAMIC_SORTING:BUFFER_TYPE_DYNAMIC_DX8),dynamic_fvf_type,vnum); + DynamicVBAccessClass Verts((sorting?BUFFER_TYPE_DYNAMIC_SORTING:BUFFER_TYPE_DYNAMIC),dynamic_fvf_type,vnum); // Copy in the data to the VB { DynamicVBAccessClass::WriteLockClass Lock(&Verts); @@ -1160,7 +1207,7 @@ void SegLineRendererClass::Render } } - DynamicIBAccessClass ib_access((sorting?BUFFER_TYPE_DYNAMIC_SORTING:BUFFER_TYPE_DYNAMIC_DX8),tidx*3); + DynamicIBAccessClass ib_access((sorting?BUFFER_TYPE_DYNAMIC_SORTING:BUFFER_TYPE_DYNAMIC),tidx*3); { unsigned int i; DynamicIBAccessClass::WriteLockClass lock(&ib_access); @@ -1174,23 +1221,25 @@ 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) { + g_renderBackend->Set_Streak_Render_Active(true); SortingRendererClass::Insert_Triangles(obj_sphere,0,tidx,0,vnum); + g_renderBackend->Set_Streak_Render_Active(false); } 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_bloom_blur.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_bloom_blur.sc new file mode 100644 index 00000000000..72d1effda58 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_bloom_blur.sc @@ -0,0 +1,23 @@ +$input v_texcoord0 + +// TheSuperHackers @feature bobtista 15/06/2026 Separable Gaussian blur for bloom. +// Run once horizontally then once vertically (direction from u_bloomBlurDir) over +// the half-res bright-pass target. + +#include + +SAMPLER2D(s_tex0, 0); + +uniform vec4 u_bloomBlurDir; // xy = texel step in the blur direction + +void main() +{ + // 5-tap Gaussian using linear-sampling offsets/weights (9-tap kernel folded to 5 fetches). + vec2 d = u_bloomBlurDir.xy; + vec3 sum = texture2D(s_tex0, v_texcoord0).rgb * 0.227027; + sum += texture2D(s_tex0, v_texcoord0 + d * 1.3846).rgb * 0.316216; + sum += texture2D(s_tex0, v_texcoord0 - d * 1.3846).rgb * 0.316216; + sum += texture2D(s_tex0, v_texcoord0 + d * 3.2308).rgb * 0.070270; + sum += texture2D(s_tex0, v_texcoord0 - d * 3.2308).rgb * 0.070270; + gl_FragColor = vec4(sum, 1.0); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_bloom_bright.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_bloom_bright.sc new file mode 100644 index 00000000000..5bf1146a133 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_bloom_bright.sc @@ -0,0 +1,32 @@ +$input v_texcoord0 + +// TheSuperHackers @feature bobtista 15/06/2026 Bloom bright-pass. Extracts the +// portion of each pixel above a luma threshold into a half-res target, which is +// then blurred and added back over the scene in the composite pass. + +#include + +SAMPLER2D(s_tex0, 0); + +uniform vec4 u_bloomParams; // x = threshold, y = intensity (unused here) + +#define LUMA_WEIGHTS vec3(0.299, 0.587, 0.114) +#define BLOOM_SOFT_KNEE 0.05 +#define EPS_DIV_GUARD 0.0001 + +void main() +{ + vec3 c = texture2D(s_tex0, v_texcoord0).rgb; + float luma = dot(c, LUMA_WEIGHTS); + float threshold = u_bloomParams.x; + // TheSuperHackers @tweak bobtista 15/06/2026 Soft-knee bright-pass with a tight + // knee so bloom only catches pixels right at/above the threshold. A wide knee + // effectively lowers the threshold and makes large bright areas (sunlit terrain, + // dirt roads) glow and wash the scene out. + float knee = BLOOM_SOFT_KNEE; + float soft = clamp(luma - threshold + knee, 0.0, 2.0 * knee); + soft = (soft * soft) / (4.0 * knee + EPS_DIV_GUARD); + float contrib = max(soft, luma - threshold); + contrib = contrib / max(luma, EPS_DIV_GUARD); + gl_FragColor = vec4(c * contrib, 1.0); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_copy.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_copy.sc new file mode 100644 index 00000000000..8f601f0d907 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_copy.sc @@ -0,0 +1,14 @@ +$input v_texcoord0 + +// TheSuperHackers @feature bobtista 15/06/2026 Trivial fullscreen texture copy. +// Used to resolve the (possibly MSAA) scene color into the single-sample smudge +// snapshot, since bgfx::blit cannot read a multisampled source. + +#include + +SAMPLER2D(s_tex0, 0); + +void main() +{ + gl_FragColor = texture2D(s_tex0, v_texcoord0); +} 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/fs_scene_composite.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_scene_composite.sc new file mode 100644 index 00000000000..c99b4c7ee4e --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_scene_composite.sc @@ -0,0 +1,201 @@ +$input v_texcoord0 + +// TheSuperHackers @feature bobtista 27/04/2026 Identity scene +// composite fragment shader. This is intentionally a no-op visual pass; +// future post effects can build on this without touching world submits. + +#include + +SAMPLER2D(s_tex0, 0); +SAMPLER2D(s_bloom, 2); +SAMPLER2D(s_ssao, 3); + +uniform vec4 u_postParams; +uniform vec4 u_postTexelSize; +// TheSuperHackers @feature bobtista 15/06/2026 u_wipeParams.x = split position +// 0..1, .y = enabled. Left of the split shows the unprocessed scene, right shows +// the processed result, for live before/after comparison of post effects. +uniform vec4 u_wipeParams; +// TheSuperHackers @feature bobtista 15/06/2026 u_colorGradeParams.x = enabled, +// .y = strength 0..1, .z = temperature -1..1 (cool..warm), .w = tint -1..1. +uniform vec4 u_colorGradeParams; +// TheSuperHackers @feature bobtista 15/06/2026 u_bloomParams.x = threshold (used +// by the bright pass), .y = intensity added over the scene here. +uniform vec4 u_bloomParams; +// TheSuperHackers @feature bobtista 15/06/2026 u_hdrParams.x = HDR enabled. When +// set, the scene target is RGBA16F and the final output is ACES-tonemapped instead +// of hard-clamped, giving highlight rolloff and richer bloom. +uniform vec4 u_hdrParams; +// TheSuperHackers @feature bobtista 15/06/2026 Cheap post effects. x = vignette +// strength, y = chromatic aberration amount, z = film grain strength, w = grain +// time seed. Each is 0 when its effect is off. +uniform vec4 u_postFx2Params; + +// TheSuperHackers @tweak bobtista 05/06/2026 BT.601 luma weights (matches fs_uber.sc). +#define LUMA_WEIGHTS vec3(0.299, 0.587, 0.114) + +// Highlight-rolloff knee: identity below, smooth compression above. +#define TONEMAP_KNEE 0.80 +// FXAA-style tuning: edge-detect threshold and direction-reduce terms (1/128, 1/512, +// per the FXAA 3.11 reference values). +#define FXAA_EDGE_THRESHOLD 0.06 +#define FXAA_REDUCE_MUL 0.0078125 +#define FXAA_REDUCE_MIN 0.001953125 +// Chromatic-aberration UV offset per unit amount, and color-grade curve terms. +#define CHROMA_OFFSET_SCALE 0.012 +#define GRADE_CHANNEL_SHIFT 0.10 +#define GRADE_CURVE_GAIN 0.20 +#define GRADE_CURVE_BASE 0.85 +// Wipe split line half-width in texels. +#define WIPE_LINE_TEXELS 1.5 + +// TheSuperHackers @tweak bobtista 15/06/2026 Gentle highlight rolloff. Identity in +// the SDR range (so the base scene is unchanged and never washed out), smoothly +// compressing only values above the knee toward 1.0 so genuine highlights do not +// clip and feed bloom cleanly. This game's art is display-referred, so a full +// filmic tonemap over-brightens it; this only touches the over-bright extremes. +vec3 tonemapHighlights(vec3 x) +{ + float knee = TONEMAP_KNEE; + float headroom = 1.0 - knee; + vec3 lo = min(x, vec3(knee, knee, knee)); + vec3 hi = max(x - vec3(knee, knee, knee), vec3(0.0, 0.0, 0.0)); + hi = hi / (1.0 + hi / headroom); + return clamp(lo + hi, 0.0, 1.0); +} + +void main() +{ + vec4 color = texture2D(s_tex0, v_texcoord0); + vec3 rawColor = color.rgb; + + // Chromatic aberration: split the red/blue channels along the radial direction, + // stronger toward the screen edges. Captured after rawColor so the wipe's + // before side stays clean. + if (u_postFx2Params.y > 0.001) + { + vec2 caOffset = (v_texcoord0 - vec2(0.5, 0.5)) * (u_postFx2Params.y * CHROMA_OFFSET_SCALE); + color.r = texture2D(s_tex0, v_texcoord0 + caOffset).r; + color.b = texture2D(s_tex0, v_texcoord0 - caOffset).b; + } + + // SSAO: darken the scene by the ambient-occlusion factor (u_hdrParams.y = apply). + if (u_hdrParams.y > 0.5) + { + color.rgb *= texture2D(s_ssao, v_texcoord0).r; + } + + // u_postParams.x = sharpen amount, y = saturation, z = contrast, + // w = edge-aware FXAA-style smoothing amount. Defaults are identity: + // (0, 1, 1, 0). + if (u_postParams.w > 0.001) + { + vec3 nw = texture2D(s_tex0, v_texcoord0 + vec2(-u_postTexelSize.x, -u_postTexelSize.y)).rgb; + vec3 ne = texture2D(s_tex0, v_texcoord0 + vec2( u_postTexelSize.x, -u_postTexelSize.y)).rgb; + vec3 sw = texture2D(s_tex0, v_texcoord0 + vec2(-u_postTexelSize.x, u_postTexelSize.y)).rgb; + vec3 se = texture2D(s_tex0, v_texcoord0 + vec2( u_postTexelSize.x, u_postTexelSize.y)).rgb; + vec3 lumaVec = LUMA_WEIGHTS; + float lumaNW = dot(nw, lumaVec); + float lumaNE = dot(ne, lumaVec); + float lumaSW = dot(sw, lumaVec); + float lumaSE = dot(se, lumaVec); + float lumaM = dot(color.rgb, lumaVec); + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); + float edgeRange = lumaMax - lumaMin; + if (edgeRange > FXAA_EDGE_THRESHOLD) + { + vec2 dir; + dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); + dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); + float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * FXAA_REDUCE_MUL, FXAA_REDUCE_MIN); + float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); + dir = clamp(dir * rcpDirMin, vec2(-2.0, -2.0), vec2(2.0, 2.0)) * u_postTexelSize.xy; + vec3 aa = (texture2D(s_tex0, v_texcoord0 + dir * -0.5).rgb + + texture2D(s_tex0, v_texcoord0 + dir * 0.5).rgb) * 0.5; + float aaLuma = dot(aa, lumaVec); + if (aaLuma >= lumaMin && aaLuma <= lumaMax) + { + color.rgb = mix(color.rgb, aa, u_postParams.w); + } + } + } + + if (u_postParams.x > 0.001) + { + vec3 n = texture2D(s_tex0, v_texcoord0 + vec2(0.0, -u_postTexelSize.y)).rgb; + vec3 s = texture2D(s_tex0, v_texcoord0 + vec2(0.0, u_postTexelSize.y)).rgb; + vec3 e = texture2D(s_tex0, v_texcoord0 + vec2( u_postTexelSize.x, 0.0)).rgb; + vec3 w = texture2D(s_tex0, v_texcoord0 + vec2(-u_postTexelSize.x, 0.0)).rgb; + vec3 blur = (n + s + e + w + color.rgb) * 0.2; + color.rgb = color.rgb + (color.rgb - blur) * u_postParams.x; + } + + float luma = dot(color.rgb, LUMA_WEIGHTS); + color.rgb = mix(vec3(luma, luma, luma), color.rgb, u_postParams.y); + color.rgb = (color.rgb - vec3(0.5, 0.5, 0.5)) * u_postParams.z + vec3(0.5, 0.5, 0.5); + + if (u_colorGradeParams.x > 0.5) + { + vec3 graded = color.rgb; + graded.r += u_colorGradeParams.z * GRADE_CHANNEL_SHIFT; + graded.b -= u_colorGradeParams.z * GRADE_CHANNEL_SHIFT; + graded.g += u_colorGradeParams.w * GRADE_CHANNEL_SHIFT; + graded = graded * (graded * GRADE_CURVE_GAIN + GRADE_CURVE_BASE); + graded = clamp(graded, 0.0, 1.0); + color.rgb = mix(color.rgb, graded, u_colorGradeParams.y); + } + + // Map the scene to display range first: highlight rolloff (HDR) or hard clamp + // (LDR). + vec3 processedOut; + if (u_hdrParams.x > 0.5) + { + processedOut = tonemapHighlights(color.rgb); + } + else + { + processedOut = clamp(color.rgb, 0.0, 1.0); + } + + // TheSuperHackers @tweak bobtista 15/06/2026 Add bloom AFTER tonemap/clamp so the + // HDR highlight rolloff does not compress the glow back down - bloom is additive + // light on top of the display-range image. + if (u_bloomParams.y > 0.001) + { + processedOut += texture2D(s_bloom, v_texcoord0).rgb * u_bloomParams.y; + } + processedOut = clamp(processedOut, 0.0, 1.0); + + // Vignette: darken toward the screen corners. + if (u_postFx2Params.x > 0.001) + { + vec2 vd = v_texcoord0 - vec2(0.5, 0.5); + float vig = 1.0 - dot(vd, vd) * (u_postFx2Params.x * 2.0); + processedOut *= clamp(vig, 0.0, 1.0); + } + + // Film grain: add animated per-pixel noise (time seed in .w keeps it moving). + if (u_postFx2Params.z > 0.001) + { + float grain = fract(sin(dot(v_texcoord0 * (u_postFx2Params.w + 1.0), vec2(12.9898, 78.233))) * 43758.5453); + processedOut += (grain - 0.5) * u_postFx2Params.z; + processedOut = clamp(processedOut, 0.0, 1.0); + } + + vec3 outRgb = processedOut; + if (u_wipeParams.y > 0.5) + { + // Left of the split shows the raw scene with every enhancement off (no + // post, color grade, bloom, or HDR tonemap) for a true before/after. + vec3 beforeOut = clamp(rawColor, 0.0, 1.0); + float side = step(u_wipeParams.x, v_texcoord0.x); + outRgb = mix(beforeOut, processedOut, side); + if (abs(v_texcoord0.x - u_wipeParams.x) < u_postTexelSize.x * WIPE_LINE_TEXELS) + { + outRgb = vec3(1.0, 1.0, 1.0); + } + } + + gl_FragColor = vec4(outRgb, color.a); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_scene_depth.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_scene_depth.sc new file mode 100644 index 00000000000..d0d4318dc8b --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_scene_depth.sc @@ -0,0 +1,11 @@ +$input v_sceneDepth + +// TheSuperHackers @feature bobtista 27/04/2026 Write normalized scene +// depth into an R32F render target for future post effects and particles. + +#include + +void main() +{ + gl_FragColor = vec4(v_sceneDepth.x, v_sceneDepth.x, v_sceneDepth.x, 1.0); +} 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..01e4841cc23 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_shadow_caster.sc @@ -0,0 +1,25 @@ +$input v_sceneDepth, v_texcoord0 + +// TheSuperHackers @feature bobtista 16/06/2026 Shadow-map caster fragment shader. +// Writes normalized light-space depth (R32F) like fs_scene_depth, but first applies +// the same alpha test the main pass uses, so alpha-tested cutout geometry (infantry, +// foliage) casts its real silhouette instead of a solid bounding quad. Opaque casters +// pass u_atestParams.y <= 0.5 and skip the test entirely. + +#include + +SAMPLER2D(s_tex0, 0); +uniform vec4 u_atestParams; // x = alpha ref, y = func id (> 0.5 = test active) + +void main() +{ + if (u_atestParams.y > 0.5) + { + float a = texture2D(s_tex0, v_texcoord0).a; + if (a < u_atestParams.x) + { + discard; + } + } + gl_FragColor = vec4(v_sceneDepth.x, v_sceneDepth.x, v_sceneDepth.x, 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_smudge.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_smudge.sc new file mode 100644 index 00000000000..87dbb6fb44c --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_smudge.sc @@ -0,0 +1,27 @@ +$input v_color0, v_texcoord0 + +// TheSuperHackers @feature bobtista 27/04/2026 Dedicated bgfx +// smudge/heat-haze fragment shader. Samples the scene-color snapshot at the +// CPU-displaced UV and writes it with the radial vertex-alpha mask, so the +// host alpha blend reproduces the DX8 fixed-function heat-haze lens. + +#include + +SAMPLER2D(s_tex0, 0); +uniform vec4 u_smudgeClip; + +void main() +{ + float mask = clamp(v_color0.a, 0.0, 1.0); + if (mask <= 0.003) + { + discard; + } + + vec2 clipUV = clamp(u_smudgeClip.zw, vec2(0.0, 0.0), vec2(1.0, 1.0)); + vec2 uv = clamp(v_texcoord0, vec2(0.0, 0.0), clipUV); + vec3 scene = texture2D(s_tex0, uv).rgb; + // Warm modulation matching the DX8 0xffeedd smudge diffuse tint. + vec3 warmTint = vec3(1.0, 0.9333, 0.8667); + gl_FragColor = vec4(scene * warmTint, mask); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_ssao.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_ssao.sc new file mode 100644 index 00000000000..5f991710cff --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_ssao.sc @@ -0,0 +1,82 @@ +$input v_texcoord0 + +// TheSuperHackers @feature bobtista 15/06/2026 Screen-space ambient occlusion. +// Reconstructs view-space position from the scene depth target and samples a +// camera-facing hemisphere; a sample is occluded when the stored geometry at its +// screen position is closer to the camera than the sample point. Output is a +// single AO factor (1 = unoccluded). + +#include + +SAMPLER2D(s_sceneDepth, 1); // R32F, stores clip.z/clip.w (homogeneous depth) + +uniform mat4 u_ssaoInvProj; +uniform mat4 u_ssaoProj; +uniform vec4 u_ssaoParams; // x = radius, y = intensity, z = bias +uniform vec4 u_postTexelSize; // .xy = 1/width, 1/height + +#define SSAO_SAMPLES 12 + +vec3 reconstructViewPos(vec2 uv, float ndcDepth) +{ + vec2 ndcXY = uv * 2.0 - 1.0; +#if !BGFX_SHADER_LANGUAGE_GLSL + ndcXY.y = -ndcXY.y; +#endif + vec4 clip = vec4(ndcXY, ndcDepth, 1.0); + vec4 view = mul(u_ssaoInvProj, clip); + return view.xyz / view.w; +} + +void main() +{ + float centerDepth = texture2D(s_sceneDepth, v_texcoord0).x; + // Skip the far plane (sky) and uninitialized depth. + if (centerDepth >= 0.9999 || centerDepth <= 0.0001) + { + gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); + return; + } + + vec3 P = reconstructViewPos(v_texcoord0, centerDepth); + // Standard fract-sin screen-space hash, scaled to a full rotation. + float rnd = fract(sin(dot(v_texcoord0, vec2(12.9898, 78.233))) * 43758.5453) * 6.2831853; + float radius = u_ssaoParams.x; + float bias = u_ssaoParams.z; + float occlusion = 0.0; + + for (int i = 0; i < SSAO_SAMPLES; i++) + { + float a = rnd + float(i) / float(SSAO_SAMPLES) * 6.2831853; + float r = sqrt((float(i) + 0.5) / float(SSAO_SAMPLES)); + // Hemisphere biased toward the camera (view -Z) so samples sit in front of + // the surface; geometry that intrudes in front of a sample occludes it. + vec3 dir = vec3(cos(a) * r, sin(a) * r, -(0.35 + 0.65 * r)); + vec3 samplePos = P + dir * radius; + + vec4 sclip = mul(u_ssaoProj, vec4(samplePos, 1.0)); + vec3 sndc = sclip.xyz / sclip.w; + vec2 suv = sndc.xy * 0.5 + 0.5; +#if !BGFX_SHADER_LANGUAGE_GLSL + suv.y = 1.0 - suv.y; +#endif + if (suv.x < 0.0 || suv.x > 1.0 || suv.y < 0.0 || suv.y > 1.0) + { + continue; + } + + float storedDepth = texture2D(s_sceneDepth, suv).x; + // Smaller homogeneous depth = closer to camera. Occluded when the stored + // geometry is meaningfully closer than the sample point. + if (sndc.z - storedDepth > bias) + { + vec3 storedPos = reconstructViewPos(suv, storedDepth); + float rangeCheck = smoothstep(0.0, 1.0, radius / max(length(storedPos - P), 0.0001)); + occlusion += rangeCheck; + } + } + + occlusion = occlusion / float(SSAO_SAMPLES); + float ao = clamp(1.0 - occlusion * u_ssaoParams.y, 0.0, 1.0); + gl_FragColor = vec4(ao, ao, ao, 1.0); +} 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..0c0e9ca1a6f --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/fs_uber.sc @@ -0,0 +1,1392 @@ +$input v_color0, v_texcoord0, v_texcoord1, v_normal, v_cloudUV, v_stage0UV, v_stage1UV, v_stage2UV, v_sceneDepth, v_worldPos + +#include + +SAMPLER2D(s_tex0, 0); +SAMPLER2D(s_tex1, 1); +SAMPLER2D(s_tex2, 2); +SAMPLER2D(s_tex3, 3); +// fs_uber_array variant: stage 0 reads a texture2DArray layer selected per +// vertex (layer index carried in v_texcoord1.x by the sorted merge path). +// Compiled from this same source with GGC_UBER_STAGE0_ARRAY=1 so every other +// shading path stays byte-identical to the plain uber program. +#if GGC_UBER_STAGE0_ARRAY +SAMPLER2DARRAY(s_texArray, 4); +#define GGC_SAMPLE_STAGE0(uv) texture2DArray(s_texArray, vec3(v_texcoord1.xy, floor(v_normal.z))) +#define GGC_SAMPLE_STAGE0_ANISO(uv) texture2DArray(s_texArray, vec3(v_texcoord1.xy, floor(v_normal.z))) +#else +#define GGC_SAMPLE_STAGE0(uv) texture2D(s_tex0, uv) +#define GGC_SAMPLE_STAGE0_ANISO(uv) sampleAniso(s_tex0, uv) +#endif +// Terrain cloud-shadow scroll texture (BASE_NOISE1/NOISE12 paths on DX8). +SAMPLER2D(s_cloudMap, 5); +SAMPLER2D(s_sceneDepth, 6); +// Specular/rim uniforms not in trunk's packed u_material[] array (which already +// provides u_matDiffuse/Ambient/Emissive/tssOps/atest via #define). +uniform vec4 u_matSpecular; // rgb = specular color, w = shininess (Blinn-Phong power) +uniform vec4 u_matFx; // x specular strength, y rim strength, z rim power, w emissive boost +uniform vec4 u_eyePos; // xyz = world-space camera position +SAMPLER2D(s_shadowMap, 7); +// TheSuperHackers @feature bobtista 23/06/2026 Point-light shadow map sampler and uniforms. +// s_pointShadowMap is the depth map rendered from the strongest CastsShadows dynamic light. +// u_pointShadowMatrix is the world->shadow-clip matrix for that light. +// u_pointShadowParams: x=active(1)/none(-1), y=bias, z=texel(1/mapSize), w=strength. +// The nuke caster is a dedicated light (not a LightEnvironment slot): +// u_pointShadowLightPos = world xyz + outer range, u_pointShadowLightColor = diffuse rgb. +SAMPLER2D(s_pointShadowMap, 8); +// TheSuperHackers @feature bobtista 14/07/2026 Second point-shadow slot: transient lightning +// flash lights near the particle-cannon beam cast their own shadow alongside the primary. +SAMPLER2D(s_pointShadowMap2, 10); +// Static terrain noise/lightmap layer (DX8 ST_TERRAIN_BASE_NOISE2); white when disabled. +SAMPLER2D(s_lightMap, 11); +// TheSuperHackers @performance bobtista Global per-frame constants (sun-shadow and point- +// shadow transforms/params, scene ambient) are identical for every draw in a frame. The +// GGC_UBER_FRAME_TEXTURE variant reads them from a small data texture instead of per-draw +// uniforms, shrinking the fs_uber constant buffer by ~352 bytes/draw so heavy scenes stay +// under bgfx's fixed 8MB Metal uniform arena. Texel layout MUST match PackFrameConstTexture() +// in BgfxBackend.cpp. The plain (non-texture) program stays byte-identical; the backend picks +// it when GGC_BGFX_NO_UNIFORM_FRAME_TEXTURE is set. +#if GGC_UBER_FRAME_TEXTURE +SAMPLER2D(s_frameConst, 9); +vec4 ggcFrameConst(int i) { return texture2DLod(s_frameConst, vec2((float(i) + 0.5) / 24.0, 0.5), 0.0); } +#define u_shadowParams ggcFrameConst(1) +#define u_shadowQuality ggcFrameConst(2) +#define u_pointShadowParams ggcFrameConst(7) +#define u_pointShadowLightPos ggcFrameConst(8) +#define u_pointShadowLightColor ggcFrameConst(9) +#define u_pointShadow2Params ggcFrameConst(14) +#define u_pointShadow2LightPos ggcFrameConst(15) +#define u_pointShadow2LightColor ggcFrameConst(16) +// The packer memcpys the bgfx column-major float[16] (texel = one column), so the matrices +// must be rebuilt with mtxFromCols - the same convention the instanced vertex path uses for +// i_data0-3. A raw mat4() constructor is not portable across shader languages and yields a +// transposed transform on Metal. +#define GGC_SUN_SHADOW_MATRIX mtxFromCols(ggcFrameConst(3), ggcFrameConst(4), ggcFrameConst(5), ggcFrameConst(6)) +#define GGC_POINT_SHADOW_MATRIX mtxFromCols(ggcFrameConst(10), ggcFrameConst(11), ggcFrameConst(12), ggcFrameConst(13)) +#define GGC_POINT_SHADOW2_MATRIX mtxFromCols(ggcFrameConst(17), ggcFrameConst(18), ggcFrameConst(19), ggcFrameConst(20)) +#define GGC_SCENE_AMBIENT u_sceneAmbient +#else +uniform mat4 u_pointShadowMatrix; +uniform vec4 u_pointShadowParams; +uniform vec4 u_pointShadowLightPos; +uniform vec4 u_pointShadowLightColor; +uniform mat4 u_pointShadow2Matrix; +uniform vec4 u_pointShadow2Params; +uniform vec4 u_pointShadow2LightPos; +uniform vec4 u_pointShadow2LightColor; +uniform mat4 u_shadowMatrices[1]; // single camera-fit sun shadow map (was [3] cascades; [1]/[2] unused) +uniform vec4 u_shadowParams; // x atlas texel size, y depth bias, z strength, w enabled +uniform vec4 u_shadowQuality; // x: >0.5 = full 36-fetch PCF, else reduced 9-fetch (default) +#define GGC_SUN_SHADOW_MATRIX u_shadowMatrices[0] +#define GGC_POINT_SHADOW_MATRIX u_pointShadowMatrix +#define GGC_POINT_SHADOW2_MATRIX u_pointShadow2Matrix +#define GGC_SCENE_AMBIENT u_sceneAmbient +#endif +uniform vec4 u_sunShadowReceive; // x>0.5 = this object draw receives the sun cast shadow +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_lightAmbients[4]; // per-light ambient color (rgb) +uniform vec4 u_lightPositions[4]; // per-light world position (xyz) +uniform vec4 u_lightParams[4]; // x inner range, y outer/range, z > 0.5 point, w enabled +// TheSuperHackers @bugfix bobtista Scene ambient is NOT frame-constant: fog-of-war shrouded +// objects carry a dimmed per-object ambient, so it stays a per-draw uniform in both variants +// (unlike the sun/point-shadow transforms, which are genuinely global per frame). Extracting it +// would light shrouded objects with the bright global ambient instead of leaving them fog-dimmed. +uniform vec4 u_sceneAmbient; // scene ambient color (rgb) +// TheSuperHackers @feature bobtista 15/07/2026 GGC_PCANNON_ENHANCED world-space radial scene dip: +// xy = dim centre (the drama light), z = 1/falloffWidth, w = far dim factor (1 = inactive). +// The area around the beam keeps full brightness; the scene eases darker with distance. +uniform vec4 u_dramaDim; +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_shroudParams; // xy = offset, zw = scale +uniform vec4 u_softParticleParams; // .x enable, .y fade scale, zw inverse scene size + +// TheSuperHackers @performance bobtista 15/06/2026 Packed per-draw material uniforms. +// Index order MUST match MaterialUniformSlot in BgfxBackend.cpp. +uniform vec4 u_material[25]; +#define u_matDiffuse u_material[0] +#define u_matAmbient u_material[1] +#define u_matEmissive u_material[2] +#define u_tssOps0 u_material[3] +#define u_tssOps1 u_material[4] +#define u_atestParams u_material[5] +#define u_texcoordSource u_material[6] +#define u_vertexColorFlags u_material[7] +#define u_texcoordSelect2 u_material[8] +#define u_projectedDecalMode u_material[9] +#define u_grayscaleEnable u_material[10] +#define u_objectShroudDim u_material[11] +#define u_cloudParams u_material[12] +#define u_texTransform0 u_material[13] +#define u_texTransform1 u_material[14] +#define u_texTransform0Z u_material[15] +#define u_tex1Transform0 u_material[16] +#define u_tex1Transform1 u_material[17] +#define u_tex1TransformZ u_material[18] +#define u_tex2Transform0 u_material[19] +#define u_tex2Transform1 u_material[20] +#define u_texProjected u_material[21] +#define u_legacyPixelShaderMode u_material[22] +#define u_zBias u_material[23] +#define u_lightMapParams u_material[24] + +// 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 +#define TSS_ADDSIGNED2X 11.0 +#define TSS_MODALPHAADDCOLOR 12.0 +#define TSS_SUBTRACTREV 13.0 + +// Arg source IDs +#define SRC_TEXTURE 0.0 +#define SRC_DIFFUSE 1.0 +#define SRC_CURRENT 2.0 + +// RenderBackendProjectedDecalMode values from IRenderBackend.h. +#define PROJECTED_DECAL_BLOB_SHADOW 1.0 +#define PROJECTED_DECAL_ADDITIVE 2.0 +#define PROJECTED_DECAL_ALPHA 3.0 +#define PROJECTED_DECAL_MULTIPLY 4.0 + +#define CLOUD_SHADOW_MIN 0.72 +// TheSuperHackers @tweak bobtista 23/06/2026 Keep the restored bgfx cloud-shadow +// layer subtle. Applying TSCloudMed at full strength creates a map-wide 14-18% +// terrain dim that reads like unrelated superweapon/explosion smoke. +#define CLOUD_SHADOW_STRENGTH 0.35 +// 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) +// Additive projector/effect textures often carry a pure-black matte around +// useful glow pixels. In D3D8 that matte is a no-op for additive blending; on +// bgfx it can survive as visible matte fragments unless discarded. +// Half of one 8-bit color step is "rounds to black" in authored effect mattes. +#define ADDITIVE_MATTE_EPSILON (0.5 / 255.0) +#define ALPHA_MASK_EPSILON (0.5 / 255.0) +// 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 + +// Maximum darkening applied at the blob center under the multiplicative blend. +// The blob darkening is driven directly by ShadowI's soft alpha (no smoothstep): +// the alpha over the coarse heightmap receiver is mostly below 0.5, so a smoothstep +// gate crushed it to nothing and made infantry shadows invisible on bgfx. +#define BLOB_MASK_MAX_DARKNESS 0.7 + +bool alphaTestPass(float alpha, float ref, float func) +{ + if (func < 0.5) + { + return true; + } + if (func < 1.5) { return false; } // D3DCMP_NEVER + if (func < 2.5) { return alpha < ref; } // D3DCMP_LESS + if (func < 3.5) { return abs(alpha - ref) <= ALPHA_MASK_EPSILON; } + if (func < 4.5) { return alpha <= ref; } // D3DCMP_LESSEQUAL + if (func < 5.5) { return alpha > ref; } // D3DCMP_GREATER + if (func < 6.5) { return abs(alpha - ref) > ALPHA_MASK_EPSILON; } + if (func < 7.5) { return alpha >= ref; } // D3DCMP_GREATEREQUAL + return true; // D3DCMP_ALWAYS +} + +vec3 applyColorOp(float op, vec3 arg1, vec3 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)/MODALPHAADDCOLOR(12) handled by + // caller, ADDSMOOTH(10), ADDSIGNED2X(11), SUBTRACTREV(13) + if (op < 6.5) + { + return arg1 + arg2 - vec3_splat(0.5); + } + if (op < 7.5) + { + return arg1 - arg2; + } + if (op < 9.5) + { + return arg1; + } + if (op < 10.5) + { + return arg1 + arg2 - arg1 * arg2; + } + if (op < 11.5) + { + return clamp((arg1 + arg2 - vec3_splat(0.5)) * 2.0, vec3_splat(0.0), vec3_splat(1.0)); + } + if (op > 12.5) + { + return arg2 - arg1; + } + return arg1; +} + +float applyAlphaOp(float op, float arg1, float 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; + } + if (op < 10.5) + { + return arg1 + arg2 - arg1 * arg2; + } + if (op < 11.5) + { + return clamp((arg1 + arg2 - 0.5) * 2.0, 0.0, 1.0); + } + if (op > 12.5) + { + return arg2 - arg1; + } + return arg1; +} + +vec3 sampleCloudShadow(vec2 cloudUV) +{ + vec3 cloudSample = texture2D(s_cloudMap, cloudUV).rgb; + vec3 cloudShadow = max(cloudSample, vec3_splat(CLOUD_SHADOW_MIN)); + return mix(vec3_splat(1.0), cloudShadow, CLOUD_SHADOW_STRENGTH); +} + +// World-space normal offset for the shadow receive point, pushing the sample off the surface +// along its normal to clear self-shadow acne without visibly detaching the shadow (peter-pan). +#define SUN_SHADOW_NORMAL_OFFSET 1.2 + +// TheSuperHackers @refactor bobtista 18/06/2026 Single camera-fit sun shadow lookup (replaces the +// 3 concentric cascades). One ortho map is fit to the visible ground footprint in SetupSunShadowView, +// so a caster and the ground its shadow lands on are always in the same map - no cascade selection, +// no tight/coarse caster-receiver mismatch, no per-zoom fall-through or cascade-blend. u_shadowMatrices[0] +// is the world->shadow-clip matrix; u_shadowParams = (uv texel, depth bias, strength, enabled). +// Returns the lit fraction in [0,1]: 1 = fully lit, 0 = fully shadowed. nrm is the surface normal. +float sampleSunShadow(vec3 worldPos, vec3 nrm) +{ + if (u_shadowParams.w < 0.5) + { + return 1.0; + } + vec3 n = normalize(nrm); + vec3 lightDir = normalize(u_lightDirs[0].xyz); + float ndotl = clamp(dot(n, lightDir), 0.0, 1.0); + float slope = 1.0 + 2.0 * (1.0 - ndotl); + float texel = u_shadowParams.x; // shadow-map texel in UV (1 / map size) + float bias = u_shadowParams.y; + vec3 biasedPos = worldPos + lightDir * (SUN_SHADOW_NORMAL_OFFSET * slope); + vec4 sc = mul(GGC_SUN_SHADOW_MATRIX, vec4(biasedPos, 1.0)); + if (sc.w <= 0.0) + { + return 1.0; + } + vec3 ndc = sc.xyz / sc.w; + vec2 cuv = ndc.xy * 0.5 + 0.5; +#if !BGFX_SHADER_LANGUAGE_GLSL + cuv.y = 1.0 - cuv.y; +#endif + if (cuv.x < 0.0 || cuv.x > 1.0 || cuv.y < 0.0 || cuv.y > 1.0) + { + return 1.0; // outside the shadow footprint = lit + } + float curDepth = clamp(ndc.z, 0.0, 1.0) - bias; + // TheSuperHackers @performance bobtista 28/06/2026 The sun-shadow PCF is the single biggest + // full-screen frame cost. The original 3x3 grid did 4 manual-bilinear point fetches per tap = 36 + // shadow-map fetches per pixel. The reduced path collapses each tap to one point fetch (9 fetches, + // 4x fewer): the 3x3 spread still softens the edge, only the sub-texel bilinear is lost (slightly + // harder texel stepping). u_shadowQuality.x selects: >0.5 = original 36-fetch, else reduced 9-fetch + // (default). GGC_BGFX_SHADOW_FULL_PCF=1 restores the original for a quality/perf A/B in one binary. + float lit = 0.0; + if (u_shadowQuality.x > 0.5) + { + float invTexel = 1.0 / texel; + for (int dy = -1; dy <= 1; ++dy) + { + for (int dx = -1; dx <= 1; ++dx) + { + vec2 sampUV = cuv + vec2(float(dx), float(dy)) * texel; + vec2 texelCoord = sampUV * invTexel - 0.5; + vec2 fracPart = fract(texelCoord); + vec2 baseUV = (floor(texelCoord) + 0.5) * texel; + float s00 = (curDepth <= texture2D(s_shadowMap, baseUV).x) ? 1.0 : 0.0; + float s10 = (curDepth <= texture2D(s_shadowMap, baseUV + vec2(texel, 0.0)).x) ? 1.0 : 0.0; + float s01 = (curDepth <= texture2D(s_shadowMap, baseUV + vec2(0.0, texel)).x) ? 1.0 : 0.0; + float s11 = (curDepth <= texture2D(s_shadowMap, baseUV + vec2(texel, texel)).x) ? 1.0 : 0.0; + lit += mix(mix(s00, s10, fracPart.x), mix(s01, s11, fracPart.x), fracPart.y); + } + } + } + else + { + for (int dy = -1; dy <= 1; ++dy) + { + for (int dx = -1; dx <= 1; ++dx) + { + vec2 sampUV = cuv + vec2(float(dx), float(dy)) * texel; + lit += (curDepth <= texture2D(s_shadowMap, sampUV).x) ? 1.0 : 0.0; + } + } + } + return lit * (1.0 / 9.0); +} + +// TheSuperHackers @feature bobtista 16/06/2026 Sun-shadow color multiplier. Returns the factor to +// multiply a lit pixel by; shadowed pixels darken toward an ambient floor (1 - strength), never to +// black. Terrain passes world-up because its mesh normal is degenerate (n.z ~ 0); objects pass +// their real normal so the normal-offset bias keeps them from self-shadowing their own facing side. +float sunShadowFactor(vec3 worldPos, vec3 rawNormal) +{ + if (u_shadowParams.w < 0.5) + { + return 1.0; + } + float normalLen2 = dot(rawNormal, rawNormal); + vec3 receiverNormal = (normalLen2 > 1e-6) ? rawNormal : vec3(0.0, 0.0, 1.0); + float lit = sampleSunShadow(worldPos, receiverNormal); + float receiverWeight = smoothstep(0.20, 0.45, receiverNormal.z); + return mix(1.0, mix(1.0 - u_shadowParams.z, 1.0, lit), receiverWeight); +} + +// TheSuperHackers @feature bobtista 23/06/2026 Point-light shadow lookup. Mirrors sampleSunShadow +// but projects through u_pointShadowMatrix (a perspective map) and offsets the sample position +// toward the point light instead of the sun direction. Returns lit fraction [0,1]. +float samplePointShadow(vec3 worldPos, vec3 nrm) +{ + if (u_pointShadowParams.x < 0.0) + { + return 1.0; + } + vec3 n = normalize(nrm); + vec3 toLight = u_pointShadowLightPos.xyz - worldPos; + float dist = length(toLight); + vec3 lightDir = (dist > 0.0001) ? (toLight / dist) : vec3(0.0, 0.0, 1.0); + float ndotl = clamp(dot(n, lightDir), 0.0, 1.0); + float slope = 1.0 + 2.0 * (1.0 - ndotl); + float texel = u_pointShadowParams.z; + float bias = u_pointShadowParams.y; + vec3 biasedPos = worldPos + lightDir * (SUN_SHADOW_NORMAL_OFFSET * slope); + vec4 sc = mul(GGC_POINT_SHADOW_MATRIX, vec4(biasedPos, 1.0)); + if (sc.w <= 0.0) + { + return 1.0; + } + vec3 ndc = sc.xyz / sc.w; + vec2 cuv = ndc.xy * 0.5 + 0.5; +#if !BGFX_SHADER_LANGUAGE_GLSL + cuv.y = 1.0 - cuv.y; +#endif + if (cuv.x < 0.0 || cuv.x > 1.0 || cuv.y < 0.0 || cuv.y > 1.0) + { + return 1.0; + } + float curDepth = clamp(ndc.z, 0.0, 1.0) - bias; + float invTexel = 1.0 / texel; + float lit = 0.0; + for (int dy = -1; dy <= 1; ++dy) + { + for (int dx = -1; dx <= 1; ++dx) + { + vec2 sampUV = cuv + vec2(float(dx), float(dy)) * (texel * 1.35); + vec2 texelCoord = sampUV * invTexel - 0.5; + vec2 fracPart = fract(texelCoord); + vec2 baseUV = (floor(texelCoord) + 0.5) * texel; + float s00 = (curDepth <= texture2D(s_pointShadowMap, baseUV).x) ? 1.0 : 0.0; + float s10 = (curDepth <= texture2D(s_pointShadowMap, baseUV + vec2(texel, 0.0)).x) ? 1.0 : 0.0; + float s01 = (curDepth <= texture2D(s_pointShadowMap, baseUV + vec2(0.0, texel)).x) ? 1.0 : 0.0; + float s11 = (curDepth <= texture2D(s_pointShadowMap, baseUV + vec2(texel, texel)).x) ? 1.0 : 0.0; + lit += mix(mix(s00, s10, fracPart.x), mix(s01, s11, fracPart.x), fracPart.y); + } + } + return lit * (1.0 / 9.0); +} + +// TheSuperHackers @feature bobtista 15/07/2026 Cheap 1D value noise for the drama electric +// pattern. Inputs stay small (the clock wraps at 30s) for sin-hash float precision. +float ggcHash11(float n) +{ + return fract(sin(n) * 43758.5453123); +} +float ggcValueNoise1(float x) +{ + float i = floor(x); + float f = fract(x); + f = f * f * (3.0 - 2.0 * f); + return mix(ggcHash11(i), ggcHash11(i + 1.0), f); +} + +// TheSuperHackers @feature bobtista 15/07/2026 GGC_PCANNON_ENHANCED radial scene dip (see +// u_dramaDim). Full brightness inside ~350 world units of the drama light, easing to the +// dim factor over the falloff width - a world-space vignette around the action. +float ggcDramaDimFactor(vec3 worldPos) +{ + + // w = -1: this view is excluded from drama lighting entirely (2D/UI draws). + if (u_dramaDim.w >= 0.999 || u_dramaDim.w < -0.5) + { + return 1.0; + } + // Distance to the nearest beam: with two cannons firing, each gets its own glow pool, so + // one beam ending never jumps the other's pool. u_pointShadow2LightPos is the second beam + // when u_pointShadow2Params.w > 0 (a persistent beam, not a transient flash pulse). + float d = length(worldPos.xy - u_dramaDim.xy); + if (u_pointShadow2Params.w > 0.0) + { + d = min(d, length(worldPos.xy - u_pointShadow2LightPos.xy)); + } + // Effect strength eases in/out with u_dramaDim.w (1.0 = inactive -> 0 strength). + float effect = 1.0 - u_dramaDim.w; + // Bright pool right around the beam: the immediate area glows more, so the beam reads as + // powerful against the dimmer surroundings. Concentrated within ~180 world units. + // abs: a negative falloff scale flags a dim-only view (sorted translucents). + float glowT = 1.0 - clamp(d * (1.0 / 210.0), 0.0, 1.0); + glowT = glowT * glowT; + float glow = effect * glowT * 2.4; + // Gentle dim that increases with distance so the rest of the screen is a bit darker. + float dimT = clamp((d - 150.0) * abs(u_dramaDim.z), 0.0, 1.0); + dimT = dimT * dimT * (3.0 - 2.0 * dimT); + float dimBase = 1.0 - effect * dimT; + return dimBase + glow; +} + +// TheSuperHackers @feature bobtista 23/06/2026 Dedicated nuke point light contribution. Dynamic +// lights never reach the per-object LightEnvironment, so the caster light is applied directly here - +// to objects and to terrain (which returns from its own branch) - and shadowed by the perspective +// point-shadow map. It both brightens lit surfaces the blast reaches and darkens occluded ones, so +// the cast shadow reads as real darkness rather than just an absence of bonus light. +// u_pointShadowParams.w controls how strongly occluded areas are darkened. +void applyNukePointLight(inout vec3 color, vec3 worldPos, vec3 nrm, vec3 albedo, vec3 viewDir, float specPower, float cutoutDamp) +{ + if (u_pointShadowParams.x < 0.0) + { + return; + } + vec3 toLight = u_pointShadowLightPos.xyz - worldPos; + float dist = length(toLight); + float range = max(u_pointShadowLightPos.w, 1.0); + float radial = clamp(1.0 - dist / range, 0.0, 1.0); + float atten = radial * radial; + vec3 lightDir = (dist > 0.0001) ? (toLight / dist) : vec3(0.0, 0.0, 1.0); + float ndotl = max(0.0, dot(nrm, lightDir)); + // Only sample the shadow map when this light actually casts one (state >= 1); a glow-only light + // (state 0, e.g. the particle cannon beam) lights and glints without shadowing. + float shadow = (u_pointShadowParams.x >= 0.5) ? samplePointShadow(worldPos, nrm) : 1.0; + // Brighten the lit surfaces the light reaches. Alpha-tested cutouts (foliage) are damped + // by the caller, and the contribution is soft-knee limited: sparse bright texels in leaf + // textures otherwise take the full addition and clip into isolated pale speckles. + vec3 pointAdd = u_pointShadowLightColor.rgb * albedo * ndotl * atten * shadow * cutoutDamp; + pointAdd /= (1.0 + 1.5 * max(pointAdd.r, max(pointAdd.g, pointAdd.b))); + // Headroom scaling: the addition fades to zero as the pixel approaches white, so bright + // sun-lit texels (foliage highlights) cannot clip into isolated speckles. + pointAdd *= clamp(1.45 - max(color.r, max(color.g, color.b)), 0.0, 1.0); + color += pointAdd; + // No specular glint: it is albedo-independent, so on foliage (whose blended leaf passes + // carry chaotic normals and no alpha-test flag) it printed as white pixel speckles. + // Darken occluded surfaces within reach so the cast shadow reads as real darkness. Safe + // against slot-swap blinking because the persistent tracking light always owns this slot + // (transient pulses ride slot 2, which stays adds-only). Terrain and decals never cast + // into this map, so the darkening cannot print the frustum footprint. + float shadowAtten = clamp(radial * 1.18, 0.0, 1.0); + shadowAtten = shadowAtten * (2.0 - shadowAtten); + color *= (1.0 - u_pointShadowParams.w * shadowAtten * (1.0 - shadow)); +} + +// TheSuperHackers @feature bobtista 14/07/2026 Second point-shadow slot: shadow lookup and light +// contribution for a transient second caster (particle-cannon lightning flash). Mirrors the +// primary slot with the u_pointShadow2* inputs so the flash throws its own brief shadow while +// the primary beam shadow stays put. +float samplePointShadow2(vec3 worldPos, vec3 nrm) +{ + vec3 n = normalize(nrm); + vec3 toLight = u_pointShadow2LightPos.xyz - worldPos; + float dist = length(toLight); + vec3 lightDir = (dist > 0.0001) ? (toLight / dist) : vec3(0.0, 0.0, 1.0); + float ndotl = clamp(dot(n, lightDir), 0.0, 1.0); + float slope = 1.0 + 2.0 * (1.0 - ndotl); + float texel = u_pointShadow2Params.z; + float bias = u_pointShadow2Params.y; + vec3 biasedPos = worldPos + lightDir * (SUN_SHADOW_NORMAL_OFFSET * slope); + vec4 sc = mul(GGC_POINT_SHADOW2_MATRIX, vec4(biasedPos, 1.0)); + if (sc.w <= 0.0) + { + return 1.0; + } + vec3 ndc = sc.xyz / sc.w; + vec2 cuv = ndc.xy * 0.5 + 0.5; +#if !BGFX_SHADER_LANGUAGE_GLSL + cuv.y = 1.0 - cuv.y; +#endif + if (cuv.x < 0.0 || cuv.x > 1.0 || cuv.y < 0.0 || cuv.y > 1.0) + { + return 1.0; + } + float curDepth = clamp(ndc.z, 0.0, 1.0) - bias; + float invTexel = 1.0 / texel; + float lit = 0.0; + for (int dy = -1; dy <= 1; ++dy) + { + for (int dx = -1; dx <= 1; ++dx) + { + vec2 sampUV = cuv + vec2(float(dx), float(dy)) * (texel * 1.35); + vec2 texelCoord = sampUV * invTexel - 0.5; + vec2 fracPart = fract(texelCoord); + vec2 baseUV = (floor(texelCoord) + 0.5) * texel; + float s00 = (curDepth <= texture2D(s_pointShadowMap2, baseUV).x) ? 1.0 : 0.0; + float s10 = (curDepth <= texture2D(s_pointShadowMap2, baseUV + vec2(texel, 0.0)).x) ? 1.0 : 0.0; + float s01 = (curDepth <= texture2D(s_pointShadowMap2, baseUV + vec2(0.0, texel)).x) ? 1.0 : 0.0; + float s11 = (curDepth <= texture2D(s_pointShadowMap2, baseUV + vec2(texel, texel)).x) ? 1.0 : 0.0; + lit += mix(mix(s00, s10, fracPart.x), mix(s01, s11, fracPart.x), fracPart.y); + } + } + // Fade the shadow out over the outer band of the map so the frustum boundary can never + // print as a hard square on the ground (transient flash lights sit close to the terrain, + // where the footprint edge lands well inside the lit radius). + float edge = min(min(cuv.x, 1.0 - cuv.x), min(cuv.y, 1.0 - cuv.y)); + float edgeFade = clamp(edge * 6.0, 0.0, 1.0); + return mix(1.0, lit * (1.0 / 9.0), edgeFade); +} + +void applyFlashPointLight(inout vec3 color, vec3 worldPos, vec3 nrm, vec3 albedo, vec3 viewDir, float specPower, float cutoutDamp) +{ + if (u_pointShadow2Params.x < 0.5) + { + return; + } + vec3 toLight = u_pointShadow2LightPos.xyz - worldPos; + float dist = length(toLight); + float range = max(u_pointShadow2LightPos.w, 1.0); + float radial = clamp(1.0 - dist / range, 0.0, 1.0); + float atten = radial * radial; + vec3 lightDir = (dist > 0.0001) ? (toLight / dist) : vec3(0.0, 0.0, 1.0); + float ndotl = max(0.0, dot(nrm, lightDir)); + float shadow = samplePointShadow2(worldPos, nrm); + // Cutout foliage is damped by the caller and the addition is soft-knee limited + // (white-speckle clipping, see applyNukePointLight). + vec3 flashAdd = u_pointShadow2LightColor.rgb * albedo * ndotl * atten * shadow * cutoutDamp; + flashAdd /= (1.0 + 0.9 * max(flashAdd.r, max(flashAdd.g, flashAdd.b))); + // Headroom scaling (see applyNukePointLight): no clipping speckles on bright texels. + flashAdd *= clamp(1.45 - max(color.r, max(color.g, color.b)), 0.0, 1.0); + color += flashAdd; + // No specular glint (foliage speckle, see applyNukePointLight). + // Occlusion darkening matching the primary slot, scaled by u_pointShadow2Params.w: the + // backend sends the light's strength for persistent beams (both cannons cast equal shadows + // from ignition) and 0 for transient flash pulses, which must only ever ADD light. + float shadowAtten = clamp(radial * 1.18, 0.0, 1.0); + shadowAtten = shadowAtten * (2.0 - shadowAtten); + color *= (1.0 - u_pointShadow2Params.w * shadowAtten * (1.0 - shadow)); +} + +// TheSuperHackers @feature bobtista 18/06/2026 Lightweight anisotropic base-texture sampling. +// bgfx's hardware anisotropy is all-or-nothing (16x or off): 16x over-sharpens grazing surfaces +// into a striped/aliased look, while plain trilinear leaves a dark fringe where high-contrast +// texture detail (building panel lines, foundation-slab edges) aliases at the RTS camera's near- +// horizontal viewing angle. This samples up to 4 taps spread along the texture footprint's major +// axis, each fetched with the minor-axis gradient so it lands on the sharper mip, yielding a +// capped ~4x anisotropic result that clears the fringe without the 16x striping. The tap count +// scales with the footprint's anisotropy ratio, so a roughly square footprint costs a single tap. +vec4 sampleAniso(sampler2D smp, vec2 uv) +{ + vec2 dx = dFdx(uv); + vec2 dy = dFdy(uv); + float lenSqX = dot(dx, dx); + float lenSqY = dot(dy, dy); + vec2 majorAxis = (lenSqX > lenSqY) ? dx : dy; + vec2 minorAxis = (lenSqX > lenSqY) ? dy : dx; + float ratio = sqrt(max(lenSqX, lenSqY) / max(min(lenSqX, lenSqY), 1e-12)); + float taps = clamp(ceil(ratio), 1.0, 4.0); + vec4 sum = vec4_splat(0.0); + for (int i = 0; i < 4; ++i) + { + if (float(i) >= taps) + { + break; + } + float t = (float(i) + 0.5) / taps - 0.5; + sum += texture2DGrad(smp, uv + majorAxis * t, minorAxis, minorAxis); + } + return sum / taps; +} + +void main() +{ + vec4 diffuse = v_color0; + vec2 stage0UV = v_stage0UV; + vec2 stage1UV = v_stage1UV; + if (u_texProjected.x > 0.5 && abs(v_sceneDepth.x) > 1e-6) + { + stage0UV /= v_sceneDepth.x; + } + if (u_texProjected.y > 0.5 && abs(v_sceneDepth.y) > 1e-6) + { + stage1UV /= v_sceneDepth.y; + } + vec2 baseStage0UV = v_texcoord0; + // --- 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 = GGC_SAMPLE_STAGE0( (u_texProjected.x > 0.5) ? stage0UV : baseStage0UV); + vec4 blendTex = texture2D(s_tex1, (u_texProjected.y > 0.5) ? stage1UV : v_texcoord1); + float blendAlpha = diffuse.a; + vec3 blended = mix(baseTex.rgb, blendTex.rgb, blendAlpha); + vec4 result = vec4(blended * diffuse.rgb, 1.0); + + if (!alphaTestPass(result.a, u_atestParams.x, u_atestParams.y)) + { + discard; + } + + // TheSuperHackers @bugfix bobtista 28/04/2026 Apply the cloudmap + // inside the terrain pixel-shader path. Terrain returns from this + // branch before the generic material path below, so placing cloud + // modulation only after the branch made the effect invisible. + if (u_cloudParams.w > 0.5) + { + result.rgb *= sampleCloudShadow(v_cloudUV); + } + if (u_lightMapParams.w > 0.5) + { + result.rgb *= texture2D(s_lightMap, v_worldPos.xy * u_lightMapParams.x).rgb; + } + + // TheSuperHackers @bugfix bobtista 16/06/2026 Terrain returns from this branch + // before the generic shadow apply below, so the sun shadow has to be applied here + // too - otherwise cast shadows land on roads/decals/objects but skip the ground. + float terrainSunFactor = sunShadowFactor(v_worldPos, vec3(0.0, 0.0, 1.0)); + result.rgb *= terrainSunFactor; + + // TheSuperHackers @feature bobtista 14/07/2026 GGC_PCANNON_ENHANCED radial scene dip for + // terrain, applied before the beam light is added so the beam's own glow stays dominant + // on the darkened battlefield. + result.rgb *= ggcDramaDimFactor(v_worldPos); + + // TheSuperHackers @feature bobtista 23/06/2026 Terrain also receives the dedicated nuke point + // light and its cast shadow (terrain returns here, never reaching the object lighting path), + // so the blast lights the ground and structures throw shadows across it. + // TheSuperHackers @bugfix bobtista 15/07/2026 The point-light adds are gated by the + // NORMALIZED sun-lit fraction: 0 inside a cast shadow, 1 in the open. A raw shadow-factor + // damp still let ~30% of the flash through, visibly refilling dark shadow interiors on + // every flash ("shadows light up") - shadowed pixels now receive nothing. + float terrainSunLit = (u_shadowParams.z > 0.01) + ? clamp((terrainSunFactor - (1.0 - u_shadowParams.z)) / u_shadowParams.z, 0.0, 1.0) + : 1.0; + applyNukePointLight(result.rgb, v_worldPos, vec3(0.0, 0.0, 1.0), blended, vec3(0.0, 0.0, 1.0), 0.0, terrainSunLit); + applyFlashPointLight(result.rgb, v_worldPos, vec3(0.0, 0.0, 1.0), blended, vec3(0.0, 0.0, 1.0), 0.0, terrainSunLit); + + + gl_FragColor = result; + return; + } + + vec4 tex0 = GGC_SAMPLE_STAGE0_ANISO(stage0UV); + vec4 tex1 = texture2D(s_tex1, stage1UV); + vec4 tex2 = texture2D(s_tex2, v_stage2UV); + vec2 stage3UV = v_texcoord0; + if (u_texcoordSource.w > 2.5) + { + stage3UV = (v_worldPos.xy + u_shroudParams.xy) * u_shroudParams.zw; + } + else if (u_texcoordSource.w > 0.5 && u_texcoordSource.w < 1.5) + { + stage3UV = v_texcoord1; + } + vec4 tex3 = texture2D(s_tex3, stage3UV); + + if (u_texcoordSelect2.z > 3.5) + { + // Zero Hour command-center driveway emblems are player-recolored + // sorted decals. The generic material path can inherit zero opacity + // from nearby water/shroud work, while the remapped texture carries + // its matte as black RGB rather than a reliable alpha channel. + float mask = max(max(tex0.r, tex0.g), tex0.b); + float alpha = clamp(mask * 4.0 * diffuse.a, 0.0, 1.0); + if (alpha <= ALPHA_MASK_EPSILON) + { + discard; + } + gl_FragColor = vec4(tex0.rgb, alpha); + return; + } + + if (u_texcoordSelect2.z > 2.5) + { + // Sneak Attack ground dirt is authored as a sorted translucent W3D + // quad. Use the texture alpha directly; inherited material opacity can + // be zero on this replay path and erase the broad dirt stain. + float alpha = clamp(tex0.a * diffuse.a, 0.0, 1.0); + if (alpha <= ALPHA_MASK_EPSILON) + { + discard; + } + gl_FragColor = vec4(tex0.rgb * diffuse.rgb, alpha); + return; + } + + if (u_texcoordSelect2.z > 1.5) + { + // Chinook rotor blur is authored as a sorted translucent mask. Keep + // it out of the generic material path so stale vehicle material alpha + // cannot erase the cards after the sorted pool is replayed. + float mask = max(tex0.a, dot(tex0.rgb, LUMA_WEIGHTS)); + float alpha = clamp(mask * diffuse.a, 0.0, 1.0); + if (alpha <= ALPHA_MASK_EPSILON) + { + discard; + } + vec3 color = tex0.rgb * diffuse.rgb; + if (max(max(color.r, color.g), color.b) <= ADDITIVE_MATTE_EPSILON) + { + color = vec3_splat(mask) * diffuse.rgb; + } + gl_FragColor = vec4(color, alpha); + return; + } + + if (u_texcoordSelect.z > 0.5) + { + // Projected shroud overlays are destination multipliers. The terrain + // vertex buffer they reuse carries baked terrain diffuse, but the DX8 + // shroud pass contributes only the shroud texture multiplier here. + vec4 shroud = tex0; + if (u_objectShroudDim.y > 0.5) + { + float maskAlpha = texture2D(s_tex1, v_texcoord0).a; + if (maskAlpha < u_objectShroudDim.z) + { + discard; + } + shroud.a *= maskAlpha; + } + // TheSuperHackers @bugfix bobtista 01/07/2026 The shroud texture RGB already + // encodes the per-cell level (level * shroudColor: fogged ~0.5, clear 1.0, + // shrouded 0), so it is the sole darkening term here just like the terrain + // shroud pass and retail DX8. An extra fogAlpha/clearAlpha dim factor would + // darken fogged structures a second time (0.5 * 0.5), rendering them near-black. + gl_FragColor = shroud; + return; + } + + if (u_legacyPixelShaderMode.x > 0.5) + { + vec4 water = vec4(tex0.rgb * diffuse.rgb, tex0.a * diffuse.a); + if (u_legacyPixelShaderMode.x > 2.5 && u_legacyPixelShaderMode.x < 3.5) + { + // The trapezoid-water vertex format carries the authored + // world-space noise UV in TEXCOORD1. The legacy D3D path also + // derives this sample through TCI_CAMERASPACEPOSITION, but using + // the explicit UV keeps bgfx out of a fragile generated-coordinate + // path and matches the same world-space mapping. + vec4 waterNoise = texture2D(s_tex2, v_texcoord1); + // Trapezoid / standing water ps.1.1: + // r0 = diffuse * t0 + // r0.rgb += t1.rgb * t2.rgb + // r0.rgb *= t3.rgb + water.rgb += tex1.rgb * waterNoise.rgb; + water.rgb *= tex3.rgb; + } + else if (u_legacyPixelShaderMode.x > 0.5 && u_legacyPixelShaderMode.x < 1.5) + { + // River water ps.1.1 uses the same sparkle/noise contribution + // and adds the stage-3 shroud contribution before the final add. + water.rgb += tex3.rgb; + water.a *= tex3.a; + water.rgb += tex1.rgb * tex2.rgb; + } + gl_FragColor = water; + return; + } + + vec2 projectedDecalUV = v_texcoord0; + vec4 projectedDecalTex = tex0; + if (u_projectedDecalMode.x > 0.5) + { + projectedDecalTex = GGC_SAMPLE_STAGE0( projectedDecalUV); + // TheSuperHackers @bugfix bobtista 31/05/2026 Default infantry blob shadows + // (mode 1) project onto a coarse heightmap receiver quad much larger than the + // [0,1] decal image, so most blob fragments have out-of-range UVs. Discarding + // them (needed for the other projected-decal modes) erased almost the whole + // blob, leaving infantry shadows nearly invisible. ShadowI is clamp-sampled + // with a transparent (alpha 0) border, so out-of-range fragments already add + // no darkening; keep them for the blob branch instead of discarding. + bool isBlobMode = u_projectedDecalMode.x > (PROJECTED_DECAL_BLOB_SHADOW - 0.5) + && u_projectedDecalMode.x < (PROJECTED_DECAL_BLOB_SHADOW + 0.5); + if (!isBlobMode + && (projectedDecalUV.x < 0.0 || projectedDecalUV.x > 1.0 || projectedDecalUV.y < 0.0 || projectedDecalUV.y > 1.0)) + { + // W3D projects decals onto terrain cell meshes that can extend beyond + // the decal image. DX8 treats that area as non-contributing; clamping the + // bgfx sample instead can repeat dark texture padding into blocky patches. + discard; + } + } + + if (u_projectedDecalMode.x > (PROJECTED_DECAL_ADDITIVE - 0.5) + && u_projectedDecalMode.x < (PROJECTED_DECAL_ADDITIVE + 0.5)) + { + // W3D projected additive decals (Spy Satellite / Spy Drone reveal + // grid, radius decals) use the fixed-function additive preset: + // stage0 texture modulated by vertex diffuse, then ONE/ONE blending. + // They are not lit meshes and must not inherit stale material, + // secondary-stage or blob-shadow state from the shared decal + // batch path. + vec4 projected = vec4(projectedDecalTex.rgb * diffuse.rgb, 0.0); + if (max(max(projected.r, projected.g), projected.b) <= ADDITIVE_MATTE_EPSILON) + { + discard; + } + gl_FragColor = projected; + return; + } + + if (u_projectedDecalMode.x > (PROJECTED_DECAL_ALPHA - 0.5) + && u_projectedDecalMode.x < (PROJECTED_DECAL_ALPHA + 0.5)) + { + // W3D projected alpha decals (selection/guard/reveal overlays) use + // the fixed-function stage-0 texture modulated by vertex diffuse and + // the draw's SRC_ALPHA/INV_SRC_ALPHA blend. Keep them out of the + // generic TSS path so stale secondary-stage/shadow state cannot turn + // transparent matte pixels into dark geometry. + float alpha = clamp(projectedDecalTex.a * diffuse.a, 0.0, 1.0); + if (alpha <= ALPHA_MASK_EPSILON) + { + discard; + } + gl_FragColor = vec4(projectedDecalTex.rgb * diffuse.rgb, alpha); + return; + } + + if (u_projectedDecalMode.x > (PROJECTED_DECAL_BLOB_SHADOW - 0.5) + && u_projectedDecalMode.x < (PROJECTED_DECAL_BLOB_SHADOW + 0.5)) + { + // Default infantry blobs are authored as a soft ALPHA mask (ShadowI RGB is + // white). Darken the terrain directly by that alpha under the ZERO/SRC_COLOR + // multiplicative blend; emitting (1 - mask) darkens by mask at the blob + // center and falls off softly to the transparent edge. No smoothstep: the + // alpha over the coarse heightmap receiver is mostly below 0.5, so a + // smoothstep gate crushed it to nothing and made the blobs invisible. + float mask = clamp(projectedDecalTex.a * diffuse.a, 0.0, 1.0) * BLOB_MASK_MAX_DARKNESS; + gl_FragColor = vec4(vec3_splat(1.0 - mask), 1.0); + return; + } + + if (u_projectedDecalMode.x > (PROJECTED_DECAL_MULTIPLY - 0.5) + && u_projectedDecalMode.x < (PROJECTED_DECAL_MULTIPLY + 0.5)) + { + // Non-blob projected shadows use W3D's preset multiplicative + // fixed-function shader: COLOROP=MODULATE and blend ZERO/SRC_COLOR. + // Their textures can still carry black RGB in transparent padding. + // DX8's texture/decal setup lets that padding contribute as neutral + // destination color; in bgfx it must be made explicit or large shadow + // receiver meshes stamp blocky black patches around the actual mask. + float mask = clamp(projectedDecalTex.a * diffuse.a, 0.0, 1.0); + vec3 multiplier = projectedDecalTex.rgb * diffuse.rgb; + gl_FragColor = vec4(mix(vec3_splat(1.0), multiplier, mask), 1.0); + return; + } + + // --- 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; + + vec4 current; + + // 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 > 2.5 && priColorOp < 3.5 + && priAlphaOp > 2.5 && priAlphaOp < 3.5 + && secColorOp < 0.5 && secAlphaOp < 0.5 + && priArg1Src < 0.5) + { + // 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 if (priColorOp > 0.5 && priColorOp < 1.5 + && priAlphaOp > 0.5 && priAlphaOp < 1.5 + && secColorOp < 0.5 && secAlphaOp < 0.5 + && priArg1Src < 0.5) + { + // 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 + && priAlphaOp > 1.5 && priAlphaOp < 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 priAlphaArg1Src = u_tssOps1.y; + + 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) + { + priColor = diffuse.rgb; + priAlpha = diffuse.a; + } + else + { + priColor = applyColorOp(priColorOp, priArg1.rgb, priArg2.rgb); + priAlpha = applyAlphaOp(priAlphaOp, priAlphaA1.a, priAlphaA2.a); + } + + current = vec4(priColor, priAlpha); + + // Secondary/detail stage + if (secColorOp > 0.5) + { + 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 if (secColorOp > 11.5 && secColorOp < 12.5) + { + // D3DTOP_MODULATEALPHA_ADDCOLOR with retail arg order (current, tex): + // current.rgb + current.a * tex.rgb + current.rgb = clamp(secArg2 + current.a * secArg1, vec3_splat(0.0), vec3_splat(1.0)); + } + 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: legacy multi-stage multiply path. In the bgfx + // backend the terrain pixel-shader branch handles cloud+noise by + // itself (u_texcoordSelect.y > 0.5 at the top of main). Non-terrain + // meshes (vehicles, buildings, infantry) only bind stage 0/1 and + // inherit stale cloud/noise handles from the previous terrain draw + // — multiplying by them turned tank turrets and GLA quad-cannon + // tops pure black. + // TheSuperHackers @bugfix bobtista 23/04/2026 gate the + // stage 2/3 multiply on the terrain texcoord select so only the + // legacy DX8 multipass path (which never takes this branch in + // standalone — terrain uses the pixel-shader branch above) can + // opt back in. + if (secColorOp > 0.5 && u_texcoordSelect.y > 0.5) + { + current *= tex2 * tex3; + } + + // --- Lighting --- + // 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. Recompute current as tex-only * lighting. + vec3 matDiffuse = (u_vertexColorFlags.y > 0.5) ? diffuse.rgb : u_matDiffuse.rgb; + vec3 matAmbient = (u_vertexColorFlags.z > 0.5) ? diffuse.rgb : u_matAmbient.rgb; + // TheSuperHackers @feature bobtista 15/06/2026 Optional emissive boost (u_matFx.w, + // neutral 1.0) brightens self-illuminated material so it reads and feeds bloom. + vec3 matEmissive = (u_vertexColorFlags.w > 0.5) ? diffuse.rgb : (u_matEmissive.rgb * u_matFx.w); + + // TheSuperHackers @bugfix bobtista 05/06/2026 Guard normalize against a zero + // normal (degenerate geometry) which would yield NaN and bleed into the color. + float nrmLen = length(v_normal); + vec3 nrm = (nrmLen > 1e-5) ? (v_normal / nrmLen) : vec3(0.0, 0.0, 1.0); + // TheSuperHackers @feature bobtista 15/06/2026 View vector for Blinn-Phong + // specular and rim lighting, both driven entirely from existing W3D material + // data and geometry normals (no new art). + vec3 viewDir = normalize(u_eyePos.xyz - v_worldPos); + vec3 specAccum = vec3(0.0, 0.0, 0.0); + // The old W3D files often carry broad, nearly-white specular values with + // shininess near zero. For the optional bgfx material FX path, treat those + // as hints and remap them to a narrower modern highlight instead of using + // them literally. + float authoredShininess = max(u_matSpecular.w, 0.0); + float specPower = (authoredShininess < 2.0) + ? (10.0 + authoredShininess * 22.0) + : min(max(authoredShininess, 10.0), 96.0); + vec3 specFxColor = min(u_matSpecular.rgb, vec3_splat(0.85)); + // D3D fixed-function folds emissive into the material color before + // texture-stage modulation. Adding it after sampling bleaches tinted + // self-lit textures like the shellmap police roof lights to white. + vec3 litColor = GGC_SCENE_AMBIENT.rgb * matAmbient + matEmissive; + for (int li = 0; li < 4; ++li) + { + if (u_lightParams[li].w > 0.5 || u_lightDirs[li].w > 0.5) + { + // TheSuperHackers @bugfix bobtista 05/06/2026 Guard against a zero light + // direction (unset but enabled slot) to avoid NaN from normalize. + float ldirLen = length(u_lightDirs[li].xyz); + vec3 ldir = (ldirLen > 1e-5) ? (u_lightDirs[li].xyz / ldirLen) : vec3(0.0, 0.0, 1.0); + float atten = 1.0; + if (u_lightParams[li].z > 0.5) + { + vec3 toLight = u_lightPositions[li].xyz - v_worldPos; + float dist = length(toLight); + ldir = (dist > 0.0001) ? (toLight / dist) : vec3(0.0, 0.0, 1.0); + float inner = max(u_lightParams[li].x, 0.0001); + float outer = max(u_lightParams[li].y, inner); + // TheSuperHackers @bugfix bobtista 16/07/2026 Match the retail D3D point-light + // falloff (dx8wrapper Set_Light_Environment): inverse linear+quadratic + // 1/(1 + 0.1/irad*d + 8/orad^2*d^2) with the linear term dropped for a + // degenerate inner/outer range, and the hard D3D Range cutoff at outer. + // The previous linear ramp rendered point lights brighter near the source + // with a harder edge than the DX8 build. + float a1 = (outer - inner < 0.001) ? 0.0 : (0.1 / inner); + float a2 = 8.0 / (outer * outer); + atten = (dist <= outer) ? (1.0 / (1.0 + a1 * dist + a2 * dist * dist)) : 0.0; + } + float nDotL = max(0.0, dot(nrm, ldir)); + litColor += (u_lightAmbients[li].rgb * matAmbient + u_lightColors[li].rgb * nDotL * matDiffuse) * atten; + if (nDotL > 0.0) + { + vec3 halfV = normalize(ldir + viewDir); + float nDotH = max(0.0, dot(nrm, halfV)); + specAccum += u_lightColors[li].rgb * pow(nDotH, specPower) * atten; + } + } + } + vec4 litDiffuse = vec4(min(vec3_splat(1.0), litColor), u_matDiffuse.a); + float litAlpha = current.a * u_matDiffuse.a; + if (priColorOp > 0.5 && priColorOp < 1.5) + { + current = tex0; + } + else if (priColorOp > 1.5 && priColorOp < 2.5) + { + current = litDiffuse; + } + else if (priColorOp > 2.5 && priColorOp < 3.5) + { + // Keep the fixed-function alpha combiner result. Some lit decals + // use stage 0 for recolored RGB and stage 1 only as an alpha mask; + // recomputing alpha from tex0 draws their black padding. + current = vec4(tex0.rgb * litDiffuse.rgb, litAlpha); + } + else if (priColorOp > 3.5 && priColorOp < 4.5) + { + current = vec4(min(tex0.rgb * litDiffuse.rgb * 2.0, vec3_splat(1.0)), + litAlpha); + } + else if (priColorOp > 4.5 && priColorOp < 5.5) + { + current = vec4(min(tex0.rgb + litDiffuse.rgb, vec3_splat(1.0)), + litAlpha); + } + else + { + float priAlphaArg1Src = u_tssOps1.y; + vec4 priArg1 = (priArg1Src < 0.5) ? tex0 : litDiffuse; + vec4 priArg2 = (priArg1Src < 0.5) ? litDiffuse : tex0; + vec4 priAlphaA1 = (priAlphaArg1Src < 0.5) ? tex0 : litDiffuse; + vec4 priAlphaA2 = (priAlphaArg1Src < 0.5) ? litDiffuse : tex0; + current = vec4(applyColorOp(priColorOp, priArg1.rgb, priArg2.rgb), + applyAlphaOp(priAlphaOp, priAlphaA1.a, priAlphaA2.a)); + } + + if (secColorOp > 0.5) + { + if (secColorOp > 7.5 && secColorOp < 8.5) + { + current.rgb = mix(current.rgb, tex1.rgb, tex1.a); + } + else if (secColorOp > 8.5 && secColorOp < 9.5) + { + current.rgb = mix(current.rgb, tex1.rgb, current.a); + } + else if (secColorOp > 11.5 && secColorOp < 12.5) + { + // D3DTOP_MODULATEALPHA_ADDCOLOR with retail arg order (current, tex): + // current.rgb + current.a * tex.rgb + current.rgb = clamp(current.rgb + current.a * tex1.rgb, vec3_splat(0.0), vec3_splat(1.0)); + } + else + { + current.rgb = applyColorOp(secColorOp, tex1.rgb, current.rgb); + } + if (u_texcoordSelect.y > 0.5) + { + current *= tex2 * tex3; + } + } + if (secAlphaOp > 0.5) + { + current.a = applyAlphaOp(secAlphaOp, tex1.a, current.a); + } + + // TheSuperHackers @feature bobtista 15/06/2026 Add Blinn-Phong specular and a + // fresnel rim term on top of the lit/textured result. Both are gated by u_matFx + // strengths (0 = off) so the retail look is unchanged unless explicitly enabled. + // Fade the additive FX on transparent and alpha-tested surfaces: billboard + // foliage, infantry cards, and blended effect meshes have flat or grazing + // normals that otherwise wash out to white. Solid meshes keep the full effect. + float fxMask = (u_atestParams.y > 0.5) ? 0.0 : current.a; + // Headroom guard: keep additive FX bounded as the surface approaches white, so + // bright sun-facing roofs and glossy domes still get a visible highlight without + // being pushed into a flat white blob. + float fxHeadroom = max(0.0, 1.0 - max(max(current.r, current.g), current.b)); + fxMask *= (0.45 + 0.55 * fxHeadroom); + float rim = pow(1.0 - max(0.0, dot(nrm, viewDir)), u_matFx.z) * u_matFx.y; + vec3 fxAdd = (specAccum * specFxColor * u_matFx.x + rim * litDiffuse.rgb) * fxMask; + // Exposure-style soft add: brightens toward white but can never overshoot it, so + // strong settings roll grazing-angle surfaces (tunnel walls, vehicle bodies) into + // a smooth highlight instead of hard-clamping them to a flat white blob. + vec3 fxLit = 1.0 - (1.0 - current.rgb) * exp(-fxAdd); + float maxFxLift = 0.14 + 0.22 * fxHeadroom; + current.rgb = min(fxLit, current.rgb + vec3_splat(maxFxLift)); + } + else + { + // Pre-lit or unlit: vertex color contains baked lighting. + current *= u_matDiffuse; + } + + // TheSuperHackers @feature bobtista 23/06/2026 Apply the dedicated dynamic light after the + // object texture/lighting path has converged. Many Generals buildings and vehicles use pre-lit + // or unlit combiner states, so applying this only inside the lit-material branch made the + // particle-cannon/nuke light visible on terrain and some organic meshes while skipping those + // larger opaque receivers. + // Projected decals (blob shadows, scorches) must not receive the drama point lights or the + // radial dim: adding light to a shadow decal's own texels visibly brightens the shadow + // graphic whenever a flash fires. u_dramaDim.w = -1 excludes the whole view (2D/UI draws, + // whose screen-space v_worldPos would otherwise be treated as world coordinates). + if (u_dramaDim.w > -0.5 + && u_softParticleParams.x < 0.5 + && u_texcoordSelect2.w < 0.5 + && u_projectedDecalMode.x < 0.5) + { + // Radial scene dip applies (and fades out) independently of the point lights, so the + // scene eases back to normal when the drama light dies instead of snapping bright. + current.rgb *= ggcDramaDimFactor(v_worldPos); + if ((u_pointShadowParams.x >= 0.0 || u_pointShadow2Params.x >= 0.5) + && u_dramaDim.z >= 0.0) + { + float pointNrmLen = length(v_normal); + vec3 pointNrm = (pointNrmLen > 1e-5) ? (v_normal / pointNrmLen) : vec3(0.0, 0.0, 1.0); + vec3 pointViewDir = normalize(u_eyePos.xyz - v_worldPos); + float authoredShininess = max(u_matSpecular.w, 0.0); + float pointSpecPower = (authoredShininess >= 2.0) ? min(max(authoredShininess, 10.0), 96.0) : 0.0; + // Alpha-tested cutouts (foliage) get a damped contribution and no glint. + float pointCutoutDamp = (u_atestParams.y > 0.5) ? 0.3 : 1.0; + // Cutouts cast flash shadows but do not RECEIVE the flash: their self-shadowing at + // map-texel granularity speckles canopies/fences with bright blotches otherwise. + float flashDamp = (u_atestParams.y > 0.5) ? 0.0 : 1.0; + // Sun-shadowed object pixels receive nothing from the drama lights (see the terrain + // branch): shadows must stay dark through a flash. + if (u_sunShadowReceive.x > 0.5 && u_shadowParams.z > 0.01) + { + float objSunFactor = sunShadowFactor(v_worldPos, v_normal); + float sunGate = clamp((objSunFactor - (1.0 - u_shadowParams.z)) / u_shadowParams.z, 0.0, 1.0); + pointCutoutDamp *= sunGate; + flashDamp *= sunGate; + } + // Electric texture on solid receivers (unit hulls, building walls): an animated + // jagged pattern rides the drama lights when the backend publishes a clock in + // u_pointShadowLightColor.w (2.0 + seconds; 1.0 = plain light, pattern off). + float dramaClock = u_pointShadowLightColor.w; + if (dramaClock > 1.5 && u_atestParams.y < 0.5) + { + // Blue lightning veins on solid surfaces near the beam: two crossing ridged + // noise fields intersect into thin filaments, sharpened and gated by a bursty + // time-noise so arcs snap on and off like an electrical storm - additive and + // blue, never a brightness wave over the whole surface. + float tt = dramaClock - 2.0; + float n1 = ggcValueNoise1(v_worldPos.z * 0.25 + (v_worldPos.x + v_worldPos.y) * 0.08 + tt * 7.0); + float n2 = ggcValueNoise1(v_worldPos.z * 0.42 - tt * 11.0 + (v_worldPos.x - v_worldPos.y) * 0.11 + 37.0); + float v1 = 1.0 - abs(n1 * 2.0 - 1.0); + float v2 = 1.0 - abs(n2 * 2.0 - 1.0); + float vein = v1 * v2; + vein = vein * vein * vein; + float burst = ggcValueNoise1(tt * 5.0 + floor((v_worldPos.x + v_worldPos.y) * 0.02) * 3.7); + float arc = vein * step(0.42, burst); + // Proximity to the NEAREST beam so both cannons get arcs, not just slot 1's beam + // (u_pointShadow2LightPos is the second beam when u_pointShadow2Params.w > 0). + float beamProx = clamp(1.0 - length(u_pointShadowLightPos.xyz - v_worldPos) / max(u_pointShadowLightPos.w, 1.0), 0.0, 1.0); + if (u_pointShadow2Params.w > 0.0) + { + float prox2 = clamp(1.0 - length(u_pointShadow2LightPos.xyz - v_worldPos) / max(u_pointShadow2LightPos.w, 1.0), 0.0, 1.0); + beamProx = max(beamProx, prox2); + } + // Headroom-limited: bright thin geometry (chainlink fences, radar dishes, white + // panels) otherwise clips to solid white under the arcs. + vec3 arcAdd = vec3(0.45, 0.8, 1.9) * arc * beamProx; + arcAdd *= clamp(0.82 - max(current.r, max(current.g, current.b)), 0.0, 1.0); + current.rgb += arcAdd; + } + applyNukePointLight(current.rgb, v_worldPos, pointNrm, current.rgb, pointViewDir, pointSpecPower, pointCutoutDamp); + applyFlashPointLight(current.rgb, v_worldPos, pointNrm, current.rgb, pointViewDir, pointSpecPower, flashDamp); + current.rgb = min(current.rgb, vec3_splat(1.0)); + } + } + + // Additive black texels are mathematical no-ops in the D3D8 fixed-function + // path. Discard them before output so large additive effect meshes with + // black matte pixels cannot contribute visible fragments on bgfx backends. + if (u_texcoordSelect2.w > 0.5 + && max(max(current.r, current.g), current.b) <= ADDITIVE_MATTE_EPSILON) + { + discard; + } + + // --- Alpha test --- + if (!alphaTestPass(current.a, u_atestParams.x, u_atestParams.y)) + { + discard; + } + + // Soft particles. Compare sorted alpha-blended fragments against the + // readable opaque scene-depth target and fade only where particles + // intersect world geometry. + if (u_softParticleParams.x > 0.5) + { + vec2 sceneUV = gl_FragCoord.xy * u_softParticleParams.zw; + float sceneDepth = texture2D(s_sceneDepth, sceneUV).x; + float particleDepth = gl_FragCoord.z; + float softFade = clamp((sceneDepth - particleDepth) * u_softParticleParams.y, 0.0, 1.0); + current.a *= softFade; + if (current.a <= 0.003) + { + discard; + } + } + + // TheSuperHackers @bugfix bobtista 14/06/2026 Cloud-shadow modulation, + // gated on u_cloudParams.w. The terrain pixel-shader branch above samples + // cloud itself and returns early, so this only runs for generic-path + // ground draws inside the terrain pass (roads and other map decals) where + // the cloud state is enabled. Units, buildings and effects render after + // the terrain pass with w == 0, so they are unaffected. Matches the DX8 + // ST_ROAD_BASE_NOISE multipass that modulated the scrolling cloud texture + // into the road colour, which the single-pass bgfx road path had dropped. + // TheSuperHackers @bugfix bobtista 17/07/2026 The noise lightmap is gated independently + // of the cloud state, matching the terrain branch and DX8's road shader where NOISE1 + // (cloud) and NOISE2 (lightmap) are separate stages. Nesting it under the cloud gate + // left roads without the noise layer on lightmapped maps with clouds disabled. + if (u_lightMapParams.w > 0.5) + { + current.rgb *= texture2D(s_lightMap, v_worldPos.xy * u_lightMapParams.x).rgb; + } + if (u_cloudParams.w > 0.5) + { + current.rgb *= sampleCloudShadow(v_cloudUV); + // TheSuperHackers @bugfix bobtista 17/06/2026 Ground decals drawn in the terrain + // pass (roads, bridges, map markings) receive the sun shadow too, so a cast shadow + // stays continuous as it crosses a road instead of vanishing on the road surface. + // This shares the cloud gate (w > 0.5) that already distinguishes ground draws from + // units/buildings/effects (which render after the terrain pass with w == 0 and only + // cast), so it cannot re-introduce the object self-shadow blob. + current.rgb *= sunShadowFactor(v_worldPos, vec3(0.0, 0.0, 1.0)); + } + else if (u_sunShadowReceive.x > 0.5) + { + // TheSuperHackers @feature bobtista 17/06/2026 Opaque/alpha-tested objects (units, + // structures, props) receive the sun cast shadow so they darken when standing inside a + // mountain/building shadow instead of staying bright. The engine sets u_sunShadowReceive + // only for the caster set (writes depth, not blended), so blended effects/particles are + // excluded. sampleSunShadow uses the object's real vertex normal for the normal-offset + // bias, which keeps the object from self-shadowing. Objects do NOT fall through to coarser + // cascades (allowFallthrough=false): the binary fall-through decision snaps a wall between + // light and dark across cascades as the camera zooms; walls use their containing cascade only. + current.rgb *= sunShadowFactor(v_worldPos, v_normal); + } + + // 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, LUMA_WEIGHTS); + current.rgb = vec3(luma, luma, luma); + } + + // TheSuperHackers @bugfix bobtista 16/06/2026 Objects (units, structures) CAST into the + // sun shadow map but do NOT receive it on themselves. A small solid caster like an + // infantryman occludes its own near side in the shadow map, so receiving the map back + // onto its body paints a dark self-shadow blob across the model (the "blob shadow" that + // is not a real ground shadow). Only the terrain receives the sun shadow (handled in the + // terrain pixel-shader branch above), so cast silhouettes still land on the ground. + + 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 new file mode 100644 index 00000000000..bf85958b67d --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/varying.def.sc @@ -0,0 +1,22 @@ +vec4 v_color0 : COLOR0 = vec4(1.0, 1.0, 1.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); +vec2 v_cloudUV : TEXCOORD3 = vec2(0.0, 0.0); +vec2 v_stage0UV : TEXCOORD4 = vec2(0.0, 0.0); +vec2 v_stage1UV : TEXCOORD5 = vec2(0.0, 0.0); +vec4 v_sceneDepth: TEXCOORD6 = vec4(1.0, 0.0, 0.0, 0.0); +vec3 v_worldPos : TEXCOORD7 = vec3(0.0, 0.0, 0.0); +vec2 v_stage2UV : TEXCOORD8 = vec2(0.0, 0.0); +vec2 v_stage3UV : TEXCOORD9 = vec2(0.0, 0.0); + +vec3 a_position : POSITION; +vec3 a_normal : NORMAL; +vec4 a_color0 : COLOR0; +vec2 a_texcoord0 : TEXCOORD0; +vec2 a_texcoord1 : TEXCOORD1; + +vec4 i_data0 : TEXCOORD7; +vec4 i_data1 : TEXCOORD6; +vec4 i_data2 : TEXCOORD5; +vec4 i_data3 : TEXCOORD4; 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..5090d3571b4 --- /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.bgra; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_scene_composite.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_scene_composite.sc new file mode 100644 index 00000000000..39c9c2d9442 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_scene_composite.sc @@ -0,0 +1,14 @@ +$input a_position +$output v_texcoord0 + +// TheSuperHackers @feature bobtista 27/04/2026 Fullscreen scene +// composite vertex shader. The scene color target is sampled back to +// the swapchain before UI draws, creating the post-processing hook. + +#include + +void main() +{ + gl_Position = vec4(a_position, 1.0); + v_texcoord0 = vec2(a_position.x * 0.5 + 0.5, 0.5 - a_position.y * 0.5); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_scene_depth.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_scene_depth.sc new file mode 100644 index 00000000000..c8479aeeec6 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_scene_depth.sc @@ -0,0 +1,18 @@ +$input a_position +$output v_sceneDepth + +// TheSuperHackers @feature bobtista 27/04/2026 Scene readable-depth +// vertex shader. Uses the current bgfx model-view-projection transform +// so duplicated opaque world draws populate a sampleable depth texture. + +#include + +void main() +{ + vec4 clip = mul(u_modelViewProj, vec4(a_position, 1.0)); + gl_Position = clip; + // TheSuperHackers @bugfix bobtista 05/06/2026 Guard the divisor: near-plane-straddling + // verts can drive clip.w to 0/negative, and clamp() does not sanitize the resulting + // Inf/NaN. max() keeps valid w unchanged and yields a finite, clamped depth otherwise. + v_sceneDepth = vec4(clamp(clip.z / max(clip.w, 1e-6), 0.0, 1.0), 0.0, 0.0, 0.0); +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_scene_depth_instanced.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_scene_depth_instanced.sc new file mode 100644 index 00000000000..8d57f596f15 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_scene_depth_instanced.sc @@ -0,0 +1,18 @@ +$input a_position, i_data0, i_data1, i_data2, i_data3 +$output v_sceneDepth + +// TheSuperHackers @feature bobtista 28/06/2026 Instanced scene-depth caster. Mirrors +// vs_scene_depth but takes the world transform from the per-instance data (mtxFromCols, like +// vs_uber_instanced) instead of u_model, so an instanced opaque batch populates the readable +// depth target for all instances in one submit. + +#include + +void main() +{ + mat4 worldMtx = mtxFromCols(i_data0, i_data1, i_data2, i_data3); + vec4 worldPos = mul(worldMtx, vec4(a_position, 1.0)); + vec4 clip = mul(u_viewProj, worldPos); + gl_Position = clip; + v_sceneDepth = vec4(clamp(clip.z / max(clip.w, 1e-6), 0.0, 1.0), 0.0, 0.0, 0.0); +} 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..92175ebd572 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_caster.sc @@ -0,0 +1,16 @@ +$input a_position, a_texcoord0 +$output v_sceneDepth, v_texcoord0 + +// TheSuperHackers @feature bobtista 16/06/2026 Shadow-map caster vertex shader. +// Like vs_scene_depth, but also forwards the base UV so the fragment shader can +// alpha-test cutout geometry (infantry, foliage) into the shadow map. + +#include + +void main() +{ + vec4 clip = mul(u_modelViewProj, vec4(a_position, 1.0)); + gl_Position = clip; + v_sceneDepth = vec4(clamp(clip.z / max(clip.w, 1e-6), 0.0, 1.0), 0.0, 0.0, 0.0); + v_texcoord0 = a_texcoord0; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_caster_instanced.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_caster_instanced.sc new file mode 100644 index 00000000000..f78f31955d2 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_caster_instanced.sc @@ -0,0 +1,19 @@ +$input a_position, a_texcoord0, i_data0, i_data1, i_data2, i_data3 +$output v_sceneDepth, v_texcoord0 + +// TheSuperHackers @feature bobtista 28/06/2026 Instanced sun-shadow caster. Mirrors +// vs_shadow_caster but takes the world transform from the per-instance data, so an instanced +// opaque batch casts every instance into the shadow map in one submit. Forwards the base UV for +// alpha-tested cutout casters, same as the non-instanced path. + +#include + +void main() +{ + mat4 worldMtx = mtxFromCols(i_data0, i_data1, i_data2, i_data3); + vec4 worldPos = mul(worldMtx, vec4(a_position, 1.0)); + vec4 clip = mul(u_viewProj, worldPos); + gl_Position = clip; + v_sceneDepth = vec4(clamp(clip.z / max(clip.w, 1e-6), 0.0, 1.0), 0.0, 0.0, 0.0); + v_texcoord0 = a_texcoord0; +} 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..6b30c49f9f2 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_shadow_volume.sc @@ -0,0 +1,29 @@ +$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 and leaves hardware +// clipping to the backend. Manually clamping only clip.z distorts the +// homogeneous edge intersection and turns valid finite volumes into long +// screen-space triangles. +// +// u_shadowBias.x: small per-pass Z offset (unused currently, kept for +// future polygon-offset experiments). +// u_shadowBias.y: emulate depth clamp by clamping clip-space Z to the +// post-projection near/far range. Metal does not expose bgfx depth clamp +// in a way that preserves the original D3D8 open shadow volume behavior. + +#include + +uniform vec4 u_shadowBias; + +void main() +{ + vec4 clip = mul(u_modelViewProj, vec4(a_position, 1.0)); + clip.z += u_shadowBias.x * clip.w; + if (u_shadowBias.y > 0.5) + { + clip.z = clamp(clip.z, 0.0, clip.w); + } + gl_Position = clip; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_smudge.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_smudge.sc new file mode 100644 index 00000000000..8d65cf1287a --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_smudge.sc @@ -0,0 +1,15 @@ +$input a_position, a_color0, a_texcoord0 +$output v_color0, v_texcoord0 + +// TheSuperHackers @feature bobtista 27/04/2026 Dedicated bgfx +// smudge/heat-haze vertex shader. The legacy smudge geometry carries +// displaced screen-copy UVs, and vertex alpha supplies the radial mask. + +#include + +void main() +{ + gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); + v_color0 = a_color0.bgra; + v_texcoord0 = a_texcoord0; +} 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..7c1ec5d258d --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_trees.sc @@ -0,0 +1,71 @@ +$input a_position, a_normal, a_color0, a_texcoord0 +$output v_color0, v_texcoord0, v_texcoord1, v_normal, v_cloudUV, v_stage0UV, v_stage1UV, v_stage2UV, v_sceneDepth, v_worldPos + +#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; +uniform vec4 u_vertexColorFlags; // .x > 0.5 = FVF supplies COLOR0; else use D3D8's white default + +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); + waveIdx = min(max(waveIdx, 0), MAX_SWAY_TYPES_PLUS1 - 1); + 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)); + vec4 worldPos = mul(u_model[0], vec4(swayed, 1.0)); + v_worldPos = worldPos.xyz; + + // 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. + // TheSuperHackers @bugfix bobtista 17/07/2026 The v1.yyyw swizzle scales only rgb; + // v1.w is the implicit 1.0 of the float3 normal stream, so alpha stays unscaled. + // Scaling all four lanes made pushed-aside trees fade instead of only darken. + vec4 diffuseColor = (u_vertexColorFlags.x > 0.5) ? a_color0.bgra : vec4_splat(1.0); + v_color0 = vec4(diffuseColor.rgb * a_normal.y, diffuseColor.a); + + // Shroud UV: (v0.xy + c32.xy) * c33.xy. + vec2 shroudUV = (a_position.xy + u_shroudOffset.xy) * u_shroudScale.xy; + v_texcoord0 = a_texcoord0; + v_texcoord1 = shroudUV; + v_stage0UV = a_texcoord0; + v_stage1UV = shroudUV; + v_stage2UV = a_texcoord0; + v_sceneDepth = vec4(1.0, 0.0, 0.0, 0.0); + + 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; +} 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..50d6da3e593 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber.sc @@ -0,0 +1,187 @@ +$input a_position, a_normal, a_color0, a_texcoord0, a_texcoord1 +$output v_color0, v_texcoord0, v_texcoord1, v_normal, v_cloudUV, v_stage0UV, v_stage1UV, v_stage2UV, v_sceneDepth, v_worldPos + +#include + +uniform vec4 u_texcoordSelect; +uniform vec4 u_shroudParams; // xy = offset, zw = scale + +// TheSuperHackers @performance bobtista 15/06/2026 Packed per-draw material uniforms. +// Index order MUST match MaterialUniformSlot in BgfxBackend.cpp. +uniform vec4 u_material[25]; +#define u_matDiffuse u_material[0] +#define u_matAmbient u_material[1] +#define u_matEmissive u_material[2] +#define u_tssOps0 u_material[3] +#define u_tssOps1 u_material[4] +#define u_atestParams u_material[5] +#define u_texcoordSource u_material[6] +#define u_vertexColorFlags u_material[7] +#define u_texcoordSelect2 u_material[8] +#define u_projectedDecalMode u_material[9] +#define u_grayscaleEnable u_material[10] +#define u_objectShroudDim u_material[11] +#define u_cloudParams u_material[12] +#define u_texTransform0 u_material[13] +#define u_texTransform1 u_material[14] +#define u_texTransform0Z u_material[15] +#define u_tex1Transform0 u_material[16] +#define u_tex1Transform1 u_material[17] +#define u_tex1TransformZ u_material[18] +#define u_tex2Transform0 u_material[19] +#define u_tex2Transform1 u_material[20] +#define u_texProjected u_material[21] +#define u_legacyPixelShaderMode u_material[22] +#define u_zBias u_material[23] + +void main() +{ + vec3 position = a_position; +#if !GGC_UBER_STAGE0_ARRAY + // Array variant skips the normal-directed bias: for merged sorted runs + // a_normal.z carries the texture-array layer index, not a direction. + if (u_zBias.y != 0.0) + { + position += a_normal * u_zBias.y; + } +#endif + gl_Position = mul(u_modelViewProj, vec4(position, 1.0)); + // TheSuperHackers @bugfix bobtista 30/04/2026 Apply post-projection Z + // bias the same way D3DRS_ZBIAS pulls geometry toward the camera in DX8. + // gl_Position.z is in clip space ahead of the perspective divide, so + // scaling the offset by .w keeps the NDC bias roughly constant across + // depths. Backend leaves u_zBias.x at 0 for normal draws. + gl_Position.z -= u_zBias.x * gl_Position.w; + // TheSuperHackers @bugfix bobtista 05/06/2026 The old near-plane "nuke the + // vertex" guard is gone. The fullscreen-tint balloon it worked around was + // caused by BGFX_RESET_DEPTH_CLAMP disabling DX11 near-plane clipping; with + // depth-clamp off (see BgfxBackend Initialize) the rasterizer clips + // near-plane-straddling sorted geometry correctly, so the Particle Uplink + // Cannon beam renders and no balloon appears - matching Metal. + + v_color0 = (u_vertexColorFlags.x > 0.5) ? a_color0.bgra : vec4_splat(1.0); + v_texcoord0 = a_texcoord0; + v_texcoord1 = a_texcoord1; + v_stage0UV = (u_texcoordSelect.x > 0.5) ? a_texcoord1 : a_texcoord0; + v_stage1UV = (u_texcoordSelect2.x > 0.5) ? a_texcoord1 : a_texcoord0; + v_stage2UV = a_texcoord0; + v_sceneDepth = vec4(1.0, 1.0, 0.0, 0.0); +#if GGC_UBER_STAGE0_ARRAY + // Merged sorted runs submit with a view-only model transform; the layer + // index in a_normal.z must reach the fragment shader untransformed. + v_normal = a_normal; +#else + v_normal = mul(u_model[0], vec4(a_normal, 0.0)).xyz; +#endif + vec4 worldPos = mul(u_model[0], vec4(a_position, 1.0)); + v_worldPos = worldPos.xyz; + + if (u_texcoordSelect.w > 0.5) + { + vec2 sourceUV = (u_texcoordSelect.x > 0.5) ? a_texcoord1 : a_texcoord0; + // TheSuperHackers @bugfix bobtista 27/04/2026 W3D's 2D mappers + // encode atlas offsets in the texture matrix's third component. + // Feed z=1 for mesh UV transforms so animated tread/wheel + // mappers land on the same texels as the DX8 fixed-function path. + vec4 source = vec4(sourceUV, 1.0, 1.0); + if (u_texcoordSource.x > 0.5 && u_texcoordSource.x < 1.5) + { + source = vec4(normalize(mul(u_view, vec4(v_normal, 0.0)).xyz), 1.0); + } + else if (u_texcoordSource.x > 1.5) + { + vec3 cameraPos = mul(u_view, worldPos).xyz; + vec3 cameraNormal = normalize(mul(u_view, vec4(v_normal, 0.0)).xyz); + if (u_texcoordSource.x < 2.5) + { + source = vec4(reflect(normalize(cameraPos), cameraNormal), 1.0); + } + else + { + source = vec4(cameraPos, 1.0); + } + } + float u0 = dot(u_texTransform0, source); + float v0 = dot(u_texTransform1, source); + if (u_texProjected.x > 0.5) + { + v_sceneDepth.x = dot(u_texTransform0Z, source); + } + v_stage0UV = vec2(u0, v0); + } + if (u_texcoordSelect2.y > 0.5) + { + vec2 sourceUV = (u_texcoordSelect2.x > 0.5) ? a_texcoord1 : a_texcoord0; + vec4 source = vec4(sourceUV, 1.0, 1.0); + if (u_texcoordSource.y > 0.5 && u_texcoordSource.y < 1.5) + { + source = vec4(normalize(mul(u_view, vec4(v_normal, 0.0)).xyz), 1.0); + } + else if (u_texcoordSource.y > 1.5) + { + vec3 cameraPos = mul(u_view, worldPos).xyz; + vec3 cameraNormal = normalize(mul(u_view, vec4(v_normal, 0.0)).xyz); + if (u_texcoordSource.y < 2.5) + { + source = vec4(reflect(normalize(cameraPos), cameraNormal), 1.0); + } + else + { + source = vec4(cameraPos, 1.0); + } + } + float u1 = dot(u_tex1Transform0, source); + float v1 = dot(u_tex1Transform1, source); + if (u_texProjected.y > 0.5) + { + v_sceneDepth.y = dot(u_tex1TransformZ, source); + } + v_stage1UV = vec2(u1, v1); + } + if (u_texcoordSource.z > 0.5 || u_tex2Transform0.x != 1.0 || u_tex2Transform1.y != 1.0 + || u_tex2Transform0.y != 0.0 || u_tex2Transform1.x != 0.0 + || u_tex2Transform0.z != 0.0 || u_tex2Transform1.z != 0.0 + || u_tex2Transform0.w != 0.0 || u_tex2Transform1.w != 0.0) + { + vec4 source = vec4(a_texcoord0, 1.0, 1.0); + if (u_texcoordSource.z > 0.5 && u_texcoordSource.z < 1.5) + { + source = vec4(normalize(mul(u_view, vec4(v_normal, 0.0)).xyz), 1.0); + } + else if (u_texcoordSource.z > 1.5) + { + vec3 cameraPos = mul(u_view, worldPos).xyz; + vec3 cameraNormal = normalize(mul(u_view, vec4(v_normal, 0.0)).xyz); + if (u_texcoordSource.z < 2.5) + { + source = vec4(reflect(normalize(cameraPos), cameraNormal), 1.0); + } + else + { + source = vec4(cameraPos, 1.0); + } + } + float u2 = dot(u_tex2Transform0, source); + float v2 = dot(u_tex2Transform1, source); + v_stage2UV = vec2(u2, v2); + } + + // 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) + { + vec2 shroudUV = (worldPos.xy + u_shroudParams.xy) * u_shroudParams.zw; + v_stage0UV = shroudUV; + v_stage1UV = shroudUV; + } + + // 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; + +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber_instanced.sc b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber_instanced.sc new file mode 100644 index 00000000000..2b9c4638652 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/shaders/vs_uber_instanced.sc @@ -0,0 +1,161 @@ +$input a_position, a_normal, a_color0, a_texcoord0, a_texcoord1, i_data0, i_data1, i_data2, i_data3 +$output v_color0, v_texcoord0, v_texcoord1, v_normal, v_cloudUV, v_stage0UV, v_stage1UV, v_stage2UV, v_sceneDepth, v_worldPos + +#include + +uniform vec4 u_texcoordSelect; +uniform vec4 u_shroudParams; + +// TheSuperHackers @performance bobtista 15/06/2026 Packed per-draw material uniforms. +// Index order MUST match MaterialUniformSlot in BgfxBackend.cpp. +uniform vec4 u_material[25]; +#define u_matDiffuse u_material[0] +#define u_matAmbient u_material[1] +#define u_matEmissive u_material[2] +#define u_tssOps0 u_material[3] +#define u_tssOps1 u_material[4] +#define u_atestParams u_material[5] +#define u_texcoordSource u_material[6] +#define u_vertexColorFlags u_material[7] +#define u_texcoordSelect2 u_material[8] +#define u_projectedDecalMode u_material[9] +#define u_grayscaleEnable u_material[10] +#define u_objectShroudDim u_material[11] +#define u_cloudParams u_material[12] +#define u_texTransform0 u_material[13] +#define u_texTransform1 u_material[14] +#define u_texTransform0Z u_material[15] +#define u_tex1Transform0 u_material[16] +#define u_tex1Transform1 u_material[17] +#define u_tex1TransformZ u_material[18] +#define u_tex2Transform0 u_material[19] +#define u_tex2Transform1 u_material[20] +#define u_texProjected u_material[21] +#define u_legacyPixelShaderMode u_material[22] +#define u_zBias u_material[23] + +void main() +{ + // TheSuperHackers @bugfix bobtista 02/07/2026 i_data0-3 must use bgfx's canonical descending + // TEXCOORD7-4 semantics in varying.def.sc (which cannot hold comments). The D3D11 backend + // hardcodes TEXCOORD7=i_data0 down to TEXCOORD4=i_data3 in its input layout; the previous + // ascending TEXCOORD6-9 mapping swapped matrix columns 0/1 and left columns 2/3 unbound, + // garbling instanced world transforms on DX11. Metal binds by attribute name, unaffected. + mat4 worldMtx = mtxFromCols(i_data0, i_data1, i_data2, i_data3); + + vec3 position = a_position; + if (u_zBias.y != 0.0) + { + position += a_normal * u_zBias.y; + } + vec4 worldPos = mul(worldMtx, vec4(position, 1.0)); + gl_Position = mul(u_viewProj, worldPos); + gl_Position.z -= u_zBias.x * gl_Position.w; + // TheSuperHackers @bugfix bobtista 05/06/2026 Near-plane guard removed; DX11 + // near-plane clipping is restored by disabling depth-clamp (see vs_uber.sc). + + v_color0 = (u_vertexColorFlags.x > 0.5) ? a_color0.bgra : vec4_splat(1.0); + v_texcoord0 = a_texcoord0; + v_texcoord1 = a_texcoord1; + v_stage0UV = (u_texcoordSelect.x > 0.5) ? a_texcoord1 : a_texcoord0; + v_stage1UV = (u_texcoordSelect2.x > 0.5) ? a_texcoord1 : a_texcoord0; + v_stage2UV = a_texcoord0; + v_sceneDepth = vec4(1.0, 1.0, 0.0, 0.0); + v_normal = mul(worldMtx, vec4(a_normal, 0.0)).xyz; + v_worldPos = worldPos.xyz; + + if (u_texcoordSelect.w > 0.5) + { + vec2 sourceUV = (u_texcoordSelect.x > 0.5) ? a_texcoord1 : a_texcoord0; + vec4 source = vec4(sourceUV, 1.0, 1.0); + if (u_texcoordSource.x > 0.5 && u_texcoordSource.x < 1.5) + { + source = vec4(normalize(mul(u_view, vec4(v_normal, 0.0)).xyz), 1.0); + } + else if (u_texcoordSource.x > 1.5) + { + vec3 cameraPos = mul(u_view, worldPos).xyz; + vec3 cameraNormal = normalize(mul(u_view, vec4(v_normal, 0.0)).xyz); + if (u_texcoordSource.x < 2.5) + { + source = vec4(reflect(normalize(cameraPos), cameraNormal), 1.0); + } + else + { + source = vec4(cameraPos, 1.0); + } + } + float u0 = dot(u_texTransform0, source); + float v0 = dot(u_texTransform1, source); + if (u_texProjected.x > 0.5) + { + v_sceneDepth.x = dot(u_texTransform0Z, source); + } + v_stage0UV = vec2(u0, v0); + } + if (u_texcoordSelect2.y > 0.5) + { + vec2 sourceUV = (u_texcoordSelect2.x > 0.5) ? a_texcoord1 : a_texcoord0; + vec4 source = vec4(sourceUV, 1.0, 1.0); + if (u_texcoordSource.y > 0.5 && u_texcoordSource.y < 1.5) + { + source = vec4(normalize(mul(u_view, vec4(v_normal, 0.0)).xyz), 1.0); + } + else if (u_texcoordSource.y > 1.5) + { + vec3 cameraPos = mul(u_view, worldPos).xyz; + vec3 cameraNormal = normalize(mul(u_view, vec4(v_normal, 0.0)).xyz); + if (u_texcoordSource.y < 2.5) + { + source = vec4(reflect(normalize(cameraPos), cameraNormal), 1.0); + } + else + { + source = vec4(cameraPos, 1.0); + } + } + float u1 = dot(u_tex1Transform0, source); + float v1 = dot(u_tex1Transform1, source); + if (u_texProjected.y > 0.5) + { + v_sceneDepth.y = dot(u_tex1TransformZ, source); + } + v_stage1UV = vec2(u1, v1); + } + if (u_texcoordSource.z > 0.5 || u_tex2Transform0.x != 1.0 || u_tex2Transform1.y != 1.0 + || u_tex2Transform0.y != 0.0 || u_tex2Transform1.x != 0.0 + || u_tex2Transform0.z != 0.0 || u_tex2Transform1.z != 0.0 + || u_tex2Transform0.w != 0.0 || u_tex2Transform1.w != 0.0) + { + vec4 source = vec4(a_texcoord0, 1.0, 1.0); + if (u_texcoordSource.z > 0.5 && u_texcoordSource.z < 1.5) + { + source = vec4(normalize(mul(u_view, vec4(v_normal, 0.0)).xyz), 1.0); + } + else if (u_texcoordSource.z > 1.5) + { + vec3 cameraPos = mul(u_view, worldPos).xyz; + vec3 cameraNormal = normalize(mul(u_view, vec4(v_normal, 0.0)).xyz); + if (u_texcoordSource.z < 2.5) + { + source = vec4(reflect(normalize(cameraPos), cameraNormal), 1.0); + } + else + { + source = vec4(cameraPos, 1.0); + } + } + float u2 = dot(u_tex2Transform0, source); + float v2 = dot(u_tex2Transform1, source); + v_stage2UV = vec2(u2, v2); + } + + if (u_texcoordSelect.z > 0.5) + { + vec2 shroudUV = (worldPos.xy + u_shroudParams.xy) * u_shroudParams.zw; + v_stage0UV = shroudUV; + v_stage1UV = shroudUV; + } + + v_cloudUV = worldPos.xy * u_cloudParams.z + u_cloudParams.xy; +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/shattersystem.cpp b/Core/Libraries/Source/WWVegas/WW3D2/shattersystem.cpp index f216f34d64a..c6804eb4ba2 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/shattersystem.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/shattersystem.cpp @@ -43,6 +43,7 @@ #include "dynamesh.h" #include "htree.h" #include "WWMath/plane.h" +#include "WW3D2/ww3dcolor.h" #include "WWLib/simplevec.h" #include "WWLib/wwstring.h" #include "WWMath/vp.h" @@ -339,10 +340,10 @@ void VertexClass::Lerp // interpolate material properies for (int ipass=0; ipassDCG[ipass]); Vector4::Lerp(dig_v0,dig_v1,res->DIG[ipass]); // Vector4::Lerp(v0.DCG[ipass],v1.DCG[ipass],lerp,&(res->DCG[ipass])); @@ -450,7 +451,7 @@ void PolygonClass::Compute_Plane() ay /= (double)NumVerts; az /= (double)NumVerts; - double len = WWMath::Sqrt(nx*nx + ny*ny + nz*nz); + double len = WWMath::Sqrt_Legacy(nx*nx + ny*ny + nz*nz); nx /= len; ny /= len; nz /= len; @@ -1204,10 +1205,10 @@ void ShatterSystem::Process_Clip_Pools // HY- Multiplying DIG with DCG as in meshmdlio if (mtl_params.DIG[ipass] != nullptr) { SHATTER_DEBUG_SAY(("DIG: pass:%d: %f %f %f",ipass,vert.DIG[ipass].X,vert.DIG[ipass].Y,vert.DIG[ipass].Z)); - Vector4 mc=DX8Wrapper::Convert_Color(mycolor); - Vector4 dc=DX8Wrapper::Convert_Color(vert.DIG[ipass]); + Vector4 mc=WW3DColor::From_ARGB(mycolor); + Vector4 dc=WW3DColor::From_ARGB(vert.DIG[ipass]); mc=Vector4(mc.X*dc.X,mc.Y*dc.Y,mc.Z*dc.Z,mc.W); - mycolor=DX8Wrapper::Convert_Color(mc); + mycolor=WW3DColor::To_ARGB(mc); } new_mesh->Color(mycolor); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp index 14b51020152..48c308bc0b5 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/sortingrenderer.cpp @@ -38,18 +38,29 @@ * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#include + #include "sortingrenderer.h" #include "dx8vertexbuffer.h" #include "dx8indexbuffer.h" -#include "dx8wrapper.h" +#include "WW3D2/dllist.h" +#include "WW3D2/FixedFunctionState.h" +#include "WW3D2/RenderBufferTypes.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "WW3D2/vertmaterial.h" #include "texture.h" -#include "d3d8.h" -#include "d3dx8math.h" #include "statistics.h" +#include "WW3D2/BgfxRenderProfile.h" +#include "GgcRuntimeFlags.h" +#include "WW3D2/ww3d.h" #include #include #include +#include +#include +#include +#include bool SortingRendererClass::_EnableTriangleDraw=true; @@ -76,6 +87,156 @@ struct TempIndexStruct float z; }; +enum BgfxSortedPacketDiagSource : unsigned char +{ + BGFX_SORTED_PACKET_SOURCE_OTHER = 0, + BGFX_SORTED_PACKET_SOURCE_POINT_GROUP, + BGFX_SORTED_PACKET_SOURCE_STREAK +}; + +enum BgfxSortedPacketDiagFallback : unsigned char +{ + BGFX_SORTED_PACKET_FALLBACK_NONE = 0, + BGFX_SORTED_PACKET_FALLBACK_DEPTH_WRITE, + BGFX_SORTED_PACKET_FALLBACK_ALPHA_TEST, + BGFX_SORTED_PACKET_FALLBACK_NON_ADDITIVE_BLEND, + BGFX_SORTED_PACKET_FALLBACK_UNKNOWN +}; + +enum BgfxSortedPacketDiagBlendClass : unsigned +{ + BGFX_SORTED_PACKET_BLEND_ALPHA = 0, + BGFX_SORTED_PACKET_BLEND_MULTIPLY, + BGFX_SORTED_PACKET_BLEND_SCREEN, + BGFX_SORTED_PACKET_BLEND_SRC_ALPHA_ONE, + BGFX_SORTED_PACKET_BLEND_OTHER, + BGFX_SORTED_PACKET_BLEND_COUNT +}; + +static bool Sorted_Packet_Collector_Diag_Enabled() +{ + static const bool enabled = GgcFlags::Enabled(GgcFlag_BgfxSortedPacketCollectorDiag); + return enabled; +} + +static unsigned Sorted_Packet_Collector_Diag_Max_Flushes() +{ + static const unsigned maxFlushes = []() -> unsigned { + const char* env = GgcFlags::StringValue(GgcFlag_BgfxSortedPacketCollectorDiagLimit); + if (env == nullptr) + { + return 512; + } + const int parsed = std::atoi(env); + return parsed > 0 ? static_cast(parsed) : 512; + }(); + return maxFlushes; +} + +static inline bool Is_Additive_Sorted_Packet_Candidate(const RenderStateStruct& rs) +{ + return rs.shader.Get_Depth_Mask() == ShaderClass::DEPTH_WRITE_DISABLE + && rs.shader.Get_Src_Blend_Func() == ShaderClass::SRCBLEND_ONE + && rs.shader.Get_Dst_Blend_Func() == ShaderClass::DSTBLEND_ONE + && rs.shader.Get_Alpha_Test() == ShaderClass::ALPHATEST_DISABLE; +} + +static BgfxSortedPacketDiagSource Classify_Sorted_Packet_Diag_Source(const RenderStateStruct& rs) +{ + if ((rs.sorted_draw_flags & RB_SORTED_DRAW_POINT_GROUP) != 0) + { + return BGFX_SORTED_PACKET_SOURCE_POINT_GROUP; + } + if ((rs.sorted_draw_flags & RB_SORTED_DRAW_STREAK) != 0) + { + return BGFX_SORTED_PACKET_SOURCE_STREAK; + } + return BGFX_SORTED_PACKET_SOURCE_OTHER; +} + +static BgfxSortedPacketDiagFallback Classify_Sorted_Packet_Diag_Fallback(const RenderStateStruct& rs) +{ + if (Is_Additive_Sorted_Packet_Candidate(rs)) + { + return BGFX_SORTED_PACKET_FALLBACK_NONE; + } + if (rs.shader.Get_Depth_Mask() != ShaderClass::DEPTH_WRITE_DISABLE) + { + return BGFX_SORTED_PACKET_FALLBACK_DEPTH_WRITE; + } + if (rs.shader.Get_Alpha_Test() != ShaderClass::ALPHATEST_DISABLE) + { + return BGFX_SORTED_PACKET_FALLBACK_ALPHA_TEST; + } + if (rs.shader.Get_Src_Blend_Func() != ShaderClass::SRCBLEND_ONE + || rs.shader.Get_Dst_Blend_Func() != ShaderClass::DSTBLEND_ONE) + { + return BGFX_SORTED_PACKET_FALLBACK_NON_ADDITIVE_BLEND; + } + return BGFX_SORTED_PACKET_FALLBACK_UNKNOWN; +} + +static BgfxSortedPacketDiagBlendClass Classify_Sorted_Packet_Diag_Blend(const RenderStateStruct& rs) +{ + const ShaderClass::SrcBlendFuncType src = rs.shader.Get_Src_Blend_Func(); + const ShaderClass::DstBlendFuncType dst = rs.shader.Get_Dst_Blend_Func(); + if (src == ShaderClass::SRCBLEND_SRC_ALPHA + && dst == ShaderClass::DSTBLEND_ONE_MINUS_SRC_ALPHA) + { + return BGFX_SORTED_PACKET_BLEND_ALPHA; + } + if (src == ShaderClass::SRCBLEND_ZERO + && dst == ShaderClass::DSTBLEND_SRC_COLOR) + { + return BGFX_SORTED_PACKET_BLEND_MULTIPLY; + } + if (src == ShaderClass::SRCBLEND_ONE + && dst == ShaderClass::DSTBLEND_ONE_MINUS_SRC_COLOR) + { + return BGFX_SORTED_PACKET_BLEND_SCREEN; + } + if (src == ShaderClass::SRCBLEND_SRC_ALPHA + && dst == ShaderClass::DSTBLEND_ONE) + { + return BGFX_SORTED_PACKET_BLEND_SRC_ALPHA_ONE; + } + return BGFX_SORTED_PACKET_BLEND_OTHER; +} + +static Matrix4x4 Multiply_Sorted_Matrix(const Matrix4x4& lhs, const Matrix4x4& rhs) +{ + Matrix4x4 result; + for (int row = 0; row < 4; ++row) { + for (int col = 0; col < 4; ++col) { + result[row][col] = + lhs[row][0] * rhs[0][col] + + lhs[row][1] * rhs[1][col] + + lhs[row][2] * rhs[2][col] + + lhs[row][3] * rhs[3][col]; + } + } + return result; +} + +static Matrix4x4 Get_Sorted_World_View_Matrix(const RenderStateStruct& state) +{ + static_assert(sizeof(state.world) == sizeof(Matrix4x4), "sorted matrix snapshot must match Matrix4x4 for reinterpret_cast"); + return Multiply_Sorted_Matrix( + reinterpret_cast(state.world), + reinterpret_cast(state.view)); +} + +static Vector3 Transform_Sorted_Point(const Vector3& point, const Matrix4x4& matrix) +{ + return Vector3( + point.X * matrix[0][0] + point.Y * matrix[1][0] + point.Z * matrix[2][0] + matrix[3][0], + point.X * matrix[0][1] + point.Y * matrix[1][1] + point.Z * matrix[2][1] + matrix[3][1], + point.X * matrix[0][2] + point.Y * matrix[1][2] + point.Z * matrix[2][2] + matrix[3][2]); +} + +static RenderBackendSortedBatchState Make_Render_Backend_Sorted_State(const RenderStateStruct& render_state); +static inline bool Is_Coalescable_Additive(const RenderStateStruct& rs); + bool operator <(const TempIndexStruct &l, const TempIndexStruct &r) { return l.z < r.z; } bool operator <=(const TempIndexStruct &l, const TempIndexStruct &r) { return l.z <= r.z; } bool operator >(const TempIndexStruct &l, const TempIndexStruct &r) { return l.z > r.z; } @@ -163,6 +324,9 @@ class SortingNodeStruct unsigned short polygon_count; // Polygon count to process (3 indices = one polygon) unsigned short min_vertex_index; // First index used in the vb unsigned short vertex_count; // Number of vertices used in vb + RenderBackendSortedBatchState render_backend_state; + unsigned char packet_diag_source; + unsigned char packet_diag_fallback; }; typedef std::list SortingNodeStructList; @@ -215,8 +379,9 @@ void SortingRendererClass::Insert_Triangles( unsigned short min_vertex_index, unsigned short vertex_count) { + GGC_RPROFILE(SORTED_INSERT); 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; } @@ -228,7 +393,7 @@ void SortingRendererClass::Insert_Triangles( SortingNodeStruct* state=Get_Sorting_Struct(); - DX8Wrapper::Get_Render_State(state->sorting_state); + g_renderBackend->Capture_Legacy_Render_State_For_Sorted_Draw(state->sorting_state); WWASSERT( ((state->sorting_state.index_buffer_type==BUFFER_TYPE_SORTING || state->sorting_state.index_buffer_type==BUFFER_TYPE_DYNAMIC_SORTING) && @@ -239,17 +404,22 @@ void SortingRendererClass::Insert_Triangles( state->polygon_count=polygon_count; state->min_vertex_index=min_vertex_index; state->vertex_count=vertex_count; + if (g_renderBackend->Has_Shader_Pipeline() && Sorted_Packet_Collector_Diag_Enabled()) + { + state->packet_diag_source = static_cast(Classify_Sorted_Packet_Diag_Source(state->sorting_state)); + state->packet_diag_fallback = static_cast(Classify_Sorted_Packet_Diag_Fallback(state->sorting_state)); + } + else + { + state->packet_diag_source = BGFX_SORTED_PACKET_SOURCE_OTHER; + state->packet_diag_fallback = BGFX_SORTED_PACKET_FALLBACK_UNKNOWN; + } + state->render_backend_state = Make_Render_Backend_Sorted_State(state->sorting_state); if (bounding_sphere.Is_Valid()) { - D3DXMATRIX mtx=(D3DXMATRIX&)state->sorting_state.world*(D3DXMATRIX&)state->sorting_state.view; - D3DXVECTOR3 vec=(D3DXVECTOR3&)bounding_sphere.Center; - D3DXVECTOR4 transformed_vec; - D3DXVec3Transform( - &transformed_vec, - &vec, - &mtx); - state->transformed_center=Vector3(transformed_vec[0],transformed_vec[1],transformed_vec[2]); + const Matrix4x4 mtx = Get_Sorted_World_View_Matrix(state->sorting_state); + state->transformed_center = Transform_Sorted_Point(bounding_sphere.Center, mtx); Insert_To_Sorted_List(state); } @@ -313,7 +483,7 @@ void Release_Refs(SortingNodeStruct* state) } REF_PTR_RELEASE(state->sorting_state.index_buffer); REF_PTR_RELEASE(state->sorting_state.material); - for (i=0;iGet_Max_Textures_Per_Pass();++i) + for (i=0;isorting_state.Textures[i]); } @@ -353,6 +523,14 @@ void SortingRendererClass::Insert_To_Sorting_Pool(SortingNodeStruct* state) return; } + // TheSuperHackers @bugfix bobtista 13/07/2026 Flush the pool early when this node would + // push it past the 65535 vertices addressable by the 16-bit sorted triangle indices. + // Beyond that the combined vertex buffer allocation truncated and the pool flush + // overflowed it. Sorting is only lost across the flush boundary in such extreme scenes. + if (overlapping_vertex_count+state->vertex_count>65535) { + Flush_Sorting_Pool(); + } + overlapping_nodes[overlapping_node_count]=state; overlapping_vertex_count+=state->vertex_count; overlapping_polygon_count+=state->polygon_count; @@ -362,228 +540,1261 @@ void SortingRendererClass::Insert_To_Sorting_Pool(SortingNodeStruct* state) // ---------------------------------------------------------------------------- //static unsigned prevLight = 0xffffffff; -static void Apply_Render_State(RenderStateStruct& render_state) +static RenderBackendLight Make_Render_Backend_Light(const RenderStateStruct & render_state, int index) +{ + const auto & light = render_state.Lights[index]; + RenderBackendLight rb_light; + rb_light.type = light.Type; + rb_light.position[0] = light.Position.x; + rb_light.position[1] = light.Position.y; + rb_light.position[2] = light.Position.z; + rb_light.direction[0] = light.Direction.x; + rb_light.direction[1] = light.Direction.y; + rb_light.direction[2] = light.Direction.z; + rb_light.diffuse[0] = light.Diffuse.r; + rb_light.diffuse[1] = light.Diffuse.g; + rb_light.diffuse[2] = light.Diffuse.b; + rb_light.ambient[0] = light.Ambient.r; + rb_light.ambient[1] = light.Ambient.g; + rb_light.ambient[2] = light.Ambient.b; + rb_light.specular[0] = light.Specular.r; + rb_light.specular[1] = light.Specular.g; + rb_light.specular[2] = light.Specular.b; + rb_light.range = light.Range; + rb_light.falloff = light.Falloff; + rb_light.attenuation[0] = light.Attenuation0; + rb_light.attenuation[1] = light.Attenuation1; + rb_light.attenuation[2] = light.Attenuation2; + rb_light.theta = light.Theta; + rb_light.phi = light.Phi; + return rb_light; +} + +static RenderBackendSortedMaterialSnapshot Make_Render_Backend_Sorted_Material_Snapshot(const RenderStateStruct& render_state) { - DX8Wrapper::Set_Shader(render_state.shader); + RenderBackendSortedMaterialSnapshot snapshot; + snapshot.valid = true; + const VertexMaterialClass* material = render_state.material; + if (material == nullptr) + { + return snapshot; + } - DX8Wrapper::Set_Material(render_state.material); + Vector3 diffuse(1.0f, 1.0f, 1.0f); + Vector3 ambient(1.0f, 1.0f, 1.0f); + const VertexMaterialClass::ColorSourceType diffuse_source = + const_cast(material)->Get_Diffuse_Color_Source(); + const VertexMaterialClass::ColorSourceType ambient_source = + const_cast(material)->Get_Ambient_Color_Source(); + const VertexMaterialClass::ColorSourceType emissive_source = + const_cast(material)->Get_Emissive_Color_Source(); + if (diffuse_source == VertexMaterialClass::MATERIAL) + { + material->Get_Diffuse(&diffuse); + } + if (ambient_source == VertexMaterialClass::MATERIAL) + { + material->Get_Ambient(&ambient); + } + snapshot.diffuse[0] = diffuse.X; + snapshot.diffuse[1] = diffuse.Y; + snapshot.diffuse[2] = diffuse.Z; + snapshot.diffuse[3] = material->Get_Opacity(); + snapshot.ambient[0] = ambient.X; + snapshot.ambient[1] = ambient.Y; + snapshot.ambient[2] = ambient.Z; + snapshot.ambient[3] = 1.0f; + snapshot.vertex_color_flags[1] = (diffuse_source == VertexMaterialClass::COLOR1) ? 1.0f : 0.0f; + snapshot.vertex_color_flags[2] = (ambient_source == VertexMaterialClass::COLOR1) ? 1.0f : 0.0f; + snapshot.vertex_color_flags[3] = (emissive_source == VertexMaterialClass::COLOR1) ? 1.0f : 0.0f; + snapshot.lighting_enabled[0] = + (material->Get_Lighting() && !WW3D::Is_Coloring_Enabled()) ? 1.0f : 0.0f; + + Vector3 emissive(0.0f, 0.0f, 0.0f); + material->Get_Emissive(&emissive); + snapshot.emissive[0] = emissive.X; + snapshot.emissive[1] = emissive.Y; + snapshot.emissive[2] = emissive.Z; + snapshot.emissive[3] = 0.0f; + + Vector3 specular(0.0f, 0.0f, 0.0f); + material->Get_Specular(&specular); + snapshot.specular[0] = specular.X; + snapshot.specular[1] = specular.Y; + snapshot.specular[2] = specular.Z; + snapshot.specular[3] = material->Get_Shininess(); + return snapshot; +} - for (int i=0;iGet_Max_Textures_Per_Pass();++i) +static RenderBackendSortedBatchState Make_Render_Backend_Sorted_State(const RenderStateStruct& render_state) +{ + static_assert(sizeof(render_state.world) == sizeof(Matrix4x4), "sorted matrix snapshot must match Matrix4x4 for reinterpret_cast"); + RenderBackendSortedBatchState rb_state; + rb_state.shader = &render_state.shader; + rb_state.material = render_state.material; + for (unsigned i = 0; i < RB_MAX_TEXTURE_STAGES; ++i) + { + rb_state.textures[i] = render_state.Textures[i]; + } + rb_state.world = &reinterpret_cast(render_state.world); + rb_state.view = &reinterpret_cast(render_state.view); + const bool use_lights = (render_state.material != nullptr && render_state.material->Get_Lighting()); + for (int i = 0; i < 4; ++i) { - DX8Wrapper::Set_Texture(i,render_state.Textures[i]); + rb_state.lights.lights[i] = Make_Render_Backend_Light(render_state, i); + rb_state.lights.enabled[i] = use_lights && render_state.LightEnable[i]; } + rb_state.material_snapshot = Make_Render_Backend_Sorted_Material_Snapshot(render_state); + rb_state.draw_flags = render_state.sorted_draw_flags; + rb_state.resolved_state = (static_cast(render_state.resolved_state_hi) << 32) + | render_state.resolved_state_lo; + rb_state.resolved_state_valid = render_state.resolved_state_valid; + return rb_state; +} - DX8Wrapper::_Set_DX8_Transform(D3DTS_WORLD,render_state.world); - DX8Wrapper::_Set_DX8_Transform(D3DTS_VIEW,render_state.view); +static bool Sorted_Batch_State_Packet_Cache_Disabled() +{ + static const bool disabled = GgcFlags::Enabled(GgcFlag_BgfxDisableSortedBatchStatePacketCache); + return disabled; +} +static void Apply_Render_State(SortingNodeStruct* state) +{ + if (Sorted_Batch_State_Packet_Cache_Disabled()) + { + g_renderBackend->Apply_Sorted_Batch_State(Make_Render_Backend_Sorted_State(state->sorting_state)); + return; + } + g_renderBackend->Apply_Sorted_Batch_State(state->render_backend_state); +} - if (!render_state.material->Get_Lighting()) - return; //no point changing lights if they are ignored. - //prevLight = render_state.lightsHash; +static bool Should_Log_Sort_Effect_Diag() +{ + static const bool enabled = GgcFlags::Enabled(GgcFlag_SortEffectDiag); + return enabled; +} - if (render_state.LightEnable[0]) { - DX8Wrapper::Set_DX8_Light(0,&render_state.Lights[0]); - if (render_state.LightEnable[1]) { - DX8Wrapper::Set_DX8_Light(1,&render_state.Lights[1]); - if (render_state.LightEnable[2]) { - DX8Wrapper::Set_DX8_Light(2,&render_state.Lights[2]); - if (render_state.LightEnable[3]) { - DX8Wrapper::Set_DX8_Light(3,&render_state.Lights[3]); - } - else { - DX8Wrapper::Set_DX8_Light(3,nullptr); - } - } - else { - DX8Wrapper::Set_DX8_Light(2,nullptr); - } +static void Log_Sort_Effect_Diag(const char* event, unsigned start_index, unsigned polygon_count, SortingNodeStruct* state) +{ + if (!Should_Log_Sort_Effect_Diag()) + { + return; + } + + TextureClass* tex0 = (state != nullptr && state->sorting_state.Textures[0] != nullptr) + ? state->sorting_state.Textures[0]->As_TextureClass() + : nullptr; + const char* texName = tex0 != nullptr ? tex0->Get_Full_Path().str() : "(null)"; + static const bool logAll = GgcFlags::Enabled(GgcFlag_SortEffectDiagAll); + if (!logAll + && strnicmp(texName, "ex", 2) != 0 + && std::strstr(texName, "fire") == nullptr + && std::strstr(texName, "smoke") == nullptr + && std::strstr(texName, "noise") == nullptr) + { + return; + } + + float worldTx = 0.0f, worldTy = 0.0f, worldTz = 0.0f; + if (state != nullptr) + { + const Matrix4x4& w = reinterpret_cast(state->sorting_state.world); + worldTx = w[3][0]; + worldTy = w[3][1]; + worldTz = w[3][2]; + } + float centerZ = state != nullptr ? state->transformed_center.Z : 0.0f; + + if (FILE* diag = std::fopen("ggc_sort_effect_diag.txt", "a")) + { + std::fprintf(diag, + "%s polys=%u tex=%s shader=0x%08x worldT=(%.2f,%.2f,%.2f) centerZ=%.3f\n", + event, + polygon_count, + texName, + state != nullptr ? state->sorting_state.shader.Get_Bits() : 0, + worldTx, worldTy, worldTz, centerZ); + std::fclose(diag); + } +} + +// ---------------------------------------------------------------------------- + +static bool Render_State_Matches(const RenderStateStruct& left, const RenderStateStruct& right) +{ + if (left.shader.Get_Bits() != right.shader.Get_Bits()) + { + return false; + } + + if (left.sorted_draw_flags != right.sorted_draw_flags) + { + return false; + } + + if (left.material != right.material) + { + return false; + } + + for (int texture_index=0; texture_indexGet_Max_Texture_Stages(); ++texture_index) + { + if (left.Textures[texture_index] != right.Textures[texture_index]) + { + return false; } - else { - DX8Wrapper::Set_DX8_Light(1,nullptr); + } + + if (std::memcmp(&left.world,&right.world,sizeof(left.world)) != 0) + { + return false; + } + + if (std::memcmp(&left.view,&right.view,sizeof(left.view)) != 0) + { + return false; + } + + for (int light_index=0; light_index<4; ++light_index) + { + if (left.LightEnable[light_index] != right.LightEnable[light_index]) + { + return false; + } + + if (left.LightEnable[light_index] && std::memcmp(&left.Lights[light_index],&right.Lights[light_index],sizeof(left.Lights[light_index])) != 0) + { + return false; } } - else { - DX8Wrapper::Set_DX8_Light(0,nullptr); + + return true; +} + +// TheSuperHackers @bugfix bobtista 10/07/2026 Particle materials are pooled +// and reconfigured between draws, so a matching material pointer does not +// guarantee matching material state. Compare the insertion-time snapshots so +// absorption cannot merge nodes whose shared material object carried +// different values when each node was captured. +static bool Sorted_Material_Snapshots_Match(const RenderBackendSortedMaterialSnapshot& left, const RenderBackendSortedMaterialSnapshot& right) +{ + if (left.valid != right.valid) + { + return false; } + if (!left.valid) + { + return true; + } + for (int i = 0; i < 4; ++i) + { + if (left.diffuse[i] != right.diffuse[i] + || left.ambient[i] != right.ambient[i] + || left.specular[i] != right.specular[i] + || left.emissive[i] != right.emissive[i] + || left.vertex_color_flags[i] != right.vertex_color_flags[i] + || left.lighting_enabled[i] != right.lighting_enabled[i]) + { + return false; + } + } + return true; +} + +// Variant for world-baked nodes: their positions were pre-transformed into +// world space at fill time, so a differing world matrix no longer forces a +// run split. View and lights must still match. +static bool Render_State_Matches_Except_Stage0_Texture_And_World(const RenderStateStruct& left, const RenderStateStruct& right) +{ + if (left.shader.Get_Bits() != right.shader.Get_Bits()) + { + return false; + } + if (left.sorted_draw_flags != right.sorted_draw_flags) + { + return false; + } + if (left.material != right.material) + { + return false; + } + for (int texture_index=1; texture_indexGet_Max_Texture_Stages(); ++texture_index) + { + if (left.Textures[texture_index] != right.Textures[texture_index]) + { + return false; + } + } + if (std::memcmp(&left.view,&right.view,sizeof(left.view)) != 0) + { + return false; + } + for (int light_index=0; light_index<4; ++light_index) + { + if (left.LightEnable[light_index] != right.LightEnable[light_index]) + { + return false; + } + if (left.LightEnable[light_index] && std::memcmp(&left.Lights[light_index],&right.Lights[light_index],sizeof(left.Lights[light_index])) != 0) + { + return false; + } + } + return true; +} +// Per-flush page/layer of each overlapping node; -1 when the node cannot +// join a texture-array merged run. Rebuilt in Flush_Sorting_Pool. +static std::vector s_sortedNodeArrayPage; + +static bool Sorted_Texture_Array_Merge_Enabled() +{ + // TheSuperHackers @performance bobtista 11/07/2026 Default ON after the + // Windows benchmark verdict (2026-07-11): visual gates clean, sorted draws + // -24%, sort-pool CPU -21%, texture binds -10%, FPS +2% with lower + // variance, nothing regressed. Opt out with GGC_BGFX_NO_SORTED_TEXTURE_ARRAY + // or -bgfxNoSortedTextureArray. + static const bool enabled = !GgcFlags::Enabled(GgcFlag_BgfxNoSortedTextureArray) + && g_renderBackend->Has_Shader_Pipeline(); + return enabled; +} +static bool Sorted_Array_Dbg_Enabled() +{ + static const bool enabled = Should_Log_Sort_Effect_Diag() || GgcFlags::Enabled(GgcFlag_Trace); + return enabled; +} +static unsigned s_sortedArrayBreakMaskCounts[256] = {}; +static unsigned s_sortedArrayDbgNodes = 0; +static unsigned s_sortedArrayDbgEligible = 0; +static unsigned s_sortedArrayDbgMerges = 0; +static unsigned s_sortedArrayDbgFlushes = 0; + +enum BgfxSortedPacketBreakReason : unsigned +{ + BGFX_SORTED_PACKET_BREAK_SHADER = 0, + BGFX_SORTED_PACKET_BREAK_FLAGS, + BGFX_SORTED_PACKET_BREAK_MATERIAL, + BGFX_SORTED_PACKET_BREAK_TEXTURE, + BGFX_SORTED_PACKET_BREAK_WORLD, + BGFX_SORTED_PACKET_BREAK_VIEW, + BGFX_SORTED_PACKET_BREAK_LIGHT_ENABLE, + BGFX_SORTED_PACKET_BREAK_LIGHT_PAYLOAD, + BGFX_SORTED_PACKET_BREAK_COUNT +}; + +static unsigned Sorted_State_Break_Mask(const RenderStateStruct& left, const RenderStateStruct& right) +{ + unsigned mask = 0; + if (left.shader.Get_Bits() != right.shader.Get_Bits()) + { + mask |= 1u << BGFX_SORTED_PACKET_BREAK_SHADER; + } + if (left.sorted_draw_flags != right.sorted_draw_flags) + { + mask |= 1u << BGFX_SORTED_PACKET_BREAK_FLAGS; + } + if (left.material != right.material) + { + mask |= 1u << BGFX_SORTED_PACKET_BREAK_MATERIAL; + } + for (int texture_index = 0; texture_index < g_renderBackend->Get_Max_Texture_Stages(); ++texture_index) + { + if (left.Textures[texture_index] != right.Textures[texture_index]) + { + mask |= 1u << BGFX_SORTED_PACKET_BREAK_TEXTURE; + break; + } + } + if (std::memcmp(&left.world, &right.world, sizeof(left.world)) != 0) + { + mask |= 1u << BGFX_SORTED_PACKET_BREAK_WORLD; + } + if (std::memcmp(&left.view, &right.view, sizeof(left.view)) != 0) + { + mask |= 1u << BGFX_SORTED_PACKET_BREAK_VIEW; + } + for (int light_index = 0; light_index < 4; ++light_index) + { + if (left.LightEnable[light_index] != right.LightEnable[light_index]) + { + mask |= 1u << BGFX_SORTED_PACKET_BREAK_LIGHT_ENABLE; + continue; + } + if (left.LightEnable[light_index] + && std::memcmp(&left.Lights[light_index], &right.Lights[light_index], sizeof(left.Lights[light_index])) != 0) + { + mask |= 1u << BGFX_SORTED_PACKET_BREAK_LIGHT_PAYLOAD; + } + } + return mask; +} + +static unsigned Sorted_State_Texture_Break_Mask(const RenderStateStruct& left, const RenderStateStruct& right) +{ + unsigned mask = 0; + for (int texture_index = 0; texture_index < g_renderBackend->Get_Max_Texture_Stages(); ++texture_index) + { + if (left.Textures[texture_index] != right.Textures[texture_index]) + { + mask |= 1u << texture_index; + } + } + return mask; +} + +static const char* Sorted_Texture_Full_Path(const RenderStateStruct& state, unsigned stage) +{ + if (stage >= RB_MAX_TEXTURE_STAGES || state.Textures[stage] == nullptr) + { + return nullptr; + } + TextureClass* texture = state.Textures[stage]->As_TextureClass(); + return texture != nullptr ? texture->Get_Full_Path().str() : nullptr; } // ---------------------------------------------------------------------------- +// TheSuperHackers @performance bobtista 04/06/2026 Coalesce z-scattered +// additive sorted triangles that share render state into contiguous runs so +// Flush_Sorting_Pool emits one draw per state instead of one draw per +// depth-interleaved run. Additive blend (SRCBLEND_ONE/DSTBLEND_ONE) with depth +// writes disabled is order-independent for non-negative fragments even under +// the per-fragment [0,1] clamp, so reordering additive triangles within a run +// bounded by any non-additive (barrier) triangle is pixel-identical. Only the +// shader pipeline runs this; GGC_NO_SORT_COALESCE reverts to the byte-identical +// z-only path and the DX8 backend never enters it. + +static bool Sort_Coalesce_Disabled() +{ + static const bool disabled = GgcFlags::Enabled(GgcFlag_NoSortCoalesce); + return disabled; +} -void SortingRendererClass::Flush_Sorting_Pool() +static inline bool Is_Coalescable_Additive(const RenderStateStruct& rs) { - if (!overlapping_node_count) return; + return rs.shader.Get_Depth_Mask() == ShaderClass::DEPTH_WRITE_DISABLE + && rs.shader.Get_Src_Blend_Func() == ShaderClass::SRCBLEND_ONE + && rs.shader.Get_Dst_Blend_Func() == ShaderClass::DSTBLEND_ONE; +} - SNAPSHOT_SAY(("SortingSystem - Flush")); +static inline unsigned long long Sort_Fnv1a(const void* data, unsigned len, unsigned long long hash) +{ + const unsigned char* p = static_cast(data); + for (unsigned i = 0; i < len; ++i) + { + hash ^= p[i]; + hash *= 1099511628211ULL; + } + return hash; +} - // Fill dynamic index buffer with sorting index buffer vertices - TempIndexStruct* tis=Get_Temp_Index_Array(overlapping_polygon_count); +// Cheap key for grouping; equal state always yields an equal key. A key +// collision between differing states only places them adjacently - the draw +// loop still splits them with the exact Render_State_Matches test, so the +// reorder can never merge states that do not truly match. +static unsigned long long Sort_Node_State_Key(const RenderStateStruct& rs) +{ + unsigned long long hash = 1469598103934665603ULL; + const unsigned int shaderBits = rs.shader.Get_Bits(); + hash = Sort_Fnv1a(&shaderBits, sizeof(shaderBits), hash); + hash = Sort_Fnv1a(&rs.sorted_draw_flags, sizeof(rs.sorted_draw_flags), hash); + const void* material = rs.material; + hash = Sort_Fnv1a(&material, sizeof(material), hash); + for (int texture_index = 0; texture_index < g_renderBackend->Get_Max_Texture_Stages(); ++texture_index) + { + const void* texture = rs.Textures[texture_index]; + hash = Sort_Fnv1a(&texture, sizeof(texture), hash); + } + hash = Sort_Fnv1a(&rs.world, sizeof(rs.world), hash); + return hash; +} - unsigned vertexAllocCount = overlapping_vertex_count; - if (DynamicVBAccessClass::Get_Default_Vertex_Count() < DEFAULT_SORTING_VERTEX_COUNT) - vertexAllocCount = DEFAULT_SORTING_VERTEX_COUNT; //make sure that we force the DX8 dynamic vertex buffer to maximum size - if (overlapping_vertex_count > vertexAllocCount) - vertexAllocCount = overlapping_vertex_count; - WWASSERT(DEFAULT_SORTING_VERTEX_COUNT == 1 || vertexAllocCount <= DEFAULT_SORTING_VERTEX_COUNT); - DynamicVBAccessClass dyn_vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,vertexAllocCount/*overlapping_vertex_count*/); +static unsigned Count_Sorted_Runs(const TempIndexStruct* tis, unsigned tri_count) +{ + if (tri_count == 0) + { + return 0; + } + unsigned runs = 1; + SortingNodeStruct* state = overlapping_nodes[tis[0].idx]; + for (unsigned i = 1; i < tri_count; ++i) { - DynamicVBAccessClass::WriteLockClass lock(&dyn_vb_access); - VertexFormatXYZNDUV2* dest_verts=(VertexFormatXYZNDUV2 *)lock.Get_Formatted_Vertex_Array(); + SortingNodeStruct* next_state = overlapping_nodes[tis[i].idx]; + if (!Render_State_Matches(state->sorting_state, next_state->sorting_state)) + { + ++runs; + state = next_state; + } + } + return runs; +} - unsigned polygon_array_offset=0; - unsigned vertex_array_offset=0; - for (unsigned node_id=0;node_id(state->sorting_state.vertex_buffers[0]); - WWASSERT(vertex_buffer); - src_verts=vertex_buffer->VertexBuffer; - WWASSERT(src_verts); - src_verts+=state->sorting_state.vba_offset; - src_verts+=state->sorting_state.index_base_offset; - src_verts+=state->min_vertex_index; - - // If you have a crash in here and "dest_verts" points to illegal memory area, - // it is because D3D is in illegal state, and the only known cure is rebooting. - // This illegal state is usually caused by Quake3-engine powered games such as MOHAA. - memcpy(dest_verts, src_verts, sizeof(VertexFormatXYZNDUV2)*state->vertex_count); - dest_verts += state->vertex_count; - - D3DXMATRIX d3d_mtx=(D3DXMATRIX&)state->sorting_state.world*(D3DXMATRIX&)state->sorting_state.view; - const Matrix4x4& mtx=(const Matrix4x4&)d3d_mtx; - - unsigned short* indices=nullptr; - SortingIndexBufferClass* index_buffer=static_cast(state->sorting_state.index_buffer); - WWASSERT(index_buffer); - indices=index_buffer->index_buffer; - WWASSERT(indices); - indices+=state->start_index; - indices+=state->sorting_state.iba_offset; - - if (mtx[0][2] == 0.0f && mtx[1][2] == 0.0f && mtx[3][2] == 0.0f && mtx[2][2] == 1.0f) { - // The common case for particle systems. - for (int i=0;ipolygon_count;++i) { - unsigned short idx1=indices[i*3]-state->min_vertex_index; - unsigned short idx2=indices[i*3+1]-state->min_vertex_index; - unsigned short idx3=indices[i*3+2]-state->min_vertex_index; - WWASSERT(idx1vertex_count); - WWASSERT(idx2vertex_count); - WWASSERT(idx3vertex_count); - const VertexFormatXYZNDUV2 *v1 = src_verts + idx1; - const VertexFormatXYZNDUV2 *v2 = src_verts + idx2; - const VertexFormatXYZNDUV2 *v3 = src_verts + idx3; - unsigned array_index=i+polygon_array_offset; - WWASSERT(array_indextri.i = idx1 + vertex_array_offset; - tis_ptr->tri.j = idx2 + vertex_array_offset; - tis_ptr->tri.k = idx3 + vertex_array_offset; - tis_ptr->idx = node_id; - tis_ptr->z = (v1->z + v2->z + v3->z)/3.0f; - DEBUG_ASSERTCRASH((! _isnan(tis_ptr->z) && _finite(tis_ptr->z)), ("Triangle has invalid center")); +static unsigned long long* sorted_packet_diag_segment_keys; +static unsigned sorted_packet_diag_segment_key_count; + +static void Emit_Sorted_Packet_Collector_Diag(const TempIndexStruct* tis, unsigned tri_count, unsigned draw_runs) +{ + if (!g_renderBackend->Has_Shader_Pipeline() || !Sorted_Packet_Collector_Diag_Enabled()) + { + return; + } + + static unsigned flush_count = 0; + if (flush_count >= Sorted_Packet_Collector_Diag_Max_Flushes()) + { + return; + } + + if (tri_count > sorted_packet_diag_segment_key_count) + { + delete[] sorted_packet_diag_segment_keys; + sorted_packet_diag_segment_key_count = tri_count; + sorted_packet_diag_segment_keys = W3DNEWARRAY unsigned long long[sorted_packet_diag_segment_key_count]; + } + + unsigned candidate_tris = 0; + unsigned fallback_tris = 0; + unsigned additive_segments = 0; + unsigned largest_additive_window = 0; + unsigned estimated_grouped_submits = 0; + unsigned largest_fallback_window = 0; + unsigned estimated_fallback_state_keys = 0; + unsigned barrier_count = 0; + unsigned fallback_packets = 0; + unsigned source_pointgroup_tris = 0; + unsigned source_streak_tris = 0; + unsigned source_other_tris = 0; + unsigned candidate_draw_runs = 0; + unsigned fallback_draw_runs = 0; + unsigned run_break_candidate_to_candidate = 0; + unsigned run_break_fallback_to_fallback = 0; + unsigned run_break_mixed_candidate_fallback = 0; + unsigned run_break_reasons[BGFX_SORTED_PACKET_BREAK_COUNT] = {}; + unsigned texture_stage_breaks[RB_MAX_TEXTURE_STAGES] = {}; + unsigned texture0_same_name_breaks = 0; + unsigned fallback_reasons[BGFX_SORTED_PACKET_FALLBACK_UNKNOWN + 1] = {}; + unsigned fallback_blend_tris[BGFX_SORTED_PACKET_BLEND_COUNT] = {}; + unsigned pointgroup_fallback_blend_tris[BGFX_SORTED_PACKET_BLEND_COUNT] = {}; + + for (unsigned a = 0; a < tri_count; ++a) + { + SortingNodeStruct* node = overlapping_nodes[tis[a].idx]; + switch (node->packet_diag_source) + { + case BGFX_SORTED_PACKET_SOURCE_POINT_GROUP: ++source_pointgroup_tris; break; + case BGFX_SORTED_PACKET_SOURCE_STREAK: ++source_streak_tris; break; + default: ++source_other_tris; break; + } + if (node->packet_diag_fallback != BGFX_SORTED_PACKET_FALLBACK_NONE) + { + const unsigned reason = node->packet_diag_fallback <= BGFX_SORTED_PACKET_FALLBACK_UNKNOWN + ? node->packet_diag_fallback + : BGFX_SORTED_PACKET_FALLBACK_UNKNOWN; + ++fallback_reasons[reason]; + const BgfxSortedPacketDiagBlendClass blendClass = Classify_Sorted_Packet_Diag_Blend(node->sorting_state); + ++fallback_blend_tris[blendClass]; + if (node->packet_diag_source == BGFX_SORTED_PACKET_SOURCE_POINT_GROUP) + { + ++pointgroup_fallback_blend_tris[blendClass]; + } + } + } + + if (tri_count > 0) + { + SortingNodeStruct* run_node = overlapping_nodes[tis[0].idx]; + for (unsigned a = 1; a < tri_count; ++a) + { + SortingNodeStruct* next_node = overlapping_nodes[tis[a].idx]; + const unsigned break_mask = Sorted_State_Break_Mask(run_node->sorting_state, next_node->sorting_state); + if (break_mask != 0) + { + for (unsigned reason = 0; reason < BGFX_SORTED_PACKET_BREAK_COUNT; ++reason) + { + if ((break_mask & (1u << reason)) != 0) + { + ++run_break_reasons[reason]; + } + } + const unsigned texture_stage_mask = Sorted_State_Texture_Break_Mask(run_node->sorting_state, next_node->sorting_state); + for (unsigned stage = 0; stage < RB_MAX_TEXTURE_STAGES; ++stage) + { + if ((texture_stage_mask & (1u << stage)) != 0) + { + ++texture_stage_breaks[stage]; + } + } + if ((texture_stage_mask & 1u) != 0) + { + const char* left_name = Sorted_Texture_Full_Path(run_node->sorting_state, 0); + const char* right_name = Sorted_Texture_Full_Path(next_node->sorting_state, 0); + if (left_name != nullptr && right_name != nullptr && stricmp(left_name, right_name) == 0) + { + ++texture0_same_name_breaks; + } + } + const bool run_candidate = run_node->packet_diag_fallback == BGFX_SORTED_PACKET_FALLBACK_NONE; + const bool next_candidate = next_node->packet_diag_fallback == BGFX_SORTED_PACKET_FALLBACK_NONE; + if (run_candidate && next_candidate) + { + ++run_break_candidate_to_candidate; + } + else if (!run_candidate && !next_candidate) + { + ++run_break_fallback_to_fallback; } - } else { - for (int i=0;ipolygon_count;++i) { - unsigned short idx1=indices[i*3]-state->min_vertex_index; - unsigned short idx2=indices[i*3+1]-state->min_vertex_index; - unsigned short idx3=indices[i*3+2]-state->min_vertex_index; - WWASSERT(idx1vertex_count); - WWASSERT(idx2vertex_count); - WWASSERT(idx3vertex_count); - const VertexFormatXYZNDUV2 *v1 = src_verts + idx1; - const VertexFormatXYZNDUV2 *v2 = src_verts + idx2; - const VertexFormatXYZNDUV2 *v3 = src_verts + idx3; - unsigned array_index=i+polygon_array_offset; - WWASSERT(array_indextri.i = idx1 + vertex_array_offset; - tis_ptr->tri.j = idx2 + vertex_array_offset; - tis_ptr->tri.k = idx3 + vertex_array_offset; - tis_ptr->idx = node_id; - tis_ptr->z = (mtx[0][2]*(v1->x + v2->x + v3->x) + - mtx[1][2]*(v1->y + v2->y + v3->y) + - mtx[2][2]*(v1->z + v2->z + v3->z))/3.0f + mtx[3][2]; - DEBUG_ASSERTCRASH((! _isnan(tis_ptr->z) && _finite(tis_ptr->z)), ("Triangle has invalid center")); + else + { + ++run_break_mixed_candidate_fallback; + } + if (run_node->packet_diag_fallback == BGFX_SORTED_PACKET_FALLBACK_NONE) + { + ++candidate_draw_runs; + } + else + { + ++fallback_draw_runs; + } + run_node = next_node; + } + } + if (run_node->packet_diag_fallback == BGFX_SORTED_PACKET_FALLBACK_NONE) + { + ++candidate_draw_runs; + } + else + { + ++fallback_draw_runs; + } + } + + unsigned i = 0; + while (i < tri_count) + { + SortingNodeStruct* node = overlapping_nodes[tis[i].idx]; + + if (node->packet_diag_fallback == BGFX_SORTED_PACKET_FALLBACK_NONE) + { + const unsigned segment_start = i; + do + { + ++i; + } + while (i < tri_count && overlapping_nodes[tis[i].idx]->packet_diag_fallback == BGFX_SORTED_PACKET_FALLBACK_NONE); + + const unsigned segment_end = i; + const unsigned segment_length = segment_end - segment_start; + candidate_tris += segment_length; + ++additive_segments; + largest_additive_window = std::max(largest_additive_window, segment_length); + + unsigned unique_keys = 0; + for (unsigned a = segment_start; a < segment_end; ++a) + { + const unsigned long long key = Sort_Node_State_Key(overlapping_nodes[tis[a].idx]->sorting_state); + bool found = false; + for (unsigned k = 0; k < unique_keys; ++k) + { + if (sorted_packet_diag_segment_keys[k] == key) + { + found = true; + break; + } + } + if (!found) + { + sorted_packet_diag_segment_keys[unique_keys++] = key; } } + estimated_grouped_submits += unique_keys; + continue; + } - state->min_vertex_index=vertex_array_offset; + ++barrier_count; + ++fallback_packets; + const unsigned segment_start = i; + while (i < tri_count) + { + SortingNodeStruct* fallback_node = overlapping_nodes[tis[i].idx]; + ++fallback_tris; + ++i; + if (i >= tri_count || overlapping_nodes[tis[i].idx]->packet_diag_fallback == BGFX_SORTED_PACKET_FALLBACK_NONE) + { + break; + } + } + const unsigned segment_end = i; + const unsigned segment_length = segment_end - segment_start; + largest_fallback_window = std::max(largest_fallback_window, segment_length); - polygon_array_offset+=state->polygon_count; - vertex_array_offset+=state->vertex_count; + unsigned unique_keys = 0; + for (unsigned a = segment_start; a < segment_end; ++a) + { + const unsigned long long key = Sort_Node_State_Key(overlapping_nodes[tis[a].idx]->sorting_state); + bool found = false; + for (unsigned k = 0; k < unique_keys; ++k) + { + if (sorted_packet_diag_segment_keys[k] == key) + { + found = true; + break; + } + } + if (!found) + { + sorted_packet_diag_segment_keys[unique_keys++] = key; + } } + estimated_fallback_state_keys += unique_keys; } - Sort(tis, tis + overlapping_polygon_count); + if (FILE* diag = std::fopen("ggc_sorted_packet_collector_diag.txt", "a")) + { + std::fprintf(diag, + "flush=%u nodes=%u tris=%u draw_runs=%u candidate_draw_runs=%u fallback_draw_runs=%u run_break_candidate_candidate=%u run_break_fallback_fallback=%u run_break_mixed=%u break_shader=%u break_flags=%u break_material=%u break_texture=%u break_texture0=%u break_texture1=%u break_texture2=%u break_texture3=%u break_texture0_same_name=%u break_world=%u break_view=%u break_light_enable=%u break_light_payload=%u candidate_packets=%u fallback_packets=%u barriers=%u candidate_tris=%u fallback_tris=%u additive_segments=%u largest_additive_window=%u estimated_grouped_submits=%u largest_fallback_window=%u estimated_fallback_state_keys=%u pointgroup_tris=%u streak_tris=%u other_tris=%u fallback_depth_write=%u fallback_alpha_test=%u fallback_non_additive_blend=%u fallback_unknown=%u fallback_blend_alpha=%u fallback_blend_multiply=%u fallback_blend_screen=%u fallback_blend_src_alpha_one=%u fallback_blend_other=%u pointgroup_fallback_blend_alpha=%u pointgroup_fallback_blend_multiply=%u pointgroup_fallback_blend_screen=%u pointgroup_fallback_blend_src_alpha_one=%u pointgroup_fallback_blend_other=%u\n", + ++flush_count, + overlapping_node_count, + tri_count, + draw_runs, + candidate_draw_runs, + fallback_draw_runs, + run_break_candidate_to_candidate, + run_break_fallback_to_fallback, + run_break_mixed_candidate_fallback, + run_break_reasons[BGFX_SORTED_PACKET_BREAK_SHADER], + run_break_reasons[BGFX_SORTED_PACKET_BREAK_FLAGS], + run_break_reasons[BGFX_SORTED_PACKET_BREAK_MATERIAL], + run_break_reasons[BGFX_SORTED_PACKET_BREAK_TEXTURE], + texture_stage_breaks[0], + texture_stage_breaks[1], + texture_stage_breaks[2], + texture_stage_breaks[3], + texture0_same_name_breaks, + run_break_reasons[BGFX_SORTED_PACKET_BREAK_WORLD], + run_break_reasons[BGFX_SORTED_PACKET_BREAK_VIEW], + run_break_reasons[BGFX_SORTED_PACKET_BREAK_LIGHT_ENABLE], + run_break_reasons[BGFX_SORTED_PACKET_BREAK_LIGHT_PAYLOAD], + estimated_grouped_submits, + fallback_packets, + barrier_count, + candidate_tris, + fallback_tris, + additive_segments, + largest_additive_window, + estimated_grouped_submits, + largest_fallback_window, + estimated_fallback_state_keys, + source_pointgroup_tris, + source_streak_tris, + source_other_tris, + fallback_reasons[BGFX_SORTED_PACKET_FALLBACK_DEPTH_WRITE], + fallback_reasons[BGFX_SORTED_PACKET_FALLBACK_ALPHA_TEST], + fallback_reasons[BGFX_SORTED_PACKET_FALLBACK_NON_ADDITIVE_BLEND], + fallback_reasons[BGFX_SORTED_PACKET_FALLBACK_UNKNOWN], + fallback_blend_tris[BGFX_SORTED_PACKET_BLEND_ALPHA], + fallback_blend_tris[BGFX_SORTED_PACKET_BLEND_MULTIPLY], + fallback_blend_tris[BGFX_SORTED_PACKET_BLEND_SCREEN], + fallback_blend_tris[BGFX_SORTED_PACKET_BLEND_SRC_ALPHA_ONE], + fallback_blend_tris[BGFX_SORTED_PACKET_BLEND_OTHER], + pointgroup_fallback_blend_tris[BGFX_SORTED_PACKET_BLEND_ALPHA], + pointgroup_fallback_blend_tris[BGFX_SORTED_PACKET_BLEND_MULTIPLY], + pointgroup_fallback_blend_tris[BGFX_SORTED_PACKET_BLEND_SCREEN], + pointgroup_fallback_blend_tris[BGFX_SORTED_PACKET_BLEND_SRC_ALPHA_ONE], + pointgroup_fallback_blend_tris[BGFX_SORTED_PACKET_BLEND_OTHER]); + std::fclose(diag); + } +} + +static TempIndexStruct* coalesce_scratch; +static unsigned coalesce_scratch_count; +static bool* coalesce_consumed; +static unsigned coalesce_consumed_count; +static unsigned long long* coalesce_node_key; +static bool* coalesce_node_add; +static unsigned coalesce_node_capacity; + +static void Coalesce_Sorted_Pool(TempIndexStruct* tis, unsigned tri_count) +{ + if (tri_count < 2) + { + return; + } + + if (overlapping_node_count > coalesce_node_capacity) + { + delete[] coalesce_node_key; + delete[] coalesce_node_add; + coalesce_node_capacity = overlapping_node_count; + coalesce_node_key = W3DNEWARRAY unsigned long long[coalesce_node_capacity]; + coalesce_node_add = W3DNEWARRAY bool[coalesce_node_capacity]; + } + for (unsigned node_id = 0; node_id < overlapping_node_count; ++node_id) + { + const RenderStateStruct& rs = overlapping_nodes[node_id]->sorting_state; + coalesce_node_add[node_id] = Is_Coalescable_Additive(rs); + coalesce_node_key[node_id] = coalesce_node_add[node_id] ? Sort_Node_State_Key(rs) : 0ULL; + } + + if (tri_count > coalesce_scratch_count) + { + delete[] coalesce_scratch; + coalesce_scratch_count = tri_count; + coalesce_scratch = W3DNEWARRAY TempIndexStruct[coalesce_scratch_count]; + } + if (tri_count > coalesce_consumed_count) + { + delete[] coalesce_consumed; + coalesce_consumed_count = tri_count; + coalesce_consumed = W3DNEWARRAY bool[coalesce_consumed_count]; + } - // 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; - unsigned chunkOffset = 0; - while (chunkOffset < overlapping_polygon_count) + unsigned i = 0; + while (i < tri_count) { - unsigned chunkCount = overlapping_polygon_count - chunkOffset; - if (chunkCount * 3 > MAX_INDEX_CHUNK) { - chunkCount = MAX_INDEX_CHUNK / 3; + if (!coalesce_node_add[tis[i].idx]) + { + ++i; + continue; } - const unsigned chunkEnd = chunkOffset + chunkCount; - DynamicIBAccessClass dyn_ib_access(BUFFER_TYPE_DYNAMIC_DX8,chunkCount*3); + // Maximal segment of consecutive coalescable additive triangles. + unsigned segment_end = i; + while (segment_end < tri_count && coalesce_node_add[tis[segment_end].idx]) { - DynamicIBAccessClass::WriteLockClass lock(&dyn_ib_access); - ShortVectorIStruct* sorted_polygon_index_array=(ShortVectorIStruct*)lock.Get_Index_Array(); + ++segment_end; + } - for (unsigned a=0;aHas_Shader_Pipeline()) + { + bool submitted = false; + if (Sorted_Batch_State_Packet_Cache_Disabled()) + { + submitted = g_renderBackend->Submit_Sorted_Packet( + Make_Render_Backend_Sorted_State(state->sorting_state), + start_index*3, count_to_render, overlapping_vertex_count, array_page); } + else + { + submitted = g_renderBackend->Submit_Sorted_Packet( + state->render_backend_state, + start_index*3, count_to_render, overlapping_vertex_count, array_page); + } + if (submitted) + { + Log_Sort_Effect_Diag("draw-run", start_index, count_to_render, state); + return; + } + } + if (array_page >= 0) + { + g_renderBackend->Set_Sorted_Texture_Array_Page(array_page); + } + Apply_Render_State(state); + if (!g_renderBackend->Has_Shader_Pipeline()) + { + g_renderBackend->Set_Index_Buffer(dyn_ib_access, 0); + g_renderBackend->Set_Vertex_Buffer(dyn_vb_access); + } + Log_Sort_Effect_Diag("draw-run", start_index, count_to_render, state); + + g_renderBackend->Draw_Triangles( + start_index*3, + count_to_render, + 0, + overlapping_vertex_count); + if (array_page >= 0) + { + g_renderBackend->Set_Sorted_Texture_Array_Page(-1); + } +} + +// ---------------------------------------------------------------------------- - // Set index buffer and render! +void SortingRendererClass::Flush_Sorting_Pool() +{ + if (!overlapping_node_count) return; + Log_Sort_Effect_Diag("flush-start", 0, overlapping_polygon_count, overlapping_nodes[0]); - 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!) + SNAPSHOT_SAY(("SortingSystem - Flush")); - DX8Wrapper::Apply_Render_State_Changes(); + // Fill dynamic index buffer with sorting index buffer vertices + TempIndexStruct* tis=Get_Temp_Index_Array(overlapping_polygon_count); - unsigned count_to_render=1; - unsigned start_index=0; - unsigned node_id=tis[chunkOffset].idx; - for (unsigned i=chunkOffset + 1;iHas_Shader_Pipeline()) + { + if (DynamicVBAccessClass::Get_Default_Vertex_Count() < DEFAULT_SORTING_VERTEX_COUNT) + vertexAllocCount = DEFAULT_SORTING_VERTEX_COUNT; //make sure that we force the DX8 dynamic vertex buffer to maximum size + if (overlapping_vertex_count > vertexAllocCount) + vertexAllocCount = overlapping_vertex_count; + WWASSERT(DEFAULT_SORTING_VERTEX_COUNT == 1 || vertexAllocCount <= DEFAULT_SORTING_VERTEX_COUNT); + } + DynamicVBAccessClass dyn_vb_access(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,vertexAllocCount/*overlapping_vertex_count*/); + GGCRenderProfile::Begin(GGCRenderProfile::SORT_POOL_BUILD); + { + DynamicVBAccessClass::WriteLockClass lock(&dyn_vb_access); + VertexFormatXYZNDUV2* dest_verts=(VertexFormatXYZNDUV2 *)lock.Get_Formatted_Vertex_Array(); + + unsigned polygon_array_offset=0; + unsigned vertex_array_offset=0; + const bool sorted_array_merge = Sorted_Texture_Array_Merge_Enabled(); + if (sorted_array_merge) + { + s_sortedNodeArrayPage.assign(overlapping_node_count, -1); + } + for (unsigned node_id=0;node_idsorting_state); + VertexFormatXYZNDUV2* src_verts=nullptr; + SortingVertexBufferClass* vertex_buffer=static_cast(state->sorting_state.vertex_buffers[0]); + WWASSERT(vertex_buffer); + src_verts=vertex_buffer->VertexBuffer; + WWASSERT(src_verts); + src_verts+=state->sorting_state.vba_offset; + src_verts+=state->sorting_state.index_base_offset; + src_verts+=state->min_vertex_index; + + // If you have a crash in here and "dest_verts" points to illegal memory area, + // it is because D3D is in illegal state, and the only known cure is rebooting. + // This illegal state is usually caused by Quake3-engine powered games such as MOHAA. + memcpy(dest_verts, src_verts, sizeof(VertexFormatXYZNDUV2)*state->vertex_count); + if (sorted_array_merge) + { + // Eligibility and the page slot were resolved by the backend at + // capture time, when the live translated draw state was + // authoritative; the fill only adds the data-dependent UV check. + int arrayPage = state->sorting_state.sorted_array_page; + const int arrayLayer = state->sorting_state.sorted_array_layer; + const float arrayScaleU = state->sorting_state.sorted_array_scale_u; + const float arrayScaleV = state->sorting_state.sorted_array_scale_v; + if (arrayPage >= 0) + { + // Page layers occupy a sub-region of a shared texture, so + // wrap addressing is unrepresentable there: tiled UVs must + // stay on the classic per-texture path. + for (unsigned checked_vertex = 0; checked_vertex < state->vertex_count; ++checked_vertex) + { + const float cu = dest_verts[checked_vertex].u1; + const float cv = dest_verts[checked_vertex].v1; + if (cu < -0.0001f || cu > 1.0001f || cv < -0.0001f || cv > 1.0001f) + { + arrayPage = -1; + break; + } + } + } + if (arrayPage >= 0) + { + // Second UV channel = page-region UVs, normal z = layer. + // Both are dead data for the eligible unlit single-stage + // profile, and only the array program reads them. + // Positions are baked into world space (row-vector + // convention, matching the z path above) so runs from + // different emitters can share one identity-world draw; + // the backend applies the view only for these runs. + const Matrix4x4& bakeWorld = reinterpret_cast(state->sorting_state.world); + const float layerCoord = static_cast(arrayLayer) + 0.5f; + for (unsigned stamped_vertex = 0; stamped_vertex < state->vertex_count; ++stamped_vertex) + { + VertexFormatXYZNDUV2 & baked = dest_verts[stamped_vertex]; + const float bx = baked.x; + const float by = baked.y; + const float bz = baked.z; + baked.x = bx * bakeWorld[0][0] + by * bakeWorld[1][0] + bz * bakeWorld[2][0] + bakeWorld[3][0]; + baked.y = bx * bakeWorld[0][1] + by * bakeWorld[1][1] + bz * bakeWorld[2][1] + bakeWorld[3][1]; + baked.z = bx * bakeWorld[0][2] + by * bakeWorld[1][2] + bz * bakeWorld[2][2] + bakeWorld[3][2]; + baked.u2 = baked.u1 * arrayScaleU; + baked.v2 = baked.v1 * arrayScaleV; + baked.nz = layerCoord; + } + } + s_sortedNodeArrayPage[node_id] = arrayPage; + if (Sorted_Array_Dbg_Enabled()) + { + ++s_sortedArrayDbgNodes; + if (arrayPage >= 0) + { + ++s_sortedArrayDbgEligible; + } + } + } + dest_verts += state->vertex_count; + + const Matrix4x4 mtx = Get_Sorted_World_View_Matrix(state->sorting_state); + + unsigned short* indices=nullptr; + SortingIndexBufferClass* index_buffer=static_cast(state->sorting_state.index_buffer); + WWASSERT(index_buffer); + indices=index_buffer->index_buffer; + WWASSERT(indices); + indices+=state->start_index; + indices+=state->sorting_state.iba_offset; + + if (mtx[0][2] == 0.0f && mtx[1][2] == 0.0f && mtx[3][2] == 0.0f && mtx[2][2] == 1.0f) { + // The common case for particle systems. + for (int i=0;ipolygon_count;++i) { + unsigned short idx1=indices[i*3]-state->min_vertex_index; + unsigned short idx2=indices[i*3+1]-state->min_vertex_index; + unsigned short idx3=indices[i*3+2]-state->min_vertex_index; + WWASSERT(idx1vertex_count); + WWASSERT(idx2vertex_count); + WWASSERT(idx3vertex_count); + const VertexFormatXYZNDUV2 *v1 = src_verts + idx1; + const VertexFormatXYZNDUV2 *v2 = src_verts + idx2; + const VertexFormatXYZNDUV2 *v3 = src_verts + idx3; + unsigned array_index=i+polygon_array_offset; + WWASSERT(array_indextri.i = idx1 + vertex_array_offset; + tis_ptr->tri.j = idx2 + vertex_array_offset; + tis_ptr->tri.k = idx3 + vertex_array_offset; + tis_ptr->idx = node_id; + tis_ptr->z = (v1->z + v2->z + v3->z)/3.0f; + DEBUG_ASSERTCRASH((! _isnan(tis_ptr->z) && _finite(tis_ptr->z)), ("Triangle has invalid center")); + } + } else { + for (int i=0;ipolygon_count;++i) { + unsigned short idx1=indices[i*3]-state->min_vertex_index; + unsigned short idx2=indices[i*3+1]-state->min_vertex_index; + unsigned short idx3=indices[i*3+2]-state->min_vertex_index; + WWASSERT(idx1vertex_count); + WWASSERT(idx2vertex_count); + WWASSERT(idx3vertex_count); + const VertexFormatXYZNDUV2 *v1 = src_verts + idx1; + const VertexFormatXYZNDUV2 *v2 = src_verts + idx2; + const VertexFormatXYZNDUV2 *v3 = src_verts + idx3; + unsigned array_index=i+polygon_array_offset; + WWASSERT(array_indextri.i = idx1 + vertex_array_offset; + tis_ptr->tri.j = idx2 + vertex_array_offset; + tis_ptr->tri.k = idx3 + vertex_array_offset; + tis_ptr->idx = node_id; + tis_ptr->z = (mtx[0][2]*(v1->x + v2->x + v3->x) + + mtx[1][2]*(v1->y + v2->y + v3->y) + + mtx[2][2]*(v1->z + v2->z + v3->z))/3.0f + mtx[3][2]; + DEBUG_ASSERTCRASH((! _isnan(tis_ptr->z) && _finite(tis_ptr->z)), ("Triangle has invalid center")); + } + } - DX8Wrapper::Draw_Triangles( - start_index*3, - count_to_render, - state->min_vertex_index, - state->vertex_count); + state->min_vertex_index=vertex_array_offset; - count_to_render=0; - start_index=i - chunkOffset; - node_id=tis[i].idx; + polygon_array_offset+=state->polygon_count; + vertex_array_offset+=state->vertex_count; } - count_to_render++; //keep track of number of polygons of same kind } + GGCRenderProfile::End(GGCRenderProfile::SORT_POOL_BUILD); - // Render any remaining polygons... - if (count_to_render) { - SortingNodeStruct* state=overlapping_nodes[node_id]; - Apply_Render_State(state->sorting_state); - - DX8Wrapper::Draw_Triangles( - start_index*3, - count_to_render, - state->min_vertex_index, - state->vertex_count); + { + GGC_RPROFILE(SORT_POOL_SORT); + Sort(tis, tis + overlapping_polygon_count); + + if (g_renderBackend->Has_Shader_Pipeline() && !Sort_Coalesce_Disabled()) + { + const bool diag = Should_Log_Sort_Effect_Diag(); + const unsigned runs_before = diag ? Count_Sorted_Runs(tis, overlapping_polygon_count) : 0; + Coalesce_Sorted_Pool(tis, overlapping_polygon_count); + if (diag) + { + const unsigned runs_after = Count_Sorted_Runs(tis, overlapping_polygon_count); + if (FILE* diagFile = std::fopen("ggc_sort_effect_diag.txt", "a")) + { + std::fprintf(diagFile, + "coalesce nodes=%u tris=%u runs_before=%u runs_after=%u saved=%d\n", + overlapping_node_count, overlapping_polygon_count, runs_before, runs_after, + (int)runs_before - (int)runs_after); + std::fclose(diagFile); + } + } + } + if (g_renderBackend->Has_Shader_Pipeline() && Sorted_Packet_Collector_Diag_Enabled()) + { + Emit_Sorted_Packet_Collector_Diag(tis, overlapping_polygon_count, Count_Sorted_Runs(tis, overlapping_polygon_count)); + } } - chunkOffset += chunkCount; + { + GGC_RPROFILE(SORT_POOL_DRAW); + // 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) + { + unsigned chunkCount = overlapping_polygon_count - chunkOffset; + if (chunkCount * 3 > MAX_INDEX_CHUNK) { + chunkCount = MAX_INDEX_CHUNK / 3; + } + const unsigned chunkEnd = chunkOffset + chunkCount; + + DynamicIBAccessClass dyn_ib_access(BUFFER_TYPE_DYNAMIC,chunkCount*3); + { + DynamicIBAccessClass::WriteLockClass lock(&dyn_ib_access); + ShortVectorIStruct* sorted_polygon_index_array=(ShortVectorIStruct*)lock.Get_Index_Array(); + + for (unsigned a=0;aSet_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!) + Log_Sort_Effect_Diag("buffers-set", 0, overlapping_polygon_count, overlapping_nodes[0]); + + g_renderBackend->Apply_Render_State_Changes(); + + unsigned count_to_render=1; + unsigned start_index=0; + SortingNodeStruct* state=overlapping_nodes[tis[chunkOffset].idx]; + const bool sorted_array_merge = Sorted_Texture_Array_Merge_Enabled(); + int run_array_page = sorted_array_merge ? s_sortedNodeArrayPage[tis[chunkOffset].idx] : -1; + for (unsigned i=chunkOffset + 1;isorting_state,next_state->sorting_state) + && next_node_page == run_array_page + && Sorted_Material_Snapshots_Match(state->render_backend_state.material_snapshot, next_state->render_backend_state.material_snapshot); + if (!plain_merge_ok) + { + // Baked runs (page >= 0) accumulate across boundaries that + // differ only by stage-0 texture and/or world: the z order + // is untouched, the per-vertex layer picks each triangle's + // texture, and positions were baked to world space at fill. + const int next_array_page = sorted_array_merge ? s_sortedNodeArrayPage[tis[i].idx] : -1; + if (run_array_page >= 0 + && next_array_page == run_array_page + && Render_State_Matches_Except_Stage0_Texture_And_World(state->sorting_state, next_state->sorting_state) + && Sorted_Material_Snapshots_Match(state->render_backend_state.material_snapshot, next_state->render_backend_state.material_snapshot)) + { + state=next_state; + if (Sorted_Array_Dbg_Enabled()) + { + ++s_sortedArrayDbgMerges; + } + } + else + { + if (run_array_page >= 0 && next_array_page == run_array_page) + { + static const bool s_traceBreaks = GgcFlags::Enabled(GgcFlag_Trace); + if (s_traceBreaks) + { + const unsigned mask = Sorted_State_Break_Mask(state->sorting_state, next_state->sorting_state) & 0xFF; + ++s_sortedArrayBreakMaskCounts[mask]; + } + } + // Eligible runs always draw through the array path, merged + // or not: their vertices are world-baked, so the normal + // captured-world transform would double-transform them. + Draw_Sorted_Run(start_index,count_to_render,state,dyn_vb_access,dyn_ib_access, + run_array_page); + + count_to_render=0; + start_index=i - chunkOffset; + state=next_state; + run_array_page = next_array_page; + } + } + count_to_render++; //keep track of number of polygons of same kind + } + + // Render any remaining polygons... + if (count_to_render) { + Draw_Sorted_Run(start_index,count_to_render,state,dyn_vb_access,dyn_ib_access, + run_array_page); + } + + chunkOffset += chunkCount; + } + + // sort flush complete, resume routing bgfx submits + // through the main engine view id. + g_renderBackend->End_Sorted_Batch_Pass(); + if (Sorted_Array_Dbg_Enabled()) + { + if ((++s_sortedArrayDbgFlushes % 200) == 0) + { + std::fprintf(stderr, + "[ggc] sorted-array dbg: flushes=%u nodes=%u eligible=%u merges=%u breaks:", + s_sortedArrayDbgFlushes, s_sortedArrayDbgNodes, + s_sortedArrayDbgEligible, s_sortedArrayDbgMerges); + for (unsigned mask = 0; mask < 256; ++mask) + { + if (s_sortedArrayBreakMaskCounts[mask] != 0) + { + std::fprintf(stderr, " 0x%x=%u", mask, s_sortedArrayBreakMaskCounts[mask]); + } + } + std::fprintf(stderr, "\n"); + std::fflush(stderr); + } + } + } } // Release all references and return nodes back to the clean list for the frame... - for (unsigned node_id=0;node_idGet_Transform(RB_TRANSFORM_VIEW, old_view); + g_renderBackend->Get_Transform(RB_TRANSFORM_WORLD, old_world); + static const bool s_probeNoSortFlush = GgcFlags::Enabled(GgcFlag_ProbeNoSortFlush); + if (s_probeNoSortFlush) { + while (!sorted_list.empty()) { + SortingNodeStruct* state = sorted_list.front(); + sorted_list.pop_front(); + Release_Refs(state); + clean_list.push_front(state); + } + for (unsigned node_id=0;node_idSet_Transform(RB_TRANSFORM_VIEW,old_view); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,old_world); + return; + } // TheSuperHackers @perf stephanmeesters 04/07/2026 // Splice nodes that have no bounding information (Z=0.0) at the correct location into the sorted list. @@ -623,29 +1857,44 @@ void SortingRendererClass::Flush() Insert_To_Sorting_Pool(state); } else { - DX8Wrapper::Set_Render_State(state->sorting_state); - DX8Wrapper::Draw_Triangles(state->start_index,state->polygon_count,state->min_vertex_index,state->vertex_count); - DX8Wrapper::Release_Render_State(); + Apply_Render_State(state); + 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 legacy DX8 render state: the backend calls above can + // mutate vba_offset/iba_offset in the DX8 render-state cache. + // Re-applying the saved state fixes this for the DX8 draw path; + // bgfx treats this as a no-op. + g_renderBackend->Restore_Legacy_Render_State_For_Sorted_Draw(state->sorting_state); + + // Use the sort view for transforms to avoid stomping view 1. + g_renderBackend->Begin_Sorted_Batch_Pass(); + + g_renderBackend->Draw_Triangles(state->start_index, state->polygon_count, state->min_vertex_index, state->vertex_count); + g_renderBackend->End_Sorted_Batch_Pass(); + g_renderBackend->Release_Legacy_Render_State_For_Sorted_Draw(); Release_Refs(state); clean_list.push_front(state); } } - bool old_enable=DX8Wrapper::_Is_Triangle_Draw_Enabled(); - DX8Wrapper::_Enable_Triangle_Draw(_EnableTriangleDraw); + bool old_enable=g_renderBackend->Is_Triangle_Draw_Enabled(); + g_renderBackend->Set_Triangle_Draw_Enabled(_EnableTriangleDraw); Flush_Sorting_Pool(); - DX8Wrapper::_Enable_Triangle_Draw(old_enable); + g_renderBackend->Set_Triangle_Draw_Enabled(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); 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/Core/Libraries/Source/WWVegas/WW3D2/sphereobj.cpp b/Core/Libraries/Source/WWVegas/WW3D2/sphereobj.cpp index ad4bb0bc549..2e3b30dc52f 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/sphereobj.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/sphereobj.cpp @@ -85,11 +85,13 @@ #include "WWLib/wwstring.h" #include "WW3D2/camera.h" #include "statistics.h" -#include "dx8wrapper.h" -#include "dx8vertexbuffer.h" -#include "dx8indexbuffer.h" +#include "ww3dcolor.h" +#include "vertexbuffer.h" +#include "indexbuffer.h" #include "sortingrenderer.h" #include "visrasterizer.h" +#include "RenderBackend.h" +#include "IRenderBackend.h" static bool Sphere_Array_Valid = false; @@ -235,6 +237,7 @@ SphereRenderObjClass::SphereRenderObjClass(const SphereRenderObjClass & src) SphereRenderObjClass::~SphereRenderObjClass() { + REF_PTR_RELEASE(SphereTexture); REF_PTR_RELEASE(SphereMaterial); } @@ -468,13 +471,13 @@ 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()); - const unsigned int buffer_type = sort ? BUFFER_TYPE_DYNAMIC_SORTING : BUFFER_TYPE_DYNAMIC_DX8; + const unsigned int buffer_type = sort ? BUFFER_TYPE_DYNAMIC_SORTING : BUFFER_TYPE_DYNAMIC; DynamicVBAccessClass vb(buffer_type, dynamic_fvf_type, mesh.Vertex_ct); { @@ -492,7 +495,7 @@ void SphereRenderObjClass::render_sphere() vb->nz = mesh.vtx_normal[i].Z; if (Flags & USE_ALPHA_VECTOR) { - vb->diffuse = DX8Wrapper::Convert_Color(mesh.dcg[i]); + vb->diffuse = WW3DColor::To_ARGB(mesh.dcg[i]); } else { vb->diffuse = 0xFFFFFFFF; // TODO could combine the material color with this and turn off lighting } @@ -517,13 +520,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); } } @@ -659,7 +662,7 @@ void SphereRenderObjClass::Render(RenderInfoClass & rinfo) // Camera Align if (Flags & USE_CAMERA_ALIGN) { Matrix4x4 view,ident(true); - DX8Wrapper::Get_Transform(D3DTS_VIEW,view); + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, view); Vector4 wpos(Transform[0][3],Transform[1][3],Transform[2][3],1); Vector4 cpos; @@ -670,12 +673,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(); } } @@ -1362,6 +1365,8 @@ fan_size(0), fans(nullptr), face_ct(0), tri_poly(nullptr), +dcg(nullptr), +IsAdditive(false), inverse_alpha(false) { // compute # of vertices @@ -1399,6 +1404,8 @@ fan_size(0), fans(nullptr), face_ct(0), tri_poly(nullptr), +dcg(nullptr), +IsAdditive(false), inverse_alpha(false) { @@ -1675,7 +1682,7 @@ void SphereMeshClass::Generate(float radius, int slices, int stacks) } // Make Sure ptr is where I expect it to be - WWASSERT(((int)out) == ((int)(tri_poly + face_ct))); + WWASSERT(out == (tri_poly + face_ct)); // // Fill in the DCG array @@ -1735,4 +1742,3 @@ void SphereMeshClass::Free() } // EOF - sphereobj.cpp - diff --git a/Core/Libraries/Source/WWVegas/WW3D2/statistics.cpp b/Core/Libraries/Source/WWVegas/WW3D2/statistics.cpp index 0c6d2311e1e..35a672f2360 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/statistics.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/statistics.cpp @@ -19,11 +19,12 @@ #include "statistics.h" #include "WWLib/wwstring.h" #include "WWLib/simplevec.h" -#include "dx8renderer.h" -#include "dx8wrapper.h" -#include "dx8caps.h" #include "textureloader.h" #include "texture.h" +#include "WW3D2/shader.h" +#include "WW3D2/ww3d.h" +#include "RenderBackend.h" +#include "IRenderBackend.h" #include #ifdef _UNIX @@ -292,7 +293,7 @@ void Debug_Statistics::Record_DX8_Skin_Polys_And_Vertices(int pcount,int vcount) void Debug_Statistics::Record_DX8_Polys_And_Vertices(int pcount,int vcount,const ShaderClass& shader) { - if (shader.Get_NPatch_Enable()==ShaderClass::NPATCH_ENABLE && DX8Wrapper::Get_Current_Caps()->Support_NPatches()) { + if (shader.Get_NPatch_Enable()==ShaderClass::NPATCH_ENABLE && g_renderBackend && g_renderBackend->Supports_NPatches()) { unsigned level=WW3D::Get_NPatches_Level(); level*=level; pcount*=level; @@ -366,8 +367,8 @@ void Debug_Statistics::Begin_Statistics() sorting_vertices=0; draw_calls=0; Record_Texture_Begin(); - DX8Wrapper::Begin_Statistics(); -// DX8MeshRendererClass::Begin_Statistics(); + if (g_renderBackend != nullptr) + g_renderBackend->Begin_Device_Statistics(); } void Debug_Statistics::End_Statistics() @@ -381,8 +382,8 @@ void Debug_Statistics::End_Statistics() last_frame_sorting_polygons=sorting_polygons; last_frame_sorting_vertices=sorting_vertices; last_frame_draw_calls=draw_calls; -// DX8MeshRendererClass::End_Statistics(); - DX8Wrapper::End_Statistics(); + if (g_renderBackend != nullptr) + g_renderBackend->End_Device_Statistics(); } void Debug_Statistics::Shutdown_Statistics() @@ -390,4 +391,3 @@ void Debug_Statistics::Shutdown_Statistics() texture_statistics_string.Release_Resources(); } // ---------------------------------------------------------------------------- - diff --git a/Core/Libraries/Source/WWVegas/WW3D2/streak.cpp b/Core/Libraries/Source/WWVegas/WW3D2/streak.cpp index aec9cd74c56..57c9f7b2d65 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/streak.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/streak.cpp @@ -43,7 +43,6 @@ #include "coltest.h" #include "WW3D2/w3d_file.h" #include "texture.h" -#include "dx8wrapper.h" #include "WWMath/vp.h" #include "WWMath/Vector3i.h" #include "sortingrenderer.h" @@ -362,7 +361,7 @@ void StreakLineClass::Set_Opacity(float opacity) void StreakLineClass::Set_Noise_Amplitude(float amplitude) { - LineRenderer.Set_Noise_Amplitude(WWMath::Fabs(amplitude)); + LineRenderer.Set_Noise_Amplitude(WWMath::Fabsf_Legacy(amplitude)); Invalidate_Cached_Bounding_Volumes(); } @@ -741,5 +740,3 @@ bool StreakLineClass::Cast_Ray(RayCollisionTestClass & raytest) return retval; } - - diff --git a/Core/Libraries/Source/WWVegas/WW3D2/streakRender.cpp b/Core/Libraries/Source/WWVegas/WW3D2/streakRender.cpp index 4341c24d7b2..5bbe02d673f 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/streakRender.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/streakRender.cpp @@ -28,12 +28,19 @@ #include "streakRender.h" #include "WW3D2/ww3d.h" #include "WW3D2/rinfo.h" -#include "dx8wrapper.h" +#include "WW3D2/ww3dcolor.h" #include "sortingrenderer.h" #include "WWMath/vp.h" #include "WWMath/Vector3i.h" #include "WWLib/RANDOM.h" #include "WWMath/v3_rnd.h" +#include "WW3D2/vertmaterial.h" +#include "WW3D2/vertexbuffer.h" +#include "WW3D2/indexbuffer.h" +#include "WW3D2/dx8fvf.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/w3d_file.h" /* We have chunking logic which handles N segments at a time. To simplify the subdivision logic, @@ -311,11 +318,11 @@ void StreakRendererClass::RenderStreak ) { Matrix4x4 view; - DX8Wrapper::Get_Transform(D3DTS_VIEW,view); + g_renderBackend->Get_Transform(RB_TRANSFORM_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). @@ -1277,7 +1284,7 @@ void StreakRendererClass::RenderStreak // If color is not white or opacity not 100%, enable gradient in shader and in renderer - otherwise disable. //unsigned int rgba; - //rgba=DX8Wrapper::Convert_Color(Color,Opacity); + //rgba=WW3DColor::To_ARGB(Color,Opacity); //bool rgba_all=(rgba==0xFFFFFFFF); // int colorIndex = 0; @@ -1286,7 +1293,7 @@ void StreakRendererClass::RenderStreak // //vertexArray[vertexIndex].diffuse = rgba;/// OLD WAY COLORS THEM ALL TO THE COLOR,OPACITY MEMBERS ///////////////// // unsigned int perPointARGB; // colorIndex = MIN(vertexIndex / 2, point_cnt); -// perPointARGB = DX8Wrapper::Convert_Color( colors[colorIndex] );// twice as many verts as points? or so? +// perPointARGB = WW3DColor::To_ARGB( colors[colorIndex] );// twice as many verts as points? or so? // vertexArray[vertexIndex].diffuse = perPointARGB; // vertexArray[vertexIndex].u1 = (float)((vertexIndex&2) == 2); // vertexArray[vertexIndex].v1 = (float)((vertexIndex&1) == 1); @@ -1303,7 +1310,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. @@ -1322,7 +1329,7 @@ void StreakRendererClass::RenderStreak ** Render */ - DynamicVBAccessClass Verts((sorting?BUFFER_TYPE_DYNAMIC_SORTING:BUFFER_TYPE_DYNAMIC_DX8),dynamic_fvf_type,vnum); + DynamicVBAccessClass Verts((sorting?BUFFER_TYPE_DYNAMIC_SORTING:BUFFER_TYPE_DYNAMIC),dynamic_fvf_type,vnum); // Copy in the data to the VB { DynamicVBAccessClass::WriteLockClass Lock(&Verts); @@ -1349,7 +1356,7 @@ void StreakRendererClass::RenderStreak vertex->X = vertexArray[i].x; vertex->Y = vertexArray[i].y; vertex->Z = vertexArray[i].z; - *reinterpret_cast(vb + diffuseOffset) = DX8Wrapper::Convert_Color_Clamp(colors[MIN((i/2), point_cnt)]); // TODO: Does not work correctly when subdivision are not 0 + *reinterpret_cast(vb + diffuseOffset) = WW3DColor::To_ARGB_Clamp(colors[MIN((i/2), point_cnt)]); // TODO: Does not work correctly when subdivision are not 0 Vector2 *texture = reinterpret_cast(vb + textureOffset); texture->U = vertexArray[i].u1; texture->V = vertexArray[i].v1; @@ -1357,7 +1364,7 @@ void StreakRendererClass::RenderStreak } } - DynamicIBAccessClass ib_access((sorting?BUFFER_TYPE_DYNAMIC_SORTING:BUFFER_TYPE_DYNAMIC_DX8),triangleIndex*3); + DynamicIBAccessClass ib_access((sorting?BUFFER_TYPE_DYNAMIC_SORTING:BUFFER_TYPE_DYNAMIC),triangleIndex*3); { unsigned int i; DynamicIBAccessClass::WriteLockClass lock(&ib_access); @@ -1372,23 +1379,25 @@ 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) { + g_renderBackend->Set_Streak_Render_Active(true); SortingRendererClass::Insert_Triangles(obj_sphere,0,triangleIndex,0,vnum); + g_renderBackend->Set_Streak_Render_Active(false); } 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/Core/Libraries/Source/WWVegas/WW3D2/surfaceclass.cpp b/Core/Libraries/Source/WWVegas/WW3D2/surfaceclass.cpp index 2de64d69a39..22885e29185 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/surfaceclass.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/surfaceclass.cpp @@ -48,12 +48,81 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "surfaceclass.h" -#include "formconv.h" +#include "texture.h" +#include "textureloader.h" +#include "dx8formatconv.h" +#include "dx8texturelegacytypes.h" +#include "texturecompat.h" +#if !defined(GGC_RENDER_BACKEND_BGFX) +#include "texturecompatibilityinterop.h" #include "dx8wrapper.h" +#endif #include "WWMath/vector2i.h" -#include "colorspace.h" +#include "WW3D2/colorspace.h" #include "WWLib/bound.h" -#include + +#if defined(GGC_RENDER_BACKEND_BGFX) +#define DX8_ErrorCode(hr) ((void)(hr)) +#endif + +namespace +{ + using LegacyRect = RECT; + + constexpr unsigned kLegacyLockReadOnly = 0x00000010L; + constexpr unsigned kLegacySurfaceCopyNoFilter = 1; + constexpr unsigned kLegacySurfaceCopyTriangleFilter = 4; + + bool Is_Block_Compressed_Format(WW3DFormat format) + { + return format == WW3D_FORMAT_DXT1 || + format == WW3D_FORMAT_DXT2 || + format == WW3D_FORMAT_DXT3 || + format == WW3D_FORMAT_DXT4 || + format == WW3D_FORMAT_DXT5; + } + + bool Can_Store_CPU_Surface_Data(const SurfaceClass::SurfaceDescription &desc) + { + return desc.Width != 0 && + desc.Height != 0 && + desc.Format != WW3D_FORMAT_UNKNOWN && + !Is_Block_Compressed_Format(desc.Format) && + ::Get_Bytes_Per_Pixel(desc.Format) != 0; + } + + bool Should_Use_CPU_Surface_Snapshots() + { +#if defined(GGC_RENDER_BACKEND_BGFX) + return true; +#else + return false; +#endif + } + + bool Should_Use_CPU_Only_Surface_Storage() + { +#if defined(GGC_RENDER_BACKEND_BGFX) + return true; +#else + return false; +#endif + } +} + +struct SurfaceCompatibilityState +{ + void *NativeSurface = nullptr; +}; + +#define NativeCompatibilitySurface CompatibilityState->NativeSurface +#define NATIVE_COMPATIBILITY_SURFACE static_cast(NativeCompatibilitySurface) +#define OTHER_NATIVE_COMPATIBILITY_SURFACE(surface) static_cast((surface)->NativeCompatibilitySurface) + +static LegacySurfaceCopyRect To_Legacy_Surface_Copy_Rect(const LegacyRect& rect) +{ + return {rect.left, rect.top, rect.right, rect.bottom}; +} void Convert_Pixel(Vector3 &rgb, const SurfaceClass::SurfaceDescription &sd, const unsigned char * pixel) { @@ -161,48 +230,148 @@ void Convert_Pixel(unsigned char * pixel,const SurfaceClass::SurfaceDescription ** SurfaceClass *************************************************************************/ SurfaceClass::SurfaceClass(unsigned width, unsigned height, WW3DFormat format): - D3DSurface(nullptr), - SurfaceFormat(format) + CompatibilityState(new SurfaceCompatibilityState), + SurfaceFormat(format), + Description{format, width, height}, + ImageData{format, width, height, 0, {}}, + RefreshCPUAfterUnlock(false), + CPULockActive(false), + CPUImagePossiblyStale(false), + TextureOwner(nullptr), + TextureOwnerLevel(0) { WWASSERT(width); WWASSERT(height); - D3DSurface = DX8Wrapper::_Create_DX8_Surface(width, height, format); + if (Should_Use_CPU_Only_Surface_Storage() && Can_Store_CPU_Surface_Data(Description)) + { + Allocate_CPU_Surface_Snapshot(); + return; + } +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "SurfaceClass(width,height,format): standalone bgfx cannot create legacy surface fallback"); + return; +#else + NativeCompatibilitySurface = Create_Legacy_Surface(width, height, format); + Update_Description_From_Native_Compatibility_Surface(); + Capture_CPU_Surface_Snapshot(); +#endif } SurfaceClass::SurfaceClass(const char *filename): - D3DSurface(nullptr) + CompatibilityState(new SurfaceCompatibilityState), + SurfaceFormat(WW3D_FORMAT_UNKNOWN), + Description{WW3D_FORMAT_UNKNOWN, 0, 0}, + ImageData{WW3D_FORMAT_UNKNOWN, 0, 0, 0, {}}, + RefreshCPUAfterUnlock(false), + CPULockActive(false), + CPUImagePossiblyStale(false), + TextureOwner(nullptr), + TextureOwnerLevel(0) +{ + if (Should_Use_CPU_Only_Surface_Storage()) + { + const bool loaded = + TextureLoader::Load_Surface_Image_Immediate(filename, WW3D_FORMAT_UNKNOWN, false, ImageData); + WWASSERT_PRINT( + loaded, + "BGFX CPU surface image load failed; no legacy surface fallback is allowed"); + if (!loaded) { + return; + } + Description.Format = ImageData.Format; + Description.Width = ImageData.Width; + Description.Height = ImageData.Height; + SurfaceFormat = Description.Format; + return; + } + +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT(false, "SurfaceClass(filename): standalone bgfx must use CPU surface path"); +#else + NativeCompatibilitySurface = Create_Legacy_Surface_From_File(filename); + Update_Description_From_Native_Compatibility_Surface(); + Capture_CPU_Surface_Snapshot(); +#endif +} + +SurfaceClass::SurfaceClass(const SurfaceImageData &image): + CompatibilityState(new SurfaceCompatibilityState), + SurfaceFormat(image.Format), + Description{image.Format, image.Width, image.Height}, + ImageData{image.Format, image.Width, image.Height, 0, {}}, + RefreshCPUAfterUnlock(false), + CPULockActive(false), + CPUImagePossiblyStale(false), + TextureOwner(nullptr), + TextureOwnerLevel(0) { - D3DSurface = DX8Wrapper::_Create_DX8_Surface(filename); - SurfaceDescription desc; - Get_Description(desc); - SurfaceFormat=desc.Format; + WWASSERT(Can_Store_CPU_Surface_Data(Description)); + const unsigned int pixel_size = ::Get_Bytes_Per_Pixel(Description.Format); + const unsigned int row_size = Description.Width * pixel_size; + WWASSERT(image.Pitch >= row_size); + WWASSERT(image.Data.size() >= static_cast(image.Pitch) * Description.Height); + + ImageData.Pitch = row_size; + ImageData.Data.resize(static_cast(row_size) * Description.Height); + for (unsigned int row = 0; row < Description.Height; ++row) + { + memcpy( + ImageData.Data.data() + row * ImageData.Pitch, + image.Data.data() + row * image.Pitch, + row_size); + } } -SurfaceClass::SurfaceClass(IDirect3DSurface8 *d3d_surface) : - D3DSurface (nullptr) +SurfaceClass::SurfaceClass(void *native_compatibility_surface) : + CompatibilityState(new SurfaceCompatibilityState), + SurfaceFormat(WW3D_FORMAT_UNKNOWN), + Description{WW3D_FORMAT_UNKNOWN, 0, 0}, + ImageData{WW3D_FORMAT_UNKNOWN, 0, 0, 0, {}}, + RefreshCPUAfterUnlock(false), + CPULockActive(false), + CPUImagePossiblyStale(false), + TextureOwner(nullptr), + TextureOwnerLevel(0) { - Attach (d3d_surface); - SurfaceDescription desc; - Get_Description(desc); - SurfaceFormat=desc.Format; + Attach_Native_Compatibility_Surface(native_compatibility_surface); } SurfaceClass::~SurfaceClass() { - if (D3DSurface) { - D3DSurface->Release(); - D3DSurface = nullptr; + if (TextureOwner != nullptr) { + TextureOwner->Release_Ref(); + TextureOwner = nullptr; + } + if (NativeCompatibilitySurface) { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "SurfaceClass::~SurfaceClass: standalone bgfx cannot release fake-D3D surfaces"); +#else + NATIVE_COMPATIBILITY_SURFACE->Release(); +#endif + NativeCompatibilitySurface = nullptr; } + delete CompatibilityState; + CompatibilityState = nullptr; +} + +void *SurfaceClass::Get_Native_Compatibility_Surface() const +{ + return CompatibilityState != nullptr ? CompatibilityState->NativeSurface : nullptr; +} + +void SurfaceClass::Set_Native_Compatibility_Surface(void *surface) +{ + WWASSERT(CompatibilityState != nullptr); + CompatibilityState->NativeSurface = surface; } void SurfaceClass::Get_Description(SurfaceDescription &surface_desc) { - D3DSURFACE_DESC d3d_desc; - ::ZeroMemory(&d3d_desc, sizeof(D3DSURFACE_DESC)); - DX8_ErrorCode(D3DSurface->GetDesc(&d3d_desc)); - surface_desc.Format = D3DFormat_To_WW3DFormat(d3d_desc.Format); - surface_desc.Height = d3d_desc.Height; - surface_desc.Width = d3d_desc.Width; + surface_desc = Description; } unsigned int SurfaceClass::Get_Bytes_Per_Pixel() @@ -214,32 +383,88 @@ unsigned int SurfaceClass::Get_Bytes_Per_Pixel() SurfaceClass::LockedSurfacePtr SurfaceClass::Lock(int *pitch) { - D3DLOCKED_RECT lock_rect; - ::ZeroMemory(&lock_rect, sizeof(D3DLOCKED_RECT)); - DX8_ErrorCode(D3DSurface->LockRect(&lock_rect, nullptr, 0)); + if (Has_Compatible_CPU_Surface_Snapshot(Description)) + { + *pitch = ImageData.Pitch; + RefreshCPUAfterUnlock = false; + CPULockActive = true; + return static_cast(ImageData.Data.data()); + } + +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "SurfaceClass::Lock: standalone bgfx requires a CPU surface snapshot"); + *pitch = 0; + return nullptr; +#else + LegacyLockedRect lock_rect; + ::ZeroMemory(&lock_rect, sizeof(lock_rect)); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->LockRect(&lock_rect, nullptr, 0)); *pitch = lock_rect.Pitch; + RefreshCPUAfterUnlock = Should_Use_CPU_Surface_Snapshots() && Has_CPU_Surface_Snapshot(); return static_cast(lock_rect.pBits); +#endif } SurfaceClass::LockedSurfacePtr SurfaceClass::Lock(int *pitch, const Vector2i &min, const Vector2i &max) { - D3DLOCKED_RECT lock_rect; - ::ZeroMemory(&lock_rect, sizeof(D3DLOCKED_RECT)); + if (Has_Compatible_CPU_Surface_Snapshot(Description)) + { + const unsigned int pixel_size = ::Get_Bytes_Per_Pixel(Description.Format); + *pitch = ImageData.Pitch; + RefreshCPUAfterUnlock = false; + CPULockActive = true; + return static_cast( + ImageData.Data.data() + + min.J * ImageData.Pitch + + min.I * pixel_size); + } - RECT rect; +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "SurfaceClass::Lock(rect): standalone bgfx requires a CPU surface snapshot"); + *pitch = 0; + return nullptr; +#else + LegacyLockedRect lock_rect; + ::ZeroMemory(&lock_rect, sizeof(lock_rect)); + + LegacyRect rect; rect.left = min.I; rect.top = min.J; rect.right = max.I; rect.bottom = max.J; - DX8_ErrorCode(D3DSurface->LockRect(&lock_rect, &rect, 0)); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->LockRect(&lock_rect, &rect, 0)); *pitch = lock_rect.Pitch; + RefreshCPUAfterUnlock = Should_Use_CPU_Surface_Snapshots() && Has_CPU_Surface_Snapshot(); return static_cast(lock_rect.pBits); +#endif } void SurfaceClass::Unlock() { - DX8_ErrorCode(D3DSurface->UnlockRect()); + if (CPULockActive) + { + CPULockActive = false; + Upload_CPU_Surface_Snapshot_To_Native_Compatibility_Surface(); + return; + } + +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "SurfaceClass::Unlock: standalone bgfx cannot unlock fake-D3D surfaces"); + return; +#else + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->UnlockRect()); + if (RefreshCPUAfterUnlock) { + RefreshCPUAfterUnlock = false; + Capture_CPU_Surface_Snapshot(); + } +#endif } /*********************************************************************************************** @@ -264,10 +489,26 @@ void SurfaceClass::Clear() // size of each pixel in bytes unsigned int size=::Get_Bytes_Per_Pixel(sd.Format); + if (Has_Compatible_CPU_Surface_Snapshot(sd)) + { + const unsigned int row_size = size * sd.Width; + for (unsigned int row = 0; row < sd.Height; ++row) + { + memset(ImageData.Data.data() + row * ImageData.Pitch, 0, row_size); + } + Upload_CPU_Surface_Snapshot_To_Native_Compatibility_Surface(); + return; + } - D3DLOCKED_RECT lock_rect; - ::ZeroMemory(&lock_rect, sizeof(D3DLOCKED_RECT)); - DX8_ErrorCode(D3DSurface->LockRect(&lock_rect,nullptr,0)); +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "SurfaceClass::Clear: standalone bgfx requires a CPU surface snapshot"); + return; +#else + LegacyLockedRect lock_rect; + ::ZeroMemory(&lock_rect, sizeof(lock_rect)); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->LockRect(&lock_rect,nullptr,0)); unsigned int i; unsigned char *mem=(unsigned char *) lock_rect.pBits; @@ -277,7 +518,9 @@ void SurfaceClass::Clear() mem+=lock_rect.Pitch; } - DX8_ErrorCode(D3DSurface->UnlockRect()); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->UnlockRect()); + Refresh_CPU_Surface_Snapshot_If_Present(); +#endif } @@ -303,20 +546,52 @@ void SurfaceClass::Copy(const unsigned char *other) // size of each pixel in bytes unsigned int size=::Get_Bytes_Per_Pixel(sd.Format); + Copy(other, sd.Width * size); +} + + +/*********************************************************************************************** + * SurfaceClass::Copy -- Copies from a pitched byte array to the surface * + *=============================================================================================*/ +void SurfaceClass::Copy(const unsigned char *other, unsigned int pitch) +{ + SurfaceDescription sd; + Get_Description(sd); + + // size of each pixel in bytes + unsigned int size=::Get_Bytes_Per_Pixel(sd.Format); + if (Has_Compatible_CPU_Surface_Snapshot(sd)) + { + const unsigned int row_size = size * sd.Width; + for (unsigned int row = 0; row < sd.Height; ++row) + { + memcpy(ImageData.Data.data() + row * ImageData.Pitch, other + row * pitch, row_size); + } + Upload_CPU_Surface_Snapshot_To_Native_Compatibility_Surface(); + return; + } - D3DLOCKED_RECT lock_rect; - ::ZeroMemory(&lock_rect, sizeof(D3DLOCKED_RECT)); - DX8_ErrorCode(D3DSurface->LockRect(&lock_rect,nullptr,0)); +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "SurfaceClass::Copy(bytes): standalone bgfx requires a CPU surface snapshot"); + return; +#else + LegacyLockedRect lock_rect; + ::ZeroMemory(&lock_rect, sizeof(lock_rect)); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->LockRect(&lock_rect,nullptr,0)); unsigned int i; unsigned char *mem=(unsigned char *) lock_rect.pBits; for (i=0; iUnlockRect()); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->UnlockRect()); + Refresh_CPU_Surface_Snapshot_If_Present(); +#endif } @@ -342,15 +617,35 @@ void SurfaceClass::Copy(const Vector2i &min, const Vector2i &max, const unsigned // size of each pixel in bytes unsigned int size=::Get_Bytes_Per_Pixel(sd.Format); + if (Has_Compatible_CPU_Surface_Snapshot(sd)) + { + int dx=max.I-min.I; + for (int i=min.J; iLockRect(&lock_rect,&rect,0)); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->LockRect(&lock_rect,&rect,0)); int i; unsigned char *mem=(unsigned char *) lock_rect.pBits; int dx=max.I-min.I; @@ -361,7 +656,9 @@ void SurfaceClass::Copy(const Vector2i &min, const Vector2i &max, const unsigned mem+=lock_rect.Pitch; } - DX8_ErrorCode(D3DSurface->UnlockRect()); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->UnlockRect()); + Refresh_CPU_Surface_Snapshot_If_Present(); +#endif } @@ -394,9 +691,32 @@ unsigned char *SurfaceClass::CreateCopy(int *width,int *height,int*size,bool fli unsigned char *other=W3DNEWARRAY unsigned char [sd.Height*sd.Width*mysize]; - D3DLOCKED_RECT lock_rect; - ::ZeroMemory(&lock_rect, sizeof(D3DLOCKED_RECT)); - DX8_ErrorCode(D3DSurface->LockRect(&lock_rect,nullptr,D3DLOCK_READONLY)); + if (Has_Compatible_CPU_Surface_Snapshot(sd)) + { + for (unsigned int i = 0; i < sd.Height; i++) + { + const unsigned char *src = ImageData.Data.data() + i * ImageData.Pitch; + if (flip) + { + memcpy(&other[(sd.Height-i-1)*sd.Width*mysize],src,mysize*sd.Width); + } else + { + memcpy(&other[i*sd.Width*mysize],src,mysize*sd.Width); + } + } + return other; + } + +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "SurfaceClass::CreateCopy: standalone bgfx requires a CPU surface snapshot"); + delete [] other; + return nullptr; +#else + LegacyLockedRect lock_rect; + ::ZeroMemory(&lock_rect, sizeof(lock_rect)); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->LockRect(&lock_rect,nullptr,kLegacyLockReadOnly)); unsigned int i; unsigned char *mem=(unsigned char *) lock_rect.pBits; @@ -412,9 +732,17 @@ unsigned char *SurfaceClass::CreateCopy(int *width,int *height,int*size,bool fli mem+=lock_rect.Pitch; } - DX8_ErrorCode(D3DSurface->UnlockRect()); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->UnlockRect()); return other; +#endif +} + +const SurfaceClass::SurfaceImageData *SurfaceClass::Get_CPU_Surface_Image() const +{ + SurfaceClass *self = const_cast(this); + self->Ensure_CPU_Surface_Snapshot_Current(); + return self->Has_CPU_Surface_Snapshot() ? &ImageData : nullptr; } @@ -447,7 +775,7 @@ void SurfaceClass::Copy( Get_Description(sd); const_cast (other)->Get_Description(osd); - RECT src; + LegacyRect src; src.left=srcx; src.right=srcx+width; src.top=srcy; @@ -458,14 +786,141 @@ void SurfaceClass::Copy( if (sd.Format==osd.Format && sd.Width==osd.Width && sd.Height==osd.Height) { - POINT dst; - dst.x=dstx; - dst.y=dsty; - DX8Wrapper::_Copy_DX8_Rects(other->D3DSurface,&src,1,D3DSurface,&dst); + if (srcx >= osd.Width || srcy >= osd.Height || dstx >= sd.Width || dsty >= sd.Height) { + return; + } + + unsigned int copy_width = width; + unsigned int copy_height = height; + copy_width = MIN(copy_width, osd.Width - srcx); + copy_width = MIN(copy_width, sd.Width - dstx); + copy_height = MIN(copy_height, osd.Height - srcy); + copy_height = MIN(copy_height, sd.Height - dsty); + + if (copy_width == 0 || copy_height == 0) { + return; + } + + const unsigned int pixel_size = ::Get_Bytes_Per_Pixel(sd.Format); + const unsigned int row_size = copy_width * pixel_size; + const bool dst_has_cpu_snapshot = Has_Compatible_CPU_Surface_Snapshot(sd); + const bool src_has_cpu_snapshot = other->Has_Compatible_CPU_Surface_Snapshot(osd); + + if (Should_Use_CPU_Only_Surface_Storage() && + (pixel_size == 0 || !dst_has_cpu_snapshot || !src_has_cpu_snapshot)) + { + WWASSERT_PRINT( + false, + "SurfaceClass::Copy: BGFX surface ownership missing CPU snapshots; no legacy surface copy fallback is allowed"); + return; + } + + if (other == this) + { + if (dst_has_cpu_snapshot) + { + unsigned char *base = ImageData.Data.data(); + for (unsigned int y = 0; y < copy_height; ++y) + { + unsigned char *dst_row = base + (dsty + y) * ImageData.Pitch + dstx * pixel_size; + unsigned char *src_row = base + (srcy + y) * ImageData.Pitch + srcx * pixel_size; + memmove(dst_row, src_row, row_size); + } + Upload_CPU_Surface_Snapshot_To_Native_Compatibility_Surface(); + return; + } + + LegacyLockedRect lock_rect; + ::ZeroMemory(&lock_rect, sizeof(lock_rect)); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->LockRect(&lock_rect, nullptr, 0)); + + unsigned char *base = static_cast(lock_rect.pBits); + for (unsigned int y = 0; y < copy_height; ++y) + { + unsigned char *dst_row = base + (dsty + y) * lock_rect.Pitch + dstx * pixel_size; + unsigned char *src_row = base + (srcy + y) * lock_rect.Pitch + srcx * pixel_size; + memmove(dst_row, src_row, row_size); + } + + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->UnlockRect()); + } + else + { + const bool can_copy_from_cpu = src_has_cpu_snapshot; + LegacyLockedRect src_lock; + ::ZeroMemory(&src_lock, sizeof(src_lock)); + const unsigned char *src_mem = nullptr; + unsigned int src_pitch = 0; + if (can_copy_from_cpu) { + src_mem = other->ImageData.Data.data() + srcy * other->ImageData.Pitch + srcx * pixel_size; + src_pitch = other->ImageData.Pitch; + } else { + LegacyRect src_rect; + src_rect.left = srcx; + src_rect.right = srcx + copy_width; + src_rect.top = srcy; + src_rect.bottom = srcy + copy_height; + DX8_ErrorCode(OTHER_NATIVE_COMPATIBILITY_SURFACE(other)->LockRect(&src_lock, &src_rect, kLegacyLockReadOnly)); + src_mem = static_cast(src_lock.pBits); + src_pitch = src_lock.Pitch; + } + + if (dst_has_cpu_snapshot) + { + unsigned char *dst_mem = ImageData.Data.data() + dsty * ImageData.Pitch + dstx * pixel_size; + for (unsigned int y = 0; y < copy_height; ++y) + { + memcpy(dst_mem, src_mem, row_size); + src_mem += src_pitch; + dst_mem += ImageData.Pitch; + } + if (!can_copy_from_cpu) { + DX8_ErrorCode(OTHER_NATIVE_COMPATIBILITY_SURFACE(other)->UnlockRect()); + } + Upload_CPU_Surface_Snapshot_To_Native_Compatibility_Surface(); + return; + } + + LegacyRect dst_rect; + dst_rect.left = dstx; + dst_rect.right = dstx + copy_width; + dst_rect.top = dsty; + dst_rect.bottom = dsty + copy_height; + + LegacyLockedRect dst_lock; + ::ZeroMemory(&dst_lock, sizeof(dst_lock)); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->LockRect(&dst_lock, &dst_rect, 0)); + + unsigned char *dst_mem = static_cast(dst_lock.pBits); + for (unsigned int y = 0; y < copy_height; ++y) + { + memcpy(dst_mem, src_mem, row_size); + src_mem += src_pitch; + dst_mem += dst_lock.Pitch; + } + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->UnlockRect()); + if (!can_copy_from_cpu) { + DX8_ErrorCode(OTHER_NATIVE_COMPATIBILITY_SURFACE(other)->UnlockRect()); + } + } } else { - RECT dest; +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "SurfaceClass::Copy: standalone bgfx does not support legacy format-converting surface copies"); + return; +#else + if (Should_Use_CPU_Only_Surface_Storage()) + { + WWASSERT_PRINT( + false, + "SurfaceClass::Copy: BGFX surface ownership does not support legacy format-converting surface copies"); + return; + } + + LegacyRect dest; dest.left=dstx; dest.right=dstx+width; dest.top=dsty; @@ -474,8 +929,15 @@ void SurfaceClass::Copy( if (dest.right>int(sd.Width)) dest.right=int(sd.Width); if (dest.bottom>int(sd.Height)) dest.bottom=int(sd.Height); - DX8_ErrorCode(D3DXLoadSurfaceFromSurface(D3DSurface,nullptr,&dest,other->D3DSurface,nullptr,&src,D3DX_FILTER_NONE,0)); + Copy_Legacy_Surface( + NATIVE_COMPATIBILITY_SURFACE, + To_Legacy_Surface_Copy_Rect(dest), + OTHER_NATIVE_COMPATIBILITY_SURFACE(other), + To_Legacy_Surface_Copy_Rect(src), + kLegacySurfaceCopyNoFilter); +#endif } + Refresh_CPU_Surface_Snapshot_If_Present(); } /*********************************************************************************************** @@ -504,19 +966,39 @@ void SurfaceClass::Stretch_Copy( Get_Description(sd); const_cast (other)->Get_Description(osd); - RECT src; +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "SurfaceClass::Stretch_Copy: standalone bgfx does not support legacy stretched surface copies"); +#else + if (Should_Use_CPU_Only_Surface_Storage()) + { + WWASSERT_PRINT( + false, + "SurfaceClass::Stretch_Copy: BGFX surface ownership does not support legacy stretched surface copies"); + return; + } + + LegacyRect src; src.left=srcx; src.right=srcx+srcwidth; src.top=srcy; src.bottom=srcy+srcheight; - RECT dest; + LegacyRect dest; dest.left=dstx; dest.right=dstx+dstwidth; dest.top=dsty; dest.bottom=dsty+dstheight; - DX8_ErrorCode(D3DXLoadSurfaceFromSurface(D3DSurface,nullptr,&dest,other->D3DSurface,nullptr,&src,D3DX_FILTER_TRIANGLE ,0)); + Copy_Legacy_Surface( + NATIVE_COMPATIBILITY_SURFACE, + To_Legacy_Surface_Copy_Rect(dest), + OTHER_NATIVE_COMPATIBILITY_SURFACE(other), + To_Legacy_Surface_Copy_Rect(src), + kLegacySurfaceCopyTriangleFilter); + Refresh_CPU_Surface_Snapshot_If_Present(); +#endif } /*********************************************************************************************** @@ -553,17 +1035,52 @@ void SurfaceClass::FindBB(Vector2i *min,Vector2i*max) break; } - D3DLOCKED_RECT lock_rect; - ::ZeroMemory(&lock_rect, sizeof(D3DLOCKED_RECT)); - RECT rect; - ::ZeroMemory(&rect, sizeof(RECT)); + if (Has_Compatible_CPU_Surface_Snapshot(sd)) + { + int x,y; + unsigned int size=::Get_Bytes_Per_Pixel(sd.Format); + Vector2i realmin=*max; + Vector2i realmax=*min; + + for (y = min->J; y < max->J; y++) { + for (x = min->I; x < max->I; x++) { + const unsigned char *alpha = + ImageData.Data.data() + + y * ImageData.Pitch + + x * size; + unsigned char myalpha=alpha[size-1]; + myalpha=(myalpha>>(8-alphabits)) & mask; + if (myalpha) { + realmin.I = MIN(realmin.I, x); + realmax.I = MAX(realmax.I, x); + realmin.J = MIN(realmin.J, y); + realmax.J = MAX(realmax.J, y); + } + } + } + + *max=realmax; + *min=realmin; + return; + } + +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "SurfaceClass::FindBB: standalone bgfx requires a CPU surface snapshot"); + return; +#else + LegacyLockedRect lock_rect; + ::ZeroMemory(&lock_rect, sizeof(lock_rect)); + LegacyRect rect; + ::ZeroMemory(&rect, sizeof(rect)); rect.bottom=max->J; rect.top=min->J; rect.left=min->I; rect.right=max->I; - DX8_ErrorCode(D3DSurface->LockRect(&lock_rect,&rect,D3DLOCK_READONLY)); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->LockRect(&lock_rect,&rect,kLegacyLockReadOnly)); int x,y; unsigned int size=::Get_Bytes_Per_Pixel(sd.Format); @@ -575,7 +1092,10 @@ void SurfaceClass::FindBB(Vector2i *min,Vector2i*max) for (x = min->I; x < max->I; x++) { // HY - this is not endian safe - unsigned char *alpha=(unsigned char*) ((unsigned int)lock_rect.pBits+(y-min->J)*lock_rect.Pitch+(x-min->I)*size); + unsigned char *alpha = + static_cast(lock_rect.pBits) + + (y - min->J) * lock_rect.Pitch + + (x - min->I) * size; unsigned char myalpha=alpha[size-1]; myalpha=(myalpha>>(8-alphabits)) & mask; if (myalpha) { @@ -587,10 +1107,11 @@ void SurfaceClass::FindBB(Vector2i *min,Vector2i*max) } } - DX8_ErrorCode(D3DSurface->UnlockRect()); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->UnlockRect()); *max=realmax; *min=realmin; +#endif } @@ -631,17 +1152,40 @@ bool SurfaceClass::Is_Transparent_Column(unsigned int column) unsigned int size=::Get_Bytes_Per_Pixel(sd.Format); - D3DLOCKED_RECT lock_rect; - ::ZeroMemory(&lock_rect, sizeof(D3DLOCKED_RECT)); - RECT rect; - ::ZeroMemory(&rect, sizeof(RECT)); + if (Has_Compatible_CPU_Surface_Snapshot(sd)) + { + for (int y = 0; y < (int) sd.Height; y++) + { + const unsigned char *alpha = + ImageData.Data.data() + + y * ImageData.Pitch + + column * size; + unsigned char myalpha=alpha[size-1]; + myalpha=(myalpha>>(8-alphabits)) & mask; + if (myalpha) { + return false; + } + } + return true; + } + +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "SurfaceClass::Is_Transparent_Column: standalone bgfx requires a CPU surface snapshot"); + return true; +#else + LegacyLockedRect lock_rect; + ::ZeroMemory(&lock_rect, sizeof(lock_rect)); + LegacyRect rect; + ::ZeroMemory(&rect, sizeof(rect)); rect.bottom=sd.Height; rect.top=0; rect.left=column; rect.right=column+1; - DX8_ErrorCode(D3DSurface->LockRect(&lock_rect,&rect,D3DLOCK_READONLY)); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->LockRect(&lock_rect,&rect,kLegacyLockReadOnly)); int y; @@ -649,17 +1193,20 @@ bool SurfaceClass::Is_Transparent_Column(unsigned int column) for (y = 0; y < (int) sd.Height; y++) { // HY - this is not endian safe - unsigned char *alpha=(unsigned char*) ((unsigned int)lock_rect.pBits+y*lock_rect.Pitch); + unsigned char *alpha = + static_cast(lock_rect.pBits) + + y * lock_rect.Pitch; unsigned char myalpha=alpha[size-1]; myalpha=(myalpha>>(8-alphabits)) & mask; if (myalpha) { - DX8_ErrorCode(D3DSurface->UnlockRect()); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->UnlockRect()); return false; } } - DX8_ErrorCode(D3DSurface->UnlockRect()); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->UnlockRect()); return true; +#endif } /*********************************************************************************************** @@ -703,17 +1250,194 @@ void SurfaceClass::Get_Pixel(Vector3 &rgb, int x, int y, LockedSurfacePtr pBits, * HISTORY: * * 3/27/2001 pds : Created. * *=============================================================================================*/ -void SurfaceClass::Attach (IDirect3DSurface8 *surface) +void SurfaceClass::Attach_Native_Compatibility_Surface(void *surface) { Detach (); - D3DSurface = surface; +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + surface == nullptr, + "SurfaceClass::Attach_Native_Compatibility_Surface: standalone bgfx cannot attach fake-D3D surfaces"); + NativeCompatibilitySurface = nullptr; + return; +#else + NativeCompatibilitySurface = static_cast(surface); // // Lock a reference onto the object // - if (D3DSurface != nullptr) { - D3DSurface->AddRef (); + if (NativeCompatibilitySurface != nullptr) { + NATIVE_COMPATIBILITY_SURFACE->AddRef (); + Update_Description_From_Native_Compatibility_Surface(); + } +#endif +} + +void SurfaceClass::Update_Description_From_Native_Compatibility_Surface() +{ + WWASSERT(NativeCompatibilitySurface != nullptr); + +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "SurfaceClass::Update_Description_From_Native_Compatibility_Surface: standalone bgfx cannot read fake-D3D surface descriptions"); +#else + LegacySurfaceDesc d3d_desc; + ::ZeroMemory(&d3d_desc, sizeof(d3d_desc)); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->GetDesc(&d3d_desc)); + + Description.Format = D3DFormat_To_WW3DFormat(d3d_desc.Format); + Description.Width = d3d_desc.Width; + Description.Height = d3d_desc.Height; + SurfaceFormat = Description.Format; +#endif +} + +void SurfaceClass::Allocate_CPU_Surface_Snapshot() +{ + ImageData.Format = Description.Format; + ImageData.Width = Description.Width; + ImageData.Height = Description.Height; + ImageData.Pitch = 0; + ImageData.Data.clear(); + CPUImagePossiblyStale = false; + + if (!Can_Store_CPU_Surface_Data(Description)) { + return; + } + + const unsigned int pixel_size = ::Get_Bytes_Per_Pixel(Description.Format); + const unsigned int row_size = Description.Width * pixel_size; + ImageData.Pitch = row_size; + ImageData.Data.resize(static_cast(row_size) * Description.Height); +} + +void SurfaceClass::Capture_CPU_Surface_Snapshot() +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + NativeCompatibilitySurface == nullptr, + "SurfaceClass::Capture_CPU_Surface_Snapshot: standalone bgfx cannot read fake-D3D surface snapshots"); + return; +#else + ImageData.Format = Description.Format; + ImageData.Width = Description.Width; + ImageData.Height = Description.Height; + ImageData.Pitch = 0; + ImageData.Data.clear(); + CPUImagePossiblyStale = false; + + if (!Should_Use_CPU_Surface_Snapshots() || + NativeCompatibilitySurface == nullptr || + !Can_Store_CPU_Surface_Data(Description)) { + return; + } + + const unsigned int pixel_size = ::Get_Bytes_Per_Pixel(Description.Format); + const unsigned int row_size = Description.Width * pixel_size; + ImageData.Pitch = row_size; + ImageData.Data.resize(static_cast(row_size) * Description.Height); + + LegacyLockedRect lock_rect; + ::ZeroMemory(&lock_rect, sizeof(lock_rect)); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->LockRect(&lock_rect, nullptr, kLegacyLockReadOnly)); + + const unsigned char *src = static_cast(lock_rect.pBits); + unsigned char *dst = ImageData.Data.data(); + for (unsigned int row = 0; row < Description.Height; ++row) + { + memcpy(dst, src, row_size); + src += lock_rect.Pitch; + dst += ImageData.Pitch; } + + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->UnlockRect()); +#endif +} + +void SurfaceClass::Refresh_CPU_Surface_Snapshot_If_Present() +{ + if (Has_CPU_Surface_Snapshot()) { + Capture_CPU_Surface_Snapshot(); + } +} + +void SurfaceClass::Upload_CPU_Surface_Snapshot_To_Native_Compatibility_Surface() +{ + if (!Has_Compatible_CPU_Surface_Snapshot(Description)) { + return; + } + + if (NativeCompatibilitySurface != nullptr) + { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "SurfaceClass::Upload_CPU_Surface_Snapshot_To_Native_Compatibility_Surface: standalone bgfx cannot write fake-D3D surfaces"); +#else + const unsigned int pixel_size = ::Get_Bytes_Per_Pixel(Description.Format); + const unsigned int row_size = Description.Width * pixel_size; + + LegacyLockedRect lock_rect; + ::ZeroMemory(&lock_rect, sizeof(lock_rect)); + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->LockRect(&lock_rect, nullptr, 0)); + + const unsigned char *src = ImageData.Data.data(); + unsigned char *dst = static_cast(lock_rect.pBits); + for (unsigned int row = 0; row < Description.Height; ++row) + { + memcpy(dst, src, row_size); + src += ImageData.Pitch; + dst += lock_rect.Pitch; + } + + DX8_ErrorCode(NATIVE_COMPATIBILITY_SURFACE->UnlockRect()); +#endif + } + + CPUImagePossiblyStale = false; + if (TextureOwner != nullptr) { + TextureOwner->Update_Surface_Level_From_Surface(TextureOwnerLevel, ImageData); + } +} + +void SurfaceClass::Ensure_CPU_Surface_Snapshot_Current() +{ + if (CPUImagePossiblyStale && Has_CPU_Surface_Snapshot()) { + Capture_CPU_Surface_Snapshot(); + } +} + +void SurfaceClass::Mark_CPU_Surface_Snapshot_Stale() +{ + if (Has_CPU_Surface_Snapshot()) { + CPUImagePossiblyStale = true; + } +} + +void SurfaceClass::Attach_Texture_Level_Owner(TextureClass *texture, unsigned int level) +{ + if (TextureOwner != texture) + { + if (TextureOwner != nullptr) { + TextureOwner->Release_Ref(); + } + TextureOwner = texture; + if (TextureOwner != nullptr) { + TextureOwner->Add_Ref(); + } + } + TextureOwnerLevel = level; +} + +bool SurfaceClass::Has_Compatible_CPU_Surface_Snapshot(const SurfaceDescription &desc) const +{ + SurfaceClass *self = const_cast(this); + self->Ensure_CPU_Surface_Snapshot_Current(); + return self->Has_CPU_Surface_Snapshot() && + Should_Use_CPU_Surface_Snapshots() && + self->ImageData.Format == desc.Format && + self->ImageData.Width == desc.Width && + self->ImageData.Height == desc.Height; } @@ -735,13 +1459,26 @@ void SurfaceClass::Attach (IDirect3DSurface8 *surface) void SurfaceClass::Detach () { // - // Release the hold we have on the D3D object + // Release the hold we have on the legacy surface object // - if (D3DSurface != nullptr) { - D3DSurface->Release (); + if (NativeCompatibilitySurface != nullptr) { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "SurfaceClass::Detach: standalone bgfx cannot release fake-D3D surfaces"); +#else + NATIVE_COMPATIBILITY_SURFACE->Release (); +#endif } - D3DSurface = nullptr; + NativeCompatibilitySurface = nullptr; + Description.Width = 0; + Description.Height = 0; + ImageData.Data.clear(); + ImageData.Width = 0; + ImageData.Height = 0; + ImageData.Pitch = 0; + RefreshCPUAfterUnlock = false; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/surfaceclass.h b/Core/Libraries/Source/WWVegas/WW3D2/surfaceclass.h index d68868ebd25..ce52170b4cf 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/surfaceclass.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/surfaceclass.h @@ -40,15 +40,19 @@ #include "WWLib/always.h" #include "ww3dformat.h" +#include -struct IDirect3DSurface8; +class SurfaceClass; +class TextureClass; +struct SurfaceCompatibilityState; class Vector2i; class Vector3; +class TextureCompatibilityInterop; /************************************************************************* ** SurfaceClass ** -** This is our surface class, which wraps IDirect3DSurface8. +** This is our surface class, which wraps a legacy render surface. ** ** Hector Yee 2/12/01 - added in fills, blits etc for font3d class ** @@ -65,15 +69,20 @@ class SurfaceClass : public RefCountClass unsigned int Height; // Surface height in pixels }; + struct SurfaceImageData { + WW3DFormat Format; + unsigned int Width; + unsigned int Height; + unsigned int Pitch; + std::vector Data; + }; + // Create surface with desired height, width and format. SurfaceClass(unsigned width, unsigned height, WW3DFormat format); // Create surface from a file. SurfaceClass(const char *filename); - // Create the surface from a D3D pointer - SurfaceClass(IDirect3DSurface8 *d3d_surface); - virtual ~SurfaceClass() override; // Get surface description @@ -100,6 +109,7 @@ class SurfaceClass : public RefCountClass // support for copying from a byte array void Copy(const unsigned char *other); + void Copy(const unsigned char *other, unsigned int pitch); // support for copying from a byte array void Copy(const Vector2i &min, const Vector2i &max, const unsigned char *other); @@ -118,12 +128,9 @@ class SurfaceClass : public RefCountClass // makes a copy of the surface into a byte array unsigned char *CreateCopy(int *width,int *height,int*size,bool flip=false); + const SurfaceImageData *Get_CPU_Surface_Image() const; - // For use by TextureClass: - IDirect3DSurface8 *Peek_D3D_Surface() { return D3DSurface; } - - // Attaching and detaching a surface pointer - void Attach (IDirect3DSurface8 *surface); + // Detaching a surface pointer void Detach (); // draws a horizontal line @@ -144,10 +151,32 @@ class SurfaceClass : public RefCountClass WW3DFormat Get_Surface_Format() const { return SurfaceFormat; } private: - - // Direct3D surface object - IDirect3DSurface8 *D3DSurface; + SurfaceClass(const SurfaceImageData &image); + SurfaceClass(void *native_compatibility_surface); + void Attach_Native_Compatibility_Surface(void *surface); + void Update_Description_From_Native_Compatibility_Surface(); + void Allocate_CPU_Surface_Snapshot(); + void Capture_CPU_Surface_Snapshot(); + void Refresh_CPU_Surface_Snapshot_If_Present(); + void Upload_CPU_Surface_Snapshot_To_Native_Compatibility_Surface(); + void Ensure_CPU_Surface_Snapshot_Current(); + void Mark_CPU_Surface_Snapshot_Stale(); + void Attach_Texture_Level_Owner(TextureClass *texture, unsigned int level); + bool Has_Compatible_CPU_Surface_Snapshot(const SurfaceDescription &desc) const; + bool Has_CPU_Surface_Snapshot() const { return !ImageData.Data.empty(); } + void *Get_Native_Compatibility_Surface() const; + void Set_Native_Compatibility_Surface(void *surface); + + SurfaceCompatibilityState *CompatibilityState; WW3DFormat SurfaceFormat; - friend class TextureClass; + SurfaceDescription Description; + SurfaceImageData ImageData; + bool RefreshCPUAfterUnlock; + bool CPULockActive; + bool CPUImagePossiblyStale; + TextureClass *TextureOwner; + unsigned int TextureOwnerLevel; + friend class TextureClass; + friend class TextureCompatibilityInterop; }; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/texproject.cpp b/Core/Libraries/Source/WWVegas/WW3D2/texproject.cpp index c0d3b1fd71c..61a1f5c42b0 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/texproject.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/texproject.cpp @@ -84,7 +84,7 @@ #include "matpass.h" #include "bwrender.h" #include "WW3D2/assetmgr.h" -#include "dx8wrapper.h" +#include "RenderBackend.h" // DEBUG DEBUG @@ -940,7 +940,7 @@ bool TexProjectClass::Compute_Perspective_Projection ** If the box is behind the viewpoint or the viewpoint is inside the box ** our FOV will be > 180 degrees. Have to give up */ - if ((box.Center.Z > 0.0f) || (box.Extent.Z > WWMath::Fabs(box.Center.Z))) { + if ((box.Center.Z > 0.0f) || (box.Extent.Z > WWMath::Fabsf_Legacy(box.Center.Z))) { return false; } @@ -958,10 +958,10 @@ bool TexProjectClass::Compute_Perspective_Projection zfar = box.Center.Z + user_zfar; } - float tan_hfov2 = WWMath::Fabs(box.Extent.X / (box.Center.Z + box.Extent.Z)); - float tan_vfov2 = WWMath::Fabs(box.Extent.Y / (box.Center.Z + box.Extent.Z)); - float hfov = 2.0f * WWMath::Atan(tan_hfov2); - float vfov = 2.0f * WWMath::Atan(tan_vfov2); + float tan_hfov2 = WWMath::Fabsf_Legacy(box.Extent.X / (box.Center.Z + box.Extent.Z)); + float tan_vfov2 = WWMath::Fabsf_Legacy(box.Extent.Y / (box.Center.Z + box.Extent.Z)); + float hfov = 2.0f * WWMath::Atan_Legacy(tan_hfov2); + float vfov = 2.0f * WWMath::Atan_Legacy(tan_vfov2); /* ** Plug in the results. @@ -1138,7 +1138,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 @@ -1163,7 +1163,10 @@ bool TexProjectClass::Compute_Texture WW3D::End_Render(false); WW3D::Activate_Snapshot(snapshot); // End_Render() ends the shapsnot, so restore the state - DX8Wrapper::Set_Render_Target((IDirect3DSurface8 *)nullptr); + // TheSuperHackers @fix bobtista 21/04/2026 Route the end-of-RTT-pass target clear through + // g_renderBackend so the bgfx renderToTexture flag is reset and later draws are not + // misrouted to the RTT view. + g_renderBackend->Set_Render_Target_With_Z(nullptr, nullptr); } @@ -1386,4 +1389,3 @@ void TexProjectClass::Update_WS_Bounding_Volume() WorldBoundingVolume.Compute_Axis_Aligned_Extent(&extent); Set_Cull_Box(AABoxClass(WorldBoundingVolume.Center,extent)); } - diff --git a/Core/Libraries/Source/WWVegas/WW3D2/texture.cpp b/Core/Libraries/Source/WWVegas/WW3D2/texture.cpp index 35993b849bf..7b61f33ecb7 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/texture.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/texture.cpp @@ -41,25 +41,323 @@ #include "texture.h" -#include -#include +#if !defined(GGC_RENDER_BACKEND_BGFX) #include "dx8wrapper.h" +#endif #include "WWLib/TARGA.h" #include #include "WW3D2/w3d_file.h" #include "WW3D2/assetmgr.h" -#include "formconv.h" +#include "WW3D2/dx8formatconv.h" +#include "WW3D2/dx8texturelegacytypes.h" +#include "WW3D2/texturecompatibilityinterop.h" #include "textureloader.h" +#include "bitmaphandler.h" #include "missingtexture.h" #include "WWLib/ffactory.h" -#include "dx8caps.h" -#include "dx8texman.h" +#include "WW3D2/TextureResourceManager.h" +#if !defined(GGC_RENDER_BACKEND_BGFX) +#include "WW3D2/dx8texman.h" +#endif #include "WW3D2/meshmatdesc.h" -#include "texturethumbnail.h" +#include "WW3D2/texturethumbnail.h" #include "WWDebug/wwprofile.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/DXTUtils.h" +#include +#include +#include + +#if defined(GGC_RENDER_BACKEND_BGFX) +#define DX8_ErrorCode(hr) ((void)(hr)) +#define DX8_RECORD_TEXTURE(texture) ((void)(texture)) +#endif const unsigned DEFAULT_INACTIVATION_TIME=20000; +namespace +{ + constexpr unsigned kLegacyLockReadOnly = 0x00000010L; + + bool Is_Block_Compressed_Texture_Format(WW3DFormat format) + { + return format == WW3D_FORMAT_DXT1 || + format == WW3D_FORMAT_DXT2 || + format == WW3D_FORMAT_DXT3 || + format == WW3D_FORMAT_DXT4 || + format == WW3D_FORMAT_DXT5; + } + + bool Should_Use_CPU_Only_Texture_Level_Surfaces() + { +#if defined(GGC_RENDER_BACKEND_BGFX) + return true; +#else + return false; +#endif + } + + bool Should_Use_CPU_Only_Surface_Textures() + { +#if defined(GGC_RENDER_BACKEND_BGFX) + return true; +#else + return false; +#endif + } + + bool Should_Block_Unmigrated_Bgfx_Texture_Type(TextureBaseClass::TexAssetType asset_type) + { + return Should_Use_CPU_Only_Surface_Textures() && asset_type != TextureBaseClass::TEX_REGULAR; + } + + bool Is_Strategy_Center_Slab_Texture(const char *name) + { + if (name == nullptr) + { + return false; + } + + const char *base = std::strrchr(name, '\\'); + const char *slash = std::strrchr(name, '/'); + if (slash != nullptr && (base == nullptr || slash > base)) + { + base = slash; + } + base = (base != nullptr) ? base + 1 : name; + + char stem[64] = {}; + std::size_t i = 0; + for (; base[i] != '\0' && base[i] != '.' && i + 1 < sizeof(stem); ++i) + { + stem[i] = base[i]; + } + + return stricmp(stem, "atstratslab") == 0 + || stricmp(stem, "atstratslab_d") == 0 + || stricmp(stem, "atstratslab_ds") == 0 + || stricmp(stem, "atstratslab_e") == 0 + || stricmp(stem, "atstratslab_es") == 0 + || stricmp(stem, "atstratslab_s") == 0; + } + + void Apply_Texture_Compatibility_Filter_Overrides(TextureClass *texture, const char *name) + { +#if defined(GGC_RENDER_BACKEND_BGFX) + if (texture == nullptr) + { + return; + } + + if (Is_Strategy_Center_Slab_Texture(name)) + { + // The Strategy Center's foundation side-wall UVs touch the exact 0/1 + // atlas borders. Repeating causes bgfx anisotropic taps to pull pixels + // from the opposite atlas edge at full zoom; the original asset expects + // edge sampling instead. + texture->Get_Filter().Set_U_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); + texture->Get_Filter().Set_V_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_CLAMP); + } +#else + (void)texture; + (void)name; +#endif + } + + MipCountType Legacy_Texture_Mip_Count_For_Construct(void *legacy_texture) + { +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)legacy_texture; + return MIP_LEVELS_1; +#else + return static_cast(static_cast(legacy_texture)->GetLevelCount()); +#endif + } + + unsigned Requested_Mip_Count(unsigned width, unsigned height, MipCountType mip_level_count) + { + if (mip_level_count == MIP_LEVELS_ALL) { + unsigned levels = 1; + unsigned size = std::max(width, height); + while (size > 1) { + size >>= 1; + ++levels; + } + return levels; + } + + switch (mip_level_count) { + case MIP_LEVELS_1: return 1; + case MIP_LEVELS_2: return 2; + case MIP_LEVELS_3: return 3; + case MIP_LEVELS_4: return 4; + case MIP_LEVELS_5: return 5; + case MIP_LEVELS_6: return 6; + case MIP_LEVELS_7: return 7; + case MIP_LEVELS_8: return 8; + case MIP_LEVELS_10: return 10; + case MIP_LEVELS_11: return 11; + case MIP_LEVELS_12: return 12; + default: return 1; + } + } + + bool Build_CPU_Texture_Mips_From_Surface( + const SurfaceClass::SurfaceImageData &surface_image, + MipCountType mip_level_count, + std::vector &mips) + { + mips.clear(); + if (surface_image.Format == WW3D_FORMAT_UNKNOWN || + Is_Block_Compressed_Texture_Format(surface_image.Format) || + surface_image.Width == 0 || + surface_image.Height == 0 || + surface_image.Data.empty()) { + return false; + } + + const unsigned bytes_per_pixel = ::Get_Bytes_Per_Pixel(surface_image.Format); + const unsigned base_row_size = surface_image.Width * bytes_per_pixel; + if (bytes_per_pixel == 0 || surface_image.Pitch < base_row_size) { + return false; + } + + const unsigned requested_levels = Requested_Mip_Count(surface_image.Width, surface_image.Height, mip_level_count); + mips.reserve(requested_levels); + + TextureBaseClass::TextureMipSnapshot base_mip; + base_mip.Width = surface_image.Width; + base_mip.Height = surface_image.Height; + base_mip.Pitch = base_row_size; + base_mip.Format = surface_image.Format; + base_mip.Data.resize(static_cast(base_row_size) * surface_image.Height); + for (unsigned y = 0; y < surface_image.Height; ++y) { + memcpy( + base_mip.Data.data() + y * base_mip.Pitch, + surface_image.Data.data() + y * surface_image.Pitch, + base_row_size); + } + mips.push_back(std::move(base_mip)); + + while (mips.size() < requested_levels) { + const TextureBaseClass::TextureMipSnapshot &previous = mips.back(); + if (previous.Width == 1 && previous.Height == 1) { + break; + } + + TextureBaseClass::TextureMipSnapshot mip; + mip.Width = std::max(1u, previous.Width / 2); + mip.Height = std::max(1u, previous.Height / 2); + mip.Pitch = mip.Width * bytes_per_pixel; + mip.Format = previous.Format; + mip.Data.resize(static_cast(mip.Pitch) * mip.Height); + + for (unsigned y = 0; y < mip.Height; ++y) { + for (unsigned x = 0; x < mip.Width; ++x) { + const unsigned src_x = x * 2; + const unsigned src_y = y * 2; + const auto read_pixel = [&](unsigned px, unsigned py) { + px = std::min(px, previous.Width - 1); + py = std::min(py, previous.Height - 1); + unsigned color = 0; + BitmapHandlerClass::Read_B8G8R8A8( + color, + previous.Data.data() + py * previous.Pitch + px * bytes_per_pixel, + previous.Format, + nullptr, + 0); + return color; + }; + + const unsigned combined = BitmapHandlerClass::Combine_A8R8G8B8( + read_pixel(src_x, src_y), + read_pixel(src_x + 1, src_y), + read_pixel(src_x, src_y + 1), + read_pixel(src_x + 1, src_y + 1)); + BitmapHandlerClass::Write_B8G8R8A8( + mip.Data.data() + y * mip.Pitch + x * bytes_per_pixel, + mip.Format, + combined); + } + } + + mips.push_back(std::move(mip)); + } + + return !mips.empty(); + } + + bool Build_Blank_CPU_Texture_Mips( + unsigned width, + unsigned height, + WW3DFormat format, + MipCountType mip_level_count, + std::vector &mips) + { + mips.clear(); + if (width == 0 || + height == 0 || + format == WW3D_FORMAT_UNKNOWN || + Is_Block_Compressed_Texture_Format(format)) { + return false; + } + + const unsigned bytes_per_pixel = ::Get_Bytes_Per_Pixel(format); + if (bytes_per_pixel == 0) { + return false; + } + + const unsigned requested_levels = Requested_Mip_Count(width, height, mip_level_count); + mips.reserve(requested_levels); + + for (unsigned level = 0; level < requested_levels; ++level) + { + TextureBaseClass::TextureMipSnapshot mip; + mip.Width = width; + mip.Height = height; + mip.Pitch = width * bytes_per_pixel; + mip.Format = format; + mip.Data.resize(static_cast(mip.Pitch) * mip.Height); + std::memset(mip.Data.data(), 0, mip.Data.size()); + mips.push_back(std::move(mip)); + + if (width == 1 && height == 1) { + break; + } + width = std::max(1u, width >> 1); + height = std::max(1u, height >> 1); + } + + return !mips.empty(); + } + + int Legacy_Texture_Pool(TextureBaseClass::PoolType pool) + { + switch (pool) + { + case TextureBaseClass::POOL_DEFAULT: return LEGACY_TEXTURE_POOL_DEFAULT; + case TextureBaseClass::POOL_MANAGED: return LEGACY_TEXTURE_POOL_MANAGED; + case TextureBaseClass::POOL_SYSTEMMEM: return LEGACY_TEXTURE_POOL_SYSTEMMEM; + default: + WWASSERT(0); + return LEGACY_TEXTURE_POOL_MANAGED; + } + } + + LegacyBaseTexture *Legacy_Texture(void *texture) + { + return static_cast(texture); + } +} + +struct TextureCompatibilityState +{ + void *NativeTexture = nullptr; +}; + +#define NativeCompatibilityTexture CompatibilityState->NativeTexture + /* ** Definitions of static members: */ @@ -83,15 +381,19 @@ TextureBaseClass::TextureBaseClass bool rendertarget, bool reducible ) -: MipLevelCount(mip_level_count), - D3DTexture(nullptr), + : MipLevelCount(mip_level_count), + CompatibilityState(new TextureCompatibilityState), + CPUTextureRevision(0), + m_backendHandle(kInvalidRenderResource), Initialized(false), Name(""), FullPath(""), texture_id(unused_texture_id++), IsLightmap(false), + IsRenderTarget(rendertarget), IsProcedural(false), IsReducible(reducible), + IsMissingTexture(false), IsCompressionAllowed(false), InactivationTime(0), ExtendedInactivationTime(0), @@ -99,6 +401,7 @@ TextureBaseClass::TextureBaseClass LastAccessed(0), Width(width), Height(height), + PreserveCPUTextureSnapshotOnNextLegacySet(false), Pool(pool), Dirty(false), TextureLoadTask(nullptr), @@ -114,18 +417,39 @@ TextureBaseClass::TextureBaseClass */ TextureBaseClass::~TextureBaseClass() { - delete TextureLoadTask; - TextureLoadTask=nullptr; - delete ThumbnailLoadTask; - ThumbnailLoadTask=nullptr; + TextureLoader::Delete_Texture_Load_Tasks(this); + + // TheSuperHackers @fix bobtista 20/04/2026 Notify the render backend + // before the legacy 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); + //: also release the backend-neutral resource. + if (m_backendHandle != kInvalidRenderResource) { + g_renderBackend->Destroy_Resource(m_backendHandle); + m_backendHandle = kInvalidRenderResource; + } + } - if (D3DTexture) + if (NativeCompatibilityTexture) { - D3DTexture->Release(); - D3DTexture = nullptr; +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "TextureBaseClass::~TextureBaseClass: standalone bgfx cannot release fake-D3D textures"); +#else + Legacy_Texture(NativeCompatibilityTexture)->Release(); +#endif + NativeCompatibilityTexture = nullptr; } + Clear_CPU_Texture_Snapshot(); - DX8TextureManagerClass::Remove(this); + TextureResourceManagerClass::Remove(this); + delete CompatibilityState; + CompatibilityState = nullptr; } @@ -189,6 +513,19 @@ void TextureBaseClass::Invalidate_Old_Unused_Textures(unsigned invalidation_time */ void TextureBaseClass::Invalidate() { + // TheSuperHackers @bugfix bobtista 07/06/2026 On the standalone bgfx backend the device is + // never lost on a window/resolution change - bgfx::reset only rebuilds the swapchain and + // preserves all user textures. The CPU mip snapshot is the only authoritative copy of the + // pixel data (there is no D3D MANAGED system-memory backing to reload from). Tearing the + // texture down here - Release_Cached_Texture + Destroy_Resource + Clear_CPU_Texture_Snapshot - + // discards that only copy, and nothing reloads it, so every file texture (palm trees, rocks, + // units) rebuilds white after a resolution change. There is no device-loss to recover from on + // bgfx, so skip the invalidation entirely. The DX8 reference backend (Has_Shader_Pipeline() + // false) is unaffected and keeps the original release-on-reset behavior. + if (g_renderBackend != nullptr && g_renderBackend->Has_Shader_Pipeline()) { + return; + } + if (TextureLoadTask) { return; } @@ -201,11 +538,27 @@ void TextureBaseClass::Invalidate() return; } - if (D3DTexture) + if (g_renderBackend != nullptr) + { + g_renderBackend->Release_Cached_Texture(this); + if (m_backendHandle != kInvalidRenderResource) { + g_renderBackend->Destroy_Resource(m_backendHandle); + m_backendHandle = kInvalidRenderResource; + } + } + + if (NativeCompatibilityTexture) { - D3DTexture->Release(); - D3DTexture = nullptr; +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "TextureBaseClass::Invalidate: standalone bgfx cannot release fake-D3D textures"); +#else + Legacy_Texture(NativeCompatibilityTexture)->Release(); +#endif + NativeCompatibilityTexture = nullptr; } + Clear_CPU_Texture_Snapshot(); Initialized=false; @@ -236,10 +589,10 @@ void TextureBaseClass::Invalidate() return; } - if (D3DTexture) + if (NativeCompatibilityTexture) { - D3DTexture->Release(); - D3DTexture = nullptr; + NativeCompatibilityTexture->Release(); + NativeCompatibilityTexture = nullptr; } Initialized=false; @@ -247,33 +600,143 @@ void TextureBaseClass::Invalidate() LastAccessed=WW3D::Get_Sync_Time();*/ } -//********************************************************************************************** -//! Returns a pointer to the d3d texture -/*! -*/ -IDirect3DBaseTexture8 * TextureBaseClass::Peek_D3D_Base_Texture() const +// TheSuperHackers @feature bobtista 16/07/2026 Shader-pipeline counterpart to Invalidate() +// for texture-reduction changes. Invalidate() is a deliberate no-op there (see above), so +// runtime texture-detail changes did nothing. Queue a fresh file load, which re-derives the +// reduction from WW3D::Get_Texture_Reduction, commits a new CPU snapshot, and bumps the +// snapshot revision so the backend rebuilds its texture; the old data keeps drawing until +// the reload lands. +void TextureBaseClass::Reload_For_Reduction() { - LastAccessed=WW3D::Get_Sync_Time(); - return D3DTexture; + if (!Initialized + || IsProcedural + || Is_Render_Target() + || Is_Missing_Texture() + || Get_Asset_Type() != TEX_REGULAR) + { + return; + } + if (TextureLoadTask != nullptr || ThumbnailLoadTask != nullptr) + { + return; + } + + Initialized = false; + TextureLoader::Request_Background_Loading(this); } -//********************************************************************************************** -//! Set the d3d texture pointer. Handles ref counts properly. -/*! -*/ -void TextureBaseClass::Set_D3D_Base_Texture(IDirect3DBaseTexture8* tex) +void TextureBaseClass::Clear_CPU_Texture_Snapshot() { - // (gth) Generals does stuff directly with the D3DTexture pointer so lets - // reset the access timer whenever someon messes with this pointer. - LastAccessed=WW3D::Get_Sync_Time(); + PreserveCPUTextureSnapshotOnNextLegacySet = false; + if (!CPUTextureMips.empty()) { + CPUTextureMips.clear(); + } + ++CPUTextureRevision; +} - if (D3DTexture != nullptr) { - D3DTexture->Release(); +void *TextureBaseClass::Get_Native_Compatibility_Texture() const +{ + return CompatibilityState != nullptr ? CompatibilityState->NativeTexture : nullptr; +} + +void TextureBaseClass::Set_Native_Compatibility_Texture(void *native_texture) +{ + WWASSERT(CompatibilityState != nullptr); + CompatibilityState->NativeTexture = native_texture; +} + +void TextureBaseClass::Set_CPU_Texture_Snapshot(std::vector &&mips) +{ + CPUTextureMips = std::move(mips); + PreserveCPUTextureSnapshotOnNextLegacySet = true; + ++CPUTextureRevision; +} + +void TextureBaseClass::Update_CPU_Texture_Mip_Snapshot(unsigned int level, TextureMipSnapshot &&mip) +{ + if (CPUTextureMips.size() <= level) { + CPUTextureMips.resize(level + 1); } - D3DTexture = tex; - if (D3DTexture != nullptr) { - D3DTexture->AddRef(); + CPUTextureMips[level] = std::move(mip); + PreserveCPUTextureSnapshotOnNextLegacySet = true; + ++CPUTextureRevision; +} + +void TextureBaseClass::Refresh_CPU_Texture_Snapshot() +{ + Capture_CPU_Texture_Snapshot(NativeCompatibilityTexture); +} + +bool TextureBaseClass::Has_Compatibility_Texture() const +{ + return NativeCompatibilityTexture != nullptr; +} + +void TextureBaseClass::Mark_CPU_Texture_Mips_Changed() +{ + PreserveCPUTextureSnapshotOnNextLegacySet = true; + ++CPUTextureRevision; +} + +void TextureBaseClass::Share_Texture_Storage_With(const TextureBaseClass *source) +{ + Share_Legacy_Texture_With(*this, source); +} + +void TextureBaseClass::Capture_CPU_Texture_Snapshot(void *native_texture) +{ + PreserveCPUTextureSnapshotOnNextLegacySet = false; + CPUTextureMips.clear(); + ++CPUTextureRevision; + +#if defined(GGC_RENDER_BACKEND_BGFX) + if (native_texture != nullptr) { + WWASSERT_PRINT( + false, + "TextureBaseClass::Capture_CPU_Texture_Snapshot: standalone bgfx cannot read fake-D3D texture snapshots"); + } + return; +#else + TextureClass * tex2d = As_TextureClass(); + if (native_texture == nullptr || tex2d == nullptr) { + return; } + + auto * d3d_texture = static_cast(native_texture); + const unsigned levels = d3d_texture->GetLevelCount(); + CPUTextureMips.reserve(levels); + for (unsigned level = 0; level < levels; ++level) { + LegacySurfaceDesc desc; + if (FAILED(d3d_texture->GetLevelDesc(level, &desc))) { + CPUTextureMips.clear(); + return; + } + + LegacyLockedRect locked = { 0 }; + if (FAILED(d3d_texture->LockRect(level, &locked, nullptr, kLegacyLockReadOnly)) + || locked.pBits == nullptr) { + CPUTextureMips.clear(); + return; + } + + TextureMipSnapshot mip; + mip.Width = desc.Width; + mip.Height = desc.Height; + mip.Pitch = static_cast(locked.Pitch); + mip.Format = D3DFormat_To_WW3DFormat(desc.Format); + const bool compressed = + mip.Format == WW3D_FORMAT_DXT1 || + mip.Format == WW3D_FORMAT_DXT2 || + mip.Format == WW3D_FORMAT_DXT3 || + mip.Format == WW3D_FORMAT_DXT4 || + mip.Format == WW3D_FORMAT_DXT5; + const unsigned rows = compressed ? DXT_SurfaceRows(mip.Height) : mip.Height; + mip.Data.resize(rows * mip.Pitch); + std::memcpy(&mip.Data[0], locked.pBits, mip.Data.size()); + DX8_ErrorCode(d3d_texture->UnlockRect(level)); + CPUTextureMips.push_back(mip); + } +#endif } @@ -284,8 +747,16 @@ void TextureBaseClass::Set_D3D_Base_Texture(IDirect3DBaseTexture8* tex) void TextureBaseClass::Load_Locked_Surface() { WWPROFILE(("TextureClass::Load_Locked_Surface()")); - if (D3DTexture) D3DTexture->Release(); - D3DTexture=nullptr; + if (NativeCompatibilityTexture) { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "TextureBaseClass::Load_Locked_Surface: standalone bgfx cannot release fake-D3D textures"); +#else + Legacy_Texture(NativeCompatibilityTexture)->Release(); +#endif + } + NativeCompatibilityTexture=nullptr; TextureLoader::Request_Thumbnail(this); Initialized=false; } @@ -297,10 +768,20 @@ void TextureBaseClass::Load_Locked_Surface() */ bool TextureBaseClass::Is_Missing_Texture() { + if (IsMissingTexture) { + return true; + } + if (Should_Use_CPU_Only_Surface_Textures()) { + return false; + } + if (NativeCompatibilityTexture == nullptr) { + return false; + } + bool flag = false; - IDirect3DBaseTexture8 *missing_texture = MissingTexture::_Get_Missing_Texture(); + LegacyBaseTexture *missing_texture = Get_Legacy_Missing_Texture(); - if (D3DTexture == missing_texture) + if (Legacy_Texture(NativeCompatibilityTexture) == missing_texture) flag = true; if (missing_texture) @@ -330,13 +811,20 @@ void TextureBaseClass::Set_Texture_Name(const char * name) */ unsigned int TextureBaseClass::Get_Priority() { - if (!D3DTexture) + if (!NativeCompatibilityTexture) { - WWASSERT_PRINT(0, "Get_Priority: D3DTexture is null!"); + WWASSERT_PRINT(0, "Get_Priority: NativeCompatibilityTexture is null!"); return 0; } - return D3DTexture->GetPriority(); +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "TextureBaseClass::Get_Priority: standalone bgfx cannot query fake-D3D texture priority"); + return 0; +#else + return Legacy_Texture(NativeCompatibilityTexture)->GetPriority(); +#endif } @@ -346,13 +834,20 @@ unsigned int TextureBaseClass::Get_Priority() */ unsigned int TextureBaseClass::Set_Priority(unsigned int priority) { - if (!D3DTexture) + if (!NativeCompatibilityTexture) { - WWASSERT_PRINT(0, "Set_Priority: D3DTexture is null!"); + WWASSERT_PRINT(0, "Set_Priority: NativeCompatibilityTexture is null!"); return 0; } - return D3DTexture->SetPriority(priority); +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "TextureBaseClass::Set_Priority: standalone bgfx cannot set fake-D3D texture priority"); + return 0; +#else + return Legacy_Texture(NativeCompatibilityTexture)->SetPriority(priority); +#endif } @@ -388,7 +883,7 @@ unsigned TextureBaseClass::Get_Reduction() const void TextureBaseClass::Apply_Null(unsigned int stage) { // This function sets the render states for a "null" texture - DX8Wrapper::Set_DX8_Texture(stage, nullptr); + g_renderBackend->Bind_Texture_Immediate(stage, nullptr); } // ---------------------------------------------------------------------------- @@ -611,28 +1106,48 @@ TextureClass::TextureClass default : break; } - D3DPOOL d3dpool=(D3DPOOL)0; - switch(pool) +#if defined(GGC_RENDER_BACKEND_BGFX) { - case POOL_DEFAULT : d3dpool=D3DPOOL_DEFAULT; break; - case POOL_MANAGED : d3dpool=D3DPOOL_MANAGED; break; - case POOL_SYSTEMMEM : d3dpool=D3DPOOL_SYSTEMMEM; break; - default: WWASSERT(0); + if (rendertarget) + { + Poke_Legacy_Texture(*this, nullptr); + LastAccessed=WW3D::Get_Sync_Time(); + return; + } + + std::vector mips; + if (Build_Blank_CPU_Texture_Mips(width, height, format, mip_level_count, mips)) + { + Set_CPU_Texture_Snapshot(std::move(mips)); + Poke_Legacy_Texture(*this, nullptr); + LastAccessed=WW3D::Get_Sync_Time(); + return; + } + + Initialized=false; + Poke_Legacy_Texture(*this, nullptr); + WWASSERT_PRINT( + false, + "TextureClass(width,height): BGFX texture ownership cannot create this procedural texture; no legacy fallback is allowed"); + LastAccessed=WW3D::Get_Sync_Time(); + return; } +#endif - Poke_Texture - ( - DX8Wrapper::_Create_DX8_Texture + const int legacy_pool = Legacy_Texture_Pool(pool); + Poke_Legacy_Texture(*this, + Create_Legacy_Texture ( width, height, format, mip_level_count, - d3dpool, + legacy_pool, rendertarget ) ); +#if !defined(GGC_RENDER_BACKEND_BGFX) if (pool==POOL_DEFAULT) { Set_Dirty(); @@ -645,8 +1160,9 @@ TextureClass::TextureClass this, rendertarget ); - DX8TextureManagerClass::Add(track); + TextureResourceManagerClass::Add(track); } +#endif LastAccessed=WW3D::Get_Sync_Time(); } @@ -685,7 +1201,7 @@ TextureClass::TextureClass // If requesting bumpmap format that isn't available we'll just return the surface in whatever color // format the texture file is in. (This is illegal case, the format support should always be queried // before creating a bump texture!) - if (!DX8Wrapper::Is_Initted() || !DX8Wrapper::Get_Current_Caps()->Support_Texture_Format(TextureFormat)) + if (!g_renderBackend || !g_renderBackend->Supports_Texture_Format(TextureFormat)) { TextureFormat=WW3D_FORMAT_UNKNOWN; } @@ -723,7 +1239,7 @@ TextureClass::TextureClass if (!WW3D::Is_Texturing_Enabled()) { Initialized=true; - Poke_Texture(nullptr); + Poke_Legacy_Texture(*this, nullptr); } // Find original size from the thumbnail (but don't create thumbnail texture yet!) @@ -743,7 +1259,7 @@ TextureClass::TextureClass // mesh is rendered. if (!WW3D::Get_Thumbnail_Enabled()) { - if (TextureLoader::Is_DX8_Thread()) + if (TextureLoader::Is_Main_Render_Thread()) { Init(); } @@ -780,36 +1296,77 @@ TextureClass::TextureClass default: break; } - Poke_Texture - ( - DX8Wrapper::_Create_DX8_Texture + const SurfaceClass::SurfaceImageData *surface_image = surface->Get_CPU_Surface_Image(); + std::vector mips; + if (surface_image != nullptr && + Build_CPU_Texture_Mips_From_Surface(*surface_image, mip_level_count, mips)) { + Set_CPU_Texture_Snapshot(std::move(mips)); + } + + LegacyBaseTexture *newTexture = nullptr; + const bool source_has_legacy_surface = surface->Get_Native_Compatibility_Surface() != nullptr; + const bool use_cpu_owned_texture = + Should_Use_CPU_Only_Surface_Textures() && + Has_CPU_Texture_Mips(); + if (source_has_legacy_surface && !use_cpu_owned_texture) + { + newTexture = Create_Legacy_Texture_From_Surface ( - surface->Peek_D3D_Surface(), + Peek_Legacy_Surface(*surface), mip_level_count - ) - ); + ); + } + + if (newTexture != nullptr || (source_has_legacy_surface && !use_cpu_owned_texture) || !Has_CPU_Texture_Mips()) { + Poke_Legacy_Texture(*this, newTexture); + } + if (!Has_CPU_Texture_Mips()) { + if (Should_Use_CPU_Only_Surface_Textures()) + { + WWASSERT_PRINT( + 0, + "TextureClass(SurfaceClass): BGFX texture ownership missing CPU mips; no legacy texture fallback is allowed"); + } + else + { + Refresh_CPU_Texture_Snapshot(); + } + } LastAccessed=WW3D::Get_Sync_Time(); } // ---------------------------------------------------------------------------- -TextureClass::TextureClass(IDirect3DBaseTexture8* d3d_texture) +TextureClass::TextureClass(void *legacy_texture) : TextureBaseClass ( 0, 0, - ((MipCountType)d3d_texture->GetLevelCount()) + Legacy_Texture_Mip_Count_For_Construct(legacy_texture) ), - Filter((MipCountType)d3d_texture->GetLevelCount()) + Filter(Legacy_Texture_Mip_Count_For_Construct(legacy_texture)) { +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)legacy_texture; + WWASSERT_PRINT( + false, + "TextureClass(native): standalone bgfx cannot wrap fake-D3D textures"); + Initialized=false; + IsProcedural=true; + IsReducible=false; + Poke_Legacy_Texture(*this, nullptr); + LastAccessed=WW3D::Get_Sync_Time(); + return; +#else + LegacyBaseTexture *d3d_texture = static_cast(legacy_texture); Initialized=true; IsProcedural=true; IsReducible=false; - Set_D3D_Base_Texture(d3d_texture); - IDirect3DSurface8* surface; - DX8_ErrorCode(Peek_D3D_Texture()->GetSurfaceLevel(0,&surface)); - D3DSURFACE_DESC d3d_desc; - ::ZeroMemory(&d3d_desc, sizeof(D3DSURFACE_DESC)); + Set_Legacy_Base_Texture(*this, d3d_texture); + NativeCompatibilityTextureSurface *surface; + DX8_ErrorCode(Peek_Legacy_Texture2D(*this)->GetSurfaceLevel(0,&surface)); + LegacySurfaceDesc d3d_desc; + ::ZeroMemory(&d3d_desc, sizeof(d3d_desc)); DX8_ErrorCode(surface->GetDesc(&d3d_desc)); Width=d3d_desc.Width; Height=d3d_desc.Height; @@ -827,6 +1384,7 @@ TextureClass::TextureClass(IDirect3DBaseTexture8* d3d_texture) } LastAccessed=WW3D::Get_Sync_Time(); +#endif } //********************************************************************************************** @@ -838,6 +1396,14 @@ void TextureClass::Init() // If the texture has already been initialised we should exit now if (Initialized) return; + if (Should_Block_Unmigrated_Bgfx_Texture_Type(Get_Asset_Type())) + { + WWASSERT_PRINT( + false, + "TextureClass::Init: cube/volume textures are not migrated to bgfx texture ownership; no legacy fallback is allowed"); + return; + } + WWPROFILE("TextureClass::Init"); // If the texture has recently been inactivated, increase the inactivation time (this texture obviously @@ -852,7 +1418,12 @@ void TextureClass::Init() } - if (!Peek_D3D_Base_Texture()) + bool has_bgfx_cpu_thumbnail = false; +#if defined(GGC_RENDER_BACKEND_BGFX) + has_bgfx_cpu_thumbnail = + Has_CPU_Texture_Mips(); +#endif + if (!Peek_Legacy_Base_Texture(*this) && !has_bgfx_cpu_thumbnail) { if (!WW3D::Get_Thumbnail_Enabled() || MipLevelCount==MIP_LEVELS_1) { @@ -879,28 +1450,33 @@ void TextureClass::Init() //! Apply new surface to texture /*! */ -void TextureClass::Apply_New_Surface +void TextureClass::Apply_Native_Compatibility_Texture ( - IDirect3DBaseTexture8* d3d_texture, + void *native_texture, bool initialized, bool disable_auto_invalidation ) { - IDirect3DBaseTexture8* d3d_tex=Peek_D3D_Base_Texture(); - - if (d3d_tex) d3d_tex->Release(); - - Poke_Texture(d3d_texture);//TextureLoadTask->Peek_D3D_Texture(); - d3d_texture->AddRef(); +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)native_texture; + (void)initialized; + (void)disable_auto_invalidation; + WWASSERT_PRINT( + false, + "TextureClass::Apply_Native_Compatibility_Texture: standalone bgfx cannot apply fake-D3D textures"); + return; +#else + LegacyBaseTexture *d3d_texture = Legacy_Texture(native_texture); + Set_Legacy_Base_Texture(*this, d3d_texture); if (initialized) Initialized=true; if (disable_auto_invalidation) InactivationTime = 0; WWASSERT(d3d_texture); - IDirect3DSurface8* surface; - DX8_ErrorCode(Peek_D3D_Texture()->GetSurfaceLevel(0,&surface)); - D3DSURFACE_DESC d3d_desc; - ::ZeroMemory(&d3d_desc, sizeof(D3DSURFACE_DESC)); + NativeCompatibilityTextureSurface *surface; + DX8_ErrorCode(Peek_Legacy_Texture2D(*this)->GetSurfaceLevel(0,&surface)); + LegacySurfaceDesc d3d_desc; + ::ZeroMemory(&d3d_desc, sizeof(d3d_desc)); DX8_ErrorCode(surface->GetDesc(&d3d_desc)); if (initialized) { @@ -910,6 +1486,7 @@ void TextureClass::Apply_New_Surface } surface->Release(); +#endif } @@ -957,11 +1534,11 @@ void TextureClass::Apply(unsigned int stage) // Set texture itself if (WW3D::Is_Texturing_Enabled()) { - DX8Wrapper::Set_DX8_Texture(stage, Peek_D3D_Base_Texture()); + g_renderBackend->Bind_Texture_Immediate(stage, this); } else { - DX8Wrapper::Set_DX8_Texture(stage, nullptr); + g_renderBackend->Bind_Texture_Immediate(stage, nullptr); } Filter.Apply(stage); @@ -973,47 +1550,378 @@ void TextureClass::Apply(unsigned int stage) */ SurfaceClass *TextureClass::Get_Surface_Level(unsigned int level) { - if (!Peek_D3D_Texture()) + const std::vector &mips = Get_CPU_Texture_Mips(); + if (level < mips.size()) { + const TextureMipSnapshot &mip = mips[level]; + if (mip.Format != WW3D_FORMAT_UNKNOWN && + !Is_Block_Compressed_Texture_Format(mip.Format) && + !mip.Data.empty() && + mip.Width != 0 && + mip.Height != 0 && + mip.Pitch >= mip.Width * ::Get_Bytes_Per_Pixel(mip.Format) && + mip.Data.size() >= static_cast(mip.Pitch) * mip.Height) + { + SurfaceClass::SurfaceImageData image; + image.Format = mip.Format; + image.Width = mip.Width; + image.Height = mip.Height; + image.Pitch = mip.Pitch; + image.Data = mip.Data; + + SurfaceClass *surface = nullptr; + if (Should_Use_CPU_Only_Texture_Level_Surfaces()) + { + surface = NEW_REF(SurfaceClass, (image)); + } + else + { + surface = NEW_REF(SurfaceClass, (mip.Width, mip.Height, mip.Format)); + surface->Copy(mip.Data.data(), mip.Pitch); + } + surface->Attach_Texture_Level_Owner(this, level); + return surface; + } + } + + if (!Peek_Legacy_Texture2D(*this)) { - WWASSERT_PRINT(0, "Get_Surface_Level: D3DTexture is null!"); + WWASSERT_PRINT(0, "Get_Surface_Level: NativeCompatibilityTexture is null!"); + return nullptr; + } + if (Should_Use_CPU_Only_Texture_Level_Surfaces()) + { + WWASSERT_PRINT( + 0, + "Get_Surface_Level: BGFX CPU texture-level surface is missing; no legacy surface fallback is allowed"); return nullptr; } - IDirect3DSurface8 *d3d_surface = nullptr; - DX8_ErrorCode(Peek_D3D_Texture()->GetSurfaceLevel(level, &d3d_surface)); - SurfaceClass *surface = new SurfaceClass(d3d_surface); + NativeCompatibilityTextureSurface *d3d_surface = nullptr; + DX8_ErrorCode(Peek_Legacy_Texture2D(*this)->GetSurfaceLevel(level, &d3d_surface)); + SurfaceClass *surface = Create_Legacy_Surface_Wrapper(d3d_surface); d3d_surface->Release(); + surface->Attach_Texture_Level_Owner(this, level); + surface->Capture_CPU_Surface_Snapshot(); return surface; } +TextureClass::MutableTextureMipView TextureClass::Begin_Mip_Write(unsigned int level) +{ + MutableTextureMipView view; + if (TextureFormat == WW3D_FORMAT_UNKNOWN || + Is_Block_Compressed_Texture_Format(TextureFormat)) + { + return view; + } + + const unsigned bytes_per_pixel = ::Get_Bytes_Per_Pixel(TextureFormat); + if (bytes_per_pixel == 0) + { + return view; + } + + unsigned mip_width = 0; + unsigned mip_height = 0; + std::vector &mips = Mutable_CPU_Texture_Mips(); + if (level < mips.size() && + mips[level].Format != WW3D_FORMAT_UNKNOWN && + mips[level].Width != 0 && + mips[level].Height != 0) + { + mip_width = mips[level].Width; + mip_height = mips[level].Height; + } + else + { + if (Width <= 0 || Height <= 0) + { + return view; + } + mip_width = std::max(1u, static_cast(Width) >> level); + mip_height = std::max(1u, static_cast(Height) >> level); + } + + const unsigned row_size = mip_width * bytes_per_pixel; + if (mips.size() <= level) + { + mips.resize(level + 1); + } + TextureMipSnapshot &mip = mips[level]; + if (mip.Format != TextureFormat || + mip.Width != mip_width || + mip.Height != mip_height || + mip.Pitch < row_size || + mip.Data.size() < static_cast(mip.Pitch) * mip.Height) + { + mip.Format = TextureFormat; + mip.Width = mip_width; + mip.Height = mip_height; + mip.Pitch = row_size; + mip.Data.assign(static_cast(row_size) * mip_height, 0); + } + + view.Format = mip.Format; + view.Width = mip.Width; + view.Height = mip.Height; + view.Pitch = mip.Pitch; + view.Data = mip.Data.data(); + return view; +} + +void TextureClass::End_Mip_Write(unsigned int level) +{ + const std::vector &mips = Get_CPU_Texture_Mips(); + if (level >= mips.size() || + mips[level].Format == WW3D_FORMAT_UNKNOWN || + mips[level].Data.empty()) + { + return; + } + + Mark_CPU_Texture_Mips_Changed(); + auto *texture = Peek_Legacy_Texture2D(*this); + if (texture != nullptr) + { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "TextureClass::End_Mip_Write: standalone bgfx cannot mirror writes to fake-D3D texture mips"); +#else + const TextureMipSnapshot &mip = mips[level]; + const unsigned bytes_per_pixel = ::Get_Bytes_Per_Pixel(mip.Format); + const unsigned row_size = mip.Width * bytes_per_pixel; + LegacyLockedRect lock_rect; + ::ZeroMemory(&lock_rect, sizeof(lock_rect)); + if (bytes_per_pixel != 0 && + row_size != 0 && + SUCCEEDED(texture->LockRect(level, &lock_rect, nullptr, 0))) + { + if (lock_rect.pBits != nullptr) + { + const unsigned char *src = mip.Data.data(); + unsigned char *dst = static_cast(lock_rect.pBits); + for (unsigned row = 0; row < mip.Height; ++row) + { + memcpy(dst, src, row_size); + src += mip.Pitch; + dst += lock_rect.Pitch; + } + } + DX8_ErrorCode(texture->UnlockRect(level)); + } +#endif + } + + if (g_renderBackend != nullptr) + { + g_renderBackend->Invalidate_Cached_Texture(this); + } +} + +void TextureClass::Update_Surface_Level_From_Surface(unsigned int level, const SurfaceClass::SurfaceImageData &image) +{ + if (image.Format == WW3D_FORMAT_UNKNOWN || + image.Width == 0 || + image.Height == 0 || + image.Data.empty() || + Is_Block_Compressed_Texture_Format(image.Format)) + { + return; + } + + const unsigned bytes_per_pixel = ::Get_Bytes_Per_Pixel(image.Format); + if (bytes_per_pixel == 0) { + return; + } + + const unsigned row_size = image.Width * bytes_per_pixel; + TextureMipSnapshot mip; + mip.Width = image.Width; + mip.Height = image.Height; + mip.Pitch = row_size; + mip.Format = image.Format; + mip.Data.resize(static_cast(row_size) * image.Height); + for (unsigned row = 0; row < image.Height; ++row) + { + memcpy( + mip.Data.data() + row * mip.Pitch, + image.Data.data() + row * image.Pitch, + row_size); + } + Update_CPU_Texture_Mip_Snapshot(level, std::move(mip)); + + auto *texture = Peek_Legacy_Texture2D(*this); + if (texture != nullptr) + { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "TextureClass::Update_Surface_Level_From_Surface: standalone bgfx cannot update fake-D3D texture mips"); +#else + LegacyLockedRect lock_rect; + ::ZeroMemory(&lock_rect, sizeof(lock_rect)); + if (SUCCEEDED(texture->LockRect(level, &lock_rect, nullptr, 0))) + { + if (lock_rect.pBits != nullptr) + { + const unsigned char *src = image.Data.data(); + unsigned char *dst = static_cast(lock_rect.pBits); + for (unsigned row = 0; row < image.Height; ++row) + { + memcpy(dst, src, row_size); + src += image.Pitch; + dst += lock_rect.Pitch; + } + } + DX8_ErrorCode(texture->UnlockRect(level)); + } +#endif + } + + if (g_renderBackend != nullptr) { + g_renderBackend->Invalidate_Cached_Texture(this); + } +} + //********************************************************************************************** //! Get surface description for a mip level /*! */ void TextureClass::Get_Level_Description( SurfaceClass::SurfaceDescription & desc, unsigned int level ) { + const std::vector &mips = Get_CPU_Texture_Mips(); + if (level < mips.size()) + { + const TextureMipSnapshot &mip = mips[level]; + if (mip.Format != WW3D_FORMAT_UNKNOWN && mip.Width != 0 && mip.Height != 0) + { + desc.Format = mip.Format; + desc.Width = mip.Width; + desc.Height = mip.Height; + return; + } + } + +#if !defined(GGC_RENDER_BACKEND_BGFX) SurfaceClass * surf = Get_Surface_Level(level); if (surf != nullptr) { surf->Get_Description(desc); } REF_PTR_RELEASE(surf); +#else + desc.Format = WW3D_FORMAT_UNKNOWN; + desc.Width = 0; + desc.Height = 0; +#endif +} + +unsigned int TextureClass::Get_Level_Count() const +{ + const std::vector &mips = Get_CPU_Texture_Mips(); + if (!mips.empty()) { + return static_cast(mips.size()); + } + auto *texture = Peek_Legacy_Texture2D(*this); +#if defined(GGC_RENDER_BACKEND_BGFX) + if (texture != nullptr) + { + WWASSERT_PRINT( + false, + "TextureClass::Get_Level_Count: standalone bgfx cannot query fake-D3D texture levels"); + } + return 0; +#else + return texture != nullptr ? texture->GetLevelCount() : 0; +#endif +} + +bool TextureClass::Generate_Mip_Levels() +{ + const std::vector &mips = Get_CPU_Texture_Mips(); + if (!mips.empty()) + { + const TextureMipSnapshot &base_mip = mips[0]; + const unsigned bytes_per_pixel = ::Get_Bytes_Per_Pixel(base_mip.Format); + if (base_mip.Format != WW3D_FORMAT_UNKNOWN && + !Is_Block_Compressed_Texture_Format(base_mip.Format) && + base_mip.Width != 0 && + base_mip.Height != 0 && + bytes_per_pixel != 0 && + base_mip.Pitch >= base_mip.Width * bytes_per_pixel && + !base_mip.Data.empty()) + { + SurfaceClass::SurfaceImageData base_image; + base_image.Format = base_mip.Format; + base_image.Width = base_mip.Width; + base_image.Height = base_mip.Height; + base_image.Pitch = base_mip.Pitch; + base_image.Data = base_mip.Data; + + std::vector rebuilt_mips; + if (Build_CPU_Texture_Mips_From_Surface(base_image, MipLevelCount, rebuilt_mips)) + { + Set_CPU_Texture_Snapshot(std::move(rebuilt_mips)); + if (Peek_Legacy_Texture2D(*this) != nullptr && !Should_Use_CPU_Only_Surface_Textures()) + { + Generate_Legacy_Texture_Mips(*this); + } + if (g_renderBackend != nullptr) + { + g_renderBackend->Invalidate_Cached_Texture(this); + } + return true; + } + } + } + + if (Should_Use_CPU_Only_Surface_Textures()) + { + WWASSERT_PRINT( + 0, + "Generate_Mip_Levels: BGFX CPU mip source is missing; no legacy mip fallback is allowed"); + return false; + } + return Generate_Legacy_Texture_Mips(*this); +} + +void TextureClass::Set_LOD(unsigned int lod) const +{ + auto *texture = Peek_Legacy_Texture2D(*this); + if (texture != nullptr) + { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "TextureClass::Set_LOD: standalone bgfx cannot set fake-D3D texture LOD"); +#else + DX8_ErrorCode(texture->SetLOD(static_cast(lod))); +#endif + } } //********************************************************************************************** -//! Get D3D surface from mip level +//! Get legacy surface from mip level /*! */ -IDirect3DSurface8 *TextureClass::Get_D3D_Surface_Level(unsigned int level) +void *TextureClass::Get_Native_Compatibility_Surface_Level(unsigned int level) { - if (!Peek_D3D_Texture()) + if (Should_Use_CPU_Only_Texture_Level_Surfaces()) { - WWASSERT_PRINT(0, "Get_D3D_Surface_Level: D3DTexture is null!"); + WWASSERT_PRINT( + 0, + "TextureClass::Get_Native_Compatibility_Surface_Level: BGFX surface ownership is enabled; no legacy surface fallback is allowed"); return nullptr; } - IDirect3DSurface8 *d3d_surface = nullptr; - DX8_ErrorCode(Peek_D3D_Texture()->GetSurfaceLevel(level, &d3d_surface)); + if (!Peek_Legacy_Texture2D(*this)) + { + WWASSERT_PRINT(0, "Get_Native_Compatibility_Surface_Level: native texture is null!"); + return nullptr; + } + + NativeCompatibilityTextureSurface *d3d_surface = nullptr; + DX8_ErrorCode(Peek_Legacy_Texture2D(*this)->GetSurfaceLevel(level, &d3d_surface)); return d3d_surface; } @@ -1023,15 +1931,32 @@ IDirect3DSurface8 *TextureClass::Get_D3D_Surface_Level(unsigned int level) */ unsigned TextureClass::Get_Texture_Memory_Usage() const { - int size=0; - if (!Peek_D3D_Texture()) return 0; - for (unsigned i=0;iGetLevelCount();++i) + const std::vector &mips = Get_CPU_Texture_Mips(); + if (!mips.empty()) { - D3DSURFACE_DESC desc; - DX8_ErrorCode(Peek_D3D_Texture()->GetLevelDesc(i,&desc)); + size_t size = 0; + for (const TextureMipSnapshot &mip : mips) { + size += mip.Data.size(); + } + return static_cast(size); + } + + int size=0; + if (!Peek_Legacy_Texture2D(*this)) return 0; +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "TextureClass::Get_Texture_Memory_Usage: standalone bgfx cannot query fake-D3D texture levels"); + return 0; +#else + for (unsigned i=0;iGetLevelCount();++i) + { + LegacySurfaceDesc desc; + DX8_ErrorCode(Peek_Legacy_Texture2D(*this)->GetLevelDesc(i,&desc)); size+=desc.Size; } return size; +#endif } @@ -1118,14 +2043,14 @@ TextureClass* Load_Texture(ChunkLoadClass & cload) case W3DTEXTURE_TYPE_BUMPMAP: { - if (DX8Wrapper::Is_Initted() && DX8Wrapper::Get_Current_Caps()->Support_Bump_Envmap()) + if (g_renderBackend && g_renderBackend->Supports_Bump_Envmap()) { // No mipmaps to bumpmap for now mipcount=MIP_LEVELS_1; - if (DX8Wrapper::Get_Current_Caps()->Support_Texture_Format(WW3D_FORMAT_U8V8)) format=WW3D_FORMAT_U8V8; - else if (DX8Wrapper::Get_Current_Caps()->Support_Texture_Format(WW3D_FORMAT_X8L8V8U8)) format=WW3D_FORMAT_X8L8V8U8; - else if (DX8Wrapper::Get_Current_Caps()->Support_Texture_Format(WW3D_FORMAT_L6V5U5)) format=WW3D_FORMAT_L6V5U5; + if (g_renderBackend->Supports_Texture_Format(WW3D_FORMAT_U8V8)) format=WW3D_FORMAT_U8V8; + else if (g_renderBackend->Supports_Texture_Format(WW3D_FORMAT_X8L8V8U8)) format=WW3D_FORMAT_X8L8V8U8; + else if (g_renderBackend->Supports_Texture_Format(WW3D_FORMAT_L6V5U5)) format=WW3D_FORMAT_L6V5U5; } break; } @@ -1151,6 +2076,8 @@ TextureClass* Load_Texture(ChunkLoadClass & cload) newtex = WW3DAssetManager::Get_Instance()->Get_Texture(name); } + Apply_Texture_Compatibility_Filter_Overrides(newtex, name); + WWASSERT(newtex); } @@ -1205,27 +2132,29 @@ ZTextureClass::ZTextureClass : TextureBaseClass(width,height, mip_level_count, pool), DepthStencilTextureFormat(zformat) { - D3DPOOL d3dpool=(D3DPOOL)0; - switch (pool) +#if defined(GGC_RENDER_BACKEND_BGFX) { - case POOL_DEFAULT: d3dpool=D3DPOOL_DEFAULT; break; - case POOL_MANAGED: d3dpool=D3DPOOL_MANAGED; break; - case POOL_SYSTEMMEM: d3dpool=D3DPOOL_SYSTEMMEM; break; - default: WWASSERT(0); + Poke_Legacy_Texture(*this, nullptr); + Initialized=true; + LastAccessed=WW3D::Get_Sync_Time(); + return; } +#endif + + const int legacy_pool = Legacy_Texture_Pool(pool); - Poke_Texture + Poke_Legacy_Texture(*this, + Create_Legacy_ZTexture ( - DX8Wrapper::_Create_DX8_ZTexture - ( - width, - height, - zformat, - mip_level_count, - d3dpool - ) + width, + height, + zformat, + mip_level_count, + legacy_pool + ) ); +#if !defined(GGC_RENDER_BACKEND_BGFX) if (pool==POOL_DEFAULT) { Set_Dirty(); @@ -1237,8 +2166,9 @@ ZTextureClass::ZTextureClass mip_level_count, this ); - DX8TextureManagerClass::Add(track); + TextureResourceManagerClass::Add(track); } +#endif Initialized=true; IsProcedural=true; IsReducible=false; @@ -1253,35 +2183,40 @@ ZTextureClass::ZTextureClass */ void ZTextureClass::Apply(unsigned int stage) { - DX8Wrapper::Set_DX8_Texture(stage, Peek_D3D_Base_Texture()); + g_renderBackend->Bind_Texture_Immediate(stage, this); } //********************************************************************************************** //! Apply new surface to texture /*! KM */ -void ZTextureClass::Apply_New_Surface +void ZTextureClass::Apply_Native_Compatibility_Texture ( - IDirect3DBaseTexture8* d3d_texture, + void *native_texture, bool initialized, bool disable_auto_invalidation ) { - IDirect3DBaseTexture8* d3d_tex=Peek_D3D_Base_Texture(); - - if (d3d_tex) d3d_tex->Release(); - - Poke_Texture(d3d_texture);//TextureLoadTask->Peek_D3D_Texture(); - d3d_texture->AddRef(); +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)native_texture; + (void)initialized; + (void)disable_auto_invalidation; + WWASSERT_PRINT( + false, + "ZTextureClass::Apply_Native_Compatibility_Texture: standalone bgfx cannot apply fake-D3D depth textures"); + return; +#else + LegacyBaseTexture *d3d_texture = Legacy_Texture(native_texture); + Set_Legacy_Base_Texture(*this, d3d_texture); if (initialized) Initialized=true; if (disable_auto_invalidation) InactivationTime = 0; - WWASSERT(Peek_D3D_Texture()); - IDirect3DSurface8* surface; - DX8_ErrorCode(Peek_D3D_Texture()->GetSurfaceLevel(0,&surface)); - D3DSURFACE_DESC d3d_desc; - ::ZeroMemory(&d3d_desc, sizeof(D3DSURFACE_DESC)); + WWASSERT(Peek_Legacy_Texture2D(*this)); + NativeCompatibilityTextureSurface *surface; + DX8_ErrorCode(Peek_Legacy_Texture2D(*this)->GetSurfaceLevel(0,&surface)); + LegacySurfaceDesc d3d_desc; + ::ZeroMemory(&d3d_desc, sizeof(d3d_desc)); DX8_ErrorCode(surface->GetDesc(&d3d_desc)); if (initialized) { @@ -1290,22 +2225,31 @@ void ZTextureClass::Apply_New_Surface Height=d3d_desc.Height; } surface->Release(); +#endif } //********************************************************************************************** -//! Get D3D surface from mip level +//! Get legacy surface from mip level /*! */ -IDirect3DSurface8* ZTextureClass::Get_D3D_Surface_Level(unsigned int level) +void *ZTextureClass::Get_Native_Compatibility_Surface_Level(unsigned int level) { - if (!Peek_D3D_Texture()) + if (Should_Use_CPU_Only_Texture_Level_Surfaces()) + { + WWASSERT_PRINT( + 0, + "ZTextureClass::Get_Native_Compatibility_Surface_Level: BGFX surface ownership is enabled; no legacy depth surface fallback is allowed"); + return nullptr; + } + + if (!Peek_Legacy_Texture2D(*this)) { - WWASSERT_PRINT(0, "Get_D3D_Surface_Level: D3DTexture is null!"); + WWASSERT_PRINT(0, "Get_Native_Compatibility_Surface_Level: native texture is null!"); return nullptr; } - IDirect3DSurface8 *d3d_surface = nullptr; - DX8_ErrorCode(Peek_D3D_Texture()->GetSurfaceLevel(level, &d3d_surface)); + NativeCompatibilityTextureSurface *d3d_surface = nullptr; + DX8_ErrorCode(Peek_Legacy_Texture2D(*this)->GetSurfaceLevel(level, &d3d_surface)); return d3d_surface; } @@ -1316,14 +2260,21 @@ IDirect3DSurface8* ZTextureClass::Get_D3D_Surface_Level(unsigned int level) unsigned ZTextureClass::Get_Texture_Memory_Usage() const { int size=0; - if (!Peek_D3D_Texture()) return 0; - for (unsigned i=0;iGetLevelCount();++i) - { - D3DSURFACE_DESC desc; - DX8_ErrorCode(Peek_D3D_Texture()->GetLevelDesc(i,&desc)); + if (!Peek_Legacy_Texture2D(*this)) return 0; +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "ZTextureClass::Get_Texture_Memory_Usage: standalone bgfx cannot query fake-D3D depth texture levels"); + return 0; +#else + for (unsigned i=0;iGetLevelCount();++i) + { + LegacySurfaceDesc desc; + DX8_ErrorCode(Peek_Legacy_Texture2D(*this)->GetLevelDesc(i,&desc)); size+=desc.Size; } return size; +#endif } @@ -1359,28 +2310,32 @@ CubeTextureClass::CubeTextureClass default : break; } - D3DPOOL d3dpool=(D3DPOOL)0; - switch(pool) + const int legacy_pool = Legacy_Texture_Pool(pool); + + if (Should_Block_Unmigrated_Bgfx_Texture_Type(Get_Asset_Type())) { - case POOL_DEFAULT : d3dpool=D3DPOOL_DEFAULT; break; - case POOL_MANAGED : d3dpool=D3DPOOL_MANAGED; break; - case POOL_SYSTEMMEM : d3dpool=D3DPOOL_SYSTEMMEM; break; - default: WWASSERT(0); + Initialized=false; + Poke_Legacy_Texture(*this, nullptr); + WWASSERT_PRINT( + false, + "CubeTextureClass: bgfx texture ownership has no cube texture implementation; no legacy fallback is allowed"); + LastAccessed=WW3D::Get_Sync_Time(); + return; } - Poke_Texture - ( - DX8Wrapper::_Create_DX8_Cube_Texture + Poke_Legacy_Texture(*this, + Create_Legacy_Cube_Texture ( width, height, format, mip_level_count, - d3dpool, + legacy_pool, rendertarget ) ); +#if !defined(GGC_RENDER_BACKEND_BGFX) if (pool==POOL_DEFAULT) { Set_Dirty(); @@ -1393,8 +2348,9 @@ CubeTextureClass::CubeTextureClass this, rendertarget ); - DX8TextureManagerClass::Add(track); + TextureResourceManagerClass::Add(track); } +#endif LastAccessed=WW3D::Get_Sync_Time(); } @@ -1430,7 +2386,7 @@ CubeTextureClass::CubeTextureClass // If requesting bumpmap format that isn't available we'll just return the surface in whatever color // format the texture file is in. (This is illegal case, the format support should always be queried // before creating a bump texture!) - if (!DX8Wrapper::Is_Initted() || !DX8Wrapper::Get_Current_Caps()->Support_Texture_Format(TextureFormat)) + if (!g_renderBackend || !g_renderBackend->Supports_Texture_Format(TextureFormat)) { TextureFormat=WW3D_FORMAT_UNKNOWN; } @@ -1465,10 +2421,22 @@ CubeTextureClass::CubeTextureClass Set_Texture_Name(name); Set_Full_Path(full_path); WWASSERT(name[0]!='\0'); + + if (Should_Block_Unmigrated_Bgfx_Texture_Type(Get_Asset_Type())) + { + Initialized=false; + Poke_Legacy_Texture(*this, nullptr); + WWASSERT_PRINT( + false, + "CubeTextureClass: bgfx texture ownership has no cube texture implementation; no legacy fallback is allowed"); + LastAccessed=WW3D::Get_Sync_Time(); + return; + } + if (!WW3D::Is_Texturing_Enabled()) { Initialized=true; - Poke_Texture(nullptr); + Poke_Legacy_Texture(*this, nullptr); } // Find original size from the thumbnail (but don't create thumbnail texture yet!) @@ -1488,118 +2456,43 @@ CubeTextureClass::CubeTextureClass // mesh is rendered. if (!WW3D::Get_Thumbnail_Enabled()) { - if (TextureLoader::Is_DX8_Thread()) + if (TextureLoader::Is_Main_Render_Thread()) { Init(); } } } -// don't know if these are needed -#if 0 -// ---------------------------------------------------------------------------- -CubeTextureClass::CubeTextureClass -( - SurfaceClass *surface, - MipCountType mip_level_count -) -: TextureClass(0,0,mip_level_count, POOL_MANAGED, false, surface->Get_Surface_Format()) -{ - IsProcedural=true; - Initialized=true; - IsReducible=false; - - SurfaceClass::SurfaceDescription sd; - surface->Get_Description(sd); - Width=sd.Width; - Height=sd.Height; - switch (sd.Format) - { - case WW3D_FORMAT_DXT1: - case WW3D_FORMAT_DXT2: - case WW3D_FORMAT_DXT3: - case WW3D_FORMAT_DXT4: - case WW3D_FORMAT_DXT5: - IsCompressionAllowed=true; - break; - default: break; - } - - Poke_Texture - ( - DX8Wrapper::_Create_DX8_Cube_Texture - ( - surface->Peek_D3D_Surface(), - mip_level_count - ) - ); - LastAccessed=WW3D::Get_Sync_Time(); -} - -// ---------------------------------------------------------------------------- -CubeTextureClass::CubeTextureClass(IDirect3DBaseTexture8* d3d_texture) -: TextureBaseClass - ( - 0, - 0, - ((MipCountType)d3d_texture->GetLevelCount()) - ), - Filter((MipCountType)d3d_texture->GetLevelCount()) -{ - Initialized=true; - IsProcedural=true; - IsReducible=false; - - Peek_Texture()->AddRef(); - IDirect3DSurface8* surface; - DX8_ErrorCode(Peek_D3D_Texture()->GetSurfaceLevel(0,&surface)); - D3DSURFACE_DESC d3d_desc; - ::ZeroMemory(&d3d_desc, sizeof(D3DSURFACE_DESC)); - DX8_ErrorCode(surface->GetDesc(&d3d_desc)); - Width=d3d_desc.Width; - Height=d3d_desc.Height; - TextureFormat=D3DFormat_To_WW3DFormat(d3d_desc.Format); - switch (TextureFormat) - { - case WW3D_FORMAT_DXT1: - case WW3D_FORMAT_DXT2: - case WW3D_FORMAT_DXT3: - case WW3D_FORMAT_DXT4: - case WW3D_FORMAT_DXT5: - IsCompressionAllowed=true; - break; - default: break; - } - - LastAccessed=WW3D::Get_Sync_Time(); -} -#endif - //********************************************************************************************** //! Apply new surface to texture /*! */ -void CubeTextureClass::Apply_New_Surface +void CubeTextureClass::Apply_Native_Compatibility_Texture ( - IDirect3DBaseTexture8* d3d_texture, + void *native_texture, bool initialized, bool disable_auto_invalidation ) { - IDirect3DBaseTexture8* d3d_tex=Peek_D3D_Base_Texture(); - - if (d3d_tex) d3d_tex->Release(); - - Poke_Texture(d3d_texture);//TextureLoadTask->Peek_D3D_Texture(); - d3d_texture->AddRef(); +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)native_texture; + (void)initialized; + (void)disable_auto_invalidation; + WWASSERT_PRINT( + false, + "CubeTextureClass::Apply_Native_Compatibility_Texture: standalone bgfx cannot apply fake-D3D cube textures"); + return; +#else + LegacyBaseTexture *d3d_texture = Legacy_Texture(native_texture); + Set_Legacy_Base_Texture(*this, d3d_texture); if (initialized) Initialized=true; if (disable_auto_invalidation) InactivationTime = 0; WWASSERT(d3d_texture); - D3DSURFACE_DESC d3d_desc; - ::ZeroMemory(&d3d_desc, sizeof(D3DSURFACE_DESC)); - DX8_ErrorCode(Peek_D3D_CubeTexture()->GetLevelDesc(0,&d3d_desc)); + LegacySurfaceDesc d3d_desc; + ::ZeroMemory(&d3d_desc, sizeof(d3d_desc)); + DX8_ErrorCode(Peek_Legacy_Cube_Texture(*this)->GetLevelDesc(0,&d3d_desc)); if (initialized) { @@ -1607,6 +2500,7 @@ void CubeTextureClass::Apply_New_Surface Width=d3d_desc.Width; Height=d3d_desc.Height; } +#endif } @@ -1643,28 +2537,32 @@ VolumeTextureClass::VolumeTextureClass default : break; } - D3DPOOL d3dpool=(D3DPOOL)0; - switch(pool) + const int legacy_pool = Legacy_Texture_Pool(pool); + + if (Should_Block_Unmigrated_Bgfx_Texture_Type(Get_Asset_Type())) { - case POOL_DEFAULT : d3dpool=D3DPOOL_DEFAULT; break; - case POOL_MANAGED : d3dpool=D3DPOOL_MANAGED; break; - case POOL_SYSTEMMEM : d3dpool=D3DPOOL_SYSTEMMEM; break; - default: WWASSERT(0); + Initialized=false; + Poke_Legacy_Texture(*this, nullptr); + WWASSERT_PRINT( + false, + "VolumeTextureClass: bgfx texture ownership has no volume texture implementation; no legacy fallback is allowed"); + LastAccessed=WW3D::Get_Sync_Time(); + return; } - Poke_Texture - ( - DX8Wrapper::_Create_DX8_Volume_Texture + Poke_Legacy_Texture(*this, + Create_Legacy_Volume_Texture ( width, height, depth, format, mip_level_count, - d3dpool + legacy_pool ) ); +#if !defined(GGC_RENDER_BACKEND_BGFX) if (pool==POOL_DEFAULT) { Set_Dirty(); @@ -1677,8 +2575,9 @@ VolumeTextureClass::VolumeTextureClass this, rendertarget ); - DX8TextureManagerClass::Add(track); + TextureResourceManagerClass::Add(track); } +#endif LastAccessed=WW3D::Get_Sync_Time(); } @@ -1715,7 +2614,7 @@ VolumeTextureClass::VolumeTextureClass // If requesting bumpmap format that isn't available we'll just return the surface in whatever color // format the texture file is in. (This is illegal case, the format support should always be queried // before creating a bump texture!) - if (!DX8Wrapper::Is_Initted() || !DX8Wrapper::Get_Current_Caps()->Support_Texture_Format(TextureFormat)) + if (!g_renderBackend || !g_renderBackend->Supports_Texture_Format(TextureFormat)) { TextureFormat=WW3D_FORMAT_UNKNOWN; } @@ -1750,10 +2649,22 @@ VolumeTextureClass::VolumeTextureClass Set_Texture_Name(name); Set_Full_Path(full_path); WWASSERT(name[0]!='\0'); + + if (Should_Block_Unmigrated_Bgfx_Texture_Type(Get_Asset_Type())) + { + Initialized=false; + Poke_Legacy_Texture(*this, nullptr); + WWASSERT_PRINT( + false, + "VolumeTextureClass: bgfx texture ownership has no volume texture implementation; no legacy fallback is allowed"); + LastAccessed=WW3D::Get_Sync_Time(); + return; + } + if (!WW3D::Is_Texturing_Enabled()) { Initialized=true; - Poke_Texture(nullptr); + Poke_Legacy_Texture(*this, nullptr); } // Find original size from the thumbnail (but don't create thumbnail texture yet!) @@ -1773,122 +2684,44 @@ VolumeTextureClass::VolumeTextureClass // mesh is rendered. if (!WW3D::Get_Thumbnail_Enabled()) { - if (TextureLoader::Is_DX8_Thread()) + if (TextureLoader::Is_Main_Render_Thread()) { Init(); } } } -// don't know if these are needed -#if 0 -// ---------------------------------------------------------------------------- -CubeTextureClass::CubeTextureClass -( - SurfaceClass *surface, - MipCountType mip_level_count -) -: TextureClass(0,0,mip_level_count, POOL_MANAGED, false, surface->Get_Surface_Format()) -{ - IsProcedural=true; - Initialized=true; - IsReducible=false; - - SurfaceClass::SurfaceDescription sd; - surface->Get_Description(sd); - Width=sd.Width; - Height=sd.Height; - switch (sd.Format) - { - case WW3D_FORMAT_DXT1: - case WW3D_FORMAT_DXT2: - case WW3D_FORMAT_DXT3: - case WW3D_FORMAT_DXT4: - case WW3D_FORMAT_DXT5: - IsCompressionAllowed=true; - break; - default: break; - } - - Poke_Texture - ( - DX8Wrapper::_Create_DX8_Cube_Texture - ( - surface->Peek_D3D_Surface(), - mip_level_count - ) - ); - LastAccessed=WW3D::Get_Sync_Time(); -} - -// ---------------------------------------------------------------------------- -CubeTextureClass::CubeTextureClass(IDirect3DBaseTexture8* d3d_texture) -: TextureBaseClass - ( - 0, - 0, - ((MipCountType)d3d_texture->GetLevelCount()) - ), - Filter((MipCountType)d3d_texture->GetLevelCount()) -{ - Initialized=true; - IsProcedural=true; - IsReducible=false; - - Peek_Texture()->AddRef(); - IDirect3DSurface8* surface; - DX8_ErrorCode(Peek_D3D_Texture()->GetSurfaceLevel(0,&surface)); - D3DSURFACE_DESC d3d_desc; - ::ZeroMemory(&d3d_desc, sizeof(D3DSURFACE_DESC)); - DX8_ErrorCode(surface->GetDesc(&d3d_desc)); - Width=d3d_desc.Width; - Height=d3d_desc.Height; - TextureFormat=D3DFormat_To_WW3DFormat(d3d_desc.Format); - switch (TextureFormat) - { - case WW3D_FORMAT_DXT1: - case WW3D_FORMAT_DXT2: - case WW3D_FORMAT_DXT3: - case WW3D_FORMAT_DXT4: - case WW3D_FORMAT_DXT5: - IsCompressionAllowed=true; - break; - default: break; - } - - LastAccessed=WW3D::Get_Sync_Time(); -} -#endif - - - - //********************************************************************************************** //! Apply new surface to texture /*! */ -void VolumeTextureClass::Apply_New_Surface +void VolumeTextureClass::Apply_Native_Compatibility_Texture ( - IDirect3DBaseTexture8* d3d_texture, + void *native_texture, bool initialized, bool disable_auto_invalidation ) { - IDirect3DBaseTexture8* d3d_tex=Peek_D3D_Base_Texture(); - - if (d3d_tex) d3d_tex->Release(); - - Poke_Texture(d3d_texture);//TextureLoadTask->Peek_D3D_Texture(); - d3d_texture->AddRef(); +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)native_texture; + (void)initialized; + (void)disable_auto_invalidation; + WWASSERT_PRINT( + false, + "VolumeTextureClass::Apply_Native_Compatibility_Texture: standalone bgfx cannot apply fake-D3D volume textures"); + return; +#else + LegacyBaseTexture *d3d_texture = Legacy_Texture(native_texture); + Set_Legacy_Base_Texture(*this, d3d_texture); if (initialized) Initialized=true; if (disable_auto_invalidation) InactivationTime = 0; WWASSERT(d3d_texture); - D3DVOLUME_DESC d3d_desc; - ::ZeroMemory(&d3d_desc, sizeof(D3DVOLUME_DESC)); + LegacyVolumeDesc d3d_desc; + ::ZeroMemory(&d3d_desc, sizeof(d3d_desc)); - DX8_ErrorCode(Peek_D3D_VolumeTexture()->GetLevelDesc(0,&d3d_desc)); + DX8_ErrorCode(Peek_Legacy_Volume_Texture(*this)->GetLevelDesc(0,&d3d_desc)); if (initialized) { @@ -1897,4 +2730,5 @@ void VolumeTextureClass::Apply_New_Surface Height=d3d_desc.Height; Depth=d3d_desc.Depth; } +#endif } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/texture.h b/Core/Libraries/Source/WWVegas/WW3D2/texture.h index 0ce681ce531..292f90a25ab 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/texture.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/texture.h @@ -48,26 +48,25 @@ #include "WWLib/wwstring.h" #include "WWMath/vector3.h" #include "texturefilter.h" +#include "IRenderBackend.h" +#include -struct IDirect3DBaseTexture8; -struct IDirect3DTexture8; -struct IDirect3DCubeTexture8; -struct IDirect3DVolumeTexture8; - -class DX8Wrapper; class TextureLoader; class LoaderThreadClass; class TextureLoadTaskClass; class TextureClass; class CubeTextureClass; class VolumeTextureClass; +class TextureCompatibilityInterop; +struct TextureCompatibilityState; class TextureBaseClass : public RefCountClass { friend class TextureLoader; friend class LoaderThreadClass; - friend class DX8TextureTrackerClass; //(gth) so it can call Poke_Texture, + friend class DX8TextureTrackerClass; //(gth) so it can poke the native texture, friend class DX8ZTextureTrackerClass; + friend class TextureCompatibilityInterop; public: @@ -139,6 +138,7 @@ class TextureBaseClass : public RefCountClass bool Is_Initialized() const { return Initialized; } bool Is_Lightmap() const { return IsLightmap; } bool Is_Procedural() const { return IsProcedural; } + bool Is_Render_Target() const { return IsRenderTarget; } bool Is_Reducible() const { return IsReducible; } //can texture be reduced in resolution for LOD purposes? static int _Get_Total_Locked_Surface_Size(); @@ -155,13 +155,32 @@ class TextureBaseClass : public RefCountClass // This utility function processes the texture reduction (used during rendering) void Invalidate(); - // texture accessors (dx8) - IDirect3DBaseTexture8 *Peek_D3D_Base_Texture() const; - void Set_D3D_Base_Texture(IDirect3DBaseTexture8* tex); + // TheSuperHackers @feature bobtista 16/07/2026 Re-load a file texture at the current + // texture reduction on backends where Invalidate() is a deliberate no-op. Clears the + // initialized state and queues a background reload; the old CPU snapshot (and the GPU + // texture built from it) stays live until the reload commits, so there is no white flash. + void Reload_For_Reduction(); + + struct TextureMipSnapshot + { + unsigned Width; + unsigned Height; + unsigned Pitch; + WW3DFormat Format; + std::vector Data; + }; + const std::vector& Get_CPU_Texture_Mips() const { return CPUTextureMips; } + bool Has_CPU_Texture_Mips() const { return !CPUTextureMips.empty(); } + void Release_CPU_Texture_Mips() { CPUTextureMips.clear(); CPUTextureMips.shrink_to_fit(); } + unsigned Get_CPU_Texture_Revision() const { return CPUTextureRevision; } + void Refresh_CPU_Texture_Snapshot(); + void Share_Texture_Storage_With(const TextureBaseClass *source); + bool Has_Compatibility_Texture() const; PoolType Get_Pool() const { return Pool; } bool Is_Missing_Texture(); + void Mark_Missing_Texture(bool missing) { IsMissingTexture = missing; } // Support for self managed textures bool Is_Dirty() { WWASSERT(Pool==POOL_DEFAULT); return Dirty; }; @@ -175,9 +194,6 @@ class TextureBaseClass : public RefCountClass unsigned Get_Reduction() const; - // Background texture loader will call this when texture has been loaded - virtual void Apply_New_Surface(IDirect3DBaseTexture8* tex, bool initialized, bool disable_auto_invalidation = false)=0; // If the parameter is true, the texture will be flagged as initialised - MipCountType MipLevelCount; // Inactivate textures that haven't been used in a while. Pass zero to use textures' @@ -196,22 +212,23 @@ class TextureBaseClass : public RefCountClass virtual CubeTextureClass* As_CubeTextureClass() { return nullptr; } virtual VolumeTextureClass* As_VolumeTextureClass() { return nullptr; } - IDirect3DTexture8* Peek_D3D_Texture() const { return (IDirect3DTexture8*)Peek_D3D_Base_Texture(); } - IDirect3DVolumeTexture8* Peek_D3D_VolumeTexture() const { return (IDirect3DVolumeTexture8*)Peek_D3D_Base_Texture(); } - IDirect3DCubeTexture8* Peek_D3D_CubeTexture() const { return (IDirect3DCubeTexture8*)Peek_D3D_Base_Texture(); } - protected: void Load_Locked_Surface(); - void Poke_Texture(IDirect3DBaseTexture8* tex) { D3DTexture = tex; } + void Set_CPU_Texture_Snapshot(std::vector &&mips); + void Update_CPU_Texture_Mip_Snapshot(unsigned int level, TextureMipSnapshot &&mip); + std::vector& Mutable_CPU_Texture_Mips() { return CPUTextureMips; } + void Mark_CPU_Texture_Mips_Changed(); bool Initialized; // For debug purposes the texture sets this true if it is a lightmap texture bool IsLightmap; + bool IsRenderTarget; bool IsCompressionAllowed; bool IsProcedural; bool IsReducible; + bool IsMissingTexture; unsigned InactivationTime; // In milliseconds @@ -228,9 +245,24 @@ class TextureBaseClass : public RefCountClass int Height; private: - - // Direct3D texture object - IDirect3DBaseTexture8 *D3DTexture; + virtual void Apply_Native_Compatibility_Texture(void *native_texture, bool initialized, bool disable_auto_invalidation = false)=0; + + TextureCompatibilityState *CompatibilityState; + void *Get_Native_Compatibility_Texture() const; + void Set_Native_Compatibility_Texture(void *native_texture); + std::vector CPUTextureMips; + unsigned CPUTextureRevision; + void Capture_CPU_Texture_Snapshot(void *native_texture); + void Clear_CPU_Texture_Snapshot(); + bool PreserveCPUTextureSnapshotOnNextLegacySet; + + // TheSuperHackers @refactor bobtista 21/04/2026 backend-neutral + // resource handle. Populated by the asset loader after it calls + // g_renderBackend->Create_Texture(). Parallel to the native compatibility + // state used by legacy/reference builds so existing compatibility code keeps + // working. Readers that want to stay backend-neutral should prefer + // m_backendHandle. + RenderResource m_backendHandle; // Name StringClass Name; @@ -264,9 +296,25 @@ class TextureBaseClass : public RefCountClass class TextureClass : public TextureBaseClass { W3DMPO_CODE(TextureClass) -// friend DX8Wrapper; public: + struct TextureAtlasRegion + { + unsigned X; + unsigned Y; + unsigned Width; + unsigned Height; + }; + struct MutableTextureMipView + { + WW3DFormat Format = WW3D_FORMAT_UNKNOWN; + unsigned Width = 0; + unsigned Height = 0; + unsigned Pitch = 0; + unsigned char *Data = nullptr; + bool Is_Valid() const { return Data != nullptr && Width != 0 && Height != 0 && Pitch != 0; } + }; + // Create texture with desired height, width and format. TextureClass @@ -300,8 +348,6 @@ class TextureClass : public TextureBaseClass MipCountType mip_level_count=MIP_LEVELS_ALL ); - TextureClass(IDirect3DBaseTexture8* d3d_texture); - // default constructors for derived classes (cube & vol) TextureClass ( @@ -318,14 +364,36 @@ class TextureClass : public TextureBaseClass virtual TexAssetType Get_Asset_Type() const override { return TEX_REGULAR; } virtual void Init() override; - - // Background texture loader will call this when texture has been loaded - virtual void Apply_New_Surface(IDirect3DBaseTexture8* tex, bool initialized, bool disable_auto_invalidation = false) override; // If the parameter is true, the texture will be flagged as initialised + void Clear_Atlas_Regions() { AtlasRegions.clear(); } + void Copy_Atlas_Regions_From(const TextureClass *texture) + { + if (texture != nullptr) { + AtlasRegions = texture->AtlasRegions; + } else { + AtlasRegions.clear(); + } + } + void Add_Atlas_Region(unsigned x, unsigned y, unsigned width, unsigned height) + { + TextureAtlasRegion region; + region.X = x; + region.Y = y; + region.Width = width; + region.Height = height; + AtlasRegions.push_back(region); + } + bool Has_Atlas_Regions() const { return !AtlasRegions.empty(); } + const std::vector &Get_Atlas_Regions() const { return AtlasRegions; } // Get the surface of one of the mipmap levels (defaults to highest-resolution one) SurfaceClass *Get_Surface_Level(unsigned int level = 0); - IDirect3DSurface8 *Get_D3D_Surface_Level(unsigned int level = 0); + MutableTextureMipView Begin_Mip_Write(unsigned int level = 0); + void End_Mip_Write(unsigned int level = 0); + void Update_Surface_Level_From_Surface(unsigned int level, const SurfaceClass::SurfaceImageData &image); void Get_Level_Description( SurfaceClass::SurfaceDescription & desc, unsigned int level = 0 ); + unsigned int Get_Level_Count() const; + bool Generate_Mip_Levels(); + void Set_LOD(unsigned int lod) const; TextureFilterClass& Get_Filter() { return Filter; } @@ -338,11 +406,20 @@ class TextureClass : public TextureBaseClass virtual TextureClass* As_TextureClass() override { return this; } protected: + TextureClass(void *legacy_texture); WW3DFormat TextureFormat; // legacy TextureFilterClass Filter; + std::vector AtlasRegions; + +private: + friend class TextureLoader; + friend class TextureLoadTaskClass; + friend class TextureCompatibilityInterop; + void Apply_Native_Compatibility_Texture(void *native_texture, bool initialized, bool disable_auto_invalidation = false) override; + void *Get_Native_Compatibility_Surface_Level(unsigned int level = 0); }; class ZTextureClass : public TextureBaseClass @@ -364,15 +441,14 @@ class ZTextureClass : public TextureBaseClass virtual void Init() override {} - // Background texture loader will call this when texture has been loaded - virtual void Apply_New_Surface(IDirect3DBaseTexture8* tex, bool initialized, bool disable_auto_invalidation = false) override; // If the parameter is true, the texture will be flagged as initialised - virtual void Apply(unsigned int stage) override; - IDirect3DSurface8 *Get_D3D_Surface_Level(unsigned int level = 0); virtual unsigned Get_Texture_Memory_Usage() const override; private: + friend class TextureCompatibilityInterop; + void Apply_Native_Compatibility_Texture(void *native_texture, bool initialized, bool disable_auto_invalidation = false) override; + void *Get_Native_Compatibility_Surface_Level(unsigned int level = 0); WW3DZFormat DepthStencilTextureFormat; }; @@ -412,14 +488,13 @@ class CubeTextureClass : public TextureClass MipCountType mip_level_count=MIP_LEVELS_ALL ); - CubeTextureClass(IDirect3DBaseTexture8* d3d_texture); - - virtual void Apply_New_Surface(IDirect3DBaseTexture8* tex, bool initialized, bool disable_auto_invalidation = false) override; // If the parameter is true, the texture will be flagged as initialised - virtual TexAssetType Get_Asset_Type() const override { return TEX_CUBEMAP; } virtual CubeTextureClass* As_CubeTextureClass() override { return this; } +private: + void Apply_Native_Compatibility_Texture(void *native_texture, bool initialized, bool disable_auto_invalidation = false) override; + }; class VolumeTextureClass : public TextureClass @@ -458,10 +533,6 @@ class VolumeTextureClass : public TextureClass MipCountType mip_level_count=MIP_LEVELS_ALL ); - VolumeTextureClass(IDirect3DBaseTexture8* d3d_texture); - - virtual void Apply_New_Surface(IDirect3DBaseTexture8* tex, bool initialized, bool disable_auto_invalidation = false) override; // If the parameter is true, the texture will be flagged as initialised - virtual TexAssetType Get_Asset_Type() const override { return TEX_VOLUME; } virtual VolumeTextureClass* As_VolumeTextureClass() override { return this; } @@ -469,6 +540,9 @@ class VolumeTextureClass : public TextureClass protected: int Depth; + +private: + void Apply_Native_Compatibility_Texture(void *native_texture, bool initialized, bool disable_auto_invalidation = false) override; }; // Utility functions for loading and saving texture descriptions from/to W3D files diff --git a/Core/Libraries/Source/WWVegas/WW3D2/texturecompat.h b/Core/Libraries/Source/WWVegas/WW3D2/texturecompat.h new file mode 100644 index 00000000000..abdaec08f1a --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/texturecompat.h @@ -0,0 +1,43 @@ +/* +** 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 + +class TextureBaseClass; + +enum LegacyTexturePoolKind +{ + LEGACY_TEXTURE_POOL_DEFAULT = 0, + LEGACY_TEXTURE_POOL_MANAGED = 1, + LEGACY_TEXTURE_POOL_SYSTEMMEM = 2 +}; + +struct LegacySurfaceCopyRect +{ + long left; + long top; + long right; + long bottom; +}; + +void Share_Legacy_Texture_With(TextureBaseClass &texture, const TextureBaseClass *source); +void Init_Legacy_Missing_Texture( + unsigned int width, + unsigned int height, + const unsigned int *pixels); +void Release_Legacy_Missing_Texture(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/texturecompatibilityinterop.cpp b/Core/Libraries/Source/WWVegas/WW3D2/texturecompatibilityinterop.cpp new file mode 100644 index 00000000000..da404ce2ad3 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/texturecompatibilityinterop.cpp @@ -0,0 +1,560 @@ +/* +** 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 . +*/ + +#include "texturecompatibilityinterop.h" + +#if defined(GGC_RENDER_BACKEND_BGFX) +#include "WWLib/win.h" +#else +#include +#include +#endif + +#if !defined(GGC_RENDER_BACKEND_BGFX) +#include "dx8formatconv.h" +#include "dx8wrapper.h" +#endif +#include "ffactory.h" +#include "IRenderBackend.h" +#include "missingtexture.h" +#include "RenderBackend.h" +#include "surfaceclass.h" +#include "texture.h" +#include "textureloader.h" +#include "WW3D2/ww3d.h" + +namespace +{ +#if defined(GGC_RENDER_BACKEND_BGFX) +#else + IDirect3DDevice8 *Legacy_Device() + { + DX8_Assert(); + return DX8_Call_Device(); + } +#endif + + LegacyLoaderTexture *s_missingTexture = nullptr; + constexpr unsigned kLegacyMipFilterBox = 5; + + HRESULT Filter_Legacy_Texture_Mips_Compat(LegacyBaseTexture *base_texture, unsigned int src_level) + { +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)base_texture; + (void)src_level; + WWASSERT_PRINT( + false, + "Filter_Legacy_Texture_Mips_Compat: standalone bgfx cannot filter fake-D3D texture mips"); + return E_FAIL; +#else + return D3DXFilterTexture(base_texture, nullptr, src_level, kLegacyMipFilterBox); +#endif + } + + HRESULT Copy_Legacy_Surface_Compat( + LegacySurface *destination, + const RECT *destination_rect, + LegacySurface *source, + const RECT *source_rect, + unsigned int filter) + { +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)destination; + (void)destination_rect; + (void)source; + (void)source_rect; + (void)filter; + WWASSERT_PRINT( + false, + "Copy_Legacy_Surface_Compat: standalone bgfx cannot copy fake-D3D surfaces"); + return E_FAIL; +#else + return D3DXLoadSurfaceFromSurface( + destination, + nullptr, + destination_rect, + source, + nullptr, + source_rect, + filter, + 0); +#endif + } +} + +LegacyBaseTexture *TextureCompatibilityInterop::Peek_Legacy_Base_Texture(const TextureBaseClass &texture) +{ + texture.LastAccessed=WW3D::Get_Sync_Time(); +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + texture.Get_Native_Compatibility_Texture() == nullptr, + "Peek_Legacy_Base_Texture: standalone bgfx cannot expose fake-D3D textures"); + return nullptr; +#else + return static_cast(texture.Get_Native_Compatibility_Texture()); +#endif +} + +LegacyLoaderTexture *TextureCompatibilityInterop::Peek_Legacy_Texture2D(const TextureBaseClass &texture) +{ + return reinterpret_cast(Peek_Legacy_Base_Texture(texture)); +} + +LegacyLoaderCubeTexture *TextureCompatibilityInterop::Peek_Legacy_Cube_Texture(const TextureBaseClass &texture) +{ + return reinterpret_cast(Peek_Legacy_Base_Texture(texture)); +} + +LegacyLoaderVolumeTexture *TextureCompatibilityInterop::Peek_Legacy_Volume_Texture(const TextureBaseClass &texture) +{ + return reinterpret_cast(Peek_Legacy_Base_Texture(texture)); +} + +void TextureCompatibilityInterop::Set_Legacy_Base_Texture(TextureBaseClass &texture, LegacyBaseTexture *native_texture) +{ + // (gth) Generals does stuff directly with the native texture pointer so lets + // reset the access timer whenever someone messes with this pointer. + texture.LastAccessed=WW3D::Get_Sync_Time(); + + LegacyBaseTexture *old_texture = static_cast(texture.Get_Native_Compatibility_Texture()); +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + old_texture == nullptr && native_texture == nullptr, + "Set_Legacy_Base_Texture: standalone bgfx cannot own fake-D3D textures"); +#else + if (old_texture != nullptr) { + old_texture->Release(); + } +#endif +#if defined(GGC_RENDER_BACKEND_BGFX) + texture.Set_Native_Compatibility_Texture(nullptr); +#else + texture.Set_Native_Compatibility_Texture(native_texture); +#endif +#if !defined(GGC_RENDER_BACKEND_BGFX) + if (native_texture != nullptr) { + native_texture->AddRef(); + } +#endif + bool preserve_cpu_snapshot = false; +#if defined(GGC_RENDER_BACKEND_BGFX) + preserve_cpu_snapshot = + texture.Has_CPU_Texture_Mips() + && texture.PreserveCPUTextureSnapshotOnNextLegacySet; +#endif + if (!preserve_cpu_snapshot) { + texture.Capture_CPU_Texture_Snapshot(texture.Get_Native_Compatibility_Texture()); + } + texture.PreserveCPUTextureSnapshotOnNextLegacySet = false; + + // Populate the backend-neutral handle after the legacy texture loader + // finished creating the compatibility texture. The backend either stores a + // wrapper around the legacy pointer or creates a parallel bgfx texture via + // the peek path. Skip when native_texture is null; that's a release, not a + // load. + if (texture.Get_Native_Compatibility_Texture() != nullptr && g_renderBackend != nullptr) { + if (texture.m_backendHandle != kInvalidRenderResource) { + g_renderBackend->Destroy_Resource(texture.m_backendHandle); + } + texture.m_backendHandle = g_renderBackend->Register_Texture_Resource(&texture); + } else if (texture.Get_Native_Compatibility_Texture() == nullptr && g_renderBackend != nullptr) { + if (texture.m_backendHandle != kInvalidRenderResource) { + g_renderBackend->Destroy_Resource(texture.m_backendHandle); + texture.m_backendHandle = kInvalidRenderResource; + } + g_renderBackend->Release_Cached_Texture(&texture); + } +} + +void TextureCompatibilityInterop::Share_Legacy_Texture_With(TextureBaseClass &texture, const TextureBaseClass *source) +{ + // TheSuperHackers @bugfix bobtista 28/05/2026 Only bump CPUTextureRevision when Set_Legacy_Base_Texture below will actually consume the preserved snapshot (i.e. the source has a non-null legacy texture to share). Bumping unconditionally invalidates downstream caches even when no real share happens. + LegacyBaseTexture *shared_legacy = source != nullptr ? Peek_Legacy_Base_Texture(*source) : nullptr; +#if defined(GGC_RENDER_BACKEND_BGFX) + if (source != nullptr + && shared_legacy != nullptr + && source->Has_CPU_Texture_Mips()) { + texture.CPUTextureMips = source->CPUTextureMips; + texture.PreserveCPUTextureSnapshotOnNextLegacySet = true; + ++texture.CPUTextureRevision; + } +#endif + Set_Legacy_Base_Texture(texture, shared_legacy); +} + +void Share_Legacy_Texture_With(TextureBaseClass &texture, const TextureBaseClass *source) +{ + TextureCompatibilityInterop::Share_Legacy_Texture_With(texture, source); +} + +void TextureCompatibilityInterop::Poke_Legacy_Texture(TextureBaseClass &texture, LegacyBaseTexture *native_texture) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + native_texture == nullptr, + "Poke_Legacy_Texture: standalone bgfx cannot store fake-D3D textures"); + texture.Set_Native_Compatibility_Texture(nullptr); +#else + texture.Set_Native_Compatibility_Texture(native_texture); +#endif +} + +void TextureCompatibilityInterop::Apply_Native_Compatibility_Texture( + TextureBaseClass &texture, + LegacyBaseTexture *native_texture, + bool initialized, + bool disable_auto_invalidation) +{ + texture.Apply_Native_Compatibility_Texture(native_texture, initialized, disable_auto_invalidation); +} + +LegacySurface *TextureCompatibilityInterop::Peek_Legacy_Surface(const SurfaceClass &surface, bool intentToWrite) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + surface.Get_Native_Compatibility_Surface() == nullptr, + "Peek_Legacy_Surface: standalone bgfx cannot expose fake-D3D surfaces"); + return nullptr; +#else + // TheSuperHackers @bugfix bobtista 28/05/2026 Only mark the CPU snapshot stale when the caller intends to write; read-only peeks (capture/back-buffer copy, ObjectPreview, etc.) leave the snapshot valid. + if (intentToWrite) + { + const_cast(surface).Mark_CPU_Surface_Snapshot_Stale(); + } + return static_cast(surface.Get_Native_Compatibility_Surface()); +#endif +} + +SurfaceClass *TextureCompatibilityInterop::Create_Legacy_Surface_Wrapper(LegacySurface *surface) +{ + return new SurfaceClass(surface); +} + +LegacySurface *TextureCompatibilityInterop::Get_Native_Compatibility_Surface_Level(TextureClass &texture, unsigned int level) +{ + return static_cast(texture.Get_Native_Compatibility_Surface_Level(level)); +} + +LegacySurface *TextureCompatibilityInterop::Get_Native_Compatibility_Surface_Level(ZTextureClass &texture, unsigned int level) +{ + return static_cast(texture.Get_Native_Compatibility_Surface_Level(level)); +} + +LegacySurface *TextureCompatibilityInterop::Create_Legacy_Surface( + unsigned int width, + unsigned int height, + WW3DFormat format) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "Create_Legacy_Surface: standalone bgfx cannot create fake-D3D surfaces"); + return nullptr; +#else + return DX8Wrapper::_Create_DX8_Surface(width, height, format); +#endif +} + +LegacySurface *TextureCompatibilityInterop::Create_Legacy_Surface_From_File(const char *filename) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "Create_Legacy_Surface_From_File: standalone bgfx cannot create fake-D3D surfaces"); + return nullptr; +#else + return DX8Wrapper::_Create_DX8_Surface(filename); +#endif +} + +LegacyLoaderTexture *TextureCompatibilityInterop::Create_Legacy_Texture( + unsigned int width, + unsigned int height, + WW3DFormat format, + MipCountType mip_level_count, + int pool, + bool render_target) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "Create_Legacy_Texture: standalone bgfx cannot create fake-D3D textures"); + return nullptr; +#else + return DX8Wrapper::_Create_DX8_Texture(width, height, format, mip_level_count, static_cast(pool), render_target); +#endif +} + +LegacyLoaderTexture *TextureCompatibilityInterop::Create_Legacy_Texture_From_Surface( + LegacySurface *surface, + MipCountType mip_level_count) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "Create_Legacy_Texture_From_Surface: standalone bgfx cannot create fake-D3D textures"); + return nullptr; +#else + return DX8Wrapper::_Create_DX8_Texture(surface, mip_level_count); +#endif +} + +LegacyLoaderTexture *TextureCompatibilityInterop::Create_Legacy_ZTexture( + unsigned int width, + unsigned int height, + WW3DZFormat zformat, + MipCountType mip_level_count, + int pool) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "Create_Legacy_ZTexture: standalone bgfx cannot create fake-D3D depth textures"); + return nullptr; +#else + return DX8Wrapper::_Create_DX8_ZTexture(width, height, zformat, mip_level_count, static_cast(pool)); +#endif +} + +LegacyLoaderCubeTexture *TextureCompatibilityInterop::Create_Legacy_Cube_Texture( + unsigned int width, + unsigned int height, + WW3DFormat format, + MipCountType mip_level_count, + int pool, + bool render_target) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "Create_Legacy_Cube_Texture: standalone bgfx cannot create fake-D3D cube textures"); + return nullptr; +#else + return DX8Wrapper::_Create_DX8_Cube_Texture(width, height, format, mip_level_count, static_cast(pool), render_target); +#endif +} + +LegacyLoaderVolumeTexture *TextureCompatibilityInterop::Create_Legacy_Volume_Texture( + unsigned int width, + unsigned int height, + unsigned int depth, + WW3DFormat format, + MipCountType mip_level_count, + int pool) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "Create_Legacy_Volume_Texture: standalone bgfx cannot create fake-D3D volume textures"); + return nullptr; +#else + return DX8Wrapper::_Create_DX8_Volume_Texture(width, height, depth, format, mip_level_count, static_cast(pool)); +#endif +} + +WW3DFormat TextureCompatibilityInterop::Legacy_Texture_Format_To_WW3DFormat(unsigned int format) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)format; + WWASSERT_PRINT( + false, + "Legacy_Texture_Format_To_WW3DFormat: standalone bgfx cannot decode D3D texture formats"); + return WW3D_FORMAT_UNKNOWN; +#else + return D3DFormat_To_WW3DFormat(static_cast(format)); +#endif +} + +bool TextureCompatibilityInterop::Generate_Legacy_Texture_Mips(TextureClass &texture) +{ + LegacyLoaderTexture *native_texture = Peek_Legacy_Texture2D(texture); + if (native_texture == nullptr) + { + return false; + } + + return SUCCEEDED(Filter_Legacy_Texture_Mips_Compat(reinterpret_cast(native_texture), 0)); +} + +LegacyLoaderTexture *Get_Legacy_Missing_Texture() +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "Get_Legacy_Missing_Texture: standalone bgfx cannot return fake-D3D missing textures"); + return nullptr; +#else + WWASSERT(s_missingTexture); + s_missingTexture->AddRef(); + return s_missingTexture; +#endif +} + +LegacySurface *Create_Legacy_Missing_Surface() +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "Create_Legacy_Missing_Surface: standalone bgfx cannot create fake-D3D missing surfaces"); + return nullptr; +#else + LegacySurface *texture_surface = nullptr; + DX8_ErrorCode(s_missingTexture->GetSurfaceLevel(0, &texture_surface)); + LegacySurfaceDesc texture_surface_desc; + ::ZeroMemory(&texture_surface_desc, sizeof(texture_surface_desc)); + DX8_ErrorCode(texture_surface->GetDesc(&texture_surface_desc)); + + LegacySurface *surface = nullptr; + DX8_ErrorCode(Legacy_Device()->CreateImageSurface( + texture_surface_desc.Width, + texture_surface_desc.Height, + texture_surface_desc.Format, + &surface)); + + LegacyLockedRect locked_rect; + ::ZeroMemory(&locked_rect, sizeof(locked_rect)); + DX8_ErrorCode(surface->LockRect(&locked_rect, nullptr, 0)); + + for (unsigned int y = 0; y < texture_surface_desc.Height; ++y) + { + unsigned int *buffer = reinterpret_cast( + static_cast(locked_rect.pBits) + locked_rect.Pitch * y); + for (unsigned int x = 0; x < texture_surface_desc.Width; ++x) + { + *buffer++ = 0x7FFF00FF; + } + } + + DX8_ErrorCode(surface->UnlockRect()); + texture_surface->Release(); + return surface; +#endif +} + +void Copy_Legacy_Surface( + LegacySurface *destination, + const LegacySurfaceCopyRect &destination_rect, + LegacySurface *source, + const LegacySurfaceCopyRect &source_rect, + unsigned int filter) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)destination; + (void)destination_rect; + (void)source; + (void)source_rect; + (void)filter; + WWASSERT_PRINT( + false, + "Copy_Legacy_Surface: standalone bgfx cannot copy fake-D3D surfaces"); +#else + RECT destination_native_rect; + destination_native_rect.left = destination_rect.left; + destination_native_rect.top = destination_rect.top; + destination_native_rect.right = destination_rect.right; + destination_native_rect.bottom = destination_rect.bottom; + RECT source_native_rect; + source_native_rect.left = source_rect.left; + source_native_rect.top = source_rect.top; + source_native_rect.right = source_rect.right; + source_native_rect.bottom = source_rect.bottom; + DX8_ErrorCode(Copy_Legacy_Surface_Compat( + destination, + &destination_native_rect, + source, + &source_native_rect, + filter)); +#endif +} + +void Init_Legacy_Missing_Texture( + unsigned int width, + unsigned int height, + const unsigned int *pixels) +{ + WWASSERT(!s_missingTexture); + +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "Init_Legacy_Missing_Texture: standalone bgfx cannot create fake-D3D missing textures"); + return; +#else + LegacyLoaderTexture *texture = Create_Legacy_Texture( + width, + height, + WW3D_FORMAT_A8R8G8B8, + MIP_LEVELS_ALL, + LEGACY_TEXTURE_POOL_MANAGED); + + LegacyLockedRect locked_rect; + RECT rect; + rect.left=0; + rect.right=width; + rect.top=0; + rect.bottom=height; + DX8_ErrorCode(texture->LockRect(0, &locked_rect, &rect, 0)); + + unsigned *buffer=static_cast(locked_rect.pBits); + for (unsigned y=0;y(locked_rect.pBits); + buffer+=locked_rect.Pitch/sizeof(unsigned)*(y+1); + } + + DX8_ErrorCode(texture->UnlockRect(0)); + + for (unsigned i=1;iGetLevelCount();++i) { + LegacySurface *src,*dst; + DX8_ErrorCode(texture->GetSurfaceLevel(i-1,&src)); + DX8_ErrorCode(texture->GetSurfaceLevel(i,&dst)); + + DX8_ErrorCode(Copy_Legacy_Surface_Compat( + dst, + nullptr, + src, + nullptr, + kLegacyMipFilterBox)); + + src->Release(); + dst->Release(); + } + + s_missingTexture=texture; +#endif +} + +void Release_Legacy_Missing_Texture() +{ + if (s_missingTexture != nullptr) { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "Release_Legacy_Missing_Texture: standalone bgfx cannot release fake-D3D missing textures"); +#else + s_missingTexture->Release(); +#endif + s_missingTexture=nullptr; + } +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/texturecompatibilityinterop.h b/Core/Libraries/Source/WWVegas/WW3D2/texturecompatibilityinterop.h new file mode 100644 index 00000000000..438f73bd12a --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/texturecompatibilityinterop.h @@ -0,0 +1,235 @@ +/* +** 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 "texturecompat.h" +#include "dx8texturelegacytypes.h" +#include "texturefilter.h" +#include "ww3dformat.h" + +class StringClass; +class SurfaceClass; +class TextureBaseClass; +class TextureClass; +class ZTextureClass; + +class TextureCompatibilityInterop +{ +public: + static LegacyBaseTexture *Peek_Legacy_Base_Texture(const TextureBaseClass &texture); + static LegacyLoaderTexture *Peek_Legacy_Texture2D(const TextureBaseClass &texture); + static LegacyLoaderCubeTexture *Peek_Legacy_Cube_Texture(const TextureBaseClass &texture); + static LegacyLoaderVolumeTexture *Peek_Legacy_Volume_Texture(const TextureBaseClass &texture); + static void Set_Legacy_Base_Texture(TextureBaseClass &texture, LegacyBaseTexture *native_texture); + static void Share_Legacy_Texture_With(TextureBaseClass &texture, const TextureBaseClass *source); + static void Poke_Legacy_Texture(TextureBaseClass &texture, LegacyBaseTexture *native_texture); + static void Apply_Native_Compatibility_Texture( + TextureBaseClass &texture, + LegacyBaseTexture *native_texture, + bool initialized, + bool disable_auto_invalidation = false); + + static LegacySurface *Peek_Legacy_Surface(const SurfaceClass &surface, bool intentToWrite = false); + static SurfaceClass *Create_Legacy_Surface_Wrapper(LegacySurface *surface); + static LegacySurface *Get_Native_Compatibility_Surface_Level(TextureClass &texture, unsigned int level = 0); + static LegacySurface *Get_Native_Compatibility_Surface_Level(ZTextureClass &texture, unsigned int level = 0); + static LegacySurface *Create_Legacy_Surface( + unsigned int width, + unsigned int height, + WW3DFormat format); + static LegacySurface *Create_Legacy_Surface_From_File(const char *filename); + + static LegacyLoaderTexture *Create_Legacy_Texture( + unsigned int width, + unsigned int height, + WW3DFormat format, + MipCountType mip_level_count, + int pool, + bool render_target = false); + static LegacyLoaderTexture *Create_Legacy_Texture_From_Surface( + LegacySurface *surface, + MipCountType mip_level_count); + static LegacyLoaderTexture *Create_Legacy_ZTexture( + unsigned int width, + unsigned int height, + WW3DZFormat zformat, + MipCountType mip_level_count, + int pool); + static LegacyLoaderCubeTexture *Create_Legacy_Cube_Texture( + unsigned int width, + unsigned int height, + WW3DFormat format, + MipCountType mip_level_count, + int pool, + bool render_target = false); + static LegacyLoaderVolumeTexture *Create_Legacy_Volume_Texture( + unsigned int width, + unsigned int height, + unsigned int depth, + WW3DFormat format, + MipCountType mip_level_count, + int pool); + static WW3DFormat Legacy_Texture_Format_To_WW3DFormat(unsigned int format); + static bool Generate_Legacy_Texture_Mips(TextureClass &texture); +}; + +inline LegacyBaseTexture *Peek_Legacy_Base_Texture(const TextureBaseClass &texture) +{ + return TextureCompatibilityInterop::Peek_Legacy_Base_Texture(texture); +} + +inline LegacyLoaderTexture *Peek_Legacy_Texture2D(const TextureBaseClass &texture) +{ + return TextureCompatibilityInterop::Peek_Legacy_Texture2D(texture); +} + +inline LegacyLoaderCubeTexture *Peek_Legacy_Cube_Texture(const TextureBaseClass &texture) +{ + return TextureCompatibilityInterop::Peek_Legacy_Cube_Texture(texture); +} + +inline LegacyLoaderVolumeTexture *Peek_Legacy_Volume_Texture(const TextureBaseClass &texture) +{ + return TextureCompatibilityInterop::Peek_Legacy_Volume_Texture(texture); +} + +inline void Set_Legacy_Base_Texture(TextureBaseClass &texture, LegacyBaseTexture *native_texture) +{ + TextureCompatibilityInterop::Set_Legacy_Base_Texture(texture, native_texture); +} + +inline void Poke_Legacy_Texture(TextureBaseClass &texture, LegacyBaseTexture *native_texture) +{ + TextureCompatibilityInterop::Poke_Legacy_Texture(texture, native_texture); +} + +inline void Apply_Native_Compatibility_Texture( + TextureBaseClass &texture, + LegacyBaseTexture *native_texture, + bool initialized, + bool disable_auto_invalidation = false) +{ + TextureCompatibilityInterop::Apply_Native_Compatibility_Texture(texture, native_texture, initialized, disable_auto_invalidation); +} + +inline LegacySurface *Peek_Legacy_Surface(const SurfaceClass &surface, bool intentToWrite = false) +{ + return TextureCompatibilityInterop::Peek_Legacy_Surface(surface, intentToWrite); +} + +inline SurfaceClass *Create_Legacy_Surface_Wrapper(LegacySurface *surface) +{ + return TextureCompatibilityInterop::Create_Legacy_Surface_Wrapper(surface); +} + +inline LegacySurface *Get_Native_Compatibility_Surface_Level(TextureClass &texture, unsigned int level = 0) +{ + return TextureCompatibilityInterop::Get_Native_Compatibility_Surface_Level(texture, level); +} + +inline LegacySurface *Get_Native_Compatibility_Surface_Level(ZTextureClass &texture, unsigned int level = 0) +{ + return TextureCompatibilityInterop::Get_Native_Compatibility_Surface_Level(texture, level); +} + +inline LegacySurface *Create_Legacy_Surface( + unsigned int width, + unsigned int height, + WW3DFormat format) +{ + return TextureCompatibilityInterop::Create_Legacy_Surface(width, height, format); +} + +inline LegacySurface *Create_Legacy_Surface_From_File(const char *filename) +{ + return TextureCompatibilityInterop::Create_Legacy_Surface_From_File(filename); +} + +inline LegacyLoaderTexture *Create_Legacy_Texture( + unsigned int width, + unsigned int height, + WW3DFormat format, + MipCountType mip_level_count, + int pool, + bool render_target = false) +{ + return TextureCompatibilityInterop::Create_Legacy_Texture(width, height, format, mip_level_count, pool, render_target); +} + +inline LegacyLoaderTexture *Create_Legacy_Texture_From_Surface( + LegacySurface *surface, + MipCountType mip_level_count) +{ + return TextureCompatibilityInterop::Create_Legacy_Texture_From_Surface(surface, mip_level_count); +} + +inline LegacyLoaderTexture *Create_Legacy_ZTexture( + unsigned int width, + unsigned int height, + WW3DZFormat zformat, + MipCountType mip_level_count, + int pool) +{ + return TextureCompatibilityInterop::Create_Legacy_ZTexture(width, height, zformat, mip_level_count, pool); +} + +inline LegacyLoaderCubeTexture *Create_Legacy_Cube_Texture( + unsigned int width, + unsigned int height, + WW3DFormat format, + MipCountType mip_level_count, + int pool, + bool render_target = false) +{ + return TextureCompatibilityInterop::Create_Legacy_Cube_Texture(width, height, format, mip_level_count, pool, render_target); +} + +inline LegacyLoaderVolumeTexture *Create_Legacy_Volume_Texture( + unsigned int width, + unsigned int height, + unsigned int depth, + WW3DFormat format, + MipCountType mip_level_count, + int pool) +{ + return TextureCompatibilityInterop::Create_Legacy_Volume_Texture(width, height, depth, format, mip_level_count, pool); +} + +inline WW3DFormat Legacy_Texture_Format_To_WW3DFormat(unsigned int format) +{ + return TextureCompatibilityInterop::Legacy_Texture_Format_To_WW3DFormat(format); +} + +inline bool Generate_Legacy_Texture_Mips(TextureClass &texture) +{ + return TextureCompatibilityInterop::Generate_Legacy_Texture_Mips(texture); +} + +LegacyLoaderTexture *Get_Legacy_Missing_Texture(); +LegacySurface *Create_Legacy_Missing_Surface(); +void Copy_Legacy_Surface( + LegacySurface *destination, + const LegacySurfaceCopyRect &destination_rect, + LegacySurface *source, + const LegacySurfaceCopyRect &source_rect, + unsigned int filter); +LegacySurface *Load_Legacy_Surface_Immediate( + const StringClass &filename, + WW3DFormat surface_format, + bool allow_compression); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/texturecompatibilitytypes.h b/Core/Libraries/Source/WWVegas/WW3D2/texturecompatibilitytypes.h new file mode 100644 index 00000000000..958b87f96dd --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/texturecompatibilitytypes.h @@ -0,0 +1,198 @@ +/* +** 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 + +#if !defined(GGC_RENDER_BACKEND_BGFX) +#error texturecompatibilitytypes.h is only for the standalone bgfx compatibility build. +#endif + +#include "WWLib/win.h" + +struct NativeCompatibilitySurfaceDesc +{ + unsigned int Format = 0; + unsigned int Type = 0; + unsigned int Usage = 0; + unsigned int Pool = 0; + unsigned int Size = 0; + unsigned int MultiSampleType = 0; + unsigned int Width = 0; + unsigned int Height = 0; +}; + +struct NativeCompatibilityVolumeDesc +{ + unsigned int Format = 0; + unsigned int Type = 0; + unsigned int Usage = 0; + unsigned int Pool = 0; + unsigned int Size = 0; + unsigned int Width = 0; + unsigned int Height = 0; + unsigned int Depth = 0; +}; + +struct NativeCompatibilityLockedRect +{ + int Pitch = 0; + void *pBits = nullptr; +}; + +struct NativeCompatibilityLockedBox +{ + int RowPitch = 0; + int SlicePitch = 0; + void *pBits = nullptr; +}; + +enum NativeCompatibilityCubeFace +{ + NATIVE_COMPATIBILITY_CUBE_FACE_POSITIVE_X = 0, + NATIVE_COMPATIBILITY_CUBE_FACE_NEGATIVE_X = 1, + NATIVE_COMPATIBILITY_CUBE_FACE_POSITIVE_Y = 2, + NATIVE_COMPATIBILITY_CUBE_FACE_NEGATIVE_Y = 3, + NATIVE_COMPATIBILITY_CUBE_FACE_POSITIVE_Z = 4, + NATIVE_COMPATIBILITY_CUBE_FACE_NEGATIVE_Z = 5, +}; + +struct NativeCompatibilityBaseTexture +{ + ULONG AddRef() { return 0; } + ULONG Release() { return 0; } + unsigned int GetLevelCount() { return 0; } +}; + +struct NativeCompatibilitySurface +{ + ULONG AddRef() { return 0; } + ULONG Release() { return 0; } + HRESULT GetDesc(NativeCompatibilitySurfaceDesc *desc) + { + if (desc != nullptr) { + *desc = NativeCompatibilitySurfaceDesc(); + } + return E_FAIL; + } + HRESULT LockRect(NativeCompatibilityLockedRect *locked_rect, const RECT *rect, DWORD flags) + { + (void)rect; + (void)flags; + if (locked_rect != nullptr) { + *locked_rect = NativeCompatibilityLockedRect(); + } + return E_FAIL; + } + HRESULT UnlockRect() { return E_FAIL; } +}; + +struct NativeCompatibilityTexture2D : NativeCompatibilityBaseTexture +{ + HRESULT GetSurfaceLevel(UINT level, NativeCompatibilitySurface **surface) + { + (void)level; + if (surface != nullptr) { + *surface = nullptr; + } + return E_FAIL; + } + HRESULT GetLevelDesc(UINT level, NativeCompatibilitySurfaceDesc *desc) + { + (void)level; + if (desc != nullptr) { + *desc = NativeCompatibilitySurfaceDesc(); + } + return E_FAIL; + } + HRESULT LockRect(UINT level, NativeCompatibilityLockedRect *locked_rect, const RECT *rect, DWORD flags) + { + (void)level; + (void)rect; + (void)flags; + if (locked_rect != nullptr) { + *locked_rect = NativeCompatibilityLockedRect(); + } + return E_FAIL; + } + HRESULT UnlockRect(UINT level) + { + (void)level; + return E_FAIL; + } +}; + +struct NativeCompatibilityCubeTexture : NativeCompatibilityBaseTexture +{ + HRESULT GetLevelDesc(UINT level, NativeCompatibilitySurfaceDesc *desc) + { + (void)level; + if (desc != nullptr) { + *desc = NativeCompatibilitySurfaceDesc(); + } + return E_FAIL; + } + HRESULT LockRect( + NativeCompatibilityCubeFace face, + UINT level, + NativeCompatibilityLockedRect *locked_rect, + const RECT *rect, + DWORD flags) + { + (void)face; + (void)level; + (void)rect; + (void)flags; + if (locked_rect != nullptr) { + *locked_rect = NativeCompatibilityLockedRect(); + } + return E_FAIL; + } + HRESULT UnlockRect(NativeCompatibilityCubeFace face, UINT level) + { + (void)face; + (void)level; + return E_FAIL; + } +}; + +struct NativeCompatibilityVolumeTexture : NativeCompatibilityBaseTexture +{ + HRESULT GetLevelDesc(UINT level, NativeCompatibilityVolumeDesc *desc) + { + (void)level; + if (desc != nullptr) { + *desc = NativeCompatibilityVolumeDesc(); + } + return E_FAIL; + } + HRESULT LockBox(UINT level, NativeCompatibilityLockedBox *locked_box, const void *box, DWORD flags) + { + (void)level; + (void)box; + (void)flags; + if (locked_box != nullptr) { + *locked_box = NativeCompatibilityLockedBox(); + } + return E_FAIL; + } + HRESULT UnlockBox(UINT level) + { + (void)level; + return E_FAIL; + } +}; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/texturefilter.cpp b/Core/Libraries/Source/WWVegas/WW3D2/texturefilter.cpp index 777f32a0bae..8d227cae4c3 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/texturefilter.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/texturefilter.cpp @@ -38,7 +38,8 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "texturefilter.h" -#include "dx8wrapper.h" +#include "RenderBackend.h" +#include "IRenderBackend.h" const char* const TextureFilterClass::TextureFilterModeString[TEXTURE_FILTER_COUNT] = { "None", @@ -58,9 +59,21 @@ TextureFilterClass::TextureFilterMode TextureFilterClass::getTextureFilterMode(c return TextureFilterClass::TEXTURE_FILTER_NONE; } -unsigned _MinTextureFilters[MAX_TEXTURE_STAGES][TextureFilterClass::FILTER_TYPE_COUNT]; -unsigned _MagTextureFilters[MAX_TEXTURE_STAGES][TextureFilterClass::FILTER_TYPE_COUNT]; -unsigned _MipMapFilters[MAX_TEXTURE_STAGES][TextureFilterClass::FILTER_TYPE_COUNT]; +static RenderBackendTextureSampleFilter _MinTextureFilters[RB_MAX_TEXTURE_STAGES][TextureFilterClass::FILTER_TYPE_COUNT]; +static RenderBackendTextureSampleFilter _MagTextureFilters[RB_MAX_TEXTURE_STAGES][TextureFilterClass::FILTER_TYPE_COUNT]; +static RenderBackendTextureSampleFilter _MipMapFilters[RB_MAX_TEXTURE_STAGES][TextureFilterClass::FILTER_TYPE_COUNT]; + +static RenderBackendTextureAddressMode TextureAddressToBackend(TextureFilterClass::TxtAddrMode mode) +{ + switch (mode) + { + case TextureFilterClass::TEXTURE_ADDRESS_CLAMP: + return RB_TEXTURE_ADDRESS_CLAMP; + case TextureFilterClass::TEXTURE_ADDRESS_REPEAT: + default: + return RB_TEXTURE_ADDRESS_WRAP; + } +} /************************************************************************* ** TextureFilterClass @@ -87,31 +100,16 @@ TextureFilterClass::TextureFilterClass(MipCountType mip_level_count) */ void TextureFilterClass::Apply(unsigned int stage) { - DX8Wrapper::Set_DX8_Texture_Stage_State(stage,D3DTSS_MINFILTER,_MinTextureFilters[stage][TextureMinFilter]); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage,D3DTSS_MAGFILTER,_MagTextureFilters[stage][TextureMagFilter]); - DX8Wrapper::Set_DX8_Texture_Stage_State(stage,D3DTSS_MIPFILTER,_MipMapFilters[stage][MipMapFilter]); - - switch (Get_U_Addr_Mode()) - { - case TEXTURE_ADDRESS_REPEAT: - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); - break; - - case TEXTURE_ADDRESS_CLAMP: - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); - break; - } - - switch (Get_V_Addr_Mode()) - { - case TEXTURE_ADDRESS_REPEAT: - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); - break; - - case TEXTURE_ADDRESS_CLAMP: - DX8Wrapper::Set_DX8_Texture_Stage_State(stage, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); - break; - } + g_renderBackend->Set_Texture_Sample_Filter( + stage, + _MinTextureFilters[stage][TextureMinFilter], + _MagTextureFilters[stage][TextureMagFilter], + _MipMapFilters[stage][MipMapFilter]); + g_renderBackend->Set_Texture_Address_Mode( + stage, + TextureAddressToBackend(Get_U_Addr_Mode()), + TextureAddressToBackend(Get_V_Addr_Mode()), + RB_TEXTURE_ADDRESS_WRAP); } //********************************************************************************************** @@ -120,22 +118,20 @@ void TextureFilterClass::Apply(unsigned int stage) */ void TextureFilterClass::_Init_Filters(TextureFilterMode texture_filter, AnisotropicFilterMode anisotropy_level) { - const D3DCAPS8& dx8caps=DX8Wrapper::Get_Current_Caps()->Get_DX8_Caps(); - // TheSuperHackers @info Init zero stage filter defaults, point filtering is the lowest type for non mip filtering - _MinTextureFilters[0][FILTER_TYPE_NONE]=D3DTEXF_POINT; - _MagTextureFilters[0][FILTER_TYPE_NONE]=D3DTEXF_POINT; - _MipMapFilters[0][FILTER_TYPE_NONE]=D3DTEXF_NONE; + _MinTextureFilters[0][FILTER_TYPE_NONE]=RB_TEXTURE_SAMPLE_POINT; + _MagTextureFilters[0][FILTER_TYPE_NONE]=RB_TEXTURE_SAMPLE_POINT; + _MipMapFilters[0][FILTER_TYPE_NONE]=RB_TEXTURE_SAMPLE_NONE; // Bilinear - _MinTextureFilters[0][FILTER_TYPE_FAST]=D3DTEXF_LINEAR; - _MagTextureFilters[0][FILTER_TYPE_FAST]=D3DTEXF_LINEAR; - _MipMapFilters[0][FILTER_TYPE_FAST]=D3DTEXF_POINT; + _MinTextureFilters[0][FILTER_TYPE_FAST]=RB_TEXTURE_SAMPLE_LINEAR; + _MagTextureFilters[0][FILTER_TYPE_FAST]=RB_TEXTURE_SAMPLE_LINEAR; + _MipMapFilters[0][FILTER_TYPE_FAST]=RB_TEXTURE_SAMPLE_POINT; // Anisotropic - MipMap interlayer filtering only goes up to linear - _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_ANISOTROPIC; - _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_ANISOTROPIC; - _MipMapFilters[0][FILTER_TYPE_BEST]=D3DTEXF_LINEAR; + _MinTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_ANISOTROPIC; + _MagTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_ANISOTROPIC; + _MipMapFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_LINEAR; // TheSuperHackers @feature Mauller 08/03/2026 Add full support for all texture filtering modes; // None, Point, Bilinear, Trilinear, Anisotropic. @@ -149,89 +145,92 @@ void TextureFilterClass::_Init_Filters(TextureFilterMode texture_filter, Anisotr case TEXTURE_FILTER_NONE: - _MinTextureFilters[0][FILTER_TYPE_FAST]=D3DTEXF_POINT; - _MagTextureFilters[0][FILTER_TYPE_FAST]=D3DTEXF_POINT; - _MipMapFilters[0][FILTER_TYPE_FAST]=D3DTEXF_NONE; + _MinTextureFilters[0][FILTER_TYPE_FAST]=RB_TEXTURE_SAMPLE_POINT; + _MagTextureFilters[0][FILTER_TYPE_FAST]=RB_TEXTURE_SAMPLE_POINT; + _MipMapFilters[0][FILTER_TYPE_FAST]=RB_TEXTURE_SAMPLE_NONE; - _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; - _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; - _MipMapFilters[0][FILTER_TYPE_BEST]=D3DTEXF_NONE; + _MinTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_POINT; + _MagTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_POINT; + _MipMapFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_NONE; break; case TEXTURE_FILTER_POINT: - _MinTextureFilters[0][FILTER_TYPE_FAST]=D3DTEXF_POINT; - _MagTextureFilters[0][FILTER_TYPE_FAST]=D3DTEXF_POINT; - _MipMapFilters[0][FILTER_TYPE_FAST]=D3DTEXF_POINT; + _MinTextureFilters[0][FILTER_TYPE_FAST]=RB_TEXTURE_SAMPLE_POINT; + _MagTextureFilters[0][FILTER_TYPE_FAST]=RB_TEXTURE_SAMPLE_POINT; + _MipMapFilters[0][FILTER_TYPE_FAST]=RB_TEXTURE_SAMPLE_POINT; - _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; - _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; - _MipMapFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; + _MinTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_POINT; + _MagTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_POINT; + _MipMapFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_POINT; break; case TEXTURE_FILTER_BILINEAR: - FilterSupported = (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MINFLINEAR) && - (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MAGFLINEAR); + FilterSupported = g_renderBackend && + g_renderBackend->Supports_Texture_Filter(RB_TEXTURE_FILTER_MIN_LINEAR) && + g_renderBackend->Supports_Texture_Filter(RB_TEXTURE_FILTER_MAG_LINEAR); if (FilterSupported) { - _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_LINEAR; - _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_LINEAR; + _MinTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_LINEAR; + _MagTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_LINEAR; } else { - _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; - _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; + _MinTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_POINT; + _MagTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_POINT; } - _MipMapFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; + _MipMapFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_POINT; break; case TEXTURE_FILTER_TRILINEAR: - FilterSupported = (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MINFLINEAR) && - (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MAGFLINEAR); + FilterSupported = g_renderBackend && + g_renderBackend->Supports_Texture_Filter(RB_TEXTURE_FILTER_MIN_LINEAR) && + g_renderBackend->Supports_Texture_Filter(RB_TEXTURE_FILTER_MAG_LINEAR); if (FilterSupported) { - _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_LINEAR; - _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_LINEAR; + _MinTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_LINEAR; + _MagTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_LINEAR; } else { - _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; - _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; + _MinTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_POINT; + _MagTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_POINT; } - if (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MIPFLINEAR) { - _MipMapFilters[0][FILTER_TYPE_BEST]=D3DTEXF_LINEAR; + if (g_renderBackend && g_renderBackend->Supports_Texture_Filter(RB_TEXTURE_FILTER_MIP_LINEAR)) { + _MipMapFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_LINEAR; } else { // TheSuperHackers @info if only linear mipmap filtering is unsupported, // Trilinear filtering becomes Bilinear filtering by default - _MipMapFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; + _MipMapFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_POINT; } break; case TEXTURE_FILTER_ANISOTROPIC: - FilterSupported = (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MAGFANISOTROPIC) && - (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MINFANISOTROPIC); + FilterSupported = g_renderBackend && + g_renderBackend->Supports_Texture_Filter(RB_TEXTURE_FILTER_MAG_ANISOTROPIC) && + g_renderBackend->Supports_Texture_Filter(RB_TEXTURE_FILTER_MIN_ANISOTROPIC); if (FilterSupported) { - _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_ANISOTROPIC; - _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_ANISOTROPIC; + _MinTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_ANISOTROPIC; + _MagTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_ANISOTROPIC; // Set the Anisotropic filtering level for all stages _Set_Max_Anisotropy(anisotropy_level); } else { - _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; - _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; + _MinTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_POINT; + _MagTextureFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_POINT; } - if (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MIPFLINEAR) { - _MipMapFilters[0][FILTER_TYPE_BEST]=D3DTEXF_LINEAR; + if (g_renderBackend && g_renderBackend->Supports_Texture_Filter(RB_TEXTURE_FILTER_MIP_LINEAR)) { + _MipMapFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_LINEAR; } else { - _MipMapFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; + _MipMapFilters[0][FILTER_TYPE_BEST]=RB_TEXTURE_SAMPLE_POINT; } break; @@ -240,7 +239,7 @@ void TextureFilterClass::_Init_Filters(TextureFilterMode texture_filter, Anisotr // For stages above zero, set best filter to the same as the stage zero int i=1; - for (;iSet_Texture_Max_Anisotropy(stage, mode); } //********************************************************************************************** @@ -307,7 +306,7 @@ void TextureFilterClass::_Set_Max_Anisotropy(AnisotropicFilterMode mode) */ void TextureFilterClass::_Set_Default_Min_Filter(FilterType filter) { - for (int i=0;i +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "WWDebug/wwmemlog.h" -#include "formconv.h" +#include "WW3D2/dx8texturelegacytypes.h" +#include "WW3D2/texturecompatibilityinterop.h" #include "texturethumbnail.h" #include "WW3D2/ddsfile.h" #include "bitmaphandler.h" +#include "WW3D2/DXTUtils.h" #include "WWDebug/wwprofile.h" +#include +#include +#include + +namespace +{ + unsigned s_mainRenderThreadId = 0; + + constexpr auto kLegacyManagedPool = LEGACY_TEXTURE_POOL_MANAGED; + constexpr auto kLegacySystemPool = LEGACY_TEXTURE_POOL_SYSTEMMEM; + constexpr auto kLegacyDefaultPool = LEGACY_TEXTURE_POOL_DEFAULT; + + bool Is_CPU_Texture_Snapshot_Staging_Format(WW3DFormat format) + { + switch (format) + { + case WW3D_FORMAT_R5G6B5: + case WW3D_FORMAT_A1R5G5B5: + case WW3D_FORMAT_A4R4G4B4: + case WW3D_FORMAT_A8: + case WW3D_FORMAT_L8: + case WW3D_FORMAT_A8R8G8B8: + case WW3D_FORMAT_X8R8G8B8: + case WW3D_FORMAT_DXT1: + case WW3D_FORMAT_DXT2: + case WW3D_FORMAT_DXT3: + case WW3D_FORMAT_DXT4: + case WW3D_FORMAT_DXT5: + return true; + default: + return false; + } + } + + bool Is_CPU_Texture_Snapshot_DXT_Format(WW3DFormat format) + { + switch (format) + { + case WW3D_FORMAT_DXT1: + case WW3D_FORMAT_DXT2: + case WW3D_FORMAT_DXT3: + case WW3D_FORMAT_DXT4: + case WW3D_FORMAT_DXT5: + return true; + default: + return false; + } + } + + unsigned Get_DXT_Block_Byte_Count(WW3DFormat format) + { + WWASSERT(Is_CPU_Texture_Snapshot_DXT_Format(format)); + return format == WW3D_FORMAT_DXT1 ? 8 : 16; + } + + bool Get_CPU_Texture_Snapshot_Staging_Layout( + WW3DFormat format, + unsigned int width, + unsigned int height, + unsigned int &pitch, + unsigned int &rows) + { + switch (format) + { + case WW3D_FORMAT_R5G6B5: + case WW3D_FORMAT_A1R5G5B5: + case WW3D_FORMAT_A4R4G4B4: + case WW3D_FORMAT_A8: + case WW3D_FORMAT_L8: + case WW3D_FORMAT_A8R8G8B8: + case WW3D_FORMAT_X8R8G8B8: + { + const unsigned int bytes_per_pixel = Get_Bytes_Per_Pixel(format); + if (bytes_per_pixel == 0) { + return false; + } + pitch = width * bytes_per_pixel; + rows = height; + return true; + } + + case WW3D_FORMAT_DXT1: + case WW3D_FORMAT_DXT2: + case WW3D_FORMAT_DXT3: + case WW3D_FORMAT_DXT4: + case WW3D_FORMAT_DXT5: + pitch = DXT_SurfacePitch(width, Get_DXT_Block_Byte_Count(format)); + rows = DXT_SurfaceRows(height); + return true; + + default: + return false; + } + } +} + +class TextureLoadTaskListNodeClass +{ + friend class TextureLoadTaskListClass; + + public: + TextureLoadTaskListNodeClass() : Next(0), Prev(0) { } + + TextureLoadTaskListClass *Get_List() { return List; } + + TextureLoadTaskListNodeClass *Next; + TextureLoadTaskListNodeClass *Prev; + TextureLoadTaskListClass * List; +}; + + +class TextureLoadTaskListClass +{ + // This class implements an unsynchronized, double-linked list of TextureLoadTaskClass + // objects, using an embedded list node. + + public: + TextureLoadTaskListClass(); + + // Returns true if list is empty, false otherwise. + bool Is_Empty () const { return (Root.Next == &Root); } + + // Add a task to beginning of list + void Push_Front (TextureLoadTaskClass *task); + + // Add a task to end of list + void Push_Back (TextureLoadTaskClass *task); + + // Remove and return a task from beginning of list, or null if list is empty. + TextureLoadTaskClass * Pop_Front (); + + // Remove and return a task from end of list, or null if list is empty + TextureLoadTaskClass * Pop_Back (); + + // Remove specified task from list, if present + void Remove (TextureLoadTaskClass *task); + + private: + // This list is implemented using a sentinel node. + TextureLoadTaskListNodeClass Root; +}; + + +class SynchronizedTextureLoadTaskListClass : public TextureLoadTaskListClass +{ + // This class added thread-safety to the basic TextureLoadTaskListClass. + + public: + SynchronizedTextureLoadTaskListClass(); + + // See comments above for description of member functions. + void Push_Front (TextureLoadTaskClass *task); + void Push_Back (TextureLoadTaskClass *task); + TextureLoadTaskClass * Pop_Front (); + TextureLoadTaskClass * Pop_Back (); + void Remove (TextureLoadTaskClass *task); + + private: + FastCriticalSectionClass CriticalSection; +}; + +/* +** (gth) The allocation system we're using for TextureLoadTaskClass has gotten a little +** complicated since Kenny added the new task types for Cube and Volume textures. The +** ::Destroy member is used to return a task to the pool now and must be over-ridden in +** each derived class to put the task back into the correct free list. +*/ + + +class TextureLoadTaskClass : public TextureLoadTaskListNodeClass +{ + public: + enum TaskType { + TASK_NONE, + TASK_THUMBNAIL, + TASK_LOAD, + }; + + enum PriorityType { + PRIORITY_LOW, + PRIORITY_HIGH, + }; + + enum StateType { + STATE_NONE, + + STATE_LOAD_BEGUN, + STATE_LOAD_MIPMAP, + STATE_LOAD_COMPLETE, + + STATE_COMPLETE, + }; + + + TextureLoadTaskClass(); + ~TextureLoadTaskClass(); + + static TextureLoadTaskClass * Create (TextureBaseClass *tc, TaskType type, PriorityType priority); + static void Delete_Free_Pool (); + + virtual void Destroy (); + virtual void Init (TextureBaseClass *tc, TaskType type, PriorityType priority); + virtual void Deinit (); + + TaskType Get_Type () const { return Type; } + PriorityType Get_Priority () const { return Priority; } + StateType Get_State () const { return State; } + + WW3DFormat Get_Format () const { return Format; } + unsigned int Get_Width () const { return Width; } + unsigned int Get_Height () const { return Height; } + unsigned int Get_Mip_Level_Count () const { return MipLevelCount; } + unsigned int Get_Reduction () const { return Reduction; } + + unsigned char * Get_Locked_Surface_Ptr (unsigned int level); + unsigned int Get_Locked_Surface_Pitch(unsigned int level) const; + + TextureBaseClass * Peek_Texture () { return Texture; } + LegacyLoaderTexture * Peek_Native_Compatibility_Texture () { return static_cast(NativeCompatibilityTexture); } + + void Set_Type (TaskType t) { Type = t; } + void Set_Priority (PriorityType p) { Priority = p; } + void Set_State (StateType s) { State = s; } + + bool Begin_Load (); + bool Load (); + void End_Load (); + void Finish_Load (); + void Apply_Missing_Texture (); + + protected: + virtual bool Begin_Compressed_Load (); + virtual bool Begin_Uncompressed_Load (); + + virtual bool Load_Compressed_Mipmap (); + virtual bool Load_Uncompressed_Mipmap(); + + virtual void Lock_Surfaces (); + virtual void Unlock_Surfaces (); + void Capture_CPU_Texture_Snapshot_From_Locked_Surfaces(); + bool Should_Use_CPU_Texture_Snapshot_Staging() const; + unsigned int Get_Requested_Mip_Level_Count(unsigned int width, unsigned int height) const; + void Allocate_CPU_Texture_Staging(); + void Commit_CPU_Texture_Staging(bool initialize); + + void Apply (bool initialize); + + TextureBaseClass* Texture; + void* NativeCompatibilityTexture; + WW3DFormat Format; + + unsigned int Width; + unsigned int Height; + unsigned int MipLevelCount; + unsigned int Reduction; + Vector3 HSVShift; + + unsigned char * LockedSurfacePtr[MIP_LEVELS_MAX]; + unsigned int LockedSurfacePitch[MIP_LEVELS_MAX]; + std::vector StagedCPUTextureMips; + bool UseCPUTextureSnapshotStaging; + + TaskType Type; + PriorityType Priority; + StateType State; +}; + +class CubeTextureLoadTaskClass : public TextureLoadTaskClass +{ +public: + CubeTextureLoadTaskClass(); + + virtual void Destroy () override; + virtual void Init (TextureBaseClass *tc, TaskType type, PriorityType priority) override; + virtual void Deinit () override; + +protected: + virtual bool Begin_Compressed_Load () override; + virtual bool Begin_Uncompressed_Load () override; + + virtual bool Load_Compressed_Mipmap () override; +// virtual bool Load_Uncompressed_Mipmap() override; + + virtual void Lock_Surfaces () override; + virtual void Unlock_Surfaces () override; + +private: + unsigned char* Get_Locked_CubeMap_Surface_Pointer(unsigned int face, unsigned int level); + unsigned int Get_Locked_CubeMap_Surface_Pitch(unsigned int face, unsigned int level) const; + + LegacyLoaderCubeTexture* Peek_Native_Compatibility_Cube_Texture() { return static_cast(NativeCompatibilityTexture); } + + unsigned char* LockedCubeSurfacePtr[6][MIP_LEVELS_MAX]; + unsigned int LockedCubeSurfacePitch[6][MIP_LEVELS_MAX]; +}; + +class VolumeTextureLoadTaskClass : public TextureLoadTaskClass +{ +public: + VolumeTextureLoadTaskClass(); + + virtual void Destroy () override; + virtual void Init (TextureBaseClass *tc, TaskType type, PriorityType priority) override; + +protected: + virtual bool Begin_Compressed_Load () override; + virtual bool Begin_Uncompressed_Load () override; + + virtual bool Load_Compressed_Mipmap () override; +// virtual bool Load_Uncompressed_Mipmap() override; + + virtual void Lock_Surfaces () override; + virtual void Unlock_Surfaces () override; + +private: + unsigned char* Get_Locked_Volume_Pointer(unsigned int level); + unsigned int Get_Locked_Volume_Row_Pitch(unsigned int level); + unsigned int Get_Locked_Volume_Slice_Pitch(unsigned int level); + +#if !defined(GGC_RENDER_BACKEND_BGFX) + auto* Peek_Native_Compatibility_Volume_Texture() { return static_cast(NativeCompatibilityTexture); } +#endif + + unsigned int LockedSurfaceSlicePitch[MIP_LEVELS_MAX]; + + unsigned int Depth; +}; bool TextureLoader::TextureLoadSuspended; int TextureLoader::TextureInactiveOverrideTime = 0; #define USE_MANAGED_TEXTURES +void TextureLoader::Delete_Texture_Load_Tasks(TextureBaseClass *tc) +{ + delete tc->TextureLoadTask; + tc->TextureLoadTask = nullptr; + delete tc->ThumbnailLoadTask; + tc->ThumbnailLoadTask = nullptr; +} + //////////////////////////////////////////////////////////////////////////////// // // TextureLoadTaskListClass implementation @@ -178,24 +517,19 @@ void SynchronizedTextureLoadTaskListClass::Push_Back(TextureLoadTaskClass *task) TextureLoadTaskClass *SynchronizedTextureLoadTaskListClass::Pop_Front() { - // this duplicates code inside base class, but saves us an unnecessary lock. + FastCriticalSectionClass::LockClass lock(CriticalSection); if (Is_Empty()) { return nullptr; } - - FastCriticalSectionClass::LockClass lock(CriticalSection); return TextureLoadTaskListClass::Pop_Front(); - } TextureLoadTaskClass *SynchronizedTextureLoadTaskListClass::Pop_Back() { - // this duplicates code inside base class, but saves us an unnecessary lock. + FastCriticalSectionClass::LockClass lock(CriticalSection); if (Is_Empty()) { return nullptr; } - - FastCriticalSectionClass::LockClass lock(CriticalSection); return TextureLoadTaskListClass::Pop_Back(); } @@ -224,6 +558,20 @@ static TextureLoadTaskListClass _TexLoadFreeList; static TextureLoadTaskListClass _CubeTexLoadFreeList; static TextureLoadTaskListClass _VolTexLoadFreeList; +static void Log_Texture_Load_Failure(const char *reason, const char *filename) +{ + char message[512]; + snprintf( + message, + sizeof(message), + "Missing texture %s: %s\n", + reason ? reason : "load failed", + filename ? filename : "(null)"); + fprintf(stderr, "%s", message); + fflush(stderr); + OutputDebugString(message); +} + // The background texture loading thread. static class LoaderThreadClass : public ThreadClass @@ -239,8 +587,9 @@ static class LoaderThreadClass : public ThreadClass } _TextureLoadThread; +#if !defined(GGC_RENDER_BACKEND_BGFX) // TODO: Legacy - remove this call! -IDirect3DTexture8* Load_Compressed_Texture( +static LegacyLoaderTexture * Load_Compressed_Texture( const StringClass& filename, unsigned reduction_factor, MipCountType mip_level_count, @@ -260,31 +609,40 @@ IDirect3DTexture8* Load_Compressed_Texture( // Note that the nearest valid format could be anything, even uncompressed. if (dest_format==WW3D_FORMAT_UNKNOWN) dest_format=Get_Valid_Texture_Format(dds_file.Get_Format(),true); - IDirect3DTexture8* d3d_texture = DX8Wrapper::_Create_DX8_Texture + LegacyLoaderTexture * d3d_texture = Create_Legacy_Texture ( width, height, dest_format, - (MipCountType)mips + (MipCountType)mips, + LEGACY_TEXTURE_POOL_MANAGED ); for (unsigned level=0;levelGetSurfaceLevel(level/*-reduction_factor*/,&d3d_surface)); - dds_file.Copy_Level_To_Surface(level,d3d_surface); - d3d_surface->Release(); + DX8_ErrorCode(d3d_texture->LockRect(level,&locked_rect,nullptr,0)); + dds_file.Copy_Level_To_Surface( + level, + dest_format, + dds_file.Get_Width(level), + dds_file.Get_Height(level), + reinterpret_cast(locked_rect.pBits), + locked_rect.Pitch); + DX8_ErrorCode(d3d_texture->UnlockRect(level)); } return d3d_texture; } +#endif static bool Is_Format_Compressed(WW3DFormat texture_format,bool allow_compression) { // Verify that the user isn't requesting compressed texture without hardware support bool compressed=false; + const bool supports_compression = g_renderBackend && g_renderBackend->Supports_Compressed_Textures(); if (texture_format!=WW3D_FORMAT_UNKNOWN) { - if (!DX8Wrapper::Get_Current_Caps()->Support_DXTC() || !allow_compression) { + if (!supports_compression || !allow_compression) { WWASSERT(texture_format!=WW3D_FORMAT_DXT1); WWASSERT(texture_format!=WW3D_FORMAT_DXT2); WWASSERT(texture_format!=WW3D_FORMAT_DXT3); @@ -304,12 +662,32 @@ static bool Is_Format_Compressed(WW3DFormat texture_format,bool allow_compressio // defined as non-compressed. compressed|=( texture_format==WW3D_FORMAT_UNKNOWN && - DX8Wrapper::Get_Current_Caps()->Support_DXTC() && + supports_compression && allow_compression); return compressed; } +// TheSuperHackers @tweak bobtista 05/06/2026 Conservative fallbacks when the backend +// reports no texture limits, so callers get a usable cap instead of zero. +static const unsigned DEFAULT_MAX_TEXTURE_DIMENSION = 2048; +static const unsigned DEFAULT_MAX_TEXTURE_ASPECT = 8; + +static RenderBackendTextureLimits Get_Backend_Texture_Limits() +{ + if (g_renderBackend) + { + RenderBackendTextureLimits limits = g_renderBackend->Get_Texture_Limits(); + if (limits.max_width == 0) limits.max_width = DEFAULT_MAX_TEXTURE_DIMENSION; + if (limits.max_height == 0) limits.max_height = DEFAULT_MAX_TEXTURE_DIMENSION; + if (limits.max_volume_extent == 0) limits.max_volume_extent = DEFAULT_MAX_TEXTURE_DIMENSION; + if (limits.max_aspect_ratio == 0) limits.max_aspect_ratio = DEFAULT_MAX_TEXTURE_ASPECT; + return limits; + } + + return { DEFAULT_MAX_TEXTURE_DIMENSION, DEFAULT_MAX_TEXTURE_DIMENSION, DEFAULT_MAX_TEXTURE_DIMENSION, DEFAULT_MAX_TEXTURE_ASPECT }; +} + //////////////////////////////////////////////////////////////////////////////// // @@ -320,6 +698,7 @@ static bool Is_Format_Compressed(WW3DFormat texture_format,bool allow_compressio void TextureLoader::Init() { WWASSERT(!_TextureLoadThread.Is_Running()); + s_mainRenderThreadId = ThreadClass::_Get_Current_Thread_ID(); ThumbnailManagerClass::Init(); @@ -339,9 +718,13 @@ void TextureLoader::Deinit() } -bool TextureLoader::Is_DX8_Thread() +bool TextureLoader::Is_Main_Render_Thread() { +#if defined(GGC_RENDER_BACKEND_BGFX) + return (ThreadClass::_Get_Current_Thread_ID() == s_mainRenderThreadId); +#else return (ThreadClass::_Get_Current_Thread_ID() == DX8Wrapper::_Get_Main_Thread_ID()); +#endif } @@ -358,7 +741,7 @@ void TextureLoader::Validate_Texture_Size unsigned& depth ) { - const D3DCAPS8& dx8caps=DX8Wrapper::Get_Current_Caps()->Get_DX8_Caps(); + const RenderBackendTextureLimits limits = Get_Backend_Texture_Limits(); unsigned poweroftwowidth = 1; while (poweroftwowidth < width) @@ -373,40 +756,36 @@ void TextureLoader::Validate_Texture_Size } unsigned poweroftwodepth = 1; - while (poweroftwodepth < depth) + while (poweroftwodepth< depth) { poweroftwodepth <<= 1; } - if (poweroftwowidth>dx8caps.MaxTextureWidth) + if (poweroftwowidth>limits.max_width) { - poweroftwowidth=dx8caps.MaxTextureWidth; + poweroftwowidth=limits.max_width; } - if (poweroftwoheight>dx8caps.MaxTextureHeight) + if (poweroftwoheight>limits.max_height) { - poweroftwoheight=dx8caps.MaxTextureHeight; + poweroftwoheight=limits.max_height; } - if (poweroftwodepth>dx8caps.MaxVolumeExtent) + if (poweroftwodepth>limits.max_volume_extent) { - poweroftwodepth=dx8caps.MaxVolumeExtent; + poweroftwodepth=limits.max_volume_extent; } - const unsigned maxTextureAspectRatio = dx8caps.MaxTextureAspectRatio; - if (maxTextureAspectRatio != 0) + if (poweroftwowidth>poweroftwoheight) { - if (poweroftwowidth>poweroftwoheight) + while (poweroftwowidth/poweroftwoheight>limits.max_aspect_ratio) { - while (poweroftwowidth/poweroftwoheight > maxTextureAspectRatio) - { - poweroftwoheight*=2; - } + poweroftwoheight*=2; } - else + } + else + { + while (poweroftwoheight/poweroftwowidth>limits.max_aspect_ratio) { - while (poweroftwoheight/poweroftwowidth > maxTextureAspectRatio) - { - poweroftwowidth*=2; - } + poweroftwowidth*=2; } } @@ -415,16 +794,18 @@ void TextureLoader::Validate_Texture_Size depth=poweroftwodepth; } -IDirect3DTexture8* TextureLoader::Load_Thumbnail(const StringClass& filename, const Vector3& hsv_shift)//,WW3DFormat texture_format) +#if !defined(GGC_RENDER_BACKEND_BGFX) +static LegacyLoaderTexture * Load_Legacy_Thumbnail(const StringClass& filename, const Vector3& hsv_shift)//,WW3DFormat texture_format) { - WWASSERT(Is_DX8_Thread()); + WWASSERT(TextureLoader::Is_Main_Render_Thread()); ThumbnailClass* thumb=nullptr; thumb=ThumbnailManagerClass::Peek_Thumbnail_Instance_From_Any_Manager(filename); // If no thumb is found return a missing texture if (!thumb) { - return MissingTexture::_Get_Missing_Texture(); + Log_Texture_Load_Failure("thumbnail", filename); + return Get_Legacy_Missing_Texture(); } WWASSERT(thumb->Get_Format()==WW3D_FORMAT_A4R4G4B4); @@ -439,19 +820,19 @@ IDirect3DTexture8* TextureLoader::Load_Thumbnail(const StringClass& filename, co WWASSERT(dest_format==texture_format); } - IDirect3DTexture8* sysmem_texture = DX8Wrapper::_Create_DX8_Texture( + LegacyLoaderTexture * sysmem_texture = Create_Legacy_Texture( thumb->Get_Width(), thumb->Get_Height(), dest_format, MIP_LEVELS_ALL, #ifdef USE_MANAGED_TEXTURES - D3DPOOL_MANAGED); + kLegacyManagedPool); #else - D3DPOOL_SYSTEMMEM); + kLegacySystemPool); #endif unsigned level=0; - D3DLOCKED_RECT locked_rects[12]={0}; + LegacyLoaderLockedRect locked_rects[12]={0}; WWASSERT(sysmem_texture->GetLevelCount()<=12); // Lock all surfaces @@ -499,12 +880,12 @@ IDirect3DTexture8* TextureLoader::Load_Thumbnail(const StringClass& filename, co #ifdef USE_MANAGED_TEXTURES return sysmem_texture; #else - IDirect3DTexture8* d3d_texture = DX8Wrapper::_Create_DX8_Texture( + LegacyLoaderTexture * d3d_texture = Create_Legacy_Texture( thumb->Get_Width(), thumb->Get_Height(), dest_format, TextureBaseClass::MIP_LEVELS_ALL, - D3DPOOL_DEFAULT); + kLegacyDefaultPool); DX8CALL(UpdateTexture(sysmem_texture,d3d_texture)); sysmem_texture->Release(); @@ -512,6 +893,115 @@ IDirect3DTexture8* TextureLoader::Load_Thumbnail(const StringClass& filename, co return d3d_texture; #endif } +#endif + +#if defined(GGC_RENDER_BACKEND_BGFX) +static bool Should_Use_CPU_Texture_Thumbnail(TextureBaseClass *texture) +{ + if (texture == nullptr || + texture->Get_Asset_Type() != TextureBaseClass::TEX_REGULAR || + texture->As_TextureClass() == nullptr) + { + return false; + } + return true; +} + +static bool Build_CPU_Texture_Thumbnail( + const StringClass& filename, + const Vector3& hsv_shift, + WW3DFormat &dest_format, + std::vector &mips) +{ + ThumbnailClass* thumb = ThumbnailManagerClass::Peek_Thumbnail_Instance_From_Any_Manager(filename); + if (!thumb) + { + Log_Texture_Load_Failure("thumbnail", filename); + return false; + } + + WWASSERT(thumb->Get_Format()==WW3D_FORMAT_A4R4G4B4); + dest_format = Get_Valid_Texture_Format(WW3D_FORMAT_A4R4G4B4, false); + + unsigned int level_count = 0; + for (unsigned int width = thumb->Get_Width(), height = thumb->Get_Height(); + width != 0 && height != 0 && level_count < MIP_LEVELS_MAX; + width >>= 1, height >>= 1) + { + ++level_count; + } + if (level_count == 0) { + return false; + } + + mips.clear(); + mips.resize(level_count); + unsigned int width = thumb->Get_Width(); + unsigned int height = thumb->Get_Height(); + for (unsigned int level = 0; level < level_count; ++level) + { + TextureBaseClass::TextureMipSnapshot &mip = mips[level]; + unsigned int pitch = 0; + unsigned int rows = 0; + if (!Get_CPU_Texture_Snapshot_Staging_Layout(dest_format, width, height, pitch, rows)) { + mips.clear(); + return false; + } + mip.Width = width; + mip.Height = height; + mip.Pitch = pitch; + mip.Format = dest_format; + mip.Data.resize(static_cast(pitch) * rows); + width >>= 1; + height >>= 1; + } + + unsigned char *src_surface = thumb->Peek_Bitmap(); + unsigned src_pitch = thumb->Get_Width() * 2; // Thumbs are always 16 bits. + WW3DFormat src_format = thumb->Get_Format(); + Vector3 hsv = hsv_shift; + for (unsigned int level = 0; level + 1 < level_count; ++level) + { + BitmapHandlerClass::Copy_Image_Generate_Mipmap( + mips[level].Width, + mips[level].Height, + mips[level].Data.data(), + mips[level].Pitch, + dest_format, + src_surface, + src_pitch, + src_format, + mips[level + 1].Data.data(), + mips[level + 1].Pitch, + hsv); + hsv = Vector3(0.0f, 0.0f, 0.0f); + src_format = dest_format; + src_surface = mips[level].Data.data(); + src_pitch = mips[level].Pitch; + } + + if (level_count == 1) + { + BitmapHandlerClass::Copy_Image( + mips[0].Data.data(), + mips[0].Width, + mips[0].Height, + mips[0].Pitch, + dest_format, + thumb->Peek_Bitmap(), + thumb->Get_Width(), + thumb->Get_Height(), + thumb->Get_Width() * 2, + thumb->Get_Format(), + nullptr, + 0, + false, + hsv_shift); + } + + return true; +} +#endif // ---------------------------------------------------------------------------- @@ -521,19 +1011,28 @@ IDirect3DTexture8* TextureLoader::Load_Thumbnail(const StringClass& filename, co // format and performs color space conversion. // // ---------------------------------------------------------------------------- -IDirect3DSurface8* TextureLoader::Load_Surface_Immediate( +LegacyLoaderSurface * Load_Legacy_Surface_Immediate( const StringClass& filename, WW3DFormat texture_format, bool allow_compression) { - WWASSERT(Is_DX8_Thread()); +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)filename; + (void)texture_format; + (void)allow_compression; + WWASSERT_PRINT( + false, + "Load_Legacy_Surface_Immediate: standalone bgfx cannot create fake-D3D surfaces"); + return nullptr; +#else + WWASSERT(TextureLoader::Is_Main_Render_Thread()); bool compressed=Is_Format_Compressed(texture_format,allow_compression); if (compressed) { - IDirect3DTexture8* comp_tex=Load_Compressed_Texture(filename,0,MIP_LEVELS_1,WW3D_FORMAT_UNKNOWN); + LegacyLoaderTexture * comp_tex=Load_Compressed_Texture(filename,0,MIP_LEVELS_1,WW3D_FORMAT_UNKNOWN); if (comp_tex) { - IDirect3DSurface8* d3d_surface=nullptr; + LegacyLoaderSurface * d3d_surface=nullptr; DX8_ErrorCode(comp_tex->GetSurfaceLevel(0,&d3d_surface)); comp_tex->Release(); return d3d_surface; @@ -542,7 +1041,10 @@ IDirect3DSurface8* TextureLoader::Load_Surface_Immediate( // Make sure the file can be opened. If not, return missing texture. Targa targa; - if (TARGA_ERROR_HANDLER(targa.Open(filename, TGA_READMODE),filename)) return MissingTexture::_Create_Missing_Surface(); + if (TARGA_ERROR_HANDLER(targa.Open(filename, TGA_READMODE),filename)) { + Log_Texture_Load_Failure("surface open", filename); + return Create_Legacy_Missing_Surface(); + } // DX8 uses image upside down compared to TGA targa.Header.ImageDescriptor ^= TGAIDF_YORIGIN; @@ -565,7 +1067,10 @@ IDirect3DSurface8* TextureLoader::Load_Surface_Immediate( // NOTE: We load the palette but we do not yet support paletted textures! char palette[256*4]; targa.SetPalette(palette); - if (TARGA_ERROR_HANDLER(targa.Load(filename, TGAF_IMAGE, false),filename)) return MissingTexture::_Create_Missing_Surface(); + if (TARGA_ERROR_HANDLER(targa.Load(filename, TGAF_IMAGE, false),filename)) { + Log_Texture_Load_Failure("surface load", filename); + return Create_Legacy_Missing_Surface(); + } unsigned char* src_surface=(unsigned char*)targa.GetImage(); @@ -598,9 +1103,9 @@ IDirect3DSurface8* TextureLoader::Load_Surface_Immediate( unsigned src_pitch=src_width*src_bpp; - IDirect3DSurface8* d3d_surface = DX8Wrapper::_Create_DX8_Surface(width,height,dest_format); + LegacyLoaderSurface * d3d_surface = Create_Legacy_Surface(width,height,dest_format); WWASSERT(d3d_surface); - D3DLOCKED_RECT locked_rect; + LegacyLoaderLockedRect locked_rect; DX8_ErrorCode( d3d_surface->LockRect( &locked_rect, @@ -627,6 +1132,114 @@ IDirect3DSurface8* TextureLoader::Load_Surface_Immediate( delete[] converted_surface; return d3d_surface; +#endif +} + +bool TextureLoader::Load_Surface_Image_Immediate( + const char *filename, + WW3DFormat texture_format, + bool allow_compression, + SurfaceClass::SurfaceImageData &image) +{ + WWASSERT(Is_Main_Render_Thread()); + + image = {WW3D_FORMAT_UNKNOWN, 0, 0, 0, {}}; + if (Is_Format_Compressed(texture_format, allow_compression)) { + return false; + } + + Targa targa; + if (TARGA_ERROR_HANDLER(targa.Open(filename, TGA_READMODE), filename)) { + Log_Texture_Load_Failure("surface open", filename); + return false; + } + + targa.Header.ImageDescriptor ^= TGAIDF_YORIGIN; + + WW3DFormat src_format; + WW3DFormat dest_format; + unsigned src_bpp = 0; + Get_WW3D_Format(dest_format, src_format, src_bpp, targa); + + if (texture_format != WW3D_FORMAT_UNKNOWN) { + dest_format = texture_format; + } + + unsigned width = targa.Header.Width; + unsigned height = targa.Header.Height; + unsigned src_width = targa.Header.Width; + unsigned src_height = targa.Header.Height; + + char palette[256 * 4]; + targa.SetPalette(palette); + if (TARGA_ERROR_HANDLER(targa.Load(filename, TGAF_IMAGE, false), filename)) { + Log_Texture_Load_Failure("surface load", filename); + return false; + } + + unsigned char *src_surface = reinterpret_cast(targa.GetImage()); + unsigned char *converted_surface = nullptr; + if (src_format == WW3D_FORMAT_A1R5G5B5 || + src_format == WW3D_FORMAT_R5G6B5 || + src_format == WW3D_FORMAT_A4R4G4B4 || + src_format == WW3D_FORMAT_P8 || + src_format == WW3D_FORMAT_L8 || + src_width != width || + src_height != height) + { + converted_surface = W3DNEWARRAY unsigned char[width * height * 4]; + dest_format = Get_Valid_Texture_Format(WW3D_FORMAT_A8R8G8B8, false); + BitmapHandlerClass::Copy_Image( + converted_surface, + width, + height, + width * 4, + WW3D_FORMAT_A8R8G8B8, + src_surface, + src_width, + src_height, + src_width * src_bpp, + src_format, + reinterpret_cast(targa.GetPalette()), + targa.Header.CMapDepth >> 3, + false); + src_surface = converted_surface; + src_format = WW3D_FORMAT_A8R8G8B8; + src_width = width; + src_height = height; + src_bpp = Get_Bytes_Per_Pixel(src_format); + } + + const unsigned int dest_bpp = Get_Bytes_Per_Pixel(dest_format); + if (dest_bpp == 0) + { + delete[] converted_surface; + return false; + } + + image.Format = dest_format; + image.Width = width; + image.Height = height; + image.Pitch = width * dest_bpp; + image.Data.resize(static_cast(image.Pitch) * image.Height); + + BitmapHandlerClass::Copy_Image( + image.Data.data(), + width, + height, + image.Pitch, + dest_format, + src_surface, + src_width, + src_height, + src_width * src_bpp, + src_format, + reinterpret_cast(targa.GetPalette()), + targa.Header.CMapDepth >> 3, + false); + + delete[] converted_surface; + return true; } @@ -637,14 +1250,20 @@ void TextureLoader::Request_Thumbnail(TextureBaseClass *tc) // serializes calls to Request_Thumbnail from multiple threads. FastCriticalSectionClass::LockClass lock(_ForegroundCriticalSection); - // Has a Direct3D texture already been loaded? - if (tc->Peek_D3D_Base_Texture()) { +#if !defined(GGC_RENDER_BACKEND_BGFX) + if (Peek_Legacy_Base_Texture(*tc)) { + return; + } +#endif +#if defined(GGC_RENDER_BACKEND_BGFX) + if (Should_Use_CPU_Texture_Thumbnail(tc) && tc->Has_CPU_Texture_Mips()) { return; } +#endif TextureLoadTaskClass *task = tc->ThumbnailLoadTask; - if (Is_DX8_Thread()) { + if (Is_Main_Render_Thread()) { // load the thumbnail immediately TextureLoader::Load_Thumbnail(tc); @@ -694,7 +1313,7 @@ void TextureLoader::Request_Background_Loading(TextureBaseClass *tc) task = TextureLoadTaskClass::Create(tc, TextureLoadTaskClass::TASK_LOAD, TextureLoadTaskClass::PRIORITY_LOW); - if (Is_DX8_Thread()) { + if (Is_Main_Render_Thread()) { Begin_Load_And_Queue(task); } else { _ForegroundQueue.Push_Back(task); @@ -719,7 +1338,7 @@ void TextureLoader::Request_Foreground_Loading(TextureBaseClass *tc) TextureLoadTaskClass *task = tc->TextureLoadTask; TextureLoadTaskClass *task_thumb = tc->ThumbnailLoadTask; - if (Is_DX8_Thread()) { + if (Is_Main_Render_Thread()) { // since we're in the DX8 thread, we can load the entire // texture right now. @@ -799,7 +1418,7 @@ void TextureLoader::Flush_Pending_Load_Tasks() // to complete texture loading. If we wanted to flush // the pending tasks from another thread, we'd probably // want to set a bool that is checked by Update(). - WWASSERT(Is_DX8_Thread()); + WWASSERT(Is_Main_Render_Thread()); for (;;) { bool done = false; @@ -847,7 +1466,7 @@ void TextureLoader::Flush_Pending_Load_Tasks() void TextureLoader::Update(void (*network_callback)()) { - WWASSERT_PRINT(Is_DX8_Thread(), "TextureLoader::Update must be called from the main thread!"); + WWASSERT_PRINT(Is_Main_Render_Thread(), "TextureLoader::Update must be called from the main thread!"); if (TextureLoadSuspended) { return; @@ -879,13 +1498,13 @@ void TextureLoader::Update(void (*network_callback)()) void TextureLoader::Suspend_Texture_Load() { - WWASSERT_PRINT(Is_DX8_Thread(),"TextureLoader::Suspend_Texture_Load must be called from the main thread!"); + WWASSERT_PRINT(Is_Main_Render_Thread(),"TextureLoader::Suspend_Texture_Load must be called from the main thread!"); TextureLoadSuspended=true; } void TextureLoader::Continue_Texture_Load() { - WWASSERT_PRINT(Is_DX8_Thread(),"TextureLoader::Continue_Texture_Load must be called from the main thread!"); + WWASSERT_PRINT(Is_Main_Render_Thread(),"TextureLoader::Continue_Texture_Load must be called from the main thread!"); TextureLoadSuspended=false; } @@ -930,7 +1549,7 @@ void TextureLoader::Process_Foreground_Load(TextureLoadTaskClass *task) void TextureLoader::Begin_Load_And_Queue(TextureLoadTaskClass *task) { // should only be called from the DX8 thread. - WWASSERT(Is_DX8_Thread()); + WWASSERT(Is_Main_Render_Thread()); if (task->Begin_Load()) { // add to front of background queue. This means the @@ -953,21 +1572,63 @@ void TextureLoader::Begin_Load_And_Queue(TextureLoadTaskClass *task) void TextureLoader::Load_Thumbnail(TextureBaseClass *tc) { - // All D3D operations must run from main thread - WWASSERT(Is_DX8_Thread()); + // All legacy texture operations must run from main thread + WWASSERT(Is_Main_Render_Thread()); + +#if defined(GGC_RENDER_BACKEND_BGFX) + if (Should_Use_CPU_Texture_Thumbnail(tc)) + { + TextureClass *texture = tc->As_TextureClass(); + std::vector mips; + WW3DFormat format = WW3D_FORMAT_UNKNOWN; + if (Build_CPU_Texture_Thumbnail(tc->Get_Full_Path(), tc->Get_HSV_Shift(), format, mips)) + { + texture->TextureFormat = format; + texture->Width = mips[0].Width; + texture->Height = mips[0].Height; + texture->Set_CPU_Texture_Snapshot(std::move(mips)); + texture->LastAccessed = WW3D::Get_Sync_Time(); + if (g_renderBackend != nullptr) { + g_renderBackend->Invalidate_Cached_Texture(texture); + } + return; + } + + Log_Texture_Load_Failure("thumbnail", tc->Get_Full_Path().str()); + MissingTexture::Build_CPU_Texture_Mips(mips); + WWASSERT(!mips.empty()); + texture->TextureFormat = WW3D_FORMAT_A8R8G8B8; + texture->Width = mips[0].Width; + texture->Height = mips[0].Height; + texture->Set_CPU_Texture_Snapshot(std::move(mips)); + texture->Mark_Missing_Texture(true); + texture->LastAccessed = WW3D::Get_Sync_Time(); + if (g_renderBackend != nullptr) { + g_renderBackend->Invalidate_Cached_Texture(texture); + } + return; + } +#endif +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "TextureLoader::Load_Thumbnail: standalone bgfx cannot use legacy thumbnail texture fallback"); + return; +#else // load thumbnail texture - IDirect3DTexture8 *d3d_texture = Load_Thumbnail(tc->Get_Full_Path(),tc->Get_HSV_Shift()); + LegacyLoaderTexture *d3d_texture = Load_Legacy_Thumbnail(tc->Get_Full_Path(),tc->Get_HSV_Shift()); // apply thumbnail to texture if (tc->Get_Asset_Type()==TextureBaseClass::TEX_REGULAR) { - tc->Apply_New_Surface(d3d_texture, false); + Apply_Native_Compatibility_Texture(*tc, d3d_texture, false); } // release our reference to thumbnail texture d3d_texture->Release(); d3d_texture = nullptr; +#endif } @@ -1008,12 +1669,14 @@ void LoaderThreadClass::Thread_Function() TextureLoadTaskClass::TextureLoadTaskClass() : Texture (nullptr), - D3DTexture (nullptr), + NativeCompatibilityTexture (nullptr), Format (WW3D_FORMAT_UNKNOWN), Width (0), Height (0), MipLevelCount (MIP_LEVELS_ALL), Reduction (0), + StagedCPUTextureMips(), + UseCPUTextureSnapshotStaging(false), Type (TASK_NONE), Priority (PRIORITY_LOW), State (STATE_NONE), @@ -1095,7 +1758,7 @@ void TextureLoadTaskClass::Init(TextureBaseClass* tc, TaskType type, PriorityTyp WWASSERT(tc); // NOTE: we must be in the main thread to avoid corrupting the texture's refcount. - WWASSERT(TextureLoader::Is_DX8_Thread()); + WWASSERT(TextureLoader::Is_Main_Render_Thread()); REF_PTR_SET(Texture, tc); // Make sure texture has a filename. @@ -1105,7 +1768,9 @@ void TextureLoadTaskClass::Init(TextureBaseClass* tc, TaskType type, PriorityTyp Priority = priority; State = STATE_NONE; - D3DTexture = nullptr; + NativeCompatibilityTexture = nullptr; + UseCPUTextureSnapshotStaging = false; + StagedCPUTextureMips.clear(); TextureClass* tex=Texture->As_TextureClass(); @@ -1152,7 +1817,9 @@ void TextureLoadTaskClass::Deinit() WWASSERT(Next == nullptr); WWASSERT(Prev == nullptr); - WWASSERT(D3DTexture == nullptr); + WWASSERT(NativeCompatibilityTexture == nullptr); + WWASSERT(!UseCPUTextureSnapshotStaging); + WWASSERT(StagedCPUTextureMips.empty()); for (int i = 0; i < MIP_LEVELS_MAX; ++i) { WWASSERT(LockedSurfacePtr[i] == nullptr); @@ -1172,7 +1839,7 @@ void TextureLoadTaskClass::Deinit() } // NOTE: we must be in main thread to avoid corrupting Texture's refcount. - WWASSERT(TextureLoader::Is_DX8_Thread()); + WWASSERT(TextureLoader::Is_Main_Render_Thread()); REF_PTR_RELEASE(Texture); } } @@ -1180,7 +1847,18 @@ void TextureLoadTaskClass::Deinit() bool TextureLoadTaskClass::Begin_Load() { - WWASSERT(TextureLoader::Is_DX8_Thread()); + WWASSERT(TextureLoader::Is_Main_Render_Thread()); + +#if defined(GGC_RENDER_BACKEND_BGFX) + if (Texture != nullptr && + Texture->Get_Asset_Type() != TextureBaseClass::TEX_REGULAR) + { + WWASSERT_PRINT( + false, + "TextureLoadTaskClass::Begin_Load: cube/volume textures are not migrated to bgfx texture ownership; no legacy fallback is allowed"); + return false; + } +#endif bool loaded = false; @@ -1218,7 +1896,7 @@ bool TextureLoadTaskClass::Begin_Load() bool TextureLoadTaskClass::Load() { WWMEMLOG(MEM_TEXTURE); - WWASSERT(Peek_D3D_Texture()); + WWASSERT(Peek_Native_Compatibility_Texture() || UseCPUTextureSnapshotStaging); bool loaded = false; @@ -1240,10 +1918,20 @@ bool TextureLoadTaskClass::Load() void TextureLoadTaskClass::End_Load() { - WWASSERT(TextureLoader::Is_DX8_Thread()); + WWASSERT(TextureLoader::Is_Main_Render_Thread()); + +#if defined(GGC_RENDER_BACKEND_BGFX) + if (!UseCPUTextureSnapshotStaging) { + Capture_CPU_Texture_Snapshot_From_Locked_Surfaces(); + } +#endif Unlock_Surfaces(); - Apply(true); + if (UseCPUTextureSnapshotStaging) { + Commit_CPU_Texture_Staging(true); + } else { + Apply(true); + } State = STATE_LOAD_COMPLETE; } @@ -1277,30 +1965,77 @@ void TextureLoadTaskClass::Finish_Load() void TextureLoadTaskClass::Apply_Missing_Texture() { - WWASSERT(TextureLoader::Is_DX8_Thread()); - WWASSERT(!D3DTexture); - - D3DTexture = MissingTexture::_Get_Missing_Texture(); + WWASSERT(TextureLoader::Is_Main_Render_Thread()); + WWASSERT(!NativeCompatibilityTexture); + + Log_Texture_Load_Failure("task", Texture ? Texture->Get_Full_Path().str() : nullptr); +#if defined(GGC_RENDER_BACKEND_BGFX) + if (Texture != nullptr && + Texture->As_TextureClass() != nullptr) + { + TextureClass *texture = Texture->As_TextureClass(); + std::vector mips; + MissingTexture::Build_CPU_Texture_Mips(mips); + WWASSERT(!mips.empty()); + texture->TextureFormat = WW3D_FORMAT_A8R8G8B8; + Texture->Width = mips[0].Width; + Texture->Height = mips[0].Height; + Texture->Set_CPU_Texture_Snapshot(std::move(mips)); + Texture->Initialized = true; + Texture->Mark_Missing_Texture(true); + Texture->LastAccessed = WW3D::Get_Sync_Time(); + if (g_renderBackend != nullptr) + { + g_renderBackend->Invalidate_Cached_Texture(Texture); + } + return; + } +#endif +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "TextureLoadTaskClass::Apply_Missing_Texture: standalone bgfx cannot apply fake-D3D missing textures"); + if (Texture != nullptr) + { + Texture->Mark_Missing_Texture(true); + } + return; +#else + NativeCompatibilityTexture = Get_Legacy_Missing_Texture(); Apply(true); + if (Texture != nullptr) + { + Texture->Mark_Missing_Texture(true); + } +#endif } void TextureLoadTaskClass::Apply(bool initialize) { - WWASSERT(D3DTexture); +#if defined(GGC_RENDER_BACKEND_BGFX) + (void)initialize; + WWASSERT_PRINT( + NativeCompatibilityTexture == nullptr, + "TextureLoadTaskClass::Apply: standalone bgfx cannot apply or release fake-D3D loader textures"); + NativeCompatibilityTexture = nullptr; + return; +#else + WWASSERT(NativeCompatibilityTexture); // Verify that none of the mip levels are locked for (unsigned i=0;iApply_New_Surface(D3DTexture, initialize); + Apply_Native_Compatibility_Texture(*Texture, Peek_Native_Compatibility_Texture(), initialize); + Texture->Mark_Missing_Texture(false); - D3DTexture->Release(); - D3DTexture = nullptr; + Peek_Native_Compatibility_Texture()->Release(); + NativeCompatibilityTexture = nullptr; +#endif } - static unsigned Get_Requested_Reduction(unsigned width, unsigned height, unsigned mip_count) { // Figure out correct reduction @@ -1326,7 +2061,6 @@ static unsigned Get_Requested_Reduction(unsigned width, unsigned height, unsigne return curReduction; } - static bool Get_Texture_Information ( const char* filename, @@ -1346,8 +2080,7 @@ static bool Get_Texture_Information if (compressed) { DDSFileClass dds_file(filename, 0); - if (!dds_file.Is_Available()) - return false; + if (!dds_file.Is_Available()) return false; // Destination size will be the next power of two square from the larger width and height... w = dds_file.Get_Width(0); @@ -1356,7 +2089,6 @@ static bool Get_Texture_Information format = dds_file.Get_Format(); mip_count = dds_file.Get_Mip_Level_Count(); reduction = Get_Requested_Reduction(w, h, mip_count); - return true; } @@ -1542,33 +2274,46 @@ bool TextureLoadTaskClass::Begin_Compressed_Load() Apply_Mip_Reduction(MipLevelCount, Reduction, Width, Height, orig_mip_count); - D3DTexture = DX8Wrapper::_Create_DX8_Texture + if (Should_Use_CPU_Texture_Snapshot_Staging() && MipLevelCount > 0) + { + UseCPUTextureSnapshotStaging = true; + return true; + } + +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "TextureLoadTaskClass::Begin_Compressed_Load: standalone bgfx cannot create legacy texture fallback"); + return false; +#else + NativeCompatibilityTexture = Create_Legacy_Texture ( Width, Height, Format, (MipCountType)MipLevelCount, #ifdef USE_MANAGED_TEXTURES - D3DPOOL_MANAGED + kLegacyManagedPool #else - D3DPOOL_SYSTEMMEM + kLegacySystemPool #endif ); return true; +#endif } bool TextureLoadTaskClass::Begin_Uncompressed_Load() { - unsigned orig_width,orig_height,orig_depth,orig_mip_count,orig_reduction; + unsigned width,height,depth,orig_mip_count,reduction; WW3DFormat orig_format; if (!Get_Texture_Information ( Texture->Get_Full_Path(), - orig_reduction, - orig_width, - orig_height, - orig_depth, + reduction, + width, + height, + depth, orig_format, orig_mip_count, false @@ -1590,16 +2335,16 @@ bool TextureLoadTaskClass::Begin_Uncompressed_Load() } // Destination size will be the next power of two square from the larger width and height... - unsigned ow = orig_width; - unsigned oh = orig_height; - TextureLoader::Validate_Texture_Size(orig_width, orig_height,orig_depth); - if (orig_width != ow || orig_height != oh) + unsigned ow = width; + unsigned oh = height; + TextureLoader::Validate_Texture_Size(width, height,depth); + if (width != ow || height != oh) { - WWDEBUG_SAY(("Invalid texture size, scaling required. Texture: %s, size: %d x %d -> %d x %d", Texture->Get_Full_Path().str(), ow, oh, orig_width, orig_height)); + WWDEBUG_SAY(("Invalid texture size, scaling required. Texture: %s, size: %d x %d -> %d x %d", Texture->Get_Full_Path().str(), ow, oh, width, height)); } - Width = orig_width; - Height = orig_height; + Width = width; + Height = height; Reduction = 0; if (Format == WW3D_FORMAT_UNKNOWN) @@ -1611,33 +2356,201 @@ bool TextureLoadTaskClass::Begin_Uncompressed_Load() Format = Get_Valid_Texture_Format(Format, false); } - D3DTexture = DX8Wrapper::_Create_DX8_Texture + if (Should_Use_CPU_Texture_Snapshot_Staging()) + { + MipLevelCount = Get_Requested_Mip_Level_Count(Width, Height); + if (MipLevelCount > 0) + { + UseCPUTextureSnapshotStaging = true; + return true; + } + } + +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "TextureLoadTaskClass::Begin_Uncompressed_Load: standalone bgfx cannot create legacy texture fallback"); + return false; +#else + NativeCompatibilityTexture = Create_Legacy_Texture ( Width, Height, Format, Texture->MipLevelCount, #ifdef USE_MANAGED_TEXTURES - D3DPOOL_MANAGED + kLegacyManagedPool #else - D3DPOOL_SYSTEMMEM + kLegacySystemPool #endif ); + return true; +#endif +} + +/* +bool TextureLoadTaskClass::Begin_Compressed_Load() +{ + DDSFileClass dds_file(Texture->Get_Full_Path(), Get_Reduction()); + if (!dds_file.Is_Available()) { + return false; + } + + // Destination size will be the next power of two square from the larger width and height... + unsigned int width = dds_file.Get_Width(0); + unsigned int height = dds_file.Get_Height(0); + TextureLoader::Validate_Texture_Size(width, height); + + // If the size doesn't match, try and see if texture reduction would help... (mainly for + // cases where loaded texture is larger than hardware limit) + if (width != dds_file.Get_Width(0) || height != dds_file.Get_Height(0)) { + for (unsigned int i = 1; i < dds_file.Get_Mip_Level_Count(); ++i) { + unsigned int w = dds_file.Get_Width(i); + unsigned int h = dds_file.Get_Height(i); + TextureLoader::Validate_Texture_Size(w,h); + + if (w == dds_file.Get_Width(i) && h == dds_file.Get_Height(i)) { + Reduction += i; + width = w; + height = h; + break; + } + } + } + + Width = width; + Height = height; + Format = Get_Valid_Texture_Format(dds_file.Get_Format(), Texture->Is_Compression_Allowed()); + + unsigned int mip_level_count = Get_Mip_Level_Count(); + + // If texture wants all mip levels, take as many as the file contains (not necessarily all) + // Otherwise take as many mip levels as the texture wants, not to exceed the count in file... + if (!mip_level_count) { + mip_level_count = dds_file.Get_Mip_Level_Count(); + } else if (mip_level_count > dds_file.Get_Mip_Level_Count()) { + mip_level_count = dds_file.Get_Mip_Level_Count(); + } + + // Once more, verify that the mip level count is correct (in case it was changed here it might not + // match the size...well actually it doesn't have to match but it can't be bigger than the size) + unsigned int max_mip_level_count = 1; + unsigned int w = 4; + unsigned int h = 4; + + while (w < Width && h < Height) { + w += w; + h += h; + max_mip_level_count++; + } + + if (mip_level_count > max_mip_level_count) { + mip_level_count = max_mip_level_count; + } + + NativeCompatibilityTexture = Create_Legacy_Texture( + Width, + Height, + Format, + (TextureBaseClass::MipCountType)mip_level_count, +#ifdef USE_MANAGED_TEXTURES + kLegacyManagedPool); +#else + kLegacySystemPool); +#endif + MipLevelCount = mip_level_count; return true; } +bool TextureLoadTaskClass::Begin_Uncompressed_Load() +{ + Targa targa; + if (TARGA_ERROR_HANDLER(targa.Open(Texture->Get_Full_Path(), TGA_READMODE), Texture->Get_Full_Path())) { + return false; + } + + unsigned int bpp; + WW3DFormat src_format, dest_format; + Get_WW3D_Format(dest_format,src_format,bpp,targa); + + if ( src_format != WW3D_FORMAT_A8R8G8B8 + && src_format != WW3D_FORMAT_R8G8B8 + && src_format != WW3D_FORMAT_X8R8G8B8) { + WWDEBUG_SAY(("Invalid TGA format used in %s - only 24 and 32 bit formats should be used!", Texture->Get_Full_Path())); + } + + // Destination size will be the next power of two square from the larger width and height... + unsigned width=targa.Header.Width, height=targa.Header.Height; + int ReductionFactor=Get_Reduction(); + int MipLevels=0; + + //Figure out how many mip levels this texture will occupy + for (int i=width, j=height; i > 0 && j > 0; i>>=1, j>>=1) + MipLevels++; + + //Adjust the reduction factor to keep textures above some minimum dimensions + if (MipLevels <= WW3D::Get_Texture_Min_Mip_Levels()) + ReductionFactor=0; + else + { int mipToDrop=MipLevels-WW3D::Get_Texture_Min_Mip_Levels(); + if (ReductionFactor >= mipToDrop) + ReductionFactor=mipToDrop; + } + + width=targa.Header.Width>>ReductionFactor; + height=targa.Header.Height>>ReductionFactor; + unsigned ow = width; + unsigned oh = height; + TextureLoader::Validate_Texture_Size(width, height); + if (width != ow || height != oh) { + WWDEBUG_SAY(("Invalid texture size, scaling required. Texture: %s, size: %d x %d -> %d x %d", Texture->Get_Full_Path(), ow, oh, width, height)); + } + + Width = width; + Height = height; + + // changed because format was being read from previous loading task?! KJM + Format=dest_format; + //if (Format == WW3D_FORMAT_UNKNOWN) { + // Format = Get_Valid_Texture_Format(dest_format, false); + //} else { + // Format = Get_Valid_Texture_Format(Format, false); + //} + + NativeCompatibilityTexture = Create_Legacy_Texture + ( + Width, + Height, + Format, + Texture->MipLevelCount, +#ifdef USE_MANAGED_TEXTURES + kLegacyManagedPool); +#else + kLegacySystemPool); +#endif + return true; +} +*/ + void TextureLoadTaskClass::Lock_Surfaces() { - MipLevelCount = D3DTexture->GetLevelCount(); + if (UseCPUTextureSnapshotStaging) + { + Allocate_CPU_Texture_Staging(); + return; + } + +#if !defined(GGC_RENDER_BACKEND_BGFX) + MipLevelCount = Peek_Native_Compatibility_Texture()->GetLevelCount(); for (unsigned int i = 0; i < MipLevelCount; ++i) { - D3DLOCKED_RECT locked_rect; + LegacyLoaderLockedRect locked_rect; DX8_ErrorCode ( - Peek_D3D_Texture()->LockRect + Peek_Native_Compatibility_Texture()->LockRect ( i, &locked_rect, @@ -1648,29 +2561,196 @@ void TextureLoadTaskClass::Lock_Surfaces() LockedSurfacePtr[i] = (unsigned char *)locked_rect.pBits; LockedSurfacePitch[i] = locked_rect.Pitch; } +#endif } void TextureLoadTaskClass::Unlock_Surfaces() { + if (UseCPUTextureSnapshotStaging) + { + for (unsigned int i = 0; i < MipLevelCount; ++i) + { + LockedSurfacePtr[i] = nullptr; + LockedSurfacePitch[i] = 0; + } + return; + } + +#if !defined(GGC_RENDER_BACKEND_BGFX) for (unsigned int i = 0; i < MipLevelCount; ++i) { if (LockedSurfacePtr[i]) { - WWASSERT(ThreadClass::_Get_Current_Thread_ID() == DX8Wrapper::_Get_Main_Thread_ID()); - DX8_ErrorCode(Peek_D3D_Texture()->UnlockRect(i)); + WWASSERT(TextureLoader::Is_Main_Render_Thread()); + DX8_ErrorCode(Peek_Native_Compatibility_Texture()->UnlockRect(i)); } LockedSurfacePtr[i] = nullptr; } #ifndef USE_MANAGED_TEXTURES - IDirect3DTexture8* tex = DX8Wrapper::_Create_DX8_Texture(Width, Height, Format, Texture->MipLevelCount,D3DPOOL_DEFAULT); - DX8CALL(UpdateTexture(Peek_D3D_Texture(),tex)); - Peek_D3D_Texture()->Release(); - D3DTexture=tex; + LegacyLoaderTexture * tex = Create_Legacy_Texture(Width, Height, Format, Texture->MipLevelCount,kLegacyDefaultPool); + DX8CALL(UpdateTexture(Peek_Native_Compatibility_Texture(),tex)); + Peek_Native_Compatibility_Texture()->Release(); + NativeCompatibilityTexture=tex; WWDEBUG_SAY(("Created non-managed texture (%s)",Texture->Get_Full_Path())); #endif +#endif + +} + +void TextureLoadTaskClass::Capture_CPU_Texture_Snapshot_From_Locked_Surfaces() +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + Peek_Native_Compatibility_Texture() == nullptr, + "Capture_CPU_Texture_Snapshot_From_Locked_Surfaces: standalone bgfx should use CPU texture staging, not locked fake-D3D surfaces"); + return; +#else + if (Texture == nullptr || Texture->As_TextureClass() == nullptr || Peek_Native_Compatibility_Texture() == nullptr) { + return; + } + + std::vector mips; + mips.reserve(MipLevelCount); + for (unsigned int level = 0; level < MipLevelCount; ++level) + { + if (LockedSurfacePtr[level] == nullptr) { + return; + } + + LegacySurfaceDesc desc; + if (FAILED(Peek_Native_Compatibility_Texture()->GetLevelDesc(level, &desc))) { + return; + } + + TextureBaseClass::TextureMipSnapshot mip; + mip.Width = desc.Width; + mip.Height = desc.Height; + mip.Pitch = LockedSurfacePitch[level]; + mip.Format = Legacy_Texture_Format_To_WW3DFormat(static_cast(desc.Format)); + const bool compressed = + mip.Format == WW3D_FORMAT_DXT1 || + mip.Format == WW3D_FORMAT_DXT2 || + mip.Format == WW3D_FORMAT_DXT3 || + mip.Format == WW3D_FORMAT_DXT4 || + mip.Format == WW3D_FORMAT_DXT5; + const unsigned rows = compressed ? DXT_SurfaceRows(mip.Height) : mip.Height; + const unsigned size = rows * mip.Pitch; + mip.Data.resize(size); + if (size != 0) { + std::memcpy(&mip.Data[0], LockedSurfacePtr[level], size); + } + mips.push_back(mip); + } + + Texture->Set_CPU_Texture_Snapshot(std::move(mips)); +#endif +} + +bool TextureLoadTaskClass::Should_Use_CPU_Texture_Snapshot_Staging() const +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + if (Texture == nullptr || + Texture->Get_Asset_Type() != TextureBaseClass::TEX_REGULAR || + Type != TASK_LOAD || + Texture->As_TextureClass() == nullptr) + { + return false; + } + + return Is_CPU_Texture_Snapshot_Staging_Format(Format); +#else + return false; +#endif +} + +unsigned int TextureLoadTaskClass::Get_Requested_Mip_Level_Count(unsigned int width, unsigned int height) const +{ + if (width == 0 || height == 0) { + return 0; + } + + unsigned int all_levels = 0; + for (unsigned int w = width, h = height; w > 0 && h > 0; w >>= 1, h >>= 1) { + ++all_levels; + } + + unsigned int requested_levels = 1; + switch (Texture->MipLevelCount) { + case MIP_LEVELS_ALL: requested_levels = all_levels; break; + case MIP_LEVELS_1: requested_levels = 1; break; + case MIP_LEVELS_2: requested_levels = 2; break; + case MIP_LEVELS_3: requested_levels = 3; break; + case MIP_LEVELS_4: requested_levels = 4; break; + case MIP_LEVELS_5: requested_levels = 5; break; + case MIP_LEVELS_6: requested_levels = 6; break; + case MIP_LEVELS_7: requested_levels = 7; break; + case MIP_LEVELS_8: requested_levels = 8; break; + case MIP_LEVELS_10: requested_levels = 10; break; + case MIP_LEVELS_11: requested_levels = 11; break; + case MIP_LEVELS_12: requested_levels = 12; break; + default: requested_levels = 1; break; + } + + return MIN(requested_levels, all_levels); +} + +void TextureLoadTaskClass::Allocate_CPU_Texture_Staging() +{ + WWASSERT(UseCPUTextureSnapshotStaging); + WWASSERT(MipLevelCount > 0); + + StagedCPUTextureMips.clear(); + StagedCPUTextureMips.resize(MipLevelCount); + + unsigned int width = Width; + unsigned int height = Height; + + for (unsigned int level = 0; level < MipLevelCount; ++level) + { + TextureBaseClass::TextureMipSnapshot &mip = StagedCPUTextureMips[level]; + unsigned int pitch = 0; + unsigned int rows = 0; + const bool valid_layout = Get_CPU_Texture_Snapshot_Staging_Layout(Format, width, height, pitch, rows); + WWASSERT(valid_layout); + + mip.Width = width; + mip.Height = height; + mip.Pitch = pitch; + mip.Format = Format; + mip.Data.resize(static_cast(mip.Pitch) * rows); + LockedSurfacePtr[level] = mip.Data.data(); + LockedSurfacePitch[level] = mip.Pitch; + width >>= 1; + height >>= 1; + } +} + +void TextureLoadTaskClass::Commit_CPU_Texture_Staging(bool initialize) +{ + WWASSERT(UseCPUTextureSnapshotStaging); + WWASSERT(Texture != nullptr); + + TextureClass *texture = Texture->As_TextureClass(); + WWASSERT(texture != nullptr); + texture->TextureFormat = Format; + if (!StagedCPUTextureMips.empty()) + { + Texture->Width = StagedCPUTextureMips[0].Width; + Texture->Height = StagedCPUTextureMips[0].Height; + } + Texture->Set_CPU_Texture_Snapshot(std::move(StagedCPUTextureMips)); + StagedCPUTextureMips.clear(); + if (initialize) { + Texture->Initialized = true; + } + Texture->LastAccessed = WW3D::Get_Sync_Time(); + if (g_renderBackend != nullptr) { + g_renderBackend->Invalidate_Cached_Texture(texture); + } + UseCPUTextureSnapshotStaging = false; } @@ -1678,7 +2758,7 @@ bool TextureLoadTaskClass::Load_Compressed_Mipmap() { DDSFileClass dds_file(Texture->Get_Full_Path(), Get_Reduction()); - // if we can't load from file, indicate error. + // if we can't load from file, indicate rror. if (!dds_file.Is_Available() || !dds_file.Load()) { return false; @@ -1703,8 +2783,8 @@ bool TextureLoadTaskClass::Load_Compressed_Mipmap() HSVShift ); - width >>= 1; - height >>= 1; + width >>= 1; + height >>= 1; } return true; @@ -1910,7 +2990,7 @@ void CubeTextureLoadTaskClass::Init(TextureBaseClass* tc, TaskType type, Priorit WWASSERT(tc); // NOTE: we must be in the main thread to avoid corrupting the texture's refcount. - WWASSERT(TextureLoader::Is_DX8_Thread()); + WWASSERT(TextureLoader::Is_Main_Render_Thread()); REF_PTR_SET(Texture, tc); // Make sure texture has a filename. @@ -1920,7 +3000,7 @@ void CubeTextureLoadTaskClass::Init(TextureBaseClass* tc, TaskType type, Priorit Priority = priority; State = STATE_NONE; - D3DTexture = nullptr; + NativeCompatibilityTexture = nullptr; CubeTextureClass* tex=Texture->As_CubeTextureClass(); @@ -1970,7 +3050,7 @@ void CubeTextureLoadTaskClass::Deinit() WWASSERT(Next == nullptr); WWASSERT(Prev == nullptr); - WWASSERT(D3DTexture == nullptr); + WWASSERT(NativeCompatibilityTexture == nullptr); for (int f=0; f<6; f++) { @@ -1996,23 +3076,26 @@ void CubeTextureLoadTaskClass::Deinit() } // NOTE: we must be in main thread to avoid corrupting Texture's refcount. - WWASSERT(TextureLoader::Is_DX8_Thread()); + WWASSERT(TextureLoader::Is_Main_Render_Thread()); REF_PTR_RELEASE(Texture); } } void CubeTextureLoadTaskClass::Lock_Surfaces() { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT(false, "CubeTextureLoadTaskClass::Lock_Surfaces: standalone bgfx does not support cube textures"); +#else for (unsigned int f=0; f<6; f++) { for (unsigned int i=0; iLockRect + Peek_Native_Compatibility_Cube_Texture()->LockRect ( - (D3DCUBEMAP_FACES)f, + (LegacyLoaderCubeFace)f, i, &locked_rect, nullptr, @@ -2023,20 +3106,24 @@ void CubeTextureLoadTaskClass::Lock_Surfaces() LockedCubeSurfacePitch[f][i]= locked_rect.Pitch; } } +#endif } void CubeTextureLoadTaskClass::Unlock_Surfaces() { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT(false, "CubeTextureLoadTaskClass::Unlock_Surfaces: standalone bgfx does not support cube textures"); +#else for (unsigned int f=0; f<6; f++) { for (unsigned int i = 0; i < MipLevelCount; ++i) { if (LockedCubeSurfacePtr[f][i]) { - WWASSERT(ThreadClass::_Get_Current_Thread_ID() == DX8Wrapper::_Get_Main_Thread_ID()); + WWASSERT(TextureLoader::Is_Main_Render_Thread()); DX8_ErrorCode ( - Peek_D3D_Cube_Texture()->UnlockRect((D3DCUBEMAP_FACES)f,i) + Peek_Native_Compatibility_Cube_Texture()->UnlockRect((LegacyLoaderCubeFace)f,i) ); } LockedCubeSurfacePtr[f][i] = nullptr; @@ -2044,26 +3131,32 @@ void CubeTextureLoadTaskClass::Unlock_Surfaces() } #ifndef USE_MANAGED_TEXTURES - IDirect3DCubeTexture8* tex = DX8Wrapper::_Create_DX8_Cube_Texture + LegacyLoaderCubeTexture * tex = Create_Legacy_Cube_Texture ( Width, Height, Format, Texture->MipLevelCount, - D3DPOOL_DEFAULT + kLegacyDefaultPool ); - DX8CALL(UpdateTexture(Peek_D3D_Volume_Texture(),tex)); - Peek_D3D_Volume_Texture()->Release(); - D3DTexture=tex; + DX8CALL(UpdateTexture(Peek_Native_Compatibility_Volume_Texture(),tex)); + Peek_Native_Compatibility_Volume_Texture()->Release(); + NativeCompatibilityTexture=tex; WWDEBUG_SAY(("Created non-managed texture (%s)",Texture->Get_Full_Path())); #endif - +#endif } bool CubeTextureLoadTaskClass::Begin_Compressed_Load() { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "CubeTextureLoadTaskClass::Begin_Compressed_Load: standalone bgfx cannot load fake-D3D cube textures"); + return false; +#else unsigned orig_width,orig_height,orig_depth,orig_mip_count,orig_reduction; WW3DFormat orig_format; if (!Get_Texture_Information @@ -2093,33 +3186,40 @@ bool CubeTextureLoadTaskClass::Begin_Compressed_Load() Apply_Mip_Reduction(MipLevelCount, Reduction, Width, Height, orig_mip_count); - D3DTexture = DX8Wrapper::_Create_DX8_Cube_Texture + NativeCompatibilityTexture = Create_Legacy_Cube_Texture ( Width, Height, Format, (MipCountType)MipLevelCount, #ifdef USE_MANAGED_TEXTURES - D3DPOOL_MANAGED + kLegacyManagedPool #else - D3DPOOL_SYSTEMMEM + kLegacySystemPool #endif ); return true; +#endif } bool CubeTextureLoadTaskClass::Begin_Uncompressed_Load() { - unsigned orig_width,orig_height,orig_depth,orig_mip_count,orig_reduction; +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "CubeTextureLoadTaskClass::Begin_Uncompressed_Load: standalone bgfx cannot load fake-D3D cube textures"); + return false; +#else + unsigned width,height,depth,orig_mip_count,reduction; WW3DFormat orig_format; if (!Get_Texture_Information ( Texture->Get_Full_Path(), - orig_reduction, - orig_width, - orig_height, - orig_depth, + reduction, + width, + height, + depth, orig_format, orig_mip_count, false @@ -2141,16 +3241,16 @@ bool CubeTextureLoadTaskClass::Begin_Uncompressed_Load() } // Destination size will be the next power of two square from the larger width and height... - unsigned ow = orig_width; - unsigned oh = orig_height; - TextureLoader::Validate_Texture_Size(orig_width, orig_height,orig_depth); - if (orig_width != ow || orig_height != oh) + unsigned ow = width; + unsigned oh = height; + TextureLoader::Validate_Texture_Size(width, height,depth); + if (width != ow || height != oh) { - WWDEBUG_SAY(("Invalid texture size, scaling required. Texture: %s, size: %d x %d -> %d x %d", Texture->Get_Full_Path().str(), ow, oh, orig_width, orig_height)); + WWDEBUG_SAY(("Invalid texture size, scaling required. Texture: %s, size: %d x %d -> %d x %d", Texture->Get_Full_Path().str(), ow, oh, width, height)); } - Width = orig_width; - Height = orig_height; + Width = width; + Height = height; Reduction = 0; if (Format == WW3D_FORMAT_UNKNOWN) @@ -2162,27 +3262,28 @@ bool CubeTextureLoadTaskClass::Begin_Uncompressed_Load() Format = Get_Valid_Texture_Format(Format, false); } - D3DTexture = DX8Wrapper::_Create_DX8_Cube_Texture + NativeCompatibilityTexture = Create_Legacy_Cube_Texture ( Width, Height, Format, Texture->MipLevelCount, #ifdef USE_MANAGED_TEXTURES - D3DPOOL_MANAGED + kLegacyManagedPool #else - D3DPOOL_SYSTEMMEM + kLegacySystemPool #endif ); return true; +#endif } bool CubeTextureLoadTaskClass::Load_Compressed_Mipmap() { DDSFileClass dds_file(Texture->Get_Full_Path(), Get_Reduction()); - // if we can't load from file, indicate error. + // if we can't load from file, indicate rror. if (!dds_file.Is_Available() || !dds_file.Load()) { return false; @@ -2211,8 +3312,8 @@ bool CubeTextureLoadTaskClass::Load_Compressed_Mipmap() HSVShift ); - width >>= 1; - height >>= 1; + width>>=1; + height>>=1; } } @@ -2267,7 +3368,7 @@ void VolumeTextureLoadTaskClass::Init(TextureBaseClass* tc, TaskType type, Prior WWASSERT(tc); // NOTE: we must be in the main thread to avoid corrupting the texture's refcount. - WWASSERT(TextureLoader::Is_DX8_Thread()); + WWASSERT(TextureLoader::Is_Main_Render_Thread()); REF_PTR_SET(Texture, tc); // Make sure texture has a filename. @@ -2277,7 +3378,7 @@ void VolumeTextureLoadTaskClass::Init(TextureBaseClass* tc, TaskType type, Prior Priority = priority; State = STATE_NONE; - D3DTexture = nullptr; + NativeCompatibilityTexture = nullptr; VolumeTextureClass* tex=Texture->As_VolumeTextureClass(); @@ -2321,12 +3422,15 @@ void VolumeTextureLoadTaskClass::Init(TextureBaseClass* tc, TaskType type, Prior void VolumeTextureLoadTaskClass::Lock_Surfaces() { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT(false, "VolumeTextureLoadTaskClass::Lock_Surfaces: standalone bgfx does not support volume textures"); +#else for (unsigned int i=0; iLockBox + Peek_Native_Compatibility_Volume_Texture()->LockBox ( i, &locked_box, @@ -2338,38 +3442,48 @@ void VolumeTextureLoadTaskClass::Lock_Surfaces() LockedSurfacePitch[i] = locked_box.RowPitch; LockedSurfaceSlicePitch[i] = locked_box.SlicePitch; } +#endif } void VolumeTextureLoadTaskClass::Unlock_Surfaces() { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT(false, "VolumeTextureLoadTaskClass::Unlock_Surfaces: standalone bgfx does not support volume textures"); +#else for (unsigned int i = 0; i < MipLevelCount; ++i) { if (LockedSurfacePtr[i]) { - WWASSERT(ThreadClass::_Get_Current_Thread_ID() == DX8Wrapper::_Get_Main_Thread_ID()); + WWASSERT(TextureLoader::Is_Main_Render_Thread()); DX8_ErrorCode ( - Peek_D3D_Volume_Texture()->UnlockBox(i) + Peek_Native_Compatibility_Volume_Texture()->UnlockBox(i) ); } LockedSurfacePtr[i] = nullptr; } #ifndef USE_MANAGED_TEXTURES - IDirect3DTexture8* tex = DX8Wrapper::_Create_DX8_Volume_Texture(Width, Height, Depth, Format, Texture->MipLevelCount,D3DPOOL_DEFAULT); - DX8CALL(UpdateTexture(Peek_D3D_Volume_Texture(),tex)); - Peek_D3D_Volume_Texture()->Release(); - D3DTexture=tex; + LegacyLoaderTexture * tex = Create_Legacy_Volume_Texture(Width, Height, Depth, Format, Texture->MipLevelCount,kLegacyDefaultPool); + DX8CALL(UpdateTexture(Peek_Native_Compatibility_Volume_Texture(),tex)); + Peek_Native_Compatibility_Volume_Texture()->Release(); + NativeCompatibilityTexture=tex; WWDEBUG_SAY(("Created non-managed texture (%s)",Texture->Get_Full_Path())); #endif - +#endif } bool VolumeTextureLoadTaskClass::Begin_Compressed_Load() { +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "VolumeTextureLoadTaskClass::Begin_Compressed_Load: standalone bgfx cannot load fake-D3D volume textures"); + return false; +#else unsigned orig_width,orig_height,orig_depth,orig_mip_count,orig_reduction; WW3DFormat orig_format; if (!Get_Texture_Information @@ -2400,7 +3514,7 @@ bool VolumeTextureLoadTaskClass::Begin_Compressed_Load() Apply_Mip_Reduction(MipLevelCount, Reduction, Width, Height, orig_mip_count); - D3DTexture = DX8Wrapper::_Create_DX8_Volume_Texture + NativeCompatibilityTexture = Create_Legacy_Volume_Texture ( Width, Height, @@ -2408,26 +3522,33 @@ bool VolumeTextureLoadTaskClass::Begin_Compressed_Load() Format, (MipCountType)MipLevelCount, #ifdef USE_MANAGED_TEXTURES - D3DPOOL_MANAGED + kLegacyManagedPool #else - D3DPOOL_SYSTEMMEM + kLegacySystemPool #endif ); return true; +#endif } bool VolumeTextureLoadTaskClass::Begin_Uncompressed_Load() { - unsigned orig_width,orig_height,orig_depth,orig_mip_count,orig_reduction; +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT_PRINT( + false, + "VolumeTextureLoadTaskClass::Begin_Uncompressed_Load: standalone bgfx cannot load fake-D3D volume textures"); + return false; +#else + unsigned width,height,depth,orig_mip_count,reduction; WW3DFormat orig_format; if (!Get_Texture_Information ( Texture->Get_Full_Path(), - orig_reduction, - orig_width, - orig_height, - orig_depth, + reduction, + width, + height, + depth, orig_format, orig_mip_count, false @@ -2449,18 +3570,18 @@ bool VolumeTextureLoadTaskClass::Begin_Uncompressed_Load() } // Destination size will be the next power of two square from the larger width and height... - unsigned ow = orig_width; - unsigned oh = orig_height; - unsigned od = orig_depth; - TextureLoader::Validate_Texture_Size(orig_width, orig_height, orig_depth); - if (orig_width != ow || orig_height != oh || orig_depth != od) + unsigned ow = width; + unsigned oh = height; + unsigned od = depth; + TextureLoader::Validate_Texture_Size(width, height, depth); + if (width != ow || height != oh || depth != od) { - WWDEBUG_SAY(("Invalid texture size, scaling required. Texture: %s, size: %d x %d -> %d x %d", Texture->Get_Full_Path().str(), ow, oh, orig_width, orig_height)); + WWDEBUG_SAY(("Invalid texture size, scaling required. Texture: %s, size: %d x %d -> %d x %d", Texture->Get_Full_Path().str(), ow, oh, width, height)); } - Width = orig_width; - Height = orig_height; - Depth = orig_depth; + Width = width; + Height = height; + Depth = depth; Reduction = 0; if (Format == WW3D_FORMAT_UNKNOWN) @@ -2472,7 +3593,7 @@ bool VolumeTextureLoadTaskClass::Begin_Uncompressed_Load() Format = Get_Valid_Texture_Format(Format, false); } - D3DTexture = DX8Wrapper::_Create_DX8_Volume_Texture + NativeCompatibilityTexture = Create_Legacy_Volume_Texture ( Width, Height, @@ -2480,20 +3601,21 @@ bool VolumeTextureLoadTaskClass::Begin_Uncompressed_Load() Format, Texture->MipLevelCount, #ifdef USE_MANAGED_TEXTURES - D3DPOOL_MANAGED + kLegacyManagedPool #else - D3DPOOL_SYSTEMMEM + kLegacySystemPool #endif ); return true; +#endif } bool VolumeTextureLoadTaskClass::Load_Compressed_Mipmap() { DDSFileClass dds_file(Texture->Get_Full_Path(), Get_Reduction()); - // if we can't load from file, indicate error. + // if we can't load from file, indicate rror. if (!dds_file.Is_Available() || !dds_file.Load()) { return false; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/textureloader.h b/Core/Libraries/Source/WWVegas/WW3D2/textureloader.h index b7d1f20b9ab..2f2dafec7c2 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/textureloader.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/textureloader.h @@ -43,7 +43,6 @@ #include "texture.h" class StringClass; -struct IDirect3DTexture8; class TextureLoadTaskClass; class TextureLoadTaskListClass; @@ -56,16 +55,12 @@ class TextureLoader // Modify given texture size to nearest valid size on current hardware. static void Validate_Texture_Size(unsigned& width, unsigned& height, unsigned& depth); - static IDirect3DTexture8 * Load_Thumbnail( - const StringClass& filename,const Vector3& hsv_shift); -// WW3DFormat texture_format); // Pass WW3D_FORMAT_UNKNOWN if you don't care - - static IDirect3DSurface8 * Load_Surface_Immediate( - const StringClass& filename, - WW3DFormat surface_format, // Pass WW3D_FORMAT_UNKNOWN if you don't care - bool allow_compression); - static void Request_Thumbnail(TextureBaseClass* tc); + static bool Load_Surface_Image_Immediate( + const char *filename, + WW3DFormat texture_format, + bool allow_compression, + SurfaceClass::SurfaceImageData &image); // Adds a loading task to the system. The task if processed in a separate // thread as soon as possible. The task will appear in finished tasks list @@ -82,7 +77,7 @@ class TextureLoader static void Update(void(*network_callback)() = nullptr); // returns true if current thread of execution is allowed to make DX8 calls. - static bool Is_DX8_Thread(); + static bool Is_Main_Render_Thread(); static void Suspend_Texture_Load(); static void Continue_Texture_Load(); @@ -90,6 +85,9 @@ class TextureLoader static void Set_Texture_Inactive_Override_Time(int time_ms) {TextureInactiveOverrideTime = time_ms;} private: + friend class TextureBaseClass; + + static void Delete_Texture_Load_Tasks(TextureBaseClass *tc); static void Process_Foreground_Load (TextureLoadTaskClass *task); static void Process_Foreground_Thumbnail (TextureLoadTaskClass *task); @@ -102,226 +100,3 @@ class TextureLoader // The default is zero. The scripted movies set this to reduce texture stalls in movies. static int TextureInactiveOverrideTime; }; - -class TextureLoadTaskListNodeClass -{ - friend class TextureLoadTaskListClass; - - public: - TextureLoadTaskListNodeClass() : Next(0), Prev(0) { } - - TextureLoadTaskListClass *Get_List() { return List; } - - TextureLoadTaskListNodeClass *Next; - TextureLoadTaskListNodeClass *Prev; - TextureLoadTaskListClass * List; -}; - - -class TextureLoadTaskListClass -{ - // This class implements an unsynchronized, double-linked list of TextureLoadTaskClass - // objects, using an embedded list node. - - public: - TextureLoadTaskListClass(); - - // Returns true if list is empty, false otherwise. - bool Is_Empty () const { return (Root.Next == &Root); } - - // Add a task to beginning of list - void Push_Front (TextureLoadTaskClass *task); - - // Add a task to end of list - void Push_Back (TextureLoadTaskClass *task); - - // Remove and return a task from beginning of list, or null if list is empty. - TextureLoadTaskClass * Pop_Front (); - - // Remove and return a task from end of list, or null if list is empty - TextureLoadTaskClass * Pop_Back (); - - // Remove specified task from list, if present - void Remove (TextureLoadTaskClass *task); - - private: - // This list is implemented using a sentinel node. - TextureLoadTaskListNodeClass Root; -}; - - -class SynchronizedTextureLoadTaskListClass : public TextureLoadTaskListClass -{ - // This class added thread-safety to the basic TextureLoadTaskListClass. - - public: - SynchronizedTextureLoadTaskListClass(); - - // See comments above for description of member functions. - void Push_Front (TextureLoadTaskClass *task); - void Push_Back (TextureLoadTaskClass *task); - TextureLoadTaskClass * Pop_Front (); - TextureLoadTaskClass * Pop_Back (); - void Remove (TextureLoadTaskClass *task); - - private: - FastCriticalSectionClass CriticalSection; -}; - -/* -** (gth) The allocation system we're using for TextureLoadTaskClass has gotten a little -** complicated since Kenny added the new task types for Cube and Volume textures. The -** ::Destroy member is used to return a task to the pool now and must be over-ridden in -** each derived class to put the task back into the correct free list. -*/ - - -class TextureLoadTaskClass : public TextureLoadTaskListNodeClass -{ - public: - enum TaskType { - TASK_NONE, - TASK_THUMBNAIL, - TASK_LOAD, - }; - - enum PriorityType { - PRIORITY_LOW, - PRIORITY_HIGH, - }; - - enum StateType { - STATE_NONE, - - STATE_LOAD_BEGUN, - STATE_LOAD_MIPMAP, - STATE_LOAD_COMPLETE, - - STATE_COMPLETE, - }; - - - TextureLoadTaskClass(); - ~TextureLoadTaskClass(); - - static TextureLoadTaskClass * Create (TextureBaseClass *tc, TaskType type, PriorityType priority); - static void Delete_Free_Pool (); - - virtual void Destroy (); - virtual void Init (TextureBaseClass *tc, TaskType type, PriorityType priority); - virtual void Deinit (); - - TaskType Get_Type () const { return Type; } - PriorityType Get_Priority () const { return Priority; } - StateType Get_State () const { return State; } - - WW3DFormat Get_Format () const { return Format; } - unsigned int Get_Width () const { return Width; } - unsigned int Get_Height () const { return Height; } - unsigned int Get_Mip_Level_Count () const { return MipLevelCount; } - unsigned int Get_Reduction () const { return Reduction; } - - unsigned char * Get_Locked_Surface_Ptr (unsigned int level); - unsigned int Get_Locked_Surface_Pitch(unsigned int level) const; - - TextureBaseClass * Peek_Texture () { return Texture; } - IDirect3DTexture8 * Peek_D3D_Texture () { return (IDirect3DTexture8*)D3DTexture; } - - void Set_Type (TaskType t) { Type = t; } - void Set_Priority (PriorityType p) { Priority = p; } - void Set_State (StateType s) { State = s; } - - bool Begin_Load (); - bool Load (); - void End_Load (); - void Finish_Load (); - void Apply_Missing_Texture (); - - protected: - virtual bool Begin_Compressed_Load (); - virtual bool Begin_Uncompressed_Load (); - - virtual bool Load_Compressed_Mipmap (); - virtual bool Load_Uncompressed_Mipmap(); - - virtual void Lock_Surfaces (); - virtual void Unlock_Surfaces (); - - void Apply (bool initialize); - - TextureBaseClass* Texture; - IDirect3DBaseTexture8* D3DTexture; - WW3DFormat Format; - - unsigned int Width; - unsigned int Height; - unsigned int MipLevelCount; - unsigned int Reduction; - Vector3 HSVShift; - - unsigned char * LockedSurfacePtr[MIP_LEVELS_MAX]; - unsigned int LockedSurfacePitch[MIP_LEVELS_MAX]; - - TaskType Type; - PriorityType Priority; - StateType State; -}; - -class CubeTextureLoadTaskClass : public TextureLoadTaskClass -{ -public: - CubeTextureLoadTaskClass(); - - virtual void Destroy () override; - virtual void Init (TextureBaseClass *tc, TaskType type, PriorityType priority) override; - virtual void Deinit () override; - -protected: - virtual bool Begin_Compressed_Load () override; - virtual bool Begin_Uncompressed_Load () override; - - virtual bool Load_Compressed_Mipmap () override; -// virtual bool Load_Uncompressed_Mipmap() override; - - virtual void Lock_Surfaces () override; - virtual void Unlock_Surfaces () override; - -private: - unsigned char* Get_Locked_CubeMap_Surface_Pointer(unsigned int face, unsigned int level); - unsigned int Get_Locked_CubeMap_Surface_Pitch(unsigned int face, unsigned int level) const; - - IDirect3DCubeTexture8* Peek_D3D_Cube_Texture() { return (IDirect3DCubeTexture8*)D3DTexture; } - - unsigned char* LockedCubeSurfacePtr[6][MIP_LEVELS_MAX]; - unsigned int LockedCubeSurfacePitch[6][MIP_LEVELS_MAX]; -}; - -class VolumeTextureLoadTaskClass : public TextureLoadTaskClass -{ -public: - VolumeTextureLoadTaskClass(); - - virtual void Destroy () override; - virtual void Init (TextureBaseClass *tc, TaskType type, PriorityType priority) override; - -protected: - virtual bool Begin_Compressed_Load () override; - virtual bool Begin_Uncompressed_Load () override; - - virtual bool Load_Compressed_Mipmap () override; -// virtual bool Load_Uncompressed_Mipmap() override; - - virtual void Lock_Surfaces () override; - virtual void Unlock_Surfaces () override; - -private: - unsigned char* Get_Locked_Volume_Pointer(unsigned int level); - unsigned int Get_Locked_Volume_Row_Pitch(unsigned int level); - unsigned int Get_Locked_Volume_Slice_Pitch(unsigned int level); - - IDirect3DVolumeTexture8* Peek_D3D_Volume_Texture() { return (IDirect3DVolumeTexture8*)D3DTexture; } - - unsigned int LockedSurfaceSlicePitch[MIP_LEVELS_MAX]; - - unsigned int Depth; -}; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/vertexbuffer.cpp similarity index 58% rename from Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp rename to Core/Libraries/Source/WWVegas/WW3D2/vertexbuffer.cpp index a6551553239..c421cf5e64b 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/vertexbuffer.cpp @@ -22,7 +22,7 @@ * * * Project Name : ww3d * * * - * $Archive:: /Commando/Code/ww3d2/dx8vertexbuffer.cpp $* + * $Archive:: /Commando/Code/ww3d2/vertexbuffer.cpp $* * * * Original Author:: Jani Penttinen * * * @@ -39,15 +39,33 @@ //#define VERTEX_BUFFER_LOG +#include "vertexbuffer.h" +#if !defined(GGC_RENDER_BACKEND_BGFX) #include "dx8vertexbuffer.h" #include "dx8wrapper.h" +#endif #include "dx8fvf.h" -#include "dx8caps.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/renderbufferclasses.h" #include "WWLib/thread.h" +#include "WW3D2/ww3dcolor.h" #include "WWDebug/wwmemlog.h" -#include +#include + +#if defined(GGC_RENDER_BACKEND_BGFX) +#define RENDER_BUFFER_THREAD_ASSERT() +#else +#define RENDER_BUFFER_THREAD_ASSERT() DX8_THREAD_ASSERT() +#endif -#define DEFAULT_VB_SIZE 5000 +// 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. + +static constexpr unsigned short kDefaultDynamicVertexBufferSize = 5000; static bool _DynamicSortingVertexArrayInUse=false; //static VertexFormatXYZNDUV2* _DynamicSortingVertexArray=nullptr; @@ -55,10 +73,10 @@ static SortingVertexBufferClass* _DynamicSortingVertexArray=nullptr; static unsigned short _DynamicSortingVertexArraySize=0; static unsigned short _DynamicSortingVertexArrayOffset=0; -static bool _DynamicDX8VertexBufferInUse=false; -static DX8VertexBufferClass* _DynamicDX8VertexBuffer=nullptr; -static unsigned short _DynamicDX8VertexBufferSize=DEFAULT_VB_SIZE; -static unsigned short _DynamicDX8VertexBufferOffset=0; +static bool _DynamicBackendVertexBufferInUse=false; +static RenderVertexBufferClass* _DynamicBackendVertexBuffer=nullptr; +static unsigned short _DynamicBackendVertexBufferSize=kDefaultDynamicVertexBufferSize; +static unsigned short _DynamicBackendVertexBufferOffset=0; static const FVFInfoClass _DynamicFVFInfo(dynamic_fvf_type); @@ -68,6 +86,35 @@ static int _VertexBufferCount; static int _VertexBufferTotalVertices; static int _VertexBufferTotalSize; +#if !defined(GGC_RENDER_BACKEND_BGFX) +using LegacyVertexBuffer = IDirect3DVertexBuffer8; + +constexpr unsigned kLegacyBufferUsageWriteOnly = D3DUSAGE_WRITEONLY, kLegacyBufferUsageDynamic = D3DUSAGE_DYNAMIC, kLegacyBufferUsageNPatches = D3DUSAGE_NPATCHES, kLegacyBufferUsageSoftwareProcessing = D3DUSAGE_SOFTWAREPROCESSING; + +static unsigned BuildLegacyBufferUsage(DX8VertexBufferClass::UsageType usage) +{ + return kLegacyBufferUsageWriteOnly | + ((usage&DX8VertexBufferClass::USAGE_DYNAMIC) ? kLegacyBufferUsageDynamic : 0) | + ((usage&DX8VertexBufferClass::USAGE_NPATCHES) ? kLegacyBufferUsageNPatches : 0) | + ((usage&DX8VertexBufferClass::USAGE_SOFTWAREPROCESSING) ? kLegacyBufferUsageSoftwareProcessing : 0); +} + +static auto GetLegacyBufferPool(DX8VertexBufferClass::UsageType usage) +{ + return (usage&DX8VertexBufferClass::USAGE_DYNAMIC) ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED; +} + +static auto Legacy_Device() +{ + return DX8Wrapper::_Get_D3D_Device8(); +} + +static LegacyVertexBuffer *Legacy_Vertex_Buffer(DX8VertexBufferClass *vertex_buffer) +{ + return static_cast(vertex_buffer->Get_Legacy_Vertex_Buffer()); +} +#endif + // ---------------------------------------------------------------------------- // // @@ -76,13 +123,18 @@ static int _VertexBufferTotalSize; VertexBufferClass::VertexBufferClass(unsigned type_, unsigned FVF, unsigned short vertex_count_) : - VertexCount(vertex_count_), - type(type_), - engine_refs(0) + VertexCount(vertex_count_), + type(type_), + engine_refs(0), + CPUBufferData(nullptr), + CPUBufferSize(0), + CPUBufferValid(false), + m_backendStaticEligible(false) { + m_backendHandle = kInvalidRenderResource; WWMEMLOG(MEM_RENDERER); WWASSERT(VertexCount); - WWASSERT(type==BUFFER_TYPE_DX8 || type==BUFFER_TYPE_SORTING); + WWASSERT(type==BUFFER_TYPE_STATIC || type==BUFFER_TYPE_SORTING); WWASSERT(FVF != 0); fvf_info=W3DNEW FVFInfoClass(FVF); @@ -112,7 +164,8 @@ VertexBufferClass::~VertexBufferClass() _VertexBufferCount, _VertexBufferTotalVertices, _VertexBufferTotalSize)); -#endif + #endif + delete[] CPUBufferData; delete fvf_info; } @@ -131,6 +184,61 @@ unsigned VertexBufferClass::Get_Total_Allocated_Memory() return _VertexBufferTotalSize; } +void *VertexBufferClass::Lock_CPU_Buffer_Data(unsigned byte_offset, unsigned size) +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + if (type == BUFFER_TYPE_STATIC && m_backendHandle == kInvalidRenderResource) { + WWASSERT_PRINT( + false, + "VertexBufferClass::Lock_CPU_Buffer_Data: standalone bgfx static vertex buffers require a backend resource"); + return nullptr; + } +#endif + const unsigned total_size = VertexCount * fvf_info->Get_FVF_Size(); + if (byte_offset > total_size || size > total_size - byte_offset) { + WWASSERT(0); + return nullptr; + } + + if (CPUBufferData == nullptr) { + CPUBufferData = W3DNEWARRAY unsigned char[total_size]; + std::memset(CPUBufferData, 0, total_size); + CPUBufferSize = total_size; + } + CPUBufferValid = true; + return CPUBufferData + byte_offset; +} + +void VertexBufferClass::Update_CPU_Buffer_Data(unsigned byte_offset, const void * data, unsigned size) +{ + if (data == nullptr || size == 0) { + return; + } + +#if defined(GGC_RENDER_BACKEND_BGFX) + if (type == BUFFER_TYPE_STATIC && m_backendHandle == kInvalidRenderResource) { + WWASSERT_PRINT( + false, + "VertexBufferClass::Update_CPU_Buffer_Data: standalone bgfx static vertex buffers require a backend resource"); + return; + } +#endif + const unsigned total_size = VertexCount * fvf_info->Get_FVF_Size(); + if (byte_offset > total_size || size > total_size - byte_offset) { + WWASSERT(0); + return; + } + + if (CPUBufferData == nullptr) { + CPUBufferData = W3DNEWARRAY unsigned char[total_size]; + std::memset(CPUBufferData, 0, total_size); + CPUBufferSize = total_size; + } + + std::memcpy(CPUBufferData + byte_offset, data, size); + CPUBufferValid = true; +} + // ---------------------------------------------------------------------------- @@ -157,12 +265,12 @@ VertexBufferClass::WriteLockClass::WriteLockClass(VertexBufferClass* VertexBuffe : VertexBufferLockClass(VertexBuffer) { - DX8_THREAD_ASSERT(); + RENDER_BUFFER_THREAD_ASSERT(); WWASSERT(VertexBuffer); WWASSERT(!VertexBuffer->Engine_Refs()); VertexBuffer->Add_Ref(); switch (VertexBuffer->Type()) { - case BUFFER_TYPE_DX8: + case BUFFER_TYPE_STATIC: #ifdef VERTEX_BUFFER_LOG { StringClass fvf_name; @@ -173,12 +281,21 @@ VertexBufferClass::WriteLockClass::WriteLockClass(VertexBufferClass* VertexBuffe fvf_name)); } #endif +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8_Assert(); - DX8_ErrorCode(static_cast(VertexBuffer)->Get_DX8_Vertex_Buffer()->Lock( - 0, - 0, - (unsigned char**)&Vertices, - flags)); //flags + if (LegacyVertexBuffer *legacy = Legacy_Vertex_Buffer(static_cast(VertexBuffer))) { + DX8_ErrorCode(legacy->Lock( + 0, + 0, + (unsigned char**)&Vertices, + flags)); //flags + } else +#endif + { + Vertices = VertexBuffer->Lock_CPU_Buffer_Data( + 0, + VertexBuffer->Get_Vertex_Count() * VertexBuffer->FVF_Info().Get_FVF_Size()); + } break; case BUFFER_TYPE_SORTING: Vertices=static_cast(VertexBuffer)->VertexBuffer; @@ -193,14 +310,28 @@ VertexBufferClass::WriteLockClass::WriteLockClass(VertexBufferClass* VertexBuffe VertexBufferClass::WriteLockClass::~WriteLockClass() { - DX8_THREAD_ASSERT(); + RENDER_BUFFER_THREAD_ASSERT(); + // TheSuperHackers @refactor bobtista 11/04/2026 Capture STATIC and SORTING vertex writes + // into the render backend before Unlock invalidates the source pointer. + if (Vertices != NULL && + (VertexBuffer->Type() == BUFFER_TYPE_STATIC || VertexBuffer->Type() == BUFFER_TYPE_SORTING)) { + const unsigned int total_bytes = VertexBuffer->Get_Vertex_Count() * VertexBuffer->FVF_Info().Get_FVF_Size(); + VertexBuffer->Update_CPU_Buffer_Data(0, Vertices, total_bytes); + if (g_renderBackend != NULL) { + g_renderBackend->Upload_Vertex_Buffer_Data(VertexBuffer, Vertices, total_bytes); + } + } switch (VertexBuffer->Type()) { - case BUFFER_TYPE_DX8: + case BUFFER_TYPE_STATIC: #ifdef VERTEX_BUFFER_LOG WWDEBUG_SAY(("VertexBuffer->Unlock()")); #endif +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8_Assert(); - DX8_ErrorCode(static_cast(VertexBuffer)->Get_DX8_Vertex_Buffer()->Unlock()); + if (LegacyVertexBuffer *legacy = Legacy_Vertex_Buffer(static_cast(VertexBuffer))) { + DX8_ErrorCode(legacy->Unlock()); + } +#endif break; case BUFFER_TYPE_SORTING: break; @@ -217,17 +348,19 @@ 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) + VertexBufferLockClass(VertexBuffer), + AppendStartIndex(start_index), + AppendIndexRange(index_range) { - DX8_THREAD_ASSERT(); + RENDER_BUFFER_THREAD_ASSERT(); WWASSERT(VertexBuffer); WWASSERT(!VertexBuffer->Engine_Refs()); WWASSERT(start_index+index_range<=VertexBuffer->Get_Vertex_Count()); VertexBuffer->Add_Ref(); switch (VertexBuffer->Type()) { - case BUFFER_TYPE_DX8: + case BUFFER_TYPE_STATIC: #ifdef VERTEX_BUFFER_LOG { StringClass fvf_name; @@ -239,12 +372,21 @@ VertexBufferClass::AppendLockClass::AppendLockClass(VertexBufferClass* VertexBuf fvf_name)); } #endif +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8_Assert(); - DX8_ErrorCode(static_cast(VertexBuffer)->Get_DX8_Vertex_Buffer()->Lock( - start_index*VertexBuffer->FVF_Info().Get_FVF_Size(), - index_range*VertexBuffer->FVF_Info().Get_FVF_Size(), - (unsigned char**)&Vertices, - 0)); // Default (no) flags + if (LegacyVertexBuffer *legacy = Legacy_Vertex_Buffer(static_cast(VertexBuffer))) { + DX8_ErrorCode(legacy->Lock( + start_index*VertexBuffer->FVF_Info().Get_FVF_Size(), + index_range*VertexBuffer->FVF_Info().Get_FVF_Size(), + (unsigned char**)&Vertices, + flags)); + } else +#endif + { + Vertices = VertexBuffer->Lock_CPU_Buffer_Data( + start_index*VertexBuffer->FVF_Info().Get_FVF_Size(), + index_range*VertexBuffer->FVF_Info().Get_FVF_Size()); + } break; case BUFFER_TYPE_SORTING: Vertices=static_cast(VertexBuffer)->VertexBuffer+start_index; @@ -259,14 +401,29 @@ VertexBufferClass::AppendLockClass::AppendLockClass(VertexBufferClass* VertexBuf VertexBufferClass::AppendLockClass::~AppendLockClass() { - DX8_THREAD_ASSERT(); + RENDER_BUFFER_THREAD_ASSERT(); + // 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 (Vertices != NULL && + (VertexBuffer->Type() == BUFFER_TYPE_STATIC || VertexBuffer->Type() == BUFFER_TYPE_SORTING)) { + const unsigned int fvf_size = VertexBuffer->FVF_Info().Get_FVF_Size(); + const unsigned int size_bytes = AppendIndexRange * fvf_size; + VertexBuffer->Update_CPU_Buffer_Data(AppendStartIndex * fvf_size, Vertices, size_bytes); + if (g_renderBackend != NULL) { + g_renderBackend->Upload_Vertex_Buffer_Sub_Range(VertexBuffer, Vertices, AppendStartIndex, size_bytes); + } + } switch (VertexBuffer->Type()) { - case BUFFER_TYPE_DX8: + case BUFFER_TYPE_STATIC: +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8_Assert(); #ifdef VERTEX_BUFFER_LOG WWDEBUG_SAY(("VertexBuffer->Unlock()")); #endif - DX8_ErrorCode(static_cast(VertexBuffer)->Get_DX8_Vertex_Buffer()->Unlock()); + if (LegacyVertexBuffer *legacy = Legacy_Vertex_Buffer(static_cast(VertexBuffer))) { + DX8_ErrorCode(legacy->Unlock()); + } +#endif break; case BUFFER_TYPE_SORTING: break; @@ -307,9 +464,10 @@ SortingVertexBufferClass::~SortingVertexBufferClass() // bool dynamic=false,bool softwarevp=false); +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8VertexBufferClass::DX8VertexBufferClass(unsigned FVF, unsigned short vertex_count_, UsageType usage) : - VertexBufferClass(BUFFER_TYPE_DX8, FVF, vertex_count_), + VertexBufferClass(BUFFER_TYPE_STATIC, FVF, vertex_count_), VertexBuffer(nullptr) { Create_Vertex_Buffer(usage); @@ -324,7 +482,7 @@ DX8VertexBufferClass::DX8VertexBufferClass( unsigned short VertexCount, UsageType usage) : - VertexBufferClass(BUFFER_TYPE_DX8, D3DFVF_XYZ|D3DFVF_TEX1|D3DFVF_NORMAL, VertexCount), + VertexBufferClass(BUFFER_TYPE_STATIC, FVFInfoClass::Build_FVF(true, false, false, 1), VertexCount), VertexBuffer(nullptr) { WWASSERT(vertices); @@ -345,7 +503,7 @@ DX8VertexBufferClass::DX8VertexBufferClass( unsigned short VertexCount, UsageType usage) : - VertexBufferClass(BUFFER_TYPE_DX8, D3DFVF_XYZ|D3DFVF_TEX1|D3DFVF_NORMAL|D3DFVF_DIFFUSE, VertexCount), + VertexBufferClass(BUFFER_TYPE_STATIC, FVFInfoClass::Build_FVF(true, true, false, 1), VertexCount), VertexBuffer(nullptr) { WWASSERT(vertices); @@ -366,7 +524,7 @@ DX8VertexBufferClass::DX8VertexBufferClass( unsigned short VertexCount, UsageType usage) : - VertexBufferClass(BUFFER_TYPE_DX8, D3DFVF_XYZ|D3DFVF_TEX1|D3DFVF_DIFFUSE, VertexCount), + VertexBufferClass(BUFFER_TYPE_STATIC, FVFInfoClass::Build_FVF(false, true, false, 1), VertexCount), VertexBuffer(nullptr) { WWASSERT(vertices); @@ -385,7 +543,7 @@ DX8VertexBufferClass::DX8VertexBufferClass( unsigned short VertexCount, UsageType usage) : - VertexBufferClass(BUFFER_TYPE_DX8, D3DFVF_XYZ|D3DFVF_TEX1, VertexCount), + VertexBufferClass(BUFFER_TYPE_STATIC, FVFInfoClass::Build_FVF(false, false, false, 1), VertexCount), VertexBuffer(nullptr) { WWASSERT(vertices); @@ -404,8 +562,40 @@ DX8VertexBufferClass::~DX8VertexBufferClass() _DX8VertexBufferCount--; WWDEBUG_SAY(("Current vertex buffer count: %d",_DX8VertexBufferCount)); #endif - VertexBuffer->Release(); + // TheSuperHackers @refactor bobtista 21/04/2026 — release + // the backend-neutral handle before the legacy resource goes away. + if (m_backendHandle != kInvalidRenderResource && g_renderBackend != nullptr) { + g_renderBackend->Destroy_Resource(m_backendHandle); + m_backendHandle = kInvalidRenderResource; + } + if (LegacyVertexBuffer *legacy = Legacy_Vertex_Buffer(this)) { + legacy->Release(); + } } +#endif + +// ---------------------------------------------------------------------------- + +#if defined(GGC_RENDER_BACKEND_BGFX) +RenderVertexBufferClass::RenderVertexBufferClass(unsigned FVF, unsigned short vertex_count_, UsageType usage) + : + VertexBufferClass(BUFFER_TYPE_STATIC, FVF, vertex_count_) +{ + RENDER_BUFFER_THREAD_ASSERT(); + Set_Backend_Static_Eligible((usage & USAGE_DYNAMIC) == 0); + if (g_renderBackend != nullptr) { + m_backendHandle = g_renderBackend->Register_Vertex_Buffer_Resource(this); + } +} + +RenderVertexBufferClass::~RenderVertexBufferClass() +{ + if (m_backendHandle != kInvalidRenderResource && g_renderBackend != nullptr) { + g_renderBackend->Destroy_Resource(m_backendHandle); + m_backendHandle = kInvalidRenderResource; + } +} +#endif // ---------------------------------------------------------------------------- // @@ -413,42 +603,55 @@ DX8VertexBufferClass::~DX8VertexBufferClass() // // ---------------------------------------------------------------------------- +#if !defined(GGC_RENDER_BACKEND_BGFX) void DX8VertexBufferClass::Create_Vertex_Buffer(UsageType usage) { - DX8_THREAD_ASSERT(); + RENDER_BUFFER_THREAD_ASSERT(); WWASSERT(!VertexBuffer); + Set_Backend_Static_Eligible((usage & USAGE_DYNAMIC) == 0); #ifdef VERTEX_BUFFER_LOG StringClass fvf_name; FVF_Info().Get_FVF_Name(fvf_name); - WWDEBUG_SAY(("CreateVertexBuffer(fvfsize=%d, vertex_count=%d, D3DUSAGE_WRITEONLY|%s|%s, fvf: %s, %s)", + WWDEBUG_SAY(("CreateVertexBuffer(fvfsize=%d, vertex_count=%d, legacy writeonly|%s|%s, fvf: %s, %s)", FVF_Info().Get_FVF_Size(), VertexCount, - (usage&USAGE_DYNAMIC) ? "D3DUSAGE_DYNAMIC" : "-", - (usage&USAGE_SOFTWAREPROCESSING) ? "D3DUSAGE_SOFTWAREPROCESSING" : "-", + (usage&USAGE_DYNAMIC) ? "legacy dynamic" : "-", + (usage&USAGE_SOFTWAREPROCESSING) ? "legacy softwareprocessing" : "-", fvf_name, - (usage&USAGE_DYNAMIC) ? "D3DPOOL_DEFAULT" : "D3DPOOL_MANAGED")); + (usage&USAGE_DYNAMIC) ? "legacy default pool" : "legacy managed pool")); _DX8VertexBufferCount++; WWDEBUG_SAY(("Current vertex buffer count: %d",_DX8VertexBufferCount)); #endif - unsigned usage_flags= - D3DUSAGE_WRITEONLY| - ((usage&USAGE_DYNAMIC) ? D3DUSAGE_DYNAMIC : 0)| - ((usage&USAGE_NPATCHES) ? D3DUSAGE_NPATCHES : 0)| - ((usage&USAGE_SOFTWAREPROCESSING) ? D3DUSAGE_SOFTWAREPROCESSING : 0); + if (g_renderBackend != nullptr && !g_renderBackend->Requires_Legacy_Buffer_Resources()) { + m_backendHandle = g_renderBackend->Register_Vertex_Buffer_Resource(this); + return; + } + +#if defined(GGC_RENDER_BACKEND_BGFX) + WWASSERT(0); + return; +#else + unsigned usage_flags=BuildLegacyBufferUsage(usage); // New Code - if (!DX8Wrapper::Get_Current_Caps()->Support_TnL()) { - usage_flags|=D3DUSAGE_SOFTWAREPROCESSING; + if (!g_renderBackend || !g_renderBackend->Supports_Hardware_Transform_And_Lighting()) { + usage_flags|=kLegacyBufferUsageSoftwareProcessing; } - HRESULT ret=DX8Wrapper::_Get_D3D_Device8()->CreateVertexBuffer( + LegacyVertexBuffer *new_vertex_buffer = nullptr; + HRESULT ret=Legacy_Device()->CreateVertexBuffer( FVF_Info().Get_FVF_Size()*VertexCount, usage_flags, FVF_Info().Get_FVF(), - (usage&USAGE_DYNAMIC) ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED, - &VertexBuffer); + GetLegacyBufferPool(usage), + &new_vertex_buffer); + VertexBuffer = new_vertex_buffer; if (SUCCEEDED(ret)) { + //: populate backend-neutral handle. + if (g_renderBackend != nullptr) { + m_backendHandle = g_renderBackend->Register_Vertex_Buffer_Resource(this); + } return; } @@ -463,18 +666,23 @@ void DX8VertexBufferClass::Create_Vertex_Buffer(UsageType usage) WW3D::_Invalidate_Mesh_Cache(); //@todo: Find some way to invalidate the textures too - ret = DX8Wrapper::_Get_D3D_Device8()->ResourceManagerDiscardBytes(0); + ret = Legacy_Device()->ResourceManagerDiscardBytes(0); // Try again... - ret=DX8Wrapper::_Get_D3D_Device8()->CreateVertexBuffer( + new_vertex_buffer = nullptr; + ret=Legacy_Device()->CreateVertexBuffer( FVF_Info().Get_FVF_Size()*VertexCount, usage_flags, FVF_Info().Get_FVF(), - (usage&USAGE_DYNAMIC) ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED, - &VertexBuffer); + GetLegacyBufferPool(usage), + &new_vertex_buffer); + VertexBuffer = new_vertex_buffer; if (SUCCEEDED(ret)) { WWDEBUG_SAY(("...Vertex buffer creation successful")); + if (g_renderBackend != nullptr) { + m_backendHandle = g_renderBackend->Register_Vertex_Buffer_Resource(this); + } } // If it still fails it is fatal @@ -485,9 +693,10 @@ void DX8VertexBufferClass::Create_Vertex_Buffer(UsageType usage) FVF_Info().Get_FVF_Size()*VertexCount, usage_flags, FVF_Info().Get_FVF(), - (usage&USAGE_DYNAMIC) ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED, + GetLegacyBufferPool(usage), &VertexBuffer)); */ +#endif } // ---------------------------------------------------------------------------- @@ -650,7 +859,7 @@ void DX8VertexBufferClass::Copy(const Vector3* loc, const Vector3* norm, const V verts[v].nz=(*norm++)[2]; verts[v].u1=(*uv)[0]; verts[v].v1=(*uv++)[1]; - verts[v].diffuse=DX8Wrapper::Convert_Color(diffuse[v]); + verts[v].diffuse=WW3DColor::To_ARGB(diffuse[v]); } } else { @@ -665,7 +874,7 @@ void DX8VertexBufferClass::Copy(const Vector3* loc, const Vector3* norm, const V verts[v].nz=(*norm++)[2]; verts[v].u1=(*uv)[0]; verts[v].v1=(*uv++)[1]; - verts[v].diffuse=DX8Wrapper::Convert_Color(diffuse[v]); + verts[v].diffuse=WW3DColor::To_ARGB(diffuse[v]); } } } @@ -689,7 +898,7 @@ void DX8VertexBufferClass::Copy(const Vector3* loc, const Vector2* uv, const Vec verts[v].z=(*loc++)[2]; verts[v].u1=(*uv)[0]; verts[v].v1=(*uv++)[1]; - verts[v].diffuse=DX8Wrapper::Convert_Color(diffuse[v]); + verts[v].diffuse=WW3DColor::To_ARGB(diffuse[v]); } } else { @@ -701,11 +910,14 @@ void DX8VertexBufferClass::Copy(const Vector3* loc, const Vector2* uv, const Vec verts[v].z=(*loc++)[2]; verts[v].u1=(*uv)[0]; verts[v].v1=(*uv++)[1]; - verts[v].diffuse=DX8Wrapper::Convert_Color(diffuse[v]); + verts[v].diffuse=WW3DColor::To_ARGB(diffuse[v]); } } } +// ---------------------------------------------------------------------------- +#endif + // ---------------------------------------------------------------------------- // // @@ -720,10 +932,10 @@ DynamicVBAccessClass::DynamicVBAccessClass(unsigned t,unsigned fvf,unsigned shor VertexBuffer(nullptr) { WWASSERT(fvf==dynamic_fvf_type); - WWASSERT(Type==BUFFER_TYPE_DYNAMIC_DX8 || Type==BUFFER_TYPE_DYNAMIC_SORTING); + WWASSERT(Type==BUFFER_TYPE_DYNAMIC || Type==BUFFER_TYPE_DYNAMIC_SORTING); - if (Type==BUFFER_TYPE_DYNAMIC_DX8) { - Allocate_DX8_Dynamic_Buffer(); + if (Type==BUFFER_TYPE_DYNAMIC) { + Allocate_Backend_Dynamic_Buffer(); } else { Allocate_Sorting_Dynamic_Buffer(); @@ -732,9 +944,9 @@ DynamicVBAccessClass::DynamicVBAccessClass(unsigned t,unsigned fvf,unsigned shor DynamicVBAccessClass::~DynamicVBAccessClass() { - if (Type==BUFFER_TYPE_DYNAMIC_DX8) { - _DynamicDX8VertexBufferInUse=false; - _DynamicDX8VertexBufferOffset+=(unsigned) VertexCount; + if (Type==BUFFER_TYPE_DYNAMIC) { + _DynamicBackendVertexBufferInUse=false; + _DynamicBackendVertexBufferOffset+=(unsigned) VertexCount; } else { _DynamicSortingVertexArrayInUse=false; @@ -748,11 +960,11 @@ DynamicVBAccessClass::~DynamicVBAccessClass() void DynamicVBAccessClass::_Deinit() { - WWASSERT ((_DynamicDX8VertexBuffer == nullptr) || (_DynamicDX8VertexBuffer->Num_Refs() == 1)); - REF_PTR_RELEASE(_DynamicDX8VertexBuffer); - _DynamicDX8VertexBufferInUse=false; - _DynamicDX8VertexBufferSize=DEFAULT_VB_SIZE; - _DynamicDX8VertexBufferOffset=0; + WWASSERT ((_DynamicBackendVertexBuffer == nullptr) || (_DynamicBackendVertexBuffer->Num_Refs() == 1)); + REF_PTR_RELEASE(_DynamicBackendVertexBuffer); + _DynamicBackendVertexBufferInUse=false; + _DynamicBackendVertexBufferSize=kDefaultDynamicVertexBufferSize; + _DynamicBackendVertexBufferOffset=0; WWASSERT ((_DynamicSortingVertexArray == nullptr) || (_DynamicSortingVertexArray->Num_Refs() == 1)); REF_PTR_RELEASE(_DynamicSortingVertexArray); @@ -762,41 +974,41 @@ void DynamicVBAccessClass::_Deinit() _DynamicSortingVertexArrayOffset=0; } -void DynamicVBAccessClass::Allocate_DX8_Dynamic_Buffer() +void DynamicVBAccessClass::Allocate_Backend_Dynamic_Buffer() { WWMEMLOG(MEM_RENDERER); - WWASSERT(!_DynamicDX8VertexBufferInUse); - _DynamicDX8VertexBufferInUse=true; + WWASSERT(!_DynamicBackendVertexBufferInUse); + _DynamicBackendVertexBufferInUse=true; // If requesting more vertices than dynamic vertex buffer can fit, delete the vb // and adjust the size to the new count. - if (VertexCount>_DynamicDX8VertexBufferSize) { - REF_PTR_RELEASE(_DynamicDX8VertexBuffer); - _DynamicDX8VertexBufferSize=VertexCount; - if (_DynamicDX8VertexBufferSize_DynamicBackendVertexBufferSize) { + REF_PTR_RELEASE(_DynamicBackendVertexBuffer); + _DynamicBackendVertexBufferSize=VertexCount; + if (_DynamicBackendVertexBufferSizeSupport_NPatches()) { - usage|=DX8VertexBufferClass::USAGE_NPATCHES; + if (!_DynamicBackendVertexBuffer) { + unsigned usage=RenderVertexBufferClass::USAGE_DYNAMIC; + if (g_renderBackend && g_renderBackend->Supports_NPatches()) { + usage|=RenderVertexBufferClass::USAGE_NPATCHES; } - _DynamicDX8VertexBuffer=NEW_REF(DX8VertexBufferClass,( + _DynamicBackendVertexBuffer=NEW_REF(RenderVertexBufferClass,( dynamic_fvf_type, - _DynamicDX8VertexBufferSize, - (DX8VertexBufferClass::UsageType)usage)); - _DynamicDX8VertexBufferOffset=0; + _DynamicBackendVertexBufferSize, + (RenderVertexBufferClass::UsageType)usage)); + _DynamicBackendVertexBufferOffset=0; } // Any room at the end of the buffer? - if (((unsigned)VertexCount+_DynamicDX8VertexBufferOffset)>_DynamicDX8VertexBufferSize) { - _DynamicDX8VertexBufferOffset=0; + if (((unsigned)VertexCount+_DynamicBackendVertexBufferOffset)>_DynamicBackendVertexBufferSize) { + _DynamicBackendVertexBufferOffset=0; } - REF_PTR_SET(VertexBuffer,_DynamicDX8VertexBuffer); - VertexBufferOffset=_DynamicDX8VertexBufferOffset; + REF_PTR_SET(VertexBuffer,_DynamicBackendVertexBuffer); + VertexBufferOffset=_DynamicBackendVertexBufferOffset; } void DynamicVBAccessClass::Allocate_Sorting_Dynamic_Buffer() @@ -805,12 +1017,22 @@ void DynamicVBAccessClass::Allocate_Sorting_Dynamic_Buffer() WWASSERT(!_DynamicSortingVertexArrayInUse); _DynamicSortingVertexArrayInUse=true; - unsigned new_vertex_count=_DynamicSortingVertexArrayOffset+VertexCount; - WWASSERT(new_vertex_count<65536); - if (new_vertex_count>_DynamicSortingVertexArraySize) { + unsigned new_vertex_count=(unsigned)_DynamicSortingVertexArrayOffset+VertexCount; + // TheSuperHackers @bugfix bobtista 13/07/2026 Start a fresh buffer when the request would + // cross the 65535 vertex capacity of SortingVertexBufferClass. The size was silently + // truncated to 16 bits, so the subsequent vertex writes overflowed the allocation. + // Draws queued earlier hold their own reference to the old buffer, so their data stays valid. + if (new_vertex_count>65535) { + REF_PTR_RELEASE(_DynamicSortingVertexArray); + _DynamicSortingVertexArraySize=VertexCount; + if (_DynamicSortingVertexArraySize_DynamicSortingVertexArraySize) { REF_PTR_RELEASE(_DynamicSortingVertexArray); _DynamicSortingVertexArraySize=new_vertex_count; - if (_DynamicSortingVertexArraySizeGet_Type()) { - case BUFFER_TYPE_DYNAMIC_DX8: + case BUFFER_TYPE_DYNAMIC: #ifdef VERTEX_BUFFER_LOG { WWASSERT(!dx8_lock); @@ -844,16 +1068,35 @@ DynamicVBAccessClass::WriteLockClass::WriteLockClass(DynamicVBAccessClass* dynam fvf_name)); } #endif - WWASSERT(_DynamicDX8VertexBuffer); -// WWASSERT(!_DynamicDX8VertexBuffer->Engine_Refs()); + WWASSERT(_DynamicBackendVertexBuffer); +// WWASSERT(!_DynamicBackendVertexBuffer->Engine_Refs()); - DX8_Assert(); // Lock with discard contents if the buffer offset is zero - DX8_ErrorCode(static_cast(DynamicVBAccess->VertexBuffer)->Get_DX8_Vertex_Buffer()->Lock( - DynamicVBAccess->VertexBufferOffset*_DynamicDX8VertexBuffer->FVF_Info().Get_FVF_Size(), - DynamicVBAccess->Get_Vertex_Count()*DynamicVBAccess->VertexBuffer->FVF_Info().Get_FVF_Size(), - (unsigned char**)&Vertices, - D3DLOCK_NOSYSLOCK | (!DynamicVBAccess->VertexBufferOffset ? D3DLOCK_DISCARD : D3DLOCK_NOOVERWRITE))); +#if !defined(GGC_RENDER_BACKEND_BGFX) + DX8_Assert(); + if (LegacyVertexBuffer *legacy = Legacy_Vertex_Buffer(static_cast(DynamicVBAccess->VertexBuffer))) { + DX8_ErrorCode(legacy->Lock( + DynamicVBAccess->VertexBufferOffset*_DynamicBackendVertexBuffer->FVF_Info().Get_FVF_Size(), + DynamicVBAccess->Get_Vertex_Count()*DynamicVBAccess->VertexBuffer->FVF_Info().Get_FVF_Size(), + (unsigned char**)&Vertices, + RB_LOCK_NOSYSLOCK | (!DynamicVBAccess->VertexBufferOffset ? RB_LOCK_DISCARD : RB_LOCK_NOOVERWRITE))); + } else +#endif + { + const unsigned int vb_bytes = DynamicVBAccess->Get_Vertex_Count() * + DynamicVBAccess->VertexBuffer->FVF_Info().Get_FVF_Size(); + if (g_renderBackend != NULL) { + Vertices = static_cast( + g_renderBackend->Begin_Dynamic_Vertex_Write(DynamicVBAccess, vb_bytes)); + } + if (Vertices != NULL) { + DirectBackendWrite = true; + } else { + Vertices = static_cast(DynamicVBAccess->VertexBuffer->Lock_CPU_Buffer_Data( + DynamicVBAccess->VertexBufferOffset*_DynamicBackendVertexBuffer->FVF_Info().Get_FVF_Size(), + vb_bytes)); + } + } break; case BUFFER_TYPE_DYNAMIC_SORTING: Vertices=static_cast(DynamicVBAccess->VertexBuffer)->VertexBuffer; @@ -870,16 +1113,33 @@ DynamicVBAccessClass::WriteLockClass::WriteLockClass(DynamicVBAccessClass* dynam DynamicVBAccessClass::WriteLockClass::~WriteLockClass() { - DX8_THREAD_ASSERT(); + RENDER_BUFFER_THREAD_ASSERT(); switch (DynamicVBAccess->Get_Type()) { - case BUFFER_TYPE_DYNAMIC_DX8: + case BUFFER_TYPE_DYNAMIC: #ifdef VERTEX_BUFFER_LOG dx8_lock--; 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(); + if (DirectBackendWrite) { + g_renderBackend->End_Dynamic_Vertex_Write(DynamicVBAccess, Vertices, total_bytes); + } else { + g_renderBackend->Capture_Dynamic_Vertex_Data(DynamicVBAccess, Vertices, total_bytes); + } + } +#if !defined(GGC_RENDER_BACKEND_BGFX) DX8_Assert(); - DX8_ErrorCode(static_cast(DynamicVBAccess->VertexBuffer)->Get_DX8_Vertex_Buffer()->Unlock()); + if (LegacyVertexBuffer *legacy = Legacy_Vertex_Buffer(static_cast(DynamicVBAccess->VertexBuffer))) { + DX8_ErrorCode(legacy->Unlock()); + } +#endif break; case BUFFER_TYPE_DYNAMIC_SORTING: break; @@ -894,11 +1154,10 @@ DynamicVBAccessClass::WriteLockClass::~WriteLockClass() void DynamicVBAccessClass::_Reset(bool frame_changed) { _DynamicSortingVertexArrayOffset=0; - if (frame_changed) _DynamicDX8VertexBufferOffset=0; + if (frame_changed) _DynamicBackendVertexBufferOffset=0; } unsigned short DynamicVBAccessClass::Get_Default_Vertex_Count() { - return _DynamicDX8VertexBufferSize; + return _DynamicBackendVertexBufferSize; } - diff --git a/Core/Libraries/Source/WWVegas/WW3D2/vertexbuffer.h b/Core/Libraries/Source/WWVegas/WW3D2/vertexbuffer.h new file mode 100644 index 00000000000..ab310d27a0e --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/vertexbuffer.h @@ -0,0 +1,120 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** 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. +*/ + +#pragma once + +#include "dx8fvf.h" +#include "vertexbufferbase.h" + +const unsigned dynamic_fvf_type=DX8_FVF_FLAG_XYZ|DX8_FVF_FLAG_NORMAL|DX8_FVF_TEX2|DX8_FVF_FLAG_DIFFUSE; + +class DX8Wrapper; +class SortingRendererClass; + +/** +** Dynamic vertex buffer access is a wrapper to a single cycled dynamic vertex +** buffer. +** DynamicVBAccess gains an access to the dynamic vertex buffer and only +** only of these are allowed at any one time. +** +** The dynamic fvf buffers are always of the same type. +** +** NOTE: Dynamic vertex buffers accessors should only be used locally! +** +*/ + +class DynamicVBAccessClass +{ + friend DX8Wrapper; + friend SortingRendererClass; + + const FVFInfoClass& FVFInfo; + unsigned Type; + unsigned short VertexCount; + unsigned short VertexBufferOffset; + VertexBufferClass* VertexBuffer; +// static VertexFormatXYZNDUV2* _Get_Sorting_Vertex_Array(); + + void Allocate_Sorting_Dynamic_Buffer(); + void Allocate_Backend_Dynamic_Buffer(); +public: + // Type parameter can be either BUFFER_TYPE_DYNAMIC or BUFFER_TYPE_DYNAMIC_SORTING. + + // Note: Even though the constructor takes fvf as a parameter, currently the + // only acceptable parameter is "dynamic_fvf_type". Any other type will + // result to an assert. + DynamicVBAccessClass(unsigned type,unsigned fvf,unsigned short vertex_count); + ~DynamicVBAccessClass(); + + // Access fvf + const FVFInfoClass& FVF_Info() const { return FVFInfo; } + unsigned Get_Type() const { return Type; } + unsigned short Get_Vertex_Count() const { return VertexCount; } + unsigned short Get_Vertex_Buffer_Offset() const { return VertexBufferOffset; } + VertexBufferClass * Get_Vertex_Buffer() const { return VertexBuffer; } + + // Call at the end of the execution, or at whatever time you wish to release + // the recycled dynamic vertex buffer. + static void _Deinit(); + static void _Reset(bool frame_changed); + static unsigned short Get_Default_Vertex_Count(); ///VertexBuffer->FVF_Info().Get_FVF() == dynamic_fvf_type); + return Vertices; +} + +// ---------------------------------------------------------------------------- + +/** +** SortingVertexBufferClass +** This class acts as a vertex buffer for the vertices that need to be passed to alpha renderer. +*/ +class SortingVertexBufferClass : public VertexBufferClass +{ + W3DMPO_CODE(SortingVertexBufferClass) + + friend DX8Wrapper; + friend SortingRendererClass; + friend VertexBufferClass::WriteLockClass; + friend VertexBufferClass::AppendLockClass; + friend DynamicVBAccessClass::WriteLockClass; + + VertexFormatXYZNDUV2* VertexBuffer; + +protected: + virtual ~SortingVertexBufferClass() override; +public: + SortingVertexBufferClass(unsigned short VertexCount); +}; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/vertexbufferbase.h b/Core/Libraries/Source/WWVegas/WW3D2/vertexbufferbase.h new file mode 100644 index 00000000000..ef09be269a9 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/vertexbufferbase.h @@ -0,0 +1,96 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** 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. +*/ + +#pragma once + +#include "always.h" +#include "WWDebug/wwdebug.h" +#include "IRenderBackend.h" +#include "RenderBufferTypes.h" + +class FVFInfoClass; +class VertexBufferClass; + +class VertexBufferLockClass +{ +protected: + VertexBufferClass* VertexBuffer; + void* Vertices; + + // This class can't be used directly, so constructor as to be protected + VertexBufferLockClass(VertexBufferClass* vertex_buffer_) : VertexBuffer(vertex_buffer_) {} +public: + void* Get_Vertex_Array() { return Vertices; } +}; + +class VertexBufferClass : public RefCountClass +{ +protected: + VertexBufferClass(unsigned type, unsigned FVF, unsigned short VertexCount); + virtual ~VertexBufferClass() override; +public: + + const FVFInfoClass& FVF_Info() const { return *fvf_info; } + unsigned short Get_Vertex_Count() const { return VertexCount; } + unsigned Type() const { return type; } + const unsigned char * Peek_CPU_Buffer_Data() const { return CPUBufferData; } + unsigned Get_CPU_Buffer_Size() const { return CPUBufferSize; } + bool Has_CPU_Buffer_Data() const { return CPUBufferValid; } + RenderResource Get_Backend_Resource() const { return m_backendHandle; } + bool Has_Backend_Resource() const { return m_backendHandle != kInvalidRenderResource; } + bool Is_Backend_Static_Eligible() const { return m_backendStaticEligible; } + void *Lock_CPU_Buffer_Data(unsigned byte_offset, unsigned size); + + void Add_Engine_Ref() const; + void Release_Engine_Ref() const; + unsigned Engine_Refs() const { return engine_refs; } + + class WriteLockClass : public VertexBufferLockClass + { + public: + WriteLockClass(VertexBufferClass* vertex_buffer, int flags=0); + ~WriteLockClass(); + }; + + class AppendLockClass : public VertexBufferLockClass + { + public: + // TheSuperHackers @refactor bobtista 15/04/2026 added + // optional `flags` (e.g. RB_LOCK_DISCARD / RB_LOCK_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 + // 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(); + static unsigned Get_Total_Allocated_Vertices(); + static unsigned Get_Total_Allocated_Memory(); + +protected: + unsigned type; + unsigned short VertexCount; + mutable int engine_refs; + FVFInfoClass* fvf_info; + unsigned char* CPUBufferData; + unsigned CPUBufferSize; + bool CPUBufferValid; + bool m_backendStaticEligible; + RenderResource m_backendHandle; + void Set_Backend_Static_Eligible(bool eligible) { m_backendStaticEligible = eligible; } + void Update_CPU_Buffer_Data(unsigned byte_offset, const void * data, unsigned size); +}; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/visrasterizer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/visrasterizer.cpp index 6f2f39a666a..2e903d60ca5 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/visrasterizer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/visrasterizer.cpp @@ -476,8 +476,8 @@ struct EdgeStruct { EdgeStruct(const GradientsStruct & grad,const Vector3 * verts,int top,int bottom) { - Y = WWMath::Ceil(verts[top].Y); - Height = WWMath::Ceil(verts[bottom].Y) - Y; + Y = WWMath::Ceilf(verts[top].Y); + Height = WWMath::Ceilf(verts[bottom].Y) - Y; float y_prestep = Y - verts[top].Y; float real_height = verts[bottom].Y - verts[top].Y; @@ -654,8 +654,8 @@ int IDBufferClass::Render_Occluder_Scanline(GradientsStruct & grads,EdgeStruct * return 0; } - int xstart = WWMath::Float_To_Long(WWMath::Max(WWMath::Ceil(left->X),1.0f)); - int width = WWMath::Float_To_Long(WWMath::Ceil(right->X)) - xstart; + int xstart = WWMath::Float_To_Long(WWMath::Max(WWMath::Ceilf(left->X),1.0f)); + int width = WWMath::Float_To_Long(WWMath::Ceilf(right->X)) - xstart; if (xstart + width > ResWidth) { width = ResWidth - xstart; } @@ -704,8 +704,8 @@ int IDBufferClass::Render_Non_Occluder_Scanline(GradientsStruct & grads,EdgeStru return 0; } - int xstart = WWMath::Float_To_Long(WWMath::Max(WWMath::Ceil(left->X),1)); - int width = WWMath::Float_To_Long(WWMath::Ceil(right->X)) - xstart; + int xstart = WWMath::Float_To_Long(WWMath::Max(WWMath::Ceilf(left->X),1)); + int width = WWMath::Float_To_Long(WWMath::Ceilf(right->X)) - xstart; if (xstart + width > ResWidth) { width = ResWidth - xstart; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/ww3dcolor.h b/Core/Libraries/Source/WWVegas/WW3D2/ww3dcolor.h new file mode 100644 index 00000000000..030c93ca59e --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/ww3dcolor.h @@ -0,0 +1,67 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** 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 "WWMath/vector3.h" +#include "WWMath/vector4.h" + +namespace WW3DColor +{ +WWINLINE Vector4 From_ARGB(unsigned int color) +{ + Vector4 converted; + converted[3] = ((color & 0xff000000) >> 24) / 255.0f; + converted[0] = ((color & 0x00ff0000) >> 16) / 255.0f; + converted[1] = ((color & 0x0000ff00) >> 8) / 255.0f; + converted[2] = ((color & 0x000000ff) >> 0) / 255.0f; + return converted; +} + +WWINLINE unsigned int To_ARGB(const Vector3 & color, float alpha) +{ + return color.Convert_To_ARGB(alpha); +} + +WWINLINE unsigned int To_ARGB(const Vector4 & color) +{ + return To_ARGB(reinterpret_cast(color), color[3]); +} + +WWINLINE void Clamp(Vector4 & color) +{ + for (int i = 0; i < 4; ++i) + { + const float nonnegative = (color[i] < 0.0f) ? 0.0f : color[i]; + color[i] = (nonnegative > 1.0f) ? 1.0f : nonnegative; + } +} + +WWINLINE unsigned int To_ARGB_Clamp(const Vector4 & color) +{ + Vector4 clamped = color; + Clamp(clamped); + return To_ARGB(clamped); +} + +WWINLINE void Set_Alpha(float alpha, unsigned int & color) +{ + color &= 0x00FFFFFF; + color |= (static_cast(alpha * 255.0f) << 24); +} +} diff --git a/Core/Libraries/Source/WWVegas/WW3D2/ww3dformat.cpp b/Core/Libraries/Source/WWVegas/WW3D2/ww3dformat.cpp index 9fd35d25651..52aa78b05b9 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/ww3dformat.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/ww3dformat.cpp @@ -41,9 +41,9 @@ #include "WWMath/vector4.h" #include "WWDebug/wwdebug.h" #include "WWLib/TARGA.h" -#include "dx8wrapper.h" -#include "dx8caps.h" -#include +#include "WW3D2/ww3dcolor.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" /* WW3D_FORMAT_UNKNOWN=0, @@ -128,7 +128,7 @@ unsigned char RGB_to_CIEY(Vector4 color) void Vector4_to_Color(unsigned int *outc,const Vector4 &inc,const WW3DFormat format) { // convert to ARGB 32-bit - unsigned int color=DX8Wrapper::Convert_Color(inc); + unsigned int color=WW3DColor::To_ARGB(inc); unsigned char *argb=(unsigned char*) &color; unsigned char r,g,b,a,lum; @@ -296,6 +296,20 @@ void Get_WW3D_Format(WW3DFormat& src_format,unsigned& src_bpp,const Targa& targa } } +static bool Backend_Supports_Texture_Format(WW3DFormat format) +{ + return g_renderBackend && g_renderBackend->Supports_Texture_Format(format); +} + +static bool Backend_Supports_DXTC() +{ + return Backend_Supports_Texture_Format(WW3D_FORMAT_DXT1) + || Backend_Supports_Texture_Format(WW3D_FORMAT_DXT2) + || Backend_Supports_Texture_Format(WW3D_FORMAT_DXT3) + || Backend_Supports_Texture_Format(WW3D_FORMAT_DXT4) + || Backend_Supports_Texture_Format(WW3D_FORMAT_DXT5); +} + // ---------------------------------------------------------------------------- // // Utility function for determining valid WW3D format @@ -307,7 +321,7 @@ WW3DFormat Get_Valid_Texture_Format(WW3DFormat format, bool is_compression_allow int w,h,bits; bool windowed; - if (!DX8Wrapper::Get_Current_Caps()->Support_DXTC() || + if (!Backend_Supports_DXTC() || !is_compression_allowed) { switch (format) { case WW3D_FORMAT_DXT1: format=WW3D_FORMAT_R8G8B8; break; @@ -322,8 +336,8 @@ WW3DFormat Get_Valid_Texture_Format(WW3DFormat format, bool is_compression_allow switch (format) { case WW3D_FORMAT_DXT1: // NVidia hack - switch to DXT2 is there is no DXT1 support (which is disabled on NVidia cards) - if (!DX8Wrapper::Get_Current_Caps()->Support_Texture_Format(WW3D_FORMAT_DXT1) && - DX8Wrapper::Get_Current_Caps()->Support_Texture_Format(WW3D_FORMAT_DXT2)) { + if (!Backend_Supports_Texture_Format(WW3D_FORMAT_DXT1) && + Backend_Supports_Texture_Format(WW3D_FORMAT_DXT2)) { format=WW3D_FORMAT_DXT2; } break; @@ -331,7 +345,9 @@ WW3DFormat Get_Valid_Texture_Format(WW3DFormat format, bool is_compression_allow case WW3D_FORMAT_DXT3: case WW3D_FORMAT_DXT4: case WW3D_FORMAT_DXT5: - if (!DX8Wrapper::Get_Current_Caps()->Support_Texture_Format(format)) format=WW3D_FORMAT_A8R8G8B8; + if (!Backend_Supports_Texture_Format(format)) format=WW3D_FORMAT_A8R8G8B8; + break; + default: break; } } @@ -340,6 +356,7 @@ WW3DFormat Get_Valid_Texture_Format(WW3DFormat format, bool is_compression_allow format=WW3D_FORMAT_X8R8G8B8; } +#if !defined(GGC_RENDER_BACKEND_BGFX) WW3D::Get_Device_Resolution(w,h,bits,windowed); if (WW3D::Get_Texture_Bitdepth()==16) bits=16; @@ -361,19 +378,24 @@ WW3DFormat Get_Valid_Texture_Format(WW3DFormat format, bool is_compression_allow } } +#else + // The bgfx standalone renderer is not constrained by DX8-era 16-bit + // texture-depth settings. Preserve native 24/32-bit texture formats so + // UI and effect alpha channels do not get quantized through A4R4G4B4. +#endif // Fallback if the hardware doesn't support the texture format - if (!DX8Wrapper::Get_Current_Caps()->Support_Texture_Format(format)) { + if (!Backend_Supports_Texture_Format(format)) { format=WW3D_FORMAT_A8R8G8B8; - if (!DX8Wrapper::Get_Current_Caps()->Support_Texture_Format(format)) { + if (!Backend_Supports_Texture_Format(format)) { format=WW3D_FORMAT_A4R4G4B4; - if (!DX8Wrapper::Get_Current_Caps()->Support_Texture_Format(format)) { + if (!Backend_Supports_Texture_Format(format)) { // If still no luck, try non-alpha formats format=WW3D_FORMAT_X8R8G8B8; - if (!DX8Wrapper::Get_Current_Caps()->Support_Texture_Format(format)) { + if (!Backend_Supports_Texture_Format(format)) { format=WW3D_FORMAT_R5G6B5; - if (!DX8Wrapper::Get_Current_Caps()->Support_Texture_Format(format)) { + if (!Backend_Supports_Texture_Format(format)) { WWASSERT_PRINT(0,("No valid texture format found")); } } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/ww3dformat.h b/Core/Libraries/Source/WWVegas/WW3D2/ww3dformat.h index 28b7eb8cd84..99d2b59a071 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/ww3dformat.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/ww3dformat.h @@ -48,13 +48,13 @@ class Vector4; class Targa; /* -** Enum for possible surface formats. This is a small subset of the D3DFORMAT -** enum which lists the formats supported by DX8; we will add new members to -** this list as needed (keeping it in the same order as D3DFORMAT). +** Enum for possible surface formats. This is a small subset of the legacy +** renderer format enum; we will add new members to this list as needed while +** preserving the compatibility ordering. ** NOTE: Whenever this is changed, formconv.h/.cpp must be modified as well -** (that contains the code for converting between this and D3DFORMAT).. +** (that contains the code for converting between this and legacy formats). ** -** The format names use the D3DFORMAT conventions: +** The format names use the legacy renderer format conventions: ** A = Alpha ** R = Red ** G = Green @@ -172,7 +172,7 @@ void Color_to_Vector4(Vector4* outc,const unsigned int inc,const WW3DFormat form // Define matching WW3D format based from Targa header. // -// dest_format - WW3DFormat that can be used as a destination (D3D surface) on current hardware +// dest_format - WW3DFormat that can be used as a destination surface on current hardware // src_format - WW3DFormat that represents the format the bitmap is stored in the targa file. // src_bpp - bytes per pixel in the source surface // targa - reference to the targa object... diff --git a/Core/Libraries/Source/WWVegas/WWAudio/SoundPseudo3D.cpp b/Core/Libraries/Source/WWVegas/WWAudio/SoundPseudo3D.cpp index 3805659b907..17aefcc701c 100644 --- a/Core/Libraries/Source/WWVegas/WWAudio/SoundPseudo3D.cpp +++ b/Core/Libraries/Source/WWVegas/WWAudio/SoundPseudo3D.cpp @@ -213,7 +213,7 @@ SoundPseudo3DClass::Update_Pseudo_Pan () // // Calculate a normalized pan from 0 (hard left) to 1.0F (hard right) // - float angle = WWMath::Atan2 (rel_sound_pos.Y, rel_sound_pos.X); + float angle = WWMath::Atan2_Legacy (rel_sound_pos.Y, rel_sound_pos.X); float pan = -WWMath::Fast_Sin (angle); pan = (pan / 2.0F) + 0.5F; diff --git a/Core/Libraries/Source/WWVegas/WWDebug/wwdebug.cpp b/Core/Libraries/Source/WWVegas/WWDebug/wwdebug.cpp index a2d59f6e482..e8788dc2def 100644 --- a/Core/Libraries/Source/WWVegas/WWDebug/wwdebug.cpp +++ b/Core/Libraries/Source/WWVegas/WWDebug/wwdebug.cpp @@ -46,6 +46,7 @@ //#include "win.h" can use this if allowed to see wwlib #include #include +#include #include #include #include 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..055e265ae9b 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 @@ -45,6 +44,8 @@ set(WWLIB_SRC ffactory.h gcd_lcm.cpp gcd_lcm.h + GgcRuntimeFlags.cpp + GgcRuntimeFlags.h #global.h hash.cpp hash.h @@ -156,6 +157,7 @@ set(WWLIB_SRC if(WIN32) list(APPEND WWLIB_SRC + DbgHelpLoader.cpp mpu.cpp MPU.h rcfile.cpp @@ -166,6 +168,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.cpp b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp index dab6686125c..01e16101f86 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp +++ b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.cpp @@ -33,6 +33,12 @@ DbgHelpLoader::DbgHelpLoader() , m_symSetOptions(nullptr) , m_symFunctionTableAccess(nullptr) , m_stackWalk(nullptr) + , m_symGetModuleBase64(nullptr) + , m_symLoadModule64(nullptr) + , m_symGetSymFromAddr64(nullptr) + , m_symGetLineFromAddr64(nullptr) + , m_symFunctionTableAccess64(nullptr) + , m_stackWalk64(nullptr) #ifdef RTS_ENABLE_CRASHDUMP , m_miniDumpWriteDump(nullptr) #endif @@ -121,6 +127,12 @@ bool DbgHelpLoader::load() Inst->m_symSetOptions = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymSetOptions")); Inst->m_symFunctionTableAccess = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymFunctionTableAccess")); Inst->m_stackWalk = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "StackWalk")); + Inst->m_symGetModuleBase64 = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymGetModuleBase64")); + Inst->m_symLoadModule64 = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymLoadModule64")); + Inst->m_symGetSymFromAddr64 = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymGetSymFromAddr64")); + Inst->m_symGetLineFromAddr64 = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymGetLineFromAddr64")); + Inst->m_symFunctionTableAccess64 = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "SymFunctionTableAccess64")); + Inst->m_stackWalk64 = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "StackWalk64")); #ifdef RTS_ENABLE_CRASHDUMP Inst->m_miniDumpWriteDump = reinterpret_cast(::GetProcAddress(Inst->m_dllModule, "MiniDumpWriteDump")); #endif @@ -177,6 +189,12 @@ void DbgHelpLoader::freeResources() Inst->m_symSetOptions = nullptr; Inst->m_symFunctionTableAccess = nullptr; Inst->m_stackWalk = nullptr; + Inst->m_symGetModuleBase64 = nullptr; + Inst->m_symLoadModule64 = nullptr; + Inst->m_symGetSymFromAddr64 = nullptr; + Inst->m_symGetLineFromAddr64 = nullptr; + Inst->m_symFunctionTableAccess64 = nullptr; + Inst->m_stackWalk64 = nullptr; #ifdef RTS_ENABLE_CRASHDUMP Inst->m_miniDumpWriteDump = nullptr; #endif @@ -342,6 +360,93 @@ BOOL DbgHelpLoader::stackWalk( return FALSE; } +DWORD64 DbgHelpLoader::symGetModuleBase64( + HANDLE hProcess, + DWORD64 dwAddr) +{ + CriticalSectionClass::LockClass lock(CriticalSection); + + if (Inst != nullptr && Inst->m_symGetModuleBase64) + return Inst->m_symGetModuleBase64(hProcess, dwAddr); + + return 0u; +} + +DWORD64 DbgHelpLoader::symLoadModule64( + HANDLE hProcess, + HANDLE hFile, + LPSTR ImageName, + LPSTR ModuleName, + DWORD64 BaseOfDll, + DWORD SizeOfDll) +{ + CriticalSectionClass::LockClass lock(CriticalSection); + + if (Inst != nullptr && Inst->m_symLoadModule64) + return Inst->m_symLoadModule64(hProcess, hFile, ImageName, ModuleName, BaseOfDll, SizeOfDll); + + return 0u; +} + +BOOL DbgHelpLoader::symGetSymFromAddr64( + HANDLE hProcess, + DWORD64 Address, + PDWORD64 Displacement, + PIMAGEHLP_SYMBOL64 Symbol) +{ + CriticalSectionClass::LockClass lock(CriticalSection); + + if (Inst != nullptr && Inst->m_symGetSymFromAddr64) + return Inst->m_symGetSymFromAddr64(hProcess, Address, Displacement, Symbol); + + return FALSE; +} + +BOOL DbgHelpLoader::symGetLineFromAddr64( + HANDLE hProcess, + DWORD64 dwAddr, + PDWORD pdwDisplacement, + PIMAGEHLP_LINE64 Line) +{ + CriticalSectionClass::LockClass lock(CriticalSection); + + if (Inst != nullptr && Inst->m_symGetLineFromAddr64) + return Inst->m_symGetLineFromAddr64(hProcess, dwAddr, pdwDisplacement, Line); + + return FALSE; +} + +PVOID DbgHelpLoader::symFunctionTableAccess64( + HANDLE hProcess, + DWORD64 AddrBase) +{ + CriticalSectionClass::LockClass lock(CriticalSection); + + if (Inst != nullptr && Inst->m_symFunctionTableAccess64) + return Inst->m_symFunctionTableAccess64(hProcess, AddrBase); + + return nullptr; +} + +BOOL DbgHelpLoader::stackWalk64( + DWORD MachineType, + HANDLE hProcess, + HANDLE hThread, + LPSTACKFRAME64 StackFrame, + PVOID ContextRecord, + PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, + PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress) +{ + CriticalSectionClass::LockClass lock(CriticalSection); + + if (Inst != nullptr && Inst->m_stackWalk64) + return Inst->m_stackWalk64(MachineType, hProcess, hThread, StackFrame, ContextRecord, ReadMemoryRoutine, FunctionTableAccessRoutine, GetModuleBaseRoutine, TranslateAddress); + + return FALSE; +} + #ifdef RTS_ENABLE_CRASHDUMP BOOL DbgHelpLoader::miniDumpWriteDump( HANDLE hProcess, diff --git a/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h b/Core/Libraries/Source/WWVegas/WWLib/DbgHelpLoader.h index fa40554ea67..8c4396880c1 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 @@ -112,6 +114,49 @@ class DbgHelpLoader PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE TranslateAddress); + // TheSuperHackers @feature bobtista 11/06/2026 64-bit DbgHelp entry points. The legacy + // 32-bit wrappers above truncate addresses to DWORD and cannot walk a 64-bit stack, so + // the x64 build (and the modern x86 build) route through these StackWalk64/Sym*64 calls. + // These work on both x86 and x64, which is why Microsoft recommends them universally. + static DWORD64 WINAPI symGetModuleBase64( + HANDLE hProcess, + DWORD64 dwAddr); + + static DWORD64 WINAPI symLoadModule64( + HANDLE hProcess, + HANDLE hFile, + LPSTR ImageName, + LPSTR ModuleName, + DWORD64 BaseOfDll, + DWORD SizeOfDll); + + static BOOL WINAPI symGetSymFromAddr64( + HANDLE hProcess, + DWORD64 Address, + PDWORD64 Displacement, + PIMAGEHLP_SYMBOL64 Symbol); + + static BOOL WINAPI symGetLineFromAddr64( + HANDLE hProcess, + DWORD64 dwAddr, + PDWORD pdwDisplacement, + PIMAGEHLP_LINE64 Line); + + static PVOID WINAPI symFunctionTableAccess64( + HANDLE hProcess, + DWORD64 AddrBase); + + static BOOL WINAPI stackWalk64( + DWORD MachineType, + HANDLE hProcess, + HANDLE hThread, + LPSTACKFRAME64 StackFrame, + PVOID ContextRecord, + PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, + PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress); + #ifdef RTS_ENABLE_CRASHDUMP static BOOL WINAPI miniDumpWriteDump( HANDLE hProcess, @@ -181,6 +226,45 @@ class DbgHelpLoader PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE TranslateAddress); + typedef DWORD64 (WINAPI *SymGetModuleBase64_t) ( + HANDLE hProcess, + DWORD64 dwAddr); + + typedef DWORD64 (WINAPI *SymLoadModule64_t) ( + HANDLE hProcess, + HANDLE hFile, + LPSTR ImageName, + LPSTR ModuleName, + DWORD64 BaseOfDll, + DWORD SizeOfDll); + + typedef BOOL (WINAPI *SymGetSymFromAddr64_t) ( + HANDLE hProcess, + DWORD64 Address, + PDWORD64 Displacement, + PIMAGEHLP_SYMBOL64 Symbol); + + typedef BOOL (WINAPI *SymGetLineFromAddr64_t) ( + HANDLE hProcess, + DWORD64 dwAddr, + PDWORD pdwDisplacement, + PIMAGEHLP_LINE64 Line); + + typedef PVOID (WINAPI *SymFunctionTableAccess64_t) ( + HANDLE hProcess, + DWORD64 AddrBase); + + typedef BOOL (WINAPI *StackWalk64_t) ( + DWORD MachineType, + HANDLE hProcess, + HANDLE hThread, + LPSTACKFRAME64 StackFrame, + PVOID ContextRecord, + PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, + PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress); + #ifdef RTS_ENABLE_CRASHDUMP typedef BOOL(WINAPI* MiniDumpWriteDump_t)( HANDLE hProcess, @@ -202,6 +286,12 @@ class DbgHelpLoader SymSetOptions_t m_symSetOptions; SymFunctionTableAccess_t m_symFunctionTableAccess; StackWalk_t m_stackWalk; + SymGetModuleBase64_t m_symGetModuleBase64; + SymLoadModule64_t m_symLoadModule64; + SymGetSymFromAddr64_t m_symGetSymFromAddr64; + SymGetLineFromAddr64_t m_symGetLineFromAddr64; + SymFunctionTableAccess64_t m_symFunctionTableAccess64; + StackWalk64_t m_stackWalk64; #ifdef RTS_ENABLE_CRASHDUMP MiniDumpWriteDump_t m_miniDumpWriteDump; #endif @@ -214,3 +304,117 @@ class DbgHelpLoader bool m_failed; bool m_loadedFromSystem; }; + +#else + +#ifndef WINAPI +#define WINAPI +#endif + +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*; + +using DWORD64 = unsigned long long; +using PDWORD64 = DWORD64*; +using PVOID = void*; +struct IMAGEHLP_SYMBOL64; +using PIMAGEHLP_SYMBOL64 = IMAGEHLP_SYMBOL64*; +struct IMAGEHLP_LINE64; +using PIMAGEHLP_LINE64 = IMAGEHLP_LINE64*; +struct STACKFRAME64; +using LPSTACKFRAME64 = STACKFRAME64*; +using PREAD_PROCESS_MEMORY_ROUTINE64 = void*; +using PFUNCTION_TABLE_ACCESS_ROUTINE64 = void*; +using PGET_MODULE_BASE_ROUTINE64 = void*; +using PTRANSLATE_ADDRESS_ROUTINE64 = 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; + } + + static DWORD64 WINAPI symGetModuleBase64(HANDLE, DWORD64) { return 0; } + static DWORD64 WINAPI symLoadModule64(HANDLE, HANDLE, LPSTR, LPSTR, DWORD64, DWORD) { return 0; } + static BOOL WINAPI symGetSymFromAddr64(HANDLE, DWORD64, PDWORD64, PIMAGEHLP_SYMBOL64) { return 0; } + static BOOL WINAPI symGetLineFromAddr64(HANDLE, DWORD64, LPDWORD, PIMAGEHLP_LINE64) { return 0; } + static PVOID WINAPI symFunctionTableAccess64(HANDLE, DWORD64) { return nullptr; } + static BOOL WINAPI stackWalk64( + DWORD, + HANDLE, + HANDLE, + LPSTACKFRAME64, + PVOID, + PREAD_PROCESS_MEMORY_ROUTINE64, + PFUNCTION_TABLE_ACCESS_ROUTINE64, + PGET_MODULE_BASE_ROUTINE64, + PTRANSLATE_ADDRESS_ROUTINE64) + { + 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/Except.cpp b/Core/Libraries/Source/WWVegas/WWLib/Except.cpp index be9c958cdf1..7a6542a849b 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/Except.cpp +++ b/Core/Libraries/Source/WWVegas/WWLib/Except.cpp @@ -112,6 +112,20 @@ int ExceptionRecursions = -1; */ DynamicVectorClass ThreadList; +// TheSuperHackers @bugfix bobtista 12/07/2026 Static-destruction sentinel for +// ThreadList. Constructed after the list, so it destructs first and flips the +// flag before the list's own destructor runs; late-exiting threads that +// unregister during atexit are then skipped instead of walking freed memory. +static bool ThreadListAlive = true; +namespace +{ +struct ThreadListLifetimeSentinel +{ + ~ThreadListLifetimeSentinel() { ThreadListAlive = false; } +}; +static ThreadListLifetimeSentinel TheThreadListLifetimeSentinel; +} + /* ** Definitions to allow run-time linking to the Imagehlp.dll functions. ** @@ -355,13 +369,13 @@ void Dump_Exception_Info(EXCEPTION_POINTERS *e_info) if (imagehelp != nullptr) { DebugString ("Exception Handler: Found IMAGEHLP.DLL - linking to required functions\n"); char const *function_name = nullptr; - unsigned long *fptr = (unsigned long*) &_SymCleanup; + ULONG_PTR *fptr = (ULONG_PTR*) &_SymCleanup; int count = 0; do { function_name = ImagehelpFunctionNames[count]; if (function_name) { - *fptr = (unsigned long) GetProcAddress(imagehelp, function_name); + *fptr = (ULONG_PTR) GetProcAddress(imagehelp, function_name); fptr++; count++; } @@ -465,8 +479,9 @@ void Dump_Exception_Info(EXCEPTION_POINTERS *e_info) symptr->SizeOfStruct = sizeof (IMAGEHLP_SYMBOL); symptr->MaxNameLength = 256-sizeof (IMAGEHLP_SYMBOL); symptr->Size = 0; - symptr->Address = context->Eip; +#if defined(_M_IX86) + symptr->Address = context->Eip; if (!IsBadCodePtr((FARPROC)context->Eip)) { if (_SymGetSymFromAddr != nullptr && _SymGetSymFromAddr (GetCurrentProcess(), context->Eip, &displacement, symptr)) { snprintf(scrap, ARRAY_SIZE(scrap), "Exception occurred at %08X - %s + %08X\r\n", @@ -481,6 +496,12 @@ void Dump_Exception_Info(EXCEPTION_POINTERS *e_info) } else { DebugString ("Exception Handler: context->Eip is bad code pointer\n"); } +#else + // TheSuperHackers @todo bobtista 11/06/2026 Symbolize the faulting address on x64 by wiring + // SymGetSymFromAddr64 into Load_Image_Helper. Build+verify on a Windows x64 machine. The raw + // faulting address is still recorded here. + sprintf (scrap, "Exception occurred at %p\r\n", (void*)context->Rip); +#endif Add_Txt (scrap); @@ -580,6 +601,7 @@ void Dump_Exception_Info(EXCEPTION_POINTERS *e_info) Add_Txt("\r\nDetails:\r\n"); +#if defined(_M_IX86) DebugString("Register dump...\n"); /* @@ -706,6 +728,15 @@ void Dump_Exception_Info(EXCEPTION_POINTERS *e_info) Add_Txt(scrap); stackptr++; } +#else + // TheSuperHackers @todo bobtista 11/06/2026 Implement the x64 register/FP context dump: print + // Rax..R15/Rip/RFlags + segment registers, the XMM register state (the x64 CONTEXT has no x87 + // FloatSave / 80387 ST registers), the bytes at RIP, and an Rsp-based stack dump symbolized + // via SymGetSymFromAddr64. Build+verify on a Windows x64 machine. The faulting address above + // and the call stack from StackDump.cpp's StackWalk64 path still land in the crash log. + Add_Txt("\r\nRegister/FP context dump is not yet implemented on x64.\r\n"); + Add_Txt("See the StackDump.cpp call stack above for the crash location.\r\n"); +#endif /* ** Unload the symbols. @@ -999,6 +1030,13 @@ HANDLE Get_Thread_Handle(int thread_index) *=============================================================================================*/ void Unregister_Thread_ID(unsigned long thread_id, char *thread_name) { + // TheSuperHackers @bugfix bobtista 12/07/2026 A thread that outlives engine + // shutdown (the texture loader when process exit bypasses it) unregisters + // here during atexit, after the static ThreadList was destructed — walking + // it then reads freed memory (0xC0000005 on win64 harness exits). + if (!ThreadListAlive) { + return; + } for (int i=0 ; iThreadName) == 0) { assert(ThreadList[i]->ThreadID == thread_id); @@ -1065,13 +1103,13 @@ void Load_Image_Helper() if (ImageHelp != nullptr) { char const *function_name = nullptr; - unsigned long *fptr = (unsigned long *) &_SymCleanup; + ULONG_PTR *fptr = (ULONG_PTR *) &_SymCleanup; int count = 0; do { function_name = ImagehelpFunctionNames[count]; if (function_name) { - *fptr = (unsigned long) GetProcAddress(ImageHelp, function_name); + *fptr = (ULONG_PTR) GetProcAddress(ImageHelp, function_name); fptr++; count++; } @@ -1226,6 +1264,7 @@ int Stack_Walk(unsigned long *return_addresses, int num_addresses, CONTEXT *cont /* ** Set up the stack frame structure for the start point of the stack walk (i.e. here). */ +#if defined(_M_IX86) STACKFRAME stack_frame; memset(&stack_frame, 0, sizeof(stack_frame)); @@ -1289,6 +1328,18 @@ int Stack_Walk(unsigned long *return_addresses, int num_addresses, CONTEXT *cont } return(pointer_index); +#else + // TheSuperHackers @todo bobtista 11/06/2026 Port this legacy stack walk to the 64-bit DbgHelp + // API: capture an x64 CONTEXT (Rip/Rsp/Rbp), build a STACKFRAME64, call StackWalk64 with + // IMAGE_FILE_MACHINE_AMD64, and load SymFunctionTableAccess64/SymGetModuleBase64 in + // Load_Image_Helper. Must be built and verified on a Windows x64 machine. The primary crash + // call stack is already produced by StackDump.cpp's StackWalk64 path, so this legacy + // duplicate reports no frames for now rather than walking with the wrong (32-bit) unwinder. + (void)return_addresses; + (void)num_addresses; + (void)context; + return(0); +#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/GgcRuntimeFlags.cpp b/Core/Libraries/Source/WWVegas/WWLib/GgcRuntimeFlags.cpp new file mode 100644 index 00000000000..a3af88cb6cb --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WWLib/GgcRuntimeFlags.cpp @@ -0,0 +1,238 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2026 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 . +*/ + +#include "GgcRuntimeFlags.h" + +#include +#include +#include +#include + +namespace +{ + +struct GgcFlagInfo +{ + const char *name; + const char *alias; + unsigned char tier; + unsigned char type; + int defaultInt; + float defaultFloat; + const char *help; +}; + +const GgcFlagInfo s_table[GgcFlagCount] = +{ +#define GGC_RUNTIME_FLAG_TABLE_ENTRY(id, name, alias, tier, type, defaultInt, defaultFloat, help) \ + { name, alias, (unsigned char)(tier), (unsigned char)(type), defaultInt, defaultFloat, help }, + GGC_RUNTIME_FLAG_LIST(GGC_RUNTIME_FLAG_TABLE_ENTRY) +#undef GGC_RUNTIME_FLAG_TABLE_ENTRY +}; + +// Storage is deliberately destructor-free: GgcFlag_StrictPoolShutdown is read +// from ObjectPoolClass destructors during static destruction at process exit. +struct GgcFlagState +{ + int enabled; + int intValue; + float floatValue; + const char *strValue; +}; + +GgcFlagState s_state[GgcFlagCount]; +std::atomic s_resolved[GgcFlagCount]; + +// Diagnostic- and probe-tier flags resolve as unset when the build does not +// compile the diagnostic tools in, so they cannot be enabled in such builds. +bool TierResolvable(unsigned char tier) +{ +#if defined(GGC_DIAGNOSTIC_TOOLS) + (void)tier; + return true; +#else + return tier != (unsigned char)GgcTier_Diagnostic && tier != (unsigned char)GgcTier_Probe; +#endif +} + +bool EqualsIgnoreCase(const char *a, const char *b) +{ + while (*a != '\0' && *b != '\0') + { + char ca = *a; + char cb = *b; + if (ca >= 'A' && ca <= 'Z') + { + ca = (char)(ca - 'A' + 'a'); + } + if (cb >= 'A' && cb <= 'Z') + { + cb = (char)(cb - 'A' + 'a'); + } + if (ca != cb) + { + return false; + } + ++a; + ++b; + } + return *a == *b; +} + +void StoreResolvedValue(GgcFlagId id, const char *value) +{ + GgcFlagState &state = s_state[id]; + const GgcFlagInfo &info = s_table[id]; + + state.strValue = value; + state.intValue = (value != NULL) ? std::atoi(value) : info.defaultInt; + state.floatValue = (value != NULL) ? (float)std::atof(value) : info.defaultFloat; + + switch (info.type) + { + case GgcFlagType_Truthy: + state.enabled = (value != NULL && (std::strcmp(value, "1") == 0 || EqualsIgnoreCase(value, "true"))) ? 1 : 0; + break; + case GgcFlagType_ZeroOff: + // On unless explicitly set to a non-empty value that parses to zero. + state.enabled = (value != NULL && *value != '\0' && std::atoi(value) == 0) ? 0 : 1; + break; + default: + state.enabled = (value != NULL) ? 1 : 0; + break; + } + + // Concurrent first reads compute identical values from the same + // environment, so racing writers are benign - same guarantee the + // per-site function-local statics gave before the registry. + s_resolved[id].store(1, std::memory_order_release); +} + +const GgcFlagState &Resolve(GgcFlagId id) +{ + GgcFlagState &state = s_state[id]; + if (s_resolved[id].load(std::memory_order_acquire) != 0) + { + return state; + } + + const GgcFlagInfo &info = s_table[id]; + const char *value = NULL; + if (TierResolvable(info.tier)) + { + value = std::getenv(info.name); + if (value == NULL && info.alias != NULL) + { + value = std::getenv(info.alias); + } + } + + StoreResolvedValue(id, value); + return state; +} + +} // namespace + +namespace GgcFlags +{ + +bool Enabled(GgcFlagId id) +{ + return Resolve(id).enabled != 0; +} + +int IntValue(GgcFlagId id) +{ + return Resolve(id).intValue; +} + +float FloatValue(GgcFlagId id) +{ + return Resolve(id).floatValue; +} + +const char *StringValue(GgcFlagId id) +{ + return Resolve(id).strValue; +} + +void SetOverride(GgcFlagId id, const char *value) +{ + if (!TierResolvable(s_table[id].tier)) + { + // Diagnostic/probe flags resolve as unset in builds without + // GGC_DIAGNOSTIC_TOOLS; dropping an explicit override silently would + // turn a bisection run into a false A/B, so say what happened. + std::fprintf(stderr, "[ggc] ignoring override for %s: diagnostic tools not compiled into this build\n", + s_table[id].name); + return; + } + const char *copy = NULL; + if (value != NULL) + { + // Deliberately never freed: flag values live for the whole process, + // matching the lifetime getenv pointers had before the registry. + const size_t size = std::strlen(value) + 1; + char *owned = (char *)std::malloc(size); + std::memcpy(owned, value, size); + copy = owned; + } + StoreResolvedValue(id, copy); +} + +void DumpTableIfRequested() +{ + if (std::getenv("GGC_LIST_FLAGS") == NULL) + { + return; + } + + static const char *const tierNames[] = { "setting", "kill-switch", "diagnostic", "probe", "harness", "workaround" }; + static const char *const typeNames[] = { "presence", "truthy", "zero-off", "int", "float", "string" }; + + std::fprintf(stderr, "[ggc] %d runtime flags (GGC_LIST_FLAGS):\n", (int)GgcFlagCount); + for (int i = 0; i < (int)GgcFlagCount; ++i) + { + const GgcFlagInfo &info = s_table[i]; + const GgcFlagState &state = Resolve((GgcFlagId)i); + const char *value = state.strValue; + char valueText[64]; + if (!TierResolvable(info.tier)) + { + std::snprintf(valueText, sizeof(valueText), "(compiled out)"); + } + else if (value == NULL) + { + std::snprintf(valueText, sizeof(valueText), "(unset)"); + } + else + { + std::snprintf(valueText, sizeof(valueText), "\"%s\"", value); + } + std::fprintf(stderr, "[ggc] %-11s %-8s %-48s %-16s %s\n", + tierNames[info.tier], typeNames[info.type], info.name, valueText, info.help); + if (info.alias != NULL) + { + std::fprintf(stderr, "[ggc] %-11s %-8s %-48s (alias of %s)\n", + tierNames[info.tier], typeNames[info.type], info.alias, info.name); + } + } + std::fflush(stderr); +} + +} diff --git a/Core/Libraries/Source/WWVegas/WWLib/GgcRuntimeFlags.h b/Core/Libraries/Source/WWVegas/WWLib/GgcRuntimeFlags.h new file mode 100644 index 00000000000..652d9edd173 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WWLib/GgcRuntimeFlags.h @@ -0,0 +1,252 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2026 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 08/07/2026 Central registry for GGC_* runtime +// environment flags. Every flag is declared once in GGC_RUNTIME_FLAG_LIST below; +// the enum and the info table in GgcRuntimeFlags.cpp both expand from that list, +// so they cannot drift apart. Values are resolved from the environment once and +// cached. Diagnostic- and probe-tier flags resolve to "unset" unless the build +// defines GGC_DIAGNOSTIC_TOOLS (CMake option, default ON). +#pragma once + +#include + +enum GgcFlagTier +{ + GgcTier_Setting, // player-meaningful configuration + GgcTier_KillSwitch, // disables an optimization/feature for bisection and support + GgcTier_Diagnostic, // logging/visualization; compiled out of resolution without GGC_DIAGNOSTIC_TOOLS + GgcTier_Probe, // pipeline-stage isolation probes; gated like Diagnostic + GgcTier_Harness, // automation: screenshots, auto-exit, triggers, frame timing + GgcTier_Workaround // platform escape hatches +}; + +enum GgcFlagType +{ + GgcFlagType_Presence, // Enabled() = variable set to any value, including "0" + GgcFlagType_Truthy, // Enabled() = set to "1" or "true" + GgcFlagType_ZeroOff, // Enabled() = unset, or set to a nonzero value ("0" disables) + GgcFlagType_Int, // IntValue() = atoi(value) when set, else the declared default + GgcFlagType_Float, // FloatValue() = atof(value) when set, else the declared default + GgcFlagType_String // StringValue() = raw value, NULL when unset +}; + +// X(id, name, alias, tier, type, defaultInt, defaultFloat, help) +// Call sites with nontrivial parsing (tri-state, clamping, dual semantics) keep +// their logic and read the raw value via StringValue(); the declared type/default +// then documents the common reading. +#define GGC_RUNTIME_FLAG_LIST(X) \ + /* --- Settings --- */ \ + X(GgcFlag_BgfxRenderer, "GGC_BGFX_RENDERER", NULL, GgcTier_Setting, GgcFlagType_String, 0, 0.0f, "bgfx renderer override: dx11, dx12, vulkan, metal, gl (INI: BgfxRenderer)") \ + X(GgcFlag_BgfxMsaa, "GGC_BGFX_MSAA", NULL, GgcTier_Setting, GgcFlagType_Int, 0, 0.0f, "MSAA sample count for the scene framebuffer and backbuffer (0 = off)") \ + X(GgcFlag_BgfxRenderScale, "GGC_BGFX_RENDER_SCALE", NULL, GgcTier_Setting, GgcFlagType_Float, 0, 1.0f, "internal supersampling scale for the 3D scene, clamped to [1.0, 2.0]") \ + X(GgcFlag_BgfxHdr, "GGC_BGFX_HDR", NULL, GgcTier_Setting, GgcFlagType_Presence, 0, 0.0f, "RGBA16F scene color target with ACES tonemap") \ + X(GgcFlag_BgfxSsao, "GGC_BGFX_SSAO", NULL, GgcTier_Setting, GgcFlagType_Presence, 0, 0.0f, "screen-space ambient occlusion") \ + X(GgcFlag_BgfxSrgb, "GGC_BGFX_SRGB", NULL, GgcTier_Setting, GgcFlagType_Presence, 0, 0.0f, "sRGB backbuffer (INI: BgfxSrgb)") \ + X(GgcFlag_BgfxShadowMap, "GGC_BGFX_SHADOWMAP", NULL, GgcTier_Setting, GgcFlagType_String, 0, 0.0f, "sun shadow map: unset = INI, 0 = force off, anything else = force on") \ + X(GgcFlag_BgfxShadowMode, "GGC_BGFX_SHADOW_MODE", NULL, GgcTier_Setting, GgcFlagType_String, 0, 0.0f, "shadow mode: stencil (default), or none/off to disable stencil volumes (INI: BgfxStencilShadows)") \ + X(GgcFlag_BgfxShadowFullPcf, "GGC_BGFX_SHADOW_FULL_PCF", NULL, GgcTier_Setting, GgcFlagType_Presence, 0, 0.0f, "full 36-fetch PCF for sun shadows instead of the reduced 9-fetch kernel (INI: BgfxShadowFullPcf)") \ + X(GgcFlag_BgfxPointFilter, "GGC_BGFX_POINT_FILTER", NULL, GgcTier_Setting, GgcFlagType_Presence, 0, 0.0f, "force point (nearest) texture filtering on all samplers") \ + X(GgcFlag_BgfxColorGrade, "GGC_BGFX_COLORGRADE", NULL, GgcTier_Setting, GgcFlagType_Presence, 0, 0.0f, "force-enable the color-grade post pass") \ + X(GgcFlag_BgfxBloom, "GGC_BGFX_BLOOM", NULL, GgcTier_Setting, GgcFlagType_Presence, 0, 0.0f, "force-enable the bloom post pass") \ + X(GgcFlag_BgfxVignette, "GGC_BGFX_VIGNETTE", NULL, GgcTier_Setting, GgcFlagType_Presence, 0, 0.0f, "force-enable the vignette post effect") \ + X(GgcFlag_BgfxChroma, "GGC_BGFX_CHROMA", NULL, GgcTier_Setting, GgcFlagType_Presence, 0, 0.0f, "force-enable the chromatic-aberration post effect") \ + X(GgcFlag_BgfxGrain, "GGC_BGFX_GRAIN", NULL, GgcTier_Setting, GgcFlagType_Presence, 0, 0.0f, "force-enable the film-grain post effect") \ + X(GgcFlag_AudioCacheMb, "GGC_AUDIO_CACHE_MB", NULL, GgcTier_Setting, GgcFlagType_Int, 0, 0.0f, "decoded-PCM audio cache size in MB, overrides the INI-derived floor") \ + X(GgcFlag_PCannonEnhanced, "GGC_PCANNON_ENHANCED", NULL, GgcTier_Setting, GgcFlagType_Presence, 0, 0.0f, "experimental enhanced Particle Cannon effects: beam glow pool, electric arc lighting, lightning flashes, local scene dim (INI: PCannonEnhanced)") \ + X(GgcFlag_PCannonNoDim, "GGC_PCANNON_NO_DIM", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "PCannon enhanced bisection: disable the scene dim") \ + X(GgcFlag_PCannonNoFlicker, "GGC_PCANNON_NO_FLICKER", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "PCannon enhanced bisection: disable the beam light flicker") \ + X(GgcFlag_PCannonNoFlash, "GGC_PCANNON_NO_FLASH", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "PCannon enhanced bisection: disable the lightning flash pulses") \ + X(GgcFlag_PCannonNoShake, "GGC_PCANNON_NO_SHAKE", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "PCannon enhanced bisection: disable the beam camera shake") \ + /* --- Kill-switches --- */ \ + X(GgcFlag_BgfxNoInstancing, "GGC_BGFX_NO_INSTANCING", "GGC_BGFX_DISABLE_INSTANCING", GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "disable GPU-instanced batching of identical rigid meshes") \ + X(GgcFlag_BgfxInstancingNoReorder, "GGC_BGFX_INSTANCING_NO_REORDER", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "skip the render-task reorder that maximizes instanced run lengths") \ + X(GgcFlag_BgfxNoRenderThread, "GGC_BGFX_NO_RENDER_THREAD", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "disable the bgfx render-thread split (single-threaded submit)") \ + X(GgcFlag_BgfxCoalesceDynamicRangeUploads, "GGC_BGFX_COALESCE_DYNAMIC_RANGE_UPLOADS", NULL, GgcTier_KillSwitch, GgcFlagType_ZeroOff, 0, 0.0f, "coalesce dynamic VB/IB range uploads into one update (0 disables)") \ + X(GgcFlag_BgfxDisableSortedMaterialRecaptureSkip, "GGC_BGFX_DISABLE_SORTED_MATERIAL_RECAPTURE_SKIP", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "disable the sorted-material recapture-skip optimization") \ + X(GgcFlag_BgfxDisableSortedMaterialSnapshot, "GGC_BGFX_DISABLE_SORTED_MATERIAL_SNAPSHOT", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "disable the sorted-material snapshot fast path in sorted replay") \ + X(GgcFlag_BgfxDisableSortedTransformRestoreSkip, "GGC_BGFX_DISABLE_SORTED_TRANSFORM_RESTORE_SKIP", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "disable the same-transform skip in sorted batch replay") \ + X(GgcFlag_BgfxDisableSortedMeshRouting, "GGC_BGFX_DISABLE_SORTED_MESH_ROUTING", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "restore texture-name-only routing of model-space sorted draws") \ + X(GgcFlag_BgfxSortedTextureArray, "GGC_BGFX_SORTED_TEXTURE_ARRAY", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "no-op since the sorted texture-array merge became the default; kept so existing harnesses passing it stay valid") \ + X(GgcFlag_BgfxNoSortedTextureArray, "GGC_BGFX_NO_SORTED_TEXTURE_ARRAY", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "disable the sorted texture-array merge of adjacent single-texture runs") \ + X(GgcFlag_BgfxDisableSortedBatchStatePacketCache, "GGC_BGFX_DISABLE_SORTED_BATCH_STATE_PACKET_CACHE", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "rebuild sorted-batch backend state per apply instead of using the cached packet") \ + X(GgcFlag_BgfxDisableSortedPacketSubmit, "GGC_BGFX_DISABLE_SORTED_PACKET_SUBMIT", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "revert sorted-pool runs to the legacy apply/draw call sequence instead of the single packet submit") \ + X(GgcFlag_BgfxSortedResolvedPipeline, "GGC_BGFX_SORTED_RESOLVED_PIPELINE", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "consume the capture-time resolved pipeline-state word for sorted packet submits instead of deriving it per draw") \ + X(GgcFlag_BgfxDisableUniformFrequencySplit, "GGC_BGFX_DISABLE_UNIFORM_FREQUENCY_SPLIT", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "re-upload frame-constant uniforms (shadow/eye/ambient) on every draw instead of once per frame per view") \ + X(GgcFlag_BgfxDisableRigidPacketSubmit, "GGC_BGFX_DISABLE_RIGID_PACKET_SUBMIT", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "revert rigid mesh draws to the legacy index-offset + draw call pair instead of the single packet submit") \ + X(GgcFlag_BgfxDisableUnlitLightInputSkip, "GGC_BGFX_DISABLE_UNLIT_LIGHT_INPUT_SKIP", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "disable skipping light-uniform uploads for unlit draws") \ + X(GgcFlag_BgfxDisableInactiveShadowUniformSkip, "GGC_BGFX_DISABLE_INACTIVE_SHADOW_UNIFORM_SKIP", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "disable skipping shadow-uniform uploads while the shadow map is inactive") \ + X(GgcFlag_BgfxNoMapperApply, "GGC_BGFX_NO_MAPPER_APPLY", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "skip applying vertex-material UV mappers at material bind, freezing animated texture transforms") \ + X(GgcFlag_NoSortCoalesce, "GGC_NO_SORT_COALESCE", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "revert additive-run coalescing in the sorting flush to the per-run path") \ + X(GgcFlag_NoTrackBatch, "GGC_NO_TRACK_BATCH", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "force per-module terrain-track draws instead of per-texture batching") \ + X(GgcFlag_NoParticleBatch, "GGC_NO_PARTICLE_BATCH", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "force per-emitter particle submission instead of batched draws") \ + X(GgcFlag_NoVolumeMerge, "GGC_NO_VOLUME_MERGE", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "force legacy per-depth-layer volume-particle draws instead of one merged draw") \ + X(GgcFlag_NoCoplanarBiasGate, "GGC_NO_COPLANAR_BIAS_GATE", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "restore the unconditional coplanar-pair scan on every dynamic vertex write") \ + X(GgcFlag_NoCloudShadows, "GGC_NO_CLOUD_SHADOWS", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "force the cloud-shadow scrolling texture off") \ + X(GgcFlag_NoLightMap, "GGC_NO_LIGHTMAP", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "force the static terrain noise/lightmap layer off") \ + X(GgcFlag_NoPropShadows, "GGC_NO_PROP_SHADOWS", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "stop marking opaque world objects as sun-shadow receivers") \ + X(GgcFlag_NoRotorShadow, "GGC_NO_ROTOR_SHADOW", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "disable the rotor-blur alpha-tested disc shadow caster") \ + X(GgcFlag_BgfxDepthClamp, "GGC_BGFX_DEPTH_CLAMP", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "re-enable depth clamp (fallback for shadow-volume near/far-clip regressions)") \ + X(GgcFlag_EnableLegacyStencilShadows, "GGC_ENABLE_LEGACY_STENCIL_SHADOWS", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "force the legacy stencil-shadow path on even when the shadow mode disables it") \ + X(GgcFlag_BgfxLegacyPostMeshStencilShadows, "GGC_BGFX_LEGACY_POSTMESH_STENCIL_SHADOWS", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "submit stencil volumes in legacy post-mesh order instead of the engine view") \ + X(GgcFlag_BgfxCullSubpixel, "GGC_BGFX_CULL_SUBPIXEL", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "skip base-mesh submission for drawables projecting under the min pixel size") \ + X(GgcFlag_BgfxCullMinPx, "GGC_BGFX_CULL_MIN_PX", NULL, GgcTier_KillSwitch, GgcFlagType_Float, 0, 2.0f, "projected-radius threshold in pixels for the subpixel cull") \ + X(GgcFlag_DisableParticleCannonTrackingLight, "GGC_DISABLE_PARTICLE_CANNON_TRACKING_LIGHT", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "disable the Particle Cannon beam-tracking dynamic light") \ + X(GgcFlag_DisableRawAnimStepSnap, "GGC_DISABLE_RAW_ANIM_STEP_SNAP", NULL, GgcTier_KillSwitch, GgcFlagType_Presence, 0, 0.0f, "opt out of the per-axis raw-anim translation step-snap fix") \ + /* --- Platform workarounds --- */ \ + X(GgcFlag_MacosFlush, "GGC_MACOS_FLUSH", NULL, GgcTier_Workaround, GgcFlagType_Presence, 0, 0.0f, "re-enable serialized Metal submit (AGX shader-compile artifact workaround)") \ + X(GgcFlag_MacosUseNsWindow, "GGC_MACOS_USE_NSWINDOW", NULL, GgcTier_Workaround, GgcFlagType_Presence, 0, 0.0f, "pass the legacy NSWindow handle to bgfx instead of SDL3's CAMetalLayer") \ + X(GgcFlag_StrictPoolShutdown, "GGC_STRICT_POOL_SHUTDOWN", NULL, GgcTier_Workaround, GgcFlagType_Presence, 0, 0.0f, "opt back into strict ObjectPoolClass destructor teardown and asserts") \ + X(GgcFlag_DisableMouseWarp, "GGC_DISABLE_MOUSE_WARP", NULL, GgcTier_Workaround, GgcFlagType_Presence, 0, 0.0f, "skip SDL_WarpMouseInWindow in mouse setPosition") \ + X(GgcFlag_DisableMouseGrab, "GGC_DISABLE_MOUSE_GRAB", NULL, GgcTier_Workaround, GgcFlagType_Presence, 0, 0.0f, "skip SDL_SetWindowMouseGrab in mouse capture (breaks edge scroll)") \ + X(GgcFlag_SdlTextureCursor, "GGC_SDL_TEXTURE_CURSOR", NULL, GgcTier_Workaround, GgcFlagType_Presence, 0, 0.0f, "enable fallback color-cursor creation from the in-game cursor texture") \ + X(GgcFlag_SdlOsCursor, "GGC_SDL_OS_CURSOR", NULL, GgcTier_Workaround, GgcFlagType_Presence, 0, 0.0f, "force the OS cursor always visible, bypassing game cursor visibility") \ + X(GgcFlag_BgfxSkipStaticVolumeShadows, "GGC_BGFX_SKIP_STATIC_VOLUME_SHADOWS", NULL, GgcTier_Workaround, GgcFlagType_Presence, 0, 0.0f, "skip stencil volume shadows for static (non-vehicle) casters") \ + /* --- Automation harness --- */ \ + X(GgcFlag_BgfxScreenshotAfter, "GGC_BGFX_SCREENSHOT_AFTER", NULL, GgcTier_Harness, GgcFlagType_Int, 0, 0.0f, "render frame at which to take the first bgfx screenshot") \ + X(GgcFlag_BgfxScreenshotInterval, "GGC_BGFX_SCREENSHOT_INTERVAL", NULL, GgcTier_Harness, GgcFlagType_Int, 500, 0.0f, "frames between repeated bgfx screenshots") \ + X(GgcFlag_BgfxScreenshotPath, "GGC_BGFX_SCREENSHOT_PATH", NULL, GgcTier_Harness, GgcFlagType_String, 0, 0.0f, "base output path for bgfx screenshots") \ + X(GgcFlag_BgfxScreenshotLogicFrame, "GGC_BGFX_SCREENSHOT_LOGICFRAME", NULL, GgcTier_Harness, GgcFlagType_Int, 0, 0.0f, "logic frame that triggers one deterministic bgfx screenshot") \ + X(GgcFlag_Dx8ScreenshotAfter, "GGC_DX8_SCREENSHOT_AFTER", NULL, GgcTier_Harness, GgcFlagType_Int, 0, 0.0f, "render frame at which to take the first DX8 backend screenshot") \ + X(GgcFlag_Dx8ScreenshotInterval, "GGC_DX8_SCREENSHOT_INTERVAL", NULL, GgcTier_Harness, GgcFlagType_Int, 0, 0.0f, "frames between repeated DX8 screenshots") \ + X(GgcFlag_Dx8ScreenshotPath, "GGC_DX8_SCREENSHOT_PATH", NULL, GgcTier_Harness, GgcFlagType_String, 0, 0.0f, "base output path for DX8 screenshots") \ + X(GgcFlag_Dx8ScreenshotLogicFrame, "GGC_DX8_SCREENSHOT_LOGICFRAME", NULL, GgcTier_Harness, GgcFlagType_Int, 0, 0.0f, "logic frame that triggers one deterministic DX8 screenshot") \ + X(GgcFlag_BgfxFrameTimingAfter, "GGC_BGFX_FRAME_TIMING_AFTER", NULL, GgcTier_Harness, GgcFlagType_Int, -1, 0.0f, "frame at which to start the per-section frame-timing CSV capture") \ + X(GgcFlag_BgfxFrameTimingInterval, "GGC_BGFX_FRAME_TIMING_INTERVAL", NULL, GgcTier_Harness, GgcFlagType_Int, 60, 0.0f, "frames between frame-timing CSV emits") \ + X(GgcFlag_BgfxFrameTimingPath, "GGC_BGFX_FRAME_TIMING_PATH", NULL, GgcTier_Harness, GgcFlagType_String, 0, 0.0f, "base path for the frame-timing CSV output") \ + X(GgcFlag_RenderDocCaptureAfter, "GGC_RENDERDOC_CAPTURE_AFTER", NULL, GgcTier_Harness, GgcFlagType_Int, -1, 0.0f, "frame at which to trigger a RenderDoc capture") \ + X(GgcFlag_RenderDocCaptureInterval, "GGC_RENDERDOC_CAPTURE_INTERVAL", NULL, GgcTier_Harness, GgcFlagType_Int, 0, 0.0f, "frames between repeated RenderDoc captures") \ + X(GgcFlag_AutoExitSeconds, "GGC_AUTO_EXIT_SECONDS", NULL, GgcTier_Harness, GgcFlagType_Int, 0, 0.0f, "exit the game after N wall-clock seconds of engine update") \ + X(GgcFlag_FreezeLogicAfter, "GGC_FREEZE_LOGIC_AFTER", NULL, GgcTier_Harness, GgcFlagType_Int, -1, 0.0f, "freeze sim time once the logic frame reaches N; rendering continues") \ + X(GgcFlag_LogSleepyFingerprint, "GGC_LOG_SLEEPY_FINGERPRINT", NULL, GgcTier_Harness, GgcFlagType_Truthy, 0, 0.0f, "log a per-frame sleepy-update scheduler fingerprint for replay desync bisection") \ + X(GgcFlag_NoAudio, "GGC_NO_AUDIO", NULL, GgcTier_Harness, GgcFlagType_Truthy, 0, 0.0f, "force the dummy audio manager (headless/automation runs)") \ + X(GgcFlag_TriggerGuiCommand, "GGC_TRIGGER_GUI_COMMAND", NULL, GgcTier_Harness, GgcFlagType_String, 0, 0.0f, "auto-fire a named ControlBar GUI command after the trigger delay") \ + X(GgcFlag_TriggerSpecialPower, "GGC_TRIGGER_SPECIAL_POWER", NULL, GgcTier_Harness, GgcFlagType_String, 0, 0.0f, "auto-fire a named special power after the trigger delay") \ + X(GgcFlag_TriggerWorld, "GGC_TRIGGER_WORLD", NULL, GgcTier_Harness, GgcFlagType_String, 0, 0.0f, "world-coordinate target x,y[,z] for the auto-fired command or power") \ + X(GgcFlag_TriggerDelayFrames, "GGC_TRIGGER_DELAY_FRAMES", NULL, GgcTier_Harness, GgcFlagType_Int, 90, 0.0f, "delay in frames before the auto-fired command or power fires") \ + /* --- Diagnostics --- */ \ + X(GgcFlag_Trace, "GGC_TRACE", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "verbose [ggc] trace: init breadcrumbs, bgfx info lines, routing logs") \ + X(GgcFlag_BgfxDebug, "GGC_BGFX_DEBUG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "enable bgfx debug/verbose init diagnostics") \ + X(GgcFlag_BgfxPerfLog, "GGC_BGFX_PERF_LOG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "periodic perf-stats logging; numeric value overrides the emit interval") \ + X(GgcFlag_BgfxPerfDir, "GGC_BGFX_PERF_DIR", NULL, GgcTier_Diagnostic, GgcFlagType_String, 0, 0.0f, "directory for the perf-stats CSV log") \ + X(GgcFlag_DrawLogAfter, "GGC_DRAWLOG_AFTER", NULL, GgcTier_Diagnostic, GgcFlagType_Int, -1, 0.0f, "frame number at which to dump a per-draw-call log") \ + X(GgcFlag_DrawLogInterval, "GGC_DRAWLOG_INTERVAL", NULL, GgcTier_Diagnostic, GgcFlagType_Int, 0, 0.0f, "repeat interval in frames for further draw-call log dumps") \ + X(GgcFlag_DrawLogPath, "GGC_DRAWLOG_PATH", NULL, GgcTier_Diagnostic, GgcFlagType_String, 0, 0.0f, "base output path for draw-call logs") \ + X(GgcFlag_PointShadowViz, "GGC_POINT_SHADOW_VIZ", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "blit the point-shadow map to a screen-corner rect for inspection") \ + X(GgcFlag_BgfxTransientDiag, "GGC_BGFX_TRANSIENT_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log transient-buffer ownership decisions") \ + X(GgcFlag_BgfxBufferUpdateDiag, "GGC_BGFX_BUFFER_UPDATE_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log dynamic buffer-update events") \ + X(GgcFlag_StencilShadowDiag, "GGC_STENCIL_SHADOW_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log stencil-shadow events") \ + X(GgcFlag_ShadowPathDiag, "GGC_SHADOW_PATH_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log shadow-type resolution path per drawable") \ + X(GgcFlag_BgfxShroudPassDiag, "GGC_BGFX_SHROUD_PASS_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log shroud-pass draw decisions") \ + X(GgcFlag_BgfxSortedDecalDiag, "GGC_BGFX_SORTED_DECAL_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log sorted-decal draw decisions") \ + X(GgcFlag_BgfxRevealDiag, "GGC_BGFX_REVEAL_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log reveal-grid draw decisions") \ + X(GgcFlag_BgfxRevealDiagVerbose, "GGC_BGFX_REVEAL_DIAG_VERBOSE", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "verbose variant of the reveal-grid diagnostics") \ + X(GgcFlag_BgfxEffectSubmitDiag, "GGC_BGFX_EFFECT_SUBMIT_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log effect-overlay submit decisions") \ + X(GgcFlag_LogShadowCasterAudit, "GGC_LOG_SHADOW_CASTER_AUDIT", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "dump every world draw that could feed the sun shadow map") \ + X(GgcFlag_LogShadowCasterStart, "GGC_LOG_SHADOW_CASTER_START", NULL, GgcTier_Diagnostic, GgcFlagType_Int, 0, 0.0f, "first frame of the shadow-caster audit window") \ + X(GgcFlag_LogShadowCasterEnd, "GGC_LOG_SHADOW_CASTER_END", NULL, GgcTier_Diagnostic, GgcFlagType_Int, INT_MAX, 0.0f, "last frame of the shadow-caster audit window") \ + X(GgcFlag_EffectTextureDiag, "GGC_EFFECT_TEXTURE_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log uploads and alpha stats of effect textures") \ + X(GgcFlag_BgfxShroudDumpDir, "GGC_BGFX_SHROUD_DUMP_DIR", NULL, GgcTier_Diagnostic, GgcFlagType_String, 0, 0.0f, "directory to dump shroud texture uploads as PPM files") \ + X(GgcFlag_BgfxShroudDumpLimit, "GGC_BGFX_SHROUD_DUMP_LIMIT", NULL, GgcTier_Diagnostic, GgcFlagType_Int, 8, 0.0f, "max number of shroud PPM dumps per run") \ + X(GgcFlag_PlayerContextDiag, "GGC_PLAYER_CONTEXT_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log player-context lines per logged event") \ + X(GgcFlag_AudioDiag, "GGC_AUDIO_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log every audio cache miss with cache occupancy") \ + X(GgcFlag_LaserDiag, "GGC_LASER_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log laser draw events for Patriot binary-data-stream templates") \ + X(GgcFlag_LaserDiagAll, "GGC_LASER_DIAG_ALL", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "widen the laser diagnostics to all drawables") \ + X(GgcFlag_MapPreviewDiag, "GGC_MAP_PREVIEW_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log map-preview image draw details") \ + X(GgcFlag_LightEnvDiag, "GGC_LIGHT_ENV_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "print the first light-environment snapshots") \ + X(GgcFlag_PointGroupDiag, "GGC_POINTGROUP_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log per-particle-group render state") \ + X(GgcFlag_SeglineDiag, "GGC_SEGLINE_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log segline geometry and submit state") \ + X(GgcFlag_BgfxSortedPacketCollectorDiag, "GGC_BGFX_SORTED_PACKET_COLLECTOR_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "classify sorted-packet source/blend/fallback per flush") \ + X(GgcFlag_BgfxSortedPacketCollectorDiagLimit, "GGC_BGFX_SORTED_PACKET_COLLECTOR_DIAG_LIMIT", NULL, GgcTier_Diagnostic, GgcFlagType_Int, 512, 0.0f, "max flushes logged by the sorted-packet collector diagnostic") \ + X(GgcFlag_SortEffectDiag, "GGC_SORT_EFFECT_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log sorted-effect draw events for effect-named textures") \ + X(GgcFlag_SortEffectDiagAll, "GGC_SORT_EFFECT_DIAG_ALL", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "remove the texture-name filter of the sorted-effect diagnostics") \ + X(GgcFlag_CursorDiag, "GGC_CURSOR_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log cursor set/lookup and surface/texture build details") \ + X(GgcFlag_SdlSoftwareCursor, "GGC_SDL_SOFTWARE_CURSOR", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "draw a line-art software cursor via the display layer") \ + X(GgcFlag_DecalDiag, "GGC_DECAL_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log decal-shadow diagnostics") \ + X(GgcFlag_W3dAssetDiag, "GGC_W3D_ASSET_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log W3D asset-manager diagnostics") \ + X(GgcFlag_W3dFileDiag, "GGC_W3D_FILE_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log W3D file-resolution probes") \ + X(GgcFlag_ParticleDiag, "GGC_PARTICLE_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log per-emitter particle render details") \ + X(GgcFlag_SceneDiag, "GGC_SCENE_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log scene render-path diagnostic counters") \ + X(GgcFlag_ShroudDiag, "GGC_SHROUD_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log checksum and pixel stats of visible shroud data per capture") \ + X(GgcFlag_ShroudDiagLimit, "GGC_SHROUD_DIAG_LIMIT", NULL, GgcTier_Diagnostic, GgcFlagType_Int, 32, 0.0f, "max shroud diagnostic samples logged") \ + X(GgcFlag_Ww3dLoadDiag, "GGC_WW3D_LOAD_DIAG", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "log WW3D asset loads") \ + X(GgcFlag_DumpLocalObjects, "GGC_DUMP_LOCAL_OBJECTS", NULL, GgcTier_Diagnostic, GgcFlagType_Presence, 0, 0.0f, "dump every local-player object once") \ + /* --- Isolation probes --- */ \ + X(GgcFlag_ProbeNullSubmit, "GGC_PROBE_NULL_SUBMIT", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "discard all engine/sorted draws instead of submitting") \ + X(GgcFlag_ProbeFreezeState, "GGC_PROBE_FREEZE_STATE", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "freeze light/texture/material uniform and bind state") \ + X(GgcFlag_ProbeNoSorted, "GGC_PROBE_NO_SORTED", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "drop all sorted-view draws") \ + X(GgcFlag_ProbeNoTexBind, "GGC_PROBE_NO_TEXBIND", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "skip texture binds") \ + X(GgcFlag_ProbeNoMatUniform, "GGC_PROBE_NO_MATUNIFORM", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "skip material uniform uploads") \ + X(GgcFlag_ProbeNoLightUniform, "GGC_PROBE_NO_LIGHTUNIFORM", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "skip light uniform uploads") \ + X(GgcFlag_ProbeNoSortFlush, "GGC_PROBE_NO_SORT_FLUSH", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "discard all sorted translucency instead of drawing it") \ + X(GgcFlag_ProbeNoParticleRender, "GGC_PROBE_NO_PARTICLE_RENDER", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "skip particle-buffer rendering entirely") \ + X(GgcFlag_ProbeNoSceneObjectRender, "GGC_PROBE_NO_SCENE_OBJECT_RENDER", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "skip the scene render-object loop") \ + X(GgcFlag_ProbeIdentityInstances, "GGC_PROBE_IDENTITY_INSTANCES", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "write identity matrices into the instance batch") \ + X(GgcFlag_ProbeTransposeInstances, "GGC_PROBE_TRANSPOSE_INSTANCES", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "write transposed instance matrices (layout debugging)") \ + X(GgcFlag_BgfxEnableDiagnosticOverrides, "GGC_BGFX_ENABLE_DIAGNOSTIC_OVERRIDES", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "master arming switch for the GGC_BGFX_SKIP_* draw-skip probes") \ + X(GgcFlag_BgfxSkipRevealGrid, "GGC_BGFX_SKIP_REVEAL_GRID", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "discard reveal-grid-textured draws (needs diagnostic overrides)") \ + X(GgcFlag_BgfxSkipEffectOverlayDraws, "GGC_BGFX_SKIP_EFFECT_OVERLAY_DRAWS", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "discard draws routed to the effect-overlay view (needs diagnostic overrides)") \ + X(GgcFlag_BgfxSkipSortedDraws, "GGC_BGFX_SKIP_SORTED_DRAWS", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "discard draws routed to the sorted view (needs diagnostic overrides)") \ + X(GgcFlag_BgfxSkipShroudOverlay, "GGC_BGFX_SKIP_SHROUD_OVERLAY", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "discard shroud-overlay draws (needs diagnostic overrides)") \ + X(GgcFlag_BgfxSkipBlobShadows, "GGC_BGFX_SKIP_BLOB_SHADOWS", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "skip default blob/decal shadows (needs diagnostic overrides)") \ + X(GgcFlag_NoEffectOverlay, "GGC_NO_EFFECT_OVERLAY", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "prevent the effect-overlay view from activating") \ + X(GgcFlag_BgfxNoCameraSpaceWorldFix, "GGC_BGFX_NO_CAMERA_SPACE_WORLD_FIX", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "disable the inverse-camera-view compensation for camera-space engine-view draws") \ + X(GgcFlag_BgfxStencilNoApply, "GGC_BGFX_STENCIL_NO_APPLY", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "skip the fullscreen stencil-darken apply pass") \ + X(GgcFlag_BgfxStencilDepth, "GGC_BGFX_STENCIL_DEPTH", NULL, GgcTier_Probe, GgcFlagType_String, 0, 0.0f, "override the shadow-volume depth test: less or always") \ + X(GgcFlag_BgfxStencilTwoSided, "GGC_BGFX_STENCIL_TWO_SIDED", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "opt-in two-sided stencil volumes (known broken for elevated casters)") \ + X(GgcFlag_BgfxStencilInvertCull, "GGC_BGFX_STENCIL_INVERT_CULL", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "invert cull face for shadow-volume passes") \ + X(GgcFlag_BgfxStencilClampClip, "GGC_BGFX_STENCIL_CLAMP_CLIP", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "shader-side clamp-clip experiment for shadow volumes") \ + X(GgcFlag_BgfxStencilAlgo, "GGC_BGFX_STENCIL_ALGO", NULL, GgcTier_Probe, GgcFlagType_String, 0, 0.0f, "stencil volume algorithm selection: zfail, zfail-swap, zpass, zpass-swap") \ + X(GgcFlag_BgfxStencilIncrSat, "GGC_BGFX_STENCIL_INCR_SAT", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "use saturating stencil increment/decrement ops for shadow volumes") \ + X(GgcFlag_BgfxFlipCapWinding, "GGC_BGFX_FLIP_CAP_WINDING", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "flip triangle winding of shadow-volume caps") \ + X(GgcFlag_BgfxClosedShadowVolumes, "GGC_BGFX_CLOSED_SHADOW_VOLUMES", NULL, GgcTier_Probe, GgcFlagType_Presence, 0, 0.0f, "request closed (capped) shadow-volume geometry from the engine") + +enum GgcFlagId +{ +#define GGC_RUNTIME_FLAG_ENUM_ENTRY(id, name, alias, tier, type, defaultInt, defaultFloat, help) id, + GGC_RUNTIME_FLAG_LIST(GGC_RUNTIME_FLAG_ENUM_ENTRY) +#undef GGC_RUNTIME_FLAG_ENUM_ENTRY + GgcFlagCount +}; + +namespace GgcFlags +{ + +// Presence/Truthy/ZeroOff evaluation per the flag's declared type. +bool Enabled(GgcFlagId id); + +// atoi of the value when the flag is set (and its tier is compiled in), +// else the declared default. +int IntValue(GgcFlagId id); + +// atof of the value when set, else the declared default. +float FloatValue(GgcFlagId id); + +// Raw value, NULL when unset or when the flag's tier is not compiled in. +// The returned pointer stays valid for the lifetime of the process. +const char *StringValue(GgcFlagId id); + +// Forces the flag to the given value (copied), overriding the environment +// and any earlier resolution. Used by command-line argument parsing. +void SetOverride(GgcFlagId id, const char *value); + +// Prints the whole flag table with resolved values to stderr when +// GGC_LIST_FLAGS is set in the environment. +void DumpTableIfRequested(); + +} diff --git a/Core/Libraries/Source/WWVegas/WWLib/Point.h b/Core/Libraries/Source/WWVegas/WWLib/Point.h index 78a9f8c7ea9..3c74f9f39ee 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/Point.h +++ b/Core/Libraries/Source/WWVegas/WWLib/Point.h @@ -36,6 +36,8 @@ #pragma once +#include "WWMath/wwmath.h" + template class TRect; /* @@ -76,9 +78,9 @@ class TPoint2D { TPoint2D const operator - () const {return(TPoint2D(-X, -Y));} // Vector support functions. - T Length() const {return(T(sqrt(X*X + Y*Y)));} + T Length() const {return(T(WWMath::Sqrt(X*X + Y*Y)));} TPoint2D const Normalize() const { - double len = sqrt(X*X + Y*Y); + double len = WWMath::Sqrt(X*X + Y*Y); if (len != 0.0) { return(TPoint2D((T)((double)X / len), (T)((double)Y / len))); } else { @@ -163,9 +165,9 @@ class TPoint3D : public TPoint2D { TPoint3D const operator - () const {return(TPoint3D(-X, -Y, -Z));} // Vector support functions. - T Length() const {return(T(sqrt(X*X + Y*Y + Z*Z)));} + T Length() const {return(T(WWMath::Sqrt(X*X + Y*Y + Z*Z)));} TPoint3D const Normalize() const { - double len = sqrt(X*X + Y*Y + Z*Z); + double len = WWMath::Sqrt(X*X + Y*Y + Z*Z); if (len != 0.0) { return(TPoint3D(X / len, Y / len, Z / len)); } else { diff --git a/Core/Libraries/Source/WWVegas/WWLib/TARGA.cpp b/Core/Libraries/Source/WWVegas/WWLib/TARGA.cpp index 4c4d1e1d6ee..942716940ce 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 @@ -79,6 +83,9 @@ #include #include +static_assert(sizeof(TGA2Footer) == 26, "TGA 2.0 footer must match on-disk size."); +static_assert(sizeof(TGA2Extension) == 495, "TGA 2.0 extension must match on-disk size."); + /**************************************************************************** * * NAME diff --git a/Core/Libraries/Source/WWVegas/WWLib/TARGA.h b/Core/Libraries/Source/WWVegas/WWLib/TARGA.h index 4ce57baaa9d..dd636a8839c 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/TARGA.h +++ b/Core/Libraries/Source/WWVegas/WWLib/TARGA.h @@ -133,8 +133,8 @@ typedef struct _TGAHeader */ typedef struct _TGA2Footer { - long Extension; - long Developer; + int Extension; + int Developer; char Signature[16]; char RsvdChar; char BZST; @@ -224,12 +224,12 @@ typedef struct _TGA2Extension TGA2TimeStamp JobTime; char SoftID[41]; TGA2SoftVer SoftVer; - long KeyColor; + int KeyColor; TGA2Ratio Aspect; TGA2Ratio Gamma; - long ColorCor; - long PostStamp; - long ScanLine; + int ColorCor; + int PostStamp; + int ScanLine; char Attributes; } TGA2Extension; diff --git a/Core/Libraries/Source/WWVegas/WWLib/WWDefines.h b/Core/Libraries/Source/WWVegas/WWLib/WWDefines.h index eb25d597db8..853a3716579 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/WWDefines.h +++ b/Core/Libraries/Source/WWVegas/WWLib/WWDefines.h @@ -18,6 +18,8 @@ #pragma once +#include "Lib/BaseDefines.h" + // Enable translation and rotation interpolation for raw animation (HRawAnimClass) updates. // This was intentionally disabled in the retail version, but likely not fully thought through. // Interpolation is certainly desired for animations that move and rotate meshes, but may not be diff --git a/Core/Libraries/Source/WWVegas/WWLib/bittype.h b/Core/Libraries/Source/WWVegas/WWLib/bittype.h index 7b40a59c2e4..df9b205e18a 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/bittype.h +++ b/Core/Libraries/Source/WWVegas/WWLib/bittype.h @@ -37,27 +37,37 @@ #pragma once +#include + typedef unsigned char uint8; typedef unsigned short uint16; -typedef unsigned long uint32; +typedef uint32_t uint32; typedef unsigned int uint; typedef signed char sint8; typedef signed short sint16; -typedef signed long sint32; +typedef int32_t sint32; typedef signed int sint; typedef float float32; typedef double float64; -typedef unsigned long DWORD; +#ifdef _WIN32 +typedef unsigned long DWORD; +#else +typedef uint32_t DWORD; +#endif typedef unsigned short WORD; typedef unsigned char BYTE; typedef int BOOL; typedef unsigned short USHORT; typedef const char * LPCSTR; typedef unsigned int UINT; -typedef unsigned long ULONG; +#ifdef _WIN32 +typedef unsigned long ULONG; +#else +typedef uint32_t ULONG; +#endif #if defined(_MSC_VER) && _MSC_VER < 1300 #ifndef _WCHAR_T_DEFINED diff --git a/Core/Libraries/Source/WWVegas/WWLib/cpudetect.cpp b/Core/Libraries/Source/WWVegas/WWLib/cpudetect.cpp index c01e7f75254..7dbe1464953 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/cpudetect.cpp +++ b/Core/Libraries/Source/WWVegas/WWLib/cpudetect.cpp @@ -28,6 +28,12 @@ #include #endif +#if defined(__APPLE__) +#include +#elif defined(__linux__) +#include +#endif + #ifdef _UNIX # include // for time(), localtime() and timezone variable. #endif @@ -919,7 +925,31 @@ void CPUDetectClass::Init_Memory() #endif // defined(_MSC_VER) && _MSC_VER < 1300 #else -#warning FIX Init_Memory() +#if defined(__APPLE__) + uint64_t total_memory = 0; + size_t total_size = sizeof(total_memory); + if (sysctlbyname("hw.memsize", &total_memory, &total_size, nullptr, 0) == 0) + { + TotalPhysicalMemory = total_memory; + AvailablePhysicalMemory = total_memory; + TotalPageMemory = total_memory; + AvailablePageMemory = total_memory; + TotalVirtualMemory = total_memory; + AvailableVirtualMemory = total_memory; + } +#elif defined(__linux__) + struct sysinfo mem; + if (sysinfo(&mem) == 0) + { + const uint64_t unit = mem.mem_unit ? mem.mem_unit : 1; + TotalPhysicalMemory = uint64_t(mem.totalram) * unit; + AvailablePhysicalMemory = uint64_t(mem.freeram) * unit; + TotalPageMemory = uint64_t(mem.totalswap) * unit; + AvailablePageMemory = uint64_t(mem.freeswap) * unit; + TotalVirtualMemory = TotalPhysicalMemory + TotalPageMemory; + AvailableVirtualMemory = AvailablePhysicalMemory + AvailablePageMemory; + } +#endif #endif // WIN32 } 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/mempool.h b/Core/Libraries/Source/WWVegas/WWLib/mempool.h index 1670d81913c..e9d8f94648f 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/mempool.h +++ b/Core/Libraries/Source/WWVegas/WWLib/mempool.h @@ -47,6 +47,7 @@ #include "bittype.h" #include "WWDebug/wwdebug.h" #include "mutex.h" +#include "GgcRuntimeFlags.h" #include #include #include @@ -199,6 +200,23 @@ ObjectPoolClass::ObjectPoolClass() : template ObjectPoolClass::~ObjectPoolClass() { +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @bugfix bobtista 04/05/2026 The legacy engine leaves + // some global multi-list users alive until process teardown. The static + // AutoPoolClass allocators can then destruct after their list owners, + // walking stale pool metadata and producing misleading crash reports during + // exit. Let the OS reclaim these small pools on process termination; set + // GGC_STRICT_POOL_SHUTDOWN=1 when specifically auditing shutdown leaks. + // TheSuperHackers @bugfix bobtista 11/07/2026 Was Apple-only; the win64 + // SDL3 build crashed 0xC0000005 in this destructor on every process exit + // (WER dump: main thread inside _execute_onexit_table reading the poisoned + // block chain after the mempool.h:216 assert), so the guard now covers all + // bgfx builds. The dx8/retail lane keeps the legacy strict teardown. + if (!GgcFlags::Enabled(GgcFlag_StrictPoolShutdown)) + { + return; + } +#endif // assert that the user gave back all of the memory he was using WWASSERT(FreeObjectCount == TotalObjectCount); 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..c2269aa8bc3 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 @@ -36,10 +37,33 @@ size_t wcsnlen(const wchar_t *str, size_t maxlen); template size_t strlcpy_t(T *dst, const T *src, size_t dstsize); template size_t strlcat_t(T *dst, const T *src, size_t dstsize); +// TheSuperHackers @build bobtista 24/07/2026 glibc 2.38 added strlcpy, strlcat, +// wcslcpy and wcslcat. Declaring or defining our own then collides with the libc +// versions (C linkage / noexcept mismatch). Detect glibc >= 2.38 early so both the +// declarations here and the inline definitions below are suppressed. Non-glibc +// platforms such as macOS are unaffected: __GLIBC__ is undefined there. +#if defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 38)) +#ifndef HAVE_STRLCPY +#define HAVE_STRLCPY 1 +#endif +#ifndef HAVE_STRLCAT +#define HAVE_STRLCAT 1 +#endif +#ifndef HAVE_WCSLCPY +#define HAVE_WCSLCPY 1 +#endif +#endif + +#ifndef HAVE_STRLCPY size_t strlcpy(char *dst, const char *src, size_t dstsize); +#endif +#ifndef HAVE_STRLCAT size_t strlcat(char *dst, const char *src, size_t dstsize); +#endif +#ifndef HAVE_WCSLCPY size_t wcslcpy(wchar_t *dst, const wchar_t *src, size_t dstsize); size_t wcslcat(wchar_t *dst, const wchar_t *src, size_t dstsize); +#endif template size_t strlmove_t(T *dst, const T *src, size_t dstsize); template size_t strlmcat_t(T *dst, const T *src, size_t dstsize); @@ -138,8 +162,10 @@ inline size_t strlcpy(char *dst, const char *src, size_t dstsize) { return strlc #ifndef HAVE_STRLCAT inline size_t strlcat(char *dst, const char *src, size_t dstsize) { return strlcat_t(dst, src, dstsize); } #endif +#ifndef HAVE_WCSLCPY inline size_t wcslcpy(wchar_t *dst, const wchar_t *src, size_t dstsize) { return strlcpy_t(dst, src, dstsize); } inline size_t wcslcat(wchar_t *dst, const wchar_t *src, size_t dstsize) { return strlcat_t(dst, src, dstsize); } +#endif // Templated strlmove. Prefer using this over strlcpy if dst and src overlap. // Moves src into dst until dstsize minus one. Always null terminates. diff --git a/Core/Libraries/Source/WWVegas/WWLib/visualc.h b/Core/Libraries/Source/WWVegas/WWLib/visualc.h index a400314d917..31639b9e623 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/visualc.h +++ b/Core/Libraries/Source/WWVegas/WWLib/visualc.h @@ -103,22 +103,5 @@ #pragma warning(disable : 4711) - -#define M_E 2.71828182845904523536 -#define M_LOG2E 1.44269504088896340736 -#define M_LOG10E 0.434294481903251827651 -#define M_LN2 0.693147180559945309417 -#define M_LN10 2.30258509299404568402 -#define M_PI 3.14159265358979323846 -#define M_PI_2 1.57079632679489661923 -#define M_PI_4 0.785398163397448309616 -#define M_1_PI 0.318309886183790671538 -#define M_2_PI 0.636619772367581343076 -#define M_1_SQRTPI 0.564189583547756286948 -#define M_2_SQRTPI 1.12837916709551257390 -#define M_SQRT2 1.41421356237309504880 -#define M_SQRT_2 0.707106781186547524401 - - #endif 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/WWMath/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WWMath/CMakeLists.txt index dca9eb68fef..4669b675165 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WWMath/CMakeLists.txt @@ -91,5 +91,11 @@ target_link_libraries(core_wwmath PRIVATE core_wwsaveload ) +if (NOT IS_VS6_BUILD) + target_link_libraries(core_wwmath PUBLIC + gamemath + ) +endif() + # @todo Test its impact and see what to do with the legacy functions. #add_compile_definitions(core_wwmath PUBLIC ALLOW_TEMPORARIES) # Enables legacy math with "temporaries" diff --git a/Core/Libraries/Source/WWVegas/WWMath/aabox.h b/Core/Libraries/Source/WWVegas/WWMath/aabox.h index 15ea5d80548..d2084eb7eb9 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/aabox.h +++ b/Core/Libraries/Source/WWVegas/WWMath/aabox.h @@ -436,7 +436,7 @@ WWINLINE float AABoxClass::Project_To_Axis(const Vector3 & axis) const float z = Extent[2] * axis[2]; // projection is the sum of the absolute values of the projections of the three extents - return (WWMath::Fabs(x) + WWMath::Fabs(y) + WWMath::Fabs(z)); + return (WWMath::Fabsf_Legacy(x) + WWMath::Fabsf_Legacy(y) + WWMath::Fabsf_Legacy(z)); } /*********************************************************************************************** diff --git a/Core/Libraries/Source/WWVegas/WWMath/colmathaabox.cpp b/Core/Libraries/Source/WWVegas/WWMath/colmathaabox.cpp index ba3927b829f..00766417bea 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/colmathaabox.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/colmathaabox.cpp @@ -70,9 +70,9 @@ bool CollisionMath::Intersection_Test(const AABoxClass & box,const AABoxClass & { Vector3 dc = box2.Center - box.Center; - if (box.Extent.X + box2.Extent.X < WWMath::Fabs(dc.X)) return false; - if (box.Extent.Y + box2.Extent.Y < WWMath::Fabs(dc.Y)) return false; - if (box.Extent.Z + box2.Extent.Z < WWMath::Fabs(dc.Z)) return false; + if (box.Extent.X + box2.Extent.X < WWMath::Fabsf_Legacy(dc.X)) return false; + if (box.Extent.Y + box2.Extent.Y < WWMath::Fabsf_Legacy(dc.Y)) return false; + if (box.Extent.Z + box2.Extent.Z < WWMath::Fabsf_Legacy(dc.Z)) return false; return true; } @@ -101,9 +101,9 @@ CollisionMath::OverlapType CollisionMath::Overlap_Test(const AABoxClass & box,co // // Check to see if the sphere is completely outside the box // - if (WWMath::Fabs(dist.X) > extent.X) return OUTSIDE; - if (WWMath::Fabs(dist.Y) > extent.Y) return OUTSIDE; - if (WWMath::Fabs(dist.Z) > extent.Z) return OUTSIDE; + if (WWMath::Fabsf_Legacy(dist.X) > extent.X) return OUTSIDE; + if (WWMath::Fabsf_Legacy(dist.Y) > extent.Y) return OUTSIDE; + if (WWMath::Fabsf_Legacy(dist.Z) > extent.Z) return OUTSIDE; return INSIDE; } @@ -224,21 +224,21 @@ CollisionMath::OverlapType CollisionMath::Overlap_Test(const AABoxClass & box,co // that 'dp' will always project to zero for this axis. Vector3 axis; axis.Set(0,-line.Get_Dir().Z,line.Get_Dir().Y); // == (1,0,0) cross (x,y,z) - box_proj = WWMath::Fabs(axis.Y*box.Extent.Y) + WWMath::Fabs(axis.Z*box.Extent.Z); + box_proj = WWMath::Fabsf_Legacy(axis.Y*box.Extent.Y) + WWMath::Fabsf_Legacy(axis.Z*box.Extent.Z); p0_proj = Vector3::Dot_Product(axis,dp0); - if (WWMath::Fabs(p0_proj) > box_proj) return OUTSIDE; + if (WWMath::Fabsf_Legacy(p0_proj) > box_proj) return OUTSIDE; // Project box and line onto (y cross line) axis.Set(line.Get_Dir().Z,0,-line.Get_Dir().X); // == (0,1,0) cross (x,y,z) - box_proj = WWMath::Fabs(axis.X*box.Extent.X) + WWMath::Fabs(axis.Z*box.Extent.Z); + box_proj = WWMath::Fabsf_Legacy(axis.X*box.Extent.X) + WWMath::Fabsf_Legacy(axis.Z*box.Extent.Z); p0_proj = Vector3::Dot_Product(axis,dp0); - if (WWMath::Fabs(p0_proj) > box_proj) return OUTSIDE; + if (WWMath::Fabsf_Legacy(p0_proj) > box_proj) return OUTSIDE; // Project box and line onto (z cross line) axis.Set(-line.Get_Dir().Y,line.Get_Dir().X,0); // == (0,0,1) cross (x,y,z) - box_proj = WWMath::Fabs(axis.X*box.Extent.X) + WWMath::Fabs(axis.Y*box.Extent.Y); + box_proj = WWMath::Fabsf_Legacy(axis.X*box.Extent.X) + WWMath::Fabsf_Legacy(axis.Y*box.Extent.Y); p0_proj = Vector3::Dot_Product(axis,dp0); - if (WWMath::Fabs(p0_proj) > box_proj) return OUTSIDE; + if (WWMath::Fabsf_Legacy(p0_proj) > box_proj) return OUTSIDE; } return OVERLAPPED; diff --git a/Core/Libraries/Source/WWVegas/WWMath/colmathaabox.h b/Core/Libraries/Source/WWVegas/WWMath/colmathaabox.h index 11db1895db2..316f9a4600f 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/colmathaabox.h +++ b/Core/Libraries/Source/WWVegas/WWMath/colmathaabox.h @@ -60,9 +60,9 @@ *=============================================================================================*/ WWINLINE CollisionMath::OverlapType CollisionMath::Overlap_Test(const AABoxClass & box,const Vector3 & point) { - if (WWMath::Fabs(point.X - box.Center.X) > box.Extent.X) return POS; - if (WWMath::Fabs(point.Y - box.Center.Y) > box.Extent.Y) return POS; - if (WWMath::Fabs(point.Z - box.Center.Z) > box.Extent.Z) return POS; + if (WWMath::Fabsf_Legacy(point.X - box.Center.X) > box.Extent.X) return POS; + if (WWMath::Fabsf_Legacy(point.Y - box.Center.Y) > box.Extent.Y) return POS; + if (WWMath::Fabsf_Legacy(point.Z - box.Center.Z) > box.Extent.Z) return POS; return NEG; } @@ -84,9 +84,9 @@ WWINLINE CollisionMath::OverlapType CollisionMath::Overlap_Test(const AABoxClass Vector3 dc; Vector3::Subtract(box2.Center,box.Center,&dc); - if (box.Extent.X + box2.Extent.X < WWMath::Fabs(dc.X)) return POS; - if (box.Extent.Y + box2.Extent.Y < WWMath::Fabs(dc.Y)) return POS; - if (box.Extent.Z + box2.Extent.Z < WWMath::Fabs(dc.Z)) return POS; + if (box.Extent.X + box2.Extent.X < WWMath::Fabsf_Legacy(dc.X)) return POS; + if (box.Extent.Y + box2.Extent.Y < WWMath::Fabsf_Legacy(dc.Y)) return POS; + if (box.Extent.Z + box2.Extent.Z < WWMath::Fabsf_Legacy(dc.Z)) return POS; if ( (dc.X + box2.Extent.X <= box.Extent.X) && (dc.Y + box2.Extent.Y <= box.Extent.Y) && diff --git a/Core/Libraries/Source/WWVegas/WWMath/colmathaabtri.cpp b/Core/Libraries/Source/WWVegas/WWMath/colmathaabtri.cpp index da1cc6c3ee4..ba833fb20c4 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/colmathaabtri.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/colmathaabtri.cpp @@ -288,9 +288,9 @@ static inline bool aabtri_check_axis() } // compute coordinates of the leading edge of the box at t0 and t1 - leb0 = CollisionContext.Box->Extent.X * WWMath::Fabs(CollisionContext.TestAxis.X) + - CollisionContext.Box->Extent.Y * WWMath::Fabs(CollisionContext.TestAxis.Y) + - CollisionContext.Box->Extent.Z * WWMath::Fabs(CollisionContext.TestAxis.Z); + leb0 = CollisionContext.Box->Extent.X * WWMath::Fabsf_Legacy(CollisionContext.TestAxis.X) + + CollisionContext.Box->Extent.Y * WWMath::Fabsf_Legacy(CollisionContext.TestAxis.Y) + + CollisionContext.Box->Extent.Z * WWMath::Fabsf_Legacy(CollisionContext.TestAxis.Z); leb1 = leb0 + axismove; // compute coordinate of "leading edge of the triangle" relative to the box center. @@ -449,9 +449,9 @@ static inline bool aabtri_check_normal_axis() CollisionContext.TestSide = 1.0f; } - leb0 = CollisionContext.Box->Extent.X * WWMath::Fabs(CollisionContext.AN[0]) + - CollisionContext.Box->Extent.Y * WWMath::Fabs(CollisionContext.AN[1]) + - CollisionContext.Box->Extent.Z * WWMath::Fabs(CollisionContext.AN[2]); + leb0 = CollisionContext.Box->Extent.X * WWMath::Fabsf_Legacy(CollisionContext.AN[0]) + + CollisionContext.Box->Extent.Y * WWMath::Fabsf_Legacy(CollisionContext.AN[1]) + + CollisionContext.Box->Extent.Z * WWMath::Fabsf_Legacy(CollisionContext.AN[2]); leb1 = leb0 + axismove; CollisionContext.TestPoint = 0; lp = dist; // this is the "optimization", don't have to find lp @@ -574,7 +574,7 @@ inline void VERIFY_CROSS(const Vector3 & a, const Vector3 & b,const Vector3 & cr Vector3 tmp_cross; Vector3::Cross_Product(a,b,&tmp_cross); Vector3 diff = cross - tmp_cross; - WWASSERT(WWMath::Fabs(diff.Length()) < 0.0001f); + WWASSERT(WWMath::Fabsf_Legacy(diff.Length()) < 0.0001f); #endif } @@ -650,7 +650,7 @@ bool CollisionMath::Collide CollisionContext.TestAxisId = AXIS_A0E0; if (CollisionContext.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = CollisionContext.AN[0]; - leb0 = box.Extent[1]*WWMath::Fabs(CollisionContext.AE[2][0]) + box.Extent[2]*WWMath::Fabs(CollisionContext.AE[1][0]); + leb0 = box.Extent[1]*WWMath::Fabsf_Legacy(CollisionContext.AE[2][0]) + box.Extent[2]*WWMath::Fabsf_Legacy(CollisionContext.AE[1][0]); if (aabtri_check_cross_axis(dp,2,leb0)) goto exit; } @@ -663,7 +663,7 @@ bool CollisionMath::Collide CollisionContext.TestAxisId = AXIS_A0E1; if (CollisionContext.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -CollisionContext.AN[0]; - leb0 = box.Extent[1]*WWMath::Fabs(CollisionContext.AE[2][1]) + box.Extent[2]*WWMath::Fabs(CollisionContext.AE[1][1]); + leb0 = box.Extent[1]*WWMath::Fabsf_Legacy(CollisionContext.AE[2][1]) + box.Extent[2]*WWMath::Fabsf_Legacy(CollisionContext.AE[1][1]); if (aabtri_check_cross_axis(dp,1,leb0)) goto exit; } @@ -681,7 +681,7 @@ bool CollisionMath::Collide if (CollisionContext.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -CollisionContext.AN[0]; - leb0 = box.Extent[1]*WWMath::Fabs(CollisionContext.AE[2][2]) + box.Extent[2]*WWMath::Fabs(CollisionContext.AE[1][2]); + leb0 = box.Extent[1]*WWMath::Fabsf_Legacy(CollisionContext.AE[2][2]) + box.Extent[2]*WWMath::Fabsf_Legacy(CollisionContext.AE[1][2]); if (aabtri_check_cross_axis(dp,1,leb0)) goto exit; } @@ -694,7 +694,7 @@ bool CollisionMath::Collide CollisionContext.TestAxisId = AXIS_A1E0; if (CollisionContext.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = CollisionContext.AN[1]; - leb0 = box.Extent[0]*WWMath::Fabs(CollisionContext.AE[2][0]) + box.Extent[2]*WWMath::Fabs(CollisionContext.AE[0][0]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(CollisionContext.AE[2][0]) + box.Extent[2]*WWMath::Fabsf_Legacy(CollisionContext.AE[0][0]); if (aabtri_check_cross_axis(dp,2,leb0)) goto exit; } @@ -707,7 +707,7 @@ bool CollisionMath::Collide CollisionContext.TestAxisId = AXIS_A1E1; if (CollisionContext.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -CollisionContext.AN[1]; - leb0 = box.Extent[0]*WWMath::Fabs(CollisionContext.AE[2][1]) + box.Extent[2]*WWMath::Fabs(CollisionContext.AE[0][1]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(CollisionContext.AE[2][1]) + box.Extent[2]*WWMath::Fabsf_Legacy(CollisionContext.AE[0][1]); if (aabtri_check_cross_axis(dp,1,leb0)) goto exit; } @@ -720,7 +720,7 @@ bool CollisionMath::Collide CollisionContext.TestAxisId = AXIS_A1E2; if (CollisionContext.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -CollisionContext.AN[1]; - leb0 = box.Extent[0]*WWMath::Fabs(CollisionContext.AE[2][2]) + box.Extent[2]*WWMath::Fabs(CollisionContext.AE[0][2]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(CollisionContext.AE[2][2]) + box.Extent[2]*WWMath::Fabsf_Legacy(CollisionContext.AE[0][2]); if (aabtri_check_cross_axis(dp,1,leb0)) goto exit; } @@ -733,7 +733,7 @@ bool CollisionMath::Collide CollisionContext.TestAxisId = AXIS_A2E0; if (CollisionContext.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = CollisionContext.AN[2]; - leb0 = box.Extent[0]*WWMath::Fabs(CollisionContext.AE[1][0]) + box.Extent[1]*WWMath::Fabs(CollisionContext.AE[0][0]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(CollisionContext.AE[1][0]) + box.Extent[1]*WWMath::Fabsf_Legacy(CollisionContext.AE[0][0]); if (aabtri_check_cross_axis(dp,2,leb0)) goto exit; } @@ -746,7 +746,7 @@ bool CollisionMath::Collide CollisionContext.TestAxisId = AXIS_A2E1; if (CollisionContext.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -CollisionContext.AN[2]; - leb0 = box.Extent[0]*WWMath::Fabs(CollisionContext.AE[1][1]) + box.Extent[1]*WWMath::Fabs(CollisionContext.AE[0][1]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(CollisionContext.AE[1][1]) + box.Extent[1]*WWMath::Fabsf_Legacy(CollisionContext.AE[0][1]); if (aabtri_check_cross_axis(dp,1,leb0)) goto exit; } @@ -759,7 +759,7 @@ bool CollisionMath::Collide CollisionContext.TestAxisId = AXIS_A2E2; if (CollisionContext.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -CollisionContext.AN[2]; - leb0 = box.Extent[0]*WWMath::Fabs(CollisionContext.AE[1][2]) + box.Extent[1]*WWMath::Fabs(CollisionContext.AE[0][2]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(CollisionContext.AE[1][2]) + box.Extent[1]*WWMath::Fabsf_Legacy(CollisionContext.AE[0][2]); if (aabtri_check_cross_axis(dp,1,leb0)) goto exit; } @@ -830,11 +830,11 @@ bool CollisionMath::Collide ** If this polygon cuts off more of the move -OR- this polygon cuts ** of the same amount but has a "better" normal, then use this normal */ - if ( (WWMath::Fabs(CollisionContext.MaxFrac - result->Fraction) > WWMATH_EPSILON) || + if ( (WWMath::Fabsf_Legacy(CollisionContext.MaxFrac - result->Fraction) > WWMATH_EPSILON) || (Vector3::Dot_Product(tmp_norm,move) < Vector3::Dot_Product(result->Normal,move))) { result->Normal = tmp_norm; - WWASSERT(WWMath::Fabs(result->Normal.Length() - 1.0f) < WWMATH_EPSILON); + WWASSERT(WWMath::Fabsf_Legacy(result->Normal.Length() - 1.0f) < WWMATH_EPSILON); } result->Fraction = CollisionContext.MaxFrac; @@ -1017,9 +1017,9 @@ static inline bool aabtri_intersect_normal_axis axis = -axis; } - leb0 = IntersectContext.Box->Extent.X * WWMath::Fabs(IntersectContext.AN[0]) + - IntersectContext.Box->Extent.Y * WWMath::Fabs(IntersectContext.AN[1]) + - IntersectContext.Box->Extent.Z * WWMath::Fabs(IntersectContext.AN[2]); + leb0 = IntersectContext.Box->Extent.X * WWMath::Fabsf_Legacy(IntersectContext.AN[0]) + + IntersectContext.Box->Extent.Y * WWMath::Fabsf_Legacy(IntersectContext.AN[1]) + + IntersectContext.Box->Extent.Z * WWMath::Fabsf_Legacy(IntersectContext.AN[2]); lp = dist; // this is the "optimization", don't have to find lp return (lp - leb0 > -WWMATH_EPSILON); @@ -1085,7 +1085,7 @@ bool CollisionMath::Intersection_Test(const AABoxClass & box,const TriClass & tr axis = IntersectContext.AxE[0][0]; if (axis.Length2() > AXISLEN_EPSILON2) { dp = IntersectContext.AN[0]; - leb0 = box.Extent[1]*WWMath::Fabs(IntersectContext.AE[2][0]) + box.Extent[2]*WWMath::Fabs(IntersectContext.AE[1][0]); + leb0 = box.Extent[1]*WWMath::Fabsf_Legacy(IntersectContext.AE[2][0]) + box.Extent[2]*WWMath::Fabsf_Legacy(IntersectContext.AE[1][0]); if (aabtri_intersect_cross_axis(axis,dp,leb0)) return false; } @@ -1096,7 +1096,7 @@ bool CollisionMath::Intersection_Test(const AABoxClass & box,const TriClass & tr axis = IntersectContext.AxE[0][1]; if (axis.Length2() > AXISLEN_EPSILON2) { dp = -IntersectContext.AN[0]; - leb0 = box.Extent[1]*WWMath::Fabs(IntersectContext.AE[2][1]) + box.Extent[2]*WWMath::Fabs(IntersectContext.AE[1][1]); + leb0 = box.Extent[1]*WWMath::Fabsf_Legacy(IntersectContext.AE[2][1]) + box.Extent[2]*WWMath::Fabsf_Legacy(IntersectContext.AE[1][1]); if (aabtri_intersect_cross_axis(axis,dp,leb0)) return false; } @@ -1109,7 +1109,7 @@ bool CollisionMath::Intersection_Test(const AABoxClass & box,const TriClass & tr IntersectContext.AE[2][2] = IntersectContext.E[2].Z; if (axis.Length2() > AXISLEN_EPSILON2) { dp = -IntersectContext.AN[0]; - leb0 = box.Extent[1]*WWMath::Fabs(IntersectContext.AE[2][2]) + box.Extent[2]*WWMath::Fabs(IntersectContext.AE[1][2]); + leb0 = box.Extent[1]*WWMath::Fabsf_Legacy(IntersectContext.AE[2][2]) + box.Extent[2]*WWMath::Fabsf_Legacy(IntersectContext.AE[1][2]); if (aabtri_intersect_cross_axis(axis,dp,leb0)) return false; } @@ -1120,7 +1120,7 @@ bool CollisionMath::Intersection_Test(const AABoxClass & box,const TriClass & tr axis = IntersectContext.AxE[1][0]; if (axis.Length2() > AXISLEN_EPSILON2) { dp = IntersectContext.AN[1]; - leb0 = box.Extent[0]*WWMath::Fabs(IntersectContext.AE[2][0]) + box.Extent[2]*WWMath::Fabs(IntersectContext.AE[0][0]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(IntersectContext.AE[2][0]) + box.Extent[2]*WWMath::Fabsf_Legacy(IntersectContext.AE[0][0]); if (aabtri_intersect_cross_axis(axis,dp,leb0)) return false; } @@ -1131,7 +1131,7 @@ bool CollisionMath::Intersection_Test(const AABoxClass & box,const TriClass & tr axis = IntersectContext.AxE[1][1]; if (axis.Length2() > AXISLEN_EPSILON2) { dp = -IntersectContext.AN[1]; - leb0 = box.Extent[0]*WWMath::Fabs(IntersectContext.AE[2][1]) + box.Extent[2]*WWMath::Fabs(IntersectContext.AE[0][1]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(IntersectContext.AE[2][1]) + box.Extent[2]*WWMath::Fabsf_Legacy(IntersectContext.AE[0][1]); if (aabtri_intersect_cross_axis(axis,dp,leb0)) return false; } @@ -1143,7 +1143,7 @@ bool CollisionMath::Intersection_Test(const AABoxClass & box,const TriClass & tr IntersectContext.AE[0][2] = IntersectContext.E[2].X; if (axis.Length2() > AXISLEN_EPSILON2) { dp = -IntersectContext.AN[1]; - leb0 = box.Extent[0]*WWMath::Fabs(IntersectContext.AE[2][2]) + box.Extent[2]*WWMath::Fabs(IntersectContext.AE[0][2]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(IntersectContext.AE[2][2]) + box.Extent[2]*WWMath::Fabsf_Legacy(IntersectContext.AE[0][2]); if (aabtri_intersect_cross_axis(axis,dp,leb0)) return false; } @@ -1154,7 +1154,7 @@ bool CollisionMath::Intersection_Test(const AABoxClass & box,const TriClass & tr axis = IntersectContext.AxE[2][0]; if (axis.Length2() > AXISLEN_EPSILON2) { dp = IntersectContext.AN[2]; - leb0 = box.Extent[0]*WWMath::Fabs(IntersectContext.AE[1][0]) + box.Extent[1]*WWMath::Fabs(IntersectContext.AE[0][0]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(IntersectContext.AE[1][0]) + box.Extent[1]*WWMath::Fabsf_Legacy(IntersectContext.AE[0][0]); if (aabtri_intersect_cross_axis(axis,dp,leb0)) return false; } @@ -1165,7 +1165,7 @@ bool CollisionMath::Intersection_Test(const AABoxClass & box,const TriClass & tr axis = IntersectContext.AxE[2][1]; if (axis.Length2() > AXISLEN_EPSILON2) { dp = -IntersectContext.AN[2]; - leb0 = box.Extent[0]*WWMath::Fabs(IntersectContext.AE[1][1]) + box.Extent[1]*WWMath::Fabs(IntersectContext.AE[0][1]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(IntersectContext.AE[1][1]) + box.Extent[1]*WWMath::Fabsf_Legacy(IntersectContext.AE[0][1]); if (aabtri_intersect_cross_axis(axis,dp,leb0)) return false; } @@ -1176,7 +1176,7 @@ bool CollisionMath::Intersection_Test(const AABoxClass & box,const TriClass & tr axis = IntersectContext.AxE[2][2]; if (axis.Length2() > AXISLEN_EPSILON2) { dp = -IntersectContext.AN[2]; - leb0 = box.Extent[0]*WWMath::Fabs(IntersectContext.AE[1][2]) + box.Extent[1]*WWMath::Fabs(IntersectContext.AE[0][2]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(IntersectContext.AE[1][2]) + box.Extent[1]*WWMath::Fabsf_Legacy(IntersectContext.AE[0][2]); if (aabtri_intersect_cross_axis(axis,dp,leb0)) return false; } diff --git a/Core/Libraries/Source/WWVegas/WWMath/colmathline.cpp b/Core/Libraries/Source/WWVegas/WWMath/colmathline.cpp index 726adee7e86..43be3a73a99 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/colmathline.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/colmathline.cpp @@ -253,7 +253,7 @@ bool CollisionMath::Collide(const LineSegClass & line,const SphereClass & sphere if (disc < 0.0f) { return false; } else { - float d = WWMath::Sqrt(disc); + float d = WWMath::Sqrt_Legacy(disc); float frac = (clen - d) / line.Get_Length(); if (frac<0.0f) frac = (clen + d) / line.Get_Length(); diff --git a/Core/Libraries/Source/WWVegas/WWMath/colmathobbobb.cpp b/Core/Libraries/Source/WWVegas/WWMath/colmathobbobb.cpp index d1e01f2522e..1fd97ad323b 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/colmathobbobb.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/colmathobbobb.cpp @@ -177,9 +177,9 @@ static bool obb_intersect_box0_basis // ra = box0 projection onto the axis // rb = box1 projection onto the axis float ra = context.Box0.Extent[axis_index]; - float rb = WWMath::Fabs(context.Box1.Extent[0]*context.AB[axis_index][0]) + - WWMath::Fabs(context.Box1.Extent[1]*context.AB[axis_index][1]) + - WWMath::Fabs(context.Box1.Extent[2]*context.AB[axis_index][2]); + float rb = WWMath::Fabsf_Legacy(context.Box1.Extent[0]*context.AB[axis_index][0]) + + WWMath::Fabsf_Legacy(context.Box1.Extent[1]*context.AB[axis_index][1]) + + WWMath::Fabsf_Legacy(context.Box1.Extent[2]*context.AB[axis_index][2]); float rsum = ra+rb; // u = projected distance between the box centers @@ -214,9 +214,9 @@ static bool obb_intersect_box1_basis { // ra = box0 projection onto the axis // rb = box1 projection onto the axis - float ra = WWMath::Fabs(context.Box0.Extent[0]*context.AB[0][axis_index]) + - WWMath::Fabs(context.Box0.Extent[1]*context.AB[1][axis_index]) + - WWMath::Fabs(context.Box0.Extent[2]*context.AB[2][axis_index]); + float ra = WWMath::Fabsf_Legacy(context.Box0.Extent[0]*context.AB[0][axis_index]) + + WWMath::Fabsf_Legacy(context.Box0.Extent[1]*context.AB[1][axis_index]) + + WWMath::Fabsf_Legacy(context.Box0.Extent[2]*context.AB[2][axis_index]); float rb = context.Box1.Extent[axis_index]; float rsum = ra+rb; @@ -340,8 +340,8 @@ bool intersect_obb_obb ///////////////////////////////////////////////////////////////////////// Vector3::Cross_Product(context.A[0],context.B[0],&axis); if (axis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[1]*context.AB[2][0])+WWMath::Fabs(context.Box0.Extent[2]*context.AB[1][0]); - rb = WWMath::Fabs(context.Box1.Extent[1]*context.AB[0][2])+WWMath::Fabs(context.Box1.Extent[2]*context.AB[0][1]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[1]*context.AB[2][0])+WWMath::Fabsf_Legacy(context.Box0.Extent[2]*context.AB[1][0]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[1]*context.AB[0][2])+WWMath::Fabsf_Legacy(context.Box1.Extent[2]*context.AB[0][1]); if (obb_intersect_axis(context,axis,ra,rb)) return false; } @@ -350,8 +350,8 @@ bool intersect_obb_obb ///////////////////////////////////////////////////////////////////////// Vector3::Cross_Product(context.A[0],context.B[1],&axis); if (axis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[1]*context.AB[2][1])+WWMath::Fabs(context.Box0.Extent[2]*context.AB[1][1]); - rb = WWMath::Fabs(context.Box1.Extent[0]*context.AB[0][2])+WWMath::Fabs(context.Box1.Extent[2]*context.AB[0][0]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[1]*context.AB[2][1])+WWMath::Fabsf_Legacy(context.Box0.Extent[2]*context.AB[1][1]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[0]*context.AB[0][2])+WWMath::Fabsf_Legacy(context.Box1.Extent[2]*context.AB[0][0]); if (obb_intersect_axis(context,axis,ra,rb)) return false; } @@ -360,8 +360,8 @@ bool intersect_obb_obb ///////////////////////////////////////////////////////////////////////// Vector3::Cross_Product(context.A[0],context.B[2],&axis); if (axis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[1]*context.AB[2][2])+WWMath::Fabs(context.Box0.Extent[2]*context.AB[1][2]); - rb = WWMath::Fabs(context.Box1.Extent[0]*context.AB[0][1])+WWMath::Fabs(context.Box1.Extent[1]*context.AB[0][0]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[1]*context.AB[2][2])+WWMath::Fabsf_Legacy(context.Box0.Extent[2]*context.AB[1][2]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[0]*context.AB[0][1])+WWMath::Fabsf_Legacy(context.Box1.Extent[1]*context.AB[0][0]); if (obb_intersect_axis(context,axis,ra,rb)) return false; } @@ -370,8 +370,8 @@ bool intersect_obb_obb ///////////////////////////////////////////////////////////////////////// Vector3::Cross_Product(context.A[1],context.B[0],&axis); if (axis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[0]*context.AB[2][0])+WWMath::Fabs(context.Box0.Extent[2]*context.AB[0][0]); - rb = WWMath::Fabs(context.Box1.Extent[1]*context.AB[1][2])+WWMath::Fabs(context.Box1.Extent[2]*context.AB[1][1]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[0]*context.AB[2][0])+WWMath::Fabsf_Legacy(context.Box0.Extent[2]*context.AB[0][0]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[1]*context.AB[1][2])+WWMath::Fabsf_Legacy(context.Box1.Extent[2]*context.AB[1][1]); if (obb_intersect_axis(context,axis,ra,rb)) return false; } @@ -380,8 +380,8 @@ bool intersect_obb_obb ///////////////////////////////////////////////////////////////////////// Vector3::Cross_Product(context.A[1],context.B[1],&axis); if (axis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[0]*context.AB[2][1])+WWMath::Fabs(context.Box0.Extent[2]*context.AB[0][1]); - rb = WWMath::Fabs(context.Box1.Extent[0]*context.AB[1][2])+WWMath::Fabs(context.Box1.Extent[2]*context.AB[1][0]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[0]*context.AB[2][1])+WWMath::Fabsf_Legacy(context.Box0.Extent[2]*context.AB[0][1]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[0]*context.AB[1][2])+WWMath::Fabsf_Legacy(context.Box1.Extent[2]*context.AB[1][0]); if (obb_intersect_axis(context,axis,ra,rb)) return false; } @@ -390,8 +390,8 @@ bool intersect_obb_obb ///////////////////////////////////////////////////////////////////////// Vector3::Cross_Product(context.A[1],context.B[2],&axis); if (axis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[0]*context.AB[2][2])+WWMath::Fabs(context.Box0.Extent[2]*context.AB[0][2]); - rb = WWMath::Fabs(context.Box1.Extent[0]*context.AB[1][1])+WWMath::Fabs(context.Box1.Extent[1]*context.AB[1][0]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[0]*context.AB[2][2])+WWMath::Fabsf_Legacy(context.Box0.Extent[2]*context.AB[0][2]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[0]*context.AB[1][1])+WWMath::Fabsf_Legacy(context.Box1.Extent[1]*context.AB[1][0]); if (obb_intersect_axis(context,axis,ra,rb)) return false; } @@ -400,8 +400,8 @@ bool intersect_obb_obb ///////////////////////////////////////////////////////////////////////// Vector3::Cross_Product(context.A[2],context.B[0],&axis); if (axis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[0]*context.AB[1][0])+WWMath::Fabs(context.Box0.Extent[1]*context.AB[0][0]); - rb = WWMath::Fabs(context.Box1.Extent[1]*context.AB[2][2])+WWMath::Fabs(context.Box1.Extent[2]*context.AB[2][1]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[0]*context.AB[1][0])+WWMath::Fabsf_Legacy(context.Box0.Extent[1]*context.AB[0][0]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[1]*context.AB[2][2])+WWMath::Fabsf_Legacy(context.Box1.Extent[2]*context.AB[2][1]); if (obb_intersect_axis(context,axis,ra,rb)) return false; } @@ -410,8 +410,8 @@ bool intersect_obb_obb ///////////////////////////////////////////////////////////////////////// Vector3::Cross_Product(context.A[2],context.B[1],&axis); if (axis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[0]*context.AB[1][1])+WWMath::Fabs(context.Box0.Extent[1]*context.AB[0][1]); - rb = WWMath::Fabs(context.Box1.Extent[0]*context.AB[2][2])+WWMath::Fabs(context.Box1.Extent[2]*context.AB[2][0]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[0]*context.AB[1][1])+WWMath::Fabsf_Legacy(context.Box0.Extent[1]*context.AB[0][1]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[0]*context.AB[2][2])+WWMath::Fabsf_Legacy(context.Box1.Extent[2]*context.AB[2][0]); if (obb_intersect_axis(context,axis,ra,rb)) return false; } @@ -420,8 +420,8 @@ bool intersect_obb_obb ///////////////////////////////////////////////////////////////////////// Vector3::Cross_Product(context.A[2],context.B[2],&axis); if (axis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[0]*context.AB[1][2])+WWMath::Fabs(context.Box0.Extent[1]*context.AB[0][2]); - rb = WWMath::Fabs(context.Box1.Extent[0]*context.AB[2][1])+WWMath::Fabs(context.Box1.Extent[1]*context.AB[2][0]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[0]*context.AB[1][2])+WWMath::Fabsf_Legacy(context.Box0.Extent[1]*context.AB[0][2]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[0]*context.AB[2][1])+WWMath::Fabsf_Legacy(context.Box1.Extent[1]*context.AB[2][0]); if (obb_intersect_axis(context,axis,ra,rb)) return false; } @@ -593,7 +593,7 @@ static inline bool obb_separation_test if ( u1 > rsum ) { context.MaxFrac = 1.0f; return true; - } else if (WWMath::Fabs(u1-u0) > 0.0f) { + } else if (WWMath::Fabsf_Legacy(u1-u0) > 0.0f) { tmp = (rsum-u0)/(u1-u0); if ( tmp > context.MaxFrac ) { context.MaxFrac = tmp; @@ -606,7 +606,7 @@ static inline bool obb_separation_test if ( u1 < -rsum ) { context.MaxFrac = 1.0f; return true; - } else if (WWMath::Fabs(u1-u0) > 0.0f) { + } else if (WWMath::Fabsf_Legacy(u1-u0) > 0.0f) { tmp = (-rsum-u0)/(u1-u0); if ( tmp > context.MaxFrac ) { context.MaxFrac = tmp; @@ -640,9 +640,9 @@ static bool obb_check_box0_basis // ra = box0 projection onto the axis // rb = box1 projection onto the axis float ra = context.Box0.Extent[axis_index]; - float rb = WWMath::Fabs(context.Box1.Extent[0]*context.AB[axis_index][0]) + - WWMath::Fabs(context.Box1.Extent[1]*context.AB[axis_index][1]) + - WWMath::Fabs(context.Box1.Extent[2]*context.AB[axis_index][2]); + float rb = WWMath::Fabsf_Legacy(context.Box1.Extent[0]*context.AB[axis_index][0]) + + WWMath::Fabsf_Legacy(context.Box1.Extent[1]*context.AB[axis_index][1]) + + WWMath::Fabsf_Legacy(context.Box1.Extent[2]*context.AB[axis_index][2]); // u0 = projected distance between the box centers at t0 // u1 = projected distance between the box centers at t1 @@ -673,9 +673,9 @@ static bool obb_check_box1_basis { // ra = box0 projection onto the axis // rb = box1 projection onto the axis - float ra = WWMath::Fabs(context.Box0.Extent[0]*context.AB[0][axis_index]) + - WWMath::Fabs(context.Box0.Extent[1]*context.AB[1][axis_index]) + - WWMath::Fabs(context.Box0.Extent[2]*context.AB[2][axis_index]); + float ra = WWMath::Fabsf_Legacy(context.Box0.Extent[0]*context.AB[0][axis_index]) + + WWMath::Fabsf_Legacy(context.Box0.Extent[1]*context.AB[1][axis_index]) + + WWMath::Fabsf_Legacy(context.Box0.Extent[2]*context.AB[2][axis_index]); float rb = context.Box1.Extent[axis_index]; // u0 = projected distance between the box centers at t0 @@ -730,13 +730,13 @@ static inline void obb_compute_projections float * rb ) { - *ra = context.Box0.Extent.X * WWMath::Fabs(Vector3::Dot_Product(context.A[0],context.TestAxis)) + - context.Box0.Extent.Y * WWMath::Fabs(Vector3::Dot_Product(context.A[1],context.TestAxis)) + - context.Box0.Extent.Z * WWMath::Fabs(Vector3::Dot_Product(context.A[2],context.TestAxis)); + *ra = context.Box0.Extent.X * WWMath::Fabsf_Legacy(Vector3::Dot_Product(context.A[0],context.TestAxis)) + + context.Box0.Extent.Y * WWMath::Fabsf_Legacy(Vector3::Dot_Product(context.A[1],context.TestAxis)) + + context.Box0.Extent.Z * WWMath::Fabsf_Legacy(Vector3::Dot_Product(context.A[2],context.TestAxis)); - *rb = context.Box1.Extent.X * WWMath::Fabs(Vector3::Dot_Product(context.B[0],context.TestAxis)) + - context.Box1.Extent.Y * WWMath::Fabs(Vector3::Dot_Product(context.B[1],context.TestAxis)) + - context.Box1.Extent.Z * WWMath::Fabs(Vector3::Dot_Product(context.B[2],context.TestAxis)); + *rb = context.Box1.Extent.X * WWMath::Fabsf_Legacy(Vector3::Dot_Product(context.B[0],context.TestAxis)) + + context.Box1.Extent.Y * WWMath::Fabsf_Legacy(Vector3::Dot_Product(context.B[1],context.TestAxis)) + + context.Box1.Extent.Z * WWMath::Fabsf_Legacy(Vector3::Dot_Product(context.B[2],context.TestAxis)); } @@ -923,7 +923,7 @@ static inline void compute_contact_point(ObbCollisionStruct & context,CastResult y[2] = eval_side(context.AB[0][1],context.Side) * context.Box1.Extent[2]; den = (1.0f - context.AB[0][0] * context.AB[0][0]); - if (WWMath::Fabs(den) > 0.0f) { + if (WWMath::Fabsf_Legacy(den) > 0.0f) { x[0] = Vector3::Dot_Product(context.A[0],dcnew); x[0] += context.AB[0][0] * (Vector3::Dot_Product(-context.B[0],dcnew) + context.AB[1][0]*x[1] + context.AB[2][0]*x[2]); x[0] += context.AB[0][1] * y[1] + context.AB[0][2] * y[2]; @@ -940,7 +940,7 @@ static inline void compute_contact_point(ObbCollisionStruct & context,CastResult y[2] = -eval_side(context.AB[0][0],context.Side) * context.Box1.Extent[2]; den = (1.0f - context.AB[0][1] * context.AB[0][1]); - if (WWMath::Fabs(den) > 0.0f) { + if (WWMath::Fabsf_Legacy(den) > 0.0f) { x[0] = Vector3::Dot_Product(context.A[0],dcnew); x[0] += context.AB[0][1] * (Vector3::Dot_Product(-context.B[1],dcnew) + context.AB[1][1]*x[1] + context.AB[2][1]*x[2]); x[0] += context.AB[0][0] * y[0] + context.AB[0][2] * y[2]; @@ -957,7 +957,7 @@ static inline void compute_contact_point(ObbCollisionStruct & context,CastResult y[1] = eval_side(context.AB[0][0],context.Side) * context.Box1.Extent[1]; den = (1.0f - context.AB[0][2] * context.AB[0][2]); - if (WWMath::Fabs(den) > 0.0f) { + if (WWMath::Fabsf_Legacy(den) > 0.0f) { x[0] = Vector3::Dot_Product(context.A[0],dcnew); x[0] += context.AB[0][2] * (Vector3::Dot_Product(-context.B[2],dcnew) + context.AB[1][2]*x[1] + context.AB[2][2]*x[2]); x[0] += context.AB[0][0] * y[0] + context.AB[0][1] * y[1]; @@ -974,7 +974,7 @@ static inline void compute_contact_point(ObbCollisionStruct & context,CastResult y[2] = eval_side(context.AB[1][1],context.Side) * context.Box1.Extent[2]; den = (1.0f - context.AB[1][0] * context.AB[1][0]); - if (WWMath::Fabs(den) > 0.0f) { + if (WWMath::Fabsf_Legacy(den) > 0.0f) { x[1] = Vector3::Dot_Product(context.A[1],dcnew); x[1] += context.AB[1][0] * (Vector3::Dot_Product(-context.B[0],dcnew) + context.AB[0][0]*x[0] + context.AB[2][0]*x[2]); x[1] += context.AB[1][1] * y[1] + context.AB[1][2] * y[2]; @@ -991,7 +991,7 @@ static inline void compute_contact_point(ObbCollisionStruct & context,CastResult y[2] = -eval_side(context.AB[1][0],context.Side) * context.Box1.Extent[2]; den = 1.0f / (1.0f - context.AB[1][1] * context.AB[1][1]); - if (WWMath::Fabs(den) > 0.0f) { + if (WWMath::Fabsf_Legacy(den) > 0.0f) { x[1] = Vector3::Dot_Product(context.A[1],dcnew); x[1] += context.AB[1][1] * (Vector3::Dot_Product(-context.B[1],dcnew) + context.AB[0][1]*x[0] + context.AB[2][1]*x[2]); x[1] += context.AB[1][0] * y[0] + context.AB[1][2] * y[2]; @@ -1008,7 +1008,7 @@ static inline void compute_contact_point(ObbCollisionStruct & context,CastResult y[1] = eval_side(context.AB[1][0],context.Side) * context.Box1.Extent[1]; den = (1.0f - context.AB[1][2] * context.AB[1][2]); - if (WWMath::Fabs(den) > 0.0f) { + if (WWMath::Fabsf_Legacy(den) > 0.0f) { x[1] = Vector3::Dot_Product(context.A[1],dcnew); x[1] += context.AB[1][2] * (Vector3::Dot_Product(-context.B[2],dcnew) + context.AB[0][2]*x[0] + context.AB[2][2]*x[2]); x[1] += context.AB[1][0] * y[0] + context.AB[1][1] * y[1]; @@ -1025,7 +1025,7 @@ static inline void compute_contact_point(ObbCollisionStruct & context,CastResult y[2] = eval_side(context.AB[2][1],context.Side) * context.Box1.Extent[2]; den = (1.0f - context.AB[2][0] * context.AB[2][0]); - if (WWMath::Fabs(den) > 0.0f) { + if (WWMath::Fabsf_Legacy(den) > 0.0f) { x[2] = Vector3::Dot_Product(context.A[2],dcnew); x[2] += context.AB[2][0] * (Vector3::Dot_Product(-context.B[0],dcnew) + context.AB[0][0]*x[0] + context.AB[1][0]*x[1]); x[2] += context.AB[2][1] * y[1] + context.AB[2][2] * y[2]; @@ -1042,7 +1042,7 @@ static inline void compute_contact_point(ObbCollisionStruct & context,CastResult y[2] = -eval_side(context.AB[2][0],context.Side) * context.Box1.Extent[2]; den = (1.0f - context.AB[2][1] * context.AB[2][1]); - if (WWMath::Fabs(den) > 0.0f) { + if (WWMath::Fabsf_Legacy(den) > 0.0f) { x[2] = Vector3::Dot_Product(context.A[2],dcnew); x[2] += context.AB[2][1] * (Vector3::Dot_Product(-context.B[1],dcnew) + context.AB[0][1]*x[0] + context.AB[1][1]*x[1]); x[2] += context.AB[2][0] * y[0] + context.AB[2][2] * y[2]; @@ -1059,7 +1059,7 @@ static inline void compute_contact_point(ObbCollisionStruct & context,CastResult y[1] = eval_side(context.AB[2][0],context.Side) * context.Box1.Extent[1]; den = (1.0f - context.AB[2][2] * context.AB[2][2]); - if (WWMath::Fabs(den) > 0.0f) { + if (WWMath::Fabsf_Legacy(den) > 0.0f) { x[2] = Vector3::Dot_Product(context.A[2],dcnew); x[2] += context.AB[2][2] * (Vector3::Dot_Product(-context.B[2],dcnew) + context.AB[0][2]*x[0] + context.AB[1][2]*x[1]); x[2] += context.AB[2][0] * y[0] + context.AB[2][1] * y[1]; @@ -1177,8 +1177,8 @@ bool collide_obb_obb Vector3::Cross_Product(context.A[0],context.B[0],&context.TestAxis); context.TestAxisId = AXIS_A0B0; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[1]*context.AB[2][0])+WWMath::Fabs(context.Box0.Extent[2]*context.AB[1][0]); - rb = WWMath::Fabs(context.Box1.Extent[1]*context.AB[0][2])+WWMath::Fabs(context.Box1.Extent[2]*context.AB[0][1]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[1]*context.AB[2][0])+WWMath::Fabsf_Legacy(context.Box0.Extent[2]*context.AB[1][0]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[1]*context.AB[0][2])+WWMath::Fabsf_Legacy(context.Box1.Extent[2]*context.AB[0][1]); if (obb_check_axis(context,ra,rb)) goto exit; } @@ -1188,8 +1188,8 @@ bool collide_obb_obb Vector3::Cross_Product(context.A[0],context.B[1],&context.TestAxis); context.TestAxisId = AXIS_A0B1; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[1]*context.AB[2][1])+WWMath::Fabs(context.Box0.Extent[2]*context.AB[1][1]); - rb = WWMath::Fabs(context.Box1.Extent[0]*context.AB[0][2])+WWMath::Fabs(context.Box1.Extent[2]*context.AB[0][0]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[1]*context.AB[2][1])+WWMath::Fabsf_Legacy(context.Box0.Extent[2]*context.AB[1][1]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[0]*context.AB[0][2])+WWMath::Fabsf_Legacy(context.Box1.Extent[2]*context.AB[0][0]); if (obb_check_axis(context,ra,rb)) goto exit; } @@ -1199,8 +1199,8 @@ bool collide_obb_obb Vector3::Cross_Product(context.A[0],context.B[2],&context.TestAxis); context.TestAxisId = AXIS_A0B2; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[1]*context.AB[2][2])+WWMath::Fabs(context.Box0.Extent[2]*context.AB[1][2]); - rb = WWMath::Fabs(context.Box1.Extent[0]*context.AB[0][1])+WWMath::Fabs(context.Box1.Extent[1]*context.AB[0][0]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[1]*context.AB[2][2])+WWMath::Fabsf_Legacy(context.Box0.Extent[2]*context.AB[1][2]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[0]*context.AB[0][1])+WWMath::Fabsf_Legacy(context.Box1.Extent[1]*context.AB[0][0]); if (obb_check_axis(context,ra,rb)) goto exit; } @@ -1210,8 +1210,8 @@ bool collide_obb_obb Vector3::Cross_Product(context.A[1],context.B[0],&context.TestAxis); context.TestAxisId = AXIS_A1B0; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[0]*context.AB[2][0])+WWMath::Fabs(context.Box0.Extent[2]*context.AB[0][0]); - rb = WWMath::Fabs(context.Box1.Extent[1]*context.AB[1][2])+WWMath::Fabs(context.Box1.Extent[2]*context.AB[1][1]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[0]*context.AB[2][0])+WWMath::Fabsf_Legacy(context.Box0.Extent[2]*context.AB[0][0]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[1]*context.AB[1][2])+WWMath::Fabsf_Legacy(context.Box1.Extent[2]*context.AB[1][1]); if (obb_check_axis(context,ra,rb)) goto exit; } @@ -1221,8 +1221,8 @@ bool collide_obb_obb Vector3::Cross_Product(context.A[1],context.B[1],&context.TestAxis); context.TestAxisId = AXIS_A1B1; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[0]*context.AB[2][1])+WWMath::Fabs(context.Box0.Extent[2]*context.AB[0][1]); - rb = WWMath::Fabs(context.Box1.Extent[0]*context.AB[1][2])+WWMath::Fabs(context.Box1.Extent[2]*context.AB[1][0]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[0]*context.AB[2][1])+WWMath::Fabsf_Legacy(context.Box0.Extent[2]*context.AB[0][1]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[0]*context.AB[1][2])+WWMath::Fabsf_Legacy(context.Box1.Extent[2]*context.AB[1][0]); if (obb_check_axis(context,ra,rb)) goto exit; } @@ -1232,8 +1232,8 @@ bool collide_obb_obb Vector3::Cross_Product(context.A[1],context.B[2],&context.TestAxis); context.TestAxisId = AXIS_A1B2; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[0]*context.AB[2][2])+WWMath::Fabs(context.Box0.Extent[2]*context.AB[0][2]); - rb = WWMath::Fabs(context.Box1.Extent[0]*context.AB[1][1])+WWMath::Fabs(context.Box1.Extent[1]*context.AB[1][0]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[0]*context.AB[2][2])+WWMath::Fabsf_Legacy(context.Box0.Extent[2]*context.AB[0][2]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[0]*context.AB[1][1])+WWMath::Fabsf_Legacy(context.Box1.Extent[1]*context.AB[1][0]); if (obb_check_axis(context,ra,rb)) goto exit; } @@ -1243,8 +1243,8 @@ bool collide_obb_obb Vector3::Cross_Product(context.A[2],context.B[0],&context.TestAxis); context.TestAxisId = AXIS_A2B0; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[0]*context.AB[1][0])+WWMath::Fabs(context.Box0.Extent[1]*context.AB[0][0]); - rb = WWMath::Fabs(context.Box1.Extent[1]*context.AB[2][2])+WWMath::Fabs(context.Box1.Extent[2]*context.AB[2][1]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[0]*context.AB[1][0])+WWMath::Fabsf_Legacy(context.Box0.Extent[1]*context.AB[0][0]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[1]*context.AB[2][2])+WWMath::Fabsf_Legacy(context.Box1.Extent[2]*context.AB[2][1]); if (obb_check_axis(context,ra,rb)) goto exit; } @@ -1254,8 +1254,8 @@ bool collide_obb_obb Vector3::Cross_Product(context.A[2],context.B[1],&context.TestAxis); context.TestAxisId = AXIS_A2B1; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[0]*context.AB[1][1])+WWMath::Fabs(context.Box0.Extent[1]*context.AB[0][1]); - rb = WWMath::Fabs(context.Box1.Extent[0]*context.AB[2][2])+WWMath::Fabs(context.Box1.Extent[2]*context.AB[2][0]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[0]*context.AB[1][1])+WWMath::Fabsf_Legacy(context.Box0.Extent[1]*context.AB[0][1]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[0]*context.AB[2][2])+WWMath::Fabsf_Legacy(context.Box1.Extent[2]*context.AB[2][0]); if (obb_check_axis(context,ra,rb)) goto exit; } @@ -1265,8 +1265,8 @@ bool collide_obb_obb Vector3::Cross_Product(context.A[2],context.B[2],&context.TestAxis); context.TestAxisId = AXIS_A2B2; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { - ra = WWMath::Fabs(context.Box0.Extent[0]*context.AB[1][2])+WWMath::Fabs(context.Box0.Extent[1]*context.AB[0][2]); - rb = WWMath::Fabs(context.Box1.Extent[0]*context.AB[2][1])+WWMath::Fabs(context.Box1.Extent[1]*context.AB[2][0]); + ra = WWMath::Fabsf_Legacy(context.Box0.Extent[0]*context.AB[1][2])+WWMath::Fabsf_Legacy(context.Box0.Extent[1]*context.AB[0][2]); + rb = WWMath::Fabsf_Legacy(context.Box1.Extent[0]*context.AB[2][1])+WWMath::Fabsf_Legacy(context.Box1.Extent[1]*context.AB[2][0]); if (obb_check_axis(context,ra,rb)) goto exit; } diff --git a/Core/Libraries/Source/WWVegas/WWMath/colmathobbox.cpp b/Core/Libraries/Source/WWVegas/WWMath/colmathobbox.cpp index c7a71d2b87d..2368738e4ce 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/colmathobbox.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/colmathobbox.cpp @@ -59,13 +59,13 @@ CollisionMath::Overlap_Test(const OBBoxClass & box,const Vector3 & point) Matrix3x3::Transpose_Rotate_Vector(box.Basis,(point - box.Center),&localpoint); // if the point is outside any of the extents, it is outside the box - if (WWMath::Fabs(localpoint.X) > box.Extent.X) { + if (WWMath::Fabsf_Legacy(localpoint.X) > box.Extent.X) { return OUTSIDE; } - if (WWMath::Fabs(localpoint.Y) > box.Extent.Y) { + if (WWMath::Fabsf_Legacy(localpoint.Y) > box.Extent.Y) { return OUTSIDE; } - if (WWMath::Fabs(localpoint.Z) > box.Extent.Z) { + if (WWMath::Fabsf_Legacy(localpoint.Z) > box.Extent.Z) { return OUTSIDE; } return INSIDE; diff --git a/Core/Libraries/Source/WWVegas/WWMath/colmathobbtri.cpp b/Core/Libraries/Source/WWVegas/WWMath/colmathobbtri.cpp index 18ea575286c..4cdab9251be 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/colmathobbtri.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/colmathobbtri.cpp @@ -313,9 +313,9 @@ static inline bool obbtri_check_collision_axis(BTCollisionStruct & context) } // compute coordinates of the leading edge of the box at t0 and t1 - leb0 = context.Box.Extent.X * WWMath::Fabs(Vector3::Dot_Product(context.TestAxis,context.A[0])) + - context.Box.Extent.Y * WWMath::Fabs(Vector3::Dot_Product(context.TestAxis,context.A[1])) + - context.Box.Extent.Z * WWMath::Fabs(Vector3::Dot_Product(context.TestAxis,context.A[2])); + leb0 = context.Box.Extent.X * WWMath::Fabsf_Legacy(Vector3::Dot_Product(context.TestAxis,context.A[0])) + + context.Box.Extent.Y * WWMath::Fabsf_Legacy(Vector3::Dot_Product(context.TestAxis,context.A[1])) + + context.Box.Extent.Z * WWMath::Fabsf_Legacy(Vector3::Dot_Product(context.TestAxis,context.A[2])); leb1 = leb0 + axismove; // compute coordinate of "leading edge of the triangle" relative to the box center. @@ -479,9 +479,9 @@ static inline bool obbtri_check_collision_normal_axis(BTCollisionStruct & contex context.TestSide = 1.0f; } - leb0 = context.Box.Extent.X * WWMath::Fabs(context.AN[0]) + - context.Box.Extent.Y * WWMath::Fabs(context.AN[1]) + - context.Box.Extent.Z * WWMath::Fabs(context.AN[2]); + leb0 = context.Box.Extent.X * WWMath::Fabsf_Legacy(context.AN[0]) + + context.Box.Extent.Y * WWMath::Fabsf_Legacy(context.AN[1]) + + context.Box.Extent.Z * WWMath::Fabsf_Legacy(context.AN[2]); leb1 = leb0 + axismove; context.TestPoint = 0; lp = dist; // this is the "optimization", don't have to find lp @@ -610,7 +610,7 @@ static inline void eval_A0_point(const BTCollisionStruct & context,float * x,int if (context.Point == 0) { yval = 0.0f; } else { yval = 1.0f; } den = Vector3::Dot_Product(context.N,context.AxE[0][edge]); - if (WWMath::Fabs(den) > 0.0f) { + if (WWMath::Fabsf_Legacy(den) > 0.0f) { Vector3::Cross_Product(context.FinalD,context.E[edge],&DxE); x[0] = Vector3::Dot_Product(context.N,DxE); @@ -653,7 +653,7 @@ static inline void eval_A1_point(const BTCollisionStruct & context,float * x,int if (context.Point == 0) { yval = 0.0f; } else { yval = 1.0f; } den = Vector3::Dot_Product(context.N,context.AxE[1][edge]); - if (WWMath::Fabs(den) > 0.0f) { + if (WWMath::Fabsf_Legacy(den) > 0.0f) { Vector3::Cross_Product(context.FinalD,context.E[edge],&DxE); x[1] = Vector3::Dot_Product(context.N,DxE); @@ -695,7 +695,7 @@ static inline void eval_A2_point(const BTCollisionStruct & context,float * x,int if (context.Point == 0) { yval = 0.0f; } else { yval = 1.0f; } den = Vector3::Dot_Product(context.N,context.AxE[2][edge]); - if (WWMath::Fabs(den) > 0.0f) { + if (WWMath::Fabsf_Legacy(den) > 0.0f) { Vector3::Cross_Product(context.FinalD,context.E[edge],&DxE); x[2] = Vector3::Dot_Product(context.N,DxE); @@ -937,7 +937,7 @@ bool CollisionMath::Collide context.TestAxisId = AXIS_A0E0; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = context.AN[0]; - leb0 = box.Extent[1]*WWMath::Fabs(context.AE[2][0]) + box.Extent[2]*WWMath::Fabs(context.AE[1][0]); + leb0 = box.Extent[1]*WWMath::Fabsf_Legacy(context.AE[2][0]) + box.Extent[2]*WWMath::Fabsf_Legacy(context.AE[1][0]); if (obbtri_check_collision_cross_axis(context,dp,2,leb0)) goto exit; } @@ -949,7 +949,7 @@ bool CollisionMath::Collide context.TestAxisId = AXIS_A0E1; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -context.AN[0]; - leb0 = box.Extent[1]*WWMath::Fabs(context.AE[2][1]) + box.Extent[2]*WWMath::Fabs(context.AE[1][1]); + leb0 = box.Extent[1]*WWMath::Fabsf_Legacy(context.AE[2][1]) + box.Extent[2]*WWMath::Fabsf_Legacy(context.AE[1][1]); if (obbtri_check_collision_cross_axis(context,dp,1,leb0)) goto exit; } @@ -963,7 +963,7 @@ bool CollisionMath::Collide context.AE[2][2] = Vector3::Dot_Product(context.A[2],context.E[2]); if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -context.AN[0]; - leb0 = box.Extent[1]*WWMath::Fabs(context.AE[2][2]) + box.Extent[2]*WWMath::Fabs(context.AE[1][2]); + leb0 = box.Extent[1]*WWMath::Fabsf_Legacy(context.AE[2][2]) + box.Extent[2]*WWMath::Fabsf_Legacy(context.AE[1][2]); if (obbtri_check_collision_cross_axis(context,dp,1,leb0)) goto exit; } @@ -975,7 +975,7 @@ bool CollisionMath::Collide context.TestAxisId = AXIS_A1E0; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = context.AN[1]; - leb0 = box.Extent[0]*WWMath::Fabs(context.AE[2][0]) + box.Extent[2]*WWMath::Fabs(context.AE[0][0]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(context.AE[2][0]) + box.Extent[2]*WWMath::Fabsf_Legacy(context.AE[0][0]); if (obbtri_check_collision_cross_axis(context,dp,2,leb0)) goto exit; } @@ -987,7 +987,7 @@ bool CollisionMath::Collide context.TestAxisId = AXIS_A1E1; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -context.AN[1]; - leb0 = box.Extent[0]*WWMath::Fabs(context.AE[2][1]) + box.Extent[2]*WWMath::Fabs(context.AE[0][1]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(context.AE[2][1]) + box.Extent[2]*WWMath::Fabsf_Legacy(context.AE[0][1]); if (obbtri_check_collision_cross_axis(context,dp,1,leb0)) goto exit; } @@ -1000,7 +1000,7 @@ bool CollisionMath::Collide context.AE[0][2] = Vector3::Dot_Product(context.A[0],context.E[2]); if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -context.AN[1]; - leb0 = box.Extent[0]*WWMath::Fabs(context.AE[2][2]) + box.Extent[2]*WWMath::Fabs(context.AE[0][2]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(context.AE[2][2]) + box.Extent[2]*WWMath::Fabsf_Legacy(context.AE[0][2]); if (obbtri_check_collision_cross_axis(context,dp,1,leb0)) goto exit; } @@ -1012,7 +1012,7 @@ bool CollisionMath::Collide context.TestAxisId = AXIS_A2E0; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = context.AN[2]; - leb0 = box.Extent[0]*WWMath::Fabs(context.AE[1][0]) + box.Extent[1]*WWMath::Fabs(context.AE[0][0]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(context.AE[1][0]) + box.Extent[1]*WWMath::Fabsf_Legacy(context.AE[0][0]); if (obbtri_check_collision_cross_axis(context,dp,2,leb0)) goto exit; } @@ -1024,7 +1024,7 @@ bool CollisionMath::Collide context.TestAxisId = AXIS_A2E1; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -context.AN[2]; - leb0 = box.Extent[0]*WWMath::Fabs(context.AE[1][1]) + box.Extent[1]*WWMath::Fabs(context.AE[0][1]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(context.AE[1][1]) + box.Extent[1]*WWMath::Fabsf_Legacy(context.AE[0][1]); if (obbtri_check_collision_cross_axis(context,dp,1,leb0)) goto exit; } @@ -1036,7 +1036,7 @@ bool CollisionMath::Collide context.TestAxisId = AXIS_A2E2; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -context.AN[2]; - leb0 = box.Extent[0]*WWMath::Fabs(context.AE[1][2]) + box.Extent[1]*WWMath::Fabs(context.AE[0][2]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(context.AE[1][2]) + box.Extent[1]*WWMath::Fabsf_Legacy(context.AE[0][2]); if (obbtri_check_collision_cross_axis(context,dp,1,leb0)) goto exit; } @@ -1098,7 +1098,7 @@ bool CollisionMath::Collide Vector3 normal; obbtri_compute_contact_normal(context,&normal); - if ( (WWMath::Fabs(context.MaxFrac - result->Fraction) > WWMATH_EPSILON) || + if ( (WWMath::Fabsf_Legacy(context.MaxFrac - result->Fraction) > WWMATH_EPSILON) || (Vector3::Dot_Product(normal,move) < Vector3::Dot_Product(result->Normal,move)) ) { result->Normal = normal; //obbtri_compute_contact_normal(context,result); @@ -1337,9 +1337,9 @@ static inline bool obbtri_check_intersection_normal_axis dist = -dist; } - leb0 = context.Box.Extent.X * WWMath::Fabs(context.AN[0]) + - context.Box.Extent.Y * WWMath::Fabs(context.AN[1]) + - context.Box.Extent.Z * WWMath::Fabs(context.AN[2]); + leb0 = context.Box.Extent.X * WWMath::Fabsf_Legacy(context.AN[0]) + + context.Box.Extent.Y * WWMath::Fabsf_Legacy(context.AN[1]) + + context.Box.Extent.Z * WWMath::Fabsf_Legacy(context.AN[2]); lp = dist; // this is the "optimization", don't have to find lp return obbtri_intersection_separation_test(context,lp,leb0); @@ -1408,7 +1408,7 @@ bool CollisionMath::Intersection_Test(const OBBoxClass & box,const TriClass & tr context.TestAxis = context.AxE[0][0]; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = context.AN[0]; - leb0 = box.Extent[1]*WWMath::Fabs(context.AE[2][0]) + box.Extent[2]*WWMath::Fabs(context.AE[1][0]); + leb0 = box.Extent[1]*WWMath::Fabsf_Legacy(context.AE[2][0]) + box.Extent[2]*WWMath::Fabsf_Legacy(context.AE[1][0]); if (obbtri_check_intersection_cross_axis(context,dp,leb0)) return false; } @@ -1419,7 +1419,7 @@ bool CollisionMath::Intersection_Test(const OBBoxClass & box,const TriClass & tr context.TestAxis = context.AxE[0][1]; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -context.AN[0]; - leb0 = box.Extent[1]*WWMath::Fabs(context.AE[2][1]) + box.Extent[2]*WWMath::Fabs(context.AE[1][1]); + leb0 = box.Extent[1]*WWMath::Fabsf_Legacy(context.AE[2][1]) + box.Extent[2]*WWMath::Fabsf_Legacy(context.AE[1][1]); if (obbtri_check_intersection_cross_axis(context,dp,leb0)) return false; } @@ -1432,7 +1432,7 @@ bool CollisionMath::Intersection_Test(const OBBoxClass & box,const TriClass & tr context.AE[2][2] = Vector3::Dot_Product(context.A[2],context.E[2]); if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -context.AN[0]; - leb0 = box.Extent[1]*WWMath::Fabs(context.AE[2][2]) + box.Extent[2]*WWMath::Fabs(context.AE[1][2]); + leb0 = box.Extent[1]*WWMath::Fabsf_Legacy(context.AE[2][2]) + box.Extent[2]*WWMath::Fabsf_Legacy(context.AE[1][2]); if (obbtri_check_intersection_cross_axis(context,dp,leb0)) return false; } @@ -1443,7 +1443,7 @@ bool CollisionMath::Intersection_Test(const OBBoxClass & box,const TriClass & tr context.TestAxis = context.AxE[1][0]; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = context.AN[1]; - leb0 = box.Extent[0]*WWMath::Fabs(context.AE[2][0]) + box.Extent[2]*WWMath::Fabs(context.AE[0][0]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(context.AE[2][0]) + box.Extent[2]*WWMath::Fabsf_Legacy(context.AE[0][0]); if (obbtri_check_intersection_cross_axis(context,dp,leb0)) return false; } @@ -1454,7 +1454,7 @@ bool CollisionMath::Intersection_Test(const OBBoxClass & box,const TriClass & tr context.TestAxis = context.AxE[1][1]; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -context.AN[1]; - leb0 = box.Extent[0]*WWMath::Fabs(context.AE[2][1]) + box.Extent[2]*WWMath::Fabs(context.AE[0][1]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(context.AE[2][1]) + box.Extent[2]*WWMath::Fabsf_Legacy(context.AE[0][1]); if (obbtri_check_intersection_cross_axis(context,dp,leb0)) return false; } @@ -1466,7 +1466,7 @@ bool CollisionMath::Intersection_Test(const OBBoxClass & box,const TriClass & tr context.AE[0][2] = Vector3::Dot_Product(context.A[0],context.E[2]); if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -context.AN[1]; - leb0 = box.Extent[0]*WWMath::Fabs(context.AE[2][2]) + box.Extent[2]*WWMath::Fabs(context.AE[0][2]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(context.AE[2][2]) + box.Extent[2]*WWMath::Fabsf_Legacy(context.AE[0][2]); if (obbtri_check_intersection_cross_axis(context,dp,leb0)) return false; } @@ -1477,7 +1477,7 @@ bool CollisionMath::Intersection_Test(const OBBoxClass & box,const TriClass & tr context.TestAxis = context.AxE[2][0]; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = context.AN[2]; - leb0 = box.Extent[0]*WWMath::Fabs(context.AE[1][0]) + box.Extent[1]*WWMath::Fabs(context.AE[0][0]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(context.AE[1][0]) + box.Extent[1]*WWMath::Fabsf_Legacy(context.AE[0][0]); if (obbtri_check_intersection_cross_axis(context,dp,leb0)) return false; } @@ -1488,7 +1488,7 @@ bool CollisionMath::Intersection_Test(const OBBoxClass & box,const TriClass & tr context.TestAxis = context.AxE[2][1]; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -context.AN[2]; - leb0 = box.Extent[0]*WWMath::Fabs(context.AE[1][1]) + box.Extent[1]*WWMath::Fabs(context.AE[0][1]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(context.AE[1][1]) + box.Extent[1]*WWMath::Fabsf_Legacy(context.AE[0][1]); if (obbtri_check_intersection_cross_axis(context,dp,leb0)) return false; } @@ -1499,7 +1499,7 @@ bool CollisionMath::Intersection_Test(const OBBoxClass & box,const TriClass & tr context.TestAxis = context.AxE[2][2]; if (context.TestAxis.Length2() > AXISLEN_EPSILON2) { dp = -context.AN[2]; - leb0 = box.Extent[0]*WWMath::Fabs(context.AE[1][2]) + box.Extent[1]*WWMath::Fabs(context.AE[0][2]); + leb0 = box.Extent[0]*WWMath::Fabsf_Legacy(context.AE[1][2]) + box.Extent[1]*WWMath::Fabsf_Legacy(context.AE[0][2]); if (obbtri_check_intersection_cross_axis(context,dp,leb0)) return false; } diff --git a/Core/Libraries/Source/WWVegas/WWMath/colmathsphere.cpp b/Core/Libraries/Source/WWVegas/WWMath/colmathsphere.cpp index fb024348cea..84e8cf1e4af 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/colmathsphere.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/colmathsphere.cpp @@ -75,9 +75,9 @@ bool CollisionMath::Intersection_Test(const SphereClass & sphere,const AABoxClas ** against a cube which encloses the sphere... */ Vector3 dc = box.Center - sphere.Center; - if (WWMath::Fabs(dc.X) < box.Extent.X + sphere.Radius) return false; - if (WWMath::Fabs(dc.Y) < box.Extent.Y + sphere.Radius) return false; - if (WWMath::Fabs(dc.Z) < box.Extent.Z + sphere.Radius) return false; + if (WWMath::Fabsf_Legacy(dc.X) < box.Extent.X + sphere.Radius) return false; + if (WWMath::Fabsf_Legacy(dc.Y) < box.Extent.Y + sphere.Radius) return false; + if (WWMath::Fabsf_Legacy(dc.Z) < box.Extent.Z + sphere.Radius) return false; return true; } @@ -103,9 +103,9 @@ bool CollisionMath::Intersection_Test(const SphereClass & sphere,const OBBoxClas Vector3 box_rel_center; Matrix3D::Inverse_Transform_Vector(tm,sphere.Center,&box_rel_center); - if (box.Extent.X < WWMath::Fabs(box_rel_center.X)) return false; - if (box.Extent.Y < WWMath::Fabs(box_rel_center.Y)) return false; - if (box.Extent.Z < WWMath::Fabs(box_rel_center.Z)) return false; + if (box.Extent.X < WWMath::Fabsf_Legacy(box_rel_center.X)) return false; + if (box.Extent.Y < WWMath::Fabsf_Legacy(box_rel_center.Y)) return false; + if (box.Extent.Z < WWMath::Fabsf_Legacy(box_rel_center.Z)) return false; return true; } diff --git a/Core/Libraries/Source/WWVegas/WWMath/euler.cpp b/Core/Libraries/Source/WWVegas/WWMath/euler.cpp index 5cf23f242b4..9685ec39705 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/euler.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/euler.cpp @@ -180,35 +180,35 @@ void EulerAnglesClass::From_Matrix(const Matrix3D & M, int order) _euler_unpack_order(order,i,j,k,h,n,s,f); if (s == EULER_REPEAT_YES) { - double sy = sqrt(M[i][j]*M[i][j] + M[i][k]*M[i][k]); + double sy = WWMath::Sqrt(M[i][j]*M[i][j] + M[i][k]*M[i][k]); if (sy > 16*FLT_EPSILON) { - Angle[0] = WWMath::Atan2(M[i][j],M[i][k]); - Angle[1] = WWMath::Atan2(sy,M[i][i]); - Angle[2] = WWMath::Atan2(M[j][i],-M[k][i]); + Angle[0] = WWMath::Atan2_Legacy(M[i][j],M[i][k]); + Angle[1] = WWMath::Atan2_Legacy(sy,M[i][i]); + Angle[2] = WWMath::Atan2_Legacy(M[j][i],-M[k][i]); } else { - Angle[0] = WWMath::Atan2(-M[j][k],M[j][j]); - Angle[1] = WWMath::Atan2(sy,M[i][i]); + Angle[0] = WWMath::Atan2_Legacy(-M[j][k],M[j][j]); + Angle[1] = WWMath::Atan2_Legacy(sy,M[i][i]); Angle[2] = 0.0; } } else { - double cy = sqrt(M[i][i]*M[i][i] + M[j][i]*M[j][i]); + double cy = WWMath::Sqrt(M[i][i]*M[i][i] + M[j][i]*M[j][i]); if (cy > 16*FLT_EPSILON) { - Angle[0] = WWMath::Atan2(M[k][j],M[k][k]); - Angle[1] = WWMath::Atan2(-M[k][i],cy); - Angle[2] = WWMath::Atan2(M[j][i],M[i][i]); + Angle[0] = WWMath::Atan2_Legacy(M[k][j],M[k][k]); + Angle[1] = WWMath::Atan2_Legacy(-M[k][i],cy); + Angle[2] = WWMath::Atan2_Legacy(M[j][i],M[i][i]); } else { - Angle[0] = WWMath::Atan2(-M[j][k],M[j][j]); - Angle[1] = WWMath::Atan2(-M[k][i],cy); + Angle[0] = WWMath::Atan2_Legacy(-M[j][k],M[j][j]); + Angle[1] = WWMath::Atan2_Legacy(-M[k][i],cy); Angle[2] = 0; } } @@ -284,8 +284,8 @@ void EulerAnglesClass::To_Matrix(Matrix3D & M) } ti = a0; tj = a1; th = a2; - ci = WWMath::Cos(ti); cj = WWMath::Cos(tj); ch = WWMath::Cos(th); - si = WWMath::Sin(ti); sj = WWMath::Sin(tj); sh = WWMath::Sin(th); + ci = WWMath::Cosf_Legacy(ti); cj = WWMath::Cosf_Legacy(tj); ch = WWMath::Cosf_Legacy(th); + si = WWMath::Sinf_Legacy(ti); sj = WWMath::Sinf_Legacy(tj); sh = WWMath::Sinf_Legacy(th); cc = ci*ch; cs = ci*sh; diff --git a/Core/Libraries/Source/WWVegas/WWMath/legacyd3dmatrix.h b/Core/Libraries/Source/WWVegas/WWMath/legacyd3dmatrix.h new file mode 100644 index 00000000000..e52e1cda74f --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WWMath/legacyd3dmatrix.h @@ -0,0 +1,41 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** 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 +** MERCHANTIBILITY 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 + +#if defined(GGC_RENDER_BACKEND_BGFX) +#ifndef D3DMATRIX_DEFINED +typedef struct _D3DMATRIX +{ + union + { + struct + { + float _11, _12, _13, _14; + float _21, _22, _23, _24; + float _31, _32, _33, _34; + float _41, _42, _43, _44; + }; + float m[4][4]; + }; +} D3DMATRIX; +#define D3DMATRIX_DEFINED +#endif +#else +#include +#endif diff --git a/Core/Libraries/Source/WWVegas/WWMath/lookuptable.h b/Core/Libraries/Source/WWVegas/WWMath/lookuptable.h index 5cba6a0e2c2..0eb6d064c35 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/lookuptable.h +++ b/Core/Libraries/Source/WWVegas/WWMath/lookuptable.h @@ -86,7 +86,7 @@ inline float LookupTableClass::Get_Value(float input) } float normalized_input = (float)(OutputSamples.Length()-1) * (input - MinInputValue) * OOMaxMinusMin; - float input0 = WWMath::Floor(normalized_input); + float input0 = WWMath::Floorf(normalized_input); int index0 = WWMath::Float_To_Long(input0); int index1 = index0+1; diff --git a/Core/Libraries/Source/WWVegas/WWMath/matrix3.cpp b/Core/Libraries/Source/WWVegas/WWMath/matrix3.cpp index fa85c2396bf..c0b15e44186 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/matrix3.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/matrix3.cpp @@ -338,9 +338,9 @@ int Matrix3x3::Is_Orthogonal() const if (Vector3::Dot_Product(y,z) > WWMATH_EPSILON) return 0; if (Vector3::Dot_Product(z,x) > WWMATH_EPSILON) return 0; - if (WWMath::Fabs(x.Length() - 1.0f) > WWMATH_EPSILON) return 0; - if (WWMath::Fabs(y.Length() - 1.0f) > WWMATH_EPSILON) return 0; - if (WWMath::Fabs(z.Length() - 1.0f) > WWMATH_EPSILON) return 0; + if (WWMath::Fabsf_Legacy(x.Length() - 1.0f) > WWMATH_EPSILON) return 0; + if (WWMath::Fabsf_Legacy(y.Length() - 1.0f) > WWMATH_EPSILON) return 0; + if (WWMath::Fabsf_Legacy(z.Length() - 1.0f) > WWMATH_EPSILON) return 0; return 1; } diff --git a/Core/Libraries/Source/WWVegas/WWMath/matrix3.h b/Core/Libraries/Source/WWVegas/WWMath/matrix3.h index 22fb3bbb5e7..004923f700f 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/matrix3.h +++ b/Core/Libraries/Source/WWVegas/WWMath/matrix3.h @@ -361,12 +361,12 @@ WWINLINE Matrix3x3::Matrix3x3(const Vector3 & axis,float s_angle,float c_angle) WWINLINE void Matrix3x3::Set(const Vector3 & axis,float angle) { - Set(axis,sinf(angle),cosf(angle)); + Set(axis,WWMath::Sinf(angle),WWMath::Cosf(angle)); } WWINLINE void Matrix3x3::Set(const Vector3 & axis,float s,float c) { - WWASSERT(WWMath::Fabs(axis.Length2() - 1.0f) < 0.001f); + WWASSERT(WWMath::Fabsf_Legacy(axis.Length2() - 1.0f) < 0.001f); Row[0].Set( (float)(axis[0]*axis[0] + c*(1.0f - axis[0]*axis[0])), @@ -437,7 +437,7 @@ WWINLINE Matrix3x3 Matrix3x3::Inverse() const // Gauss-Jordan elimination wit // Find largest pivot in column j among rows j..3 i1 = j; for (i=j+1; i<3; i++) { - if (WWMath::Fabs(a[i][j]) > WWMath::Fabs(a[i1][j])) { + if (WWMath::Fabsf_Legacy(a[i][j]) > WWMath::Fabsf_Legacy(a[i1][j])) { i1 = i; } } @@ -589,7 +589,7 @@ WWINLINE Matrix3x3& Matrix3x3::operator /= (float d) WWINLINE float Matrix3x3::Get_X_Rotation() const { Vector3 v = (*this) * Vector3(0.0,1.0,0.0); - return WWMath::Atan2(v[2], v[1]); + return WWMath::Atan2_Legacy(v[2], v[1]); } /*********************************************************************************************** @@ -607,7 +607,7 @@ WWINLINE float Matrix3x3::Get_X_Rotation() const WWINLINE float Matrix3x3::Get_Y_Rotation() const { Vector3 v = (*this) * Vector3(0.0,0.0,1.0); - return WWMath::Atan2(v[0],v[2]); + return WWMath::Atan2_Legacy(v[0],v[2]); } /*********************************************************************************************** @@ -625,7 +625,7 @@ WWINLINE float Matrix3x3::Get_Y_Rotation() const WWINLINE float Matrix3x3::Get_Z_Rotation() const { Vector3 v = (*this) * Vector3(1.0,0.0,0.0); - return WWMath::Atan2(v[1],v[0]); + return WWMath::Atan2_Legacy(v[1],v[0]); } WWINLINE Vector3 Matrix3x3::Get_X_Vector() const @@ -775,7 +775,7 @@ WWINLINE int operator != (const Matrix3x3 & a, const Matrix3x3 & b) *=============================================================================================*/ WWINLINE void Matrix3x3::Rotate_X(float theta) { - Rotate_X(sinf(theta),cosf(theta)); + Rotate_X(WWMath::Sinf(theta),WWMath::Cosf(theta)); } WWINLINE void Matrix3x3::Rotate_X(float s,float c) @@ -809,7 +809,7 @@ WWINLINE void Matrix3x3::Rotate_X(float s,float c) *=============================================================================================*/ WWINLINE void Matrix3x3::Rotate_Y(float theta) { - Rotate_Y(sinf(theta),cosf(theta)); + Rotate_Y(WWMath::Sinf(theta),WWMath::Cosf(theta)); } WWINLINE void Matrix3x3::Rotate_Y(float s,float c) @@ -844,7 +844,7 @@ WWINLINE void Matrix3x3::Rotate_Y(float s,float c) *=============================================================================================*/ WWINLINE void Matrix3x3::Rotate_Z(float theta) { - Rotate_Z(sinf(theta),cosf(theta)); + Rotate_Z(WWMath::Sinf(theta),WWMath::Cosf(theta)); } WWINLINE void Matrix3x3::Rotate_Z(float s,float c) @@ -898,7 +898,7 @@ WWINLINE Matrix3x3 Create_X_Rotation_Matrix3(float s,float c) WWINLINE Matrix3x3 Create_X_Rotation_Matrix3(float rad) { - return Create_X_Rotation_Matrix3(sinf(rad),cosf(rad)); + return Create_X_Rotation_Matrix3(WWMath::Sinf(rad),WWMath::Cosf(rad)); } /*********************************************************************************************** @@ -934,7 +934,7 @@ WWINLINE Matrix3x3 Create_Y_Rotation_Matrix3(float s,float c) WWINLINE Matrix3x3 Create_Y_Rotation_Matrix3(float rad) { - return Create_Y_Rotation_Matrix3(sinf(rad),cosf(rad)); + return Create_Y_Rotation_Matrix3(WWMath::Sinf(rad),WWMath::Cosf(rad)); } /*********************************************************************************************** @@ -970,7 +970,7 @@ WWINLINE Matrix3x3 Create_Z_Rotation_Matrix3(float s,float c) WWINLINE Matrix3x3 Create_Z_Rotation_Matrix3(float rad) { - return Create_Z_Rotation_Matrix3(sinf(rad),cosf(rad)); + return Create_Z_Rotation_Matrix3(WWMath::Sinf(rad),WWMath::Cosf(rad)); } WWINLINE void Matrix3x3::Rotate_Vector(const Matrix3x3 & A,const Vector3 & in,Vector3 * out) @@ -1018,7 +1018,7 @@ WWINLINE void Matrix3x3::Rotate_AABox_Extent(const Vector3 & extent,Vector3 * se (*set_extent)[i] = 0.0f; for (int j=0; j<3; j++) { - (*set_extent)[i] += WWMath::Fabs(Row[i][j] * extent[j]); + (*set_extent)[i] += WWMath::Fabsf_Legacy(Row[i][j] * extent[j]); } } } diff --git a/Core/Libraries/Source/WWVegas/WWMath/matrix3d.cpp b/Core/Libraries/Source/WWVegas/WWMath/matrix3d.cpp index 5f2c886884a..45b707282cb 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/matrix3d.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/matrix3d.cpp @@ -66,8 +66,12 @@ #include "quat.h" #include "WWLib/win.h" -#include +#include "legacyd3dmatrix.h" +#if defined(GGC_RENDER_BACKEND_BGFX) +struct D3DXMATRIX : public D3DMATRIX {}; +#else #include +#endif // some static matrices which are sometimes useful const Matrix3D Matrix3D::Identity @@ -248,7 +252,7 @@ void Matrix3D::Set_Rotation(const Quaternion & q) *=============================================================================================*/ float Matrix3D::Get_X_Rotation() const { - return WWMath::Atan2(Row[2][1], Row[1][1]); + return WWMath::Atan2_Legacy(Row[2][1], Row[1][1]); } @@ -266,7 +270,7 @@ float Matrix3D::Get_X_Rotation() const *=============================================================================================*/ float Matrix3D::Get_Y_Rotation() const { - return WWMath::Atan2(Row[0][2], Row[2][2]); + return WWMath::Atan2_Legacy(Row[0][2], Row[2][2]); } @@ -284,7 +288,7 @@ float Matrix3D::Get_Y_Rotation() const *=============================================================================================*/ float Matrix3D::Get_Z_Rotation() const { - return WWMath::Atan2(Row[1][0], Row[0][0]); + return WWMath::Atan2_Legacy(Row[1][0], Row[0][0]); } @@ -372,7 +376,7 @@ void Matrix3D::Look_At_Dir(const Vector3 &pos, const Vector3 &dir, float roll) float dz = dir.Z; // length of projection onto XY plane - float len2 = (float)WWMath::Sqrt(dx*dx + dy*dy); + float len2 = (float)WWMath::Sqrt_Legacy(dx*dx + dy*dy); // pitch sinp = dz; @@ -414,7 +418,7 @@ void Matrix3D::buildTransformMatrix( const Vector3 &pos, const Vector3 &dir ) float sinp, cosp; // sine and cosine of the pitch ("up-down" tilt about y) float siny, cosy; // sine and cosine of the yaw ("left-right"tilt about z) - float len2 = (float)sqrt( (dir.X * dir.X) + (dir.Y * dir.Y) ); + float len2 = (float)WWMath::Sqrt( (dir.X * dir.X) + (dir.Y * dir.Y) ); sinp = dir.Z; cosp = len2; @@ -472,8 +476,8 @@ void Matrix3D::Obj_Look_At(const Vector3 &p,const Vector3 &t,float roll) dy = (t[1] - p[1]); dz = (t[2] - p[2]); - len1 = (float)sqrt(dx*dx + dy*dy + dz*dz); - len2 = (float)sqrt(dx*dx + dy*dy); + len1 = (float)WWMath::Sqrt(dx*dx + dy*dy + dz*dz); + len2 = (float)WWMath::Sqrt(dx*dx + dy*dy); if (len1 != 0.0f) { sinp = dz/len1; @@ -552,7 +556,7 @@ Matrix3D * Matrix3D::Get_Inverse(Matrix3D * out, float * detOut, const Matrix3D if (detOut) *detOut = det; - if (fabsf(det) < 1e-8f) + if (WWMath::Fabsf(det) < 1e-8f) return NULL; const float invDet = 1.0f / det; @@ -1121,7 +1125,7 @@ void Matrix3D::Transform_Center_Extent_AABox for (int j=0; j<3; j++) { (*set_center)[i] += Row[i][j] * center[j]; - (*set_extent)[i] += WWMath::Fabs(Row[i][j] * extent[j]); + (*set_extent)[i] += WWMath::Fabsf_Legacy(Row[i][j] * extent[j]); } } @@ -1150,9 +1154,9 @@ int Matrix3D::Is_Orthogonal() const if (Vector3::Dot_Product(y,z) > WWMATH_EPSILON) return 0; if (Vector3::Dot_Product(z,x) > WWMATH_EPSILON) return 0; - if (WWMath::Fabs(x.Length2() - 1.0f) > WWMATH_EPSILON) return 0; - if (WWMath::Fabs(y.Length2() - 1.0f) > WWMATH_EPSILON) return 0; - if (WWMath::Fabs(z.Length2() - 1.0f) > WWMATH_EPSILON) return 0; + if (WWMath::Fabsf_Legacy(x.Length2() - 1.0f) > WWMATH_EPSILON) return 0; + if (WWMath::Fabsf_Legacy(y.Length2() - 1.0f) > WWMATH_EPSILON) return 0; + if (WWMath::Fabsf_Legacy(z.Length2() - 1.0f) > WWMATH_EPSILON) return 0; return 1; } diff --git a/Core/Libraries/Source/WWVegas/WWMath/matrix3d.h b/Core/Libraries/Source/WWVegas/WWMath/matrix3d.h index e9e5c976183..55368684c66 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/matrix3d.h +++ b/Core/Libraries/Source/WWVegas/WWMath/matrix3d.h @@ -566,8 +566,8 @@ WWINLINE void Matrix3D::Set( const Vector3 &x, // x-axis unit vector *=============================================================================================*/ WWINLINE void Matrix3D::Set(const Vector3 & axis,float angle) { - float c = cosf(angle); - float s = sinf(angle); + float c = WWMath::Cosf(angle); + float s = WWMath::Sinf(angle); Set(axis,s,c); } @@ -586,7 +586,7 @@ WWINLINE void Matrix3D::Set(const Vector3 & axis,float angle) *=============================================================================================*/ WWINLINE void Matrix3D::Set(const Vector3 & axis,float s,float c) { - assert(WWMath::Fabs(axis.Length2() - 1.0f) < 0.001f); + assert(WWMath::Fabsf_Legacy(axis.Length2() - 1.0f) < 0.001f); Row[0].Set( (float)(axis[0]*axis[0] + c*(1.0f - axis[0]*axis[0])), @@ -768,8 +768,8 @@ WWINLINE void Matrix3D::Rotate_X(float theta) float tmp1,tmp2; float s,c; - s = sinf(theta); - c = cosf(theta); + s = WWMath::Sinf(theta); + c = WWMath::Cosf(theta); tmp1 = Row[0][1]; tmp2 = Row[0][2]; Row[0][1] = (float)( c*tmp1 + s*tmp2); @@ -836,8 +836,8 @@ WWINLINE void Matrix3D::Rotate_Y(float theta) float tmp1,tmp2; float s,c; - s = sinf(theta); - c = cosf(theta); + s = WWMath::Sinf(theta); + c = WWMath::Cosf(theta); tmp1 = Row[0][0]; tmp2 = Row[0][2]; Row[0][0] = (float)(c*tmp1 - s*tmp2); @@ -903,8 +903,8 @@ WWINLINE void Matrix3D::Rotate_Z(float theta) float tmp1,tmp2; float c,s; - c = cosf(theta); - s = sinf(theta); + c = WWMath::Cosf(theta); + s = WWMath::Sinf(theta); tmp1 = Row[0][0]; tmp2 = Row[0][1]; Row[0][0] = (float)( c*tmp1 + s*tmp2); @@ -1055,8 +1055,8 @@ WWINLINE void Matrix3D::Pre_Rotate_X(float theta) float tmp1,tmp2; float c,s; - c = cosf(theta); - s = sinf(theta); + c = WWMath::Cosf(theta); + s = WWMath::Sinf(theta); tmp1 = Row[1][0]; tmp2 = Row[2][0]; Row[1][0] = (float)(c*tmp1 - s*tmp2); @@ -1093,8 +1093,8 @@ WWINLINE void Matrix3D::Pre_Rotate_Y(float theta) float tmp1,tmp2; float c,s; - c = cosf(theta); - s = sinf(theta); + c = WWMath::Cosf(theta); + s = WWMath::Sinf(theta); tmp1 = Row[0][0]; tmp2 = Row[2][0]; Row[0][0] = (float)( c*tmp1 + s*tmp2); @@ -1131,8 +1131,8 @@ WWINLINE void Matrix3D::Pre_Rotate_Z(float theta) float tmp1,tmp2; float c,s; - c = cosf(theta); - s = sinf(theta); + c = WWMath::Cosf(theta); + s = WWMath::Sinf(theta); tmp1 = Row[0][0]; tmp2 = Row[1][0]; Row[0][0] = (float)(c*tmp1 - s*tmp2); @@ -1274,8 +1274,8 @@ WWINLINE void Matrix3D::In_Place_Pre_Rotate_X(float theta) float tmp1,tmp2; float c,s; - c = cosf(theta); - s = sinf(theta); + c = WWMath::Cosf(theta); + s = WWMath::Sinf(theta); tmp1 = Row[1][0]; tmp2 = Row[2][0]; Row[1][0] = (float)(c*tmp1 - s*tmp2); @@ -1308,8 +1308,8 @@ WWINLINE void Matrix3D::In_Place_Pre_Rotate_Y(float theta) float tmp1,tmp2; float c,s; - c = cosf(theta); - s = sinf(theta); + c = WWMath::Cosf(theta); + s = WWMath::Sinf(theta); tmp1 = Row[0][0]; tmp2 = Row[2][0]; Row[0][0] = (float)( c*tmp1 + s*tmp2); @@ -1342,8 +1342,8 @@ WWINLINE void Matrix3D::In_Place_Pre_Rotate_Z(float theta) float tmp1,tmp2; float c,s; - c = cosf(theta); - s = sinf(theta); + c = WWMath::Cosf(theta); + s = WWMath::Sinf(theta); tmp1 = Row[0][0]; tmp2 = Row[1][0]; Row[0][0] = (float)(c*tmp1 - s*tmp2); diff --git a/Core/Libraries/Source/WWVegas/WWMath/matrix4.cpp b/Core/Libraries/Source/WWVegas/WWMath/matrix4.cpp index 0b489a40c72..547b1674e38 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/matrix4.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/matrix4.cpp @@ -44,8 +44,12 @@ #include #include "WWLib/win.h" -#include +#include "legacyd3dmatrix.h" +#if defined(GGC_RENDER_BACKEND_BGFX) +struct D3DXMATRIX : public D3DMATRIX {}; +#else #include +#endif /*********************************************************************************************** * Matrix4x4::Multiply -- Multiply two Matrix4x4's together * diff --git a/Core/Libraries/Source/WWVegas/WWMath/matrix4.h b/Core/Libraries/Source/WWVegas/WWMath/matrix4.h index 626d441d4e9..834c1341a5b 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/matrix4.h +++ b/Core/Libraries/Source/WWVegas/WWMath/matrix4.h @@ -574,7 +574,7 @@ WWINLINE Matrix4x4* Matrix4x4::Inverse(Matrix4x4* out, float* detOut, const Matr if (detOut) *detOut = det; - if (fabsf(det) < 1e-8f) + if (WWMath::Fabsf(det) < 1e-8f) return NULL; const float invDet = 1.0f / det; diff --git a/Core/Libraries/Source/WWVegas/WWMath/obbox.cpp b/Core/Libraries/Source/WWVegas/WWMath/obbox.cpp index 68aa7c48456..b6f82984c17 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/obbox.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/obbox.cpp @@ -161,13 +161,13 @@ OBBoxClass::OBBoxClass(const Vector3 * /*points*/, int /*n*/) float dx = pt[i].x - box.center.x; float dy = pt[i].y - box.center.y; float dz = pt[i].z - box.center.z; - float absdot = float(WWMath::Fabs(U.x*dx+U.y*dy+U.z*dz)); + float absdot = float(WWMath::Fabsf(U.x*dx+U.y*dy+U.z*dz)); if ( absdot > amax ) amax = absdot; - absdot = float(WWMath::Fabs(V.x*dx+V.y*dy+V.z*dz)); + absdot = float(WWMath::Fabsf(V.x*dx+V.y*dy+V.z*dz)); if ( absdot > bmax ) bmax = absdot; - absdot = float(WWMath::Fabs(W.x*dx+W.y*dy+W.z*dz)); + absdot = float(WWMath::Fabsf(W.x*dx+W.y*dy+W.z*dz)); if ( absdot > cmax ) cmax = absdot; } @@ -264,13 +264,13 @@ void OBBoxClass::Init_From_Box_Points(Vector3 * points,int num) float dy = points[i].Y - Center.Y; float dz = points[i].Z - Center.Z; - float xprj = float(WWMath::Fabs(axis0.X * dx + axis0.Y * dy + axis0.Z * dz)); + float xprj = float(WWMath::Fabsf_Legacy(axis0.X * dx + axis0.Y * dy + axis0.Z * dz)); if (xprj > Extent.X) Extent.X = xprj; - float yprj = float(WWMath::Fabs(axis1.X * dx + axis1.Y * dy + axis1.Z * dz)); + float yprj = float(WWMath::Fabsf_Legacy(axis1.X * dx + axis1.Y * dy + axis1.Z * dz)); if (yprj > Extent.Y) Extent.Y = yprj; - float zprj = float(WWMath::Fabs(axis2.X * dx + axis2.Y * dy + axis2.Z * dz)); + float zprj = float(WWMath::Fabsf_Legacy(axis2.X * dx + axis2.Y * dy + axis2.Z * dz)); if (zprj > Extent.Z) Extent.Z = zprj; } } @@ -334,7 +334,7 @@ bool Oriented_Boxes_Intersect_On_Axis ra = box0.Project_To_Axis(axis); rb = box1.Project_To_Axis(axis); - rsum = WWMath::Fabs(ra) + WWMath::Fabs(rb); + rsum = WWMath::Fabsf_Legacy(ra) + WWMath::Fabsf_Legacy(rb); // project the center distance onto the line: Vector3 C = box1.Center - box0.Center; @@ -456,7 +456,7 @@ bool Oriented_Boxes_Collide_On_Axis ra = box0.Project_To_Axis(axis); rb = box1.Project_To_Axis(axis); - rsum = WWMath::Fabs(ra) + WWMath::Fabs(rb); + rsum = WWMath::Fabsf_Legacy(ra) + WWMath::Fabsf_Legacy(rb); // project the center distance onto the line: Vector3 C = box1.Center - box0.Center; diff --git a/Core/Libraries/Source/WWVegas/WWMath/obbox.h b/Core/Libraries/Source/WWVegas/WWMath/obbox.h index 83c3ff2686b..5199059c14d 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/obbox.h +++ b/Core/Libraries/Source/WWVegas/WWMath/obbox.h @@ -136,7 +136,7 @@ inline float OBBoxClass::Project_To_Axis(const Vector3 & axis) const float z = Extent[2] * Vector3::Dot_Product(axis,Vector3(Basis[0][2],Basis[1][2],Basis[2][2])); // projection is the sum of the absolute values of the projections of the three extents - return (WWMath::Fabs(x) + WWMath::Fabs(y) + WWMath::Fabs(z)); + return (WWMath::Fabsf_Legacy(x) + WWMath::Fabsf_Legacy(y) + WWMath::Fabsf_Legacy(z)); } @@ -214,17 +214,17 @@ inline void OBBoxClass::Compute_Axis_Aligned_Extent(Vector3 * set_extent) const WWASSERT(set_extent != nullptr); // x extent is the box projected onto the x axis - set_extent->X = WWMath::Fabs(Extent[0] * Basis[0][0]) + - WWMath::Fabs(Extent[1] * Basis[0][1]) + - WWMath::Fabs(Extent[2] * Basis[0][2]); + set_extent->X = WWMath::Fabsf_Legacy(Extent[0] * Basis[0][0]) + + WWMath::Fabsf_Legacy(Extent[1] * Basis[0][1]) + + WWMath::Fabsf_Legacy(Extent[2] * Basis[0][2]); - set_extent->Y = WWMath::Fabs(Extent[0] * Basis[1][0]) + - WWMath::Fabs(Extent[1] * Basis[1][1]) + - WWMath::Fabs(Extent[2] * Basis[1][2]); + set_extent->Y = WWMath::Fabsf_Legacy(Extent[0] * Basis[1][0]) + + WWMath::Fabsf_Legacy(Extent[1] * Basis[1][1]) + + WWMath::Fabsf_Legacy(Extent[2] * Basis[1][2]); - set_extent->Z = WWMath::Fabs(Extent[0] * Basis[2][0]) + - WWMath::Fabs(Extent[1] * Basis[2][1]) + - WWMath::Fabs(Extent[2] * Basis[2][2]); + set_extent->Z = WWMath::Fabsf_Legacy(Extent[0] * Basis[2][0]) + + WWMath::Fabsf_Legacy(Extent[1] * Basis[2][1]) + + WWMath::Fabsf_Legacy(Extent[2] * Basis[2][2]); } diff --git a/Core/Libraries/Source/WWVegas/WWMath/quat.cpp b/Core/Libraries/Source/WWVegas/WWMath/quat.cpp index d262891f412..cc119ac72d6 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/quat.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/quat.cpp @@ -87,8 +87,8 @@ static float project_to_sphere(float,float,float); *=============================================================================================*/ Quaternion::Quaternion(const Vector3 & axis,float angle) { - float s = WWMath::Sin(angle/2); - float c = WWMath::Cos(angle/2); + float s = WWMath::Sinf_Legacy(angle/2); + float c = WWMath::Cosf_Legacy(angle/2); X = s * axis.X; Y = s * axis.Y; Z = s * axis.Z; @@ -114,7 +114,7 @@ void Quaternion::Normalize() if (0.0f == len2) { return; } else { - float inv_mag = WWMath::Inv_Sqrt(len2); + float inv_mag = WWMath::Inv_Sqrt_Legacy(len2); X *= inv_mag; Y *= inv_mag; @@ -200,7 +200,7 @@ Quaternion Trackball(float x0, float y0, float x1, float y1, float sphsize) // Avoid problems with out of control values if (t > 1.0f) t = 1.0f; if (t < -1.0f) t = -1.0f; - phi = 2.0f * WWMath::Asin(t); + phi = 2.0f * WWMath::Asin_Legacy(t); return Axis_To_Quat(a, phi); } @@ -228,8 +228,8 @@ Quaternion Axis_To_Quat(const Vector3 &a, float phi) q[1] = tmp[1]; q[2] = tmp[2]; - q.Scale(WWMath::Sin(phi / 2.0f)); - q[3] = WWMath::Cos(phi / 2.0f); + q.Scale(WWMath::Sinf_Legacy(phi / 2.0f)); + q[3] = WWMath::Cosf_Legacy(phi / 2.0f); return q; } @@ -324,11 +324,11 @@ void __cdecl Fast_Slerp(Quaternion& res, const Quaternion & p,const Quaternion & // ---------------------------------------------------------------------------- // normal slerp! // else { -// theta = WWMath::Acos(cos_t); -// sin_t = WWMath::Sin(theta); +// theta = WWMath::Acos_Legacy(cos_t); +// sin_t = WWMath::Sinf_Legacy(theta); // oo_sin_t = 1.0 / sin_t; -// beta = WWMath::Sin(theta - alpha*theta) * oo_sin_t; -// alpha = WWMath::Sin(alpha*theta) * oo_sin_t; +// beta = WWMath::Sinf_Legacy(theta - alpha*theta) * oo_sin_t; +// alpha = WWMath::Sinf_Legacy(alpha*theta) * oo_sin_t; // } // if (qflip) { // alpha = -alpha; @@ -512,11 +512,11 @@ void Slerp(Quaternion& res, const Quaternion & p,const Quaternion & q,float alph } else { // normal slerp! - theta = WWMath::Acos(cos_t); - float sin_t = WWMath::Sin(theta); + theta = WWMath::Acos_Legacy(cos_t); + float sin_t = WWMath::Sinf_Legacy(theta); oo_sin_t = 1.0f / sin_t; - beta = WWMath::Sin(theta - alpha*theta) * oo_sin_t; - alpha = WWMath::Sin(alpha*theta) * oo_sin_t; + beta = WWMath::Sinf_Legacy(theta - alpha*theta) * oo_sin_t; + alpha = WWMath::Sinf_Legacy(alpha*theta) * oo_sin_t; } if (qflip) { @@ -567,8 +567,8 @@ void Slerp_Setup(const Quaternion & p,const Quaternion & q,SlerpInfoStruct * sle } else { slerpinfo->Linear = false; - slerpinfo->Theta = WWMath::Acos(cos_t); - slerpinfo->SinT = WWMath::Sin(slerpinfo->Theta); + slerpinfo->Theta = WWMath::Acos_Legacy(cos_t); + slerpinfo->SinT = WWMath::Sinf_Legacy(slerpinfo->Theta); } } @@ -600,8 +600,8 @@ Quaternion Cached_Slerp(const Quaternion & p,const Quaternion & q,float alpha,Sl // normal slerp! oo_sin_t = 1.0f / slerpinfo->Theta; - beta = WWMath::Sin(slerpinfo->Theta - alpha*slerpinfo->Theta) * oo_sin_t; - alpha = WWMath::Sin(alpha*slerpinfo->Theta) * oo_sin_t; + beta = WWMath::Sinf_Legacy(slerpinfo->Theta - alpha*slerpinfo->Theta) * oo_sin_t; + alpha = WWMath::Sinf_Legacy(alpha*slerpinfo->Theta) * oo_sin_t; } if (slerpinfo->Flip) { @@ -632,8 +632,8 @@ void Cached_Slerp(const Quaternion & p,const Quaternion & q,float alpha,SlerpInf // normal slerp! oo_sin_t = 1.0f / slerpinfo->Theta; - beta = WWMath::Sin(slerpinfo->Theta - alpha*slerpinfo->Theta) * oo_sin_t; - alpha = WWMath::Sin(alpha*slerpinfo->Theta) * oo_sin_t; + beta = WWMath::Sinf_Legacy(slerpinfo->Theta - alpha*slerpinfo->Theta) * oo_sin_t; + alpha = WWMath::Sinf_Legacy(alpha*slerpinfo->Theta) * oo_sin_t; } if (slerpinfo->Flip) { @@ -670,7 +670,7 @@ Quaternion Build_Quaternion(const Matrix3D & mat) if (tr > 0.0f) { - s = sqrt(tr + 1.0); + s = WWMath::Sqrt(tr + 1.0); q[3] = s * 0.5; s = 0.5 / s; @@ -686,7 +686,7 @@ Quaternion Build_Quaternion(const Matrix3D & mat) j = _nxt[i]; k = _nxt[j]; - s = sqrt((mat[i][i] - (mat[j][j] + mat[k][k])) + 1.0); + s = WWMath::Sqrt((mat[i][i] - (mat[j][j] + mat[k][k])) + 1.0); q[i] = s * 0.5; if (s != 0.0) { @@ -713,7 +713,7 @@ Quaternion Build_Quaternion(const Matrix3x3 & mat) if (tr > 0.0) { - s = sqrt(tr + 1.0); + s = WWMath::Sqrt(tr + 1.0); q[3] = s * 0.5; s = 0.5 / s; @@ -730,7 +730,7 @@ Quaternion Build_Quaternion(const Matrix3x3 & mat) j = _nxt[i]; k = _nxt[j]; - s = sqrt( (mat[i][i] - (mat[j][j]+mat[k][k])) + 1.0); + s = WWMath::Sqrt( (mat[i][i] - (mat[j][j]+mat[k][k])) + 1.0); q[i] = s * 0.5; @@ -757,7 +757,7 @@ Quaternion Build_Quaternion(const Matrix4x4 & mat) if (tr > 0.0) { - s = sqrt(tr + 1.0); + s = WWMath::Sqrt(tr + 1.0); q[3] = s * 0.5; s = 0.5 / s; @@ -774,7 +774,7 @@ Quaternion Build_Quaternion(const Matrix4x4 & mat) j = _nxt[i]; k = _nxt[j]; - s = sqrt( (mat[i][i] - (mat[j][j]+mat[k][k])) + 1.0); + s = WWMath::Sqrt( (mat[i][i] - (mat[j][j]+mat[k][k])) + 1.0); q[i] = s * 0.5; if (s != 0.0) { @@ -868,10 +868,10 @@ float project_to_sphere(float r, float x, float y) { const float SQRT2 = 1.41421356f; float t, z; - float d = WWMath::Sqrt(x * x + y * y); + float d = WWMath::Sqrt_Legacy(x * x + y * y); if (d < r * (SQRT2/(2.0f))) // inside sphere - z = WWMath::Sqrt(r * r - d * d); + z = WWMath::Sqrt_Legacy(r * r - d * d); else { // on hyperbola t = r / SQRT2; z = t * t / d; diff --git a/Core/Libraries/Source/WWVegas/WWMath/quat.h b/Core/Libraries/Source/WWVegas/WWMath/quat.h index 6b9ef24e700..805225a8c86 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/quat.h +++ b/Core/Libraries/Source/WWVegas/WWMath/quat.h @@ -87,7 +87,7 @@ class Quaternion WWINLINE float Length2() const { return (X*X + Y*Y + Z*Z + W*W); } // Magnitude of the quaternion - WWINLINE float Length() const { return WWMath::Sqrt(Length2()); } + WWINLINE float Length() const { return WWMath::Sqrt_Legacy(Length2()); } // Make the quaternion unit length void Normalize(); @@ -277,10 +277,10 @@ WWINLINE bool Quaternion::Is_Valid() const WWINLINE bool Equal_Within_Epsilon(const Quaternion &a, const Quaternion &b, float epsilon) { - return( (WWMath::Fabs(a.X - b.X) < epsilon) && - (WWMath::Fabs(a.Y - b.Y) < epsilon) && - (WWMath::Fabs(a.Z - b.Z) < epsilon) && - (WWMath::Fabs(a.W - b.W) < epsilon) ); + return( (WWMath::Fabsf_Legacy(a.X - b.X) < epsilon) && + (WWMath::Fabsf_Legacy(a.Y - b.Y) < epsilon) && + (WWMath::Fabsf_Legacy(a.Z - b.Z) < epsilon) && + (WWMath::Fabsf_Legacy(a.W - b.W) < epsilon) ); } /*********************************************************************************************** diff --git a/Core/Libraries/Source/WWVegas/WWMath/sphere.h b/Core/Libraries/Source/WWVegas/WWMath/sphere.h index 1c97e904f1e..3afec2888de 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/sphere.h +++ b/Core/Libraries/Source/WWVegas/WWMath/sphere.h @@ -193,7 +193,7 @@ inline SphereClass::SphereClass(const Vector3 *Position,const int VertCount) dz = dia2.Z - center.Z; double radsqr = dx*dx + dy*dy + dz*dz; - double radius = sqrt(radsqr); + double radius = WWMath::Sqrt(radsqr); // SECOND PASS: @@ -210,7 +210,7 @@ inline SphereClass::SphereClass(const Vector3 *Position,const int VertCount) // this point was outside the old sphere, compute a new // center point and radius which contains this point - double testrad = sqrt(testrad2); + double testrad = WWMath::Sqrt(testrad2); // adjust center and radius radius = (radius + testrad) / 2.0; diff --git a/Core/Libraries/Source/WWVegas/WWMath/tri.cpp b/Core/Libraries/Source/WWVegas/WWMath/tri.cpp index 854991cf587..5f8d1449306 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/tri.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/tri.cpp @@ -59,9 +59,9 @@ static inline void find_dominant_plane_fast(const TriClass & tri, FDPRec& info) /* ** Find the largest component of the normal */ - float x = WWMath::Fabs(tri.N->X); - float y = WWMath::Fabs(tri.N->Y); - float z = WWMath::Fabs(tri.N->Z); + float x = WWMath::Fabsf_Legacy(tri.N->X); + float y = WWMath::Fabsf_Legacy(tri.N->Y); + float z = WWMath::Fabsf_Legacy(tri.N->Z); float val = x; int ni = 0; @@ -86,9 +86,9 @@ static inline void find_dominant_plane(const TriClass & tri, int * axis1,int * a ** Find the largest component of the normal */ int ni = 0; - float x = WWMath::Fabs(tri.N->X); - float y = WWMath::Fabs(tri.N->Y); - float z = WWMath::Fabs(tri.N->Z); + float x = WWMath::Fabsf_Legacy(tri.N->X); + float y = WWMath::Fabsf_Legacy(tri.N->Y); + float z = WWMath::Fabsf_Legacy(tri.N->Z); float val = x; if (y > val) { @@ -146,9 +146,9 @@ void TriClass::Find_Dominant_Plane(int * axis1,int * axis2) const ** Find the largest component of the normal */ int ni = 0; - float x = WWMath::Fabs(N->X); - float y = WWMath::Fabs(N->Y); - float z = WWMath::Fabs(N->Z); + float x = WWMath::Fabsf_Legacy(N->X); + float y = WWMath::Fabsf_Legacy(N->Y); + float z = WWMath::Fabsf_Legacy(N->Z); float val = x; if (y > val) { diff --git a/Core/Libraries/Source/WWVegas/WWMath/v3_rnd.cpp b/Core/Libraries/Source/WWVegas/WWMath/v3_rnd.cpp index ebbd9bf572c..3b8e235fdd1 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/v3_rnd.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/v3_rnd.cpp @@ -118,7 +118,7 @@ void Vector3HollowSphereRandomizer::Get_Vector(Vector3 &vector) if (v_l2 <= 1.0f && v_l2 > 0.0f) break; } - float scale = Radius * WWMath::Inv_Sqrt(v_l2); + float scale = Radius * WWMath::Inv_Sqrt_Legacy(v_l2); vector.X *= scale; vector.Y *= scale; diff --git a/Core/Libraries/Source/WWVegas/WWMath/vector2.h b/Core/Libraries/Source/WWVegas/WWMath/vector2.h index 456e831a569..a7d110e58d2 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/vector2.h +++ b/Core/Libraries/Source/WWVegas/WWMath/vector2.h @@ -309,7 +309,7 @@ WWINLINE bool operator != (const Vector2 &a,const Vector2 &b) *========================================================================*/ WWINLINE bool Equal_Within_Epsilon(const Vector2 &a,const Vector2 &b,float epsilon) { - return( (WWMath::Fabs(a.X - b.X) < epsilon) && (WWMath::Fabs(a.Y - b.Y) < epsilon) ); + return( (WWMath::Fabsf_Legacy(a.X - b.X) < epsilon) && (WWMath::Fabsf_Legacy(a.Y - b.Y) < epsilon) ); } /************************************************************************** @@ -327,7 +327,7 @@ WWINLINE void Vector2::Normalize() { float len2 = Length2(); if (len2 != 0.0f) { - float oolen = WWMath::Inv_Sqrt(len2); + float oolen = WWMath::Inv_Sqrt_Legacy(len2); X *= oolen; Y *= oolen; } @@ -337,7 +337,7 @@ WWINLINE Vector2 Normalize(const Vector2 & vec) { float len2 = vec.Length2(); if (len2 != 0.0f) { - float oolen = WWMath::Inv_Sqrt(len2); + float oolen = WWMath::Inv_Sqrt_Legacy(len2); return vec / oolen; } return Vector2(0.0f,0.0f); @@ -356,7 +356,7 @@ WWINLINE Vector2 Normalize(const Vector2 & vec) *========================================================================*/ WWINLINE float Vector2::Length() const { - return (float)WWMath::Sqrt(Length2()); + return (float)WWMath::Sqrt_Legacy(Length2()); } /************************************************************************** @@ -389,7 +389,7 @@ WWINLINE float Vector2::Length2() const *========================================================================*/ WWINLINE void Vector2::Rotate(float theta) { - Rotate(WWMath::Sin(theta), WWMath::Cos(theta)); + Rotate(WWMath::Sinf_Legacy(theta), WWMath::Cosf_Legacy(theta)); } /************************************************************************** @@ -429,7 +429,7 @@ WWINLINE void Vector2::Rotate(float s, float c) *========================================================================*/ WWINLINE bool Vector2::Rotate_Towards_Vector(Vector2 &target, float max_theta, bool & positive_turn) { - return Rotate_Towards_Vector(target, WWMath::Sin(max_theta), WWMath::Cos(max_theta), positive_turn); + return Rotate_Towards_Vector(target, WWMath::Sinf_Legacy(max_theta), WWMath::Cosf_Legacy(max_theta), positive_turn); } /************************************************************************** @@ -597,8 +597,8 @@ WWINLINE float Quick_Distance(float x1, float y1, float x2, float y2) float x_diff = x1 - x2; float y_diff = y1 - y2; - WWMath::Fabs(x_diff); - WWMath::Fabs(y_diff); + WWMath::Fabsf_Legacy(x_diff); + WWMath::Fabsf_Legacy(y_diff); if (x_diff > y_diff) { @@ -638,7 +638,7 @@ WWINLINE float Distance(float x1, float y1, float x2, float y2) float x_diff = x1 - x2; float y_diff = y1 - y2; - return (WWMath::Sqrt((x_diff * x_diff) + (y_diff * y_diff))); + return (WWMath::Sqrt_Legacy((x_diff * x_diff) + (y_diff * y_diff))); } diff --git a/Core/Libraries/Source/WWVegas/WWMath/vector3.h b/Core/Libraries/Source/WWVegas/WWMath/vector3.h index e1cb8ed4383..3e058579cba 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/vector3.h +++ b/Core/Libraries/Source/WWVegas/WWMath/vector3.h @@ -343,9 +343,9 @@ WWINLINE bool operator != (const Vector3 &a,const Vector3 &b) *========================================================================*/ WWINLINE bool Equal_Within_Epsilon(const Vector3 &a,const Vector3 &b,float epsilon) { - return( (WWMath::Fabs(a.X - b.X) < epsilon) && - (WWMath::Fabs(a.Y - b.Y) < epsilon) && - (WWMath::Fabs(a.Z - b.Z) < epsilon) ); + return( (WWMath::Fabsf_Legacy(a.X - b.X) < epsilon) && + (WWMath::Fabsf_Legacy(a.Y - b.Y) < epsilon) && + (WWMath::Fabsf_Legacy(a.Z - b.Z) < epsilon) ); } @@ -419,7 +419,7 @@ WWINLINE void Vector3::Normalize() float len2 = Length2(); if (len2 != 0.0f) { - float oolen = WWMath::Inv_Sqrt(len2); + float oolen = WWMath::Inv_Sqrt_Legacy(len2); X *= oolen; Y *= oolen; Z *= oolen; @@ -432,7 +432,7 @@ WWINLINE Vector3 Normalize(const Vector3 & vec) float len2 = vec.Length2(); if (len2 != 0.0f) { - float oolen = WWMath::Inv_Sqrt(len2); + float oolen = WWMath::Inv_Sqrt_Legacy(len2); return vec * oolen; } return vec; @@ -452,7 +452,7 @@ WWINLINE Vector3 Normalize(const Vector3 & vec) *========================================================================*/ WWINLINE float Vector3::Length() const { - return WWMath::Sqrt(Length2()); + return WWMath::Sqrt_Legacy(Length2()); } /************************************************************************** @@ -488,9 +488,9 @@ WWINLINE float Vector3::Quick_Length() const { // this method of approximating the length comes from Graphics Gems 1 and // supposedly gives an error of +/- 8% - float max = WWMath::Fabs(X); - float mid = WWMath::Fabs(Y); - float min = WWMath::Fabs(Z); + float max = WWMath::Fabsf_Legacy(X); + float mid = WWMath::Fabsf_Legacy(Y); + float min = WWMath::Fabsf_Legacy(Z); float tmp; if (max < mid) { tmp = max; max = mid; mid = tmp; } @@ -708,7 +708,7 @@ WWINLINE void Vector3::Scale(const Vector3 & scale) *=============================================================================================*/ WWINLINE void Vector3::Rotate_X(float angle) { - Rotate_X(sinf(angle),cosf(angle)); + Rotate_X(WWMath::Sinf(angle),WWMath::Cosf(angle)); } @@ -748,7 +748,7 @@ WWINLINE void Vector3::Rotate_X(float s_angle,float c_angle) *=============================================================================================*/ WWINLINE void Vector3::Rotate_Y(float angle) { - Rotate_Y(sinf(angle),cosf(angle)); + Rotate_Y(WWMath::Sinf(angle),WWMath::Cosf(angle)); } @@ -788,7 +788,7 @@ WWINLINE void Vector3::Rotate_Y(float s_angle,float c_angle) *=============================================================================================*/ WWINLINE void Vector3::Rotate_Z(float angle) { - Rotate_Z(sinf(angle),cosf(angle)); + Rotate_Z(WWMath::Sinf(angle),WWMath::Cosf(angle)); } diff --git a/Core/Libraries/Source/WWVegas/WWMath/vector4.h b/Core/Libraries/Source/WWVegas/WWMath/vector4.h index c0bc336ac9c..18764bebad3 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/vector4.h +++ b/Core/Libraries/Source/WWVegas/WWMath/vector4.h @@ -272,7 +272,7 @@ WWINLINE void Vector4::Normalize() { float len2 = Length2(); if (len2 != 0.0f) { - float oolen = WWMath::Inv_Sqrt(len2); + float oolen = WWMath::Inv_Sqrt_Legacy(len2); X *= oolen; Y *= oolen; Z *= oolen; @@ -284,7 +284,7 @@ WWINLINE Vector4 Normalize(const Vector4 & vec) { float len2 = vec.Length2(); if (len2 != 0.0f) { - float oolen = WWMath::Inv_Sqrt(len2); + float oolen = WWMath::Inv_Sqrt_Legacy(len2); return vec * oolen; } return Vector4(0.0f,0.0f,0.0f,0.0f); @@ -303,7 +303,7 @@ WWINLINE Vector4 Normalize(const Vector4 & vec) *========================================================================*/ WWINLINE float Vector4::Length() const { - return WWMath::Sqrt(Length2()); + return WWMath::Sqrt_Legacy(Length2()); } /************************************************************************** diff --git a/Core/Libraries/Source/WWVegas/WWMath/vehiclecurve.cpp b/Core/Libraries/Source/WWVegas/WWMath/vehiclecurve.cpp index ddacd48b50e..cb52599cb09 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/vehiclecurve.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/vehiclecurve.cpp @@ -95,15 +95,15 @@ Find_Tangent // float delta_x = point.X - center.X; float delta_y = point.Y - center.Y; - float dist = ::sqrt (delta_x * delta_x + delta_y * delta_y); + float dist = WWMath::Sqrt(delta_x * delta_x + delta_y * delta_y); if (dist >= radius) { // // Determine the offset angle (from the line between the point and center) // where the 2 tangent points lie. // - float angle_offset = WWMath::Acos (radius / dist); - float base_angle = WWMath::Atan2 (delta_x, -delta_y); + float angle_offset = WWMath::Acos_Legacy (radius / dist); + float base_angle = WWMath::Atan2_Legacy (delta_x, -delta_y); base_angle = WWMath::Wrap (base_angle, 0, DEG_TO_RADF (360)); // @@ -185,10 +185,10 @@ Find_Turn_Arc // the point halfway between the angles formed by the (prev-curr) and // (next-curr) vectors. // - float angle1 = ::WWMath::Atan2 ((prev_pt.Y - curr_pt.Y), prev_pt.X - curr_pt.X); + float angle1 = ::WWMath::Atan2_Legacy ((prev_pt.Y - curr_pt.Y), prev_pt.X - curr_pt.X); angle1 = WWMath::Wrap (angle1, 0, DEG_TO_RADF (360)); - float angle2 = ::WWMath::Atan2 ((next_pt.Y - curr_pt.Y), next_pt.X - curr_pt.X); + float angle2 = ::WWMath::Atan2_Legacy ((next_pt.Y - curr_pt.Y), next_pt.X - curr_pt.X); angle2 = WWMath::Wrap (angle2, 0, DEG_TO_RADF (360)); float avg_angle = (angle1 + angle2) * 0.5F; @@ -197,8 +197,8 @@ Find_Turn_Arc // Find the shortest delta between the two angles (either clockwise or // counterclockwise). // - float delta1 = WWMath::Fabs (::Get_Angle_Delta (angle1, angle2, true)); - float delta2 = WWMath::Fabs (::Get_Angle_Delta (angle1, angle2, false)); + float delta1 = WWMath::Fabsf_Legacy (::Get_Angle_Delta (angle1, angle2, true)); + float delta2 = WWMath::Fabsf_Legacy (::Get_Angle_Delta (angle1, angle2, false)); if (delta1 < delta2) { avg_angle = angle1 - (delta1 * 0.5F); } else { @@ -208,8 +208,8 @@ Find_Turn_Arc // // Find the point on the circle at this angle // - arc_center->X = curr_pt.X + (radius * ::WWMath::Cos (avg_angle)); - arc_center->Y = curr_pt.Y + (radius * ::WWMath::Sin (avg_angle)); + arc_center->X = curr_pt.X + (radius * ::WWMath::Cosf_Legacy (avg_angle)); + arc_center->Y = curr_pt.Y + (radius * ::WWMath::Sinf_Legacy (avg_angle)); arc_center->Z = curr_pt.Z; // @@ -252,7 +252,7 @@ Find_Tangents // // Find the angle where the current position lies on the turn arc // - (*point_angle) = ::WWMath::Atan2 (curr_pt.X - arc_center.X, -(curr_pt.Y - arc_center.Y)); + (*point_angle) = ::WWMath::Atan2_Legacy (curr_pt.X - arc_center.X, -(curr_pt.Y - arc_center.Y)); (*point_angle) = WWMath::Wrap ((*point_angle), 0, DEG_TO_RADF (360)); // @@ -378,12 +378,12 @@ VehicleCurveClass::Update_Arc_List () // Determine at what points these angles intersect the arc // Vector3 point_in (0, 0, 0); - point_in.X = arc_center.X + (m_Radius * ::WWMath::Sin (point_angle + angle_in_delta)); - point_in.Y = arc_center.Y + (m_Radius * -::WWMath::Cos (point_angle + angle_in_delta)); + point_in.X = arc_center.X + (m_Radius * ::WWMath::Sinf_Legacy (point_angle + angle_in_delta)); + point_in.Y = arc_center.Y + (m_Radius * -::WWMath::Cosf_Legacy (point_angle + angle_in_delta)); Vector3 point_out (0, 0, 0); - point_out.X = arc_center.X + (m_Radius * ::WWMath::Sin (point_angle + angle_out_delta)); - point_out.Y = arc_center.Y + (m_Radius * -::WWMath::Cos (point_angle + angle_out_delta)); + point_out.X = arc_center.X + (m_Radius * ::WWMath::Sinf_Legacy (point_angle + angle_out_delta)); + point_out.Y = arc_center.Y + (m_Radius * -::WWMath::Cosf_Legacy (point_angle + angle_out_delta)); // // Sanity check to ensure the vehicle doesn't try to go the long way around the @@ -489,8 +489,8 @@ VehicleCurveClass::Evaluate (float time, Vector3 *set_val) // - Straight line from exit of last curve to enter of this curve // - Enter curve for the current point // - float arc_length0 = arc_info0.radius * WWMath::Fabs (arc_info0.angle_out_delta); - float arc_length1 = arc_info1.radius * WWMath::Fabs (arc_info1.angle_in_delta); + float arc_length0 = arc_info0.radius * WWMath::Fabsf_Legacy (arc_info0.angle_out_delta); + float arc_length1 = arc_info1.radius * WWMath::Fabsf_Legacy (arc_info1.angle_in_delta); float other_length = ((arc_info1.point_in - arc_info0.point_out).Length ()) / 2; float total_length = arc_length0 + arc_length1 + other_length; @@ -513,10 +513,10 @@ VehicleCurveClass::Evaluate (float time, Vector3 *set_val) //float angle = arc_info0.point_angle + (arc_info0.angle_out_delta) * percent; float angle = arc_info0.point_angle + arc_info0.angle_out_delta; - set_val->X = arc_info0.center.X + (arc_info0.radius * ::WWMath::Sin (angle)); - set_val->Y = arc_info0.center.Y + (arc_info0.radius * -::WWMath::Cos (angle)); + set_val->X = arc_info0.center.X + (arc_info0.radius * ::WWMath::Sinf_Legacy (angle)); + set_val->Y = arc_info0.center.Y + (arc_info0.radius * -::WWMath::Cosf_Legacy (angle)); - m_Sharpness = WWMath::Clamp (WWMath::Fabs (arc_info0.angle_out_delta) / DEG_TO_RADF (15), 0, 1.0F); + m_Sharpness = WWMath::Clamp (WWMath::Fabsf_Legacy (arc_info0.angle_out_delta) / DEG_TO_RADF (15), 0, 1.0F); m_SharpnessPos.X = set_val->X; m_SharpnessPos.Y = set_val->Y; m_SharpnessPos.Z = Keys[index0].Point.Z + (Keys[index1].Point.Z - Keys[index0].Point.Z) * seg_time; @@ -542,7 +542,7 @@ VehicleCurveClass::Evaluate (float time, Vector3 *set_val) //set_val->X = arc_info0.point_out.X + (arc_info1.point_in.X - arc_info0.point_out.X) * percent; //set_val->Y = arc_info0.point_out.Y + (arc_info1.point_in.Y - arc_info0.point_out.Y) * percent; - m_Sharpness = WWMath::Clamp (WWMath::Fabs (arc_info1.angle_out_delta) / DEG_TO_RADF (15), 0, 1.0F); + m_Sharpness = WWMath::Clamp (WWMath::Fabsf_Legacy (arc_info1.angle_out_delta) / DEG_TO_RADF (15), 0, 1.0F); m_SharpnessPos = arc_info1.point_in; m_LastTime = Keys[index0].Time + (Keys[index1].Time - Keys[index0].Time) * time2; @@ -556,15 +556,15 @@ VehicleCurveClass::Evaluate (float time, Vector3 *set_val) /*float percent = 1.0F - ((seg_time - time2) / (1.0F - time2)); float angle = arc_info1.point_angle + (arc_info1.angle_in_delta * percent); - set_val->X = arc_info1.center.X + (arc_info1.radius * ::WWMath::Sin (angle)); - set_val->Y = arc_info1.center.Y + (arc_info1.radius * -::WWMath::Cos (angle)); */ + set_val->X = arc_info1.center.X + (arc_info1.radius * ::WWMath::Sinf_Legacy (angle)); + set_val->Y = arc_info1.center.Y + (arc_info1.radius * -::WWMath::Cosf_Legacy (angle)); */ float angle = arc_info1.point_angle + (arc_info1.angle_out_delta); - set_val->X = arc_info1.center.X + (arc_info1.radius * ::WWMath::Sin (angle)); - set_val->Y = arc_info1.center.Y + (arc_info1.radius * -::WWMath::Cos (angle)); + set_val->X = arc_info1.center.X + (arc_info1.radius * ::WWMath::Sinf_Legacy (angle)); + set_val->Y = arc_info1.center.Y + (arc_info1.radius * -::WWMath::Cosf_Legacy (angle)); - m_Sharpness = WWMath::Clamp (WWMath::Fabs (arc_info1.angle_out_delta) / DEG_TO_RADF (15), 0, 1.0F); + m_Sharpness = WWMath::Clamp (WWMath::Fabsf_Legacy (arc_info1.angle_out_delta) / DEG_TO_RADF (15), 0, 1.0F); m_SharpnessPos.X = set_val->X; m_SharpnessPos.Y = set_val->Y; m_SharpnessPos.Z = Keys[index0].Point.Z + (Keys[index1].Point.Z - Keys[index0].Point.Z) * seg_time; diff --git a/Core/Libraries/Source/WWVegas/WWMath/wwmath.cpp b/Core/Libraries/Source/WWVegas/WWMath/wwmath.cpp index 526d1556650..62e29520f38 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/wwmath.cpp +++ b/Core/Libraries/Source/WWVegas/WWMath/wwmath.cpp @@ -55,13 +55,22 @@ void WWMath::Init() int a=0; for (;a0) { _FastInvSinTable[a]=1.0f/_FastSinTable[a]; diff --git a/Core/Libraries/Source/WWVegas/WWMath/wwmath.h b/Core/Libraries/Source/WWVegas/WWMath/wwmath.h index 8262f2732de..fa5ff666fd9 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/wwmath.h +++ b/Core/Libraries/Source/WWVegas/WWMath/wwmath.h @@ -41,11 +41,16 @@ #include #include +#if USE_DETERMINISTIC_MATH +#include "gmath.h" +#endif + /* ** Some global constants. */ #define WWMATH_EPSILON 0.0001f #define WWMATH_EPSILON2 WWMATH_EPSILON * WWMATH_EPSILON +#define WWMATH_HALF_PI 1.570796327f #define WWMATH_PI 3.141592654f #define WWMATH_TWO_PI 6.283185308f #define WWMATH_FLOAT_MAX (FLT_MAX) @@ -87,6 +92,7 @@ extern float _FastInvSinTable[SIN_TABLE_SIZE]; ** Include the various other header files in the WWMATH library ** in order to get matrices, quaternions, etc. */ +// TheSuperHackers @todo The Legacy functions can be removed when retail compatibility is abandoned. class WWMath { public: @@ -96,65 +102,84 @@ class WWMath static void Init(); static void Shutdown(); -// These are meant to be a collection of small math utility functions to be optimized at some point. -static WWINLINE float Fabs(float val) -{ - int value=*(int*)&val; - value&=0x7fffffff; - return *(float*)&value; -} - -static WWINLINE int Float_To_Int_Chop(const float& f); -static WWINLINE int Float_To_Int_Floor(const float& f); - -#if defined(_MSC_VER) && defined(_M_IX86) -static WWINLINE float Cos(float val); -static WWINLINE float Sin(float val); -static WWINLINE float Sqrt(float val); -static WWINLINE float Inv_Sqrt(float a); // Some 30% faster inverse square root than regular C++ compiled, from Intel's math library -static WWINLINE long Float_To_Long(float f); -#else -static WWINLINE float Cos(float val); -static WWINLINE float Sin(float val); -static WWINLINE float Sqrt(float val); -static WWINLINE float Inv_Sqrt(float a); -static WWINLINE long Float_To_Long(float f); -#endif - - -static WWINLINE float Fast_Sin(float val); -static WWINLINE float Fast_Inv_Sin(float val); -static WWINLINE float Fast_Cos(float val); -static WWINLINE float Fast_Inv_Cos(float val); - -static WWINLINE float Fast_Acos(float val); -static WWINLINE float Acos(float val); -static WWINLINE float Fast_Asin(float val); -static WWINLINE float Asin(float val); - - -static WWINLINE float Atan(float x) { return static_cast(atan(x)); } -static WWINLINE float Atan2(float y,float x) { return static_cast(atan2(y,x)); } -static WWINLINE float Sign(float val); -static WWINLINE float Ceil(float val) { return ceilf(val); } -static WWINLINE float Floor(float val) { return floorf(val); } -static WWINLINE float Round(float val) { return floorf(val + 0.5f); } -static WWINLINE bool Fast_Is_Float_Positive(const float & val); -static WWINLINE bool Is_Power_Of_2(const unsigned int val); +static WWINLINE double Pow(double x, double y); +static WWINLINE float Powf(float x, float y); +static WWINLINE double Sqr(double x); +static WWINLINE float Sqrf(float x); +static WWINLINE float Sqrt_Legacy(float val); +static WWINLINE double Sqrt(double x); +static WWINLINE float Sqrtf(float x); +static WWINLINE float Inv_Sqrt_Legacy(float a); +static WWINLINE double Inv_Sqrt(double x); +static WWINLINE float Inv_Sqrtf(float x); + +static WWINLINE float Fast_Acos(float val); +static WWINLINE float Fast_Asin(float val); +static WWINLINE float Acos_Legacy(float val); +static WWINLINE double Acos(double x); +static WWINLINE float Acosf(float x); +static WWINLINE float Asin_Legacy(float val); +static WWINLINE double Asin(double x); +static WWINLINE float Asinf(float x); +static WWINLINE float Atan_Legacy(float x); +static WWINLINE double Atan(double x); +static WWINLINE float Atanf(float x); +static WWINLINE float Atan2_Legacy(float x, float y); +static WWINLINE double Atan2(double x, double y); +static WWINLINE float Atan2f(float x, float y); + +static WWINLINE float Fast_Cos(float val); +static WWINLINE float Fast_Inv_Cos(float val); +static WWINLINE float Fast_Sin(float val); +static WWINLINE float Fast_Inv_Sin(float val); +static WWINLINE double Cos(double val); +static WWINLINE float Cosf(float val); +static WWINLINE float Cosf_Legacy(float val); +static WWINLINE double Sin(double val); +static WWINLINE float Sinf(float val); +static WWINLINE float Sinf_Legacy(float val); +static WWINLINE double Tan(double x); +static WWINLINE float Tanf(float x); + +static WWINLINE double Cosh(double x); +static WWINLINE float Coshf(float x); +static WWINLINE double Sinh(double x); +static WWINLINE float Sinhf(float x); +static WWINLINE double Tanh(double x); +static WWINLINE float Tanhf(float x); + +static WWINLINE double Fabs(double x); +static WWINLINE float Fabsf(float x); +static WWINLINE float Fabsf_Legacy(float val); + +static WWINLINE double Ceil(double x); +static WWINLINE float Ceilf(float x); +static WWINLINE double Floor(double x); +static WWINLINE float Floorf(float x); +static WWINLINE double Round(double x) { return Floor(x + 0.5); } +static WWINLINE float Roundf(float x) { return Floorf(x + 0.5f); } + +static WWINLINE double Exp(double x); +static WWINLINE float Expf(float x); +static WWINLINE double Log10(double x); +static WWINLINE float Log10f(float x); +static WWINLINE double Log(double x); +static WWINLINE float Logf(float x); + +static WWINLINE bool Fast_Is_Float_Positive(const float & val); +static WWINLINE bool Is_Power_Of_2(const unsigned int val); static float Random_Float(); static WWINLINE float Random_Float(float min,float max); static WWINLINE float Clamp(float val, float min = 0.0f, float max = 1.0f); -static WWINLINE double Clamp(double val, double min = 0.0f, double max = 1.0f); +static WWINLINE double Clamp(double val, double min = 0.0f, double max = 1.0f); static WWINLINE int Clamp_Int(int val, int min_val, int max_val); static WWINLINE float Wrap(float val, float min = 0.0f, float max = 1.0f); -static WWINLINE double Wrap(double val, double min = 0.0f, double max = 1.0f); +static WWINLINE double Wrap(double val, double min = 0.0f, double max = 1.0f); static WWINLINE float Min(float a, float b); static WWINLINE float Max(float a, float b); -static WWINLINE int Float_As_Int(const float f) { return *((int*)&f); } - // Linearly interpolates between a and b using parameter t in [0, 1]. // t = 0 returns a, t = 1 returns b, values in between return a proportionate blend. static WWINLINE float Lerp(float a, float b, float t); @@ -165,27 +190,667 @@ static WWINLINE double Lerp(double a, double b, float t); static WWINLINE float Inverse_Lerp(float a, float b, float v); static WWINLINE double Inverse_Lerp(double a, double b, float v); -static WWINLINE long Float_To_Long(double f); - -static WWINLINE unsigned char Unit_Float_To_Byte(float f) { return (unsigned char)(f*255.0f); } -static WWINLINE float Byte_To_Unit_Float(unsigned char byte) { return ((float)byte) / 255.0f; } - static WWINLINE bool Is_Valid_Float(float x); static WWINLINE bool Is_Valid_Double(double x); +static WWINLINE int Float_To_Int_Chop(float f); +static WWINLINE int Float_To_Int_Floor(float f); +static WWINLINE long Float_To_Long(float f); +static WWINLINE long Float_To_Long(double f); +static WWINLINE int Float_As_Int(const float f) { return *((int*)&f); } +static WWINLINE unsigned char Unit_Float_To_Byte(float f) { return (unsigned char)(f*255.0f); } +static WWINLINE float Byte_To_Unit_Float(unsigned char byte) { return ((float)byte) / 255.0f; } + static WWINLINE float Normalize_Angle(float angle); // Normalizes the angle to the range -PI..PI -}; +}; + + +WWINLINE double WWMath::Pow(double x, double y) +{ +#if USE_DETERMINISTIC_MATH + return gm_pow(x, y); +#else + return pow(x, y); +#endif +} + +// TheSuperHackers @bugfix bobtista 21/07/2026 Square via a plain multiply under deterministic math. +// Pow(x, 2) routes squaring through gm_pow/gm_powf (fdlibm), which is not bit-identical between the +// x87 32-bit Windows build and macOS ARM64. A multiply is exact and cross-platform deterministic. +WWINLINE double WWMath::Sqr(double x) +{ +#if USE_DETERMINISTIC_MATH + return x * x; +#else + return Pow(x, 2.0); +#endif +} + +WWINLINE float WWMath::Sqrf(float x) +{ +#if USE_DETERMINISTIC_MATH + return x * x; +#else + return Powf(x, 2.0f); +#endif +} + +WWINLINE float WWMath::Powf(float x, float y) +{ +#if USE_DETERMINISTIC_MATH + return gm_powf(x, y); +#else + return powf(x, y); +#endif +} + +WWINLINE float WWMath::Sqrt_Legacy(float val) +{ +#if USE_DETERMINISTIC_MATH + return gm_sqrtf(val); + +#elif defined(_MSC_VER) && defined(_M_IX86) + float retval; + __asm { + fld [val] + fsqrt + fstp [retval] + } + return retval; + +#else + return (float)sqrt((double)val); +#endif +} + +WWINLINE double WWMath::Sqrt(double x) +{ +#if USE_DETERMINISTIC_MATH + return gm_sqrt(x); +#else + return sqrt(x); +#endif +} + +WWINLINE float WWMath::Sqrtf(float x) +{ +#if USE_DETERMINISTIC_MATH + return gm_sqrtf(x); +#else + return sqrtf(x); +#endif +} + +WWINLINE float WWMath::Inv_Sqrt_Legacy(float a) +{ +#if USE_DETERMINISTIC_MATH + return 1.0f / gm_sqrtf(a); + +#elif defined(_MSC_VER) && defined(_M_IX86) + // Some 30% faster inverse square root than regular C++ compiled, from Intel's math library + float retval; + + __asm { + mov eax, 0be6eb508h + mov DWORD PTR [esp-12],03fc00000h ; 1.5 on the stack + sub eax, DWORD PTR [a]; a + sub DWORD PTR [a], 800000h ; a/2 a=Y0 + shr eax, 1 ; firs approx in eax=R0 + mov DWORD PTR [esp-8], eax + + fld DWORD PTR [esp-8] ;r + fmul st, st ;r*r + fld DWORD PTR [esp-8] ;r + fxch st(1) + fmul DWORD PTR [a];a ;r*r*y0 + fld DWORD PTR [esp-12];load 1.5 + fld st(0) + fsub st,st(2) ;r1 = 1.5 - y1 + ;x1 = st(3) + ;y1 = st(2) + ;1.5 = st(1) + ;r1 = st(0) + + fld st(1) + fxch st(1) + fmul st(3),st ; y2=y1*r1*... + fmul st(3),st ; y2=y1*r1*r1 + fmulp st(4),st ; x2=x1*r1 + fsub st,st(2) ; r2=1.5-y2 + ;x2=st(3) + ;y2=st(2) + ;1.5=st(1) + ;r2 = st(0) + + fmul st(2),st ;y3=y2*r2*... + fmul st(3),st ;x3=x2*r2 + fmulp st(2),st ;y3=y2*r2*r2 + fxch st(1) + fsubp st(1),st ;r3= 1.5 - y3 + ;x3 = st(1) + ;r3 = st(0) + fmulp st(1), st + + fstp retval + } + + return retval; + +#else + return 1.0f / (float)sqrt((double)a); +#endif +} + +WWINLINE double WWMath::Inv_Sqrt(double x) +{ + return 1.0 / Sqrt(x); +} + +WWINLINE float WWMath::Inv_Sqrtf(float x) +{ + return 1.0f / Sqrtf(x); +} + +WWINLINE float WWMath::Fast_Acos(float val) +{ + // Near -1 and +1, the table becomes too inaccurate + if (Fabsf_Legacy(val) > 0.975f) { + return Acos_Legacy(val); + } + + val*=float(ARC_TABLE_SIZE/2); + + int idx0=Float_To_Int_Floor(val); + int idx1=idx0+1; + float frac=val-(float)idx0; + + idx0+=ARC_TABLE_SIZE/2; + idx1+=ARC_TABLE_SIZE/2; + + // we dont even get close to the edge of the table... + assert((idx0 >= 0) && (idx0 < ARC_TABLE_SIZE)); + assert((idx1 >= 0) && (idx1 < ARC_TABLE_SIZE)); + + // compute and return the interpolated value + return (1.0f - frac) * _FastAcosTable[idx0] + frac * _FastAcosTable[idx1]; +} + +WWINLINE float WWMath::Fast_Asin(float val) +{ + // Near -1 and +1, the table becomes too inaccurate + if (Fabsf_Legacy(val) > 0.975f) { + return Asin_Legacy(val); + } + + val*=float(ARC_TABLE_SIZE/2); + + int idx0=Float_To_Int_Floor(val); + int idx1=idx0+1; + float frac=val-(float)idx0; + + idx0+=ARC_TABLE_SIZE/2; + idx1+=ARC_TABLE_SIZE/2; + + // we dont even get close to the edge of the table... + assert((idx0 >= 0) && (idx0 < ARC_TABLE_SIZE)); + assert((idx1 >= 0) && (idx1 < ARC_TABLE_SIZE)); + + // compute and return the interpolated value + return (1.0f - frac) * _FastAsinTable[idx0] + frac * _FastAsinTable[idx1]; +} + +WWINLINE float WWMath::Acos_Legacy(float val) +{ +#if USE_DETERMINISTIC_MATH + return gm_acosf(val); +#else + return (float)acos((double)val); +#endif +} + +WWINLINE double WWMath::Acos(double x) +{ +#if USE_DETERMINISTIC_MATH + return gm_acos(x); +#else + return acos(x); +#endif +} + +WWINLINE float WWMath::Acosf(float x) +{ +#if USE_DETERMINISTIC_MATH + return gm_acosf(x); +#else + return acosf(x); +#endif +} + +WWINLINE float WWMath::Asin_Legacy(float val) +{ +#if USE_DETERMINISTIC_MATH + return gm_asinf(val); +#else + return (float)asin((double)val); +#endif +} + +WWINLINE double WWMath::Asin(double x) +{ +#if USE_DETERMINISTIC_MATH + return gm_asin(x); +#else + return asin(x); +#endif +} +WWINLINE float WWMath::Asinf(float x) +{ +#if USE_DETERMINISTIC_MATH + return gm_asinf(x); +#else + return asinf(x); +#endif +} + +WWINLINE float WWMath::Atan_Legacy(float x) +{ +#if USE_DETERMINISTIC_MATH + return gm_atanf(x); +#else + return (float)atan((double)x); +#endif +} + +WWINLINE double WWMath::Atan(double x) +{ +#if USE_DETERMINISTIC_MATH + return gm_atan(x); +#else + return atan(x); +#endif +} + +WWINLINE float WWMath::Atanf(float x) +{ +#if USE_DETERMINISTIC_MATH + return gm_atanf(x); +#else + return atanf(x); +#endif +} + +WWINLINE float WWMath::Atan2_Legacy(float x, float y) +{ +#if USE_DETERMINISTIC_MATH + return gm_atan2f(x, y); +#else + return (float)atan2((double)x, (double)y); +#endif +} + +WWINLINE double WWMath::Atan2(double x, double y) +{ +#if USE_DETERMINISTIC_MATH + return gm_atan2(x, y); +#else + return atan2(x, y); +#endif +} + +WWINLINE float WWMath::Atan2f(float x, float y) +{ +#if USE_DETERMINISTIC_MATH + return gm_atan2f(x, y); +#else + return atan2f(x, y); +#endif +} + +WWINLINE float WWMath::Fast_Cos(float val) +{ + val+=(WWMATH_PI * 0.5f); + val*=float(SIN_TABLE_SIZE) / (2.0f * WWMATH_PI); + + int idx0=Float_To_Int_Floor(val); + int idx1=idx0+1; + float frac=val-(float)idx0; + + idx0 = ((unsigned)idx0) & (SIN_TABLE_SIZE-1); + idx1 = ((unsigned)idx1) & (SIN_TABLE_SIZE-1); + + return (1.0f - frac) * _FastSinTable[idx0] + frac * _FastSinTable[idx1]; +} + +WWINLINE float WWMath::Fast_Inv_Cos(float val) +{ +#if 0 // TODO: more testing, not reliable! + float index = val + (WWMATH_PI * 0.5f); + index *= float(SIN_TABLE_SIZE) / (2.0f * WWMATH_PI); + + int idx0=Float_To_Int_Chop(index); + int idx1=idx0+1; + float frac=val-(float)idx0; + + idx0 = ((unsigned)idx0) & (SIN_TABLE_SIZE-1); + idx1 = ((unsigned)idx1) & (SIN_TABLE_SIZE-1); + + // The table becomes inaccurate near 0 and 2pi so fall back to doing a divide. + if ((idx0 <= 2) || (idx0 >= SIN_TABLE_SIZE-3)) { + return 1.0f / Fast_Cos(val); + } else { + return (1.0f - frac) * _FastInvSinTable[idx0] + frac * _FastInvSinTable[idx1]; + } +#else + return 1.0f / Fast_Cos(val); +#endif +} + +WWINLINE float WWMath::Fast_Sin(float val) +{ + val*=float(SIN_TABLE_SIZE) / (2.0f * WWMATH_PI); + + int idx0=Float_To_Int_Floor(val); + int idx1=idx0+1; + float frac=val-(float)idx0; + + idx0 = ((unsigned)idx0) & (SIN_TABLE_SIZE-1); + idx1 = ((unsigned)idx1) & (SIN_TABLE_SIZE-1); + + return (1.0f - frac) * _FastSinTable[idx0] + frac * _FastSinTable[idx1]; +} + +WWINLINE float WWMath::Fast_Inv_Sin(float val) +{ +#if 0 // TODO: more testing, not reliable! + float index = val * float(SIN_TABLE_SIZE) / (2.0f * WWMATH_PI); + + int idx0=Float_To_Int_Floor(index); + int idx1=idx0+1; + float frac=val-(float)idx0; + + idx0 = ((unsigned)idx0) & (SIN_TABLE_SIZE-1); + idx1 = ((unsigned)idx1) & (SIN_TABLE_SIZE-1); + + // The table becomes inaccurate near 0 and 2pi so fall back to doing a divide. + const int BUFFER = 16; + if ((idx0 <= BUFFER) || (idx0 >= SIN_TABLE_SIZE-BUFFER-1)) { + return 1.0f / Fast_Sin(val); + } else { + return (1.0f - frac) * _FastInvSinTable[idx0] + frac * _FastInvSinTable[idx1]; + } +#else + return 1.0f / Fast_Sin(val); +#endif +} + +WWINLINE double WWMath::Cos(double val) +{ +#if USE_DETERMINISTIC_MATH + return gm_cos(val); +#else + return cos(val); +#endif +} + +WWINLINE float WWMath::Cosf(float val) +{ +#if USE_DETERMINISTIC_MATH + return gm_cosf(val); +#else + return cosf(val); +#endif +} + +WWINLINE float WWMath::Cosf_Legacy(float val) +{ +#if USE_DETERMINISTIC_MATH + return gm_cosf(val); + +#elif defined(_MSC_VER) && defined(_M_IX86) + float retval; + __asm { + fld [val] + fcos + fstp [retval] + } + return retval; + +#else + return cosf(val); +#endif +} + +WWINLINE double WWMath::Sin(double val) +{ +#if USE_DETERMINISTIC_MATH + return gm_sin(val); +#else + return sin(val); +#endif +} + +WWINLINE float WWMath::Sinf(float val) +{ +#if USE_DETERMINISTIC_MATH + return gm_sinf(val); +#else + return sinf(val); +#endif +} + +WWINLINE float WWMath::Sinf_Legacy(float val) +{ +#if USE_DETERMINISTIC_MATH + return gm_sinf(val); + +#elif defined(_MSC_VER) && defined(_M_IX86) + float retval; + __asm { + fld [val] + fsin + fstp [retval] + } + return retval; + +#else + return sinf(val); +#endif +} + +WWINLINE double WWMath::Tan(double x) +{ +#if USE_DETERMINISTIC_MATH + return gm_tan(x); +#else + return tan(x); +#endif +} + +WWINLINE float WWMath::Tanf(float x) +{ +#if USE_DETERMINISTIC_MATH + return gm_tanf(x); +#else + return tanf(x); +#endif +} + +WWINLINE double WWMath::Cosh(double x) +{ +#if USE_DETERMINISTIC_MATH + return gm_cosh(x); +#else + return cosh(x); +#endif +} + +WWINLINE float WWMath::Coshf(float x) +{ +#if USE_DETERMINISTIC_MATH + return gm_coshf(x); +#else + return coshf(x); +#endif +} + +WWINLINE double WWMath::Sinh(double x) +{ +#if USE_DETERMINISTIC_MATH + return gm_sinh(x); +#else + return sinh(x); +#endif +} + +WWINLINE float WWMath::Sinhf(float x) +{ +#if USE_DETERMINISTIC_MATH + return gm_sinhf(x); +#else + return sinhf(x); +#endif +} + +WWINLINE double WWMath::Tanh(double x) +{ +#if USE_DETERMINISTIC_MATH + return gm_tanh(x); +#else + return tanh(x); +#endif +} + +WWINLINE float WWMath::Tanhf(float x) +{ +#if USE_DETERMINISTIC_MATH + return gm_tanhf(x); +#else + return tanhf(x); +#endif +} + +WWINLINE double WWMath::Fabs(double x) +{ +#if USE_DETERMINISTIC_MATH + return gm_fabs(x); +#else + return fabs(x); +#endif +} + +WWINLINE float WWMath::Fabsf(float x) +{ +#if USE_DETERMINISTIC_MATH + return gm_fabsf(x); +#else + return fabsf(x); +#endif +} + +WWINLINE float WWMath::Fabsf_Legacy(float val) +{ +#if USE_DETERMINISTIC_MATH + return gm_fabsf(val); + +#elif defined(_MSC_VER) && defined(_M_IX86) + int value=*(int*)&val; + value&=0x7fffffff; + return *(float*)&value; + +#else + return fabsf(val); +#endif +} + +WWINLINE double WWMath::Ceil(double x) +{ +#if USE_DETERMINISTIC_MATH + return gm_ceil(x); +#else + return ceil(x); +#endif +} + +WWINLINE float WWMath::Ceilf(float x) +{ +#if USE_DETERMINISTIC_MATH + return gm_ceilf(x); +#else + return ceilf(x); +#endif +} + +WWINLINE double WWMath::Floor(double x) +{ +#if USE_DETERMINISTIC_MATH + return gm_floor(x); +#else + return floor(x); +#endif +} + +WWINLINE float WWMath::Floorf(float x) +{ +#if USE_DETERMINISTIC_MATH + return gm_floorf(x); +#else + return floorf(x); +#endif +} + +WWINLINE double WWMath::Exp(double x) +{ +#if USE_DETERMINISTIC_MATH + return gm_exp(x); +#else + return exp(x); +#endif +} + +WWINLINE float WWMath::Expf(float x) +{ +#if USE_DETERMINISTIC_MATH + return gm_expf(x); +#else + return expf(x); +#endif +} + +WWINLINE double WWMath::Log10(double x) +{ +#if USE_DETERMINISTIC_MATH + return gm_log10(x); +#else + return log10(x); +#endif +} + +WWINLINE float WWMath::Log10f(float x) +{ +#if USE_DETERMINISTIC_MATH + return gm_log10f(x); +#else + return log10f(x); +#endif +} + +WWINLINE double WWMath::Log(double x) +{ +#if USE_DETERMINISTIC_MATH + return gm_log(x); +#else + return log(x); +#endif +} -WWINLINE float WWMath::Sign(float val) +WWINLINE float WWMath::Logf(float x) { - if (val > 0.0f) { - return +1.0f; - } - if (val < 0.0f) { - return -1.0f; - } - return 0.0f; +#if USE_DETERMINISTIC_MATH + return gm_logf(x); +#else + return logf(x); +#endif } WWINLINE bool WWMath::Fast_Is_Float_Positive(const float & val) @@ -309,278 +974,43 @@ WWINLINE bool WWMath::Is_Valid_Double(double x) return true; } -// ---------------------------------------------------------------------------- -// Float to long -// ---------------------------------------------------------------------------- - -#if defined(_MSC_VER) && defined(_M_IX86) WWINLINE long WWMath::Float_To_Long(float f) { - long i; +#if USE_DETERMINISTIC_MATH + return gm_lrintf(f); +#elif defined(_MSC_VER) && defined(_M_IX86) + long i; __asm { fld [f] fistp [i] } - return i; -} + #else -WWINLINE long WWMath::Float_To_Long(float f) -{ - return (long) f; -} + return (long)f; #endif +} WWINLINE long WWMath::Float_To_Long(double f) { -#if defined(_MSC_VER) && defined(_M_IX86) +#if USE_DETERMINISTIC_MATH + return gm_lrint(f); + +#elif defined(_MSC_VER) && defined(_M_IX86) long retval; __asm { fld qword ptr [f] fistp dword ptr [retval] } return retval; -#else - return (long) f; -#endif -} - -// ---------------------------------------------------------------------------- -// Cos -// ---------------------------------------------------------------------------- - -#if defined(_MSC_VER) && defined(_M_IX86) -WWINLINE float WWMath::Cos(float val) -{ - float retval; - __asm { - fld [val] - fcos - fstp [retval] - } - return retval; -} -#else -WWINLINE float WWMath::Cos(float val) -{ - return cosf(val); -} -#endif - -// ---------------------------------------------------------------------------- -// Sin -// ---------------------------------------------------------------------------- - -#if defined(_MSC_VER) && defined(_M_IX86) -WWINLINE float WWMath::Sin(float val) -{ - float retval; - __asm { - fld [val] - fsin - fstp [retval] - } - return retval; -} -#else -WWINLINE float WWMath::Sin(float val) -{ - return sinf(val); -} -#endif - -// ---------------------------------------------------------------------------- -// Fast, table based sin -// ---------------------------------------------------------------------------- - -WWINLINE float WWMath::Fast_Sin(float val) -{ - val*=float(SIN_TABLE_SIZE) / (2.0f * WWMATH_PI); - - int idx0=Float_To_Int_Floor(val); - int idx1=idx0+1; - float frac=val-(float)idx0; - - idx0 = ((unsigned)idx0) & (SIN_TABLE_SIZE-1); - idx1 = ((unsigned)idx1) & (SIN_TABLE_SIZE-1); - - return (1.0f - frac) * _FastSinTable[idx0] + frac * _FastSinTable[idx1]; -} - -// ---------------------------------------------------------------------------- -// Fast, table based 1.0f/sin -// ---------------------------------------------------------------------------- - -WWINLINE float WWMath::Fast_Inv_Sin(float val) -{ -#if 0 // TODO: more testing, not reliable! - float index = val * float(SIN_TABLE_SIZE) / (2.0f * WWMATH_PI); - - int idx0=Float_To_Int_Floor(index); - int idx1=idx0+1; - float frac=val-(float)idx0; - - idx0 = ((unsigned)idx0) & (SIN_TABLE_SIZE-1); - idx1 = ((unsigned)idx1) & (SIN_TABLE_SIZE-1); - - // The table becomes inaccurate near 0 and 2pi so fall back to doing a divide. - const int BUFFER = 16; - if ((idx0 <= BUFFER) || (idx0 >= SIN_TABLE_SIZE-BUFFER-1)) { - return 1.0f / WWMath::Fast_Sin(val); - } else { - return (1.0f - frac) * _FastInvSinTable[idx0] + frac * _FastInvSinTable[idx1]; - } -#else - return 1.0f / WWMath::Fast_Sin(val); -#endif -} - -// ---------------------------------------------------------------------------- -// Fast, table based cos -// ---------------------------------------------------------------------------- - -WWINLINE float WWMath::Fast_Cos(float val) -{ - val+=(WWMATH_PI * 0.5f); - val*=float(SIN_TABLE_SIZE) / (2.0f * WWMATH_PI); - - int idx0=Float_To_Int_Floor(val); - int idx1=idx0+1; - float frac=val-(float)idx0; - - idx0 = ((unsigned)idx0) & (SIN_TABLE_SIZE-1); - idx1 = ((unsigned)idx1) & (SIN_TABLE_SIZE-1); - - return (1.0f - frac) * _FastSinTable[idx0] + frac * _FastSinTable[idx1]; -} - -// ---------------------------------------------------------------------------- -// Fast, table based 1.0f/cos -// ---------------------------------------------------------------------------- - -WWINLINE float WWMath::Fast_Inv_Cos(float val) -{ -#if 0 // TODO: more testing, not reliable! - float index = val + (WWMATH_PI * 0.5f); - index *= float(SIN_TABLE_SIZE) / (2.0f * WWMATH_PI); - - int idx0=Float_To_Int_Chop(index); - int idx1=idx0+1; - float frac=val-(float)idx0; - - idx0 = ((unsigned)idx0) & (SIN_TABLE_SIZE-1); - idx1 = ((unsigned)idx1) & (SIN_TABLE_SIZE-1); - - // The table becomes inaccurate near 0 and 2pi so fall back to doing a divide. - if ((idx0 <= 2) || (idx0 >= SIN_TABLE_SIZE-3)) { - return 1.0f / WWMath::Fast_Cos(val); - } else { - return (1.0f - frac) * _FastInvSinTable[idx0] + frac * _FastInvSinTable[idx1]; - } #else - return 1.0f / WWMath::Fast_Cos(val); + return (long)f; #endif } -// ---------------------------------------------------------------------------- -// Fast, table based arc cos -// ---------------------------------------------------------------------------- - -WWINLINE float WWMath::Fast_Acos(float val) -{ - // Near -1 and +1, the table becomes too inaccurate - if (WWMath::Fabs(val) > 0.975f) { - return WWMath::Acos(val); - } - - val*=float(ARC_TABLE_SIZE/2); - - int idx0=Float_To_Int_Floor(val); - int idx1=idx0+1; - float frac=val-(float)idx0; - - idx0+=ARC_TABLE_SIZE/2; - idx1+=ARC_TABLE_SIZE/2; - - // we dont even get close to the edge of the table... - assert((idx0 >= 0) && (idx0 < ARC_TABLE_SIZE)); - assert((idx1 >= 0) && (idx1 < ARC_TABLE_SIZE)); - - // compute and return the interpolated value - return (1.0f - frac) * _FastAcosTable[idx0] + frac * _FastAcosTable[idx1]; -} - -// ---------------------------------------------------------------------------- -// Arc cos -// ---------------------------------------------------------------------------- - -WWINLINE float WWMath::Acos(float val) -{ - return (float)acos(val); -} - -// ---------------------------------------------------------------------------- -// Fast, table based arc sin -// ---------------------------------------------------------------------------- - -WWINLINE float WWMath::Fast_Asin(float val) -{ - // Near -1 and +1, the table becomes too inaccurate - if (WWMath::Fabs(val) > 0.975f) { - return WWMath::Asin(val); - } - - val*=float(ARC_TABLE_SIZE/2); - - int idx0=Float_To_Int_Floor(val); - int idx1=idx0+1; - float frac=val-(float)idx0; - - idx0+=ARC_TABLE_SIZE/2; - idx1+=ARC_TABLE_SIZE/2; - - // we dont even get close to the edge of the table... - assert((idx0 >= 0) && (idx0 < ARC_TABLE_SIZE)); - assert((idx1 >= 0) && (idx1 < ARC_TABLE_SIZE)); - - // compute and return the interpolated value - return (1.0f - frac) * _FastAsinTable[idx0] + frac * _FastAsinTable[idx1]; -} - -// ---------------------------------------------------------------------------- -// Arc sin -// ---------------------------------------------------------------------------- - -WWINLINE float WWMath::Asin(float val) -{ - return (float)asin(val); -} - -// ---------------------------------------------------------------------------- -// Sqrt -// ---------------------------------------------------------------------------- - -#if defined(_MSC_VER) && defined(_M_IX86) -WWINLINE float WWMath::Sqrt(float val) -{ - float retval; - __asm { - fld [val] - fsqrt - fstp [retval] - } - return retval; -} -#else -WWINLINE float WWMath::Sqrt(float val) -{ - return (float)sqrt(val); -} -#endif - -WWINLINE int WWMath::Float_To_Int_Chop(const float& f) +WWINLINE int WWMath::Float_To_Int_Chop(float f) { int a = *reinterpret_cast(&f); // take bit pattern of float into a register int sign = (a>>31); // sign = 0xFFFFFFFF if original value is negative, 0 if positive @@ -590,7 +1020,7 @@ WWINLINE int WWMath::Float_To_Int_Chop(const float& f) return ((r ^ (sign)) - sign ) &~ (exponent>>31); // add original sign. If exponent was negative, make return value 0. } -WWINLINE int WWMath::Float_To_Int_Floor (const float& f) +WWINLINE int WWMath::Float_To_Int_Floor(float f) { int a = *reinterpret_cast(&f); // take bit pattern of float into a register int sign = (a>>31); // sign = 0xFFFFFFFF if original value is negative, 0 if positive @@ -606,68 +1036,6 @@ WWINLINE int WWMath::Float_To_Int_Floor (const float& f) return r; } -// ---------------------------------------------------------------------------- -// Inverse square root -// ---------------------------------------------------------------------------- - -#if defined(_MSC_VER) && defined(_M_IX86) -WWINLINE float WWMath::Inv_Sqrt(float a) -{ - float retval; - - __asm { - mov eax, 0be6eb508h - mov DWORD PTR [esp-12],03fc00000h ; 1.5 on the stack - sub eax, DWORD PTR [a]; a - sub DWORD PTR [a], 800000h ; a/2 a=Y0 - shr eax, 1 ; firs approx in eax=R0 - mov DWORD PTR [esp-8], eax - - fld DWORD PTR [esp-8] ;r - fmul st, st ;r*r - fld DWORD PTR [esp-8] ;r - fxch st(1) - fmul DWORD PTR [a];a ;r*r*y0 - fld DWORD PTR [esp-12];load 1.5 - fld st(0) - fsub st,st(2) ;r1 = 1.5 - y1 - ;x1 = st(3) - ;y1 = st(2) - ;1.5 = st(1) - ;r1 = st(0) - - fld st(1) - fxch st(1) - fmul st(3),st ; y2=y1*r1*... - fmul st(3),st ; y2=y1*r1*r1 - fmulp st(4),st ; x2=x1*r1 - fsub st,st(2) ; r2=1.5-y2 - ;x2=st(3) - ;y2=st(2) - ;1.5=st(1) - ;r2 = st(0) - - fmul st(2),st ;y3=y2*r2*... - fmul st(3),st ;x3=x2*r2 - fmulp st(2),st ;y3=y2*r2*r2 - fxch st(1) - fsubp st(1),st ;r3= 1.5 - y3 - ;x3 = st(1) - ;r3 = st(0) - fmulp st(1), st - - fstp retval - } - - return retval; -} -#else -WWINLINE float WWMath::Inv_Sqrt(float val) -{ - return 1.0f / (float)sqrt(val); -} -#endif - WWINLINE float WWMath::Normalize_Angle(float angle) { return angle - (WWMATH_TWO_PI * Floor((angle + WWMATH_PI) / WWMATH_TWO_PI)); diff --git a/Core/Libraries/Source/WWVegas/WWSaveLoad/persistfactory.h b/Core/Libraries/Source/WWVegas/WWSaveLoad/persistfactory.h index 2281a626520..73753d1b9a7 100644 --- a/Core/Libraries/Source/WWVegas/WWSaveLoad/persistfactory.h +++ b/Core/Libraries/Source/WWVegas/WWSaveLoad/persistfactory.h @@ -120,9 +120,9 @@ SimplePersistFactoryClass::Load(ChunkLoadClass & cload) const template void SimplePersistFactoryClass::Save(ChunkSaveClass & csave,PersistClass * obj) const { - uint32 objptr = (uint32)obj; + uintptr_t objptr = reinterpret_cast(obj); csave.Begin_Chunk(SIMPLEFACTORY_CHUNKID_OBJPOINTER); - csave.Write(&objptr,sizeof(uint32)); + csave.Write(&objptr,sizeof(objptr)); csave.End_Chunk(); csave.Begin_Chunk(SIMPLEFACTORY_CHUNKID_OBJDATA); 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/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..4ce475dbaec --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/dinput.h @@ -0,0 +1,124 @@ +#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 = 1, + 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, + + // 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 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..774b4920b56 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/file_compat.h @@ -0,0 +1,317 @@ +#pragma once + +#include +#include + +#define _stat stat + +// GeneralsX @build BenderAI 10/02/2026 - Win32 file API → POSIX +#ifndef _WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include + +inline std::string NormalizeWin32PathForHost(const char* path) { + std::string normalized(path ? path : ""); + std::replace(normalized.begin(), normalized.end(), '\\', '/'); + return normalized; +} + +inline int _access(const char* path, int mode) { + const std::string normalized = NormalizeWin32PathForHost(path); + return access(normalized.c_str(), mode); +} +#else +#define _access access +#endif +#ifndef _WIN32 +// 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 +// TheSuperHackers @bugfix bobtista 28/05/2026 Return FILE_ATTRIBUTE_DIRECTORY when the path is a directory so callers can distinguish files from directories. +inline uint32_t GetFileAttributes(const char* path) { + struct stat st; + if (stat(path, &st) != 0) + { + return 0xFFFFFFFF; // INVALID_FILE_ATTRIBUTES + } + if (S_ISDIR(st.st_mode)) + { + return 0x10; // FILE_ATTRIBUTE_DIRECTORY + } + 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 { + const std::string normalized = NormalizeWin32PathForHost(path); + std::filesystem::current_path(normalized); + 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 { + const std::string normalized = NormalizeWin32PathForHost(path); + return std::filesystem::remove(normalized) ? 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 { + const std::string normalizedExisting = NormalizeWin32PathForHost(existingFile); + const std::string normalizedNew = NormalizeWin32PathForHost(newFile); + std::filesystem::copy_options opts = failIfExists + ? std::filesystem::copy_options::none + : std::filesystem::copy_options::overwrite_existing; + std::filesystem::copy_file(normalizedExisting, normalizedNew, 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 { + const std::string normalizedPattern = NormalizeWin32PathForHost(pattern); + std::filesystem::path p(normalizedPattern); + 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..5d43f2f0677 --- /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 shims 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..9a706cfbac0 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/windows.h @@ -0,0 +1,1246 @@ +#pragma once + +#include "WWLib/bittype.h" + +#include +#include +#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; } +// 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 == nullptr) { + return; + } + + struct timeval tv; + if (gettimeofday(&tv, nullptr) != 0) { + *t = SYSTEMTIME{}; + return; + } + + struct tm local_tm; + if (localtime_r(&tv.tv_sec, &local_tm) == nullptr) { + *t = SYSTEMTIME{}; + return; + } + + t->wYear = static_cast(local_tm.tm_year + 1900); + t->wMonth = static_cast(local_tm.tm_mon + 1); + t->wDayOfWeek = static_cast(local_tm.tm_wday); + t->wDay = static_cast(local_tm.tm_mday); + t->wHour = static_cast(local_tm.tm_hour); + t->wMinute = static_cast(local_tm.tm_min); + t->wSecond = static_cast(local_tm.tm_sec); + t->wMilliseconds = static_cast(tv.tv_usec / 1000); +} +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 MB_APPLMODAL +#define MB_APPLMODAL 0x00000000 +#endif +#ifndef MB_DEFBUTTON3 +#define MB_DEFBUTTON3 0x00000200 +#endif + +#ifndef IDOK +#define IDOK 1 +#endif +#ifndef IDCANCEL +#define IDCANCEL 2 +#endif +#ifndef IDABORT +#define IDABORT 3 +#endif +#ifndef IDRETRY +#define IDRETRY 4 +#endif +#ifndef IDIGNORE +#define IDIGNORE 5 +#endif +#ifndef IDYES +#define IDYES 6 +#endif +#ifndef IDNO +#define IDNO 7 +#endif + +inline void DebugBreak() {} + +#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*/) +{ + const std::string normalized = NormalizeWin32PathForHost(p); + return mkdir(normalized.c_str(), 0755) == 0; +} +inline int CreateDirectoryA(const char *p, void * /*attrs*/) +{ + const std::string normalized = NormalizeWin32PathForHost(p); + return mkdir(normalized.c_str(), 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) +{ + // 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); +} + +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 and GetFileAttributes are provided by file_compat.h. + +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..54f3f961f29 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/compat/win32_shims/winsock.h @@ -0,0 +1,291 @@ +#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 +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 +#define WSACleanup() (0) +#endif + +#ifndef WSAEWOULDBLOCK +#define WSAEWOULDBLOCK EAGAIN +#endif + +#ifndef WSAEINTR +#define WSAEINTR EINTR +#endif + +#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 +#define WSAEINPROGRESS EINPROGRESS +#endif + +#ifndef WSAEALREADY +#define WSAEALREADY EALREADY +#endif + +#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 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 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" 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/debug/debug_debug.cpp b/Core/Libraries/Source/debug/debug_debug.cpp index 291a06aff27..4f44828839d 100644 --- a/Core/Libraries/Source/debug/debug_debug.cpp +++ b/Core/Libraries/Source/debug/debug_debug.cpp @@ -33,6 +33,10 @@ #include "internal_io.h" #include #include +// TheSuperHackers @build bobtista 12/06/2026 _ReturnAddress intrinsic for the x64 frame capture. +#if defined(_MSC_VER) +#include +#endif #include #include // needed for placement new prototype @@ -306,12 +310,16 @@ bool Debug::SkipNext() // do not implement this function inline, we do need // a valid frame pointer here! unsigned help; -#if defined(_MSC_VER) +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(__i386__)) _asm { mov eax,[ebp+4] // return address mov help,eax }; +#elif defined(_MSC_VER) + // TheSuperHackers @build bobtista 12/06/2026 MSVC x64 has no inline asm; the intrinsic returns + // this function's return address (the [ebp+4] equivalent), truncated to the 32-bit frame key. + help = (unsigned)(size_t)_ReturnAddress(); #elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(_M_IX86)) // GCC/Clang inline assembly for x86-32 __asm__ __volatile__( @@ -434,8 +442,10 @@ bool Debug::AssertDone() } break; case IDRETRY: -#if defined(_MSC_VER) +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(__i386__)) _asm int 0x03 +#elif defined(_MSC_VER) + __debugbreak(); #elif defined(__GNUC__) __builtin_trap(); #else @@ -708,8 +718,10 @@ bool Debug::CrashDone(bool die) } break; case IDRETRY: -#if defined(_MSC_VER) +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(__i386__)) _asm int 0x03 +#elif defined(_MSC_VER) + __debugbreak(); #elif defined(__GNUC__) __builtin_trap(); #else diff --git a/Core/Libraries/Source/debug/debug_except.cpp b/Core/Libraries/Source/debug/debug_except.cpp index a8c5a286757..344e1fef759 100644 --- a/Core/Libraries/Source/debug/debug_except.cpp +++ b/Core/Libraries/Source/debug/debug_except.cpp @@ -110,7 +110,13 @@ void DebugExceptionhandler::LogExceptionLocation(Debug &dbg, struct _EXCEPTION_P struct _CONTEXT &ctx=*exptr->ContextRecord; char buf[512]; + // TheSuperHackers @build bobtista 12/06/2026 x64 _CONTEXT uses Rip; GetSymbol takes a 32-bit + // address, so the high half is truncated (the real x64 walk uses StackWalk64 elsewhere). +#if defined(_M_X64) || defined(__x86_64__) + DebugStackwalk::Signature::GetSymbol((unsigned)ctx.Rip,buf,sizeof(buf)); +#else DebugStackwalk::Signature::GetSymbol(ctx.Eip,buf,sizeof(buf)); +#endif dbg << "Exception occured at\n" << buf << "."; } @@ -118,6 +124,36 @@ void DebugExceptionhandler::LogRegisters(Debug &dbg, struct _EXCEPTION_POINTERS { struct _CONTEXT &ctx=*exptr->ContextRecord; + // TheSuperHackers @build bobtista 12/06/2026 x64 _CONTEXT exposes the 64-bit R* registers (the + // Debug stream has unsigned __int64 support); the x86 E* dump is preserved verbatim under its guard. +#if defined(_M_X64) || defined(__x86_64__) + dbg << Debug::FillChar('0') + << Debug::Hex() + << "RAX:" << Debug::Width(16) << ctx.Rax + << " RBX:" << Debug::Width(16) << ctx.Rbx + << " RCX:" << Debug::Width(16) << ctx.Rcx << "\n" + << "RDX:" << Debug::Width(16) << ctx.Rdx + << " RSI:" << Debug::Width(16) << ctx.Rsi + << " RDI:" << Debug::Width(16) << ctx.Rdi << "\n" + << "RIP:" << Debug::Width(16) << ctx.Rip + << " RSP:" << Debug::Width(16) << ctx.Rsp + << " RBP:" << Debug::Width(16) << ctx.Rbp << "\n" + << "R8: " << Debug::Width(16) << ctx.R8 + << " R9: " << Debug::Width(16) << ctx.R9 + << " R10:" << Debug::Width(16) << ctx.R10 << "\n" + << "R11:" << Debug::Width(16) << ctx.R11 + << " R12:" << Debug::Width(16) << ctx.R12 + << " R13:" << Debug::Width(16) << ctx.R13 << "\n" + << "R14:" << Debug::Width(16) << ctx.R14 + << " R15:" << Debug::Width(16) << ctx.R15 << "\n" + << "Flags:" << Debug::Bin() << Debug::Width(32) << ctx.EFlags << Debug::Hex() << "\n" + << "CS:" << Debug::Width(4) << ctx.SegCs + << " DS:" << Debug::Width(4) << ctx.SegDs + << " SS:" << Debug::Width(4) << ctx.SegSs + << "\nES:" << Debug::Width(4) << ctx.SegEs + << " FS:" << Debug::Width(4) << ctx.SegFs + << " GS:" << Debug::Width(4) << ctx.SegGs << "\n" << Debug::FillChar() << Debug::Dec(); +#else dbg << Debug::FillChar('0') << Debug::Hex() << "EAX:" << Debug::Width(8) << ctx.Eax @@ -136,6 +172,7 @@ void DebugExceptionhandler::LogRegisters(Debug &dbg, struct _EXCEPTION_POINTERS << "\nES:" << Debug::Width(4) << ctx.SegEs << " FS:" << Debug::Width(4) << ctx.SegFs << " GS:" << Debug::Width(4) << ctx.SegGs << "\n" << Debug::FillChar() << Debug::Dec(); +#endif } void DebugExceptionhandler::LogFPURegisters(Debug &dbg, struct _EXCEPTION_POINTERS *exptr) @@ -148,6 +185,36 @@ void DebugExceptionhandler::LogFPURegisters(Debug &dbg, struct _EXCEPTION_POINTE return; } + // TheSuperHackers @build bobtista 12/06/2026 x64 stores the legacy x87 state in ctx.FltSave + // (XMM_SAVE_AREA32) rather than the x86 ctx.FloatSave (FLOATING_SAVE_AREA); long double is 8 bytes + // on MSVC x64 so the 80-bit->double conversion is x86-only. The x86 dump is preserved verbatim. +#if defined(_M_X64) || defined(__x86_64__) + XMM_SAVE_AREA32 &flt=ctx.FltSave; + dbg << Debug::Bin() << Debug::FillChar('0') + << "CW:" << Debug::Width(16) << (flt.ControlWord&0xffff) << "\n" + << "SW:" << Debug::Width(16) << (flt.StatusWord&0xffff) << "\n" + << "TW:" << Debug::Width(16) << (flt.TagWord&0xff) << "\n" + << Debug::Hex() + << "ErrOfs: " << Debug::Width(8) << flt.ErrorOffset + << " ErrSel: " << Debug::Width(8) << flt.ErrorSelector << "\n" + << "DataOfs: " << Debug::Width(8) << flt.DataOffset + << " DataSel: " << Debug::Width(8) << flt.DataSelector << "\n" + << "MxCsr: " << Debug::Width(8) << flt.MxCsr << "\n" + ; + + for (unsigned k=0;k<8;++k) + { + dbg << Debug::Dec() << "ST(" << k << ") "; + dbg.SetPrefixAndRadix("",16); + + BYTE *value=(BYTE *)&flt.FloatRegisters[k]; + for (unsigned i=0;i<10;i++) + dbg << Debug::Width(2) << value[i]; + + dbg << "\n"; + } + dbg << Debug::FillChar() << Debug::Dec(); +#else FLOATING_SAVE_AREA &flt=ctx.FloatSave; dbg << Debug::Bin() << Debug::FillChar('0') << "CW:" << Debug::Width(16) << (flt.ControlWord&0xffff) << "\n" @@ -181,6 +248,7 @@ void DebugExceptionhandler::LogFPURegisters(Debug &dbg, struct _EXCEPTION_POINTE dbg << "\n"; } dbg << Debug::FillChar() << Debug::Dec(); +#endif } // include exception dialog box @@ -195,7 +263,9 @@ static char regInfo[1024],verInfo[256]; // and this saves us from doing a stack walk twice static DebugStackwalk::Signature sig; -static BOOL CALLBACK ExceptionDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +// TheSuperHackers @build bobtista 12/06/2026 DLGPROC returns INT_PTR (64-bit on x64); BOOL no longer +// matches the DialogBoxIndirect signature on x64. INT_PTR is identical to BOOL's int ABI on x86. +static INT_PTR CALLBACK ExceptionDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { @@ -240,7 +310,11 @@ static BOOL CALLBACK ExceptionDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // address struct _CONTEXT &ctx=*exPtrs->ContextRecord; +#if defined(_M_X64) || defined(__x86_64__) + DebugStackwalk::Signature::GetSymbol((unsigned)ctx.Rip,regInfo,sizeof(regInfo)); +#else DebugStackwalk::Signature::GetSymbol(ctx.Eip,regInfo,sizeof(regInfo)); +#endif SendDlgItemMessage(hWnd,102,WM_SETTEXT,0,(LPARAM)regInfo); // stack @@ -396,7 +470,11 @@ LONG __stdcall DebugExceptionhandler::ExceptionFilter(struct _EXCEPTION_POINTERS dbg.m_stackWalk.StackWalk(sig,pExPtrs->ContextRecord); dbg << sig << "\n"; +#if defined(_M_X64) || defined(__x86_64__) + dbg << "Bytes around RIP:" << Debug::MemDump::Char(((char *)(pExPtrs->ContextRecord->Rip))-32,80); +#else dbg << "Bytes around EIP:" << Debug::MemDump::Char(((char *)(pExPtrs->ContextRecord->Eip))-32,80); +#endif dbg.FlushOutput(); diff --git a/Core/Libraries/Source/debug/debug_stack.cpp b/Core/Libraries/Source/debug/debug_stack.cpp index 8e0aca49557..a61c21855a5 100644 --- a/Core/Libraries/Source/debug/debug_stack.cpp +++ b/Core/Libraries/Source/debug/debug_stack.cpp @@ -335,6 +335,11 @@ bool DebugStackwalk::IsOldDbghelp() return g_oldDbghelp; } +// TheSuperHackers @build bobtista 12/06/2026 On x64 does #define StackWalk StackWalk64, +// which mangles this class method's name. The dbghelp API itself is called via the gDbg._StackWalk +// function pointer, not the macro, so undefining it is safe (and a no-op on x86). +#undef StackWalk + int DebugStackwalk::StackWalk(Signature &sig, struct _CONTEXT *ctx) { InitDbghelp(); @@ -356,15 +361,32 @@ int DebugStackwalk::StackWalk(Signature &sig, struct _CONTEXT *ctx) // Use the context struct if it was provided. if (ctx) { + // TheSuperHackers @build bobtista 12/06/2026 x64 _CONTEXT uses R* registers. This legacy + // core_debug walker still uses the 32-bit STACKFRAME/StackWalk, so x64 offsets are truncated + // here (stub); the real x64 walk lives in StackDump.cpp (StackWalk64/RtlCaptureContext). +#if defined(_M_X64) || defined(__x86_64__) + stackFrame.AddrPC.Offset = (DWORD)ctx->Rip; + stackFrame.AddrStack.Offset = (DWORD)ctx->Rsp; + stackFrame.AddrFrame.Offset = (DWORD)ctx->Rbp; +#else stackFrame.AddrPC.Offset = ctx->Eip; stackFrame.AddrStack.Offset = ctx->Esp; stackFrame.AddrFrame.Offset = ctx->Ebp; +#endif } else { // walk stack back using current call chain unsigned long reg_eip, reg_ebp, reg_esp; -#if defined(_MSC_VER) +#if defined(_M_X64) || defined(__x86_64__) + // TheSuperHackers @build bobtista 12/06/2026 x64 has no inline asm; capture the live context. + // Offsets are truncated into the 32-bit STACKFRAME (legacy walker stub - see note above). + CONTEXT selfCtx; + RtlCaptureContext(&selfCtx); + reg_eip = (unsigned long)selfCtx.Rip; + reg_ebp = (unsigned long)selfCtx.Rbp; + reg_esp = (unsigned long)selfCtx.Rsp; +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(__i386__)) __asm { here: @@ -391,6 +413,10 @@ int DebugStackwalk::StackWalk(Signature &sig, struct _CONTEXT *ctx) // Walk the stack by the requested number of return address iterations. bool skipFirst=!ctx; + // TheSuperHackers @build bobtista 12/06/2026 The 32-bit StackWalk + STACKFRAME signature is x86-only + // (on x64 _StackWalk resolves to StackWalk64, which needs STACKFRAME64 and the ...64 callbacks). This + // legacy core_debug walker is a stub on x64; the real x64 stack walk lives in StackDump.cpp. +#if !defined(_M_X64) && !defined(__x86_64__) while (sig.m_numAddr -) +# 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/Core/Tools/W3DView/RingSizePropPage.cpp b/Core/Tools/W3DView/RingSizePropPage.cpp index bd7c6e32e63..5d7e7b35e84 100644 --- a/Core/Tools/W3DView/RingSizePropPage.cpp +++ b/Core/Tools/W3DView/RingSizePropPage.cpp @@ -735,5 +735,5 @@ Is_LERP { float percent = (curr_time - last_time) / (next_time - last_time); float interpolated_value = last_value + ((next_value-last_value) * percent); - return bool(WWMath::Fabs (interpolated_value - curr_value) < WWMATH_EPSILON); + return bool(WWMath::Fabsf (interpolated_value - curr_value) < WWMATH_EPSILON); } diff --git a/Core/Tools/W3DView/SphereSizePropPage.cpp b/Core/Tools/W3DView/SphereSizePropPage.cpp index f46f07455d0..54648574745 100644 --- a/Core/Tools/W3DView/SphereSizePropPage.cpp +++ b/Core/Tools/W3DView/SphereSizePropPage.cpp @@ -555,5 +555,5 @@ Is_LERP { float percent = (curr_time - last_time) / (next_time - last_time); float interpolated_value = last_value + ((next_value-last_value) * percent); - return bool(WWMath::Fabs (interpolated_value - curr_value) < WWMATH_EPSILON); + return bool(WWMath::Fabsf (interpolated_value - curr_value) < WWMATH_EPSILON); } 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/interlocked_adapter.h b/Dependencies/Utility/Utility/interlocked_adapter.h index 126c82f1997..3b54f70884b 100644 --- a/Dependencies/Utility/Utility/interlocked_adapter.h +++ b/Dependencies/Utility/Utility/interlocked_adapter.h @@ -37,4 +37,23 @@ inline PVOID InterlockedCompareExchangePointer(PVOID volatile *Destination, PVOI return InterlockedCompareExchange(const_cast(Destination), Exchange, Comparand); } +#elif !defined(_WIN32) + +inline PVOID InterlockedExchangePointer(PVOID volatile *Target, PVOID Value) +{ + return __atomic_exchange_n(Target, Value, __ATOMIC_SEQ_CST); +} + +inline PVOID InterlockedCompareExchangePointer(PVOID volatile *Destination, PVOID Exchange, PVOID Comparand) +{ + __atomic_compare_exchange_n( + Destination, + &Comparand, + Exchange, + false, + __ATOMIC_SEQ_CST, + __ATOMIC_SEQ_CST); + return Comparand; +} + #endif diff --git a/Dependencies/Utility/Utility/intrin_compat.h b/Dependencies/Utility/Utility/intrin_compat.h index efea3e62c78..eadecab2a81 100644 --- a/Dependencies/Utility/Utility/intrin_compat.h +++ b/Dependencies/Utility/Utility/intrin_compat.h @@ -98,6 +98,13 @@ static inline uint64_t _rdtsc() return __builtin_readcyclecounter(); #elif defined(__has_builtin) && __has_builtin(__builtin_ia32_rdtsc) return __builtin_ia32_rdtsc(); +#elif defined(__aarch64__) + // TheSuperHackers @build bobtista 24/07/2026 GCC on aarch64 lacks + // __builtin_readcyclecounter; read the virtual counter directly. It is a + // monotonic, user-readable timer suitable for the profiler's timestamps. + uint64_t virtual_timer_value; + __asm__ __volatile__("mrs %0, cntvct_el0" : "=r"(virtual_timer_value)); + return virtual_timer_value; #else #error "No implementation for _rdtsc" #endif 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; } - diff --git a/Generals/Code/GameEngine/Include/GameClient/AnimateWindowManager.h b/Generals/Code/GameEngine/Include/GameClient/AnimateWindowManager.h index aad9e80b7a2..2ad5c53812c 100644 --- a/Generals/Code/GameEngine/Include/GameClient/AnimateWindowManager.h +++ b/Generals/Code/GameEngine/Include/GameClient/AnimateWindowManager.h @@ -192,6 +192,10 @@ class AnimateWindowManager : public SubsystemInterface ProcessAnimateWindowSlideFromTopFast *m_slideFromTopFast; ///< holds the process in wich the windows slide from the top,fast ProcessAnimateWindow *getProcessAnimate( AnimTypes animType); ///< returns the process for the kind of animation we need. + void updateStep(); ///< Runs a single base-rate step of all registered window animations + UnsignedInt m_lastUpdateTime; ///< Wall-clock time of the previous update, for frame-rate independent pacing + Real m_updateAccumulator; ///< Carries fractional base-rate steps between updates + }; //----------------------------------------------------------------------------- diff --git a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp index 6ff00eaf6db..e46997387c5 100644 --- a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -2367,7 +2367,7 @@ void Player::doBountyForKill(const Object* killer, const Object* victim) Int bounty = REAL_TO_INT_CEIL(costToBuild * m_cashBountyPercent); #else // TheSuperHackers @bugfix Stubbjax 20/02/2026 Subtract epsilon to ensure bounty is rounded up correctly. - Int bounty = ceil((costToBuild * m_cashBountyPercent) - WWMATH_EPSILON); + Int bounty = WWMath::Ceil((costToBuild * m_cashBountyPercent) - WWMATH_EPSILON); #endif if( bounty ) diff --git a/Generals/Code/GameEngine/Source/Common/System/BuildAssistant.cpp b/Generals/Code/GameEngine/Source/Common/System/BuildAssistant.cpp index 6f362160d5c..944d639315c 100644 --- a/Generals/Code/GameEngine/Source/Common/System/BuildAssistant.cpp +++ b/Generals/Code/GameEngine/Source/Common/System/BuildAssistant.cpp @@ -752,8 +752,8 @@ Bool BuildAssistant::isLocationClearOfObjects( const Coord3D *worldPos, if (myFactoryExitWidth>0) { myExitPos = *worldPos; checkMyExit = true; - Real c = (Real)cos(angle); - Real s = (Real)sin(angle); + Real c = (Real)WWMath::Cos(angle); + Real s = (Real)WWMath::Sin(angle); Real offset = build->getTemplateGeometryInfo().getMajorRadius() + myFactoryExitWidth/2.0f; myExitPos.x += c*offset; myExitPos.y += s*offset; @@ -787,8 +787,8 @@ Bool BuildAssistant::isLocationClearOfObjects( const Coord3D *worldPos, if (themFactoryExitWidth>0) { hisExitPos = *them->getPosition(); checkHisExit = true; - Real c = (Real)cos(them->getOrientation()); - Real s = (Real)sin(them->getOrientation()); + Real c = (Real)WWMath::Cos(them->getOrientation()); + Real s = (Real)WWMath::Sin(them->getOrientation()); Real offset = them->getGeometryInfo().getMajorRadius() + themFactoryExitWidth/2.0f; hisExitPos.x += c*offset; hisExitPos.y += s*offset; @@ -1405,7 +1405,7 @@ Bool BuildAssistant::moveObjectsForConstruction( const ThingTemplate *whatToBuil Bool anyUnmovables = false; MemoryPoolObjectHolder hold( iter ); - Real radius = sqrt(pow(gi.getMajorRadius(), 2) + pow(gi.getMinorRadius(), 2)); + Real radius = WWMath::Sqrt(WWMath::Pow(gi.getMajorRadius(), 2) + WWMath::Pow(gi.getMinorRadius(), 2)); radius *= 1.4f; // Fudge the distance, for( Object *them = iter->first(); them; them = iter->next() ) diff --git a/Generals/Code/GameEngine/Source/Common/System/Geometry.cpp b/Generals/Code/GameEngine/Source/Common/System/Geometry.cpp index 2e4c35a2421..66b04f57ea4 100644 --- a/Generals/Code/GameEngine/Source/Common/System/Geometry.cpp +++ b/Generals/Code/GameEngine/Source/Common/System/Geometry.cpp @@ -173,17 +173,17 @@ void GeometryInfo::calcPitches(const Coord3D& thisPos, const GeometryInfo& that, Coord3D thisCenter; getCenterPosition(thisPos, thisCenter); - Real dxy = sqrt(sqr(thatPos.x - thisCenter.x) + sqr(thatPos.y - thisCenter.y)); + Real dxy = WWMath::Sqrt(sqr(thatPos.x - thisCenter.x) + sqr(thatPos.y - thisCenter.y)); Real dz; /** @todo srj -- this could be better, by calcing it for all the corners, not just top-center and bottom-center... oh well */ dz = (thatPos.z + that.getMaxHeightAbovePosition()) - thisCenter.z; - maxPitch = atan2(dz, dxy); + maxPitch = WWMath::Atan2(dz, dxy); dz = (thatPos.z - that.getMaxHeightBelowPosition()) - thisCenter.z; - minPitch = atan2(dz, dxy); + minPitch = WWMath::Atan2(dz, dxy); } //============================================================================= @@ -279,8 +279,8 @@ void GeometryInfo::get2DBounds(const Coord3D& geomCenter, Real angle, Region2D& case GEOMETRY_BOX: { - Real c = (Real)cos(angle); - Real s = (Real)sin(angle); + Real c = (Real)WWMath::Cos(angle); + Real s = (Real)WWMath::Sin(angle); Real exc = m_majorRadius*c; Real eyc = m_minorRadius*c; Real exs = m_majorRadius*s; @@ -329,7 +329,7 @@ void GeometryInfo::clipPointToFootprint(const Coord3D& geomCenter, Coord3D& ptTo { Real dx = ptToClip.x - geomCenter.x; Real dy = ptToClip.y - geomCenter.y; - Real radius = sqrt(sqr(dx) + sqr(dy)); + Real radius = WWMath::Sqrt(sqr(dx) + sqr(dy)); if (radius > m_majorRadius) { Real ratio = m_majorRadius / radius; @@ -361,7 +361,7 @@ Bool GeometryInfo::isPointInFootprint(const Coord3D& geomCenter, const Coord3D& { Real dx = pt.x - geomCenter.x; Real dy = pt.y - geomCenter.y; - Real radius = sqrt(sqr(dx) + sqr(dy)); + Real radius = WWMath::Sqrt(sqr(dx) + sqr(dy)); return (radius <= m_majorRadius); break; } @@ -506,8 +506,8 @@ void GeometryInfo::calcBoundingStuff() case GEOMETRY_BOX: { - m_boundingCircleRadius = sqrt(sqr(m_majorRadius) + sqr(m_minorRadius)); - m_boundingSphereRadius = sqrt(sqr(m_majorRadius) + sqr(m_minorRadius) + sqr(m_height*0.5)); + m_boundingCircleRadius = WWMath::Sqrt(sqr(m_majorRadius) + sqr(m_minorRadius)); + m_boundingSphereRadius = WWMath::Sqrt(sqr(m_majorRadius) + sqr(m_minorRadius) + sqr(m_height*0.5)); break; } }; diff --git a/Generals/Code/GameEngine/Source/Common/System/Trig.cpp b/Generals/Code/GameEngine/Source/Common/System/Trig.cpp index b09c4cb55d8..7cd4808f1a2 100644 --- a/Generals/Code/GameEngine/Source/Common/System/Trig.cpp +++ b/Generals/Code/GameEngine/Source/Common/System/Trig.cpp @@ -29,117 +29,24 @@ #include "PreRTS.h" -#include -#include - #include "Lib/BaseType.h" #include "Lib/trig.h" -#define TWOPI 6.28318530718f -#define DEG2RAD 0.0174532925199f -#define TRIG_RES 4096 - -// the following are for fixed point ints with 12 fractional bits -#define INT_ONE 4096 -#define INT_TWOPI 25736 -#define INT_THREEPIOVERTWO 19302 -#define INT_PI 12868 -#define INT_HALFPI 6434 - -Real Sin(Real x) -{ - return sinf(x); -} - -Real Cos(Real x) -{ - return cosf(x); -} - -Real Tan(Real x) -{ - return tanf(x); -} - -Real ACos(Real x) -{ - return acosf(x); -} - -Real ASin(Real x) -{ - return asinf(x); -} +#if USE_DETERMINISTIC_MATH +#include "gmath.h" +#endif -#ifdef REGENERATE_TRIG_TABLES -void initTrig() +Real Sin(Real x) { return WWMath::Sinf(x); } +Real Cos(Real x) { return WWMath::Cosf(x); } +Real Tan(Real x) { return WWMath::Tanf(x); } +Real ACos(Real x) { return WWMath::Acosf(x); } +Real ASin(Real x) { return WWMath::Asinf(x); } +Real Sqrt(Real x) { - static Byte inited = FALSE; - Real angle, r; - int i; - - if (inited) - return; - - inited = TRUE; - - static int columns = 8; - int column = 0; - FILE *fp = fopen("trig.txt", "w"); - fprintf(fp, "static Int sinLookup[TRIG_RES] = {\n"); - for( i=0; i maxSteps) + { + steps = maxSteps; + } + + while (steps-- > 0) + { + updateStep(); + } +} + +void AnimateWindowManager::updateStep() { ProcessAnimateWindow *processAnim = nullptr; diff --git a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp index c13b9f89b30..4c45ce60037 100644 --- a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -1631,7 +1631,7 @@ void InGameUI::handleBuildPlacements() if (isInForceAttackMode()) { const Real snapRadians = DEG_TO_RADF(45); - angle = WWMath::Round(angle / snapRadians) * snapRadians; + angle = WWMath::Roundf(angle / snapRadians) * snapRadians; } } } diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp index f3b2f362db5..b24d74ceb63 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp @@ -711,7 +711,7 @@ Object *AI::findClosestEnemy( const Object *me, Real range, UnsignedInt qualifie } Real distSqr = ThePartitionManager->getDistanceSquared(me, theEnemy, FROM_BOUNDINGSPHERE_2D); - Real dist = sqrt(distSqr); + Real dist = WWMath::Sqrt(distSqr); Int modifier = dist/getAiData()->m_attackPriorityDistanceModifier; Int modPriority = curPriority-modifier; if (modPriority < 1) diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp index c91139780ea..676e45df147 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp @@ -1839,8 +1839,8 @@ void getHelicopterOffset( Coord3D& posOut, Int idx ) } Coord3D tempCtr = posOut; - posOut.x = tempCtr.x + (sin(angle) * radius); - posOut.y = tempCtr.y + (cos(angle) * radius); + posOut.x = tempCtr.x + (WWMath::Sin(angle) * radius); + posOut.y = tempCtr.y + (WWMath::Cos(angle) * radius); } diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp index 2472c2ffefc..f4e026bad3d 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp @@ -488,7 +488,7 @@ Object *AIPlayer::buildStructureNow(const ThingTemplate *bldgPlan, BuildListInfo { Coord3D rallyPoint; Bool gotOffset = false; - if (fabs(info->getRallyOffset()->x) > 1.0f || fabs(info->getRallyOffset()->y)>1.0f) { + if (WWMath::Fabs(info->getRallyOffset()->x) > 1.0f || WWMath::Fabs(info->getRallyOffset()->y)>1.0f) { gotOffset; } if (!exitInterface->getNaturalRallyPoint(rallyPoint)) { @@ -646,7 +646,7 @@ Object *AIPlayer::buildStructureWithDozer(const ThingTemplate *bldgPlan, BuildLi dx = dozer->getPosition()->x - pos.x; dy = dozer->getPosition()->y - pos.y; - Int count = sqrt(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2); + Int count = WWMath::Sqrt(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2); if (count<2) count = 2; Int i; color.green = 1; @@ -668,7 +668,7 @@ Object *AIPlayer::buildStructureWithDozer(const ThingTemplate *bldgPlan, BuildLi { Coord3D rallyPoint; Bool gotOffset = false; - if (fabs(info->getRallyOffset()->x) > 1.0f || fabs(info->getRallyOffset()->y)>1.0f) { + if (WWMath::Fabs(info->getRallyOffset()->x) > 1.0f || WWMath::Fabs(info->getRallyOffset()->y)>1.0f) { gotOffset; } if (!exitInterface->getNaturalRallyPoint(rallyPoint)) { @@ -1251,7 +1251,7 @@ Int AIPlayer::getPlayerSuperweaponValue(Coord3D *center, Int playerNdx, Real rad Real dx = center->x - pos.x; Real dy = center->y - pos.y; if (dx*dx+dy*dygetTemplate()->calcCostToBuild(pPlayer); if (pObj->isKindOf(KINDOF_COMMANDCENTER)) { @@ -2812,7 +2812,7 @@ void AIPlayer::computeCenterAndRadiusOfBase(Coord3D *center, Real *radius) Real radSqr = dx*dx+dy*dy; if (radSqr>maxRadSqr) maxRadSqr=radSqr; } - *radius = sqrt(maxRadSqr); + *radius = WWMath::Sqrt(maxRadSqr); } //---------------------------------------------------------------------------------------------------------- diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp index afb876af064..382237663b9 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp @@ -663,8 +663,8 @@ void AISkirmishPlayer::buildAIBaseDefenseStructure(const AsciiString &thingName, } if (angle > PI/3) break; - Real s = sin(angle); - Real c = cos(angle); + Real s = WWMath::Sin(angle); + Real c = WWMath::Cos(angle); // TheSuperHackers @info helmutbuhler 21/04/2025 This debug mutates the code to become CRC incompatible #if defined(RTS_DEBUG) || !RETAIL_COMPATIBLE_CRC @@ -1029,8 +1029,8 @@ void AISkirmishPlayer::adjustBuildList(BuildListInfo *list) angle += 3*PI/4; - Real s = sin(angle); - Real c = cos(angle); + Real s = WWMath::Sin(angle); + Real c = WWMath::Cos(angle); cur = list; while (cur) { diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index 7ce23ef377e..ccb6c616a28 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -524,7 +524,7 @@ StateReturnType AIRappelState::onEnter() obj->setLayer(layerAtDest); AIUpdateInterface *ai = obj->getAI(); - Real MAX_RAPPEL_RATE = fabs(TheGlobalData->m_gravity) * LOGICFRAMES_PER_SECOND * 2.5f; + Real MAX_RAPPEL_RATE = WWMath::Fabs(TheGlobalData->m_gravity) * LOGICFRAMES_PER_SECOND * 2.5f; m_rappelRate = -min(ai->getDesiredSpeed(), MAX_RAPPEL_RATE); return STATE_CONTINUE; @@ -3572,7 +3572,7 @@ StateReturnType AIAttackMoveToState::update() if (distSqr < sqr(ATTACK_CLOSE_ENOUGH_CELLS*PATHFIND_CELL_SIZE_F)) { return ret; } - DEBUG_LOG(("AIAttackMoveToState::update Distance from goal %f, retrying.", sqrt(distSqr))); + DEBUG_LOG(("AIAttackMoveToState::update Distance from goal %f, retrying.", WWMath::Sqrt(distSqr))); ret = STATE_CONTINUE; m_retryCount--; @@ -3802,16 +3802,16 @@ void AIFollowWaypointPathState::computeGoal(Bool useGroupOffsets) if (m_priorWaypoint) { dx = dest.x - m_priorWaypoint->getLocation()->x; dy = dest.y - m_priorWaypoint->getLocation()->y; - angle = atan2(dy, dx); + angle = WWMath::Atan2(dy, dx); Real deltaAngle = angle - m_angle; - Real s = sin(deltaAngle); - Real c = cos(deltaAngle); + Real s = WWMath::Sin(deltaAngle); + Real c = WWMath::Cos(deltaAngle); Real x = m_groupOffset.x * c - m_groupOffset.y * s; Real y = m_groupOffset.y * c + m_groupOffset.x * s; m_groupOffset.x = x; m_groupOffset.y = y; } else { - angle = atan2(dy, dx); + angle = WWMath::Atan2(dy, dx); } m_angle = angle; #endif @@ -4933,7 +4933,7 @@ StateReturnType AIAttackAimAtTargetState::update() //DEBUG_LOG(("AIM: desired %f, actual %f, delta %f, aimDelta %f, goalpos %f %f",rad2deg(obj->getOrientation() + relAngle),rad2deg(obj->getOrientation()),rad2deg(relAngle),rad2deg(aimDelta),victim->getPosition()->x,victim->getPosition()->y)); if (m_canTurnInPlace) { - if (fabs(relAngle) > aimDelta) + if (WWMath::Fabs(relAngle) > aimDelta) { Real desiredAngle = source->getOrientation() + relAngle; sourceAI->setLocomotorGoalOrientation(desiredAngle); @@ -4945,7 +4945,7 @@ StateReturnType AIAttackAimAtTargetState::update() sourceAI->setLocomotorGoalPositionExplicit(m_isAttackingObject ? *victim->getPosition() : *getMachineGoalPosition()); } - if (fabs(relAngle) < aimDelta /*&& !m_preAttackFrames*/ ) + if (WWMath::Fabs(relAngle) < aimDelta /*&& !m_preAttackFrames*/ ) { AIUpdateInterface* victimAI = victim ? victim->getAI() : nullptr; // add ourself as a targeter BEFORE calling isTemporarilyPreventingAimSuccess(). @@ -6989,7 +6989,7 @@ StateReturnType AIFaceState::update() Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, pos ); const Real REL_THRESH = 0.035f; // about 2 degrees. (getRelativeAngle2D is current only accurate to about 1.25 degrees) - if( fabs( relAngle ) < REL_THRESH ) + if( WWMath::Fabs( relAngle ) < REL_THRESH ) { return STATE_SUCCESS; } diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/TurretAI.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/TurretAI.cpp index ba41dd60151..cdf0802f400 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/TurretAI.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/TurretAI.cpp @@ -401,7 +401,7 @@ Bool TurretAI::friend_turnTowardsAngle(Real desiredAngle, Real rateModifier, Rea Real angleDiff = normalizeAngle(desiredAngle - actualAngle); // Are we close enough to the desired angle to just snap there? - if (fabs(angleDiff) < turnRate) + if (WWMath::Fabs(angleDiff) < turnRate) { // we are centered actualAngle = desiredAngle; @@ -424,7 +424,7 @@ Bool TurretAI::friend_turnTowardsAngle(Real desiredAngle, Real rateModifier, Rea if( m_angle != origAngle ) getOwner()->reactToTurretChange( m_whichTurret, origAngle, m_pitch ); - Bool aligned = fabs(m_angle - desiredAngle) <= relThresh; + Bool aligned = WWMath::Fabs(m_angle - desiredAngle) <= relThresh; return aligned; } @@ -442,7 +442,7 @@ Bool TurretAI::friend_turnTowardsPitch(Real desiredPitch, Real rateModifier) Real pitchRate = getPitchRate() * rateModifier; Real pitchDiff = normalizeAngle(desiredPitch - actualPitch); - if (fabs(pitchDiff) < pitchRate) + if (WWMath::Fabs(pitchDiff) < pitchRate) { // we are centered actualPitch = desiredPitch; @@ -1085,7 +1085,7 @@ StateReturnType TurretAIAimTurretState::update() turret->friend_setPositiveSweep(!turret->friend_getPositiveSweep()); Real angleDiff = normalizeAngle(relAngle - turret->getTurretAngle()); - turnAlignedToNemesis = (fabs(angleDiff) < sweep); + turnAlignedToNemesis = (WWMath::Fabs(angleDiff) < sweep); } Bool pitchAlignedToNemesis = true; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp b/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp index 6cea7c6227a..c17f6f8315a 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp @@ -270,7 +270,7 @@ void PolygonTrigger::updateBounds() const Real halfWidth = (m_bounds.hi.x - m_bounds.lo.x) / 2.0f; Real halfHeight = (m_bounds.hi.y + m_bounds.lo.y) / 2.0f; - m_radius = sqrt(halfHeight*halfHeight + halfWidth*halfWidth); + m_radius = WWMath::Sqrt(halfHeight*halfHeight + halfWidth*halfWidth); } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp index 00386047267..777676daee6 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp @@ -1469,7 +1469,7 @@ void makeAlignToNormalMatrix( Real angle, const Coord3D& pos, const Coord3D& nor /* It is extremely important that the resulting matrix is such that the xvector points in the angle we specified; specifically, - that atan2(xvec.y, xvec.x) == angle. So we must construct + that WWMath::Atan2(xvec.y, xvec.x) == angle. So we must construct the matrix carefully to ensure this! */ x.x = Cos( angle ); @@ -1490,7 +1490,7 @@ void makeAlignToNormalMatrix( Real angle, const Coord3D& pos, const Coord3D& nor x.normalize(); } - DEBUG_ASSERTCRASH(fabs(x.x*z.x + x.y*z.y + x.z*z.z)<0.0001,("dot is not zero (%f)",fabs(x.x*z.x + x.y*z.y + x.z*z.z))); + DEBUG_ASSERTCRASH(WWMath::Fabs(x.x*z.x + x.y*z.y + x.z*z.z)<0.0001,("dot is not zero (%f)",WWMath::Fabs(x.x*z.x + x.y*z.y + x.z*z.z))); // now computing the y vector is trivial. y.crossProduct( z, x, y ); @@ -1691,12 +1691,12 @@ PathfindLayerEnum TerrainLogic::getLayerForDestination(const Coord3D *pos) { Bridge *pBridge = getFirstBridge(); PathfindLayerEnum bestLayer = LAYER_GROUND; - Real bestDistance = fabs(pos->z - getGroundHeight(pos->x, pos->y)); + Real bestDistance = WWMath::Fabs(pos->z - getGroundHeight(pos->x, pos->y)); if (bestDistance > TheAI->pathfinder()->getWallHeight()/2) { // check wall. if (TheAI->pathfinder()->isPointOnWall(pos)) { - Real delta = fabs(pos->z-TheAI->pathfinder()->getWallHeight()); + Real delta = WWMath::Fabs(pos->z-TheAI->pathfinder()->getWallHeight()); if (deltaisPointOnBridge(pos) ) { - Real delta = fabs(pos->z-pBridge->getBridgeHeight(pos, nullptr)); + Real delta = WWMath::Fabs(pos->z-pBridge->getBridgeHeight(pos, nullptr)); if (deltagetLayer(); bestDistance = delta; @@ -1730,7 +1730,7 @@ PathfindLayerEnum TerrainLogic::getHighestLayerForDestination(const Coord3D *pos if (TheAI->pathfinder()->isPointOnWall(pos)) { Real delta = pos->z - TheAI->pathfinder()->getWallHeight(); // must be ABOVE (or on) the wall for this call. (srj) - if (delta >= 0 && fabs(delta) < fabs(bestDistance)) { + if (delta >= 0 && WWMath::Fabs(delta) < WWMath::Fabs(bestDistance)) { bestLayer = (PathfindLayerEnum)LAYER_WALL; bestDistance = delta; } @@ -1745,7 +1745,7 @@ PathfindLayerEnum TerrainLogic::getHighestLayerForDestination(const Coord3D *pos if (pBridge->isPointOnBridge(pos) ) { Real delta = pos->z - pBridge->getBridgeHeight(pos, nullptr); // must be ABOVE (or on) the bridge for this call. (srj) - if (delta >= 0 && fabs(delta) < fabs(bestDistance)) { + if (delta >= 0 && WWMath::Fabs(delta) < WWMath::Fabs(bestDistance)) { bestLayer = pBridge->getLayer(); bestDistance = delta; } @@ -1794,7 +1794,7 @@ Bool TerrainLogic::objectInteractsWithBridgeLayer(Object *obj, Int layer, Bool c if (match) { Real bridgeHeight = pBridge->getBridgeHeight(obj->getPosition(), nullptr); - Real delta = fabs(obj->getPosition()->z-bridgeHeight); + Real delta = WWMath::Fabs(obj->getPosition()->z-bridgeHeight); if (delta>LAYER_Z_CLOSE_ENOUGH_F) { return false; } @@ -1843,7 +1843,7 @@ Bool TerrainLogic::objectInteractsWithBridgeEnd(Object *obj, Int layer) const if (match) { Real bridgeHeight = pBridge->getBridgeHeight(obj->getPosition(), nullptr); - Real delta = fabs(obj->getPosition()->z-bridgeHeight); + Real delta = WWMath::Fabs(obj->getPosition()->z-bridgeHeight); if (delta>LAYER_Z_CLOSE_ENOUGH_F) { return false; @@ -2073,10 +2073,10 @@ Coord3D TerrainLogic::findClosestEdgePoint ( const Coord3D *closestTo ) const getExtent( &mapExtent ); Real distances[4]; - distances[0] = fabs( closestTo->y - mapExtent.lo.y );//top - distances[1] = fabs( closestTo->x - mapExtent.hi.x );//right - distances[2] = fabs( closestTo->y - mapExtent.hi.y );//bottom - distances[3] = fabs( closestTo->x - mapExtent.lo.x );//left + distances[0] = WWMath::Fabs( closestTo->y - mapExtent.lo.y );//top + distances[1] = WWMath::Fabs( closestTo->x - mapExtent.hi.x );//right + distances[2] = WWMath::Fabs( closestTo->y - mapExtent.hi.y );//bottom + distances[3] = WWMath::Fabs( closestTo->x - mapExtent.lo.x );//left Real bestDistance = distances[0]; Int bestDistanceIndex = 0; for( Int lameIndex = 1; lameIndex < 4; lameIndex++ ) @@ -2387,7 +2387,7 @@ void TerrainLogic::setWaterHeight( const WaterHandle *water, Real height, Real d center.z = 0.0f; // irrelavant // the max radius to scan around us is the diagonal of the bounding region - Real maxDist = sqrt( affectedRegion.width() * affectedRegion.width() + + Real maxDist = WWMath::Sqrt( affectedRegion.width() * affectedRegion.width() + affectedRegion.height() * affectedRegion.height() ); // scan the objects in the area of the water affected diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp index 7c4bc633863..66e57aa0036 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp @@ -169,24 +169,24 @@ static Bool calcTrajectory( Real dz = end.z - start.z; // calculating the angle is trivial. - angle = atan2(dy, dx); + angle = WWMath::Atan2(dy, dx); // calculating the pitch requires a bit more effort. Real horizDistSqr = sqr(dx) + sqr(dy); - Real horizDist = sqrt(horizDistSqr); + Real horizDist = WWMath::Sqrt(horizDistSqr); // calc the two possible pitches that will cover the given horizontal range. // (this is actually only true if dz==0, but is a good first guess) - Real gravity = fabs(TheGlobalData->m_gravity); + Real gravity = WWMath::Fabs(TheGlobalData->m_gravity); Real gravityTwoDZ = gravity * 2.0f * dz; // let's start by aiming directly for it. we know this isn't right (unless gravity // is zero, which it's not) but is a good starting point... - Real theta = atan2(dz, horizDist); + Real theta = WWMath::Atan2(dz, horizDist); // if the angle isn't pretty shallow, we can get a better initial guess by using // the code below... const Real SHALLOW_ANGLE = 0.5f * PI / 180.0f; - if (fabs(theta) > SHALLOW_ANGLE) + if (WWMath::Fabs(theta) > SHALLOW_ANGLE) { Real t = horizDist / velocity; Real vz = (dz/t + 0.5f*gravity*t); @@ -287,7 +287,7 @@ static Bool calcTrajectory( #endif vx = velocity*cosPitches[preferred]; - Real actualRange = (vx*(vz + sqrt(root)))/gravity; + Real actualRange = (vx*(vz + WWMath::Sqrt(root)))/gravity; const Real CLOSE_ENOUGH_RANGE = 5.0f; if (tooClose || (actualRange < horizDist - CLOSE_ENOUGH_RANGE)) { @@ -366,7 +366,7 @@ void DumbProjectileBehavior::projectileFireAtObjectOrPosition( const Object *vic // Some weapons want to scale their start speed to the range Real minRange = detWeap->getMinimumAttackRange(); Real maxRange = detWeap->getUnmodifiedAttackRange(); - Real range = sqrt(ThePartitionManager->getDistanceSquared( projectile, &victimPosToUse, FROM_CENTER_2D ) ); + Real range = WWMath::Sqrt(ThePartitionManager->getDistanceSquared( projectile, &victimPosToUse, FROM_CENTER_2D ) ); Real rangeRatio = (range - minRange) / (maxRange - minRange); m_flightPathSpeed = (rangeRatio * (weaponSpeed - minWeaponSpeed)) + minWeaponSpeed; } @@ -441,7 +441,7 @@ Bool DumbProjectileBehavior::calcFlightPath(Bool recalcNumSegments) if (recalcNumSegments) { Real flightDistance = flightCurve.getApproximateLength(); - m_flightPathSegments = ceil( flightDistance / m_flightPathSpeed ); + m_flightPathSegments = WWMath::Ceil( flightDistance / m_flightPathSpeed ); } flightCurve.getSegmentPoints( m_flightPathSegments, &m_flightPath ); DEBUG_ASSERTCRASH(m_flightPathSegments == m_flightPath.size(), ("m_flightPathSegments mismatch")); @@ -596,7 +596,7 @@ UpdateSleepTime DumbProjectileBehavior::update() Real distVictimMovedSqr = sqr(delta.x) + sqr(delta.y) + sqr(delta.z); if (distVictimMovedSqr > 0.1f) { - Real distVictimMoved = sqrtf(distVictimMovedSqr); + Real distVictimMoved = WWMath::Sqrtf(distVictimMovedSqr); if (distVictimMoved > d->m_flightPathAdjustDistPerFrame) distVictimMoved = d->m_flightPathAdjustDistPerFrame; delta.normalize(); diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp index b37cf0f2ab3..335e4bc02de 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp @@ -232,7 +232,7 @@ void GenerateMinefieldBehavior::placeMinesAlongLine(const Coord3D& posStart, con Real dx = posEnd.x - posStart.x; Real dy = posEnd.y - posStart.y; - Real len = sqrt(sqr(dx) + sqr(dy)); + Real len = WWMath::Sqrt(sqr(dx) + sqr(dy)); Real mineRadius = mineTemplate->getTemplateGeometryInfo().getBoundingCircleRadius(); Real mineDiameter = mineRadius * 2.0f; Real mineJitter = mineRadius*d->m_randomJitter; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/MinefieldBehavior.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/MinefieldBehavior.cpp index 5a4b2727060..f0d474ebb35 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/MinefieldBehavior.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/MinefieldBehavior.cpp @@ -562,7 +562,7 @@ void MinefieldBehavior::setScootParms(const Coord3D& start, const Coord3D& end) if (start.z > endOnGround.z) { // figure out how long it will take to fall, and replace scoot time with that - UnsignedInt fallingTime = REAL_TO_INT_CEIL(sqrtf(2.0f * (start.z - endOnGround.z) / fabs(TheGlobalData->m_gravity))); + UnsignedInt fallingTime = REAL_TO_INT_CEIL(WWMath::Sqrtf(2.0f * (start.z - endOnGround.z) / WWMath::Fabs(TheGlobalData->m_gravity))); // we can scoot after we land, but don't want to stop scooting before we land if (scootFromStartingPointTime < fallingTime) scootFromStartingPointTime = fallingTime; @@ -580,8 +580,8 @@ void MinefieldBehavior::setScootParms(const Coord3D& start, const Coord3D& end) Real dx = endOnGround.x - start.x; Real dy = endOnGround.y - start.y; Real dz = endOnGround.z - start.z; - Real dist = sqrt(sqr(dx) + sqr(dy)); - if (dist <= 0.1f && fabs(dz) <= 0.1f) + Real dist = WWMath::Sqrt(sqr(dx) + sqr(dy)); + if (dist <= 0.1f && WWMath::Fabs(dz) <= 0.1f) { obj->setPosition(&endOnGround); m_scootFramesLeft = 0; @@ -590,7 +590,7 @@ void MinefieldBehavior::setScootParms(const Coord3D& start, const Coord3D& end) { Real t = (Real)scootFromStartingPointTime; Real scootFromStartingPointSpeed = dist / t; - Real accelMag = fabs(2.0f * (dist - scootFromStartingPointSpeed*t)/sqr(t)); + Real accelMag = WWMath::Fabs(2.0f * (dist - scootFromStartingPointSpeed*t)/sqr(t)); Real dxNorm = (dist <= 0.1f) ? 0.0f : (dx / dist); Real dyNorm = (dist <= 0.1f) ? 0.0f : (dy / dist); m_scootVel.x = dxNorm * scootFromStartingPointSpeed; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp index 20bdea05e43..9a2c8f66c29 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp @@ -299,7 +299,7 @@ void SlowDeathBehavior::beginSlowDeath(const DamageInfo *damageInfo) physics->setExtraBounciness(-1.0); // we don't want this guy to bounce at all physics->setExtraFriction(-3 * SECONDS_PER_LOGICFRAME_REAL); // reduce his ground friction a bit physics->setAllowBouncing(true); - Real orientation = atan2(force.y, force.x); + Real orientation = WWMath::Atan2(force.y, force.x); physics->setAngles(orientation, 0, 0); obj->getDrawable()->setModelConditionState(MODELCONDITION_EXPLODED_FLAILING); m_flags |= (1<getPosition()->z) >= d->m_paraOpenDist) + if (WWMath::Fabs(m_startZ - parachute->getPosition()->z) >= d->m_paraOpenDist) { m_opened = true; parachute->clearAndSetModelConditionState(MODELCONDITION_FREEFALL, MODELCONDITION_PARACHUTING); diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index d751f77dc73..ca49b168ea1 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -84,7 +84,7 @@ static Real calcSlowDownDist(Real curSpeed, Real desiredSpeed, Real maxBraking) if (delta <= 0) return 0.0f; - Real dist = (sqr(delta) / fabs(maxBraking)) * 0.5f; + Real dist = (sqr(delta) / WWMath::Fabs(maxBraking)) * 0.5f; // use a little fudge so that things can stop "on a dime" more easily... const Real FUDGE = 1.05f; @@ -95,14 +95,14 @@ static Real calcSlowDownDist(Real curSpeed, Real desiredSpeed, Real maxBraking) inline Bool isNearlyZero(Real a) { const Real TINY_EPSILON = 0.001f; - return fabs(a) < TINY_EPSILON; + return WWMath::Fabs(a) < TINY_EPSILON; } //----------------------------------------------------------------------------- inline Bool isNearly(Real a, Real val) { const Real TINY_EPSILON = 0.001f; - return fabs(a - val) < TINY_EPSILON; + return WWMath::Fabs(a - val) < TINY_EPSILON; } //----------------------------------------------------------------------------- @@ -141,7 +141,7 @@ static Real tryToRotateVector3D( } } - if (fabs(angleBetween) <= maxAngle) + if (WWMath::Fabs(angleBetween) <= maxAngle) { // close enough actualDir = goalDir; @@ -231,9 +231,9 @@ static void calcDirectionToApplyThrust( Bool foundSolution = false; Real distToGoalSqr = vecToGoal.Length2(); - Real distToGoal = sqrt(distToGoalSqr); + Real distToGoal = WWMath::Sqrt(distToGoalSqr); Real curVelMagSqr = curVel.Length2(); - Real curVelMag = sqrt(curVelMagSqr); + Real curVelMag = WWMath::Sqrt(curVelMagSqr); Real maxAccelSqr = sqr(maxAccel); Real denom = curVelMagSqr - maxAccelSqr; @@ -971,7 +971,7 @@ void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalP Real dx = goalPos.x - obj->getPosition()->x; Real dy = goalPos.y - obj->getPosition()->y; Real dz = goalPos.z - obj->getPosition()->z; - Real dist = sqrt(dx*dx+dy*dy); + Real dist = WWMath::Sqrt(dx*dx+dy*dy); if (dist>onPathDistToGoal) { if (!obj->isKindOf(KINDOF_PROJECTILE) && dist>2*onPathDistToGoal) @@ -1083,7 +1083,7 @@ void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalP // Projectiles never stop braking once they start. jba. obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ) ); // Projectiles cheat in 3 dimensions. - dist = sqrt(dx*dx+dy*dy+dz*dz); + dist = WWMath::Sqrt(dx*dx+dy*dy+dz*dz); Real vel = physics->getVelocityMagnitude(); if (vel < MIN_VEL) vel = MIN_VEL; @@ -1107,7 +1107,7 @@ void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalP // Normalize. if (dist > 0.001f) { - Real vel = fabs(physics->getForwardSpeed2D()); + Real vel = WWMath::Fabs(physics->getForwardSpeed2D()); if (vel < MIN_VEL) vel = MIN_VEL; if (vel > dist) @@ -1152,7 +1152,7 @@ void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, // Modulate speed according to turning. The more we have to turn, the slower we go // const Real QUAETERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / QUAETERPI; + Real angleCoeff = (Real)WWMath::Fabs( relAngle ) / QUAETERPI; if (angleCoeff > 1.0f) angleCoeff = 1.0; @@ -1223,7 +1223,7 @@ void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, see how much force we really need to achieve our goal speed... */ Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) + if (WWMath::Fabs(accelForce) > WWMath::Fabs(maxForceNeeded)) accelForce = maxForceNeeded; const Coord3D *dir = obj->getUnitDirectionVector2D(); @@ -1258,7 +1258,7 @@ void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, Real angle = obj->getOrientation(); // Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); // Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real desiredAngle = WWMath::Atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); Real relAngle = stdAngleDiff(desiredAngle, angle); Bool moveBackwards = false; @@ -1275,14 +1275,14 @@ void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, #if 1 if (actualSpeed==0.0f) { setFlag(MOVING_BACKWARDS, false); - if (m_template->m_canMoveBackward && fabs(relAngle) > PI/2) { + if (m_template->m_canMoveBackward && WWMath::Fabs(relAngle) > PI/2) { setFlag(MOVING_BACKWARDS, true ); setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); } } if (getFlag(MOVING_BACKWARDS)) { - if (fabs(relAngle) < PI/2) { + if (WWMath::Fabs(relAngle) < PI/2) { moveBackwards = false; setFlag(MOVING_BACKWARDS, false); } else { @@ -1298,7 +1298,7 @@ void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, #endif const Real SMALL_TURN = PI / 20.0f; - if ((Real)fabs( relAngle ) > SMALL_TURN) + if ((Real)WWMath::Fabs( relAngle ) > SMALL_TURN) { if (desiredSpeed>turnSpeed) { @@ -1323,7 +1323,7 @@ void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Real FIFTEEN_DEGREES = PI / 12.0f; const Real PROJECT_FRAMES = LOGICFRAMES_PER_SECOND/2; // Project out 1/2 second. - if (fabs( relAngle ) > FIFTEEN_DEGREES) + if (WWMath::Fabs( relAngle ) > FIFTEEN_DEGREES) { // If we're turning more than 10 degrees, check & see if we're moving into "impassable territory" Real distance = PROJECT_FRAMES * (goalSpeed+actualSpeed)/2.0f; @@ -1462,7 +1462,7 @@ void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, see how much force we really need to achieve our goal speed... */ Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) + if (WWMath::Fabs(accelForce) > WWMath::Fabs(maxForceNeeded)) accelForce = maxForceNeeded; //DEBUG_LOG(("Braking %d, actualSpeed %f, goalSpeed %f, delta %f, accel %f", getFlag(IS_BRAKING), @@ -1533,7 +1533,7 @@ Bool Locomotor::fixInvalidPosition(Object* obj, PhysicsBehavior *physics) //physics->clearAcceleration(); if (dot<0) { - dot = sqrt(-dot); + dot = WWMath::Sqrt(-dot); correctionNormalized.x *= dot*physics->getMass(); correctionNormalized.y *= dot*physics->getMass(); physics->applyMotiveForce(&correctionNormalized); @@ -1597,7 +1597,7 @@ void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, c Real angle = obj->getOrientation(); // Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); // Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real desiredAngle = WWMath::Atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); if (m_template->m_wanderWidthFactor != 0.0f) { Real angleLimit = PI/8 * m_template->m_wanderWidthFactor; @@ -1623,7 +1623,7 @@ void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, c // Modulate speed according to turning. The more we have to turn, the slower we go // const Real QUARTERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); + Real angleCoeff = (Real)WWMath::Fabs( relAngle ) / (QUARTERPI); if (angleCoeff > 1.0f) angleCoeff = 1.0; @@ -1653,7 +1653,7 @@ void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, c see how much force we really need to achieve our goal speed... */ Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) + if (WWMath::Fabs(accelForce) > WWMath::Fabs(maxForceNeeded)) accelForce = maxForceNeeded; const Coord3D *dir = obj->getUnitDirectionVector2D(); @@ -1695,7 +1695,7 @@ void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, if (dz*dz > sqr(PATHFIND_CELL_SIZE_F)) { setFlag(CLIMBING, true); } - if (fabs(dz)<1) { + if (WWMath::Fabs(dz)<1) { setFlag(CLIMBING, false); } @@ -1715,7 +1715,7 @@ void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, moveBackwards = true; } - Real groundSlope = fabs(delta.z - pos.z); + Real groundSlope = WWMath::Fabs(delta.z - pos.z); if (groundSlope<1.0f) groundSlope = 1.0f; if (groundSlope>1.0f) { @@ -1730,7 +1730,7 @@ void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, Real angle = obj->getOrientation(); // Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); // Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real desiredAngle = WWMath::Atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); Real relAngle = stdAngleDiff(desiredAngle, angle); if (moveBackwards) { @@ -1744,7 +1744,7 @@ void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, // Modulate speed according to turning. The more we have to turn, the slower we go // const Real QUARTERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); + Real angleCoeff = (Real)WWMath::Fabs( relAngle ) / (QUARTERPI); if (angleCoeff > 1.0f) angleCoeff = 1.0; @@ -1786,7 +1786,7 @@ void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, see how much force we really need to achieve our goal speed... */ Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) + if (WWMath::Fabs(accelForce) > WWMath::Fabs(maxForceNeeded)) accelForce = maxForceNeeded; const Coord3D *dir = obj->getUnitDirectionVector2D(); @@ -1813,7 +1813,7 @@ void Locomotor::moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, Real dx = goalPos.x - pos->x; Real dy = goalPos.y - pos->y; Real dz = goalPos.z - pos->z; - if (fabs(dz) > m_circleThresh) + if (WWMath::Fabs(dz) > m_circleThresh) { // aim for the spot on the opposite side of the circle. @@ -1821,7 +1821,7 @@ void Locomotor::moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, Real angleTowardPos = (isNearlyZero(dx) && isNearlyZero(dy)) ? obj->getOrientation() : - atan2(dy, dx); + WWMath::Atan2(dy, dx); Real aimDir = (PI - PI/8); angleTowardPos += aimDir; @@ -1910,7 +1910,7 @@ void Locomotor::moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, // so we tend to "level out" at that height. we don't use this till // below, but go ahead and calc it now... Real MAX_VERTICAL_DAMP_RANGE = m_preferredHeight * 0.5; - delta = fabs(delta); + delta = WWMath::Fabs(delta); if (delta > MAX_VERTICAL_DAMP_RANGE) delta = MAX_VERTICAL_DAMP_RANGE; zDirDamping = 1.0f - (delta / MAX_VERTICAL_DAMP_RANGE); @@ -2027,7 +2027,7 @@ Real Locomotor::calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real cu // see how far we need to slow to dead stop, given max braking Real desiredAccel; const Real TINY_ACCEL = 0.001f; - if (fabs(maxAccel) > TINY_ACCEL) + if (WWMath::Fabs(maxAccel) > TINY_ACCEL) { Real deltaZ = preferredHeight - curZ; // calc how far it will take for us to go from cur speed to zero speed, at max accel. @@ -2035,14 +2035,14 @@ Real Locomotor::calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real cu // in theory, the above is the correct calculation, but in practice, // doesn't work in some situations (eg, opening of USA01 map). Why, I dunno. // But for now I have gone back to the old, looks-incorrect-to-me-but-works calc. (srj) - Real brakeDist = (sqr(curVelZ) / fabs(maxAccel)); - if (fabs(brakeDist) > fabs(deltaZ)) + Real brakeDist = (sqr(curVelZ) / WWMath::Fabs(maxAccel)); + if (WWMath::Fabs(brakeDist) > WWMath::Fabs(deltaZ)) { // if the dist-to-accel (or dist-to-brake) is further than the dist-to-go, // use the max accel. desiredAccel = maxAccel; } - else if (fabs(curVelZ) > m_template->m_speedLimitZ) + else if (WWMath::Fabs(curVelZ) > m_template->m_speedLimitZ) { // or, if we're going too fast, limit it here. desiredAccel = m_template->m_speedLimitZ - curVelZ; @@ -2116,8 +2116,8 @@ PhysicsTurningType Locomotor::rotateObjAroundLocoPivot(Object* obj, const Coord3 Real dx =goalPos.x - turnPos.x; Real dy = goalPos.y - turnPos.y; // If we are very close to the goal, we twitch due to rounding error. So just return. jba. - if (fabs(dx)<0.1f && fabs(dy)<0.1f) return TURN_NONE; - Real desiredAngle = atan2(dy, dx); + if (WWMath::Fabs(dx)<0.1f && WWMath::Fabs(dy)<0.1f) return TURN_NONE; + Real desiredAngle = WWMath::Atan2(dy, dx); Real amount = stdAngleDiff(desiredAngle, angle); if (relAngle) *relAngle = amount; if (amount>maxTurnRate) { @@ -2139,7 +2139,7 @@ PhysicsTurningType Locomotor::rotateObjAroundLocoPivot(Object* obj, const Coord3 // so, the thing is, we want to rotate ourselves so that our *center* is rotated // by the given amount, but the rotation must be around turnPos. so do a little // back-calculation. - Real angleDesiredForTurnPos = atan2(desiredPos.y - turnPos.y, desiredPos.x - turnPos.x); + Real angleDesiredForTurnPos = WWMath::Atan2(desiredPos.y - turnPos.y, desiredPos.x - turnPos.x); amount = angleDesiredForTurnPos - angle; #endif /// @todo srj -- there's probably a more efficient & more direct way to do this. find it. @@ -2155,7 +2155,7 @@ PhysicsTurningType Locomotor::rotateObjAroundLocoPivot(Object* obj, const Coord3 } else { - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real desiredAngle = WWMath::Atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); Real amount = stdAngleDiff(desiredAngle, angle); if (relAngle) *relAngle = amount; if (amount>maxTurnRate) { @@ -2333,8 +2333,8 @@ void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, //fabs(goalPos.y - pos->y),fabs(goalPos.x - pos->x), //fabs(goalPos.y - pos->y)/goalSpeed,fabs(goalPos.x - pos->x)/goalSpeed)); if (getFlag(ULTRA_ACCURATE) && - fabs(goalPos.y - pos->y) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor && - fabs(goalPos.x - pos->x) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor) + WWMath::Fabs(goalPos.y - pos->y) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor && + WWMath::Fabs(goalPos.x - pos->x) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor) { // don't turn, just slide in the right direction physics->setTurning(TURN_NONE); @@ -2373,7 +2373,7 @@ void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, see how much force we really need to achieve our goal speed... */ Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) + if (WWMath::Fabs(accelForce) > WWMath::Fabs(maxForceNeeded)) accelForce = maxForceNeeded; Coord3D force; @@ -2488,7 +2488,7 @@ void Locomotor::maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physi Real angleTowardMaintainPos = (isNearlyZero(dx) && isNearlyZero(dy)) ? obj->getOrientation() : - atan2(dy, dx); + WWMath::Atan2(dy, dx); Real aimDir = (PI - PI/8); if (turnRadius < 0) @@ -2522,7 +2522,7 @@ void Locomotor::maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physi // Real minSpeed = max( 1.0E-10f, m_template->m_minSpeed ); Real speedDelta = minSpeed - actualSpeed; - if (fabs(speedDelta) > minSpeed) + if (WWMath::Fabs(speedDelta) > minSpeed) { Real mass = physics->getMass(); Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); @@ -2533,7 +2533,7 @@ void Locomotor::maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physi see how much force we really need to achieve our goal speed... */ Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) + if (WWMath::Fabs(accelForce) > WWMath::Fabs(maxForceNeeded)) accelForce = maxForceNeeded; const Coord3D *dir = obj->getUnitDirectionVector2D(); diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 63b25332dc2..b123dfa8473 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -1647,13 +1647,13 @@ inline Bool isPosDifferent(const Coord3D* a, const Coord3D* b) // so we must put in some cleverness... const Real THRESH = 0.01f; - if (fabs(a->x - b->x) > THRESH) + if (WWMath::Fabs(a->x - b->x) > THRESH) return true; - if (fabs(a->y - b->y) > THRESH) + if (WWMath::Fabs(a->y - b->y) > THRESH) return true; - if (fabs(a->z - b->z) > THRESH) + if (WWMath::Fabs(a->z - b->z) > THRESH) return true; return false; @@ -1669,7 +1669,7 @@ inline Bool isAngleDifferent(Real a, Real b) const Real THRESH = 0.01f; // in radians, this is approx 1/2 degree. - if (fabs(a - b) > THRESH) + if (WWMath::Fabs(a - b) > THRESH) return true; return false; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp index a894d9331a2..ca2d2bc2aa6 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp @@ -281,7 +281,7 @@ class DeliverPayloadNugget : public ObjectCreationNugget Real dy = primary->y - secondary->y; //Calc length - Real length = sqrt( dx*dx + dy*dy ); + Real length = WWMath::Sqrt( dx*dx + dy*dy ); //Normalize length dx /= length; @@ -348,7 +348,7 @@ class DeliverPayloadNugget : public ObjectCreationNugget } - Real orient = atan2( moveToPos.y - startPos.y, moveToPos.x - startPos.x); + Real orient = WWMath::Atan2( moveToPos.y - startPos.y, moveToPos.x - startPos.x); if( m_data.m_distToTarget > 0 ) { const Real SLOP = 1.5f; @@ -1070,7 +1070,7 @@ class GenericObjectCreationNugget : public ObjectCreationNugget objUp->applyForce(&force); if (m_orientInForceDirection) - orientation = atan2(force.y, force.x); + orientation = WWMath::Atan2(force.y, force.x); } } @@ -1158,7 +1158,7 @@ class GenericObjectCreationNugget : public ObjectCreationNugget objUp->applyForce(&force); if (m_orientInForceDirection) { - orientation = atan2(force.y, force.x); + orientation = WWMath::Atan2(force.y, force.x); } DUMPREAL(orientation); objUp->setAngles(orientation, 0, 0); diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index f9b464c58bd..7108f28c08d 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -411,13 +411,13 @@ static void testRotatedPointsAgainstRect( Real pty = pts->y - a->position.y; // inverse-rotate it to the right coord system - Real ptx_new = (Real)fabs(ptx*c - pty*s); - Real pty_new = (Real)fabs(ptx*s + pty*c); + Real ptx_new = (Real)WWMath::Fabs(ptx*c - pty*s); + Real pty_new = (Real)WWMath::Fabs(ptx*s + pty*c); #ifdef INTENSE_DEBUG Real mag_a = sqr(ptx)+sqr(pty); Real mag_b = sqr(ptx_new)+sqr(pty_new); - DEBUG_ASSERTCRASH(fabs(mag_a - mag_b) <= 1.0, ("hmm, unlikely")); + DEBUG_ASSERTCRASH(WWMath::Fabs(mag_a - mag_b) <= 1.0, ("hmm, unlikely")); #endif if (ptx_new <= major && pty_new <= minor) @@ -617,7 +617,7 @@ inline Bool z_collideTest_Sphere_Nonsphere(CollideTestProc xyproc, const Collide // find the radius of the slice of the sphere that is at b_bot CollideInfo amod = *a; amod.position.z = b_bot; - amod.geom.setMajorRadius((Real)sqrtf(sqr(a->geom.getMajorRadius()) - sqr(b_bot - a->position.z))); + amod.geom.setMajorRadius((Real)WWMath::Sqrtf(sqr(a->geom.getMajorRadius()) - sqr(b_bot - a->position.z))); if (xyproc(&amod, b, cinfo)) { // if you want to have 'end' collisions, you should add something like: @@ -635,7 +635,7 @@ inline Bool z_collideTest_Sphere_Nonsphere(CollideTestProc xyproc, const Collide { CollideInfo amod = *a; amod.position.z = b_top; - amod.geom.setMajorRadius((Real)sqrtf(sqr(a->geom.getMajorRadius()) - sqr(a->position.z - b_top))); + amod.geom.setMajorRadius((Real)WWMath::Sqrtf(sqr(a->geom.getMajorRadius()) - sqr(a->position.z - b_top))); if (xyproc(&amod, b, cinfo)) { // if you want to have 'end' collisions, you should add something like: @@ -823,7 +823,7 @@ static Bool distCalcProc_BoundaryAndBoundary_2D( if (totalRad > 0.0f) { - Real actualDist = sqrtf(actualDistSqr); + Real actualDist = WWMath::Sqrtf(actualDistSqr); Real shrunkenDist = actualDist - totalRad; if (shrunkenDist <= 0.0f) { @@ -911,7 +911,7 @@ static Bool distCalcProc_BoundaryAndBoundary_3D( Real totalRad = (geomA?geomA->getBoundingSphereRadius():0) + (geomB?geomB->getBoundingSphereRadius():0); if (totalRad > 0.0f) { - Real actualDist = sqrtf(actualDistSqr); + Real actualDist = WWMath::Sqrtf(actualDistSqr); Real shrunkenDist = actualDist - totalRad; if (shrunkenDist <= 0.0f) { @@ -2219,7 +2219,7 @@ Int PartitionData::calcMaxCoiForShape(GeometryType geom, Real majorRadius, Real } case GEOMETRY_BOX: { - Real diagonal = (Real)(sqrtf(majorRadius*majorRadius + minorRadius*minorRadius)); + Real diagonal = (Real)(WWMath::Sqrtf(majorRadius*majorRadius + minorRadius*minorRadius)); Int cells = ThePartitionManager->worldToCellDist(diagonal*2) + 1; result = cells * cells; break; @@ -2636,7 +2636,7 @@ static void calcHeights(const Region3D& world, Real cellSize, Int x, Int y, Real Real xbase = world.lo.x + (x * cellSize); Real ybase = world.lo.y + (y * cellSize); const Real ROUGH_STEP_SIZE = 2; // roughly every 2 ft, please - Real numSteps = ceilf(cellSize / ROUGH_STEP_SIZE); + Real numSteps = WWMath::Ceilf(cellSize / ROUGH_STEP_SIZE); Real step = cellSize / numSteps; loZ = HUGE_DIST; // huge positive hiZ = -HUGE_DIST; // huge negative @@ -3210,7 +3210,7 @@ Int PartitionManager::calcMinRadius(const ICoord2D& cur) } // double, not real - double dist = sqrtf(minDistSqr); + double dist = WWMath::Sqrtf(minDistSqr); Int minRadius = REAL_TO_INT_CEIL( dist / m_cellSize ); return minRadius; @@ -3228,7 +3228,7 @@ void PartitionManager::calcRadiusVec() // double, not real double dx = (double)cx * (double)cellSize; double dy = (double)cy * (double)cellSize; - double maxPossibleDist = sqrt(dx*dx + dy*dy); + double maxPossibleDist = WWMath::Sqrt(dx*dx + dy*dy); m_maxGcoRadius = REAL_TO_INT_CEIL(maxPossibleDist / cellSize); @@ -3498,7 +3498,7 @@ Object *PartitionManager::getClosestObjects( } if (closestDistArg) { - *closestDistArg = (Real)sqrtf(closestDistSqr); + *closestDistArg = (Real)WWMath::Sqrtf(closestDistSqr); } #ifdef RTS_DEBUG @@ -3625,7 +3625,7 @@ Real PartitionManager::getRelativeAngle2D( const Object *obj, const Coord3D *pos v.y = pos->y - objPos.y; v.z = 0.0f; - Real dist = (Real)sqrtf(sqr(v.x) + sqr(v.y)); + Real dist = (Real)WWMath::Sqrtf(sqr(v.x) + sqr(v.y)); // normalize if (dist == 0.0f) @@ -3803,7 +3803,7 @@ Bool PartitionManager::tryPosition( const Coord3D *center, pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ); } - if (fabs(pos.z - center->z) > options->maxZDelta) + if (WWMath::Fabs(pos.z - center->z) > options->maxZDelta) return FALSE; // @@ -4556,7 +4556,7 @@ Int PartitionManager::iterateCellsBreadthFirst(const Coord3D *pos, CellBreadthFi //----------------------------------------------------------------------------- static Real calcDist2D(Real x1, Real y1, Real x2, Real y2) { - return sqrtf(sqr(x1-x2) + sqr(y1-y2)); + return WWMath::Sqrtf(sqr(x1-x2) + sqr(y1-y2)); } //----------------------------------------------------------------------------- @@ -5715,7 +5715,7 @@ void hLineAddThreat(Int x1, Int x2, Int y, void *threatValueParms) if (x < 0 || x >= ThePartitionManager->m_cellCountX) continue; - distance = sqrt( pow(x - parms->xCenter, 2) + pow(y - parms->yCenter, 2) ); + distance = WWMath::Sqrt( WWMath::Pow(x - parms->xCenter, 2) + WWMath::Pow(y - parms->yCenter, 2) ); mulVal = 1 - distance / parms->radius; if (mulVal < 0.0f) mulVal = 0.0f; @@ -5743,7 +5743,7 @@ void hLineRemoveThreat(Int x1, Int x2, Int y, void *threatValueParms) if (x < 0 || x >= ThePartitionManager->m_cellCountX) continue; - distance = sqrt( pow(x - parms->xCenter, 2) + pow(y - parms->yCenter, 2) ); + distance = WWMath::Sqrt( WWMath::Pow(x - parms->xCenter, 2) + WWMath::Pow(y - parms->yCenter, 2) ); mulVal = 1 - distance / parms->radius; if (mulVal < 0.0f) mulVal = 0.0f; @@ -5771,7 +5771,7 @@ void hLineAddValue(Int x1, Int x2, Int y, void *threatValueParms) if (x < 0 || x >= ThePartitionManager->m_cellCountX) continue; - distance = sqrt( pow(x - parms->xCenter, 2) + pow(y - parms->yCenter, 2) ); + distance = WWMath::Sqrt( WWMath::Pow(x - parms->xCenter, 2) + WWMath::Pow(y - parms->yCenter, 2) ); mulVal = 1 - distance / parms->radius; if (mulVal < 0.0f) mulVal = 0.0f; @@ -5799,7 +5799,7 @@ void hLineRemoveValue(Int x1, Int x2, Int y, void *threatValueParms) if (x < 0 || x >= ThePartitionManager->m_cellCountX) continue; - distance = sqrt( pow(x - parms->xCenter, 2) + pow(y - parms->yCenter, 2) ); + distance = WWMath::Sqrt( WWMath::Pow(x - parms->xCenter, 2) + WWMath::Pow(y - parms->yCenter, 2) ); mulVal = 1 - distance / parms->radius; if (mulVal < 0.0f) mulVal = 0.0f; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index c8fcd2f3221..b04ddb64bc4 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -1282,8 +1282,8 @@ Bool AIUpdateInterface::blockedBy(Object *other) // If we are near our final goal, don't get stuck. if (goalCell.x>0 && goalCell.y>0) { - Real dx = fabs(goalPos.x-pos.x); - Real dy = fabs(goalPos.y-pos.y); + Real dx = WWMath::Fabs(goalPos.x-pos.x); + Real dy = WWMath::Fabs(goalPos.y-pos.y); if (dxgetRelativeAngle2D( getObject(), &info.posOnPath ); } - if (fabs(deltaAngle)>PI/30) + if (WWMath::Fabs(deltaAngle)>PI/30) { return TRUE; } @@ -2230,7 +2230,7 @@ UpdateSleepTime AIUpdateInterface::doLocomotor() } else { - Real dist = sqrtf(dSqr); + Real dist = WWMath::Sqrtf(dSqr); if (dist<1) dist = 1; pos.x += 2*PATHFIND_CELL_SIZE_F*dx/(dist*LOGICFRAMES_PER_SECOND); pos.y += 2*PATHFIND_CELL_SIZE_F*dy/(dist*LOGICFRAMES_PER_SECOND); @@ -2424,7 +2424,7 @@ Real AIUpdateInterface::getLocomotorDistanceToGoal() dest = m_path->getLastNode()->getPosition(); } Real distance = ThePartitionManager->getDistanceSquared( me, dest, FROM_CENTER_3D ); - return sqrt( distance );// Other paths return dots of normalized vectors, so one sqrt ain't so bad + return WWMath::Sqrt( distance );// Other paths return dots of normalized vectors, so one sqrt ain't so bad } else { @@ -2456,7 +2456,7 @@ Real AIUpdateInterface::getLocomotorDistanceToGoal() { if (sqr(dist) > distSqr) { - return sqrt(distSqr); + return WWMath::Sqrt(distSqr); } else { @@ -2465,7 +2465,7 @@ Real AIUpdateInterface::getLocomotorDistanceToGoal() } if (distgetExtent( &terrainExtent ); const Real FUDGE = 1.2f; - Real HUGE_DIST = FUDGE * sqrt(sqr(terrainExtent.hi.x - terrainExtent.lo.x) + sqr(terrainExtent.hi.y - terrainExtent.lo.y)); + Real HUGE_DIST = FUDGE * WWMath::Sqrt(sqr(terrainExtent.hi.x - terrainExtent.lo.x) + sqr(terrainExtent.hi.y - terrainExtent.lo.y)); exitCoord.x += dir->x * HUGE_DIST; exitCoord.y += dir->y * HUGE_DIST; @@ -569,7 +569,7 @@ class ChinookCombatDropState : public State { if (it->ropeLen < it->ropeLenMax) { - it->ropeSpeed += fabs(TheGlobalData->m_gravity); + it->ropeSpeed += WWMath::Fabs(TheGlobalData->m_gravity); if (it->ropeSpeed > d->m_ropeDropSpeed) it->ropeSpeed = d->m_ropeDropSpeed; it->ropeLen += it->ropeSpeed; @@ -761,7 +761,7 @@ class ChinookMoveToBldgState : public AIMoveToState StateReturnType status = AIMoveToState::update(); const Real THRESH = 3.0f; - if (status != STATE_CONTINUE && fabs(obj->getPosition()->z - m_destZ) > THRESH) + if (status != STATE_CONTINUE && WWMath::Fabs(obj->getPosition()->z - m_destZ) > THRESH) status = STATE_CONTINUE; return status; @@ -840,7 +840,7 @@ ChinookAIUpdateModuleData::ChinookAIUpdateModuleData() m_minDropHeight = 30.0f; m_ropeFinalHeight = 0.0f; m_ropeDropSpeed = 1e10f; // um, fast. - m_rappelSpeed = fabs(TheGlobalData->m_gravity) * LOGICFRAMES_PER_SECOND * 0.5f; + m_rappelSpeed = WWMath::Fabs(TheGlobalData->m_gravity) * LOGICFRAMES_PER_SECOND * 0.5f; m_ropeWobbleLen = 10.0f; m_ropeWobbleAmp = 1.0f; m_ropeWobbleRate = 0.1f; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp index e053a0d4b55..1d8fce44416 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp @@ -201,8 +201,8 @@ UpdateSleepTime DeliverPayloadAIUpdate::update() { //Calc strafe ratio Real startDiveDistance = getData()->m_diveStartDistance; - Real endDiveDistance = sqrt( endDiveDistanceSquared ); - Real currentDistance = sqrt( currentDistanceSquared ); + Real endDiveDistance = WWMath::Sqrt( endDiveDistanceSquared ); + Real currentDistance = WWMath::Sqrt( currentDistanceSquared ); Real diveRatio = (startDiveDistance - currentDistance) / (startDiveDistance - endDiveDistance); @@ -1081,7 +1081,7 @@ StateReturnType RecoverFromOffMapState::update() // Success if we should try aga enterCoord.z = owner->getPosition()->z; owner->setPosition(&enterCoord); - Real enterAngle = atan2(ai->getMoveToPos()->y - enterCoord.y, ai->getMoveToPos()->x - enterCoord.x); + Real enterAngle = WWMath::Atan2(ai->getMoveToPos()->y - enterCoord.y, ai->getMoveToPos()->x - enterCoord.x); owner->setOrientation(enterAngle); PhysicsBehavior* physics = owner->getPhysics(); @@ -1121,7 +1121,7 @@ StateReturnType HeadOffMapState::onEnter() // Give move order out of town Region3D terrainExtent; TheTerrainLogic->getExtent( &terrainExtent ); const Real FUDGE = 1.2f; - Real HUGE_DIST = FUDGE * sqrt(sqr(terrainExtent.hi.x - terrainExtent.lo.x) + sqr(terrainExtent.hi.y - terrainExtent.lo.y)); + Real HUGE_DIST = FUDGE * WWMath::Sqrt(sqr(terrainExtent.hi.x - terrainExtent.lo.x) + sqr(terrainExtent.hi.y - terrainExtent.lo.y)); exitCoord.x += dir->x * HUGE_DIST; exitCoord.y += dir->y * HUGE_DIST; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp index 483cf6b45f7..d64461da9e1 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp @@ -457,8 +457,8 @@ class JetOrHeliTaxiState : public AIMoveOutOfTheWayState Coord3D intermedPt; Bool intermed = false; - Real orient = atan2(ppinfo.runwayPrep.y - ppinfo.parkingSpace.y, ppinfo.runwayPrep.x - ppinfo.parkingSpace.x); - if (fabs(stdAngleDiff(orient, ppinfo.parkingOrientation)) > PI/128) + Real orient = WWMath::Atan2(ppinfo.runwayPrep.y - ppinfo.parkingSpace.y, ppinfo.runwayPrep.x - ppinfo.parkingSpace.x); + if (WWMath::Fabs(stdAngleDiff(orient, ppinfo.parkingOrientation)) > PI/128) { intermedPt.z = (ppinfo.parkingSpace.z + ppinfo.runwayPrep.z) * 0.5f; intermed = intersectInfiniteLine2D( @@ -884,7 +884,7 @@ class HeliTakeoffOrLandingState : public State } else { - Real dist = sqrtf(dSqr); + Real dist = WWMath::Sqrtf(dSqr); if (dist<1) dist = 1; pos.x += PATHFIND_CELL_SIZE_F*dx/(dist*LOGICFRAMES_PER_SECOND); pos.y += PATHFIND_CELL_SIZE_F*dy/(dist*LOGICFRAMES_PER_SECOND); @@ -1012,7 +1012,7 @@ class JetOrHeliParkOrientState : public State return STATE_FAILURE; const Real THRESH = 0.001f; - if (fabs(stdAngleDiff(jet->getOrientation(), ppinfo.parkingOrientation)) <= THRESH) + if (WWMath::Fabs(stdAngleDiff(jet->getOrientation(), ppinfo.parkingOrientation)) <= THRESH) return STATE_SUCCESS; // magically position it correctly. @@ -2070,7 +2070,7 @@ void JetAIUpdate::positionLockon() Real dx = getObject()->getPosition()->x - pos.x; Real dy = getObject()->getPosition()->y - pos.y; if (dx || dy) - m_lockonDrawable->setOrientation(atan2(dy, dx)); + m_lockonDrawable->setOrientation(WWMath::Atan2(dy, dx)); // the Gaussian sum, to avoid keeping a running total: // diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp index 688a0918a5a..5d6f623b2c7 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp @@ -223,7 +223,7 @@ void MissileAIUpdate::projectileFireAtObjectOrPosition( const Object *victim, co Real deltaZ = victimPos->z - obj->getPosition()->z; Real dx = victimPos->x - obj->getPosition()->x; Real dy = victimPos->y - obj->getPosition()->y; - Real xyDist = sqrt(sqr(dx)+sqr(dy)); + Real xyDist = WWMath::Sqrt(sqr(dx)+sqr(dy)); if (xyDist<1) xyDist = 1; Real zFactor = 0; if (deltaZ>0) { @@ -619,7 +619,7 @@ UpdateSleepTime MissileAIUpdate::update() Coord3D newPos = *getObject()->getPosition(); if (m_noTurnDistLeft > 0.0f && m_state >= IGNITION) { - Real distThisTurn = sqrtf(sqr(newPos.x-m_prevPos.x) + sqr(newPos.y-m_prevPos.y) + sqr(newPos.z-m_prevPos.z)); + Real distThisTurn = WWMath::Sqrtf(sqr(newPos.x-m_prevPos.x) + sqr(newPos.y-m_prevPos.y) + sqr(newPos.z-m_prevPos.z)); m_noTurnDistLeft -= distThisTurn; m_prevPos = newPos; } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/POWTruckAIUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/POWTruckAIUpdate.cpp index dc83acdf5db..52333f5c8b5 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/POWTruckAIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/POWTruckAIUpdate.cpp @@ -480,7 +480,7 @@ void POWTruckAIUpdate::updateCollectingTarget() { // are we close enough to tell them to start moving to us - Real distSq = pow( us->getGeometryInfo().getBoundingSphereRadius() * 2.0f, 2 ); + Real distSq = WWMath::Pow( us->getGeometryInfo().getBoundingSphereRadius() * 2.0f, 2 ); if( ThePartitionManager->getDistanceSquared( us, target, FROM_CENTER_2D ) <= distSq ) { diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/RailroadGuideAIUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/RailroadGuideAIUpdate.cpp index 24d9d6498ed..b74afb4dd9e 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/RailroadGuideAIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/RailroadGuideAIUpdate.cpp @@ -315,7 +315,7 @@ void RailroadBehavior::onCollide( Object *other, const Coord3D *loc, const Coord m_whistleSound.setPlayingHandle(TheAudio->addAudioEvent( &m_whistleSound )); - Real dist = (Real)sqrtf( dlt.x*dlt.x + dlt.y*dlt.y + dlt.z*dlt.z); + Real dist = (Real)WWMath::Sqrtf( dlt.x*dlt.x + dlt.y*dlt.y + dlt.z*dlt.z); Real usRadius = obj->getGeometryInfo().getMajorRadius(); Real themRadius = other->getGeometryInfo().getMajorRadius(); Real overlap = ((usRadius + themRadius) - dist) + 1;// the plus 1 makes them go just outside of me. @@ -472,8 +472,8 @@ void RailroadBehavior::playImpactSound(Object *victim, const Coord3D *impactPosi impact.setPosition(impactPosition); if ( theirPhys ) { - vel += fabs(theirPhys->getVelocity()->length()); - mass += fabs(theirPhys->getMass()); + vel += WWMath::Fabs(theirPhys->getVelocity()->length()); + mass += WWMath::Fabs(theirPhys->getMass()); vel /= 2; mass /= 2;//average of him and me @@ -675,7 +675,7 @@ UpdateSleepTime RailroadBehavior::update() if ( m_conductorState == APPLY_BRAKES ) { conductorPullInfo.speed *= modData->m_braking; - if (fabs(conductorPullInfo.speed) < 0.01f) + if (WWMath::Fabs(conductorPullInfo.speed) < 0.01f) { conductorPullInfo.speed = 0; ///////////////////////////////////////( &m_hissySteamSound ); @@ -1193,7 +1193,7 @@ void alignToTerrain( Real angle, const Coord3D& pos, const Coord3D& normal, Matr x.normalize(); } - DEBUG_ASSERTCRASH(fabs(x.x*z.x + x.y*z.y + x.z*z.z)<0.0001,("dot is not zero")); + DEBUG_ASSERTCRASH(WWMath::Fabs(x.x*z.x + x.y*z.y + x.z*z.z)<0.0001,("dot is not zero")); // now computing the y vector is trivial. y.crossProduct( z, x, y ); @@ -1259,7 +1259,7 @@ void RailroadBehavior::updatePositionTrackDistance( PullInfo *pullerInfo, PullIn trackPosDelta.z = 0; Real dx = pullerInfo->towHitchPosition.x - turnPos.x; Real dy = pullerInfo->towHitchPosition.y - turnPos.y; - Real desiredAngle = atan2(dy, dx); + Real desiredAngle = WWMath::Atan2(dy, dx); Real relAngle = stdAngleDiff(desiredAngle, obj->getTransformMatrix()->Get_Z_Rotation()); diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/CleanupHazardUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/CleanupHazardUpdate.cpp index 8a23910d7d3..57cc8f8211a 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/CleanupHazardUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/CleanupHazardUpdate.cpp @@ -172,7 +172,7 @@ UpdateSleepTime CleanupHazardUpdate::update() AIUpdateInterface *ai = obj->getAI(); if( ai && (ai->isIdle() || ai->isBusy()) ) { - Real fDist = sqrt( ThePartitionManager->getDistanceSquared( obj, &m_pos, FROM_CENTER_2D ) ); + Real fDist = WWMath::Sqrt( ThePartitionManager->getDistanceSquared( obj, &m_pos, FROM_CENTER_2D ) ); if( fDist < 25.0f ) { //Abort clean area because there's nothing left to clean! @@ -204,7 +204,7 @@ void CleanupHazardUpdate::fireWhenReady() bonus.clear(); Real fireRange = m_weaponTemplate->getAttackRange( bonus ); Object *me = getObject(); - Real fDist = sqrt( ThePartitionManager->getDistanceSquared( me, target, FROM_CENTER_2D ) ); + Real fDist = WWMath::Sqrt( ThePartitionManager->getDistanceSquared( me, target, FROM_CENTER_2D ) ); if( fDist < fireRange ) { //We are currently in range! diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/CommandButtonHuntUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/CommandButtonHuntUpdate.cpp index e7df932630c..936da747afe 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/CommandButtonHuntUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/CommandButtonHuntUpdate.cpp @@ -290,7 +290,7 @@ Object* CommandButtonHuntUpdate::scanClosestTarget() } } Real distSqr = ThePartitionManager->getDistanceSquared(me, other, FROM_BOUNDINGSPHERE_2D); - Real dist = sqrt(distSqr); + Real dist = WWMath::Sqrt(distSqr); Int curPriority = data->m_scanRange - dist; if (info) curPriority = info->getPriority(other->getTemplate()); if (curPriority == 0) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/DockUpdate/SupplyWarehouseDockUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/DockUpdate/SupplyWarehouseDockUpdate.cpp index 62ac01c158a..faac75b83d4 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/DockUpdate/SupplyWarehouseDockUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/DockUpdate/SupplyWarehouseDockUpdate.cpp @@ -95,7 +95,7 @@ Bool SupplyWarehouseDockUpdate::action( Object* docker, Object *drone ) Real closeEnoughSqr = sqr(docker->getGeometryInfo().getBoundingCircleRadius()*2); Real curDistSqr = ThePartitionManager->getDistanceSquared(docker, getObject(), FROM_BOUNDINGSPHERE_2D); if (curDistSqr > closeEnoughSqr) { - DEBUG_LOG(("Failing dock, dist %f, not close enough(%f).", sqrt(curDistSqr), sqrt(closeEnoughSqr))); + DEBUG_LOG(("Failing dock, dist %f, not close enough(%f).", WWMath::Sqrt(curDistSqr), WWMath::Sqrt(closeEnoughSqr))); // Make it twitch a little. Coord3D newPos = *docker->getPosition(); Real range = 0.4*PATHFIND_CELL_SIZE_F; @@ -170,7 +170,7 @@ void SupplyWarehouseDockUpdate::setDockCrippled( Bool setting ) void SupplyWarehouseDockUpdate::setCashValue( Int cashValue ) { // A script can tell us our set value, and we need to figure out the boxes needed to provide that. - m_boxesStored = ceil(cashValue / (float)TheGlobalData->m_baseValuePerSupplyBox); + m_boxesStored = WWMath::Ceil(cashValue / (float)TheGlobalData->m_baseValuePerSupplyBox); Drawable *draw = getObject()->getDrawable(); if( draw ) { diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/DynamicShroudClearingRangeUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/DynamicShroudClearingRangeUpdate.cpp index 5ad5b511a23..5d919cfbb31 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/DynamicShroudClearingRangeUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/DynamicShroudClearingRangeUpdate.cpp @@ -166,8 +166,8 @@ void DynamicShroudClearingRangeUpdate::animateGridDecals() for (int d = 0; d < GRID_FX_DECAL_COUNT; ++d) { - pos.x = ctr->x + (sinf(angle) * radius); - pos.y = ctr->y + (cosf(angle) * radius); + pos.x = ctr->x + (WWMath::Sinf(angle) * radius); + pos.y = ctr->y + (WWMath::Cosf(angle) * radius); pos.x -= ((Int)pos.x)%23; pos.y -= ((Int)pos.y)%23; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/FloatUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/FloatUpdate.cpp index 29f256c4801..40d350662bd 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/FloatUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/FloatUpdate.cpp @@ -119,8 +119,8 @@ UpdateSleepTime FloatUpdate::update() { Real angle = INT_TO_REAL(TheGameLogic->getFrame()); - Real yaw = sin(angle * 0.0291f) * 0.05f; - Real pitch = sin(angle * 0.0515f) * 0.05f; + Real yaw = WWMath::Sin(angle * 0.0291f) * 0.05f; + Real pitch = WWMath::Sin(angle * 0.0515f) * 0.05f; Matrix3D mx = *draw->getInstanceMatrix(); diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileUpdate.cpp index 9bf55521dde..172b323e01b 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileUpdate.cpp @@ -297,7 +297,7 @@ static Real calcTransform(const Object* obj, const Coord3D *pos, Real maxTurnRat Real angle = (Real)ACos( c ); Vector3 newDir; - if (fabs(angle) < maxTurnRate) + if (WWMath::Fabs(angle) < maxTurnRate) { // close enough -- point exactly in the right dir newDir = otherDir; @@ -355,7 +355,7 @@ void NeutronMissileUpdate::doAttack() // // Modulate speed according to turning. The more we have to turn, the slower we go // - Real angleCoeff = (Real)fabs( relAngle ) / (PI / 2.0f); + Real angleCoeff = (Real)WWMath::Fabs( relAngle ) / (PI / 2.0f); if (angleCoeff > 1.0f) angleCoeff = 1.0; } @@ -512,7 +512,7 @@ UpdateSleepTime NeutronMissileUpdate::update() if (m_noTurnDistLeft > 0.0f && oldPosValid) { Coord3D newPos = *getObject()->getPosition(); - Real distThisTurn = sqrt(sqr(newPos.x-oldPos.x) + sqr(newPos.y-oldPos.y) + sqr(newPos.z-oldPos.z)); + Real distThisTurn = WWMath::Sqrt(sqr(newPos.x-oldPos.x) + sqr(newPos.y-oldPos.y) + sqr(newPos.z-oldPos.z)); //DEBUG_LOG(("noTurnDist goes from %f to %f",m_noTurnDistLeft,m_noTurnDistLeft-distThisTurn)); m_noTurnDistLeft -= distThisTurn; } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp index 44c344ff071..1ac10d290e3 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp @@ -479,7 +479,7 @@ UpdateSleepTime ParticleUplinkCannonUpdate::update() Real cxDistance = (factor * data->m_swathOfDeathDistance ) - (data->m_swathOfDeathDistance * 0.5f); //cx is cartesian x //Now calculate the amplitude value. - Real height = sin( radians ); + Real height = WWMath::Sin( radians ); Real cxHeight = height * data->m_swathOfDeathAmplitude; Coord3D buildingToInitialTargetVector; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index b20d43f7259..954159bca8f 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -95,7 +95,7 @@ static Real heightToSpeed(Real height) { // don't bother trying to remember how far we've fallen; instead, // back-calc it from our speed & gravity... v = sqrt(2*g*h) - return sqrt(fabs(2.0f * TheGlobalData->m_gravity * height)); + return WWMath::Sqrt(WWMath::Fabs(2.0f * TheGlobalData->m_gravity * height)); } //------------------------------------------------------------------------------------------------- @@ -440,7 +440,7 @@ Bool PhysicsBehavior::handleBounce(Real oldZ, Real newZ, Real groundZ, Coord3D* Real vz = getVelocity()->z; if (oldZ > groundZ && vz < 0.0f) { - desiredAccelZ = fabs(vz) * stiffness; + desiredAccelZ = WWMath::Fabs(vz) * stiffness; } bounceForce->x = 0.0f; @@ -472,7 +472,7 @@ Bool PhysicsBehavior::handleBounce(Real oldZ, Real newZ, Real groundZ, Coord3D* inline Bool isVerySmall3D(const Coord3D& v) { const Real THRESH = 0.01f; - return (fabs(v.x) < THRESH && fabs(v.y) < THRESH && fabs(v.z) < THRESH); + return (WWMath::Fabs(v.x) < THRESH && WWMath::Fabs(v.y) < THRESH && WWMath::Fabs(v.z) < THRESH); } //------------------------------------------------------------------------------------------------- @@ -571,9 +571,9 @@ UpdateSleepTime PhysicsBehavior::update() // when vel gets tiny, just clamp to zero const Real THRESH = 0.001f; - if (fabsf(m_vel.x) < THRESH) m_vel.x = 0.0f; - if (fabsf(m_vel.y) < THRESH) m_vel.y = 0.0f; - if (fabsf(m_vel.z) < THRESH) m_vel.z = 0.0f; + if (WWMath::Fabsf(m_vel.x) < THRESH) m_vel.x = 0.0f; + if (WWMath::Fabsf(m_vel.y) < THRESH) m_vel.y = 0.0f; + if (WWMath::Fabsf(m_vel.z) < THRESH) m_vel.z = 0.0f; m_velMag = INVALID_VEL_MAG; @@ -638,8 +638,8 @@ UpdateSleepTime PhysicsBehavior::update() if (offset != 0.0f) { Vector3 xvec = mtx.Get_X_Vector(); - Real xy = sqrtf(sqr(xvec.X) + sqr(xvec.Y)); - Real pitchAngle = atan2(xvec.Z, xy); + Real xy = WWMath::Sqrtf(sqr(xvec.X) + sqr(xvec.Y)); + Real pitchAngle = WWMath::Atan2(xvec.Z, xy); Real remainingAngle = (offset > 0) ? ((PI/2) - pitchAngle) : (-(PI/2) + pitchAngle); Real s = Sin(remainingAngle); pitchRateToUse *= s; @@ -739,8 +739,8 @@ UpdateSleepTime PhysicsBehavior::update() // going down hills don't injure themselves (unless the hill is really steep) const Real MIN_ANGLE_TAN = 3.0f; // roughly 71 degrees const Real TINY_DELTA = 0.01f; - if ((fabs(m_vel.x) <= TINY_DELTA || fabs(activeVelZ / m_vel.x) >= MIN_ANGLE_TAN) && - (fabs(m_vel.y) <= TINY_DELTA || fabs(activeVelZ / m_vel.y) >= MIN_ANGLE_TAN)) + if ((WWMath::Fabs(m_vel.x) <= TINY_DELTA || WWMath::Fabs(activeVelZ / m_vel.x) >= MIN_ANGLE_TAN) && + (WWMath::Fabs(m_vel.y) <= TINY_DELTA || WWMath::Fabs(activeVelZ / m_vel.y) >= MIN_ANGLE_TAN)) { Real damageAmt = netSpeed * getMass() * d->m_fallHeightDamageFactor; @@ -819,7 +819,7 @@ Real PhysicsBehavior::getVelocityMagnitude() const { if (m_velMag == INVALID_VEL_MAG) { - m_velMag = (Real)sqrtf( sqr(m_vel.x) + sqr(m_vel.y) + sqr(m_vel.z) ); + m_velMag = (Real)WWMath::Sqrtf( sqr(m_vel.x) + sqr(m_vel.y) + sqr(m_vel.z) ); } return m_velMag; } @@ -841,7 +841,7 @@ Real PhysicsBehavior::getForwardSpeed2D() const Real speedSquared = vx*vx + vy*vy; // DEBUG_ASSERTCRASH( speedSquared != 0, ("zero speedSquared will overflow sqrtf()!") );// lorenzen... sanity check - Real speed = (Real)sqrtf( speedSquared ); + Real speed = (Real)WWMath::Sqrtf( speedSquared ); if (dot >= 0.0f) return speed; @@ -864,7 +864,7 @@ Real PhysicsBehavior::getForwardSpeed3D() const Real dot = vx + vy + vz; - Real speed = (Real)sqrtf( vx*vx + vy*vy + vz*vz ); + Real speed = (Real)WWMath::Sqrtf( vx*vx + vy*vy + vz*vz ); if (dot >= 0.0f) return speed; @@ -887,7 +887,7 @@ Bool PhysicsBehavior::wasPreviouslyOverlapped(Object *obj) const //------------------------------------------------------------------------------------------------- void PhysicsBehavior::scrubVelocityZ( Real desiredVelocity ) { - if (fabs(desiredVelocity) < 0.001f) + if (WWMath::Fabs(desiredVelocity) < 0.001f) { m_vel.z = 0; } @@ -911,7 +911,7 @@ void PhysicsBehavior::scrubVelocity2D( Real desiredVelocity ) } else { - Real curVelocity = sqrtf(m_vel.x*m_vel.x + m_vel.y*m_vel.y); + Real curVelocity = WWMath::Sqrtf(m_vel.x*m_vel.x + m_vel.y*m_vel.y); if (desiredVelocity > curVelocity) { return; @@ -994,9 +994,9 @@ void PhysicsBehavior::doBounceSound(const Coord3D& prevPos) //Real vel = fabs(getVelocity()->z); // can't use velocity, because it's already been updated this frame, and will be zero... (srj) - Real vel = fabs(prevPos.z - getObject()->getPosition()->z); + Real vel = WWMath::Fabs(prevPos.z - getObject()->getPosition()->z); - Real mass = fabs(getMass()); + Real mass = WWMath::Fabs(getMass()); if (vel > NORMAL_VEL_Z) { vel = NORMAL_VEL_Z; } @@ -1196,7 +1196,7 @@ void PhysicsBehavior::onCollide( Object *other, const Coord3D *loc, const Coord3 m_lastCollidee = other->getID(); - Real dist = sqrtf(distSqr); + Real dist = WWMath::Sqrtf(distSqr); Real overlap = usRadius + themRadius - dist; // if objects are coincident, dist is zero, so force would be infinite -- clearly @@ -1337,7 +1337,7 @@ static Bool perpsLogicallyEqual( Real perpOne, Real perpTwo ) { // Equality with a wiggle fudge. const Real PERP_RANGE = 0.15f; - return fabs( perpOne - perpTwo ) <= PERP_RANGE; + return WWMath::Fabs( perpOne - perpTwo ) <= PERP_RANGE; } //------------------------------------------------------------------------------------------------- diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PointDefenseLaserUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PointDefenseLaserUpdate.cpp index 7c12ee6fd36..6546c004c3d 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PointDefenseLaserUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/PointDefenseLaserUpdate.cpp @@ -167,7 +167,7 @@ void PointDefenseLaserUpdate::fireWhenReady() bonus.clear(); Real fireRange = data->m_weaponTemplate->getAttackRange( bonus ); Object *me = getObject(); - Real fDist = sqrt( ThePartitionManager->getDistanceSquared( me, target, FROM_CENTER_2D ) ); + Real fDist = WWMath::Sqrt( ThePartitionManager->getDistanceSquared( me, target, FROM_CENTER_2D ) ); if( fDist < fireRange ) { //We are currently in range! @@ -285,7 +285,7 @@ Object* PointDefenseLaserUpdate::scanClosestTarget() continue; } - Real fDist = sqrt( ThePartitionManager->getDistanceSquared( me, other, FROM_CENTER_2D ) ); + Real fDist = WWMath::Sqrt( ThePartitionManager->getDistanceSquared( me, other, FROM_CENTER_2D ) ); if( fDist <= fireRange ) { @@ -312,7 +312,7 @@ Object* PointDefenseLaserUpdate::scanClosestTarget() pos.add( *other->getPosition() ); //Recalculate the distance. - fDist = sqrt( ThePartitionManager->getDistanceSquared( me, other, FROM_CENTER_2D ) ); + fDist = WWMath::Sqrt( ThePartitionManager->getDistanceSquared( me, other, FROM_CENTER_2D ) ); } } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp index a653701eca8..b92d9743a34 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp @@ -411,7 +411,7 @@ UpdateSleepTime StealthUpdate::update() m_disguiseHalfpointReached = true; } //Opacity ranges from full to none at midpoint and full again at the end - Real opacity = fabs( 1.0f - (factor * 2.0f) ); + Real opacity = WWMath::Fabs( 1.0f - (factor * 2.0f) ); Real overrideOpacity = opacity < 1.0f ? 0.0f : 1.0f; draw->setEffectiveOpacity( opacity, overrideOpacity ); if( !m_disguiseTransitionFrames && !m_transitioningToDisguise ) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/TensileFormationUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/TensileFormationUpdate.cpp index 30b216a4484..f6e7b05ee8d 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/TensileFormationUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/TensileFormationUpdate.cpp @@ -348,7 +348,7 @@ UpdateSleepTime TensileFormationUpdate::update() else draw->clearModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_MOVING)); - if ( fabs( pos->z - newPos.z ) > 0.2f && m_life < 100) + if ( WWMath::Fabs( pos->z - newPos.z ) > 0.2f && m_life < 100) draw->setModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_FREEFALL)); else draw->clearModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_FREEFALL)); diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ToppleUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ToppleUpdate.cpp index bd725493a58..335d8f39e33 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ToppleUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ToppleUpdate.cpp @@ -130,7 +130,7 @@ static Real angleClosestTo(Real a1, Real a2, Real desired) { a1 = normalizeAngle(a1); a2 = normalizeAngle(a2); - return (fabs(stdAngleDiff(desired, a1)) < fabs(stdAngleDiff(desired, a2))) ? a1 : a2; + return (WWMath::Fabs(stdAngleDiff(desired, a1)) < WWMath::Fabs(stdAngleDiff(desired, a2))) ? a1 : a2; } //------------------------------------------------------------------------------------------------- @@ -185,7 +185,7 @@ void ToppleUpdate::applyTopplingForce( const Coord3D* toppleDirection, Real topp // yeah, it assumes the models are constructed appropriately, but is a cheap way // of minimizing the problem. (srj) Real curAngleX = normalizeAngle(getObject()->getOrientation()); - Real toppleAngle = normalizeAngle(atan2(m_toppleDirection.y, m_toppleDirection.x)); + Real toppleAngle = normalizeAngle(WWMath::Atan2(m_toppleDirection.y, m_toppleDirection.x)); if (d->m_toppleLeftOrRightOnly) { // it's a fence or such, and can only topple left or right, so pick the closest @@ -298,7 +298,7 @@ UpdateSleepTime ToppleUpdate::update() m_angularVelocity *= -d->m_bounceVelocityPercent; if( BitIsSet( m_options, TOPPLE_OPTIONS_NO_BOUNCE ) == TRUE || - fabs(m_angularVelocity) < VELOCITY_BOUNCE_LIMIT ) + WWMath::Fabs(m_angularVelocity) < VELOCITY_BOUNCE_LIMIT ) { // too slow, just stop m_angularVelocity = 0; @@ -338,7 +338,7 @@ UpdateSleepTime ToppleUpdate::update() } } } - else if( fabs(m_angularVelocity) >= VELOCITY_BOUNCE_SOUND_LIMIT ) + else if( WWMath::Fabs(m_angularVelocity) >= VELOCITY_BOUNCE_SOUND_LIMIT ) { // fast enough bounce to warrant the bounce fx if( BitIsSet( m_options, TOPPLE_OPTIONS_NO_FX ) == FALSE ) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 8e883698a05..1f3f72d5d52 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -384,8 +384,8 @@ void WeaponTemplate::reset() // No matter what we have now, we want to convert it to frames from msec. // ShotDelay used to use parseDurationUnsignedInt, and we are expanding on that. - self->m_minDelayBetweenShots = ceilf(ConvertDurationFromMsecsToFrames((Real)self->m_minDelayBetweenShots)); - self->m_maxDelayBetweenShots = ceilf(ConvertDurationFromMsecsToFrames((Real)self->m_maxDelayBetweenShots)); + self->m_minDelayBetweenShots = WWMath::Ceilf(ConvertDurationFromMsecsToFrames((Real)self->m_minDelayBetweenShots)); + self->m_maxDelayBetweenShots = WWMath::Ceilf(ConvertDurationFromMsecsToFrames((Real)self->m_maxDelayBetweenShots)); } @@ -869,7 +869,7 @@ UnsignedInt WeaponTemplate::fireWeaponTemplate if (distSqr < minAttackRangeSqr-0.5f && !isProjectileDetonation) #endif { - DEBUG_ASSERTCRASH(distSqr > minAttackRangeSqr*0.8f, ("*** victim is closer than min attack range (%f vs %f) of this weapon -- why did we attempt to fire?",sqrtf(distSqr),sqrtf(minAttackRangeSqr))); + DEBUG_ASSERTCRASH(distSqr > minAttackRangeSqr*0.8f, ("*** victim is closer than min attack range (%f vs %f) of this weapon -- why did we attempt to fire?",WWMath::Sqrtf(distSqr),WWMath::Sqrtf(minAttackRangeSqr))); //-extraLogging #if defined(RTS_DEBUG) @@ -895,7 +895,7 @@ UnsignedInt WeaponTemplate::fireWeaponTemplate targetPos.set( *victimPos ); } Real reAngle = getWeaponRecoilAmount(); - Real reDir = reAngle != 0.0f ? (atan2(victimPos->y - sourcePos->y, victimPos->x - sourcePos->x)) : 0.0f; + Real reDir = reAngle != 0.0f ? (WWMath::Atan2(victimPos->y - sourcePos->y, victimPos->x - sourcePos->x)) : 0.0f; VeterancyLevel v = sourceObj->getVeterancyLevel(); const FXList* fx = isProjectileDetonation ? getProjectileDetonateFX(v) : getFireFX(v); @@ -1942,11 +1942,11 @@ Bool Weapon::computeApproachTarget(const Object *source, const Object *target, c if (source->isAboveTerrain()) { // Don't do a 180 degree turn. - Real angle = atan2(-dir.y, -dir.x); + Real angle = WWMath::Atan2(-dir.y, -dir.x); Real relAngle = source->getOrientation()- angle; if (relAngle>2*PI) relAngle -= 2*PI; if (relAngle<-2*PI) relAngle += 2*PI; - if (fabs(relAngle)getPosition(); const Real ACCEPTABLE_DZ = 10.0f; - if (fabs(dst->z - src->z) < ACCEPTABLE_DZ) + if (WWMath::Fabs(dst->z - src->z) < ACCEPTABLE_DZ) return true; // always good enough if dz is small, regardless of pitch Real minPitch, maxPitch; diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 857c63995b4..759d22d23ae 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -808,7 +808,7 @@ static void populateRandomStartPosition( GameInfo *game ) { Coord3D p1 = c1->second; Coord3D p2 = c2->second; - startSpotDistance[i][j] = sqrt( sqr(p1.x-p2.x) + sqr(p1.y-p2.y) ); + startSpotDistance[i][j] = WWMath::Sqrt( sqr(p1.x-p2.x) + sqr(p1.y-p2.y) ); } } else diff --git a/Generals/Code/GameEngine/Source/GameNetwork/GameSpyGameInfo.cpp b/Generals/Code/GameEngine/Source/GameNetwork/GameSpyGameInfo.cpp index 708f90ce5cc..5d52c29b65f 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/GameSpyGameInfo.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/GameSpyGameInfo.cpp @@ -563,8 +563,8 @@ void GameSpyLaunchGame() TheGlobalData->m_useFpsLimit = false; // Set the random seed - InitGameLogicRandom( TheGameSpyGame->getSeed() ); - DEBUG_LOG(("InitGameLogicRandom( %d )", TheGameSpyGame->getSeed())); + InitRandom( TheGameSpyGame->getSeed() ); + DEBUG_LOG(("InitRandom( %d )", TheGameSpyGame->getSeed())); if (TheNAT != nullptr) { delete TheNAT; @@ -748,4 +748,3 @@ AsciiString GameSpyGameInfo::generateGameResultsPacket() return results; } - 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..facdb88191f 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" @@ -1686,9 +1687,9 @@ void W3DVolumetricShadow::Update() if (fabs(pos.Z - groundHeight) >= AIRBORNE_UNIT_GROUND_DELTA) { Real extent = MAX_SHADOW_LENGTH_EXTRA_AIRBORNE_SCALE_FACTOR * m_robjExtent; - if (WWMath::Fabs(pos.X - bcX) > (beX + extent) || - WWMath::Fabs(pos.Y - bcY) > (beY + extent) || - WWMath::Fabs(pos.Z - bcZ) > (beZ + extent)) + if (WWMath::Fabsf_Legacy(pos.X - bcX) > (beX + extent) || + WWMath::Fabsf_Legacy(pos.Y - bcY) > (beY + extent) || + WWMath::Fabsf_Legacy(pos.Z - bcZ) > (beZ + extent)) return; //shadow can't be visible so no point in updating. //this unit is above ground, extend shadow volume to reach lowest point on the terrain plus extra bit to make @@ -1699,9 +1700,9 @@ void W3DVolumetricShadow::Update() { //normal object that is not floating above ground so we don't need to extend the shadow lower than the object's //base since it should be sitting directly at ground level. - if (WWMath::Fabs(pos.X - bcX) > (beX + m_robjExtent) || - WWMath::Fabs(pos.Y - bcY) > (beY + m_robjExtent) || - WWMath::Fabs(pos.Z - bcZ) > (beZ + m_robjExtent)) + if (WWMath::Fabsf_Legacy(pos.X - bcX) > (beX + m_robjExtent) || + WWMath::Fabsf_Legacy(pos.Y - bcY) > (beY + m_robjExtent) || + WWMath::Fabsf_Legacy(pos.Z - bcZ) > (beZ + m_robjExtent)) return; //shadow can't be visible so no point in updating. //check if this object has never had it's extrusion length updated. Will only be true for @@ -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); @@ -3329,8 +3336,8 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) #else //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); + { oldColorWriteEnable = g_renderBackend->Get_Color_Write_Mask(); + g_renderBackend->Set_Color_Write_Mask(0); } else { //device does not support disabling writes to color buffer so fake it through alpha blending @@ -3447,8 +3454,10 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) //m_pDev->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); + // oldColorWriteEnable is a captured DWORD bitmask; restore it through + // the mask variant instead of re-decoding individual channels. 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 @@ -3462,24 +3471,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 +3665,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/W3DAssetManager.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DAssetManager.cpp index 383777eaef5..10afeec2a56 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DAssetManager.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DAssetManager.cpp @@ -688,7 +688,7 @@ RenderObjClass * W3DAssetManager::Create_Render_Obj( GetPrecisionTimer(&startTime64); #endif - Bool reallyscale = (WWMath::Fabs(scale - ident_scale) > scale_epsilon); + Bool reallyscale = (WWMath::Fabsf_Legacy(scale - ident_scale) > scale_epsilon); Bool reallycolor = (color & 0xFFFFFF) != 0; //black is not a valid color and assumes no custom coloring. Bool reallytexture = (oldTexture != nullptr && newTexture != nullptr); @@ -1303,9 +1303,9 @@ static inline void Munge_Texture_Name(char *newname, const char *oldname, const RenderObjClass * W3DAssetManager::Create_Render_Obj(const char * name,float scale, const Vector3 &hsv_shift) { Bool isGranny = false; - Bool reallyscale = (WWMath::Fabs(scale - ident_scale) > scale_epsilon); - Bool reallyhsv_shift = (WWMath::Fabs(hsv_shift.X - ident_HSV.X) > H_epsilon || - WWMath::Fabs(hsv_shift.Y - ident_HSV.Y) > S_epsilon || WWMath::Fabs(hsv_shift.Z - ident_HSV.Z) > V_epsilon); + Bool reallyscale = (WWMath::Fabsf(scale - ident_scale) > scale_epsilon); + Bool reallyhsv_shift = (WWMath::Fabsf(hsv_shift.X - ident_HSV.X) > H_epsilon || + WWMath::Fabsf(hsv_shift.Y - ident_HSV.Y) > S_epsilon || WWMath::Fabsf(hsv_shift.Z - ident_HSV.Z) > V_epsilon); // base case, no scale or hue shifting if (!reallyscale && !reallyhsv_shift) return WW3DAssetManager::Create_Render_Obj(name); @@ -1402,8 +1402,8 @@ TextureClass * W3DAssetManager::Get_Texture_With_HSV_Shift(const char * filename { WWPROFILE( "W3DAssetManager::Get_Texture with HSV shift" ); - Bool is_hsv_shift = (WWMath::Fabs(hsv_shift.X - ident_HSV.X) > H_epsilon || - WWMath::Fabs(hsv_shift.Y - ident_HSV.Y) > S_epsilon || WWMath::Fabs(hsv_shift.Z - ident_HSV.Z) > V_epsilon); + Bool is_hsv_shift = (WWMath::Fabsf(hsv_shift.X - ident_HSV.X) > H_epsilon || + WWMath::Fabsf(hsv_shift.Y - ident_HSV.Y) > S_epsilon || WWMath::Fabsf(hsv_shift.Z - ident_HSV.Z) > V_epsilon); if (!is_hsv_shift) { 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/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/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/W3DDisplay.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 29ef7f98967..4b3a110028d 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -57,6 +57,7 @@ static void drawFramerateBar(); #include "GameClient/Drawable.h" #include "GameClient/GameText.h" #include "GameClient/GraphDraw.h" +#include "GameClient/Image.h" #include "GameClient/Line2D.h" #include "GameClient/Mouse.h" #include "GameClient/GlobalLanguage.h" @@ -91,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" @@ -121,6 +124,46 @@ static Real theLightYOffset = 0.07f; static Int theFlashCount = 0; #endif +static RectClass makeAtlasSafeUVRect(const Image *image) +{ + const Region2D *uv = image->getUV(); + RectClass uvRect(uv->lo.x, uv->lo.y, uv->hi.x, uv->hi.y); + + if (BitIsSet(image->getStatus(), IMAGE_STATUS_RAW_TEXTURE)) + { + return uvRect; + } + + const ICoord2D *textureSize = image->getTextureSize(); + if (textureSize == nullptr || textureSize->x <= 0 || textureSize->y <= 0) + { + return uvRect; + } + + const bool isAtlasSubRect = + uvRect.Left > 0.0f || uvRect.Top > 0.0f || + uvRect.Right < 1.0f || uvRect.Bottom < 1.0f; + if (!isAtlasSubRect) + { + return uvRect; + } + + const float halfU = 0.5f / static_cast(textureSize->x); + const float halfV = 0.5f / static_cast(textureSize->y); + if (uvRect.Width() > halfU * 2.0f) + { + uvRect.Left += halfU; + uvRect.Right -= halfU; + } + if (uvRect.Height() > halfV * 2.0f) + { + uvRect.Top += halfV; + uvRect.Bottom -= halfV; + } + + return uvRect; +} + //***************************************************************************************** //***************************************************************************************** //**** Start Statistical Dump ************************************************************* @@ -498,7 +541,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 */ @@ -1859,7 +1902,7 @@ void W3DDisplay::draw() do { // update all views of the world - recomputes data which will affect drawing - if (DX8Wrapper::_Get_D3D_Device8() && (DX8Wrapper::_Get_D3D_Device8()->TestCooperativeLevel()) == D3D_OK) + if (g_renderBackend != nullptr && !g_renderBackend->Is_Device_Lost()) { //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) @@ -2644,8 +2687,6 @@ void W3DDisplay::drawImage( const Image *image, Int startX, Int startY, // but it not derived on the W3DDisplay // !! - const Region2D *uv = image->getUV(); - TextureClass *tex = nullptr; if (BitIsSet(image->getStatus(), IMAGE_STATUS_RAW_TEXTURE)) tex = (TextureClass *)(image->getRawTextureData()); @@ -2656,7 +2697,7 @@ void W3DDisplay::drawImage( const Image *image, Int startX, Int startY, setup2DRenderState(tex, mode, grayscale); RectClass screen_rect(startX,startY,endX,endY); - RectClass uv_rect(uv->lo.x,uv->lo.y,uv->hi.x,uv->hi.y); + RectClass uv_rect = makeAtlasSafeUVRect(image); if (m_isClippedEnabled) { //need to clip this quad to clip rectangle @@ -2790,7 +2831,7 @@ VideoBuffer* W3DDisplay::createVideoBuffer() // first try to use the native format - WW3DFormat displayFormat = DX8Wrapper::getBackBufferFormat(); + WW3DFormat displayFormat = g_renderBackend->Get_Back_Buffer_Format(); if ( DX8Wrapper::Get_Current_Caps()->Support_Texture_Format( displayFormat )) { 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..7f278501a28 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); } @@ -2872,7 +2875,7 @@ void W3DRoadBuffer::insertCurveSegmentAt(Int ndx1, Int ndx2) line1.Set(Vector3(pr1->X, pr1->Y, 0), Vector3(pr2->X, pr2->Y, 0)); line2.Set(Vector3(pr3->X, pr3->Y, 0), Vector3(pr4->X, pr4->Y, 0)); } - Real angle = WWMath::Acos(curSin); + Real angle = WWMath::Acos_Legacy(curSin); Real count = angle / (PI/6.0f); // number of 30 degree steps. if (count<0.9 || m_roads[ndx1].m_pt1.isAngled) { miter(ndx1, ndx2); @@ -3245,6 +3248,14 @@ void W3DRoadBuffer::drawRoads(CameraClass * camera, TextureClass *cloudTexture, //Find number of passes required to render current shader devicePasses=W3DShaderManager::getShaderPasses(st); +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @bugfix bobtista 24/04/2026 Roads use + // the same cloud/noise multipass family as terrain; bgfx's fixed + // function fallback does not emulate TCI_CAMERASPACEPOSITION, so + // pass 2+ reads from garbage UVs and paints terrain/road tiles black. + devicePasses = 1; +#endif + W3DShaderManager::setTexture(1,cloudTexture); //cloud W3DShaderManager::setTexture(2,noiseTexture); //noise/lightmap @@ -3257,10 +3268,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 +3285,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 +3295,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..10d50f1a8df 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,64 +1239,61 @@ 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 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); + oldColorWriteEnable = g_renderBackend->Get_Color_Write_Mask(); + g_renderBackend->Set_Color_Write_Mask(0); } 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) - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,oldColorWriteEnable); + g_renderBackend->Set_Color_Write_Mask(oldColorWriteEnable); } @@ -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. } @@ -1947,4 +1952,3 @@ void RTS3DScene::Visibility_Check(CameraClass * camera) * */ - diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp index e60510a3c8b..4fec52b6354 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,34 @@ 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); + } + + // 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, + visStartX, visStartY, + dstPoint.x, dstPoint.y, + m_srcTexturePitch, + srcDesc.Format); } 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 5ac13989f8d..89c0d8365e2 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,16 @@ void W3DStatusCircle::Render(RenderInfoClass & rinfo) if (m_needUpdate) { updateCircleVB(); } + // 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. + //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 +335,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 +346,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 +356,32 @@ 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: - DX8Wrapper::Set_DX8_Render_State(D3DRS_BLENDOP, D3DBLENDOP_REVSUBTRACT ); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); - DX8Wrapper::Set_DX8_Render_State(D3DRS_BLENDOP, D3DBLENDOP_ADD ); + // 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)); + 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); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); + 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); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); + g_renderBackend->Set_Blend_Factors(RB_BLEND_ZERO, RB_BLEND_SRC_COLOR); + g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); break; } ShaderClass::Invalidate(); 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/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/Libraries/Source/WWVegas/WW3D2/camera.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp index b66ebc26cda..ec0350ae8e2 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp @@ -769,13 +769,13 @@ void CameraClass::Get_Clip_Planes(float & znear,float & zfar) const float CameraClass::Get_Horizontal_FOV() const { float width = ViewPlane.Max.X - ViewPlane.Min.X; - return 2*WWMath::Atan2(width,2.0); + return 2*WWMath::Atan2_Legacy(width,2.0); } float CameraClass::Get_Vertical_FOV() const { float height = ViewPlane.Max.Y - ViewPlane.Min.Y; - return 2*WWMath::Atan2(height,2.0); + return 2*WWMath::Atan2_Legacy(height,2.0); } float CameraClass::Get_Aspect_Ratio() const diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/ddsfile.h b/Generals/Code/Libraries/Source/WWVegas/WW3D2/ddsfile.h index 973b7e71fc0..28d6e757f06 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/ddsfile.h +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/ddsfile.h @@ -138,7 +138,7 @@ struct LegacyDDSURFACEDESC2 { }; unsigned AlphaBitDepth; unsigned Reserved; - void* Surface; + unsigned Surface; union { LegacyDDCOLORKEY CKDestOverlay; @@ -152,6 +152,8 @@ struct LegacyDDSURFACEDESC2 { unsigned TextureStage; }; +static_assert(sizeof(LegacyDDSURFACEDESC2) == 124, "DDS surface descriptor must match on-disk size."); + enum DDSType { diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/mapper.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/mapper.cpp index c1c0d4ba9ca..eb99832beb1 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/mapper.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/mapper.cpp @@ -102,8 +102,8 @@ void LinearOffsetTextureMapperClass::Apply(int uv_array_index) float offset_v = CurrentUVOffset.Y + UVOffsetDeltaPerMS.Y * del; // ensure both coordinates of offset are in [0, 1] range: - offset_u = offset_u - WWMath::Floor(offset_u); - offset_v = offset_v - WWMath::Floor(offset_v); + offset_u = offset_u - WWMath::Floorf(offset_u); + offset_v = offset_v - WWMath::Floorf(offset_v); // Set up the offset matrix Matrix3D m(true); @@ -326,8 +326,8 @@ void RotateTextureMapperClass::Apply(int uv_array_index) // Set up the rotation matrix float c,s; - c=WWMath::Cos(CurrentAngle); - s=WWMath::Sin(CurrentAngle); + c=WWMath::Cosf_Legacy(CurrentAngle); + s=WWMath::Sinf_Legacy(CurrentAngle); Matrix4x4 m(true); // subtract center @@ -392,8 +392,8 @@ void SineLinearOffsetTextureMapperClass::Apply(int uv_array_index) float offset_v=VAFP.X*sin(VAFP.Y*CurrentAngle+VAFP.Z*WWMATH_PI); // ensure both coordinates of offset are in [0, 1] range: - offset_u = offset_u - WWMath::Floor(offset_u); - offset_v = offset_v - WWMath::Floor(offset_v); + offset_u = offset_u - WWMath::Floorf(offset_u); + offset_v = offset_v - WWMath::Floorf(offset_v); // Set up the offset matrix Matrix3D m(true); @@ -455,8 +455,8 @@ void StepLinearOffsetTextureMapperClass::Apply(int uv_array_index) } // ensure both coordinates of offset are in [0, 1] range: - CurrentStep.U -= WWMath::Floor(CurrentStep.U); - CurrentStep.V -= WWMath::Floor(CurrentStep.V); + CurrentStep.U -= WWMath::Floorf(CurrentStep.U); + CurrentStep.V -= WWMath::Floorf(CurrentStep.V); // Set up the offset matrix Matrix3D m(true); @@ -533,8 +533,8 @@ void ZigZagLinearOffsetTextureMapperClass::Apply(int uv_array_index) } // ensure both coordinates of offset are in [0, 1] range: - offset_u = offset_u - WWMath::Floor(offset_u); - offset_v = offset_v - WWMath::Floor(offset_v); + offset_u = offset_u - WWMath::Floorf(offset_u); + offset_v = offset_v - WWMath::Floorf(offset_v); // Set up the offset matrix Matrix3D m(true); @@ -642,7 +642,7 @@ void EdgeMapperClass::Apply(int uv_array_index) LastUsedSyncTime=now; VOffset+=delta*VSpeed; - VOffset-=WWMath::Floor(VOffset); + VOffset-=WWMath::Floorf(VOffset); // takes the Z component and // uses it to index the texture @@ -740,8 +740,8 @@ void ScreenMapperClass::Apply(int uv_array_index) float offset_v = CurrentUVOffset.Y + UVOffsetDeltaPerMS.Y * del; // ensure both coordinates of offset are in [0, 1] range: - offset_u = offset_u - WWMath::Floor(offset_u); - offset_v = offset_v - WWMath::Floor(offset_v); + offset_u = offset_u - WWMath::Floorf(offset_u); + offset_v = offset_v - WWMath::Floorf(offset_v); // multiply by projection matrix // followed by scale and translation diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/motchan.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/motchan.cpp index 26642645d59..c56fefbb256 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/motchan.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/motchan.cpp @@ -846,7 +846,7 @@ AdaptiveDeltaMotionChannelClass::AdaptiveDeltaMotionChannelClass() : //ratio = ((ratio + 1.0f) / 128.0f); ratio/=((float) FILTER_TABLE_GEN_SIZE); - filtertable[i + FILTER_TABLE_GEN_START] = 1.0f - WWMath::Sin( DEG_TO_RAD(90.0f * ratio)); + filtertable[i + FILTER_TABLE_GEN_START] = 1.0f - WWMath::Sinf_Legacy( DEG_TO_RAD(90.0f * ratio)); } table_valid = true; diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp index 6d32d14b4ee..44f19504a0f 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp @@ -661,7 +661,7 @@ void ParticleEmitterClass::Initialize_Particle(NewParticleStruct * newpart, Vector3 outwards; float pos_l2 = rand_pos.Length2(); if (pos_l2) { - outwards = rand_pos * (OutwardVel * WWMath::Inv_Sqrt(pos_l2)); + outwards = rand_pos * (OutwardVel * WWMath::Inv_Sqrt_Legacy(pos_l2)); } else { outwards.X = OutwardVel; outwards.Y = 0.0f; diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp index a36cf4a5901..aae18026636 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" @@ -198,8 +199,8 @@ Vector2 Render2DClass::Convert_Vert( const Vector2 & v ) out.Y = (out.Y - 1.0f) * (Get_Screen_Resolution().Height() * -0.5f); // Round to nearest pixel - out.X = WWMath::Floor( out.X + 0.5f ); - out.Y = WWMath::Floor( out.Y + 0.5f ); + out.X = WWMath::Floorf( out.X + 0.5f ); + out.Y = WWMath::Floorf( out.Y + 0.5f ); // Bias if ( WW3D::Is_Screen_UV_Biased() ) { // Global bais setting @@ -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,14 @@ 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. + 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); @@ -615,16 +617,19 @@ 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. + g_renderBackend->Set_Grayscale_Mode(false); + } } @@ -781,4 +786,3 @@ Vector2 Render2DTextClass::Get_Text_Extents( const WCHAR * text ) return extent; } - diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp index c12469332d5..84d1d443f2d 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp +++ b/Generals/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" @@ -1360,19 +1361,14 @@ void WW3D::Make_Screen_Shot( const char * filename_base , const float gamma, con gamma_lut[i] = (unsigned char) (256.0f * powf(i / 256.0f, recip)); } - // TheSuperHackers @bugfix xezon 21/05/2025 Get the back buffer and create a copy of the surface. - // Originally this code took the front buffer and tried to lock it. This does not work when the - // render view clips outside the desktop boundaries. It crashed the game. - SurfaceClass* surface = DX8Wrapper::_Get_DX8_Back_Buffer(); + SurfaceClass* surfaceCopy = (g_renderBackend != nullptr) ? g_renderBackend->Capture_Back_Buffer_Surface(0) : nullptr; + if (surfaceCopy == nullptr) + { + return; + } SurfaceClass::SurfaceDescription surfaceDesc; - surface->Get_Description(surfaceDesc); - - SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (DX8Wrapper::_Create_DX8_Surface(surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format))); - DX8Wrapper::_Copy_DX8_Rects(surface->Peek_D3D_Surface(), nullptr, 0, surfaceCopy->Peek_D3D_Surface(), nullptr); - - surface->Release_Ref(); - surface = nullptr; + surfaceCopy->Get_Description(surfaceDesc); struct Rect { @@ -1710,19 +1706,14 @@ void WW3D::Update_Movie_Capture() WWPROFILE("WW3D::Update_Movie_Capture"); WWDEBUG_SAY(( "Updating")); - // TheSuperHackers @bugfix xezon 21/05/2025 Get the back buffer and create a copy of the surface. - // Originally this code took the front buffer and tried to lock it. This does not work when the - // render view clips outside the desktop boundaries. It crashed the game. - SurfaceClass* surface = DX8Wrapper::_Get_DX8_Back_Buffer(); + SurfaceClass* surfaceCopy = (g_renderBackend != nullptr) ? g_renderBackend->Capture_Back_Buffer_Surface(0) : nullptr; + if (surfaceCopy == nullptr) + { + return; + } SurfaceClass::SurfaceDescription surfaceDesc; - surface->Get_Description(surfaceDesc); - - SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (DX8Wrapper::_Create_DX8_Surface(surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format))); - DX8Wrapper::_Copy_DX8_Rects(surface->Peek_D3D_Surface(), nullptr, 0, surfaceCopy->Peek_D3D_Surface(), nullptr); - - surface->Release_Ref(); - surface = nullptr; + surfaceCopy->Get_Description(surfaceDesc); struct Rect { @@ -2019,12 +2010,14 @@ void WW3D::Update_Pixel_Center() void WW3D::Set_Texture_Bitdepth(int bitdepth) { - DX8Wrapper::Set_Texture_Bitdepth(bitdepth); + if (g_renderBackend != nullptr) { + g_renderBackend->Set_Texture_Bitdepth(bitdepth); + } } int WW3D::Get_Texture_Bitdepth() { - return DX8Wrapper::Get_Texture_Bitdepth(); + return (g_renderBackend != nullptr) ? g_renderBackend->Get_Texture_Bitdepth() : 16; } void WW3D::Set_MSAA_Mode(MultiSampleModeEnum mode) 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/Generals/Code/Tools/WorldBuilder/src/GlobalLightOptions.cpp b/Generals/Code/Tools/WorldBuilder/src/GlobalLightOptions.cpp index 1eaa4ba4f2c..daccb868e94 100644 --- a/Generals/Code/Tools/WorldBuilder/src/GlobalLightOptions.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/GlobalLightOptions.cpp @@ -54,8 +54,8 @@ static void calcNewLight(Int lr, Int fb, Vector3 *newLight) newLight->Set(0,0,-1); Real yAngle = PI*(lr-90)/180; Real xAngle = PI*(fb-90)/180; - Real zAngle = xAngle * WWMath::Sin(yAngle); - xAngle *= WWMath::Cos(yAngle); + Real zAngle = xAngle * WWMath::Sinf(yAngle); + xAngle *= WWMath::Cosf(yAngle); newLight->Rotate_Y(yAngle); newLight->Rotate_X(xAngle); newLight->Rotate_Z(zAngle); @@ -94,8 +94,8 @@ void GlobalLightOptions::updateEditFields() void GlobalLightOptions::showLightFeedback(Int lightIndex) { Vector3 light(0,0,0); - light.X = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*cos(m_angleAzimuth[lightIndex]/180.0f*PI);// -WWMath::Sin(PI*(m_angleLR[lightIndex]-90)/180); - light.Y = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*sin(m_angleAzimuth[lightIndex]/180.0f*PI);//-WWMath::Sin(PI*(m_angleFB[lightIndex]-90)/180); + light.X = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*cos(m_angleAzimuth[lightIndex]/180.0f*PI);// -WWMath::Sinf(PI*(m_angleLR[lightIndex]-90)/180); + light.Y = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*sin(m_angleAzimuth[lightIndex]/180.0f*PI);//-WWMath::Sinf(PI*(m_angleFB[lightIndex]-90)/180); light.Z = cos (PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI); WbView3d * pView = CWorldBuilderDoc::GetActive3DView(); @@ -109,8 +109,8 @@ void GlobalLightOptions::showLightFeedback(Int lightIndex) void GlobalLightOptions::applyAngle(Int lightIndex) { Vector3 light(0,0,0); - light.X = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*cos(m_angleAzimuth[lightIndex]/180.0f*PI);// -WWMath::Sin(PI*(m_angleLR[lightIndex]-90)/180); - light.Y = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*sin(m_angleAzimuth[lightIndex]/180.0f*PI);//-WWMath::Sin(PI*(m_angleFB[lightIndex]-90)/180); + light.X = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*cos(m_angleAzimuth[lightIndex]/180.0f*PI);// -WWMath::Sinf(PI*(m_angleLR[lightIndex]-90)/180); + light.Y = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*sin(m_angleAzimuth[lightIndex]/180.0f*PI);//-WWMath::Sinf(PI*(m_angleFB[lightIndex]-90)/180); light.Z = cos (PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI); CString str; 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/Common/BezierSegment.h b/GeneralsMD/Code/GameEngine/Include/Common/BezierSegment.h index eb8dd12b6f9..f07a9ebb579 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/BezierSegment.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/BezierSegment.h @@ -29,7 +29,6 @@ #pragma once -#include #include "Common/STLTypedefs.h" #define USUAL_TOLERANCE 1.0f @@ -37,7 +36,15 @@ class BezierSegment { protected: - static const D3DXMATRIX s_bezBasisMatrix; + struct AxisCoefficients + { + Real cubic; + Real quadratic; + Real linear; + Real constant; + }; + + static AxisCoefficients getAxisCoefficients(Real p0, Real p1, Real p2, Real p3); Coord3D m_controlPoints[4]; public: // Constructors 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]; diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index 7f484111672..1af4cabba46 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -149,6 +149,67 @@ class GlobalData : public SubsystemInterface Bool m_useCloudPlane; Bool m_useShadowVolumes; Bool m_useShadowDecals; + Bool m_bgfxPostProcessing; + Real m_bgfxPostSharpenAmount; + Real m_bgfxPostSaturation; + Real m_bgfxPostContrast; + Real m_bgfxPostFxaaAmount; + Bool m_bgfxWipeEnabled; + Bool m_bgfxWipeFollowMouse; + Real m_bgfxWipeSplit; + Bool m_bgfxColorGrade; + Real m_bgfxColorGradeStrength; + Real m_bgfxColorGradeTemperature; + Real m_bgfxColorGradeTint; + Bool m_bgfxBloom; + Real m_bgfxBloomThreshold; + Real m_bgfxBloomIntensity; + Bool m_bgfxHdr; + Bool m_bgfxVignette; + Real m_bgfxVignetteStrength; + Bool m_bgfxChromaticAberration; + Real m_bgfxChromaticAberrationAmount; + Bool m_bgfxFilmGrain; + Real m_bgfxFilmGrainStrength; + Bool m_bgfxSSAO; + Real m_bgfxSSAORadius; + Real m_bgfxSSAOIntensity; + Real m_bgfxRenderScale; + Bool m_bgfxSpecular; + Real m_bgfxSpecularStrength; + Bool m_bgfxRimLight; + Real m_bgfxRimStrength; + Real m_bgfxRimPower; + Bool m_bgfxEmissiveBoost; + Real m_bgfxEmissiveBoostScale; + Bool m_bgfxShadowMaps; + Real m_bgfxShadowMapBias; + Real m_bgfxShadowMapStrength; + Bool m_bgfxShadowFullPcf; + Bool m_bgfxStencilShadows; + // TheSuperHackers @feature bobtista 23/06/2026 Toggle for the perspective point-light shadow map. + Bool m_bgfxDynamicLightShadows; + // TheSuperHackers @feature bobtista 16/07/2026 INI toggle for the experimental dramatic + // Particle Cannon lighting (mirrors the GGC_PCANNON_ENHANCED env flag, which still overrides). + Bool m_pcannonEnhanced; + // TheSuperHackers @tweak bobtista 18/07/2026 Live-tunable knobs for the enhanced Particle + // Cannon lighting so its feel can be dialed from Bgfx.ini without a rebuild. + Real m_pcannonFlashRadius; ///< Max horizontal offset of a lightning flash from the beam + Int m_pcannonFlashInterval; ///< Frames between flash windows (lower = more frequent) + Int m_pcannonFlashFadeIn; ///< Flash light fade-in frames (0 = instant pop) + Int m_pcannonFlashFadeOut; ///< Flash light fade-out frames + Real m_pcannonDimTarget; ///< Scene ambient floor at the beam (lower = darker/more impact) + Bool m_bgfxPointFilter; + Bool m_bgfxSoftParticles; + Real m_bgfxSoftParticleFadeScale; + Bool m_bgfxLogStats; + Bool m_bgfxNoSceneFramebuffer; + Bool m_bgfxNoPostFx; + Int m_bgfxMsaa; + Bool m_bgfxSrgb; + AsciiString m_bgfxRenderer; // empty = platform default; dx11, dx12, vulkan, metal, gl + Int m_bgfxScreenshotAfter; // 0 = disabled; otherwise once frameIndex >= this value, request a native bgfx screenshot every 500 frames into m_bgfxScreenshotPath..bmp + AsciiString m_bgfxScreenshotPath; Int m_textureReductionFactor; //how much to cut texture resolution: 2 is half, 3 is quarter, etc. Bool m_enableBehindBuildingMarkers; Real m_waterPositionX; @@ -351,6 +412,8 @@ 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 + AsciiString m_loadReplayGame; ///< If this is specified, load a replay 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/Include/GameClient/AnimateWindowManager.h b/GeneralsMD/Code/GameEngine/Include/GameClient/AnimateWindowManager.h index f232411213e..c26a0909fd8 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/AnimateWindowManager.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/AnimateWindowManager.h @@ -192,6 +192,10 @@ class AnimateWindowManager : public SubsystemInterface ProcessAnimateWindowSlideFromTopFast *m_slideFromTopFast; ///< holds the process in which the windows slide from the top,fast ProcessAnimateWindow *getProcessAnimate( AnimTypes animType); ///< returns the process for the kind of animation we need. + void updateStep(); ///< Runs a single base-rate step of all registered window animations + UnsignedInt m_lastUpdateTime; ///< Wall-clock time of the previous update, for frame-rate independent pacing + Real m_updateAccumulator; ///< Carries fractional base-rate steps between updates + }; //----------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h index e976c6f5735..36741fe1259 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h @@ -280,6 +280,9 @@ friend class GameWindow; virtual Int winSetModal( GameWindow *window ); ///< put at top of modal stack virtual Int winUnsetModal( GameWindow *window ); /**< take window off modal stack, if window is not at top of stack and error will occur */ + // TheSuperHackers @bugfix bobtista 09/07/2026 Removes every modal stack entry of a window, used + // when a window is destroyed. Destroyed windows below the top of the stack were never removed. + virtual void winRemoveFromModalStack( GameWindow *window ); //--------------------------------------------------------------------------- ///////////////////////////////////////////////////////////////////////////// diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h b/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h index ecc1c4fdaf5..e2179a90640 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h @@ -567,6 +567,7 @@ friend class Drawable; // for selection/deselection transactions static std::vector getUniqueIdleWorkers(const ObjectList& idleWorkers); virtual void recreateControlBar(); + virtual void onResolutionChanged(); ///< Rebuild the whole in-game HUD after a resolution/window-size change virtual void refreshCustomUiResources(); virtual void refreshNetworkLatencyResources(); virtual void refreshRenderFpsResources(); @@ -782,6 +783,12 @@ friend class Drawable; // for selection/deselection transactions UnsignedInt m_lastRenderFpsLimit; UnsignedInt m_lastRenderFpsUpdateMs; + // TheSuperHackers @bugfix bobtista 20/07/2026 Money/income last-displayed cache, moved off + // function-local statics in update() so a mid-match resolution change can reset it and force + // the freshly recreated MoneyDisplay gadget to repaint. + UnsignedInt m_lastMoneyDisplayed; + UnsignedInt m_lastIncomeDisplayed; + // System Time DisplayString * m_systemTimeString; AsciiString m_systemTimeFont; @@ -800,6 +807,8 @@ friend class Drawable; // for selection/deselection transactions Coord2D m_gameTimePosition; Color m_gameTimeColor; Color m_gameTimeDropColor; + Int m_gameTimeReservedWidth; + Int m_gameTimeFrameReservedWidth; struct PlayerInfoList { 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/GameClient/Shell.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Shell.h index a593f3cbcba..356c2b7cb5a 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Shell.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Shell.h @@ -131,7 +131,7 @@ class Shell : public SubsystemInterface // pseudo-stack operations for manipulating layouts void push( AsciiString filename, Bool shutdownImmediate = FALSE ); ///< load new screen on top, optionally doing an immediate shutdown void pop(); ///< pop top layout - void popImmediate(); ///< pop now, don't wait for shutdown + void popImmediate( Bool suppressNextInit = FALSE ); ///< pop now, don't wait for shutdown void showShell( Bool runInit = TRUE ); ///< init the top of stack void hideShell(); ///< shutdown the top of stack WindowLayout *top(); ///< return top layout diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/ScriptEngine.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/ScriptEngine.h index 3ed3d9fc00c..2fb45958dc9 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/ScriptEngine.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/ScriptEngine.h @@ -413,8 +413,15 @@ class ScriptEngine : public SubsystemInterface, VecSequentialScriptPtr m_sequentialScripts; + // TheSuperHackers @bugfix bobtista 09/07/2026 Sequential scripts removed while + // evaluateAndProgressAllSequentialScripts is still using them are kept here and + // deleted after the evaluation. Not xfered. + VecSequentialScriptPtr m_deferredDeleteSequentialScripts; + void evaluateAndProgressAllSequentialScripts(); VecSequentialScriptPtrIt cleanupSequentialScript(VecSequentialScriptPtrIt it, Bool cleanDanglers); + size_t cleanupSequentialScriptAtIndex(size_t index, Bool cleanDanglers); + void deleteDeferredSequentialScripts(); Bool hasUnitCompletedSequentialScript( Object *object, const AsciiString& sequentialScriptName ); Bool hasTeamCompletedSequentialScript( Team *team, const AsciiString& sequentialScriptName ); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h index c5997707ba7..28cad7246c9 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h @@ -781,6 +781,9 @@ class Weapon : public MemoryPoolObject, ObjectID* projectileID, Bool inflictDamage ); + // finish the ammo/barrel/reload bookkeeping for a shot that was just fired. returns true if we auto-reloaded. + Bool finalizeFiredShot(const Object *sourceObj, UnsignedInt now, const WeaponBonus& bonus); + Real estimateWeaponDamage(const Object *sourceObj, const Object *victimObj, const Coord3D* victimPos); void reloadWithBonus(const Object *source, const WeaponBonus& bonus, Bool loadInstantly); @@ -852,6 +855,11 @@ class WeaponStore : public SubsystemInterface void createAndFireTempWeapon(const WeaponTemplate* w, const Object *source, const Coord3D* pos); void createAndFireTempWeapon(const WeaponTemplate* w, const Object *source, Object *target); + // TheSuperHackers @bugfix bobtista 08/07/2026 Deletes a weapon at the end of the frame instead of + // immediately. Used by WeaponSet::updateWeaponSet, which can run while one of its weapons is still + // firing further down the call stack. + void deleteWeaponDeferred(Weapon* weapon); + void handleProjectileDetonation( const WeaponTemplate* w, const Object *source, const Coord3D* pos, WeaponBonusConditionFlags extraBonusFlags, Bool inflictDamage = TRUE ); static void parseWeaponTemplateDefinition(INI* ini); @@ -864,6 +872,7 @@ class WeaponStore : public SubsystemInterface WeaponTemplate *newOverride( WeaponTemplate *weaponTemplate ); void deleteAllDelayedDamage(); + void deleteAllDeferredWeapons(); void resetWeaponTemplates(); void setDelayedDamage(const WeaponTemplate *weapon, const Coord3D* pos, UnsignedInt whichFrame, ObjectID sourceID, ObjectID victimID, const WeaponBonus& bonus); @@ -893,6 +902,8 @@ class WeaponStore : public SubsystemInterface WeaponTemplateMap m_weaponTemplateHashMap; std::list m_weaponDDI; + + std::vector m_deferredDeleteWeapons; ///< weapons replaced mid-fire, deleted at the end of the frame }; // EXTERNALS ////////////////////////////////////////////////////////////////////////////////////// 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/Bezier/BezFwdIterator.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Bezier/BezFwdIterator.cpp index 03f199fb338..dca2b4e1370 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Bezier/BezFwdIterator.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Bezier/BezFwdIterator.cpp @@ -57,22 +57,18 @@ void BezFwdIterator::start() float d2 = d * d; float d3 = d * d2; - D3DXVECTOR4 px(mBezSeg.m_controlPoints[0].x, mBezSeg.m_controlPoints[1].x, mBezSeg.m_controlPoints[2].x, mBezSeg.m_controlPoints[3].x); - D3DXVECTOR4 py(mBezSeg.m_controlPoints[0].y, mBezSeg.m_controlPoints[1].y, mBezSeg.m_controlPoints[2].y, mBezSeg.m_controlPoints[3].y); - D3DXVECTOR4 pz(mBezSeg.m_controlPoints[0].z, mBezSeg.m_controlPoints[1].z, mBezSeg.m_controlPoints[2].z, mBezSeg.m_controlPoints[3].z); - - D3DXVECTOR4 cVec[3]; - D3DXVec4Transform(&cVec[0], &px, &BezierSegment::s_bezBasisMatrix); - D3DXVec4Transform(&cVec[1], &py, &BezierSegment::s_bezBasisMatrix); - D3DXVec4Transform(&cVec[2], &pz, &BezierSegment::s_bezBasisMatrix); + BezierSegment::AxisCoefficients cVec[3]; + cVec[0] = BezierSegment::getAxisCoefficients(mBezSeg.m_controlPoints[0].x, mBezSeg.m_controlPoints[1].x, mBezSeg.m_controlPoints[2].x, mBezSeg.m_controlPoints[3].x); + cVec[1] = BezierSegment::getAxisCoefficients(mBezSeg.m_controlPoints[0].y, mBezSeg.m_controlPoints[1].y, mBezSeg.m_controlPoints[2].y, mBezSeg.m_controlPoints[3].y); + cVec[2] = BezierSegment::getAxisCoefficients(mBezSeg.m_controlPoints[0].z, mBezSeg.m_controlPoints[1].z, mBezSeg.m_controlPoints[2].z, mBezSeg.m_controlPoints[3].z); mCurrPoint = mBezSeg.m_controlPoints[0]; int i = 3; while (i--) { - float a = cVec[i].x; - float b = cVec[i].y; - float c = cVec[i].z; + float a = cVec[i].cubic; + float b = cVec[i].quadratic; + float c = cVec[i].linear; float *pD, *pDD, *pDDD; @@ -117,4 +113,3 @@ void BezFwdIterator::next() ++mStep; } - diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Bezier/BezierSegment.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Bezier/BezierSegment.cpp index 3154ec0f351..ea1c6d57d0b 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Bezier/BezierSegment.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Bezier/BezierSegment.cpp @@ -27,8 +27,6 @@ #include "Common/BezierSegment.h" #include "Common/BezFwdIterator.h" -#include - //------------------------------------------------------------------------------------------------- BezierSegment::BezierSegment() { @@ -95,6 +93,17 @@ BezierSegment::BezierSegment(Coord3D cp[4]) } +//------------------------------------------------------------------------------------------------- +BezierSegment::AxisCoefficients BezierSegment::getAxisCoefficients(Real p0, Real p1, Real p2, Real p3) +{ + AxisCoefficients coeffs; + coeffs.cubic = -p0 + (3.0f * p1) - (3.0f * p2) + p3; + coeffs.quadratic = (3.0f * p0) - (6.0f * p1) + (3.0f * p2); + coeffs.linear = (-3.0f * p0) + (3.0f * p1); + coeffs.constant = p0; + return coeffs; +} + //------------------------------------------------------------------------------------------------- void BezierSegment::evaluateBezSegmentAtT(Real tValue, Coord3D *outResult) const @@ -102,18 +111,15 @@ void BezierSegment::evaluateBezSegmentAtT(Real tValue, Coord3D *outResult) const if (!outResult) return; - D3DXVECTOR4 tVec(tValue * tValue * tValue, tValue * tValue, tValue, 1); + const Real tSquared = tValue * tValue; + const Real tCubed = tSquared * tValue; + const AxisCoefficients x = getAxisCoefficients(m_controlPoints[0].x, m_controlPoints[1].x, m_controlPoints[2].x, m_controlPoints[3].x); + const AxisCoefficients y = getAxisCoefficients(m_controlPoints[0].y, m_controlPoints[1].y, m_controlPoints[2].y, m_controlPoints[3].y); + const AxisCoefficients z = getAxisCoefficients(m_controlPoints[0].z, m_controlPoints[1].z, m_controlPoints[2].z, m_controlPoints[3].z); - D3DXVECTOR4 xCoords(m_controlPoints[0].x, m_controlPoints[1].x, m_controlPoints[2].x, m_controlPoints[3].x); - D3DXVECTOR4 yCoords(m_controlPoints[0].y, m_controlPoints[1].y, m_controlPoints[2].y, m_controlPoints[3].y); - D3DXVECTOR4 zCoords(m_controlPoints[0].z, m_controlPoints[1].z, m_controlPoints[2].z, m_controlPoints[3].z); - - D3DXVECTOR4 tResult; - D3DXVec4Transform(&tResult, &tVec, &BezierSegment::s_bezBasisMatrix); - - outResult->x = D3DXVec4Dot(&xCoords, &tResult); - outResult->y = D3DXVec4Dot(&yCoords, &tResult); - outResult->z = D3DXVec4Dot(&zCoords, &tResult); + outResult->x = (x.cubic * tCubed) + (x.quadratic * tSquared) + (x.linear * tValue) + x.constant; + outResult->y = (y.cubic * tCubed) + (y.quadratic * tSquared) + (y.linear * tValue) + y.constant; + outResult->z = (z.cubic * tCubed) + (z.quadratic * tSquared) + (z.linear * tValue) + z.constant; } //------------------------------------------------------------------------------------------------- @@ -235,12 +241,3 @@ void BezierSegment::splitSegmentAtT(Real tValue, BezierSegment &outSeg1, BezierS outSeg2.m_controlPoints[2] = p2p3; outSeg2.m_controlPoints[3] = m_controlPoints[3]; } - -//------------------------------------------------------------------------------------------------- -// The Basis Matrix for a bezier segment -const D3DXMATRIX BezierSegment::s_bezBasisMatrix( - -1.0f, 3.0f, -3.0f, 1.0f, - 3.0f, -6.0f, 3.0f, 0.0f, - -3.0f, 3.0f, 0.0f, 0.0f, - 1.0f, 0.0f, 0.0f, 0.0f -); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp index 772830f0f67..06de018179a 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp @@ -28,6 +28,8 @@ #include "Common/ArchiveFileSystem.h" #include "Common/CommandLine.h" #include "Common/CRCDebug.h" +#include "Common/Diagnostic/SimulationMathCrc.h" +#include "Common/FramePacer.h" #include "Common/LocalFileSystem.h" #include "Common/Recorder.h" #include "Common/version.h" @@ -35,11 +37,11 @@ #include "GameClient/TerrainVisual.h" // for TERRAIN_LOD_MIN definition #include "GameClient/GameText.h" #include "GameNetwork/NetworkDefs.h" +#include "GgcRuntimeFlags.h" #include "WWLib/trim.h" - Bool TheDebugIgnoreSyncErrors = FALSE; extern Int DX8Wrapper_PreserveFPU; @@ -122,6 +124,174 @@ Int parseWin(char *args[], int) return 1; } +//============================================================================= +//============================================================================= +// TheSuperHackers @feature bobtista 24/06/2026 Enable the bgfx renderer's optional +// lighting effects in one switch (for demos/sharing). These all default off (CRC-safe) +// and are no-ops on the DX8 backend. Equivalent to the Ctrl+Alt+N / +Y / +R / +K dev hotkeys. +Int parseBgfxEffects(char *args[], int) +{ + TheWritableGlobalData->m_bgfxDynamicLightShadows = TRUE; // nuke + particle-cannon lights/shadows + TheWritableGlobalData->m_bgfxShadowMaps = TRUE; // sun shadow map + TheWritableGlobalData->m_bgfxRimLight = TRUE; // rim light + TheWritableGlobalData->m_bgfxEmissiveBoost = TRUE; // emissive boost + TheWritableGlobalData->m_bgfxEmissiveBoostScale = 2.0f; + + return 1; +} + +//============================================================================= +//============================================================================= +Int parseBgfxNoEffects(char *args[], int) +{ + TheWritableGlobalData->m_bgfxDynamicLightShadows = FALSE; + TheWritableGlobalData->m_bgfxShadowMaps = FALSE; + TheWritableGlobalData->m_bgfxRimLight = FALSE; + TheWritableGlobalData->m_bgfxEmissiveBoost = FALSE; + + return 1; +} + +//============================================================================= +//============================================================================= +static Int parseSetFlag(GgcFlagId id) +{ + GgcFlags::SetOverride(id, "1"); + return 1; +} + +Int parseBgfxProbeNullSubmit(char *args[], int) +{ + return parseSetFlag(GgcFlag_ProbeNullSubmit); +} + +Int parseBgfxProbeFreezeState(char *args[], int) +{ + return parseSetFlag(GgcFlag_ProbeFreezeState); +} + +Int parseBgfxProbeNoSorted(char *args[], int) +{ + return parseSetFlag(GgcFlag_ProbeNoSorted); +} + +Int parseBgfxProbeNoTexBind(char *args[], int) +{ + return parseSetFlag(GgcFlag_ProbeNoTexBind); +} + +Int parseBgfxProbeNoMaterialUniform(char *args[], int) +{ + return parseSetFlag(GgcFlag_ProbeNoMatUniform); +} + +Int parseBgfxProbeNoLightUniform(char *args[], int) +{ + return parseSetFlag(GgcFlag_ProbeNoLightUniform); +} + +Int parseBgfxProbeNoRenderThread(char *args[], int) +{ + return parseSetFlag(GgcFlag_BgfxNoRenderThread); +} + +Int parseBgfxProbeNoParticleRender(char *args[], int) +{ + return parseSetFlag(GgcFlag_ProbeNoParticleRender); +} + +Int parseBgfxProbeNoSortFlush(char *args[], int) +{ + return parseSetFlag(GgcFlag_ProbeNoSortFlush); +} + +Int parseBgfxProbeNoSceneObjectRender(char *args[], int) +{ + return parseSetFlag(GgcFlag_ProbeNoSceneObjectRender); +} + +Int parseBgfxProbeIdentityInstances(char *args[], int) +{ + return parseSetFlag(GgcFlag_ProbeIdentityInstances); +} + +Int parseBgfxProbeTransposeInstances(char *args[], int) +{ + return parseSetFlag(GgcFlag_ProbeTransposeInstances); +} + +Int parseBgfxInstancingNoReorder(char *args[], int) +{ + return parseSetFlag(GgcFlag_BgfxInstancingNoReorder); +} + +Int parseBgfxDisableInstancing(char *args[], int) +{ + return parseSetFlag(GgcFlag_BgfxNoInstancing); +} + +Int parseBgfxDisableSortedMaterialRecaptureSkip(char *args[], int) +{ + return parseSetFlag(GgcFlag_BgfxDisableSortedMaterialRecaptureSkip); +} + +Int parseBgfxDisableSortedMaterialSnapshot(char *args[], int) +{ + return parseSetFlag(GgcFlag_BgfxDisableSortedMaterialSnapshot); +} + +Int parseBgfxSortedTextureArray(char *args[], int) +{ + // No-op since the merge became the default; accepted so existing harnesses stay valid. + return parseSetFlag(GgcFlag_BgfxSortedTextureArray); +} + +Int parseBgfxNoSortedTextureArray(char *args[], int) +{ + return parseSetFlag(GgcFlag_BgfxNoSortedTextureArray); +} + +Int parseBgfxFrameTimingAfter(char *args[], int num) +{ + if (num > 1) + { + GgcFlags::SetOverride(GgcFlag_BgfxFrameTimingAfter, args[1]); + return 2; + } + return 1; +} + +Int parseBgfxFrameTimingInterval(char *args[], int num) +{ + if (num > 1) + { + GgcFlags::SetOverride(GgcFlag_BgfxFrameTimingInterval, args[1]); + return 2; + } + return 1; +} + +Int parseBgfxFrameTimingPath(char *args[], int num) +{ + if (num > 1) + { + GgcFlags::SetOverride(GgcFlag_BgfxFrameTimingPath, args[1]); + return 2; + } + return 1; +} + +Int parseBgfxSortedPacketCollectorDiag(char *args[], int num) +{ + GgcFlags::SetOverride(GgcFlag_BgfxSortedPacketCollectorDiag, "1"); + if (num > 1 && args[1][0] != '-') + { + GgcFlags::SetOverride(GgcFlag_BgfxSortedPacketCollectorDiagLimit, args[1]); + return 2; + } + return 1; +} + //============================================================================= //============================================================================= Int parseNoMusic(char *args[], int) @@ -390,6 +560,34 @@ Int parseFullVersion(char *args[], int num) return 1; } +// TheSuperHackers @feature bobtista 09/06/2026 Print the deterministic simulation-math CRC +// for cross-platform parity testing. Run on each machine with -mathCrcCheck and compare the +// printed value; identical CRCs confirm the deterministic math path matches across architectures. +Int parseMathCrcCheck(char *args[], int) +{ + const UnsignedInt crc = SimulationMathCrc::calculate(); + const UnsignedInt crcDouble = SimulationMathCrc::calculateDouble(); + printf("SimulationMathCrc = %08X\n", crc); + printf("SimulationMathCrcDouble = %08X\n", crcDouble); + fflush(stdout); + DEBUG_LOG(("SimulationMathCrc = %08X", crc)); + DEBUG_LOG(("SimulationMathCrcDouble = %08X", crcDouble)); + // TheSuperHackers @feature bobtista 14/06/2026 Also write the result to a plain file so the + // parity probe is machine-readable on a Windows GUI build (no stdout) and in pure Release + // builds (DEBUG_LOG compiled out). scripts/determinism/check-mathcrc compares this artifact. + FILE *crcFile = fopen("SimulationMathCrc.txt", "wt"); + if (crcFile != nullptr) + { + fprintf(crcFile, "SimulationMathCrc = %08X\n", crc); + fprintf(crcFile, "SimulationMathCrcDouble = %08X\n", crcDouble); + fclose(crcFile); + } + // Pure math parity check - print and exit before launching the game so it can be run + // instantly on each machine and the value compared. + exit(0); + return 1; +} + Int parseNoShadows(char *args[], int) { TheWritableGlobalData->m_useShadowVolumes = false; @@ -671,6 +869,64 @@ Int parsePreload( char *args[], int num ) #endif +// TheSuperHackers @feature bobtista 17/04/2026 Load a save game file from the command line. +// TheSuperHackers @tweak bobtista 01/06/2026 also skip intro/sizzle/shellmap so frame counting +// starts near gameplay — needed for deterministic side-by-side diagnostic captures. +Int parseLoadSave(char *args[], int num) +{ + if (num > 1) + { + TheWritableGlobalData->m_loadSaveGame = args[1]; + TheWritableGlobalData->m_shellMapOn = FALSE; + TheWritableGlobalData->m_playIntro = FALSE; + TheWritableGlobalData->m_playSizzle = FALSE; + } + return 2; +} + +// TheSuperHackers @feature bobtista 14/05/2026 Load a map directly from the +// command line in release builds. Useful for creating visual regression saves +// from maps that are normally only reached through shell flow. +Int parseLoadMap(char *args[], int num) +{ + if (num > 1) + { + TheWritableGlobalData->m_initialFile = args[1]; + TheWritableGlobalData->m_shellMapOn = FALSE; + TheWritableGlobalData->m_playIntro = FALSE; + return 2; + } + return 1; +} + +// TheSuperHackers @feature bobtista 30/04/2026 Load a replay visually from the command line +Int parseLoadReplay(char *args[], int num) +{ + if (num > 1) + { + AsciiString filename = args[1]; + if (!filename.endsWithNoCase(RecorderClass::getReplayExtention())) + { + printf("Invalid replay name \"%s\"\n", filename.str()); + exit(1); + } + + TheWritableGlobalData->m_loadReplayGame = filename; + TheWritableGlobalData->m_playIntro = FALSE; + TheWritableGlobalData->m_playSizzle = FALSE; + TheWritableGlobalData->m_shellMapOn = FALSE; + // TheSuperHackers @feature bobtista 30/04/2026 Command-line visual + // replay loads are used as rendering/performance harnesses across + // patched builds, so keep CRC mismatch banners from covering the view. + TheDebugIgnoreSyncErrors = true; + + return 2; + } + + return 1; +} + + #if defined(RTS_DEBUG) Int parseDisplayDebug(char *args[], int) { @@ -689,7 +945,6 @@ Int parseFile(char *args[], int num) return 2; } - Int parsePreloadEverything( char *args[], int num ) { TheWritableGlobalData->m_preloadAssets = TRUE; @@ -819,7 +1074,6 @@ Int parseWinCursors(char *args[], int num) Int parseQuickStart( char *args[], int num ) { parseNoLogo( args, num ); - parseNoShellMap( args, num ); parseNoWindowAnimation( args, num ); return 1; } @@ -1011,6 +1265,146 @@ Int parseNoFPSLimit(char *args[], int num) return 1; } +Int parseMaxRenderFPS(char *args[], int num) +{ + if (num > 1) + { + int fps = atoi(args[1]); + if (fps <= 0) + { + TheWritableGlobalData->m_useFpsLimit = false; + TheWritableGlobalData->m_framesPerSecondLimit = 30000; + } + else + { + TheWritableGlobalData->m_useFpsLimit = true; + TheWritableGlobalData->m_framesPerSecondLimit = fps; + } + return 2; + } + return 1; +} + +// TheSuperHackers @feature bobtista 03/06/2026 Pin the logic-tick rate to +// fps regardless of render rate. Use with -noFPSLimit to measure pure +// rendering throughput: render runs as fast as possible while simulation +// state advances at the same rate every run. +Int parseFixedLogicFPS(char *args[], int num) +{ + if (num > 1) + { + int fps = atoi(args[1]); + if (fps > 0 && TheFramePacer != nullptr) + { + TheFramePacer->enableLogicTimeScale(TRUE); + TheFramePacer->setLogicTimeScaleFps(fps); + } + return 2; + } + return 1; +} + +Int parseMsaa(char *args[], int num) +{ + if (num > 1) + { + int level = atoi(args[1]); + if (level == 2 || level == 4 || level == 8 || level == 16) + { + TheWritableGlobalData->m_bgfxMsaa = level; + char buf[8]; + snprintf(buf, sizeof(buf), "%d", level); + GgcFlags::SetOverride(GgcFlag_BgfxMsaa, buf); + } + return 2; + } + return 1; +} + +Int parseSrgb(char *args[], int num) +{ + GgcFlags::SetOverride(GgcFlag_BgfxSrgb, "1"); + return 1; +} + +Int parseLogFrameTimes(char *args[], int num) +{ + if (TheFramePacer != nullptr) + { + TheFramePacer->enablePerformanceLog(TRUE); + } + else + { + DEBUG_LOG(("parseLogFrameTimes() - TheFramePacer is not initialized")); + } + + return 1; +} + +Int parseLogBgfxStats(char *args[], int num) +{ + TheWritableGlobalData->m_bgfxLogStats = TRUE; + + return 1; +} + +Int parsePerfAutoExitSeconds(char *args[], int num) +{ + if (num > 1) + { + int seconds = atoi(args[1]); + if (seconds > 0) + { + char buf[16]; + snprintf(buf, sizeof(buf), "%d", seconds); + GgcFlags::SetOverride(GgcFlag_AutoExitSeconds, buf); + } + return 2; + } + + return 1; +} + +Int parseBgfxSkipStaticVolumeShadows(char *args[], int) +{ + GgcFlags::SetOverride(GgcFlag_BgfxSkipStaticVolumeShadows, "1"); + return 1; +} + +Int parseBgfxNoSceneFramebuffer(char *args[], int num) +{ + TheWritableGlobalData->m_bgfxNoSceneFramebuffer = TRUE; + + return 1; +} + +Int parseBgfxNoPostFx(char *args[], int num) +{ + TheWritableGlobalData->m_bgfxNoPostFx = TRUE; + + return 1; +} + +Int parseBgfxScreenshotAfter(char *args[], int num) +{ + // -bgfxScreenshotAfter [] + // Once frameIndex >= , request a native bgfx screenshot every 500 + // frames into .NNNNNN.bmp. Default base path is bgfx_capture.bmp + // in the working directory (developer iteration tool — not shipped). + if (num > 1) + { + TheWritableGlobalData->m_bgfxScreenshotAfter = atoi(args[1]); + if (num > 2 && args[2][0] != '-') + { + TheWritableGlobalData->m_bgfxScreenshotPath = args[2]; + return 3; + } + TheWritableGlobalData->m_bgfxScreenshotPath = "bgfx_capture.bmp"; + return 2; + } + return 1; +} + Int parseDumpAssetUsage(char *args[], int num) { TheWritableGlobalData->m_dumpAssetUsage = true; @@ -1053,6 +1447,12 @@ Int parseMod(char *args[], Int num) if (!TheLocalFileSystem->doesFileExist(modPath.str())) { + // TheSuperHackers @tweak bobtista 29/07/2026 A mistyped mod name used to + // drop back to the base game silently in release builds, which looks just + // like a mod that loaded and changed nothing. Report it where a release + // build can see it, as failed archive mounts already do. + fprintf(stderr, "[ggc] mod not found, starting without it: %s\n", modPath.str()); + fflush(stderr); DEBUG_LOG(("Mod does not exist.")); return 2; // no such file/dir. } @@ -1061,6 +1461,8 @@ Int parseMod(char *args[], Int num) struct _stat statBuf; if (_stat(modPath.str(), &statBuf) != 0) { + fprintf(stderr, "[ggc] mod could not be read, starting without it: %s\n", modPath.str()); + fflush(stderr); DEBUG_LOG(("Could not _stat() mod.")); return 2; // could not stat the file/dir. } @@ -1147,11 +1549,14 @@ static CommandLineParam paramsForStartup[] = static CommandLineParam paramsForEngineInit[] = { { "-nologo", parseNoLogo }, // TheSuperHackers @tweak Is now available in Release builds. + { "-bgfxEffects", parseBgfxEffects }, // must be in this (post-INI) table so it overrides Bgfx.ini/GameData.ini + { "-bgfxNoEffects", parseBgfxNoEffects }, // must be post-INI so parity/perf runs can override Bgfx.ini/GameData.ini { "-noshellmap", parseNoShellMap }, { "-noShellAnim", parseNoWindowAnimation }, // TheSuperHackers @tweak Is now available in Release builds. { "-xres", parseXRes }, { "-yres", parseYRes }, { "-fullVersion", parseFullVersion }, + { "-mathCrcCheck", parseMathCrcCheck }, { "-particleEdit", parseParticleEdit }, { "-scriptDebug", parseScriptDebug }, { "-playStats", parsePlayStats }, @@ -1159,6 +1564,44 @@ static CommandLineParam paramsForEngineInit[] = { "-noshaders", parseNoShaders }, { "-quickstart", parseQuickStart }, { "-useWaveEditor", parseUseWaveEditor }, + { "-loadmap", parseLoadMap }, + { "-loadsave", parseLoadSave }, + { "-loadreplay", parseLoadReplay }, + { "-ignoresync", parseSync }, + { "-noFPSLimit", parseNoFPSLimit }, + { "-maxRenderFPS", parseMaxRenderFPS }, + { "-fixedLogicFPS", parseFixedLogicFPS }, + { "-perfAutoExitSeconds", parsePerfAutoExitSeconds }, + { "-bgfxSkipStaticVolumeShadows", parseBgfxSkipStaticVolumeShadows }, + { "-bgfxProbeNullSubmit", parseBgfxProbeNullSubmit }, + { "-bgfxProbeFreezeState", parseBgfxProbeFreezeState }, + { "-bgfxProbeNoSorted", parseBgfxProbeNoSorted }, + { "-bgfxProbeNoTexBind", parseBgfxProbeNoTexBind }, + { "-bgfxProbeNoMaterialUniform", parseBgfxProbeNoMaterialUniform }, + { "-bgfxProbeNoLightUniform", parseBgfxProbeNoLightUniform }, + { "-bgfxProbeNoRenderThread", parseBgfxProbeNoRenderThread }, + { "-bgfxProbeNoParticleRender", parseBgfxProbeNoParticleRender }, + { "-bgfxProbeNoSortFlush", parseBgfxProbeNoSortFlush }, + { "-bgfxProbeNoSceneObjectRender", parseBgfxProbeNoSceneObjectRender }, + { "-bgfxProbeIdentityInstances", parseBgfxProbeIdentityInstances }, + { "-bgfxProbeTransposeInstances", parseBgfxProbeTransposeInstances }, + { "-bgfxInstancingNoReorder", parseBgfxInstancingNoReorder }, + { "-bgfxDisableInstancing", parseBgfxDisableInstancing }, + { "-bgfxDisableSortedMaterialRecaptureSkip", parseBgfxDisableSortedMaterialRecaptureSkip }, + { "-bgfxDisableSortedMaterialSnapshot", parseBgfxDisableSortedMaterialSnapshot }, + { "-bgfxSortedTextureArray", parseBgfxSortedTextureArray }, + { "-bgfxNoSortedTextureArray", parseBgfxNoSortedTextureArray }, + { "-bgfxFrameTimingAfter", parseBgfxFrameTimingAfter }, + { "-bgfxFrameTimingInterval", parseBgfxFrameTimingInterval }, + { "-bgfxFrameTimingPath", parseBgfxFrameTimingPath }, + { "-bgfxSortedPacketCollectorDiag", parseBgfxSortedPacketCollectorDiag }, + { "-msaa", parseMsaa }, + { "-srgb", parseSrgb }, + { "-logFrameTimes", parseLogFrameTimes }, + { "-logBgfxStats", parseLogBgfxStats }, + { "-bgfxNoSceneFramebuffer", parseBgfxNoSceneFramebuffer }, + { "-bgfxNoPostFx", parseBgfxNoPostFx }, + { "-bgfxScreenshotAfter", parseBgfxScreenshotAfter }, // TheSuperHackers @feature xezon 03/08/2025 Force full viewport for 'Control Bar Pro' Addons like GenTool did it. { "-forcefullviewport", parseFullViewport }, @@ -1278,7 +1721,6 @@ static CommandLineParam paramsForEngineInit[] = { "-constantDebug", parseConstantDebug }, { "-seed", parseSeed }, { "-noagpfix", parseIncrAGPBuf }, - { "-noFPSLimit", parseNoFPSLimit }, { "-dumpAssetUsage", parseDumpAssetUsage }, { "-jumpToFrame", parseJumpToFrame }, { "-updateImages", parseUpdateImages }, diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index 32b93d3dba7..3706900bf92 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -107,6 +107,8 @@ #include "GameNetwork/GameSpy/GameResultsThread.h" #include "Common/version.h" +#include +#include "GgcRuntimeFlags.h" //------------------------------------------------------------------------------------------------- @@ -169,7 +171,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 +256,9 @@ GameEngine::GameEngine() m_quitting = FALSE; m_isActive = FALSE; +#ifdef _WIN32 _Module.Init(nullptr, ApplicationHInstance, nullptr); +#endif } //------------------------------------------------------------------------------------------------- @@ -299,7 +306,9 @@ GameEngine::~GameEngine() Drawable::killStaticImages(); +#ifdef _WIN32 _Module.Term(); +#endif #ifdef PERF_TIMERS PerfGather::termPerfDump(); @@ -469,6 +478,15 @@ void GameEngine::init() ini.loadFileDirectory( "Data\\INI\\GameDataDebug", INI_LOAD_OVERWRITE, nullptr ); #endif + // TheSuperHackers @feature bobtista 14/07/2026 Load optional bgfx render settings from a + // separate file so they never require a modified GameData.ini. Excluded from the INI CRC + // because the settings are client render only. + if (TheFileSystem->doesFileExist("Data\\INI\\Bgfx.ini")) + { + ini.load("Data\\INI\\Bgfx.ini", INI_LOAD_OVERWRITE, nullptr); + DEBUG_LOG(("GameEngine::init() - loaded Data\\INI\\Bgfx.ini")); + } + // special-case: parse command-line parameters after loading global data CommandLine::parseCommandLineForEngineInit(); @@ -732,6 +750,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) { @@ -741,7 +770,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; + } } } @@ -898,6 +931,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 @@ -909,6 +943,30 @@ void GameEngine::update() TheGameClient->UPDATE(); TheMessageStream->propagateMessages(); + // TheSuperHackers @bugfix bobtista 30/04/2026 Defer visual + // command-line replay loading until after the no-logo shell startup + // runs. The replay menu starts playback from live shell UI state, + // and direct loading before that leaves replay/control-bar windows + // in a different state. + if (TheGlobalData->m_loadReplayGame.isEmpty() == FALSE) + { + AsciiString replayGame = TheGlobalData->m_loadReplayGame; + TheWritableGlobalData->m_loadReplayGame.clear(); + + if (TheRecorder->playbackFile(replayGame)) + { + if (TheShell) + { + TheShell->hideShell(); + } + } + else + { + DEBUG_LOG(("Failed to load replay '%s'", replayGame.str())); + m_quitting = TRUE; + } + } + if (TheNetwork != nullptr) { TheNetwork->UPDATE(); @@ -918,6 +976,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()) @@ -925,6 +984,26 @@ void GameEngine::update() TheGameClient->step(); } } + + // TheSuperHackers @feature bobtista 03/06/2026 Render benchmark freeze. + // GGC_FREEZE_LOGIC_AFTER=N freezes the simulation once logic reaches frame + // N (rendering continues). The scene then becomes byte-identical across + // runs, eliminating the save-forward divergence that otherwise makes + // sub-fps render-backend comparisons unmeasurable. Freeze early (small N) + // for the most reproducible scene. + static int s_freezeAfter = -2; + if (s_freezeAfter == -2) + { + s_freezeAfter = GgcFlags::IntValue(GgcFlag_FreezeLogicAfter); + } + if (s_freezeAfter > 0 + && TheGameLogic != nullptr + && TheGameLogic->getFrame() >= (UnsignedInt)s_freezeAfter + && TheFramePacer != nullptr + && !TheFramePacer->isTimeFrozen()) + { + TheFramePacer->setTimeFrozen(TRUE); + } } } @@ -941,6 +1020,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..3a2c9baaf9e 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -59,11 +59,13 @@ #include "Common/version.h" #include "GameLogic/AI.h" +#include "GameLogic/GameLogic.h" #include "GameLogic/Weapon.h" #include "GameLogic/Module/BodyModule.h" #include "GameClient/Color.h" #include "GameClient/Display.h" +#include "GameClient/Mouse.h" #include "GameClient/TerrainVisual.h" #include "GameNetwork/FirewallHelper.h" @@ -74,6 +76,446 @@ GlobalData* TheWritableGlobalData = nullptr; ///< The global data singleton //------------------------------------------------------------------------------------------------- GlobalData* GlobalData::m_theOriginal = nullptr; +extern "C" void GGC_GetBgfxPostProcessParams(float * params) +{ + if (!params) + { + return; + } + + params[0] = 0.08f; + params[1] = 1.015f; + params[2] = 1.01f; + params[3] = 0.35f; + if (!TheGlobalData) + { + return; + } + + if (!TheGlobalData->m_bgfxPostProcessing || TheGlobalData->m_bgfxNoPostFx) + { + params[0] = 0.0f; + params[1] = 1.0f; + params[2] = 1.0f; + params[3] = 0.0f; + } + else + { + params[0] = TheGlobalData->m_bgfxPostSharpenAmount; + params[1] = TheGlobalData->m_bgfxPostSaturation; + params[2] = TheGlobalData->m_bgfxPostContrast; + params[3] = TheGlobalData->m_bgfxPostFxaaAmount; + } +} + +// TheSuperHackers @feature bobtista 15/06/2026 Expose the split-screen wipe state +// to the bgfx composite pass. params: x = split position 0..1, y = enabled. +extern "C" void GGC_GetBgfxWipeParams(float * params) +{ + if (!params) + { + return; + } + + params[0] = 0.5f; + params[1] = 0.0f; + params[2] = 0.0f; + params[3] = 0.0f; + if (!TheGlobalData || !TheGlobalData->m_bgfxWipeEnabled) + { + return; + } + + Real split = TheGlobalData->m_bgfxWipeSplit; + if (TheGlobalData->m_bgfxWipeFollowMouse && TheMouse && TheDisplay) + { + const Int width = (Int)TheDisplay->getWidth(); + const MouseIO *status = TheMouse->getMouseStatus(); + if (width > 0 && status) + { + split = (Real)status->pos.x / (Real)width; + } + } + + if (split < 0.0f) + { + split = 0.0f; + } + if (split > 1.0f) + { + split = 1.0f; + } + + params[0] = split; + params[1] = 1.0f; +} + +// TheSuperHackers @feature bobtista 15/06/2026 Expose color-grade controls to the +// bgfx composite pass. params: x = enabled, y = strength, z = temperature, w = tint. +extern "C" void GGC_GetBgfxColorGradeParams(float * params) +{ + if (!params) + { + return; + } + + params[0] = 0.0f; + params[1] = 1.0f; + params[2] = 0.0f; + params[3] = 0.0f; + if (!TheGlobalData || !TheGlobalData->m_bgfxColorGrade) + { + return; + } + + params[0] = 1.0f; + params[1] = TheGlobalData->m_bgfxColorGradeStrength; + params[2] = TheGlobalData->m_bgfxColorGradeTemperature; + params[3] = TheGlobalData->m_bgfxColorGradeTint; +} + +// TheSuperHackers @feature bobtista 15/06/2026 Expose bloom controls to the bgfx +// composite/bloom passes. params: x = enabled, y = threshold, z = intensity. +extern "C" void GGC_GetBgfxBloomParams(float * params) +{ + if (!params) + { + return; + } + + params[0] = 0.0f; + params[1] = 0.75f; + params[2] = 0.0f; + params[3] = 0.0f; + if (!TheGlobalData || !TheGlobalData->m_bgfxBloom) + { + return; + } + + params[0] = 1.0f; + params[1] = TheGlobalData->m_bgfxBloomThreshold; + params[2] = TheGlobalData->m_bgfxBloomIntensity; +} + +// TheSuperHackers @feature bobtista 15/06/2026 Opt-in HDR scene color. When set, +// the bgfx scene/bloom targets use RGBA16F and the composite applies ACES +// tonemapping, giving richer bloom and highlight rolloff. Default off. +extern "C" int GGC_GetBgfxHdrEnabled() +{ + if (!TheGlobalData || !TheGlobalData->m_bgfxHdr) + { + return 0; + } + return 1; +} + +// TheSuperHackers @feature bobtista 15/06/2026 Cheap fullscreen post effects. +// params: x = vignette strength, y = chromatic aberration amount, z = film grain +// strength. Zero means off. +extern "C" void GGC_GetBgfxPostFx2Params(float * params) +{ + if (!params) + { + return; + } + + params[0] = 0.0f; + params[1] = 0.0f; + params[2] = 0.0f; + params[3] = 0.0f; + if (!TheGlobalData) + { + return; + } + + if (TheGlobalData->m_bgfxVignette) + { + params[0] = TheGlobalData->m_bgfxVignetteStrength; + } + if (TheGlobalData->m_bgfxChromaticAberration) + { + params[1] = TheGlobalData->m_bgfxChromaticAberrationAmount; + } + if (TheGlobalData->m_bgfxFilmGrain) + { + params[2] = TheGlobalData->m_bgfxFilmGrainStrength; + } +} + +// TheSuperHackers @feature bobtista 15/06/2026 MSAA sample count for the offscreen +// scene framebuffer (0/2/4/8). Drives true multisampling of the 3D scene, distinct +// from the backbuffer reset MSAA. +extern "C" int GGC_GetBgfxMsaaSamples() +{ + if (!TheGlobalData) + { + return 0; + } + return TheGlobalData->m_bgfxMsaa; +} + +// TheSuperHackers @feature bobtista 15/06/2026 Internal render-scale (supersampling) +// for the 3D scene. 1.0 = native; up to 2.0 renders the scene at a multiple and the +// composite downsamples to native, antialiasing geometry and shading alike. +extern "C" float GGC_GetBgfxRenderScale() +{ + if (!TheGlobalData) + { + return 1.0f; + } + return TheGlobalData->m_bgfxRenderScale; +} + +// TheSuperHackers @feature bobtista 15/06/2026 Screen-space ambient occlusion. +// params: x = enabled, y = radius (world units), z = intensity. +extern "C" void GGC_GetBgfxSSAOParams(float * params) +{ + if (!params) + { + return; + } + + params[0] = 0.0f; + params[1] = 1.0f; + params[2] = 1.0f; + params[3] = 0.0f; + if (!TheGlobalData || !TheGlobalData->m_bgfxSSAO) + { + return; + } + + params[0] = 1.0f; + params[1] = TheGlobalData->m_bgfxSSAORadius; + params[2] = TheGlobalData->m_bgfxSSAOIntensity; +} + +// TheSuperHackers @feature bobtista 15/06/2026 Uber-shader material lighting effects. +// These read existing W3D material data (specular color/shininess) and the camera eye +// vector; no new art assets are required. +// params: x = specular strength (0 = off), y = rim-light strength (0 = off), +// z = rim-light power, w = emissive boost multiplier (1 = neutral). +extern "C" void GGC_GetBgfxMaterialFxParams(float * params) +{ + if (!params) + { + return; + } + + params[0] = 0.0f; + params[1] = 0.0f; + params[2] = 3.0f; + params[3] = 1.0f; + if (!TheGlobalData) + { + return; + } + + if (TheGlobalData->m_bgfxSpecular) + { + params[0] = TheGlobalData->m_bgfxSpecularStrength; + } + if (TheGlobalData->m_bgfxRimLight) + { + params[1] = TheGlobalData->m_bgfxRimStrength; + params[2] = TheGlobalData->m_bgfxRimPower; + } + if (TheGlobalData->m_bgfxEmissiveBoost) + { + params[3] = TheGlobalData->m_bgfxEmissiveBoostScale; + } +} + +// TheSuperHackers @feature bobtista 15/06/2026 Sun shadow map. Render-only directional +// shadows from the sun's POV as a modern alternative to stencil volumes / blob decals. +extern "C" int GGC_GetBgfxShadowMapEnabled() +{ + if (!TheGlobalData || !TheGlobalData->m_bgfxShadowMaps) + { + return 0; + } + return 1; +} + +// TheSuperHackers @feature bobtista 23/06/2026 Perspective point-light shadow map toggle. +// Default FALSE; enable via INI BgfxDynamicLightShadows=Yes or the Ctrl+Alt dev hotkey. +extern "C" int GGC_GetBgfxDynamicLightShadowsEnabled() +{ + if (!TheGlobalData || !TheGlobalData->m_bgfxDynamicLightShadows) + { + return 0; + } + return 1; +} + +// TheSuperHackers @feature bobtista 16/07/2026 INI toggle for the experimental dramatic Particle +// Cannon lighting. Data/INI/Bgfx.ini "PCannonEnhanced = Yes" enables it without the env flag; the +// GGC_PCANNON_ENHANCED env flag still overrides (checked alongside this in the read sites). +extern "C" int GGC_GetPCannonEnhancedEnabled() +{ + return (TheGlobalData && TheGlobalData->m_pcannonEnhanced) ? 1 : 0; +} + +// TheSuperHackers @tweak bobtista 18/07/2026 Scene ambient floor at the beam for the enhanced +// Particle Cannon dim, read by the bgfx backend. Lower = darker/more impact. +extern "C" float GGC_GetPCannonDimTarget() +{ + return (TheGlobalData) ? (float)TheGlobalData->m_pcannonDimTarget : 0.72f; +} + +// params: x = depth bias, y = shadow strength (0 = none, 1 = full). +extern "C" void GGC_GetBgfxShadowMapParams(float * params) +{ + if (!params) + { + return; + } + + params[0] = 0.0015f; + params[1] = 0.7f; + params[2] = 0.0f; + params[3] = 0.0f; + if (!TheGlobalData) + { + return; + } + + params[0] = TheGlobalData->m_bgfxShadowMapBias; + params[1] = TheGlobalData->m_bgfxShadowMapStrength; +} + +// TheSuperHackers @feature bobtista 16/06/2026 Debug: force nearest/point texture +// filtering (the old blocky look) instead of the smooth linear/trilinear baseline. +extern "C" int GGC_GetBgfxPointFilter() +{ + if (!TheGlobalData || !TheGlobalData->m_bgfxPointFilter) + { + return 0; + } + return 1; +} + +extern "C" const char * GGC_GetBgfxRenderer() +{ + if (!TheGlobalData) + { + return ""; + } + return TheGlobalData->m_bgfxRenderer.str(); +} + +extern "C" int GGC_GetBgfxSrgb() +{ + if (!TheGlobalData || !TheGlobalData->m_bgfxSrgb) + { + return 0; + } + return 1; +} + +extern "C" int GGC_GetBgfxShadowFullPcf() +{ + if (!TheGlobalData || !TheGlobalData->m_bgfxShadowFullPcf) + { + return 0; + } + return 1; +} + +extern "C" int GGC_GetBgfxStencilShadowsEnabled() +{ + if (!TheGlobalData) + { + return 1; + } + // TheSuperHackers @feature bobtista 15/07/2026 The sun shadow map replaces the legacy + // stencil volumes; running both doubles every unit shadow. GGC_ENABLE_LEGACY_STENCIL_SHADOWS + // still forces the legacy path back on in the backend for A/B comparison. + if (TheGlobalData->m_bgfxShadowMaps) + { + return 0; + } + return TheGlobalData->m_bgfxStencilShadows ? 1 : 0; +} + +extern "C" void GGC_GetBgfxDiagnosticFlags(int * logStats, int * noSceneFramebuffer, int * noPostFx) +{ + if (logStats) + { + *logStats = 0; + } + if (noSceneFramebuffer) + { + *noSceneFramebuffer = 0; + } + if (noPostFx) + { + *noPostFx = 0; + } + if (!TheGlobalData) + { + return; + } + + if (logStats) + { + *logStats = TheGlobalData->m_bgfxLogStats ? 1 : 0; + } + if (noSceneFramebuffer) + { + *noSceneFramebuffer = TheGlobalData->m_bgfxNoSceneFramebuffer ? 1 : 0; + } + if (noPostFx) + { + *noPostFx = TheGlobalData->m_bgfxNoPostFx ? 1 : 0; + } +} + +extern "C" int GGC_GetBgfxScreenshotFrame() +{ + return TheGlobalData ? TheGlobalData->m_bgfxScreenshotAfter : 0; +} + +extern "C" const char * GGC_GetBgfxScreenshotPath() +{ + return TheGlobalData ? TheGlobalData->m_bgfxScreenshotPath.str() : ""; +} + +// TheSuperHackers @feature bobtista 03/06/2026 Expose the simulation frame so the +// render backend can trigger a screenshot at a deterministic logic frame (identical +// scene state across runs/backends) rather than a render frame (render rate varies). +extern "C" int GGC_GetCurrentLogicFrame() +{ + return TheGameLogic ? (int)TheGameLogic->getFrame() : 0; +} + +extern "C" void GGC_ClearBgfxScreenshotRequest() +{ + if (TheWritableGlobalData) + { + TheWritableGlobalData->m_bgfxScreenshotAfter = 0; + } +} + +extern "C" void GGC_GetBgfxSoftParticleParams(float * params) +{ + if (!params) + { + return; + } + + params[0] = 1.0f; + params[1] = 80.0f; + params[2] = 0.0f; + params[3] = 0.0f; + if (!TheGlobalData) + { + return; + } + + params[0] = TheGlobalData->m_bgfxSoftParticles ? 1.0f : 0.0f; + params[1] = TheGlobalData->m_bgfxSoftParticleFadeScale; +} + /////////////////////////////////////////////////////////////////////////////////////////////////// // PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -110,6 +552,63 @@ GlobalData* GlobalData::m_theOriginal = nullptr; { "DownwindAngle", INI::parseReal, nullptr, offsetof( GlobalData, m_downwindAngle ) }, { "UseShadowVolumes", INI::parseBool, nullptr, offsetof( GlobalData, m_useShadowVolumes ) }, { "UseShadowDecals", INI::parseBool, nullptr, offsetof( GlobalData, m_useShadowDecals ) }, + // TheSuperHackers @feature bobtista 27/04/2026 bgfx scene-composite + // post-process controls. Values are intentionally subtle by default + // so Zero Hour keeps its original visual identity. + { "BgfxPostProcessing", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxPostProcessing ) }, + { "BgfxPostSharpenAmount", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxPostSharpenAmount ) }, + { "BgfxPostSaturation", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxPostSaturation ) }, + { "BgfxPostContrast", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxPostContrast ) }, + { "BgfxPostFxaaAmount", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxPostFxaaAmount ) }, + { "BgfxWipeEnabled", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxWipeEnabled ) }, + { "BgfxWipeFollowMouse", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxWipeFollowMouse ) }, + { "BgfxWipeSplit", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxWipeSplit ) }, + { "BgfxColorGrade", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxColorGrade ) }, + { "BgfxColorGradeStrength", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxColorGradeStrength ) }, + { "BgfxColorGradeTemperature", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxColorGradeTemperature ) }, + { "BgfxColorGradeTint", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxColorGradeTint ) }, + { "BgfxBloom", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxBloom ) }, + { "BgfxBloomThreshold", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxBloomThreshold ) }, + { "BgfxBloomIntensity", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxBloomIntensity ) }, + { "BgfxHdr", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxHdr ) }, + { "BgfxVignette", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxVignette ) }, + { "BgfxVignetteStrength", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxVignetteStrength ) }, + { "BgfxChromaticAberration", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxChromaticAberration ) }, + { "BgfxChromaticAberrationAmount", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxChromaticAberrationAmount ) }, + { "BgfxFilmGrain", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxFilmGrain ) }, + { "BgfxFilmGrainStrength", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxFilmGrainStrength ) }, + { "BgfxSSAO", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxSSAO ) }, + { "BgfxSSAORadius", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxSSAORadius ) }, + { "BgfxSSAOIntensity", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxSSAOIntensity ) }, + { "BgfxRenderScale", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxRenderScale ) }, + { "BgfxSpecular", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxSpecular ) }, + { "BgfxSpecularStrength", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxSpecularStrength ) }, + { "BgfxRimLight", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxRimLight ) }, + { "BgfxRimStrength", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxRimStrength ) }, + { "BgfxRimPower", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxRimPower ) }, + { "BgfxEmissiveBoost", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxEmissiveBoost ) }, + { "BgfxEmissiveBoostScale", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxEmissiveBoostScale ) }, + { "BgfxShadowMaps", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxShadowMaps ) }, + { "BgfxShadowMapBias", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxShadowMapBias ) }, + { "BgfxShadowMapStrength", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxShadowMapStrength ) }, + { "BgfxShadowFullPcf", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxShadowFullPcf ) }, + { "BgfxStencilShadows", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxStencilShadows ) }, + { "BgfxDynamicLightShadows", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxDynamicLightShadows ) }, + { "PCannonEnhanced", INI::parseBool, nullptr, offsetof( GlobalData, m_pcannonEnhanced ) }, + { "PCannonFlashRadius", INI::parseReal, nullptr, offsetof( GlobalData, m_pcannonFlashRadius ) }, + { "PCannonFlashInterval", INI::parseInt, nullptr, offsetof( GlobalData, m_pcannonFlashInterval ) }, + { "PCannonFlashFadeIn", INI::parseInt, nullptr, offsetof( GlobalData, m_pcannonFlashFadeIn ) }, + { "PCannonFlashFadeOut", INI::parseInt, nullptr, offsetof( GlobalData, m_pcannonFlashFadeOut ) }, + { "PCannonDimTarget", INI::parseReal, nullptr, offsetof( GlobalData, m_pcannonDimTarget ) }, + { "BgfxPointFilter", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxPointFilter ) }, + { "BgfxSoftParticles", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxSoftParticles ) }, + { "BgfxSoftParticleFadeScale", INI::parseReal, nullptr, offsetof( GlobalData, m_bgfxSoftParticleFadeScale ) }, + { "BgfxLogStats", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxLogStats ) }, + { "BgfxNoSceneFramebuffer", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxNoSceneFramebuffer ) }, + { "BgfxNoPostFx", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxNoPostFx ) }, + { "BgfxMSAA", INI::parseInt, nullptr, offsetof( GlobalData, m_bgfxMsaa ) }, + { "BgfxSrgb", INI::parseBool, nullptr, offsetof( GlobalData, m_bgfxSrgb ) }, + { "BgfxRenderer", INI::parseAsciiString, nullptr, offsetof( GlobalData, m_bgfxRenderer ) }, { "TextureReductionFactor", INI::parseInt, nullptr, offsetof( GlobalData, m_textureReductionFactor ) }, { "UseBehindBuildingMarker", INI::parseBool, nullptr, offsetof( GlobalData, m_enableBehindBuildingMarkers ) }, { "WaterPositionX", INI::parseReal, nullptr, offsetof( GlobalData, m_waterPositionX ) }, @@ -666,6 +1165,67 @@ GlobalData::GlobalData() m_downwindAngle = ( -0.785f );//Northeast! m_useShadowVolumes = FALSE; m_useShadowDecals = FALSE; + m_bgfxPostProcessing = TRUE; + m_bgfxPostSharpenAmount = 0.08f; + m_bgfxPostSaturation = 1.015f; + m_bgfxPostContrast = 1.01f; + m_bgfxPostFxaaAmount = 0.35f; + m_bgfxWipeEnabled = FALSE; + m_bgfxWipeFollowMouse = TRUE; + m_bgfxWipeSplit = 0.5f; + m_bgfxColorGrade = FALSE; + m_bgfxColorGradeStrength = 1.0f; + m_bgfxColorGradeTemperature = 0.0f; + m_bgfxColorGradeTint = 0.0f; + m_bgfxBloom = FALSE; + // TheSuperHackers @tweak bobtista 15/06/2026 A threshold around 0.7 is the + // practical middle ground on this game's display-referred art: bright effects + // and surfaces bloom while most terrain stays clean. Best paired with BgfxHdr. + m_bgfxBloomThreshold = 0.7f; + m_bgfxBloomIntensity = 0.5f; + m_bgfxHdr = FALSE; + m_bgfxVignette = FALSE; + m_bgfxVignetteStrength = 0.4f; + m_bgfxChromaticAberration = FALSE; + m_bgfxChromaticAberrationAmount = 0.5f; + m_bgfxFilmGrain = FALSE; + m_bgfxFilmGrainStrength = 0.08f; + m_bgfxSSAO = FALSE; + // Radius is in world units; Generals' camera is far, so contact AO needs a + // fairly large radius to read at all. + m_bgfxSSAORadius = 12.0f; + m_bgfxSSAOIntensity = 1.0f; + m_bgfxRenderScale = 1.0f; + m_bgfxSpecular = FALSE; + m_bgfxSpecularStrength = 3.0f; + m_bgfxRimLight = FALSE; + m_bgfxRimStrength = 0.3f; + m_bgfxRimPower = 3.0f; + m_bgfxEmissiveBoost = FALSE; + m_bgfxEmissiveBoostScale = 2.0f; + m_bgfxShadowMaps = FALSE; + m_bgfxShadowMapBias = 0.0006f; + m_bgfxShadowMapStrength = 0.35f; + m_bgfxDynamicLightShadows = FALSE; + m_pcannonEnhanced = FALSE; + m_pcannonFlashRadius = 55.0f; + m_pcannonFlashInterval = 7; + m_pcannonFlashFadeIn = 4; + m_pcannonFlashFadeOut = 14; + m_pcannonDimTarget = 0.72f; + m_bgfxPointFilter = FALSE; + m_bgfxSoftParticles = FALSE; + m_bgfxSoftParticleFadeScale = 80.0f; + m_bgfxLogStats = FALSE; + m_bgfxNoSceneFramebuffer = FALSE; + m_bgfxNoPostFx = FALSE; + m_bgfxMsaa = 4; + m_bgfxSrgb = FALSE; + m_bgfxRenderer = ""; + m_bgfxShadowFullPcf = FALSE; + m_bgfxStencilShadows = TRUE; + m_bgfxScreenshotAfter = 0; + m_bgfxScreenshotPath = ""; m_textureReductionFactor = -1; m_enableBehindBuildingMarkers = TRUE; m_scriptDebug = FALSE; @@ -998,6 +1558,8 @@ GlobalData::GlobalData() m_buildMapCache = FALSE; m_initialFile.clear(); m_pendingFile.clear(); + m_loadSaveGame.clear(); + m_loadReplayGame.clear(); m_simulateReplays.clear(); m_simulateReplayJobs = SIMULATE_REPLAYS_SEQUENTIAL; @@ -1119,7 +1681,15 @@ GlobalData *GlobalData::newOverride() // copy the data from the latest override (TheWritableGlobalData) to the newly created instance DEBUG_ASSERTCRASH( TheWritableGlobalData, ("GlobalData::newOverride() - no existing data") ); + // TheSuperHackers @bugfix bobtista 09/07/2026 The default copy also copied m_weaponBonusSet, + // making the override share the set owned by the copied instance and leaking its own. The + // destructor then freed the shared set when the override was deleted on reset, leaving the + // original with a dangling pointer and a double free at shutdown. Keep the override's own set + // and copy the contents instead. + WeaponBonusSet *ownWeaponBonusSet = overrideData->m_weaponBonusSet; *overrideData = *TheWritableGlobalData; + overrideData->m_weaponBonusSet = ownWeaponBonusSet; + *overrideData->m_weaponBonusSet = *TheWritableGlobalData->m_weaponBonusSet; // // link the override to the previously created one, the link order is important here diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp index 02cfac1be99..537a7d0afdd 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -1652,6 +1652,27 @@ void Player::onStructureConstructionComplete( Object *builder, Object *structure if( m_ai ) m_ai->onStructureProduced( builder, structure ); +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix bobtista 10/06/2026 Buildings constructed under OBJECT_STATUS_UNDER_CONSTRUCTION + // skip the SpecialPowerModule recharge and, without a SpecialPowerCreate module, never leave the + // uninitialized 0xFFFFFFFF ready frame (e.g. Strategy Center battle plans, Detention Camp CIA Intelligence). + // Make any such special power ready now on completion to match retail availability. + for( BehaviorModule** module = structure->getBehaviorModules(); *module; ++module ) + { + SpecialPowerModuleInterface* specialPower = (*module)->getSpecialPower(); + if( specialPower == nullptr ) + { + continue; + } + if( specialPower->getReadyFrame() == 0xFFFFFFFF ) + { + specialPower->setReadyFrame( TheGameLogic->getFrame() ); + DEBUG_LOG(( "onStructureConstructionComplete: charged uninitialized special power '%s' on '%s' to ready.", + specialPower->getPowerName().str(), structure->getTemplate()->getName().str() )); + } + } +#endif + // the GUI needs to re-evaluate the information being displayed to the user now if( TheControlBar ) TheControlBar->markUIDirty(); @@ -2469,7 +2490,7 @@ void Player::doBountyForKill(const Object* killer, const Object* victim) Int bounty = REAL_TO_INT_CEIL(costToBuild * m_cashBountyPercent); #else // TheSuperHackers @bugfix Stubbjax 20/02/2026 Subtract epsilon to ensure bounty is rounded up correctly. - Int bounty = ceil((costToBuild * m_cashBountyPercent) - WWMATH_EPSILON); + Int bounty = WWMath::Ceil((costToBuild * m_cashBountyPercent) - WWMATH_EPSILON); #endif if( bounty ) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp index 03aaead2883..335a504c7e5 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp @@ -2474,17 +2474,50 @@ void Team::killTeam() // ------------------------------------------------------------------------ Bool Team::damageTeamMembers(Real amount) { + // TheSuperHackers @bugfix bobtista 09/07/2026 Damage the members from a snapshot of the member list, + // like killTeam does. Killing a garrisoned civilian building empties it, which moves the building + // back to its original team while we are walking this team's member list, so the DLINK iterator + // walks into the other team's list and kills unrelated objects. See the comment in deleteTeam. + std::list objectsToProcess; for (DLINK_ITERATOR iter = iterate_TeamMemberList(); !iter.done(); iter.advance()) { if (iter.cur()->isEffectivelyDead()) + { continue; + } if (iter.cur()->isDestroyed()) + { continue; + } + + objectsToProcess.push_back(iter.cur()); + } + + std::list::iterator objIt; + for (objIt = objectsToProcess.begin(); objIt != objectsToProcess.end(); ++objIt) + { + Object *obj = *objIt; + + if (obj->isEffectivelyDead()) + { + continue; + } + + if (obj->isDestroyed()) + { + continue; + } + + // the object's team could change while damaging other members. + if (obj->getTeam() != this) + { + continue; + } // do max amount of damage to object if (amount < 0.0) { - iter.cur()->kill(); + obj->kill(); } else { DamageInfo damageInfo; @@ -2492,7 +2525,7 @@ Bool Team::damageTeamMembers(Real amount) damageInfo.in.m_deathType = DEATH_NORMAL; damageInfo.in.m_sourceID = INVALID_ID; damageInfo.in.m_amount = amount; - iter.cur()->attemptDamage( &damageInfo ); + obj->attemptDamage( &damageInfo ); } } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp index 2d9bfea3a16..20df0483b81 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp @@ -428,6 +428,15 @@ void RecorderClass::updatePlayback() { // This is reached if there are no more commands to be executed. return; } + + // TheSuperHackers @bugfix bobtista 13/07/2026 Do not inject recorded commands before the replay's + // game has begun. The logic frame counter is also 0 in the shell and while the game loads, so frame 0 + // commands would otherwise dispatch in an earlier command batch than in the original game. That + // creates the dispatcher's transient AI groups in a different order, which desyncs the playback. + if (!m_doingAnalysis && (!TheGameLogic->isInGame() || TheGameLogic->isInShellGame())) { + return; + } + UnsignedInt curFrame = TheGameLogic->getFrame(); if (m_doingAnalysis) curFrame = m_nextFrame; @@ -1004,6 +1013,17 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f // playbackCRC, newCRC, TheGameLogic->getFrame()-m_crcInfo.GetQueueSize()-1, playerIndex)); if (TheGameLogic->getFrame() > 0 && newCRC != playbackCRC && !m_crcInfo.sawCRCMismatch()) { + if (TheDebugIgnoreSyncErrors) + { + static Bool loggedReplayCRCIgnore = FALSE; + if (!loggedReplayCRCIgnore) + { + DEBUG_LOG(("RecorderClass::handleCRCMessage() - ignoring replay CRC mismatch due to -ignoresync.")); + loggedReplayCRCIgnore = TRUE; + } + return; + } + //Kris: Patch 1.01 November 10, 2003 (integrated changes from Matt Campbell) // Since we don't seem to have any *visible* desyncs when replaying games, but get this warning // virtually every replay, the assumption is our CRC checking is faulty. Since we're at the @@ -1228,7 +1248,9 @@ UnicodeString RecorderClass::readUnicodeString() { } str[index] = c; - while (index < 1024 && str[index] != 0) { + // TheSuperHackers @bugfix bobtista 09/07/2026 Stop one short of the end of the buffer. Entering the + // loop body with index 1023 wrote str[1024], one element past the array. + while (index + 1 < 1024 && str[index] != 0) { ++index; Int c = m_file->readWideChar(); if (c == EOF) { @@ -1256,7 +1278,9 @@ AsciiString RecorderClass::readAsciiString() { } str[index] = c; - while (index < 1024 && str[index] != 0) { + // TheSuperHackers @bugfix bobtista 09/07/2026 Stop one short of the end of the buffer. Entering the + // loop body with index 1023 wrote str[1024], one element past the array. + while (index + 1 < 1024 && str[index] != 0) { ++index; Int c = m_file->readChar(); if (c == EOF) { @@ -1599,6 +1623,13 @@ AsciiString RecorderClass::getLastReplayFileName() AsciiString fullPlusNum; AsciiString mapName = game->getMap(); const char *fname = mapName.reverseFind('\\'); +#ifndef _WIN32 + const char *fwdSlash = mapName.reverseFind('/'); + if (fwdSlash && (!fname || fwdSlash > fname)) + { + fname = fwdSlash; + } +#endif if (fname) mapName = fname+1; for (Int i=0; ifindMap(TheGlobalData->m_mapName); AsciiString name = TheGlobalData->m_mapName; const char *fname = name.reverseFind('\\'); +#ifndef _WIN32 + const char *fwdSlash = name.reverseFind('/'); + if (fwdSlash && (!fname || fwdSlash > fname)) + { + fname = fwdSlash; + } +#endif if (fname) name = fname+1; name.truncateBy(4); // ".map" diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/BuildAssistant.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/BuildAssistant.cpp index 138d4e3430b..4cadf12855c 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/BuildAssistant.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/BuildAssistant.cpp @@ -806,8 +806,8 @@ LegalBuildCode BuildAssistant::isLocationClearOfObjects( const Coord3D *worldPos if (myFactoryExitWidth>0) { myExitPos = *worldPos; checkMyExit = true; - Real c = (Real)cos(angle); - Real s = (Real)sin(angle); + Real c = (Real)WWMath::Cos(angle); + Real s = (Real)WWMath::Sin(angle); Real offset = build->getTemplateGeometryInfo().getMajorRadius() + myFactoryExitWidth/2.0f; myExitPos.x += c*offset; myExitPos.y += s*offset; @@ -854,8 +854,8 @@ LegalBuildCode BuildAssistant::isLocationClearOfObjects( const Coord3D *worldPos if (themFactoryExitWidth>0) { hisExitPos = *them->getPosition(); checkHisExit = true; - Real c = (Real)cos(them->getOrientation()); - Real s = (Real)sin(them->getOrientation()); + Real c = (Real)WWMath::Cos(them->getOrientation()); + Real s = (Real)WWMath::Sin(them->getOrientation()); Real offset = them->getGeometryInfo().getMajorRadius() + themFactoryExitWidth/2.0f; hisExitPos.x += c*offset; hisExitPos.y += s*offset; @@ -1435,7 +1435,7 @@ Bool BuildAssistant::moveObjectsForConstruction( const ThingTemplate *whatToBuil Bool anyUnmovables = false; MemoryPoolObjectHolder hold( iter ); - Real radius = sqrt(pow(gi.getMajorRadius(), 2) + pow(gi.getMinorRadius(), 2)); + Real radius = WWMath::Sqrt(WWMath::Sqr(gi.getMajorRadius()) + WWMath::Sqr(gi.getMinorRadius())); radius *= 1.4f; // Fudge the distance, for( Object *them = iter->first(); them; them = iter->next() ) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/DataChunk.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/DataChunk.cpp index 0d5d2ce7fe6..caa8cd7042f 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/DataChunk.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/DataChunk.cpp @@ -357,7 +357,15 @@ void DataChunkOutput::writeUnicodeString( UnicodeString theString ) { UnsignedShort len = theString.getLength(); ::fwrite( (const char *)&len, sizeof(UnsignedShort) , 1, m_tmp_file ); +#ifdef _WIN32 ::fwrite( theString.str(), len*sizeof(WideChar) , 1, m_tmp_file ); +#else + UnsignedShort *diskBuffer = new UnsignedShort[len]; + for( Int i = 0; i < len; ++i ) + diskBuffer[i] = (UnsignedShort)theString.str()[i]; + ::fwrite( diskBuffer, len*sizeof(UnsignedShort) , 1, m_tmp_file ); + delete [] diskBuffer; +#endif } void DataChunkOutput::writeNameKey( const NameKeyType key ) @@ -966,12 +974,25 @@ UnicodeString DataChunkInput::readUnicodeString() DEBUG_ASSERTCRASH(m_chunkStack->dataLeft>=sizeof(UnsignedShort), ("Read past end of chunk.")); m_file->read( &len, sizeof(UnsignedShort) ); decrementDataLeft( sizeof(UnsignedShort) ); +#ifdef _WIN32 DEBUG_ASSERTCRASH(m_chunkStack->dataLeft>=len, ("Read past end of chunk.")); +#else + DEBUG_ASSERTCRASH(m_chunkStack->dataLeft>=len*sizeof(UnsignedShort), ("Read past end of chunk.")); +#endif UnicodeString theString; if (len>0) { WideChar *str = theString.getBufferForRead(len); +#ifdef _WIN32 m_file->read( (char*)str, len*sizeof(WideChar) ); decrementDataLeft( len*sizeof(WideChar) ); +#else + UnsignedShort *diskBuffer = new UnsignedShort[len]; + m_file->read( (char*)diskBuffer, len*sizeof(UnsignedShort) ); + decrementDataLeft( len*sizeof(UnsignedShort) ); + for( Int i = 0; i < len; ++i ) + str[i] = (WideChar)diskBuffer[i]; + delete [] diskBuffer; +#endif // add null delimiter to string. Note that getBufferForRead allocates space for terminating null. str[len] = '\000'; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp index 1f927ae2615..16f47f2e951 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp @@ -173,17 +173,17 @@ void GeometryInfo::calcPitches(const Coord3D& thisPos, const GeometryInfo& that, Coord3D thisCenter; getCenterPosition(thisPos, thisCenter); - Real dxy = sqrt(sqr(thatPos.x - thisCenter.x) + sqr(thatPos.y - thisCenter.y)); + Real dxy = WWMath::Sqrt(sqr(thatPos.x - thisCenter.x) + sqr(thatPos.y - thisCenter.y)); Real dz; /** @todo srj -- this could be better, by calcing it for all the corners, not just top-center and bottom-center... oh well */ dz = (thatPos.z + that.getMaxHeightAbovePosition()) - thisCenter.z; - maxPitch = atan2(dz, dxy); + maxPitch = WWMath::Atan2(dz, dxy); dz = (thatPos.z - that.getMaxHeightBelowPosition()) - thisCenter.z; - minPitch = atan2(dz, dxy); + minPitch = WWMath::Atan2(dz, dxy); } //============================================================================= @@ -279,8 +279,8 @@ void GeometryInfo::get2DBounds(const Coord3D& geomCenter, Real angle, Region2D& case GEOMETRY_BOX: { - Real c = (Real)cos(angle); - Real s = (Real)sin(angle); + Real c = (Real)WWMath::Cos(angle); + Real s = (Real)WWMath::Sin(angle); Real exc = m_majorRadius*c; Real eyc = m_minorRadius*c; Real exs = m_majorRadius*s; @@ -329,7 +329,7 @@ void GeometryInfo::clipPointToFootprint(const Coord3D& geomCenter, Coord3D& ptTo { Real dx = ptToClip.x - geomCenter.x; Real dy = ptToClip.y - geomCenter.y; - Real radius = sqrt(sqr(dx) + sqr(dy)); + Real radius = WWMath::Sqrt(sqr(dx) + sqr(dy)); if (radius > m_majorRadius) { Real ratio = m_majorRadius / radius; @@ -361,7 +361,7 @@ Bool GeometryInfo::isPointInFootprint(const Coord3D& geomCenter, const Coord3D& { Real dx = pt.x - geomCenter.x; Real dy = pt.y - geomCenter.y; - Real radius = sqrt(sqr(dx) + sqr(dy)); + Real radius = WWMath::Sqrt(sqr(dx) + sqr(dy)); return (radius <= m_majorRadius); break; } @@ -506,8 +506,8 @@ void GeometryInfo::calcBoundingStuff() case GEOMETRY_BOX: { - m_boundingCircleRadius = sqrt(sqr(m_majorRadius) + sqr(m_minorRadius)); - m_boundingSphereRadius = sqrt(sqr(m_majorRadius) + sqr(m_minorRadius) + sqr(m_height*0.5)); + m_boundingCircleRadius = WWMath::Sqrt(sqr(m_majorRadius) + sqr(m_minorRadius)); + m_boundingSphereRadius = WWMath::Sqrt(sqr(m_majorRadius) + sqr(m_minorRadius) + sqr(m_height*0.5)); break; } }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/QuotedPrintable.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/QuotedPrintable.cpp index 42a42148234..239e0126185 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/QuotedPrintable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/QuotedPrintable.cpp @@ -53,33 +53,31 @@ static int hexDigitToInt(char c) } // Convert unicode strings into ascii quoted-printable strings +// TheSuperHackers @bugfix bobtista 09/06/2026 Encode each WideChar as two +// little-endian bytes (the retail UTF-16 format) instead of walking the raw +// wchar_t buffer two bytes at a time. wchar_t is 4 bytes on macOS/Linux, so the +// old code saw the 0x0000 upper half of the first character as a string +// terminator and truncated every name to its first letter (e.g. "Macbook" -> "M_00"). AsciiString UnicodeStringToQuotedPrintable(UnicodeString original) { static char dest[1024]; - const unsigned char *src = reinterpret_cast(original.str()); + const WideChar *src = original.str(); int i=0; - while ( !(src[0]=='\0' && src[1]=='\0') && i<1021 ) + while ( *src != 0 && i < (int)sizeof(dest)-7 ) { - if (!isalnum(*src)) + const unsigned char bytes[2] = { (unsigned char)((*src)&0xff), (unsigned char)(((*src)>>8)&0xff) }; + for (int b=0; b<2; ++b) { - dest[i++] = MAGIC_CHAR; - dest[i++] = intToHexDigit((*src)>>4); - dest[i++] = intToHexDigit((*src)&0xf); - } - else - { - dest[i++] = *src; - } - src ++; - if (!isalnum(*src)) - { - dest[i++] = MAGIC_CHAR; - dest[i++] = intToHexDigit((*src)>>4); - dest[i++] = intToHexDigit((*src)&0xf); - } - else - { - dest[i++] = *src; + if (!isalnum(bytes[b])) + { + dest[i++] = MAGIC_CHAR; + dest[i++] = intToHexDigit(bytes[b]>>4); + dest[i++] = intToHexDigit(bytes[b]&0xf); + } + else + { + dest[i++] = bytes[b]; + } } src ++; } @@ -114,16 +112,22 @@ AsciiString AsciiStringToQuotedPrintable(AsciiString original) } // Convert ascii quoted-printable strings into unicode strings +// TheSuperHackers @bugfix bobtista 09/06/2026 Reassemble decoded bytes into +// little-endian 16-bit code units before storing them as WideChar. wchar_t is +// 4 bytes on macOS/Linux, so writing decoded bytes straight into the wchar_t +// buffer (as the old code did) produced garbage on those platforms. UnicodeString QuotedPrintableToUnicodeString(AsciiString original) { static WideChar dest[1024]; - int i=0; + int di=0; - unsigned char *c = reinterpret_cast(dest); const unsigned char *src = reinterpret_cast(original.str()); - while (*src && i<1023) + unsigned char lowByte=0; + Bool haveLow=FALSE; + while (*src && di<1023) { + unsigned char value; if (*src == MAGIC_CHAR) { if (src[1] == '\0') @@ -131,35 +135,38 @@ UnicodeString QuotedPrintableToUnicodeString(AsciiString original) // string ends with MAGIC_CHAR break; } - *c = hexDigitToInt(src[1]); + value = hexDigitToInt(src[1]); src++; if (src[1] != '\0') { - *c = *c<<4; - *c = *c | hexDigitToInt(src[1]); + value = (value<<4) | hexDigitToInt(src[1]); src++; } } else { - *c = *src; + value = *src; } src++; - c++; - } - // Fixup odd-length strings - if ((c-(unsigned char *)dest)%2) - { - // OK + if (!haveLow) + { + lowByte = value; + haveLow = TRUE; + } + else + { + dest[di++] = (WideChar)(lowByte | (value << 8)); + haveLow = FALSE; + } } - else + + if (haveLow && di<1023) { - *c = '\0'; - c++; + dest[di++] = (WideChar)lowByte; } - *c = 0; + dest[di] = 0; return dest; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp index 04cc701b5e1..5aae939b412 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameState.cpp @@ -36,6 +36,7 @@ #include "Common/GameStateMap.h" #include "Common/LatchRestore.h" #include "Common/MapObject.h" +#include "Common/Player.h" #include "Common/PlayerList.h" #include "Common/RandomValue.h" #include "Common/Radar.h" @@ -49,16 +50,23 @@ #include "GameClient/GameText.h" #include "GameClient/MapUtil.h" #include "GameClient/MessageBox.h" +#include "GameClient/Drawable.h" #include "GameClient/InGameUI.h" #include "GameClient/ParticleSys.h" #include "GameClient/TerrainVisual.h" #include "GameLogic/GameLogic.h" +#include "GameLogic/Object.h" +#include "GgcRuntimeFlags.h" #include "GameLogic/GhostObject.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/ScriptEngine.h" #include "GameLogic/SidesList.h" #include "GameLogic/TerrainLogic.h" +#ifndef _WIN32 +#include +#include +#endif // PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// GameState *TheGameState = nullptr; @@ -172,6 +180,36 @@ Bool SaveDate::isNewerThan( SaveDate *other ) } +#ifndef _WIN32 +static Bool isInvalidSaveDate( const SaveDate &date ) +{ + return date.year == 0 || date.month == 0 || date.day == 0; +} + +static void useFileModifiedTimeForSaveDate( AsciiString filename, SaveDate *date ) +{ + if( date == nullptr || isInvalidSaveDate( *date ) == FALSE ) + return; + + struct stat st; + if( stat( filename.str(), &st ) != 0 ) + return; + + struct tm local_tm; + if( localtime_r( &st.st_mtime, &local_tm ) == nullptr ) + return; + + date->year = local_tm.tm_year + 1900; + date->month = local_tm.tm_mon + 1; + date->dayOfWeek = local_tm.tm_wday; + date->day = local_tm.tm_mday; + date->hour = local_tm.tm_hour; + date->minute = local_tm.tm_min; + date->second = local_tm.tm_sec; + date->milliseconds = 0; +} +#endif + // ------------------------------------------------------------------------------------------------ /** Find a snapshot block info that matches the token passed in */ // ------------------------------------------------------------------------------------------------ @@ -666,6 +704,9 @@ SaveCode GameState::loadGame( AvailableGameInfo gameInfo ) // construct path to file AsciiString filepath = getFilePathInSaveDirectory(gameInfo.filename); +#ifndef _WIN32 + std::string filepathForError = filepath.str(); +#endif // open the save file XferLoad xferLoad; @@ -719,7 +760,11 @@ SaveCode GameState::loadGame( AvailableGameInfo gameInfo ) // print error message to the user UnicodeString ufilepath; +#ifdef _WIN32 ufilepath.translate(filepath); +#else + ufilepath.translate(filepathForError.c_str()); +#endif UnicodeString msg; msg.format( TheGameText->fetch("GUI:ErrorLoadingGame"), ufilepath.str() ); @@ -730,6 +775,46 @@ SaveCode GameState::loadGame( AvailableGameInfo gameInfo ) } + // TheSuperHackers @feature bobtista 11/07/2026 End-of-load visibility census + // for the intermittent drawable-less load (units simulate but never render; + // only ghost-visible objects draw). Pairs with the per-frame GGC_SCENE_DIAG + // counters: this line shows whether the local player's shroud state is + // already wrong when the load completes, or breaks afterwards. + if( GgcFlags::Enabled(GgcFlag_SceneDiag) ) + { + Int objectCount = 0; + Int shroudClear = 0; + Int shroudFogged = 0; + Int shroudInvisible = 0; + const Int localPlayerIndex = ThePlayerList->getLocalPlayer() + ? ThePlayerList->getLocalPlayer()->getPlayerIndex() + : -1; + for( Object *obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() ) + { + ++objectCount; + if( localPlayerIndex >= 0 ) + { + const ObjectShroudStatus ss = obj->getShroudedStatus( localPlayerIndex ); + if( ss <= OBJECTSHROUD_PARTIAL_CLEAR ) + ++shroudClear; + else if( ss == OBJECTSHROUD_FOGGED ) + ++shroudFogged; + else + ++shroudInvisible; + } + } + Int drawableCount = 0; + for( Drawable *draw = TheGameClient->firstDrawable(); draw; draw = draw->getNextDrawable() ) + { + ++drawableCount; + } + std::fprintf( stderr, + "[ggc] load census: objects=%d drawables=%d localPlayer=%d shroudClear=%d fogged=%d invisible=%d\n", + objectCount, drawableCount, localPlayerIndex, + shroudClear, shroudFogged, shroudInvisible ); + std::fflush( stderr ); + } + // // when loading a mission save, we want to do as much normal loading stuff as we // can cause we don't have any real save game data to load other than the @@ -761,7 +846,11 @@ SaveCode GameState::loadGame( AvailableGameInfo gameInfo ) AsciiString GameState::getSaveDirectory() const { AsciiString tmp = TheGlobalData->getPath_UserData(); +#ifdef _WIN32 tmp.concat("Save\\"); +#else + tmp.concat("Save/"); +#endif return tmp; } @@ -783,6 +872,13 @@ Bool GameState::isInSaveDirectory(const AsciiString& path) const AsciiString GameState::getMapLeafName(const AsciiString& in) const { const char* p = strrchr(in.str(), '\\'); +#ifndef _WIN32 + const char* fwd = strrchr(in.str(), '/'); + if (fwd && (!p || fwd > p)) + { + p = fwd; + } +#endif if (p) { // @@ -802,11 +898,15 @@ AsciiString GameState::getMapLeafName(const AsciiString& in) const } // ------------------------------------------------------------------------------------------------ -static const char* findLastBackslashInRangeInclusive(const char* start, const char* end) +static const char* findLastSeparatorInRangeInclusive(const char* start, const char* end) { while (end >= start) { +#ifdef _WIN32 if (*end == '\\') +#else + if (*end == '\\' || *end == '/') +#endif return end; --end; } @@ -818,10 +918,10 @@ static AsciiString getMapLeafAndDirName(const AsciiString& in) { const char* start = in.str(); const char* end = in.str() + in.getLength() - 1; - const char* p = findLastBackslashInRangeInclusive(start, end); + const char* p = findLastSeparatorInRangeInclusive(start, end); if (p) { - const char* p2 = findLastBackslashInRangeInclusive(start, p-1); + const char* p2 = findLastSeparatorInRangeInclusive(start, p-1); if (p2) { // we have something like: @@ -874,6 +974,16 @@ AsciiString GameState::realMapPathToPortableMapPath(const AsciiString& in) const // uncaught exceptions crash us. better to just use a bad path. prefix = in; } + // TheSuperHackers @bugfix bobtista 09/06/2026 Normalize separators to '\\' (the retail + // portable-path format). On macOS getMapLeafAndDirName returns '/'-separated names while + // PORTABLE_MAPS uses '\\', producing a mixed path like "maps\\alpine assault/..." that the + // game-options encoder (which walks the path on '\\') truncated to just "maps" - so the + // joiner could not resolve the host's map and the cross-platform map-name CRCs mismatched. + { + std::string normalized(prefix.str()); + std::replace(normalized.begin(), normalized.end(), '/', '\\'); + prefix.set(normalized.c_str()); + } prefix.toLower(); return prefix; } @@ -881,31 +991,53 @@ AsciiString GameState::realMapPathToPortableMapPath(const AsciiString& in) const // ------------------------------------------------------------------------------------------------ AsciiString GameState::portableMapPathToRealMapPath(const AsciiString& in) const { + // TheSuperHackers @bugfix bobtista 11/07/2026 Normalize the portable input + // to the portable '\\' separator form before parsing. Saves written before + // the 09/06/2026 portable-path normalization carry '/' or mixed separators; + // on Windows the '\\'-only separator scan then mis-splits a mixed name + // ("maps\\dir/leaf.map" keeps its "maps\\" prefix and double-prefixes the + // real path to "maps\\maps\\..."), and an all-'/' name fails the portable + // prefix match outright on both platforms. + AsciiString portableIn = in; + { + std::string normalized(portableIn.str()); + std::replace(normalized.begin(), normalized.end(), '/', '\\'); + portableIn.set(normalized.c_str()); + } + AsciiString prefix; // The directory where the real map path should be contained in. AsciiString containingBasePath; - if (in.startsWithNoCase(PORTABLE_SAVE)) + if (portableIn.startsWithNoCase(PORTABLE_SAVE)) { // the save dir ends with "\\" prefix = getSaveDirectory(); containingBasePath = prefix; - prefix.concat(getMapLeafName(in)); + prefix.concat(getMapLeafName(portableIn)); } - else if (in.startsWithNoCase(PORTABLE_MAPS)) + else if (portableIn.startsWithNoCase(PORTABLE_MAPS)) { // the map dir DOES NOT end with "\\", must add it prefix = TheMapCache->getMapDir(); +#ifdef _WIN32 prefix.concat("\\"); +#else + prefix.concat("/"); +#endif containingBasePath = prefix; - prefix.concat(getMapLeafAndDirName(in)); + prefix.concat(getMapLeafAndDirName(portableIn)); } - else if (in.startsWithNoCase(PORTABLE_USER_MAPS)) + else if (portableIn.startsWithNoCase(PORTABLE_USER_MAPS)) { // the map dir DOES NOT end with "\\", must add it prefix = TheMapCache->getUserMapDir(); +#ifdef _WIN32 prefix.concat("\\"); +#else + prefix.concat("/"); +#endif containingBasePath = prefix; - prefix.concat(getMapLeafAndDirName(in)); + prefix.concat(getMapLeafAndDirName(portableIn)); } else { @@ -914,6 +1046,14 @@ AsciiString GameState::portableMapPathToRealMapPath(const AsciiString& in) const return AsciiString::TheEmptyString; } +#ifndef _WIN32 + { + std::string normalized(prefix.str()); + std::replace(normalized.begin(), normalized.end(), '\\', '/'); + prefix.set(normalized.c_str()); + } +#endif + if (!FileSystem::isPathInDirectory(prefix, containingBasePath)) { DEBUG_LOG(("Normalized file path for '%s' was outside the expected base path of '%s'.", prefix.str(), containingBasePath.str())); @@ -1077,6 +1217,9 @@ static void addGameToAvailableList( AsciiString filename, void *userData ) // get header info from this listbox SaveGameInfo saveGameInfo; TheGameState->getSaveGameInfoFromFile( filename, &saveGameInfo ); +#ifndef _WIN32 + useFileModifiedTimeForSaveDate( filename, &saveGameInfo.date ); +#endif // allocate new info AvailableGameInfo *newInfo = new AvailableGameInfo; @@ -1202,7 +1345,7 @@ void GameState::populateSaveGameListbox( GameWindow *listbox, SaveLoadLayoutType displayLabel = TheGameText->fetch( saveGameInfo->mapLabel, &exists ); if( exists == FALSE ) - displayLabel.format( L"%S", saveGameInfo->mapLabel.str() ); + displayLabel.translate( saveGameInfo->mapLabel ); } @@ -1608,11 +1751,18 @@ void GameState::xfer( Xfer *xfer ) if (exists == FALSE || saveGameInfo->mapLabel == AsciiString::TheEmptyString) { const char* p = TheGlobalData->m_mapName.reverseFind('\\'); +#ifndef _WIN32 + const char* fwd = TheGlobalData->m_mapName.reverseFind('/'); + if (fwd && (!p || fwd > p)) + { + p = fwd; + } +#endif if (p == nullptr) saveGameInfo->mapLabel = TheGlobalData->m_mapName; else { - p++; // skip the '\' we're on + p++; // skip the separator we're on saveGameInfo->mapLabel.set(p); } } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp index f503e975f8c..20f9dd8da65 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp @@ -45,7 +45,6 @@ // GLOBALS //////////////////////////////////////////////////////////////////////////////////////// GameStateMap *TheGameStateMap = nullptr; - // METHODS //////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ @@ -131,7 +130,12 @@ static void embedPristineMap( AsciiString map, Xfer *xfer ) // ------------------------------------------------------------------------------------------------ static void embedInUseMap( AsciiString map, Xfer *xfer ) { +#ifdef _WIN32 FILE *fp = fopen( map.str(), "rb" ); +#else + const std::string normalizedPath = NormalizeWin32PathForHost( map.str() ); + FILE *fp = fopen( normalizedPath.c_str(), "rb" ); +#endif // sanity if( fp == nullptr ) @@ -191,7 +195,12 @@ static void extractAndSaveMap( AsciiString mapToSave, Xfer *xfer ) UnsignedInt dataSize; // open handle to output file +#ifdef _WIN32 FILE *fp = fopen( mapToSave.str(), "w+b" ); +#else + const std::string normalizedPath = NormalizeWin32PathForHost( mapToSave.str() ); + FILE *fp = fopen( normalizedPath.c_str(), "w+b" ); +#endif if( fp == nullptr ) { diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp index 08328666992..8fd2d606cfc 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/StackDump.cpp @@ -37,14 +37,44 @@ // Prototypes //***************************************************************************** BOOL InitSymbolInfo(); -void MakeStackTrace(DWORD myeip,DWORD myesp,DWORD myebp, int skipFrames, void (*callback)(const char*)); +void MakeStackTrace(CONTEXT& context, int skipFrames, void (*callback)(const char*)); void GetFunctionDetails(void *pointer, char*name, char*filename, unsigned int* linenumber, unsigned int* address); void WriteStackLine(void*address, void (*callback)(const char*)); -//***************************************************************************** -// Mis-named globals :-) -//***************************************************************************** -static CONTEXT gsContext; +// TheSuperHackers @feature bobtista 11/06/2026 Walk the stack with the 64-bit DbgHelp API +// (StackWalk64 + STACKFRAME64) instead of the legacy 32-bit STACKFRAME so addresses are not +// truncated on the x64 build. The image machine type selects the unwinder; on x86 this is +// fully equivalent to the old IMAGE_FILE_MACHINE_I386 walk. +static DWORD getStackWalkMachineType() +{ +#if defined(_M_X64) || defined(__x86_64__) + return IMAGE_FILE_MACHINE_AMD64; +#elif defined(_M_IX86) || defined(__i386__) + return IMAGE_FILE_MACHINE_I386; +#else + #error "Unsupported architecture for stack walking" +#endif +} + +// Seed a STACKFRAME64 with the program counter, stack and frame pointers from a captured CONTEXT. +static void initStackFrame64(STACKFRAME64& frame, const CONTEXT& context) +{ + memset(&frame, 0, sizeof(frame)); + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrStack.Mode = AddrModeFlat; + frame.AddrFrame.Mode = AddrModeFlat; +#if defined(_M_X64) || defined(__x86_64__) + frame.AddrPC.Offset = context.Rip; + frame.AddrStack.Offset = context.Rsp; + frame.AddrFrame.Offset = context.Rbp; +#elif defined(_M_IX86) || defined(__i386__) + frame.AddrPC.Offset = context.Eip; + frame.AddrStack.Offset = context.Esp; + frame.AddrFrame.Offset = context.Ebp; +#else + #error "Unsupported architecture for stack walking" +#endif +} //***************************************************************************** @@ -67,36 +97,10 @@ void StackDump(void (*callback)(const char*)) if (!InitSymbolInfo()) return; - DWORD myeip,myesp,myebp; + CONTEXT context; + RtlCaptureContext(&context); -#if defined(_MSC_VER) -_asm -{ -MYEIP1: - mov eax, MYEIP1 - mov dword ptr [myeip] , eax - mov eax, esp - mov dword ptr [myesp] , eax - mov eax, ebp - mov dword ptr [myebp] , eax -} -#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(_M_IX86)) - // GCC/Clang inline assembly for x86-32 - __asm__ __volatile__( - "call 1f\n\t" - "1: pop %0\n\t" - "mov %%esp, %1\n\t" - "mov %%ebp, %2" - : "=r"(myeip), "=r"(myesp), "=r"(myebp) - : - : "memory" - ); -#else - #error "Unsupported compiler or architecture for register capture" -#endif - - - MakeStackTrace(myeip,myesp,myebp, 2, callback); + MakeStackTrace(context, 2, callback); } @@ -112,7 +116,20 @@ void StackDumpFromContext(DWORD eip,DWORD esp,DWORD ebp, void (*callback)(const if (!InitSymbolInfo()) return; - MakeStackTrace(eip,esp,ebp, 0, callback); + CONTEXT context; + memset(&context, 0, sizeof(context)); + context.ContextFlags = CONTEXT_FULL; +#if defined(_M_X64) || defined(__x86_64__) + context.Rip = eip; + context.Rsp = esp; + context.Rbp = ebp; +#elif defined(_M_IX86) || defined(__i386__) + context.Eip = eip; + context.Esp = esp; + context.Ebp = ebp; +#endif + + MakeStackTrace(context, 0, callback); } @@ -155,7 +172,7 @@ BOOL InitSymbolInfo() { // regenerate the name of the app ::GetModuleFileName(nullptr, pathname, _MAX_PATH); - if(DbgHelpLoader::symLoadModule(process, nullptr, pathname, nullptr, 0, 0)) + if(DbgHelpLoader::symLoadModule64(process, nullptr, pathname, nullptr, 0, 0)) { //Load any other relevant modules (ie dlls) here atexit(DbgHelpLoader::unload); @@ -170,76 +187,56 @@ BOOL InitSymbolInfo() //***************************************************************************** //***************************************************************************** -void MakeStackTrace(DWORD myeip,DWORD myesp,DWORD myebp, int skipFrames, void (*callback)(const char*)) +void MakeStackTrace(CONTEXT& context, int skipFrames, void (*callback)(const char*)) { -STACKFRAME stack_frame; +STACKFRAME64 stack_frame; BOOL b_ret = TRUE; HANDLE thread = GetCurrentThread(); HANDLE process = GetCurrentProcess(); -memset(&gsContext, 0, sizeof(CONTEXT)); -gsContext.ContextFlags = CONTEXT_FULL; +const DWORD machineType = getStackWalkMachineType(); +initStackFrame64(stack_frame, context); -memset(&stack_frame, 0, sizeof(STACKFRAME)); -stack_frame.AddrPC.Mode = AddrModeFlat; -stack_frame.AddrPC.Offset = myeip; -stack_frame.AddrStack.Mode = AddrModeFlat; -stack_frame.AddrStack.Offset = myesp; -stack_frame.AddrFrame.Mode = AddrModeFlat; -stack_frame.AddrFrame.Offset = myebp; { -/* - if(GetThreadContext(thread, &gsContext)) - { - memset(&stack_frame, 0, sizeof(STACKFRAME)); - stack_frame.AddrPC.Mode = AddrModeFlat; - stack_frame.AddrPC.Offset = gsContext.Eip; - stack_frame.AddrStack.Mode = AddrModeFlat; - stack_frame.AddrStack.Offset = gsContext.Esp; - stack_frame.AddrFrame.Mode = AddrModeFlat; - stack_frame.AddrFrame.Offset = gsContext.Ebp; -*/ - - //{ - callback("Call Stack\n**********\n"); + callback("Call Stack\n**********\n"); - // Skip some ? - unsigned int skip = skipFrames; - while (b_ret&&skip) - { - b_ret = DbgHelpLoader::stackWalk( IMAGE_FILE_MACHINE_I386, - process, - thread, - &stack_frame, - nullptr, //&gsContext, - nullptr, - DbgHelpLoader::symFunctionTableAccess, - DbgHelpLoader::symGetModuleBase, - nullptr); - skip--; - } + // Skip some ? + unsigned int skip = skipFrames; + while (b_ret&&skip) + { + b_ret = DbgHelpLoader::stackWalk64( machineType, + process, + thread, + &stack_frame, + &context, + nullptr, + DbgHelpLoader::symFunctionTableAccess64, + DbgHelpLoader::symGetModuleBase64, + nullptr); + skip--; + } - skip = 30; - while(b_ret&&skip) - { + skip = 30; + while(b_ret&&skip) + { - b_ret = DbgHelpLoader::stackWalk( IMAGE_FILE_MACHINE_I386, - process, - thread, - &stack_frame, - nullptr, //&gsContext, - nullptr, - DbgHelpLoader::symFunctionTableAccess, - DbgHelpLoader::symGetModuleBase, - nullptr); + b_ret = DbgHelpLoader::stackWalk64( machineType, + process, + thread, + &stack_frame, + &context, + nullptr, + DbgHelpLoader::symFunctionTableAccess64, + DbgHelpLoader::symGetModuleBase64, + nullptr); - if (b_ret) WriteStackLine((void *) stack_frame.AddrPC.Offset, callback); - skip--; - } - } + if (b_ret) WriteStackLine((void *)(ULONG_PTR) stack_frame.AddrPC.Offset, callback); + skip--; + } +} } @@ -267,18 +264,19 @@ void GetFunctionDetails(void *pointer, char*name, char*filename, unsigned int* l *address = 0xFFFFFFFF; } - ULONG displacement = 0; + DWORD64 symDisplacement = 0; + DWORD lineDisplacement = 0; HANDLE process = ::GetCurrentProcess(); - char symbol_buffer[512 + sizeof(IMAGEHLP_SYMBOL)]; + char symbol_buffer[512 + sizeof(IMAGEHLP_SYMBOL64)]; memset(symbol_buffer, 0, sizeof(symbol_buffer)); - PIMAGEHLP_SYMBOL psymbol = (PIMAGEHLP_SYMBOL)symbol_buffer; + PIMAGEHLP_SYMBOL64 psymbol = (PIMAGEHLP_SYMBOL64)symbol_buffer; psymbol->SizeOfStruct = sizeof(symbol_buffer); psymbol->MaxNameLength = 512; - if (DbgHelpLoader::symGetSymFromAddr(process, (DWORD) pointer, &displacement, psymbol)) + if (DbgHelpLoader::symGetSymFromAddr64(process, (DWORD64)(ULONG_PTR) pointer, &symDisplacement, psymbol)) { if (name) { @@ -288,11 +286,11 @@ void GetFunctionDetails(void *pointer, char*name, char*filename, unsigned int* l // Get line now - IMAGEHLP_LINE line; + IMAGEHLP_LINE64 line; memset(&line,0,sizeof(line)); line.SizeOfStruct = sizeof(line); - if (DbgHelpLoader::symGetLineFromAddr(process, (DWORD) pointer, &displacement, &line)) + if (DbgHelpLoader::symGetLineFromAddr64(process, (DWORD64)(ULONG_PTR) pointer, &lineDisplacement, &line)) { if (filename) { @@ -319,96 +317,51 @@ void FillStackAddresses(void**addresses, unsigned int count, unsigned int skip) if (!InitSymbolInfo()) return; - STACKFRAME stack_frame; + STACKFRAME64 stack_frame; HANDLE thread = GetCurrentThread(); HANDLE process = GetCurrentProcess(); - memset(&gsContext, 0, sizeof(CONTEXT)); - gsContext.ContextFlags = CONTEXT_FULL; + CONTEXT context; + RtlCaptureContext(&context); - DWORD myeip,myesp,myebp; -#if defined(_MSC_VER) -_asm -{ -MYEIP2: - mov eax, MYEIP2 - mov dword ptr [myeip] , eax - mov eax, esp - mov dword ptr [myesp] , eax - mov eax, ebp - mov dword ptr [myebp] , eax - xor eax,eax -} -#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(_M_IX86)) - // GCC/Clang inline assembly for x86-32 - __asm__ __volatile__( - "call 1f\n\t" - "1: pop %0\n\t" - "mov %%esp, %1\n\t" - "mov %%ebp, %2\n\t" - "xor %%eax, %%eax" - : "=r"(myeip), "=r"(myesp), "=r"(myebp) - : - : "eax", "memory" - ); -#else - #error "Unsupported compiler or architecture for register capture" -#endif -memset(&stack_frame, 0, sizeof(STACKFRAME)); -stack_frame.AddrPC.Mode = AddrModeFlat; -stack_frame.AddrPC.Offset = myeip; -stack_frame.AddrStack.Mode = AddrModeFlat; -stack_frame.AddrStack.Offset = myesp; -stack_frame.AddrFrame.Mode = AddrModeFlat; -stack_frame.AddrFrame.Offset = myebp; + const DWORD machineType = getStackWalkMachineType(); + initStackFrame64(stack_frame, context); { -/* - if(GetThreadContext(thread, &gsContext)) - { - memset(&stack_frame, 0, sizeof(STACKFRAME)); - stack_frame.AddrPC.Mode = AddrModeFlat; - stack_frame.AddrPC.Offset = gsContext.Eip; - stack_frame.AddrStack.Mode = AddrModeFlat; - stack_frame.AddrStack.Offset = gsContext.Esp; - stack_frame.AddrFrame.Mode = AddrModeFlat; - stack_frame.AddrFrame.Offset = gsContext.Ebp; -*/ - Bool stillgoing = TRUE; // unsigned int cd = count; // Skip some? while (stillgoing&&skip) { - stillgoing = DbgHelpLoader::stackWalk(IMAGE_FILE_MACHINE_I386, + stillgoing = DbgHelpLoader::stackWalk64(machineType, process, thread, &stack_frame, - nullptr, //&gsContext, + &context, nullptr, - DbgHelpLoader::symFunctionTableAccess, - DbgHelpLoader::symGetModuleBase, + DbgHelpLoader::symFunctionTableAccess64, + DbgHelpLoader::symGetModuleBase64, nullptr) != 0; skip--; } while(stillgoing&&count) { - stillgoing = DbgHelpLoader::stackWalk(IMAGE_FILE_MACHINE_I386, + stillgoing = DbgHelpLoader::stackWalk64(machineType, process, thread, &stack_frame, - nullptr, //&gsContext, + &context, nullptr, - DbgHelpLoader::symFunctionTableAccess, - DbgHelpLoader::symGetModuleBase, + DbgHelpLoader::symFunctionTableAccess64, + DbgHelpLoader::symGetModuleBase64, nullptr) != 0; if (stillgoing) { - *addresses = (void*)stack_frame.AddrPC.Offset; + *addresses = (void*)(ULONG_PTR)stack_frame.AddrPC.Offset; addresses++; count--; } @@ -589,12 +542,35 @@ void DumpExceptionInfo( unsigned int u, EXCEPTION_POINTERS* e_info ) } DOUBLE_DEBUG (("\nStack Dump:")); - StackDumpFromContext(context->Eip, context->Esp, context->Ebp, nullptr); - DOUBLE_DEBUG (("\nDetails:")); - DOUBLE_DEBUG (("Register dump...")); + char scrap[512]; + unsigned char *eip_ptr; + // TheSuperHackers @build bobtista 12/06/2026 x64 _CONTEXT uses R* registers; the legacy x86 + // register dump truncates the 64-bit IP only for the legacy StackDumpFromContext call (the real + // x64 walk uses RtlCaptureContext/StackWalk64 above), the dump itself prints the full registers. +#if defined(_M_X64) || defined(__x86_64__) + StackDumpFromContext((DWORD)context->Rip, (DWORD)context->Rsp, (DWORD)context->Rbp, nullptr); + + /* + ** Dump the registers. + */ + DOUBLE_DEBUG ( ( "Rip:%016llX\tRsp:%016llX\tRbp:%016llX", context->Rip, context->Rsp, context->Rbp)); + DOUBLE_DEBUG ( ( "Rax:%016llX\tRbx:%016llX\tRcx:%016llX", context->Rax, context->Rbx, context->Rcx)); + DOUBLE_DEBUG ( ( "Rdx:%016llX\tRsi:%016llX\tRdi:%016llX", context->Rdx, context->Rsi, context->Rdi)); + DOUBLE_DEBUG ( ( "EFlags:%08X ", context->EFlags)); + DOUBLE_DEBUG ( ( "CS:%04x SS:%04x DS:%04x ES:%04x FS:%04x GS:%04x", context->SegCs, context->SegSs, context->SegDs, context->SegEs, context->SegFs, context->SegGs)); + + /* + ** Dump the bytes at RIP. This will make it easier to match the crash address with later versions of the game. + */ + DOUBLE_DEBUG ( ("RIP bytes dump...")); + wsprintf (scrap, "\nBytes at CS:RIP (%016llX) : ", context->Rip); + eip_ptr = (unsigned char *) (context->Rip); +#else + StackDumpFromContext(context->Eip, context->Esp, context->Ebp, nullptr); + /* ** Dump the registers. */ @@ -607,11 +583,10 @@ void DumpExceptionInfo( unsigned int u, EXCEPTION_POINTERS* e_info ) /* ** Dump the bytes at EIP. This will make it easier to match the crash address with later versions of the game. */ - char scrap[512]; DOUBLE_DEBUG ( ("EIP bytes dump...")); wsprintf (scrap, "\nBytes at CS:EIP (%08X) : ", context->Eip); - - unsigned char *eip_ptr = (unsigned char *) (context->Eip); + eip_ptr = (unsigned char *) (context->Eip); +#endif char bytestr[32]; for (int c = 0 ; c < 32 ; c++) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/Trig.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/Trig.cpp index 2ffb79bbae2..bfedd93bd5a 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/Trig.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/Trig.cpp @@ -29,117 +29,24 @@ #include "PreRTS.h" -#include -#include - #include "Lib/BaseType.h" #include "Lib/trig.h" -#define TWOPI 6.28318530718f -#define DEG2RAD 0.0174532925199f -#define TRIG_RES 4096 - -// the following are for fixed point ints with 12 fractional bits -#define INT_ONE 4096 -#define INT_TWOPI 25736 -#define INT_THREEPIOVERTWO 19302 -#define INT_PI 12868 -#define INT_HALFPI 6434 - -Real Sin(Real x) -{ - return sinf(x); -} - -Real Cos(Real x) -{ - return cosf(x); -} - -Real Tan(Real x) -{ - return tanf(x); -} - -Real ACos(Real x) -{ - return acosf(x); -} - -Real ASin(Real x) -{ - return asinf(x); -} +#if USE_DETERMINISTIC_MATH +#include "gmath.h" +#endif -#ifdef REGENERATE_TRIG_TABLES -void initTrig() +Real Sin(Real x) { return WWMath::Sinf(x); } +Real Cos(Real x) { return WWMath::Cosf(x); } +Real Tan(Real x) { return WWMath::Tanf(x); } +Real ACos(Real x) { return WWMath::Acosf(x); } +Real ASin(Real x) { return WWMath::Asinf(x); } +Real Sqrt(Real x) { - static Byte inited = FALSE; - Real angle, r; - int i; - - if (inited) - return; - - inited = TRUE; - - static int columns = 8; - int column = 0; - FILE *fp = fopen("trig.txt", "w"); - fprintf(fp, "static Int sinLookup[TRIG_RES] = {\n"); - for( i=0; igetNextToken(); 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/AnimateWindowManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp index 151464bdc04..07b3abf8f42 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/AnimateWindowManager.cpp @@ -133,6 +133,8 @@ AnimateWindowManager::AnimateWindowManager() m_winList.clear(); m_needsUpdate = FALSE; m_reverse = FALSE; + m_lastUpdateTime = 0; + m_updateAccumulator = 0.0f; m_winMustFinishList.clear(); } AnimateWindowManager::~AnimateWindowManager() @@ -159,6 +161,8 @@ void AnimateWindowManager::init() clearWinList(m_winMustFinishList); m_needsUpdate = FALSE; m_reverse = FALSE; + m_lastUpdateTime = 0; + m_updateAccumulator = 0.0f; } void AnimateWindowManager::reset() @@ -168,9 +172,43 @@ void AnimateWindowManager::reset() clearWinList(m_winMustFinishList); m_needsUpdate = FALSE; m_reverse = FALSE; + m_lastUpdateTime = 0; + m_updateAccumulator = 0.0f; } +// TheSuperHackers @tweak bobtista 27/06/2026 Decouple GUI window-move animations from the +// render frame rate. The animation logic runs at the historic base rate; here we run the +// number of base-rate steps that match the elapsed wall-clock time, carrying the fractional +// remainder between updates. This is independent of how often update() is called, so callers +// pumped per render frame and callers throttled to a fixed rate both animate at the same speed. void AnimateWindowManager::update() +{ + const UnsignedInt now = timeGetTime(); + if (m_lastUpdateTime == 0) + { + m_lastUpdateTime = now; + } + const UnsignedInt elapsed = now - m_lastUpdateTime; + m_lastUpdateTime = now; + + m_updateAccumulator += (Real)elapsed * ((Real)BaseFps / 1000.0f); + Int steps = (Int)m_updateAccumulator; + m_updateAccumulator -= (Real)steps; + + // Cap the catch-up burst so a long stall (load, alt-tab) cannot snap an animation instantly. + const Int maxSteps = 6; + if (steps > maxSteps) + { + steps = maxSteps; + } + + while (steps-- > 0) + { + updateStep(); + } +} + +void AnimateWindowManager::updateStep() { ProcessAnimateWindow *processAnim = nullptr; 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/GameInfoWindow.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/GameInfoWindow.cpp index d3f68fe3bb9..974ee45c150 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/GameInfoWindow.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/GameInfoWindow.cpp @@ -119,6 +119,15 @@ void RefreshGameInfoWindow(GameInfo *gameInfo, UnicodeString gameName) { // can happen if the map will have to be transferred... so use the leaf name (srj) const char *noPath = gameInfo->getMap().reverseFind('\\'); +#ifndef _WIN32 + { + const char *fwd = gameInfo->getMap().reverseFind('/'); + if (fwd && (!noPath || fwd > noPath)) + { + noPath = fwd; + } + } +#endif if (noPath) { ++noPath; 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..b4f318ccf46 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(); @@ -822,6 +822,15 @@ void DeinitLanGameGadgets() //------------------------------------------------------------------------------------------------- void LanGameOptionsMenuInit( WindowLayout *layout, void *userData ) { + // TheSuperHackers @bugfix bobtista 09/06/2026 Bail if there is no current LAN game. + // This init is re-run when the shell rebuilds its window layouts (e.g. on a window + // resize); if it runs after the lobby game is gone, the gadget setup below dereferences + // a null GetMyGame(). Return without popping, since this can fire mid stack-rebuild. + if (TheLAN == NULL || TheLAN->GetMyGame() == NULL) + { + return; + } + if (TheLAN->GetMyGame() && TheLAN->GetMyGame()->isGameInProgress()) { // If we init while the game is in progress, we are really returning to the menu @@ -951,10 +960,25 @@ void updateGameOptions() else { AsciiString s = TheLAN->GetMyGame()->getMap(); +#ifdef _WIN32 if (s.reverseFind('\\')) { s = s.reverseFind('\\') + 1; } +#else + { + const char* sep = s.reverseFind('\\'); + const char* fwd = s.reverseFind('/'); + if (fwd && (!sep || fwd > sep)) + { + sep = fwd; + } + if (sep) + { + s = sep + 1; + } + } +#endif mapDisplayName.format(L"%hs", s.str()); } UnicodeString old = GadgetStaticTextGetText(textEntryMapDisplay); @@ -967,7 +991,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..29ddd98d883 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) { @@ -429,13 +429,19 @@ void LanLobbyMenuInit( WindowLayout *layout, void *userData ) } */ DEBUG_ASSERTCRASH(IPlist, ("No IP addresses found!")); - if (!IPlist) + // TheSuperHackers @bugfix bobtista 09/06/2026 Guard against an empty IP + // list. In Release the assert above is compiled out, so dereferencing a + // null IPlist below crashed the game when entering the network lobby. + if (IPlist) + { + IPSource = L"Local IP chosen"; + IP = IPlist->getIP(); + } + else { /// @todo: display error and exit lan lobby if no IPs are found + IPSource = L"No local IP found"; } - - IPSource = L"Local IP chosen"; - IP = IPlist->getIP(); } else { diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp index f51953e454f..e4cfe6befaa 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp @@ -204,7 +204,7 @@ static Bool launchChallengeMenu = FALSE; static Bool dontAllowTransitions = FALSE; -const Int /*TIME_OUT = 15,*/ CORNER = 10; +//const Int TIME_OUT = 15, CORNER = 10; void AcceptResolution(); void DeclineResolution(); GameWindow *resAcceptMenu = nullptr; @@ -693,6 +693,9 @@ void AcceptResolution() //set to off oldDispSettings = newDispSettings; dispChanged = FALSE; + // TheSuperHackers @bugfix bobtista 25/06/2026 Clear the tracked dialog window so the resize-driven + // RecreateResolutionDialogIfActive does not touch the now-destroyed box. + resAcceptMenu = nullptr; } //------------------------------------------------------------------------------------------------- @@ -726,6 +729,9 @@ void DeclineResolution() TheInGameUI->recreateControlBar(); } + // TheSuperHackers @bugfix bobtista 25/06/2026 Clear the tracked dialog window so the resize-driven + // RecreateResolutionDialogIfActive does not touch the now-destroyed box. + resAcceptMenu = nullptr; } //------------------------------------------------------------------------------------------------- @@ -743,12 +749,34 @@ void DoResolutionDialog() resTimerString.concat(resolutionNew); - resAcceptMenu = TheWindowManager->gogoMessageBox( CORNER, CORNER, -1, -1,MSG_BOX_OK | MSG_BOX_CANCEL , + // TheSuperHackers @bugfix bobtista 25/06/2026 Center the resolution confirmation box (-1,-1) instead of corner-positioning it; gogoMessageBox's reposition path moves only the parent frame, leaving the OK/Cancel buttons and text detached at their default layout positions. + resAcceptMenu = TheWindowManager->gogoMessageBox( -1, -1, -1, -1,MSG_BOX_OK | MSG_BOX_CANCEL , TheGameText->fetch("GUI:Resolution"), resTimerString, nullptr, nullptr, AcceptResolution, DeclineResolution); } +//------------------------------------------------------------------------------------------------- +// TheSuperHackers @bugfix bobtista 25/06/2026 Rebuild the resolution-confirm dialog at the live +// resolution. On macOS a fullscreen resolution change settles asynchronously after the dialog is +// created, so the box gets laid out at the pre-change resolution and then renders mis-scaled once +// the display reaches the new mode. The engine resize path calls this after the display settles so +// the dialog is recreated to match. dispChanged is true only while a confirmation is pending. +//------------------------------------------------------------------------------------------------- +void RecreateResolutionDialogIfActive() +{ + // Only act when the confirmation dialog is actually open: dispChanged becomes true the moment the + // resolution is changed in the options menu - before the dialog is created on menu exit - so the + // live resAcceptMenu guard prevents creating the dialog prematurely. + if (!dispChanged || resAcceptMenu == nullptr) + { + return; + } + TheWindowManager->winDestroy(resAcceptMenu); + resAcceptMenu = nullptr; + DoResolutionDialog(); +} + /* This function is not being currently used because we do not need a timer on the // dialog box. //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp index 7c9c462f9b9..8bdd744e7dd 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/NetworkDirectConnect.cpp @@ -96,7 +96,9 @@ void PopulateRemoteIPComboBox() { GadgetComboBoxSetSelectedPos(comboboxRemoteIP, 0, TRUE); } - userprefs.write(); + // TheSuperHackers @bugfix bobtista 09/06/2026 Do not write prefs from this read-only populate + // function; the remote-IP list is persisted by UpdateRemoteIPList. Writing here served no + // purpose and risked clobbering the file during display refreshes. } void UpdateRemoteIPList() @@ -110,11 +112,14 @@ void UpdateRemoteIPList() AsciiString sel; sel.translate(unisel); -// UnicodeString newEntry = prefs.getRemoteIPEntry(0); - UnicodeString newEntry = unisel; - UnicodeString newIP; - newEntry.nextToken(&newIP, L":"); - Int numFields = swscanf(newIP.str(), L"%d.%d.%d.%d", &(n1[0]), &(n1[1]), &(n1[2]), &(n1[3])); + // TheSuperHackers @bugfix bobtista 09/06/2026 Parse the IP from the AsciiString with narrow + // sscanf. The wide swscanf previously used here returned the wrong field count on macOS + // (4-byte wchar_t), so a valid typed IP was treated as malformed and the remote-IP list was + // never saved - even though the direct connect itself (which uses narrow sscanf) worked. + AsciiString newEntryAscii = sel; + AsciiString newIPAscii; + newEntryAscii.nextToken(&newIPAscii, ":"); + Int numFields = sscanf(newIPAscii.str(), "%d.%d.%d.%d", &(n1[0]), &(n1[1]), &(n1[2]), &(n1[3])); if (numFields != 4) { // this is not a properly formatted IP, don't change a thing. @@ -142,7 +147,7 @@ void UpdateRemoteIPList() UnicodeString oldIP; oldEntry.nextToken(&oldIP, L":"); - swscanf(oldIP.str(), L"%d.%d.%d.%d", &(n2[0]), &(n2[1]), &(n2[2]), &(n2[3])); + AsciiString oldIPAscii; oldIPAscii.translate(oldIP); sscanf(oldIPAscii.str(), "%d.%d.%d.%d", &(n2[0]), &(n2[1]), &(n2[2]), &(n2[3])); Bool isEqual = TRUE; for (Int i = 0; (i < 4) && (isEqual == TRUE); ++i) { 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..0651aacfb02 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); } } @@ -877,6 +877,13 @@ static void saveOptions() TheInGameUI->recreateControlBar(); TheInGameUI->refreshCustomUiResources(); + + // TheSuperHackers @fix Mauller 08/05/2026 Update the view so the camera height re-scales when changing resolution + TheTacticalView->setDefaultView( + DEG_TO_RADF(TheGlobalData->m_cameraPitch), + DEG_TO_RADF(TheGlobalData->m_cameraYaw), + 1.0f); + TheTacticalView->setZoomToDefault(); } } } 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/PopupSaveLoad.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp index 45cb58fd4b9..4516a348732 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupSaveLoad.cpp @@ -460,6 +460,13 @@ static void setEditDescription( GameWindow *editControl ) else { const char *mapName = TheGlobalData->m_mapName.reverseFind( '\\' ); +#ifndef _WIN32 + const char *fwdSlash = TheGlobalData->m_mapName.reverseFind( '/' ); + if (fwdSlash && (!mapName || fwdSlash > mapName)) + { + mapName = fwdSlash; + } +#endif if( mapName ) defaultDesc.format( L"%S", mapName + 1 ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp index 4188d26ba91..862cafeaa36 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp @@ -155,6 +155,19 @@ static UnicodeString createReplayName(const AsciiString& filename) //------------------------------------------------------------------------------------------------- +static const char *findPathLeaf(const AsciiString& path) +{ + const char* slash = path.reverseFind('\\'); +#ifndef _WIN32 + const char* forwardSlash = path.reverseFind('/'); + if (forwardSlash && (!slash || forwardSlash > slash)) + slash = forwardSlash; +#endif + return slash ? slash + 1 : path.str(); +} + +//------------------------------------------------------------------------------------------------- + static UnicodeString createMapName(const AsciiString& filename, const ReplayGameInfo& info, const MapMetaData *mapData) { UnicodeString mapName; @@ -162,8 +175,12 @@ static UnicodeString createMapName(const AsciiString& filename, const ReplayGame { // TheSuperHackers @bugfix helmutbuhler 08/03/2025 Just use the filename. // Displaying a long map path string would break the map list gui. +#ifdef _WIN32 const char* filename = info.getMap().reverseFind('\\'); mapName.translate(filename ? filename + 1 : info.getMap()); +#else + mapName.translate(findPathLeaf(info.getMap())); +#endif } else { @@ -278,7 +295,11 @@ void PopulateReplayFileListbox(GameWindow *listbox) for (it = replayFilenames.begin(); it != replayFilenames.end(); ++it) { // just want the filename +#ifdef _WIN32 asciistr.set((*it).reverseFind('\\') + 1); +#else + asciistr.set(findPathLeaf(*it)); +#endif RecorderClass::ReplayHeader header; ReplayGameInfo info; 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..fa8291f5eef 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ScoreScreen.cpp @@ -195,7 +195,9 @@ void startNextCampaignGame() TheShell->popImmediate(); TheShell->hideShell(); TheWritableGlobalData->m_pendingFile = TheCampaignManager->getCurrentMap(); - if (TheCampaignManager->getCurrentCampaign() && TheCampaignManager->getCurrentCampaign()->isChallengeCampaign()) + const Bool isChallenge = (TheCampaignManager->getCurrentCampaign() + && TheCampaignManager->getCurrentCampaign()->isChallengeCampaign()); + if (isChallenge) { DEBUG_ASSERTCRASH( TheChallengeGameInfo, ("TheChallengeGameInfo doesn't exist.") ); TheChallengeGameInfo->init(); @@ -211,6 +213,14 @@ void startNextCampaignGame() slot.setPlayerTemplate(templateNum); TheChallengeGameInfo->setSlot(0, slot); + // TheSuperHackers @tweak bobtista 12/06/2026 Set up the next challenge battle like ChallengeMenu's + // launch: restore the challenge difficulty onto both the campaign and the script engine. + if (TheChallengeGenerals) + { + TheCampaignManager->setGameDifficulty(TheChallengeGenerals->getCurrentDifficulty()); + TheScriptEngine->setGlobalDifficulty(TheChallengeGenerals->getCurrentDifficulty()); + } + if (TheGameLogic->isInGame()) TheGameLogic->clearGameData(); } @@ -221,6 +231,14 @@ void startNextCampaignGame() msg->appendIntegerArgument(TheCampaignManager->getGameDifficulty()); msg->appendIntegerArgument(TheCampaignManager->getRankPoints()); + // TheSuperHackers @bugfix bobtista 12/06/2026 A challenge game is a SkirmishGame in single-player + // clothing; match ChallengeMenu's launch and pass the frame cap so GameEngine applies it as it + // does for solo missions. + if (isChallenge) + { + msg->appendIntegerArgument(LOGICFRAMES_PER_SECOND); + } + InitRandom(0); } @@ -613,7 +631,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..1a7b65fb58c 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(); @@ -971,10 +971,25 @@ void WOLDisplayGameOptions() else { AsciiString s = TheGameSpyInfo->getCurrentStagingRoom()->getMap(); +#ifdef _WIN32 if (s.reverseFind('\\')) { s = s.reverseFind('\\') + 1; } +#else + { + const char* sep = s.reverseFind('\\'); + const char* fwd = s.reverseFind('/'); + if (fwd && (!sep || fwd > sep)) + { + sep = fwd; + } + if (sep) + { + s = sep + 1; + } + } +#endif UnicodeString mapDisplay; mapDisplay.translate(s); GadgetStaticTextSetText(textEntryMapDisplay, mapDisplay); @@ -1013,7 +1028,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/GameClient/GUI/GameWindowManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp index 773c22dd94e..7865bef0095 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp @@ -103,8 +103,7 @@ void GameWindowManager::processDestroyList() if( m_keyboardFocus == doDestroy ) winSetFocus( nullptr ); - if( (m_modalHead != nullptr) && (doDestroy == m_modalHead->window) ) - winUnsetModal( m_modalHead->window ); + winRemoveFromModalStack( doDestroy ); if( m_currMouseRgn == doDestroy ) m_currMouseRgn = nullptr; @@ -1422,8 +1421,7 @@ Int GameWindowManager::winDestroy( GameWindow *window ) if( m_keyboardFocus == window ) winSetFocus( nullptr ); - if( (m_modalHead != nullptr) && (window == m_modalHead->window) ) - winUnsetModal( m_modalHead->window ); + winRemoveFromModalStack( window ); if( m_currMouseRgn == window ) m_currMouseRgn = nullptr; @@ -1555,6 +1553,44 @@ Int GameWindowManager::winUnsetModal( GameWindow *window ) } +//------------------------------------------------------------------------------------------------- +/** removes every modal stack entry that refers to the given window. */ +//------------------------------------------------------------------------------------------------- +// TheSuperHackers @bugfix bobtista 09/07/2026 Destroying a window that was not at the top of the +// modal stack left its entry in the stack. When the entries above it were popped, the dangling +// entry became the head and winProcessMouseEvent dereferenced the freed window on every mouse +// event. Unlink all of a window's entries when it is destroyed, not just a topmost one. +void GameWindowManager::winRemoveFromModalStack( GameWindow *window ) +{ + ModalWindow *prev = nullptr; + ModalWindow *modal = m_modalHead; + + while( modal ) + { + if( modal->window == window ) + { + ModalWindow *next = modal->next; + + if( prev ) + { + prev->next = next; + } + else + { + m_modalHead = next; + } + + deleteInstance(modal); + modal = next; + } + else + { + prev = modal; + modal = modal->next; + } + } +} + //------------------------------------------------------------------------------------------------- /** Get the grabbed window */ //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp index d524365f132..5a8faa98f64 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp @@ -2882,4 +2882,3 @@ GameWindow *GameWindowManager::winCreateFromScript( AsciiString filenameString, return firstWindow; } - diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp index e7d1333905b..3fdf24a7e9e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp @@ -93,7 +93,11 @@ void Shell::deconstruct() WindowLayout *newTop = top(); while(newTop) { - popImmediate(); + // TheSuperHackers @bugfix bobtista 24/07/2026 Do not initialize the menu exposed by each + // pop while tearing down the entire stack. Subsystems used by menu init callbacks may + // already have been destroyed; the save/load menu, for example, scans saves through + // TheGameState and crashed when it was exposed underneath the score screen on exit. + popImmediate( TRUE ); newTop = top(); } @@ -425,7 +429,7 @@ void Shell::pop() * from the shutdown() for the screen, it will be immediately popped off * the stack */ //------------------------------------------------------------------------------------------------- -void Shell::popImmediate() +void Shell::popImmediate( Bool suppressNextInit ) { WindowLayout *screen = top(); @@ -449,7 +453,7 @@ void Shell::popImmediate() screen->runShutdown( &immediatePop ); // pop the screen of the stack - doPop( FALSE ); + doPop( suppressNextInit ); if (TheIMEManager) TheIMEManager->detach(); @@ -466,7 +470,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,8 +532,10 @@ 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() || TheGlobalData->m_loadReplayGame.isNotEmpty()) + { return; + } if(useShellMap && TheGlobalData->m_shellMapOn) { // we're already in a shell game, return diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index e396c3861b2..9a7d16b7e55 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -402,7 +402,9 @@ void GameClient::init() // finish initializing the mouse. TheMouse->init(); TheMouse->initCapture(); - TheMouse->setPosition( 0, 0 ); + // TheSuperHackers @bugfix bobtista 11/07/2026 Was setPosition(0, 0); the + // default implementation still is. See Mouse::syncPositionToSystemCursor. + TheMouse->syncPositionToSystemCursor(); TheMouse->setMouseLimits(); TheMouse->setName("TheMouse"); } @@ -536,6 +538,10 @@ void GameClient::update() TheShell->showShellMap(TRUE); TheShell->showShell(); + // TheSuperHackers @bugfix bobtista 27/04/2026 Resume rendering after + // startup movie cancellation. With the shell map enabled, MainMenuInit + // may not run before the first shell-map frames. + TheWritableGlobalData->m_breakTheMovie = FALSE; } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index b3927ee92bd..f7a796300a4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -1177,6 +1177,8 @@ InGameUI::InGameUI() m_lastRenderFps = ~0u; m_lastRenderFpsLimit = ~0u; m_lastRenderFpsUpdateMs = 0u; + m_lastMoneyDisplayed = ~0u; + m_lastIncomeDisplayed = ~0u; m_systemTimeString = nullptr; m_systemTimeFont = "Tahoma"; @@ -1196,6 +1198,8 @@ InGameUI::InGameUI() m_gameTimePosition.y = kHudAnchorY; m_gameTimeColor = GameMakeColor( 255, 255, 255, 255 ); m_gameTimeDropColor = GameMakeColor( 0, 0, 0, 255 ); + m_gameTimeReservedWidth = 0; + m_gameTimeFrameReservedWidth = 0; m_playerInfoListFont = "Tahoma"; m_playerInfoListPointSize = TheGlobalData->m_playerInfoListFontSize; @@ -1513,6 +1517,15 @@ void InGameUI::handleRadiusCursor() { if (!m_curRadiusCursor.isEmpty()) { + // The quit menu consumes gameplay input, but preDraw still runs and the hardware mouse + // position continues to change. Hide the targeting decal while the menu is open so this + // render-time update cannot make a special-power target appear interactive through it. + if (m_isQuitMenuVisible) + { + m_curRadiusCursor.setOpacity(0.0f); + return; + } + if ( TheGlobalData->m_doubleClickAttackMove && m_duringDoubleClickAttackMoveGuardHintTimer > 0 ) { m_curRadiusCursor.setOpacity( m_duringDoubleClickAttackMoveGuardHintTimer * 0.1f ); @@ -1664,7 +1677,7 @@ void InGameUI::handleBuildPlacements() if (isInForceAttackMode()) { const Real snapRadians = DEG_TO_RADF(45); - angle = WWMath::Round(angle / snapRadians) * snapRadians; + angle = WWMath::Roundf(angle / snapRadians) * snapRadians; } } } @@ -2027,8 +2040,6 @@ void InGameUI::update() // update the player money window if the money amount has changed // this seems like as good a place as any to do the power hide/show - static UnsignedInt lastMoney = ~0u; - static UnsignedInt lastIncome = ~0u; static NameKeyType moneyWindowKey = TheNameKeyGenerator->nameToKey( "ControlBar.wnd:MoneyDisplay" ); static NameKeyType powerWindowKey = TheNameKeyGenerator->nameToKey( "ControlBar.wnd:PowerWindow" ); @@ -2051,13 +2062,13 @@ void InGameUI::update() if (!doShowIncome) { UnsignedInt currentMoney = money->countMoney(); - if( lastMoney != currentMoney ) + if( m_lastMoneyDisplayed != currentMoney ) { UnicodeString buffer; buffer.format(TheGameText->fetch( "GUI:ControlBarMoneyDisplay" ), currentMoney ); GadgetStaticTextSetText( moneyWin, buffer ); - lastMoney = currentMoney; + m_lastMoneyDisplayed = currentMoney; } } @@ -2066,7 +2077,7 @@ void InGameUI::update() // TheSuperHackers @feature L3-M 21/08/2025 player money per minute UnsignedInt currentMoney = money->countMoney(); UnsignedInt cashPerMin = money->getCashPerMinute(); - if ( lastMoney != currentMoney || lastIncome != cashPerMin ) + if ( m_lastMoneyDisplayed != currentMoney || m_lastIncomeDisplayed != cashPerMin ) { UnicodeString buffer; UnicodeString moneyStr = formatMoneyValue(currentMoney); @@ -2074,8 +2085,8 @@ void InGameUI::update() buffer.format(TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:ControlBarMoneyDisplayIncome", L"$ %ls +%ls/min", moneyStr.str(), incomeStr.str())); GadgetStaticTextSetText(moneyWin, buffer); - lastMoney = currentMoney; - lastIncome = cashPerMin; + m_lastMoneyDisplayed = currentMoney; + m_lastIncomeDisplayed = cashPerMin; } } moneyWin->winHide(FALSE); @@ -2312,7 +2323,7 @@ void InGameUI::message( AsciiString stringManagerLabel, ... ) va_list args; va_start( args, stringManagerLabel ); WideChar buf[ UnicodeString::MAX_FORMAT_BUF_LEN ]; - int result = vswprintf(buf, sizeof( buf )/sizeof( WideChar ), stringManagerString.str(), args ); + int result = formatStringW(buf, sizeof( buf )/sizeof( WideChar ), stringManagerString.str(), args ); va_end(args); if( result >= 0 ) @@ -2353,7 +2364,7 @@ void InGameUI::message( UnicodeString format, ... ) va_list args; va_start( args, format ); WideChar buf[ UnicodeString::MAX_FORMAT_BUF_LEN ]; - int result = vswprintf(buf, sizeof( buf )/sizeof( WideChar ), format.str(), args ); + int result = formatStringW(buf, sizeof( buf )/sizeof( WideChar ), format.str(), args ); va_end(args); if( result >= 0 ) @@ -2380,7 +2391,7 @@ void InGameUI::messageColor( const RGBColor *rgbColor, UnicodeString format, ... va_list args; va_start( args, format ); WideChar buf[ UnicodeString::MAX_FORMAT_BUF_LEN ]; - int result = vswprintf(buf, sizeof( buf )/sizeof( WideChar ), format.str(), args ); + int result = formatStringW(buf, sizeof( buf )/sizeof( WideChar ), format.str(), args ); va_end(args); if( result >= 0 ) @@ -6001,8 +6012,14 @@ void InGameUI::resetIdleWorker() void InGameUI::recreateControlBar() { - GameWindow *win = TheWindowManager->winGetWindowFromId(nullptr, TheNameKeyGenerator->nameToKey("ControlBar.wnd")); - deleteInstance(win); + // TheSuperHackers @bugfix bobtista 20/07/2026 The control bar layout has a single top-level window + // named "ControlBar.wnd:ControlBarParent" (see ControlBar.wnd); there is NO window named just + // "ControlBar.wnd". The old lookup for "ControlBar.wnd" returned NULL, so winDestroy was a no-op + // and every rebuild (each mid-match resize) stacked another control bar over the orphaned old one. + // Destroy the real parent; winDestroy recurses through every HUD child (radar, money, portrait). + static const NameKeyType controlBarParentKey = TheNameKeyGenerator->nameToKey("ControlBar.wnd:ControlBarParent"); + GameWindow *win = TheWindowManager->winGetWindowFromId(nullptr, controlBarParentKey); + TheWindowManager->winDestroy(win); m_idleWorkerWin = nullptr; @@ -6013,6 +6030,77 @@ void InGameUI::recreateControlBar() TheControlBar->init(); } +// TheSuperHackers @bugfix bobtista 20/07/2026 Retail never changed resolution mid-match, so no path +// rebuilds the whole in-game HUD on a live window resize. recreateControlBar() alone only rebuilds the +// ControlBar.wnd tree; the money/superweapon/named-timer state and the radar window pointer are left +// stale. This reproduces the game-start HUD build (GameLogic::startNewGame) and clears the cached +// state that does not self-correct, so the HUD comes back intact after a resize. +void InGameUI::onResolutionChanged() +{ + recreateControlBar(); + + const Bool inRealGame = + (TheGameLogic != NULL && TheGameLogic->isInGame() && !TheGameLogic->isInShellGame()); + if (inRealGame) + { + if (TheControlBar != NULL && ThePlayerList != NULL) + { + Player *localPlayer = ThePlayerList->getLocalPlayer(); + if (localPlayer != NULL) + { + TheControlBar->setControlBarSchemeByPlayer(localPlayer); + TheControlBar->rebuildSpecialPowerShortcutBarForResolution(localPlayer); + TheControlBar->markUIDirty(); + } + } + + // The radar window pointer was destroyed with the old ControlBar.wnd tree; re-hook it to the + // freshly created LeftHUD window without resetting the radar data (newMap would wipe it). + if (TheRadar != NULL) + { + TheRadar->reattachWindow(); + } + } + + refreshCustomUiResources(); + + // The superweapon countdown and named-timer display strings cache their rendered width and only + // recompute it on setFont/setText. Their font size is resolution-scaled, so re-apply the font and + // force a text refresh; otherwise the name and time columns drift apart at the new size. + for (Int i = 0; i < MAX_PLAYER_COUNT; ++i) + { + for (SuperweaponMap::iterator mapIt = m_superweapons[i].begin(); mapIt != m_superweapons[i].end(); ++mapIt) + { + for (SuperweaponList::iterator listIt = mapIt->second.begin(); listIt != mapIt->second.end(); ++listIt) + { + SuperweaponInfo *info = *listIt; + if (info != NULL) + { + info->setFont(m_superweaponNormalFont, m_superweaponNormalPointSize, m_superweaponNormalBold); + info->m_forceUpdateText = TRUE; + } + } + } + } + for (NamedTimerMapIt timerIt = m_namedTimers.begin(); timerIt != m_namedTimers.end(); ++timerIt) + { + NamedTimerInfo *info = timerIt->second; + if (info != NULL && info->displayString != NULL) + { + info->displayString->setFont( TheFontLibrary->getFont( m_namedTimerNormalFont, + TheGlobalLanguageData->adjustFontSize(m_namedTimerNormalPointSize), m_namedTimerNormalBold ) ); + info->timestamp = ~0u; + } + } + + // Force the value caches to repaint the freshly recreated (blank) gadgets on the next update. + m_lastMoneyDisplayed = ~0u; + m_lastIncomeDisplayed = ~0u; + m_lastNetworkLatencyFrames = ~0u; + m_lastRenderFps = ~0u; + m_lastRenderFpsLimit = ~0u; +} + void InGameUI::refreshCustomUiResources() { refreshNetworkLatencyResources(); @@ -6093,6 +6181,28 @@ void InGameUI::refreshGameTimeResources() GameFont* gameTimeFont = TheWindowManager->winFindFont(m_gameTimeFont, adjustedGameTimeFontSize, m_gameTimeBold); m_gameTimeString->setFont(gameTimeFont); m_gameTimeFrameString->setFont(gameTimeFont); + + // TheSuperHackers @fix bobtista 07/07/2026 Reserve widths from the widest digit so the clock does not jitter + Int maxDigitWidth = 0; + for (WideChar digit = L'0'; digit <= L'9'; ++digit) + { + UnicodeString digitString; + digitString.concat(digit); + m_gameTimeString->setText(digitString); + Int digitWidth = m_gameTimeString->getWidth(); + if (digitWidth > maxDigitWidth) + { + maxDigitWidth = digitWidth; + } + } + + m_gameTimeString->setText(UnicodeString(L":")); + Int colonWidth = m_gameTimeString->getWidth(); + m_gameTimeString->setText(UnicodeString(L".")); + Int dotWidth = m_gameTimeString->getWidth(); + + m_gameTimeReservedWidth = 6 * maxDigitWidth + 2 * colonWidth; + m_gameTimeFrameReservedWidth = dotWidth + 2 * maxDigitWidth; } void InGameUI::refreshPlayerInfoListResources() @@ -6284,8 +6394,9 @@ void InGameUI::drawGameTime() m_gameTimeFrameString->setText(gameTimeFrameString); // TheSuperHackers @info this implicitly offsets the game timer from the right instead of left of the screen - int horizontalTimerOffset = TheDisplay->getWidth() - (Int)m_gameTimePosition.x - m_gameTimeString->getWidth() - m_gameTimeFrameString->getWidth(); - int horizontalFrameOffset = TheDisplay->getWidth() - (Int)m_gameTimePosition.x - m_gameTimeFrameString->getWidth(); + // TheSuperHackers @fix bobtista 07/07/2026 Anchor timer left to reserved widths so it stops jittering; butt frame against its live right edge + int horizontalTimerOffset = TheDisplay->getWidth() - (Int)m_gameTimePosition.x - m_gameTimeReservedWidth - m_gameTimeFrameReservedWidth; + int horizontalFrameOffset = horizontalTimerOffset + m_gameTimeString->getWidth(); m_gameTimeString->draw(horizontalTimerOffset, m_gameTimePosition.y, m_gameTimeColor, m_gameTimeDropColor); m_gameTimeFrameString->draw(horizontalFrameOffset, m_gameTimePosition.y, GameMakeColor(180,180,180,255), m_gameTimeDropColor); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp index ff0d155d9f2..4f84f2c8727 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AI.cpp @@ -714,8 +714,12 @@ Object *AI::findClosestEnemy( const Object *me, Real range, UnsignedInt qualifie } Real distSqr = ThePartitionManager->getDistanceSquared(me, theEnemy, FROM_BOUNDINGSPHERE_2D); - Real dist = sqrt(distSqr); - Int modifier = dist/getAiData()->m_attackPriorityDistanceModifier; + Real dist = WWMath::Sqrtf(distSqr); + // TheSuperHackers @bugfix bobtista 10/06/2026 m_attackPriorityDistanceModifier defaults to 0; + // dist/0 is Inf and (Int)Inf is platform-divergent UB (arm64 vs x86) that would skew target + // selection differently per machine and desync lockstep. Treat a non-positive modifier as none. + const Real distModifier = getAiData()->m_attackPriorityDistanceModifier; + Int modifier = (distModifier > 0.0f) ? (Int)(dist / distModifier) : 0; Int modPriority = curPriority-modifier; if (modPriority < 1) modPriority = 1; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp index dfc65a9c666..949f4cde9fc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp @@ -1883,8 +1883,8 @@ void getHelicopterOffset( Coord3D& posOut, Int idx ) } Coord3D tempCtr = posOut; - posOut.x = tempCtr.x + (sin(angle) * radius); - posOut.y = tempCtr.y + (cos(angle) * radius); + posOut.x = tempCtr.x + (WWMath::Sinf(angle) * radius); + posOut.y = tempCtr.y + (WWMath::Cosf(angle) * radius); } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp index 330969a65ed..6e88d9500f6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp @@ -495,7 +495,7 @@ Object *AIPlayer::buildStructureNow(const ThingTemplate *bldgPlan, BuildListInfo { Coord3D rallyPoint; Bool gotOffset = false; - if (fabs(info->getRallyOffset()->x) > 1.0f || fabs(info->getRallyOffset()->y)>1.0f) { + if (WWMath::Fabs(info->getRallyOffset()->x) > 1.0f || WWMath::Fabs(info->getRallyOffset()->y)>1.0f) { gotOffset; } if (!exitInterface->getNaturalRallyPoint(rallyPoint)) { @@ -653,7 +653,7 @@ Object *AIPlayer::buildStructureWithDozer(const ThingTemplate *bldgPlan, BuildLi dx = dozer->getPosition()->x - pos.x; dy = dozer->getPosition()->y - pos.y; - Int count = sqrt(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2); + Int count = WWMath::Sqrtf(dx*dx+dy*dy)/(PATHFIND_CELL_SIZE_F/2); if (count<2) count = 2; Int i; color.green = 1; @@ -675,7 +675,7 @@ Object *AIPlayer::buildStructureWithDozer(const ThingTemplate *bldgPlan, BuildLi { Coord3D rallyPoint; Bool gotOffset = false; - if (fabs(info->getRallyOffset()->x) > 1.0f || fabs(info->getRallyOffset()->y)>1.0f) { + if (WWMath::Fabs(info->getRallyOffset()->x) > 1.0f || WWMath::Fabs(info->getRallyOffset()->y)>1.0f) { gotOffset; } if (!exitInterface->getNaturalRallyPoint(rallyPoint)) { @@ -1360,7 +1360,7 @@ Int AIPlayer::getPlayerSuperweaponValue(Coord3D *center, Int playerNdx, Real rad Real dy = center->y - pos.y; if (dx*dx+dy*dygetTemplate()->calcCostToBuild(pPlayer); if (pObj->isKindOf(KINDOF_COMMANDCENTER)) @@ -3145,7 +3145,7 @@ void AIPlayer::computeCenterAndRadiusOfBase(Coord3D *center, Real *radius) Real radSqr = dx*dx+dy*dy; if (radSqr>maxRadSqr) maxRadSqr=radSqr; } - *radius = sqrt(maxRadSqr); + *radius = WWMath::Sqrtf(maxRadSqr); } //---------------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp index 0a1a7ea64da..ab9d54e2500 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp @@ -636,7 +636,9 @@ void AISkirmishPlayer::buildAIBaseDefenseStructure(const AsciiString &thingName, Real structureRadius = tTemplate->getTemplateGeometryInfo().getBoundingCircleRadius(); Real baseCircumference = 2*PI*defenseDistance; - Real angleOffset = 2*PI*(structureRadius*4/baseCircumference); + // TheSuperHackers @bugfix bobtista 10/06/2026 Guard a zero circumference: the division would be + // NaN/Inf, propagating into base-defense placement angles and desyncing AI lockstep across arch. + Real angleOffset = (baseCircumference > 0.0f) ? (2*PI*(structureRadius*4/baseCircumference)) : 0.0f; Int selector; Real angle; @@ -672,8 +674,8 @@ void AISkirmishPlayer::buildAIBaseDefenseStructure(const AsciiString &thingName, } if (angle > PI/3) break; - Real s = sin(angle); - Real c = cos(angle); + Real s = WWMath::Sinf(angle); + Real c = WWMath::Cosf(angle); // TheSuperHackers @info helmutbuhler 21/04/2025 This debug mutates the code to become CRC incompatible #if defined(RTS_DEBUG) || !RETAIL_COMPATIBLE_CRC @@ -1038,8 +1040,8 @@ void AISkirmishPlayer::adjustBuildList(BuildListInfo *list) angle += 3*PI/4; - Real s = sin(angle); - Real c = cos(angle); + Real s = WWMath::Sinf(angle); + Real c = WWMath::Cosf(angle); cur = list; while (cur) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index f78e882cc0c..3cf5e92df94 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -527,7 +527,7 @@ StateReturnType AIRappelState::onEnter() obj->setLayer(layerAtDest); AIUpdateInterface *ai = obj->getAI(); - Real MAX_RAPPEL_RATE = fabs(TheGlobalData->m_gravity) * LOGICFRAMES_PER_SECOND * 2.5f; + Real MAX_RAPPEL_RATE = WWMath::Fabs(TheGlobalData->m_gravity) * LOGICFRAMES_PER_SECOND * 2.5f; m_rappelRate = -min(ai->getDesiredSpeed(), MAX_RAPPEL_RATE); return STATE_CONTINUE; @@ -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(); @@ -3674,7 +3674,7 @@ StateReturnType AIAttackMoveToState::update() if (distSqr < sqr(ATTACK_CLOSE_ENOUGH_CELLS*PATHFIND_CELL_SIZE_F)) { return ret; } - DEBUG_LOG(("AIAttackMoveToState::update Distance from goal %f, retrying.", sqrt(distSqr))); + DEBUG_LOG(("AIAttackMoveToState::update Distance from goal %f, retrying.", WWMath::Sqrtf(distSqr))); ret = STATE_CONTINUE; m_retryCount--; @@ -3904,16 +3904,16 @@ void AIFollowWaypointPathState::computeGoal(Bool useGroupOffsets) if (m_priorWaypoint) { dx = dest.x - m_priorWaypoint->getLocation()->x; dy = dest.y - m_priorWaypoint->getLocation()->y; - angle = atan2(dy, dx); + angle = WWMath::Atan2f(dy, dx); Real deltaAngle = angle - m_angle; - Real s = sin(deltaAngle); - Real c = cos(deltaAngle); + Real s = WWMath::Sinf(deltaAngle); + Real c = WWMath::Cosf(deltaAngle); Real x = m_groupOffset.x * c - m_groupOffset.y * s; Real y = m_groupOffset.y * c + m_groupOffset.x * s; m_groupOffset.x = x; m_groupOffset.y = y; } else { - angle = atan2(dy, dx); + angle = WWMath::Atan2f(dy, dx); } m_angle = angle; #endif @@ -5078,7 +5078,7 @@ StateReturnType AIAttackAimAtTargetState::update() //DEBUG_LOG(("AIM: desired %f, actual %f, delta %f, aimDelta %f, goalpos %f %f",rad2deg(obj->getOrientation() + relAngle),rad2deg(obj->getOrientation()),rad2deg(relAngle),rad2deg(aimDelta),victim->getPosition()->x,victim->getPosition()->y)); if (m_canTurnInPlace) { - if (fabs(relAngle) > aimDelta) + if (WWMath::Fabs(relAngle) > aimDelta) { Real desiredAngle = source->getOrientation() + relAngle; sourceAI->setLocomotorGoalOrientation(desiredAngle); @@ -5090,7 +5090,7 @@ StateReturnType AIAttackAimAtTargetState::update() sourceAI->setLocomotorGoalPositionExplicit(m_isAttackingObject ? *victim->getPosition() : *getMachineGoalPosition()); } - if (fabs(relAngle) < aimDelta /*&& !m_preAttackFrames*/ ) + if (WWMath::Fabs(relAngle) < aimDelta /*&& !m_preAttackFrames*/ ) { AIUpdateInterface* victimAI = victim ? victim->getAI() : nullptr; // add ourself as a targeter BEFORE calling isTemporarilyPreventingAimSuccess(). @@ -7490,7 +7490,7 @@ StateReturnType AIFaceState::update() Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, pos ); const Real REL_THRESH = 0.035f; // about 2 degrees. (getRelativeAngle2D is current only accurate to about 1.25 degrees) - if( fabs( relAngle ) < REL_THRESH ) + if( WWMath::Fabs( relAngle ) < REL_THRESH ) { return STATE_SUCCESS; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/TurretAI.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/TurretAI.cpp index 02d7291071a..53eab1dc7c8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/TurretAI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/TurretAI.cpp @@ -401,7 +401,7 @@ Bool TurretAI::friend_turnTowardsAngle(Real desiredAngle, Real rateModifier, Rea Real angleDiff = normalizeAngle(desiredAngle - actualAngle); // Are we close enough to the desired angle to just snap there? - if (fabs(angleDiff) < turnRate) + if (WWMath::Fabs(angleDiff) < turnRate) { // we are centered actualAngle = desiredAngle; @@ -424,7 +424,7 @@ Bool TurretAI::friend_turnTowardsAngle(Real desiredAngle, Real rateModifier, Rea if( m_angle != origAngle ) getOwner()->reactToTurretChange( m_whichTurret, origAngle, m_pitch ); - Bool aligned = fabs(m_angle - desiredAngle) <= relThresh; + Bool aligned = WWMath::Fabs(m_angle - desiredAngle) <= relThresh; return aligned; } @@ -442,7 +442,7 @@ Bool TurretAI::friend_turnTowardsPitch(Real desiredPitch, Real rateModifier) Real pitchRate = getPitchRate() * rateModifier; Real pitchDiff = normalizeAngle(desiredPitch - actualPitch); - if (fabs(pitchDiff) < pitchRate) + if (WWMath::Fabs(pitchDiff) < pitchRate) { // we are centered actualPitch = desiredPitch; @@ -1102,7 +1102,7 @@ StateReturnType TurretAIAimTurretState::update() turret->friend_setPositiveSweep(!turret->friend_getPositiveSweep()); Real angleDiff = normalizeAngle(relAngle - turret->getTurretAngle()); - turnAlignedToNemesis = (fabs(angleDiff) < sweep); + turnAlignedToNemesis = (WWMath::Fabs(angleDiff) < sweep); } Bool pitchAlignedToNemesis = true; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp index 4f19ee9cfc2..473712bb5ef 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp @@ -286,7 +286,7 @@ void PolygonTrigger::updateBounds() const Real halfWidth = (m_bounds.hi.x - m_bounds.lo.x) / 2.0f; Real halfHeight = (m_bounds.hi.y + m_bounds.lo.y) / 2.0f; - m_radius = sqrt(halfHeight*halfHeight + halfWidth*halfWidth); + m_radius = WWMath::Sqrtf(halfHeight*halfHeight + halfWidth*halfWidth); } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp index b2c05e49597..10e3ae835c9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp @@ -500,7 +500,16 @@ void SidesList::prepareForMP_or_Skirmish() // Don't remove FactionCivilian. continue; } +#ifdef _WIN32 if (m_numSides == 1) break; // can't remove the last side. +#else + if (m_numSides == 1) + { + m_sides[0].clear(); + m_numSides = 0; + break; + } +#endif removeSide(i); i--; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp index 5a3d16f86ae..3a6c3d2ee9f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp @@ -1182,6 +1182,15 @@ void TerrainLogic::enableWaterGrid( Bool enable ) // create stripped map name c = strrchr( TheGlobalData->m_mapName.str(), '\\' ); +#ifndef _WIN32 + { + const char *fwd = strrchr( TheGlobalData->m_mapName.str(), '/' ); + if (fwd && (!c || fwd > c)) + { + c = fwd; + } + } +#endif if( c ) strippedMapNameOnly.set( c ); else @@ -1189,6 +1198,15 @@ void TerrainLogic::enableWaterGrid( Bool enable ) // create stripped compare name c = strrchr( TheGlobalData->m_vertexWaterAvailableMaps[ i ].str(), '\\' ); +#ifndef _WIN32 + { + const char *fwd = strrchr( TheGlobalData->m_vertexWaterAvailableMaps[ i ].str(), '/' ); + if (fwd && (!c || fwd > c)) + { + c = fwd; + } + } +#endif if( c ) strippedCompareMapNameOnly.set( c ); else @@ -1257,6 +1275,11 @@ Bool TerrainLogic::loadMap( AsciiString filename, Bool query ) // Add waypoint objects. MapObject *pObj; for (pObj = MapObject::getFirstMapObject(); pObj; pObj = pObj->getNext()) { +#ifndef _WIN32 + if (!pObj->isWaypoint() && pObj->getProperties()->getType(TheKey_waypointID) == Dict::DICT_INT) { + pObj->setIsWaypoint(); + } +#endif if (pObj->isWaypoint()) { addWaypoint(pObj); } @@ -1469,7 +1492,7 @@ void makeAlignToNormalMatrix( Real angle, const Coord3D& pos, const Coord3D& nor /* It is extremely important that the resulting matrix is such that the xvector points in the angle we specified; specifically, - that atan2(xvec.y, xvec.x) == angle. So we must construct + that WWMath::Atan2f(xvec.y, xvec.x) == angle. So we must construct the matrix carefully to ensure this! */ x.x = Cos( angle ); @@ -1490,7 +1513,7 @@ void makeAlignToNormalMatrix( Real angle, const Coord3D& pos, const Coord3D& nor x.normalize(); } - DEBUG_ASSERTCRASH(fabs(x.x*z.x + x.y*z.y + x.z*z.z)<0.0001,("dot is not zero (%f)",fabs(x.x*z.x + x.y*z.y + x.z*z.z))); + DEBUG_ASSERTCRASH(WWMath::Fabs(x.x*z.x + x.y*z.y + x.z*z.z)<0.0001,("dot is not zero (%f)",WWMath::Fabs(x.x*z.x + x.y*z.y + x.z*z.z))); // now computing the y vector is trivial. y.crossProduct( z, x, y ); @@ -1691,12 +1714,12 @@ PathfindLayerEnum TerrainLogic::getLayerForDestination(const Coord3D *pos) { Bridge *pBridge = getFirstBridge(); PathfindLayerEnum bestLayer = LAYER_GROUND; - Real bestDistance = fabs(pos->z - getGroundHeight(pos->x, pos->y)); + Real bestDistance = WWMath::Fabs(pos->z - getGroundHeight(pos->x, pos->y)); if (bestDistance > TheAI->pathfinder()->getWallHeight()/2) { // check wall. if (TheAI->pathfinder()->isPointOnWall(pos)) { - Real delta = fabs(pos->z-TheAI->pathfinder()->getWallHeight()); + Real delta = WWMath::Fabs(pos->z-TheAI->pathfinder()->getWallHeight()); if (deltaisPointOnBridge(pos) ) { - Real delta = fabs(pos->z-pBridge->getBridgeHeight(pos, nullptr)); + Real delta = WWMath::Fabs(pos->z-pBridge->getBridgeHeight(pos, nullptr)); if (deltagetLayer(); bestDistance = delta; @@ -1730,7 +1753,7 @@ PathfindLayerEnum TerrainLogic::getHighestLayerForDestination(const Coord3D *pos if (TheAI->pathfinder()->isPointOnWall(pos)) { Real delta = pos->z - TheAI->pathfinder()->getWallHeight(); // must be ABOVE (or on) the wall for this call. (srj) - if (delta >= 0 && fabs(delta) < fabs(bestDistance)) { + if (delta >= 0 && WWMath::Fabs(delta) < WWMath::Fabs(bestDistance)) { bestLayer = (PathfindLayerEnum)LAYER_WALL; bestDistance = delta; } @@ -1745,7 +1768,7 @@ PathfindLayerEnum TerrainLogic::getHighestLayerForDestination(const Coord3D *pos if (pBridge->isPointOnBridge(pos) ) { Real delta = pos->z - pBridge->getBridgeHeight(pos, nullptr); // must be ABOVE (or on) the bridge for this call. (srj) - if (delta >= 0 && fabs(delta) < fabs(bestDistance)) { + if (delta >= 0 && WWMath::Fabs(delta) < WWMath::Fabs(bestDistance)) { bestLayer = pBridge->getLayer(); bestDistance = delta; } @@ -1794,7 +1817,7 @@ Bool TerrainLogic::objectInteractsWithBridgeLayer(Object *obj, Int layer, Bool c if (match) { Real bridgeHeight = pBridge->getBridgeHeight(obj->getPosition(), nullptr); - Real delta = fabs(obj->getPosition()->z-bridgeHeight); + Real delta = WWMath::Fabs(obj->getPosition()->z-bridgeHeight); if (delta>LAYER_Z_CLOSE_ENOUGH_F) { return false; } @@ -1843,7 +1866,7 @@ Bool TerrainLogic::objectInteractsWithBridgeEnd(Object *obj, Int layer) const if (match) { Real bridgeHeight = pBridge->getBridgeHeight(obj->getPosition(), nullptr); - Real delta = fabs(obj->getPosition()->z-bridgeHeight); + Real delta = WWMath::Fabs(obj->getPosition()->z-bridgeHeight); if (delta>LAYER_Z_CLOSE_ENOUGH_F) { return false; @@ -2073,10 +2096,10 @@ Coord3D TerrainLogic::findClosestEdgePoint ( const Coord3D *closestTo ) const getExtent( &mapExtent ); Real distances[4]; - distances[0] = fabs( closestTo->y - mapExtent.lo.y );//top - distances[1] = fabs( closestTo->x - mapExtent.hi.x );//right - distances[2] = fabs( closestTo->y - mapExtent.hi.y );//bottom - distances[3] = fabs( closestTo->x - mapExtent.lo.x );//left + distances[0] = WWMath::Fabs( closestTo->y - mapExtent.lo.y );//top + distances[1] = WWMath::Fabs( closestTo->x - mapExtent.hi.x );//right + distances[2] = WWMath::Fabs( closestTo->y - mapExtent.hi.y );//bottom + distances[3] = WWMath::Fabs( closestTo->x - mapExtent.lo.x );//left Real bestDistance = distances[0]; Int bestDistanceIndex = 0; for( Int lameIndex = 1; lameIndex < 4; lameIndex++ ) @@ -2387,7 +2410,7 @@ void TerrainLogic::setWaterHeight( const WaterHandle *water, Real height, Real d center.z = 0.0f; // irrelevant // the max radius to scan around us is the diagonal of the bounding region - Real maxDist = sqrt( affectedRegion.width() * affectedRegion.width() + + Real maxDist = WWMath::Sqrtf( affectedRegion.width() * affectedRegion.width() + affectedRegion.height() * affectedRegion.height() ); // scan the objects in the area of the water affected @@ -2897,7 +2920,7 @@ void TerrainLogic::createCraterInTerrain(Object *obj) deltaX = ( i * MAP_XY_FACTOR ) - pos->x; deltaY = ( j * MAP_XY_FACTOR ) - pos->y; - Real distance = sqrt( sqr( deltaX ) + sqr( deltaY ) ); + Real distance = WWMath::Sqrtf( sqr( deltaX ) + sqr( deltaY ) ); if ( distance < radius ) //inside circle { @@ -3043,4 +3066,3 @@ void TerrainLogic::loadPostProcess() } } - diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp index 307b5e3a65d..7f471d04af6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BridgeBehavior.cpp @@ -1115,7 +1115,9 @@ void BridgeBehavior::createScaffolding() // to the center area of the bridge // Real tileDistance = leftVector.length(); - Int numObjects = REAL_TO_INT_CEIL( tileDistance / spacing ) + 1; + // TheSuperHackers @bugfix bobtista 10/06/2026 Guard a zero scaffold spacing: tileDistance/0 is Inf + // and REAL_TO_INT_CEIL(Inf) is platform-divergent UB (arm64 vs x86) that desyncs the object count. + Int numObjects = (spacing > 0.0f) ? (REAL_TO_INT_CEIL( tileDistance / spacing ) + 1) : 1; // // given the number of objects that we need to tile across the whole bridge, we will diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp index 9b782240938..1534f3dda88 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp @@ -172,24 +172,24 @@ static Bool calcTrajectory( Real dz = end.z - start.z; // calculating the angle is trivial. - angle = atan2(dy, dx); + angle = WWMath::Atan2f(dy, dx); // calculating the pitch requires a bit more effort. Real horizDistSqr = sqr(dx) + sqr(dy); - Real horizDist = sqrt(horizDistSqr); + Real horizDist = WWMath::Sqrtf(horizDistSqr); // calc the two possible pitches that will cover the given horizontal range. // (this is actually only true if dz==0, but is a good first guess) - Real gravity = fabs(TheGlobalData->m_gravity); + Real gravity = WWMath::Fabs(TheGlobalData->m_gravity); Real gravityTwoDZ = gravity * 2.0f * dz; // let's start by aiming directly for it. we know this isn't right (unless gravity // is zero, which it's not) but is a good starting point... - Real theta = atan2(dz, horizDist); + Real theta = WWMath::Atan2f(dz, horizDist); // if the angle isn't pretty shallow, we can get a better initial guess by using // the code below... const Real SHALLOW_ANGLE = 0.5f * PI / 180.0f; - if (fabs(theta) > SHALLOW_ANGLE) + if (WWMath::Fabs(theta) > SHALLOW_ANGLE) { Real t = horizDist / velocity; Real vz = (dz/t + 0.5f*gravity*t); @@ -290,7 +290,7 @@ static Bool calcTrajectory( #endif vx = velocity*cosPitches[preferred]; - Real actualRange = (vx*(vz + sqrt(root)))/gravity; + Real actualRange = (vx*(vz + WWMath::Sqrtf(root)))/gravity; const Real CLOSE_ENOUGH_RANGE = 5.0f; if (tooClose || (actualRange < horizDist - CLOSE_ENOUGH_RANGE)) { @@ -369,7 +369,7 @@ void DumbProjectileBehavior::projectileFireAtObjectOrPosition( const Object *vic // Some weapons want to scale their start speed to the range Real minRange = detWeap->getMinimumAttackRange(); Real maxRange = detWeap->getUnmodifiedAttackRange(); - Real range = sqrt(ThePartitionManager->getDistanceSquared( projectile, &victimPosToUse, FROM_CENTER_2D ) ); + Real range = WWMath::Sqrtf(ThePartitionManager->getDistanceSquared( projectile, &victimPosToUse, FROM_CENTER_2D ) ); Real rangeRatio = (range - minRange) / (maxRange - minRange); m_flightPathSpeed = (rangeRatio * (weaponSpeed - minWeaponSpeed)) + minWeaponSpeed; } @@ -444,7 +444,10 @@ Bool DumbProjectileBehavior::calcFlightPath(Bool recalcNumSegments) if (recalcNumSegments) { Real flightDistance = flightCurve.getApproximateLength(); - m_flightPathSegments = ceil( flightDistance / m_flightPathSpeed ); + // TheSuperHackers @bugfix bobtista 12/06/2026 Guard the division so a zero flight-path speed + // can't yield Inf and a platform-divergent int cast (lockstep desync). Matches the Inf/NaN + // int-cast guards on the other simulation sites; only affects the degenerate speed==0 case. + m_flightPathSegments = (m_flightPathSpeed > 0.0f) ? (Int)WWMath::Ceil( flightDistance / m_flightPathSpeed ) : 1; } // TheSuperHackers @info The way flight paths are used requires at least two curve points. @@ -611,7 +614,7 @@ UpdateSleepTime DumbProjectileBehavior::update() Real distVictimMovedSqr = sqr(delta.x) + sqr(delta.y) + sqr(delta.z); if (distVictimMovedSqr > 0.1f) { - Real distVictimMoved = sqrtf(distVictimMovedSqr); + Real distVictimMoved = WWMath::Sqrtf(distVictimMovedSqr); if (distVictimMoved > d->m_flightPathAdjustDistPerFrame) distVictimMoved = d->m_flightPathAdjustDistPerFrame; delta.normalize(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp index 03194dc1981..72268f442b8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp @@ -248,11 +248,13 @@ void GenerateMinefieldBehavior::placeMinesAlongLine(const Coord3D& posStart, con Real dx = posEnd.x - posStart.x; Real dy = posEnd.y - posStart.y; - Real len = sqrt(sqr(dx) + sqr(dy)); + Real len = WWMath::Sqrtf(sqr(dx) + sqr(dy)); Real mineRadius = mineTemplate->getTemplateGeometryInfo().getBoundingCircleRadius(); Real mineDiameter = mineRadius * 2.0f; Real mineJitter = mineRadius*d->m_randomJitter; - Int numMines = REAL_TO_INT_CEIL(len / mineDiameter); + // TheSuperHackers @bugfix bobtista 10/06/2026 Guard a zero mine diameter: len/0 is Inf and + // REAL_TO_INT_CEIL(Inf) is platform-divergent UB (arm64 vs x86) that desyncs the spawned count. + Int numMines = (mineDiameter > 0.0f) ? REAL_TO_INT_CEIL(len / mineDiameter) : 1; if (numMines < 1) numMines = 1; Real inc = len/numMines; @@ -312,7 +314,9 @@ void GenerateMinefieldBehavior::placeMinesAroundCircle(const Coord3D& pos, Real Real mineRadius = mineTemplate->getTemplateGeometryInfo().getBoundingCircleRadius(); Real mineDiameter = mineRadius * 2.0f; Real mineJitter = mineRadius*d->m_randomJitter; - Int numMines = REAL_TO_INT_CEIL(circum / mineDiameter); + // TheSuperHackers @bugfix bobtista 10/06/2026 Guard a zero mine diameter: circum/0 is Inf and + // REAL_TO_INT_CEIL(Inf) is platform-divergent UB (arm64 vs x86) that desyncs the spawned count. + Int numMines = (mineDiameter > 0.0f) ? REAL_TO_INT_CEIL(circum / mineDiameter) : 1; if (numMines < 1) numMines = 1; Real angleInc = (2*PI)/numMines; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/MinefieldBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/MinefieldBehavior.cpp index 7bdecb3be0d..d1bfff42420 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/MinefieldBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/MinefieldBehavior.cpp @@ -457,7 +457,10 @@ void MinefieldBehavior::onDamage( DamageInfo *damageInfo ) for (;;) { - Real virtualMinesExpectedF = ((Real)d->m_numVirtualMines * body->getHealth() / body->getMaxHealth()); + // TheSuperHackers @bugfix bobtista 10/06/2026 Guard zero max health: the division would be NaN/Inf + // and REAL_TO_INT of it is platform-divergent UB (arm64 vs x86) that desyncs the mine count. + const Real bodyMaxHealth = body->getMaxHealth(); + Real virtualMinesExpectedF = (bodyMaxHealth > 0.0f) ? ((Real)d->m_numVirtualMines * body->getHealth() / bodyMaxHealth) : 0.0f; Int virtualMinesExpected = damageInfo->in.m_damageType == DAMAGE_HEALING ? REAL_TO_INT_FLOOR(virtualMinesExpectedF) : @@ -570,7 +573,7 @@ void MinefieldBehavior::setScootParms(const Coord3D& start, const Coord3D& end) if (start.z > endOnGround.z) { // figure out how long it will take to fall, and replace scoot time with that - UnsignedInt fallingTime = REAL_TO_INT_CEIL(sqrtf(2.0f * (start.z - endOnGround.z) / fabs(TheGlobalData->m_gravity))); + UnsignedInt fallingTime = REAL_TO_INT_CEIL(WWMath::Sqrtf(2.0f * (start.z - endOnGround.z) / WWMath::Fabs(TheGlobalData->m_gravity))); // we can scoot after we land, but don't want to stop scooting before we land if (scootFromStartingPointTime < fallingTime) scootFromStartingPointTime = fallingTime; @@ -588,8 +591,8 @@ void MinefieldBehavior::setScootParms(const Coord3D& start, const Coord3D& end) Real dx = endOnGround.x - start.x; Real dy = endOnGround.y - start.y; Real dz = endOnGround.z - start.z; - Real dist = sqrt(sqr(dx) + sqr(dy)); - if (dist <= 0.1f && fabs(dz) <= 0.1f) + Real dist = WWMath::Sqrtf(sqr(dx) + sqr(dy)); + if (dist <= 0.1f && WWMath::Fabs(dz) <= 0.1f) { obj->setPosition(&endOnGround); m_scootFramesLeft = 0; @@ -598,7 +601,7 @@ void MinefieldBehavior::setScootParms(const Coord3D& start, const Coord3D& end) { Real t = (Real)scootFromStartingPointTime; Real scootFromStartingPointSpeed = dist / t; - Real accelMag = fabs(2.0f * (dist - scootFromStartingPointSpeed*t)/sqr(t)); + Real accelMag = WWMath::Fabs(2.0f * (dist - scootFromStartingPointSpeed*t)/sqr(t)); Real dxNorm = (dist <= 0.1f) ? 0.0f : (dx / dist); Real dyNorm = (dist <= 0.1f) ? 0.0f : (dy / dist); m_scootVel.x = dxNorm * scootFromStartingPointSpeed; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp index f2942322f9a..9f02dc6c914 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/SlowDeathBehavior.cpp @@ -178,9 +178,18 @@ Int SlowDeathBehavior::getProbabilityModifier( const DamageInfo *damageInfo ) co // Calculating how far past dead we were allows us to pick more spectacular deaths when // severely killed, and more sedate ones when only slightly killed. // eg ( 200 hp max, had 10 left, took 50 damage, 40 overkill, (40/200) * 100 = 20 overkill %) + // TheSuperHackers @bugfix bobtista 10/06/2026 Guard against a zero max health. Dividing by it + // gave 0/0 = NaN, and (Int)NaN is undefined behavior that differs across architectures (arm64 + // yields 0, x86 yields INT_MIN), which flipped the probability total and made one machine roll + // the GameLogic RNG while the other did not - a cross-platform lockstep desync. Int overkillDamage = damageInfo->out.m_actualDamageDealt - damageInfo->out.m_actualDamageClipped; - Real overkillPercent = (float)overkillDamage / (float)getObject()->getBodyModule()->getMaxHealth(); - Int overkillModifier = overkillPercent * getSlowDeathBehaviorModuleData()->m_modifierBonusPerOverkillPercent; + Int overkillModifier = 0; + const Real maxHealth = getObject()->getBodyModule()->getMaxHealth(); + if (maxHealth > 0.0f) + { + Real overkillPercent = (float)overkillDamage / maxHealth; + overkillModifier = overkillPercent * getSlowDeathBehaviorModuleData()->m_modifierBonusPerOverkillPercent; + } return max( getSlowDeathBehaviorModuleData()->m_probabilityModifier + overkillModifier, 1 ); } @@ -299,7 +308,7 @@ void SlowDeathBehavior::beginSlowDeath(const DamageInfo *damageInfo) physics->setExtraBounciness(-1.0); // we don't want this guy to bounce at all physics->setExtraFriction(-3 * SECONDS_PER_LOGICFRAME_REAL); // reduce his ground friction a bit physics->setAllowBouncing(true); - Real orientation = atan2(force.y, force.x); + Real orientation = WWMath::Atan2f(force.y, force.x); physics->setAngles(orientation, 0, 0); obj->getDrawable()->setModelConditionState(MODELCONDITION_EXPLODED_FLAILING); m_flags |= (1<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<getContainedItemsList(); if (items) { - for( ContainedItemsList::const_iterator it = items->begin(); (it != items->end()) && (numKilled < killsToMake); it++ ) + // TheSuperHackers @bugfix bobtista 09/07/2026 Use a temporary copy of the contain list to + // iterate over, because killing an occupant can remove elements from the list while + // iterating over it, which may be unsafe. + const ContainedItemsList itemsCopy(*items); + for( ContainedItemsList::const_iterator it = itemsCopy.begin(); (it != itemsCopy.end()) && (numKilled < killsToMake); it++ ) { Object* thingToKill = *it; if (!thingToKill->isEffectivelyDead() ) @@ -910,7 +914,10 @@ void ActiveBody::setMaxHealth( Real maxHealth, MaxHealthChangeType healthChangeT { //400/500 (80%) + 100 becomes 480/600 (80%) //200/500 (40%) - 100 becomes 160/400 (40%) - Real ratio = m_currentHealth / prevMaxHealth; + // TheSuperHackers @bugfix bobtista 10/06/2026 Guard a zero previous max health: the division + // would be NaN/Inf, corrupting health into NaN (and platform-divergent once int-cast) - a + // lockstep desync. A prior max health of 0 means there is no ratio to preserve, so keep full. + Real ratio = (prevMaxHealth > 0.0f) ? (m_currentHealth / prevMaxHealth) : 1.0f; Real newHealth = maxHealth * ratio; internalChangeHealth( newHealth - m_currentHealth ); break; 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/Contain/ParachuteContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/ParachuteContain.cpp index 1432b5790d9..119e8e45dee 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/ParachuteContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/ParachuteContain.cpp @@ -304,7 +304,7 @@ UpdateSleepTime ParachuteContain::update() if (!m_opened) { // see if we need to open. - if (fabs(m_startZ - parachute->getPosition()->z) >= d->m_paraOpenDist) + if (WWMath::Fabs(m_startZ - parachute->getPosition()->z) >= d->m_paraOpenDist) { m_opened = true; parachute->clearAndSetModelConditionState(MODELCONDITION_FREEFALL, MODELCONDITION_PARACHUTING); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index 57392ff6db5..0ab0a79a2fc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -84,7 +84,7 @@ static Real calcSlowDownDist(Real curSpeed, Real desiredSpeed, Real maxBraking) if (delta <= 0) return 0.0f; - Real dist = (sqr(delta) / fabs(maxBraking)) * 0.5f; + Real dist = (sqr(delta) / WWMath::Fabs(maxBraking)) * 0.5f; // use a little fudge so that things can stop "on a dime" more easily... const Real FUDGE = 1.05f; @@ -95,14 +95,14 @@ static Real calcSlowDownDist(Real curSpeed, Real desiredSpeed, Real maxBraking) inline Bool isNearlyZero(Real a) { const Real TINY_EPSILON = 0.001f; - return fabs(a) < TINY_EPSILON; + return WWMath::Fabs(a) < TINY_EPSILON; } //----------------------------------------------------------------------------- inline Bool isNearly(Real a, Real val) { const Real TINY_EPSILON = 0.001f; - return fabs(a - val) < TINY_EPSILON; + return WWMath::Fabs(a - val) < TINY_EPSILON; } //----------------------------------------------------------------------------- @@ -141,7 +141,7 @@ static Real tryToRotateVector3D( } } - if (fabs(angleBetween) <= maxAngle) + if (WWMath::Fabs(angleBetween) <= maxAngle) { // close enough actualDir = goalDir; @@ -231,9 +231,9 @@ static void calcDirectionToApplyThrust( Bool foundSolution = false; Real distToGoalSqr = vecToGoal.Length2(); - Real distToGoal = sqrt(distToGoalSqr); + Real distToGoal = WWMath::Sqrtf(distToGoalSqr); Real curVelMagSqr = curVel.Length2(); - Real curVelMag = sqrt(curVelMagSqr); + Real curVelMag = WWMath::Sqrtf(curVelMagSqr); Real maxAccelSqr = sqr(maxAccel); Real denom = curVelMagSqr - maxAccelSqr; @@ -569,7 +569,8 @@ void LocomotorStore::reset() Overridable *locoTemp = it->second->deleteOverrides(); if (!locoTemp) { - m_locomotorTemplates.erase(it); + // TheSuperHackers @bugfix bobtista 09/07/2026 Erase without invalidating the loop iterator. + m_locomotorTemplates.erase(it++); } else { @@ -995,7 +996,7 @@ void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalP Real dx = goalPos.x - obj->getPosition()->x; Real dy = goalPos.y - obj->getPosition()->y; Real dz = goalPos.z - obj->getPosition()->z; - Real dist = sqrt(dx*dx+dy*dy); + Real dist = WWMath::Sqrtf(dx*dx+dy*dy); if (dist>onPathDistToGoal) { if (!obj->isKindOf(KINDOF_PROJECTILE) && dist>2*onPathDistToGoal) @@ -1113,7 +1114,7 @@ void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalP // Projectiles never stop braking once they start. jba. obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ) ); // Projectiles cheat in 3 dimensions. - dist = sqrt(dx*dx+dy*dy+dz*dz); + dist = WWMath::Sqrtf(dx*dx+dy*dy+dz*dz); Real vel = physics->getVelocityMagnitude(); if (vel < MIN_VEL) vel = MIN_VEL; @@ -1137,7 +1138,7 @@ void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalP // Normalize. if (dist > 0.001f) { - Real vel = fabs(physics->getForwardSpeed2D()); + Real vel = WWMath::Fabs(physics->getForwardSpeed2D()); if (vel < MIN_VEL) vel = MIN_VEL; if (vel > dist) @@ -1182,7 +1183,7 @@ void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, // Modulate speed according to turning. The more we have to turn, the slower we go // const Real QUAETERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / QUAETERPI; + Real angleCoeff = (Real)WWMath::Fabs( relAngle ) / QUAETERPI; if (angleCoeff > 1.0f) angleCoeff = 1.0; @@ -1253,7 +1254,7 @@ void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, see how much force we really need to achieve our goal speed... */ Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) + if (WWMath::Fabs(accelForce) > WWMath::Fabs(maxForceNeeded)) accelForce = maxForceNeeded; const Coord3D *dir = obj->getUnitDirectionVector2D(); @@ -1288,7 +1289,7 @@ void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, Real angle = obj->getOrientation(); // Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); // Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real desiredAngle = WWMath::Atan2f(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); Real relAngle = stdAngleDiff(desiredAngle, angle); Bool moveBackwards = false; @@ -1305,14 +1306,14 @@ void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, #if 1 if (actualSpeed==0.0f) { setFlag(MOVING_BACKWARDS, false); - if (m_template->m_canMoveBackward && fabs(relAngle) > PI/2) { + if (m_template->m_canMoveBackward && WWMath::Fabs(relAngle) > PI/2) { setFlag(MOVING_BACKWARDS, true ); setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); } } if (getFlag(MOVING_BACKWARDS)) { - if (fabs(relAngle) < PI/2) { + if (WWMath::Fabs(relAngle) < PI/2) { moveBackwards = false; setFlag(MOVING_BACKWARDS, false); } else { @@ -1328,7 +1329,7 @@ void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, #endif const Real SMALL_TURN = PI / 20.0f; - if ((Real)fabs( relAngle ) > SMALL_TURN) + if ((Real)WWMath::Fabs( relAngle ) > SMALL_TURN) { if (desiredSpeed>turnSpeed) { @@ -1353,7 +1354,7 @@ void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Real FIFTEEN_DEGREES = PI / 12.0f; const Real PROJECT_FRAMES = LOGICFRAMES_PER_SECOND/2; // Project out 1/2 second. - if (fabs( relAngle ) > FIFTEEN_DEGREES) + if (WWMath::Fabs( relAngle ) > FIFTEEN_DEGREES) { // If we're turning more than 10 degrees, check & see if we're moving into "impassable territory" Real distance = PROJECT_FRAMES * (goalSpeed+actualSpeed)/2.0f; @@ -1492,7 +1493,7 @@ void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, see how much force we really need to achieve our goal speed... */ Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) + if (WWMath::Fabs(accelForce) > WWMath::Fabs(maxForceNeeded)) accelForce = maxForceNeeded; //DEBUG_LOG(("Braking %d, actualSpeed %f, goalSpeed %f, delta %f, accel %f", getFlag(IS_BRAKING), @@ -1563,7 +1564,7 @@ Bool Locomotor::fixInvalidPosition(Object* obj, PhysicsBehavior *physics) //physics->clearAcceleration(); if (dot<0) { - dot = sqrt(-dot); + dot = WWMath::Sqrtf(-dot); correctionNormalized.x *= dot*physics->getMass(); correctionNormalized.y *= dot*physics->getMass(); physics->applyMotiveForce(&correctionNormalized); @@ -1627,7 +1628,7 @@ void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, c Real angle = obj->getOrientation(); // Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); // Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real desiredAngle = WWMath::Atan2f(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); if (m_template->m_wanderWidthFactor != 0.0f) { Real angleLimit = PI/8 * m_template->m_wanderWidthFactor; @@ -1653,7 +1654,7 @@ void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, c // Modulate speed according to turning. The more we have to turn, the slower we go // const Real QUARTERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); + Real angleCoeff = (Real)WWMath::Fabs( relAngle ) / (QUARTERPI); if (angleCoeff > 1.0f) angleCoeff = 1.0; @@ -1683,7 +1684,7 @@ void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, c see how much force we really need to achieve our goal speed... */ Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) + if (WWMath::Fabs(accelForce) > WWMath::Fabs(maxForceNeeded)) accelForce = maxForceNeeded; const Coord3D *dir = obj->getUnitDirectionVector2D(); @@ -1725,7 +1726,7 @@ void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, if (dz*dz > sqr(PATHFIND_CELL_SIZE_F)) { setFlag(CLIMBING, true); } - if (fabs(dz)<1) { + if (WWMath::Fabs(dz)<1) { setFlag(CLIMBING, false); } @@ -1745,7 +1746,7 @@ void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, moveBackwards = true; } - Real groundSlope = fabs(delta.z - pos.z); + Real groundSlope = WWMath::Fabs(delta.z - pos.z); if (groundSlope<1.0f) groundSlope = 1.0f; if (groundSlope>1.0f) { @@ -1760,7 +1761,7 @@ void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, Real angle = obj->getOrientation(); // Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); // Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real desiredAngle = WWMath::Atan2f(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); Real relAngle = stdAngleDiff(desiredAngle, angle); if (moveBackwards) { @@ -1774,7 +1775,7 @@ void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, // Modulate speed according to turning. The more we have to turn, the slower we go // const Real QUARTERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); + Real angleCoeff = (Real)WWMath::Fabs( relAngle ) / (QUARTERPI); if (angleCoeff > 1.0f) angleCoeff = 1.0; @@ -1816,7 +1817,7 @@ void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, see how much force we really need to achieve our goal speed... */ Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) + if (WWMath::Fabs(accelForce) > WWMath::Fabs(maxForceNeeded)) accelForce = maxForceNeeded; const Coord3D *dir = obj->getUnitDirectionVector2D(); @@ -1843,7 +1844,7 @@ void Locomotor::moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, Real dx = goalPos.x - pos->x; Real dy = goalPos.y - pos->y; Real dz = goalPos.z - pos->z; - if (fabs(dz) > m_circleThresh) + if (WWMath::Fabs(dz) > m_circleThresh) { // aim for the spot on the opposite side of the circle. @@ -1851,7 +1852,7 @@ void Locomotor::moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, Real angleTowardPos = (isNearlyZero(dx) && isNearlyZero(dy)) ? obj->getOrientation() : - atan2(dy, dx); + WWMath::Atan2f(dy, dx); Real aimDir = (PI - PI/8); angleTowardPos += aimDir; @@ -1940,7 +1941,7 @@ void Locomotor::moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, // so we tend to "level out" at that height. we don't use this till // below, but go ahead and calc it now... Real MAX_VERTICAL_DAMP_RANGE = m_preferredHeight * 0.5; - delta = fabs(delta); + delta = WWMath::Fabs(delta); if (delta > MAX_VERTICAL_DAMP_RANGE) delta = MAX_VERTICAL_DAMP_RANGE; zDirDamping = 1.0f - (delta / MAX_VERTICAL_DAMP_RANGE); @@ -2057,7 +2058,7 @@ Real Locomotor::calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real cu // see how far we need to slow to dead stop, given max braking Real desiredAccel; const Real TINY_ACCEL = 0.001f; - if (fabs(maxAccel) > TINY_ACCEL) + if (WWMath::Fabs(maxAccel) > TINY_ACCEL) { Real deltaZ = preferredHeight - curZ; // calc how far it will take for us to go from cur speed to zero speed, at max accel. @@ -2065,14 +2066,14 @@ Real Locomotor::calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real cu // in theory, the above is the correct calculation, but in practice, // doesn't work in some situations (eg, opening of USA01 map). Why, I dunno. // But for now I have gone back to the old, looks-incorrect-to-me-but-works calc. (srj) - Real brakeDist = (sqr(curVelZ) / fabs(maxAccel)); - if (fabs(brakeDist) > fabs(deltaZ)) + Real brakeDist = (sqr(curVelZ) / WWMath::Fabs(maxAccel)); + if (WWMath::Fabs(brakeDist) > WWMath::Fabs(deltaZ)) { // if the dist-to-accel (or dist-to-brake) is further than the dist-to-go, // use the max accel. desiredAccel = maxAccel; } - else if (fabs(curVelZ) > m_template->m_speedLimitZ) + else if (WWMath::Fabs(curVelZ) > m_template->m_speedLimitZ) { // or, if we're going too fast, limit it here. desiredAccel = m_template->m_speedLimitZ - curVelZ; @@ -2146,8 +2147,8 @@ PhysicsTurningType Locomotor::rotateObjAroundLocoPivot(Object* obj, const Coord3 Real dx =goalPos.x - turnPos.x; Real dy = goalPos.y - turnPos.y; // If we are very close to the goal, we twitch due to rounding error. So just return. jba. - if (fabs(dx)<0.1f && fabs(dy)<0.1f) return TURN_NONE; - Real desiredAngle = atan2(dy, dx); + if (WWMath::Fabs(dx)<0.1f && WWMath::Fabs(dy)<0.1f) return TURN_NONE; + Real desiredAngle = WWMath::Atan2f(dy, dx); Real amount = stdAngleDiff(desiredAngle, angle); if (relAngle) *relAngle = amount; if (amount>maxTurnRate) { @@ -2169,7 +2170,7 @@ PhysicsTurningType Locomotor::rotateObjAroundLocoPivot(Object* obj, const Coord3 // so, the thing is, we want to rotate ourselves so that our *center* is rotated // by the given amount, but the rotation must be around turnPos. so do a little // back-calculation. - Real angleDesiredForTurnPos = atan2(desiredPos.y - turnPos.y, desiredPos.x - turnPos.x); + Real angleDesiredForTurnPos = WWMath::Atan2f(desiredPos.y - turnPos.y, desiredPos.x - turnPos.x); amount = angleDesiredForTurnPos - angle; #endif /// @todo srj -- there's probably a more efficient & more direct way to do this. find it. @@ -2185,7 +2186,7 @@ PhysicsTurningType Locomotor::rotateObjAroundLocoPivot(Object* obj, const Coord3 } else { - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real desiredAngle = WWMath::Atan2f(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); Real amount = stdAngleDiff(desiredAngle, angle); if (relAngle) *relAngle = amount; if (amount>maxTurnRate) { @@ -2363,8 +2364,8 @@ void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, //fabs(goalPos.y - pos->y),fabs(goalPos.x - pos->x), //fabs(goalPos.y - pos->y)/goalSpeed,fabs(goalPos.x - pos->x)/goalSpeed)); if (getFlag(ULTRA_ACCURATE) && - fabs(goalPos.y - pos->y) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor && - fabs(goalPos.x - pos->x) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor) + WWMath::Fabs(goalPos.y - pos->y) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor && + WWMath::Fabs(goalPos.x - pos->x) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor) { // don't turn, just slide in the right direction physics->setTurning(TURN_NONE); @@ -2403,7 +2404,7 @@ void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, see how much force we really need to achieve our goal speed... */ Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) + if (WWMath::Fabs(accelForce) > WWMath::Fabs(maxForceNeeded)) accelForce = maxForceNeeded; Coord3D force; @@ -2519,7 +2520,7 @@ void Locomotor::maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physi Real angleTowardMaintainPos = (isNearlyZero(dx) && isNearlyZero(dy)) ? obj->getOrientation() : - atan2(dy, dx); + WWMath::Atan2f(dy, dx); Real aimDir = (PI - PI/8); if (turnRadius < 0) @@ -2553,7 +2554,7 @@ void Locomotor::maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physi // Real minSpeed = max( 1.0E-10f, m_template->m_minSpeed ); Real speedDelta = minSpeed - actualSpeed; - if (fabs(speedDelta) > minSpeed) + if (WWMath::Fabs(speedDelta) > minSpeed) { Real mass = physics->getMass(); Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); @@ -2564,7 +2565,7 @@ void Locomotor::maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physi see how much force we really need to achieve our goal speed... */ Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) + if (WWMath::Fabs(accelForce) > WWMath::Fabs(maxForceNeeded)) accelForce = maxForceNeeded; const Coord3D *dir = obj->getUnitDirectionVector2D(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 8ddeb128638..ffc01cbe23b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -1546,9 +1546,16 @@ void Object::fireCurrentWeapon(Object *target) if (weapon && (weapon->getStatus() == READY_TO_FIRE)) { Bool reloaded = weapon->fireWeapon(this, target); + // TheSuperHackers @bugfix bobtista 08/07/2026 The shot can rebuild the weapon set (e.g. a veterancy + // promotion earned by the kill), replacing the fired weapon. Track the shot with the replacement. + Weapon* firedWeapon = getWeaponInWeaponSlot(weapon->getWeaponSlot()); + if (firedWeapon == nullptr) + { + firedWeapon = weapon; + } DEBUG_ASSERTCRASH(m_firingTracker, ("hey, we are firing but have no firing tracker. this is wrong.")); if (m_firingTracker) - m_firingTracker->shotFired(weapon, target->getID()); + m_firingTracker->shotFired(firedWeapon, target->getID()); if (reloaded) releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks. @@ -1568,9 +1575,16 @@ void Object::fireCurrentWeapon(const Coord3D* pos) if (weapon && (weapon->getStatus() == READY_TO_FIRE)) { Bool reloaded = weapon->fireWeapon(this, pos); + // TheSuperHackers @bugfix bobtista 08/07/2026 The shot can rebuild the weapon set (e.g. a veterancy + // promotion earned by the kill), replacing the fired weapon. Track the shot with the replacement. + Weapon* firedWeapon = getWeaponInWeaponSlot(weapon->getWeaponSlot()); + if (firedWeapon == nullptr) + { + firedWeapon = weapon; + } DEBUG_ASSERTCRASH(m_firingTracker, ("hey, we are firing but have no firing tracker. this is wrong.")); if (m_firingTracker) - m_firingTracker->shotFired(weapon, INVALID_ID); + m_firingTracker->shotFired(firedWeapon, INVALID_ID); if (reloaded) releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks. @@ -1807,13 +1821,13 @@ inline Bool isPosDifferent(const Coord3D* a, const Coord3D* b) // so we must put in some cleverness... const Real THRESH = 0.01f; - if (fabs(a->x - b->x) > THRESH) + if (WWMath::Fabs(a->x - b->x) > THRESH) return true; - if (fabs(a->y - b->y) > THRESH) + if (WWMath::Fabs(a->y - b->y) > THRESH) return true; - if (fabs(a->z - b->z) > THRESH) + if (WWMath::Fabs(a->z - b->z) > THRESH) return true; return false; @@ -1829,7 +1843,7 @@ inline Bool isAngleDifferent(Real a, Real b) const Real THRESH = 0.01f; // in radians, this is approx 1/2 degree. - if (fabs(a - b) > THRESH) + if (WWMath::Fabs(a - b) > THRESH) return true; return false; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp index 9d9ffce18b1..4523d8ec8a7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp @@ -290,7 +290,7 @@ class DeliverPayloadNugget : public ObjectCreationNugget Real dy = primary->y - secondary->y; //Calc length - Real length = sqrt( dx*dx + dy*dy ); + Real length = WWMath::Sqrtf( dx*dx + dy*dy ); //Normalize length dx /= length; @@ -357,7 +357,7 @@ class DeliverPayloadNugget : public ObjectCreationNugget } - Real orient = atan2( moveToPos.y - startPos.y, moveToPos.x - startPos.x); + Real orient = WWMath::Atan2f( moveToPos.y - startPos.y, moveToPos.x - startPos.x); if( m_data.m_distToTarget > 0 ) { const Real SLOP = 1.5f; @@ -1108,7 +1108,7 @@ class GenericObjectCreationNugget : public ObjectCreationNugget objUp->applyForce(&force); if (m_orientInForceDirection) - orientation = atan2(force.y, force.x); + orientation = WWMath::Atan2f(force.y, force.x); } } @@ -1196,7 +1196,7 @@ class GenericObjectCreationNugget : public ObjectCreationNugget objUp->applyForce(&force); if (m_orientInForceDirection) { - orientation = atan2(force.y, force.x); + orientation = WWMath::Atan2f(force.y, force.x); } DUMPREAL(orientation); objUp->setAngles(orientation, 0, 0); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index a5f0a55128c..d3e2c93b90f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -80,6 +80,11 @@ #include "GameClient/Line2D.h" #include "GameClient/ControlBar.h" +#include +#include +#include +#include + #ifdef RTS_DEBUG //#include "GameClient/InGameUI.h" // for debugHints #endif @@ -415,13 +420,13 @@ static void testRotatedPointsAgainstRect( Real pty = pts->y - a->position.y; // inverse-rotate it to the right coord system - Real ptx_new = (Real)fabs(ptx*c - pty*s); - Real pty_new = (Real)fabs(ptx*s + pty*c); + Real ptx_new = (Real)WWMath::Fabs(ptx*c - pty*s); + Real pty_new = (Real)WWMath::Fabs(ptx*s + pty*c); #ifdef INTENSE_DEBUG Real mag_a = sqr(ptx)+sqr(pty); Real mag_b = sqr(ptx_new)+sqr(pty_new); - DEBUG_ASSERTCRASH(fabs(mag_a - mag_b) <= 1.0, ("hmm, unlikely")); + DEBUG_ASSERTCRASH(WWMath::Fabs(mag_a - mag_b) <= 1.0, ("hmm, unlikely")); #endif if (ptx_new <= major && pty_new <= minor) @@ -621,7 +626,7 @@ inline Bool z_collideTest_Sphere_Nonsphere(CollideTestProc xyproc, const Collide // find the radius of the slice of the sphere that is at b_bot CollideInfo amod = *a; amod.position.z = b_bot; - amod.geom.setMajorRadius((Real)sqrtf(sqr(a->geom.getMajorRadius()) - sqr(b_bot - a->position.z))); + amod.geom.setMajorRadius((Real)WWMath::Sqrtf(sqr(a->geom.getMajorRadius()) - sqr(b_bot - a->position.z))); if (xyproc(&amod, b, cinfo)) { // if you want to have 'end' collisions, you should add something like: @@ -639,7 +644,7 @@ inline Bool z_collideTest_Sphere_Nonsphere(CollideTestProc xyproc, const Collide { CollideInfo amod = *a; amod.position.z = b_top; - amod.geom.setMajorRadius((Real)sqrtf(sqr(a->geom.getMajorRadius()) - sqr(a->position.z - b_top))); + amod.geom.setMajorRadius((Real)WWMath::Sqrtf(sqr(a->geom.getMajorRadius()) - sqr(a->position.z - b_top))); if (xyproc(&amod, b, cinfo)) { // if you want to have 'end' collisions, you should add something like: @@ -827,7 +832,7 @@ static Bool distCalcProc_BoundaryAndBoundary_2D( if (totalRad > 0.0f) { - Real actualDist = sqrtf(actualDistSqr); + Real actualDist = WWMath::Sqrtf(actualDistSqr); Real shrunkenDist = actualDist - totalRad; if (shrunkenDist <= 0.0f) { @@ -915,7 +920,7 @@ static Bool distCalcProc_BoundaryAndBoundary_3D( Real totalRad = (geomA?geomA->getBoundingSphereRadius():0) + (geomB?geomB->getBoundingSphereRadius():0); if (totalRad > 0.0f) { - Real actualDist = sqrtf(actualDistSqr); + Real actualDist = WWMath::Sqrtf(actualDistSqr); Real shrunkenDist = actualDist - totalRad; if (shrunkenDist <= 0.0f) { @@ -2226,7 +2231,7 @@ Int PartitionData::calcMaxCoiForShape(GeometryType geom, Real majorRadius, Real } case GEOMETRY_BOX: { - Real diagonal = (Real)(sqrtf(majorRadius*majorRadius + minorRadius*minorRadius)); + Real diagonal = (Real)(WWMath::Sqrtf(majorRadius*majorRadius + minorRadius*minorRadius)); Int cells = ThePartitionManager->worldToCellDist(diagonal*2) + 1; result = cells * cells; break; @@ -2643,7 +2648,7 @@ static void calcHeights(const Region3D& world, Real cellSize, Int x, Int y, Real Real xbase = world.lo.x + (x * cellSize); Real ybase = world.lo.y + (y * cellSize); const Real ROUGH_STEP_SIZE = MAP_XY_FACTOR; // no point in stepping smaller than grid scale - Real numSteps = ceilf(cellSize / ROUGH_STEP_SIZE); + Real numSteps = WWMath::Ceilf(cellSize / ROUGH_STEP_SIZE); Real step = cellSize / numSteps; loZ = HUGE_DIST; // huge positive hiZ = -HUGE_DIST; // huge negative @@ -3202,22 +3207,24 @@ Int PartitionManager::calcMinRadius(const ICoord2D& cur) so it really shouldn't matter... (I hope) */ - double minDistSqr = 1e12; // double, not real + // TheSuperHackers @bugfix bobtista 10/06/2026 Single precision, not double: on 32-bit x86 the x87 + // FPU runs at _PC_24, so double intermediates here diverge from the arm64 build's full-double math + // and can cross the REAL_TO_INT_CEIL boundary, corrupting the whole getClosestObjects radius table + // and breaking cross-platform lockstep. (The result is narrowed to float at Sqrtf regardless.) + Real minDistSqr = 1e12f; for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { - // double, not real - double dx = centerPos[i].x - otherPos[j].x; - double dy = centerPos[i].y - otherPos[j].y; - double curDistSqr = dx*dx + dy*dy; + Real dx = centerPos[i].x - otherPos[j].x; + Real dy = centerPos[i].y - otherPos[j].y; + Real curDistSqr = dx*dx + dy*dy; if (minDistSqr > curDistSqr) minDistSqr = curDistSqr; } } - // double, not real - double dist = sqrtf(minDistSqr); + Real dist = WWMath::Sqrtf(minDistSqr); Int minRadius = REAL_TO_INT_CEIL( dist / m_cellSize ); return minRadius; @@ -3232,10 +3239,11 @@ void PartitionManager::calcRadiusVec() Int cx = getCellCountX(); Int cy = getCellCountY(); - // double, not real - double dx = (double)cx * (double)cellSize; - double dy = (double)cy * (double)cellSize; - double maxPossibleDist = sqrt(dx*dx + dy*dy); + // TheSuperHackers @bugfix bobtista 10/06/2026 Single precision, not double, for cross-platform + // determinism (x87 _PC_24 vs arm64 full-double would diverge at the REAL_TO_INT_CEIL boundary). + Real dx = (Real)cx * cellSize; + Real dy = (Real)cy * cellSize; + Real maxPossibleDist = WWMath::Sqrtf(dx*dx + dy*dy); m_maxGcoRadius = REAL_TO_INT_CEIL(maxPossibleDist / cellSize); @@ -3505,7 +3513,7 @@ Object *PartitionManager::getClosestObjects( } if (closestDistArg) { - *closestDistArg = (Real)sqrtf(closestDistSqr); + *closestDistArg = (Real)WWMath::Sqrtf(closestDistSqr); } #ifdef RTS_DEBUG @@ -3632,7 +3640,7 @@ Real PartitionManager::getRelativeAngle2D( const Object *obj, const Coord3D *pos v.y = pos->y - objPos.y; v.z = 0.0f; - Real dist = (Real)sqrtf(sqr(v.x) + sqr(v.y)); + Real dist = (Real)WWMath::Sqrtf(sqr(v.x) + sqr(v.y)); // normalize if (dist == 0.0f) @@ -3810,7 +3818,7 @@ Bool PartitionManager::tryPosition( const Coord3D *center, pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ); } - if (fabs(pos.z - center->z) > options->maxZDelta) + if (WWMath::Fabs(pos.z - center->z) > options->maxZDelta) return FALSE; // @@ -4134,6 +4142,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); @@ -4566,7 +4580,7 @@ Int PartitionManager::iterateCellsBreadthFirst(const Coord3D *pos, CellBreadthFi //----------------------------------------------------------------------------- static Real calcDist2D(Real x1, Real y1, Real x2, Real y2) { - return sqrtf(sqr(x1-x2) + sqr(y1-y2)); + return WWMath::Sqrtf(sqr(x1-x2) + sqr(y1-y2)); } //----------------------------------------------------------------------------- @@ -4741,6 +4755,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 +4775,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 { @@ -5678,7 +5724,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 +5741,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 +5758,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 +5775,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) @@ -5757,7 +5803,7 @@ void hLineAddThreat(Int x1, Int x2, Int y, void *threatValueParms) if (x < 0 || x >= ThePartitionManager->m_cellCountX) continue; - distance = sqrt( pow(x - parms->xCenter, 2) + pow(y - parms->yCenter, 2) ); + distance = WWMath::Sqrtf( WWMath::Sqrf(x - parms->xCenter) + WWMath::Sqrf(y - parms->yCenter) ); mulVal = 1 - distance / parms->radius; if (mulVal < 0.0f) mulVal = 0.0f; @@ -5785,7 +5831,7 @@ void hLineRemoveThreat(Int x1, Int x2, Int y, void *threatValueParms) if (x < 0 || x >= ThePartitionManager->m_cellCountX) continue; - distance = sqrt( pow(x - parms->xCenter, 2) + pow(y - parms->yCenter, 2) ); + distance = WWMath::Sqrtf( WWMath::Sqrf(x - parms->xCenter) + WWMath::Sqrf(y - parms->yCenter) ); mulVal = 1 - distance / parms->radius; if (mulVal < 0.0f) mulVal = 0.0f; @@ -5813,7 +5859,7 @@ void hLineAddValue(Int x1, Int x2, Int y, void *threatValueParms) if (x < 0 || x >= ThePartitionManager->m_cellCountX) continue; - distance = sqrt( pow(x - parms->xCenter, 2) + pow(y - parms->yCenter, 2) ); + distance = WWMath::Sqrtf( WWMath::Sqrf(x - parms->xCenter) + WWMath::Sqrf(y - parms->yCenter) ); mulVal = 1 - distance / parms->radius; if (mulVal < 0.0f) mulVal = 0.0f; @@ -5841,7 +5887,7 @@ void hLineRemoveValue(Int x1, Int x2, Int y, void *threatValueParms) if (x < 0 || x >= ThePartitionManager->m_cellCountX) continue; - distance = sqrt( pow(x - parms->xCenter, 2) + pow(y - parms->yCenter, 2) ); + distance = WWMath::Sqrtf( WWMath::Sqrf(x - parms->xCenter) + WWMath::Sqrf(y - parms->yCenter) ); mulVal = 1 - distance / parms->radius; if (mulVal < 0.0f) mulVal = 0.0f; @@ -5925,4 +5971,3 @@ SightingInfo::~SightingInfo() { } - diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index 2eed5b09878..af09d6df57d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -1296,8 +1296,8 @@ Bool AIUpdateInterface::blockedBy(Object *other) // If we are near our final goal, don't get stuck. if (goalCell.x>0 && goalCell.y>0) { - Real dx = fabs(goalPos.x-pos.x); - Real dy = fabs(goalPos.y-pos.y); + Real dx = WWMath::Fabs(goalPos.x-pos.x); + Real dy = WWMath::Fabs(goalPos.y-pos.y); if (dxgetRelativeAngle2D( getObject(), &info.posOnPath ); } - if (fabs(deltaAngle)>PI/30) + if (WWMath::Fabs(deltaAngle)>PI/30) { return TRUE; } @@ -2272,7 +2272,7 @@ UpdateSleepTime AIUpdateInterface::doLocomotor() } else { - Real dist = sqrtf(dSqr); + Real dist = WWMath::Sqrtf(dSqr); if (dist<1) dist = 1; pos.x += 2*PATHFIND_CELL_SIZE_F*dx/(dist*LOGICFRAMES_PER_SECOND); pos.y += 2*PATHFIND_CELL_SIZE_F*dy/(dist*LOGICFRAMES_PER_SECOND); @@ -2473,7 +2473,7 @@ Real AIUpdateInterface::getLocomotorDistanceToGoal() dest = m_path->getLastNode()->getPosition(); } Real distance = ThePartitionManager->getDistanceSquared( me, dest, FROM_CENTER_3D ); - return sqrt( distance );// Other paths return dots of normalized vectors, so one sqrt ain't so bad + return WWMath::Sqrtf( distance );// Other paths return dots of normalized vectors, so one sqrt ain't so bad } else { @@ -2505,7 +2505,7 @@ Real AIUpdateInterface::getLocomotorDistanceToGoal() { if (sqr(dist) > distSqr) { - return sqrt(distSqr); + return WWMath::Sqrtf(distSqr); } else { @@ -2514,7 +2514,7 @@ Real AIUpdateInterface::getLocomotorDistanceToGoal() } if (distropeLen < it->ropeLenMax) { - it->ropeSpeed += fabs(TheGlobalData->m_gravity); + it->ropeSpeed += WWMath::Fabs(TheGlobalData->m_gravity); if (it->ropeSpeed > d->m_ropeDropSpeed) it->ropeSpeed = d->m_ropeDropSpeed; it->ropeLen += it->ropeSpeed; @@ -762,7 +762,7 @@ class ChinookMoveToBldgState : public AIMoveToState StateReturnType status = AIMoveToState::update(); const Real THRESH = 3.0f; - if (status != STATE_CONTINUE && fabs(obj->getPosition()->z - m_destZ) > THRESH) + if (status != STATE_CONTINUE && WWMath::Fabs(obj->getPosition()->z - m_destZ) > THRESH) status = STATE_CONTINUE; return status; @@ -891,7 +891,7 @@ ChinookAIUpdateModuleData::ChinookAIUpdateModuleData() m_minDropHeight = 30.0f; m_ropeFinalHeight = 0.0f; m_ropeDropSpeed = 1e10f; // um, fast. - m_rappelSpeed = fabs(TheGlobalData->m_gravity) * LOGICFRAMES_PER_SECOND * 0.5f; + m_rappelSpeed = WWMath::Fabs(TheGlobalData->m_gravity) * LOGICFRAMES_PER_SECOND * 0.5f; m_ropeWobbleLen = 10.0f; m_ropeWobbleAmp = 1.0f; m_ropeWobbleRate = 0.1f; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp index ca618b60a80..bc42f542096 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp @@ -202,8 +202,8 @@ UpdateSleepTime DeliverPayloadAIUpdate::update() { //Calc strafe ratio Real startDiveDistance = getData()->m_diveStartDistance; - Real endDiveDistance = sqrt( endDiveDistanceSquared ); - Real currentDistance = sqrt( currentDistanceSquared ); + Real endDiveDistance = WWMath::Sqrtf( endDiveDistanceSquared ); + Real currentDistance = WWMath::Sqrtf( currentDistanceSquared ); Real diveRatio = (startDiveDistance - currentDistance) / (startDiveDistance - endDiveDistance); @@ -347,7 +347,12 @@ Real DeliverPayloadAIUpdate::calcMinTurnRadius(Real* timeToTravelThatDist) const Real minTurnRadius = (maxTurnRate > 0.0f) ? (maxSpeed / maxTurnRate) : 999999.0f; if (timeToTravelThatDist) - *timeToTravelThatDist = minTurnRadius / maxSpeed; + { + // TheSuperHackers @bugfix bobtista 10/06/2026 A zero max speed (e.g. EMP/subdual sets it to 0) + // makes this Inf, and the caller feeds it to REAL_TO_INT_CEIL - platform-divergent UB that + // desyncs the re-entry frame. Fall back to a large finite time when stopped. + *timeToTravelThatDist = (maxSpeed > 0.0f) ? (minTurnRadius / maxSpeed) : 999999.0f; + } return minTurnRadius; } @@ -1108,7 +1113,7 @@ StateReturnType RecoverFromOffMapState::update() // Success if we should try aga enterCoord.z = owner->getPosition()->z; owner->setPosition(&enterCoord); - Real enterAngle = atan2(ai->getMoveToPos()->y - enterCoord.y, ai->getMoveToPos()->x - enterCoord.x); + Real enterAngle = WWMath::Atan2f(ai->getMoveToPos()->y - enterCoord.y, ai->getMoveToPos()->x - enterCoord.x); owner->setOrientation(enterAngle); PhysicsBehavior* physics = owner->getPhysics(); @@ -1148,7 +1153,7 @@ StateReturnType HeadOffMapState::onEnter() // Give move order out of town Region3D terrainExtent; TheTerrainLogic->getExtent( &terrainExtent ); const Real FUDGE = 1.2f; - Real HUGE_DIST = FUDGE * sqrt(sqr(terrainExtent.hi.x - terrainExtent.lo.x) + sqr(terrainExtent.hi.y - terrainExtent.lo.y)); + Real HUGE_DIST = FUDGE * WWMath::Sqrtf(sqr(terrainExtent.hi.x - terrainExtent.lo.x) + sqr(terrainExtent.hi.y - terrainExtent.lo.y)); exitCoord.x += dir->x * HUGE_DIST; exitCoord.y += dir->y * HUGE_DIST; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp index 2ccaaa00d38..c9279ab65d7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DozerAIUpdate.cpp @@ -530,6 +530,11 @@ StateReturnType DozerActionDoActionState::update() // increase the construction percent of the goal object Int framesToBuild = goalObject->getTemplate()->calcTimeToBuild( dozer->getControllingPlayer() ); + // TheSuperHackers @bugfix bobtista 10/06/2026 Guard a zero build time: dividing by it gives + // Inf, which corrupts construction percent and the int-cast health change with platform- + // divergent UB (arm64 vs x86) - a lockstep desync. Clamp to at least one frame. + if( framesToBuild < 1 ) + framesToBuild = 1; Real percentProgressThisFrame = 100.0f / framesToBuild; goalObject->setConstructionPercent( goalObject->getConstructionPercent() + percentProgressThisFrame ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp index 7bf9abfdd6a..a8700b842ad 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp @@ -516,8 +516,8 @@ class JetOrHeliTaxiState : public AIMoveOutOfTheWayState Coord3D intermedPt; Bool intermed = false; - Real orient = atan2(ppinfo.runwayPrep.y - ppinfo.parkingSpace.y, ppinfo.runwayPrep.x - ppinfo.parkingSpace.x); - if (fabs(stdAngleDiff(orient, ppinfo.parkingOrientation)) > PI/128) + Real orient = WWMath::Atan2f(ppinfo.runwayPrep.y - ppinfo.parkingSpace.y, ppinfo.runwayPrep.x - ppinfo.parkingSpace.x); + if (WWMath::Fabs(stdAngleDiff(orient, ppinfo.parkingOrientation)) > PI/128) { intermedPt.z = (ppinfo.parkingSpace.z + ppinfo.runwayPrep.z) * 0.5f; intermed = intersectInfiniteLine2D( @@ -1071,7 +1071,7 @@ class HeliTakeoffOrLandingState : public State } else { - Real dist = sqrtf(dSqr); + Real dist = WWMath::Sqrtf(dSqr); if (dist<1) dist = 1; pos.x += PATHFIND_CELL_SIZE_F*dx/(dist*LOGICFRAMES_PER_SECOND); pos.y += PATHFIND_CELL_SIZE_F*dy/(dist*LOGICFRAMES_PER_SECOND); @@ -1199,7 +1199,7 @@ class JetOrHeliParkOrientState : public State return STATE_FAILURE; const Real THRESH = 0.001f; - if (fabs(stdAngleDiff(jet->getOrientation(), ppinfo.parkingOrientation)) <= THRESH) + if (WWMath::Fabs(stdAngleDiff(jet->getOrientation(), ppinfo.parkingOrientation)) <= THRESH) return STATE_SUCCESS; // magically position it correctly. @@ -2297,7 +2297,7 @@ void JetAIUpdate::positionLockon() Real dx = getObject()->getPosition()->x - pos.x; Real dy = getObject()->getPosition()->y - pos.y; if (dx || dy) - m_lockonDrawable->setOrientation(atan2(dy, dx)); + m_lockonDrawable->setOrientation(WWMath::Atan2f(dy, dx)); // the Gaussian sum, to avoid keeping a running total: // diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp index 78b44dc99ca..fa71ab53b83 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp @@ -232,7 +232,7 @@ void MissileAIUpdate::projectileFireAtObjectOrPosition( const Object *victim, co Real deltaZ = victimPos->z - obj->getPosition()->z; Real dx = victimPos->x - obj->getPosition()->x; Real dy = victimPos->y - obj->getPosition()->y; - Real xyDist = sqrt(sqr(dx)+sqr(dy)); + Real xyDist = WWMath::Sqrtf(sqr(dx)+sqr(dy)); if (xyDist<1) xyDist = 1; Real zFactor = 0; if (deltaZ>0) { @@ -649,7 +649,7 @@ UpdateSleepTime MissileAIUpdate::update() Coord3D newPos = *getObject()->getPosition(); if (m_noTurnDistLeft > 0.0f && m_state >= IGNITION) { - Real distThisTurn = sqrtf(sqr(newPos.x-m_prevPos.x) + sqr(newPos.y-m_prevPos.y) + sqr(newPos.z-m_prevPos.z)); + Real distThisTurn = WWMath::Sqrtf(sqr(newPos.x-m_prevPos.x) + sqr(newPos.y-m_prevPos.y) + sqr(newPos.z-m_prevPos.z)); m_noTurnDistLeft -= distThisTurn; m_prevPos = newPos; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/POWTruckAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/POWTruckAIUpdate.cpp index cfe716f8440..80eac2297bb 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/POWTruckAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/POWTruckAIUpdate.cpp @@ -480,7 +480,7 @@ void POWTruckAIUpdate::updateCollectingTarget() { // are we close enough to tell them to start moving to us - Real distSq = pow( us->getGeometryInfo().getBoundingSphereRadius() * 2.0f, 2 ); + Real distSq = WWMath::Sqrf( us->getGeometryInfo().getBoundingSphereRadius() * 2.0f ); if( ThePartitionManager->getDistanceSquared( us, target, FROM_CENTER_2D ) <= distSq ) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/RailroadGuideAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/RailroadGuideAIUpdate.cpp index 1ece8505529..02504350ae0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/RailroadGuideAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/RailroadGuideAIUpdate.cpp @@ -320,7 +320,7 @@ void RailroadBehavior::onCollide( Object *other, const Coord3D *loc, const Coord m_whistleSound.setPlayingHandle(TheAudio->addAudioEvent( &m_whistleSound )); - Real dist = (Real)sqrtf( dlt.x*dlt.x + dlt.y*dlt.y + dlt.z*dlt.z); + Real dist = (Real)WWMath::Sqrtf( dlt.x*dlt.x + dlt.y*dlt.y + dlt.z*dlt.z); Real usRadius = obj->getGeometryInfo().getMajorRadius(); Real themRadius = other->getGeometryInfo().getMajorRadius(); Real overlap = ((usRadius + themRadius) - dist) + 1;// the plus 1 makes them go just outside of me. @@ -467,8 +467,8 @@ void RailroadBehavior::playImpactSound(Object *victim, const Coord3D *impactPosi impact.setPosition(impactPosition); if ( theirPhys ) { - vel += fabs(theirPhys->getVelocity()->length()); - mass += fabs(theirPhys->getMass()); + vel += WWMath::Fabs(theirPhys->getVelocity()->length()); + mass += WWMath::Fabs(theirPhys->getMass()); vel /= 2; mass /= 2;//average of him and me @@ -700,7 +700,7 @@ UpdateSleepTime RailroadBehavior::update() if ( m_conductorState == APPLY_BRAKES ) { conductorPullInfo.speed *= modData->m_braking; - if (fabs(conductorPullInfo.speed) < 0.1f) + if (WWMath::Fabs(conductorPullInfo.speed) < 0.1f) { conductorPullInfo.speed = 0; ///////////////////////////////////////( &m_hissySteamSound ); @@ -1233,7 +1233,7 @@ void alignToTerrain( Real angle, const Coord3D& pos, const Coord3D& normal, Matr x.normalize(); } - DEBUG_ASSERTCRASH(fabs(x.x*z.x + x.y*z.y + x.z*z.z)<0.0001,("dot is not zero")); + DEBUG_ASSERTCRASH(WWMath::Fabs(x.x*z.x + x.y*z.y + x.z*z.z)<0.0001,("dot is not zero")); // now computing the y vector is trivial. y.crossProduct( z, x, y ); @@ -1299,7 +1299,11 @@ void RailroadBehavior::updatePositionTrackDistance( PullInfo *pullerInfo, PullIn trackPosDelta.z = 0; Real dx = pullerInfo->towHitchPosition.x - turnPos.x; Real dy = pullerInfo->towHitchPosition.y - turnPos.y; - Real desiredAngle = atan2(dy, dx); + // TheSuperHackers @bugfix bobtista 10/06/2026 Use single-precision Atan2f instead of the double + // overload. On 32-bit x86 (x87 at _PC_24) the double result truncates to 24-bit and diverges from + // the arm64 build's full-double computation, breaking cross-platform lockstep; single precision is + // deterministic across both (matches Get_Z_Rotation, which already uses the float Atan2_Legacy). + Real desiredAngle = WWMath::Atan2f(dy, dx); Real relAngle = stdAngleDiff(desiredAngle, obj->getTransformMatrix()->Get_Z_Rotation()); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/CleanupHazardUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/CleanupHazardUpdate.cpp index 0d40b8eb230..71383890f38 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/CleanupHazardUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/CleanupHazardUpdate.cpp @@ -172,7 +172,7 @@ UpdateSleepTime CleanupHazardUpdate::update() AIUpdateInterface *ai = obj->getAI(); if( ai && (ai->isIdle() || ai->isBusy()) ) { - Real fDist = sqrt( ThePartitionManager->getDistanceSquared( obj, &m_pos, FROM_CENTER_2D ) ); + Real fDist = WWMath::Sqrtf( ThePartitionManager->getDistanceSquared( obj, &m_pos, FROM_CENTER_2D ) ); if( fDist < 25.0f ) { //Abort clean area because there's nothing left to clean! @@ -204,7 +204,7 @@ void CleanupHazardUpdate::fireWhenReady() bonus.clear(); Real fireRange = m_weaponTemplate->getAttackRange( bonus ); Object *me = getObject(); - Real fDist = sqrt( ThePartitionManager->getDistanceSquared( me, target, FROM_CENTER_2D ) ); + Real fDist = WWMath::Sqrtf( ThePartitionManager->getDistanceSquared( me, target, FROM_CENTER_2D ) ); if( fDist < fireRange ) { //We are currently in range! diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/CommandButtonHuntUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/CommandButtonHuntUpdate.cpp index 344f3e882ae..15d519443c7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/CommandButtonHuntUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/CommandButtonHuntUpdate.cpp @@ -338,12 +338,16 @@ Object* CommandButtonHuntUpdate::scanClosestTarget() } } Real distSqr = ThePartitionManager->getDistanceSquared(me, other, FROM_BOUNDINGSPHERE_2D); - Real dist = sqrt(distSqr); + Real dist = WWMath::Sqrtf(distSqr); Int curPriority = data->m_scanRange - dist; if (info) curPriority = info->getPriority(other->getTemplate()); if (curPriority == 0) continue; // don't attack 0 priority targets. - Int modifier = dist/TheAI->getAiData()->m_attackPriorityDistanceModifier; + // TheSuperHackers @bugfix bobtista 10/06/2026 m_attackPriorityDistanceModifier defaults to + // 0; dist/0 is Inf and (Int)Inf is platform-divergent UB (arm64 vs x86) that skews target + // selection per machine and desyncs lockstep. Treat a non-positive modifier as none. + const Real distModifier = TheAI->getAiData()->m_attackPriorityDistanceModifier; + Int modifier = (distModifier > 0.0f) ? (Int)(dist / distModifier) : 0; Int modPriority = curPriority-modifier; if (modPriority < 1) modPriority = 1; 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/DockUpdate/SupplyWarehouseDockUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DockUpdate/SupplyWarehouseDockUpdate.cpp index 51a9913c933..7814ae3e488 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DockUpdate/SupplyWarehouseDockUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DockUpdate/SupplyWarehouseDockUpdate.cpp @@ -95,7 +95,7 @@ Bool SupplyWarehouseDockUpdate::action( Object* docker, Object *drone ) Real closeEnoughSqr = sqr(docker->getGeometryInfo().getBoundingCircleRadius()*2); Real curDistSqr = ThePartitionManager->getDistanceSquared(docker, getObject(), FROM_BOUNDINGSPHERE_2D); if (curDistSqr > closeEnoughSqr) { - DEBUG_LOG(("Failing dock, dist %f, not close enough(%f).", sqrt(curDistSqr), sqrt(closeEnoughSqr))); + DEBUG_LOG(("Failing dock, dist %f, not close enough(%f).", WWMath::Sqrtf(curDistSqr), WWMath::Sqrtf(closeEnoughSqr))); // Make it twitch a little. Coord3D newPos = *docker->getPosition(); Real range = 0.4*PATHFIND_CELL_SIZE_F; @@ -170,7 +170,7 @@ void SupplyWarehouseDockUpdate::setDockCrippled( Bool setting ) void SupplyWarehouseDockUpdate::setCashValue( Int cashValue ) { // A script can tell us our set value, and we need to figure out the boxes needed to provide that. - m_boxesStored = ceil(cashValue / (float)TheGlobalData->m_baseValuePerSupplyBox); + m_boxesStored = WWMath::Ceil(cashValue / (float)TheGlobalData->m_baseValuePerSupplyBox); Drawable *draw = getObject()->getDrawable(); if( draw ) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DynamicShroudClearingRangeUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DynamicShroudClearingRangeUpdate.cpp index e7100955fb6..d29b6055388 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DynamicShroudClearingRangeUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DynamicShroudClearingRangeUpdate.cpp @@ -166,8 +166,8 @@ void DynamicShroudClearingRangeUpdate::animateGridDecals() for (int d = 0; d < GRID_FX_DECAL_COUNT; ++d) { - pos.x = ctr->x + (sinf(angle) * radius); - pos.y = ctr->y + (cosf(angle) * radius); + pos.x = ctr->x + (WWMath::Sinf(angle) * radius); + pos.y = ctr->y + (WWMath::Cosf(angle) * radius); pos.x -= ((Int)pos.x)%23; pos.y -= ((Int)pos.y)%23; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/FloatUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/FloatUpdate.cpp index 8959c3a4db6..b490abf3a30 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/FloatUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/FloatUpdate.cpp @@ -119,8 +119,8 @@ UpdateSleepTime FloatUpdate::update() { Real angle = INT_TO_REAL(TheGameLogic->getFrame()); - Real yaw = sin(angle * 0.0291f) * 0.05f; - Real pitch = sin(angle * 0.0515f) * 0.05f; + Real yaw = WWMath::Sinf(angle * 0.0291f) * 0.05f; + Real pitch = WWMath::Sinf(angle * 0.0515f) * 0.05f; Matrix3D mx = *draw->getInstanceMatrix(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/LaserUpdate.cpp index 4a46ae7f527..ccb5882036e 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,11 +98,14 @@ 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 3aca8d125ed..66df4b36ae4 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" //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- @@ -126,8 +126,31 @@ void LifetimeUpdate::xfer( Xfer *xfer ) // ------------------------------------------------------------------------------------------------ void LifetimeUpdate::loadPostProcess() { - // extend base class 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) + { + now = 1; + } + + UnsignedInt wakeFrame = m_dieFrame; + if (wakeFrame < now) + { + wakeFrame = now; + } + + friend_setNextCallFrame(wakeFrame); } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp index 55d59777358..2d8b796dd42 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp @@ -30,11 +30,13 @@ // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine #include "Common/GameState.h" +#include "Common/GlobalData.h" #include "Common/Player.h" #include "Common/Xfer.h" #include "GameClient/FXList.h" #include "GameClient/Drawable.h" #include "GameClient/GameClient.h" +#include "GameClient/Display.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Object.h" #include "GameLogic/ObjectIter.h" @@ -245,6 +247,21 @@ UpdateSleepTime NeutronMissileSlowDeathBehavior::update() m_activationFrame = currFrame; FXList::doFXPos( modData->m_fxList, &pos ); + + // TheSuperHackers @feature bobtista 23/06/2026 Spawn a shadow-casting dynamic light at the + // nuke blast so the detonation lights the surrounding area and structures cast real shadows. + // Gated on the BGFX dynamic-light shadow feature so it is a no-op on the DX8 reference path + // (which never reads the shadow flag). Render-only: a client display light, no CRC effect. + if( TheDisplay != NULL && TheGlobalData != NULL && TheGlobalData->m_bgfxDynamicLightShadows ) + { + Coord3D lightPos = pos; + lightPos.z += 300.0f; + RGBColor blastColor; + blastColor.red = 0.85f; + blastColor.green = 0.65f; + blastColor.blue = 0.4f; + TheDisplay->createLightPulse( &lightPos, &blastColor, 150.0f, 450.0f, 30, 330, TRUE, 0.0015f, 0.35f ); + } } // see if it's time for any explosions diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileUpdate.cpp index 62de8b3f53c..d45ee14ea9e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileUpdate.cpp @@ -297,7 +297,7 @@ static Real calcTransform(const Object* obj, const Coord3D *pos, Real maxTurnRat Real angle = (Real)ACos( c ); Vector3 newDir; - if (fabs(angle) < maxTurnRate) + if (WWMath::Fabs(angle) < maxTurnRate) { // close enough -- point exactly in the right dir newDir = otherDir; @@ -355,7 +355,7 @@ void NeutronMissileUpdate::doAttack() // // Modulate speed according to turning. The more we have to turn, the slower we go // - Real angleCoeff = (Real)fabs( relAngle ) / (PI / 2.0f); + Real angleCoeff = (Real)WWMath::Fabs( relAngle ) / (PI / 2.0f); if (angleCoeff > 1.0f) angleCoeff = 1.0; } @@ -512,7 +512,7 @@ UpdateSleepTime NeutronMissileUpdate::update() if (m_noTurnDistLeft > 0.0f && oldPosValid) { Coord3D newPos = *getObject()->getPosition(); - Real distThisTurn = sqrt(sqr(newPos.x-oldPos.x) + sqr(newPos.y-oldPos.y) + sqr(newPos.z-oldPos.z)); + Real distThisTurn = WWMath::Sqrtf(sqr(newPos.x-oldPos.x) + sqr(newPos.y-oldPos.y) + sqr(newPos.z-oldPos.z)); //DEBUG_LOG(("noTurnDist goes from %f to %f",m_noTurnDistLeft,m_noTurnDistLeft-distThisTurn)); m_noTurnDistLeft -= distThisTurn; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp index f8452289f2d..404a75d7878 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp @@ -42,10 +42,14 @@ #include "GameClient/ControlBar.h" #include "GameClient/GameClient.h" +#include "GameClient/Display.h" +#include "GameClient/View.h" #include "GameClient/Drawable.h" #include "GameClient/ParticleSys.h" #include "GameClient/FXList.h" +#include "Common/GlobalData.h" + #include "GameLogic/GameLogic.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/Object.h" @@ -57,6 +61,7 @@ #include "GameLogic/Module/ParticleUplinkCannonUpdate.h" #include "GameLogic/Module/PhysicsUpdate.h" #include "GameLogic/Module/ActiveBody.h" +#include "GgcRuntimeFlags.h" // TheSuperHackers @fix Mirelle 04/02/2026: Raised from 500.0f so that // enormous camera heights cannot see above the laser origin. @@ -531,7 +536,7 @@ UpdateSleepTime ParticleUplinkCannonUpdate::update() Real cxDistance = (factor * data->m_swathOfDeathDistance ) - (data->m_swathOfDeathDistance * 0.5f); //cx is cartesian x //Now calculate the amplitude value. - Real height = sin( radians ); + Real height = WWMath::Sinf( radians ); Real cxHeight = height * data->m_swathOfDeathAmplitude; Coord3D buildingToInitialTargetVector; @@ -655,11 +660,132 @@ UpdateSleepTime ParticleUplinkCannonUpdate::update() audioPos.z += ORBITAL_BEAM_AUDIO_Z_OFFSET; beam->setPosition( &audioPos ); } + // TheSuperHackers @refactor helmutbuhler/xezon 17/05/2025 // Originally the damageRadius was calculated with a value updated by LaserUpdate::clientUpdate. // To no longer rely on GameClient updates, this class now maintains a copy of the LaserRadiusUpdate. m_orbitToTargetLaserRadius.updateRadius(); const Real logicalLaserRadius = templateLaserRadius * m_orbitToTargetLaserRadius.getWidthScale(); + + // TheSuperHackers @feature bobtista 23/06/2026 Coloured light that rides the beam, washing + // nearby vehicles/buildings in the beam's faction colour (blue, or magenta for the + // Superweapon General) with a specular glint and a cast shadow. Gated on the BGFX + // dynamic-light feature so it is a no-op on the DX8 reference path; render-only, no CRC + // effect. One persistent light is repositioned each frame (no per-frame flicker). Its + // intensity follows the logic-owned laser radius rather than the client drawable's current + // width, so the light/shadow envelope decays smoothly with the beam instead of snapping on + // client update ordering. + static const Bool disableParticleCannonTrackingLight = GgcFlags::Enabled(GgcFlag_DisableParticleCannonTrackingLight); + if( !disableParticleCannonTrackingLight && TheDisplay != NULL && TheGlobalData != NULL && TheGlobalData->m_bgfxDynamicLightShadows ) + { + Real intensity = (templateLaserRadius > 0.0f) ? (logicalLaserRadius / templateLaserRadius) : 1.0f; + if( intensity > 1.0f ) { intensity = 1.0f; } + if( intensity < 0.0f ) { intensity = 0.0f; } + const Bool isSuperweapon = (strstr( data->m_particleBeamLaserName.str(), "SupW" ) != NULL); + RGBColor beamColor; + if( isSuperweapon ) + { + beamColor.red = 1.25f; beamColor.green = 0.1f; beamColor.blue = 1.25f; + } + else + { + beamColor.red = 0.06f; beamColor.green = 0.18f; beamColor.blue = 0.85f; + } + beamColor.red *= intensity; + beamColor.green *= intensity; + beamColor.blue *= intensity; + Coord3D beamLightPos = m_currentTargetPosition; + beamLightPos.z += 95.0f; + Real beamShadowStrength = 0.48f * intensity; + Bool snapBlend = FALSE; + // TheSuperHackers @feature bobtista 14/07/2026 Experimental dramatic beam lighting + // (GGC_PCANNON_ENHANCED): the primary beam light (and its main shadow) stays put with a + // gentle intensity flicker; the drama comes from short-lived "lightning" pulse lights + // spawned beside the beam at hashed, irregular moments. Each pulse decays over a few + // frames and casts its own real shadow through the renderer's second point-shadow + // slot, so extra shadows blink in from changing directions while the main shadow + // never moves. Purely visual: lights feed the render backend only, no logic state is + // touched, and the randomness is a hash of the logic frame (no game RNG consumed). + static const Bool dramaticBeamLight = GgcFlags::Enabled(GgcFlag_PCannonEnhanced) + || (TheGlobalData != NULL && TheGlobalData->m_pcannonEnhanced); + if( dramaticBeamLight ) + { + static const Bool noFlicker = GgcFlags::Enabled(GgcFlag_PCannonNoFlicker); + static const Bool noFlash = GgcFlags::Enabled(GgcFlag_PCannonNoFlash); + // The drama beam glow runs brighter than the plain tracking light: with the + // point-light contributions gated out of shadows and off cutouts/decals, the + // remaining lit-ground glow needs the extra energy to read at all. + beamColor.red *= 2.4f; + beamColor.green *= 2.4f; + beamColor.blue *= 2.4f; + // The primary light stays fixed on the beam column: one steady main shadow. + // The "messy" secondary shadows come only from the flash pulses sparking just + // outside the beam (an orbiting primary read as a nonsensical rotating shadow). + if( !noFlicker ) + { + UnsignedInt h = now; + h = (h ^ 61u) ^ (h >> 16); h *= 9u; h = h ^ (h >> 4); h *= 0x27d4eb2du; h = h ^ (h >> 15); + const Real flickerRand = (Real)(h & 0xFFFFu) / 65535.0f; + const Real flicker = 0.62f + 0.38f * flickerRand; + beamColor.red *= flicker; + beamColor.green *= flicker; + beamColor.blue *= flicker; + // The beam's shadow darkness stays constant: only the light's brightness + // flickers. Any throb on shadow strength reads as shadow patches flashing. + snapBlend = TRUE; + } + + // One lightning pulse may fire per window (PCannonFlashInterval frames), at a hashed + // frame within it, from a hashed direction beside the beam. The pulse fades in over + // PCannonFlashFadeIn frames and out over PCannonFlashFadeOut frames so it pulses + // softly instead of strobing. + const UnsignedInt flashWindow = (UnsignedInt)max( 1, TheGlobalData->m_pcannonFlashInterval ); + const UnsignedInt window = now / flashWindow; + UnsignedInt wh = window * 7919u + 97u; + wh = (wh ^ 61u) ^ (wh >> 16); wh *= 9u; wh = wh ^ (wh >> 4); wh *= 0x27d4eb2du; wh = wh ^ (wh >> 15); + const Bool windowHasFlash = ((wh & 0xFFu) > 40u); // ~84% of windows + const UnsignedInt flashStart = window * flashWindow + ((wh >> 8) % 8u); + if( !noFlash && windowHasFlash && now == flashStart ) + { + const Real flashAngle = ((Real)((wh >> 12) & 0x3FFu) / 1023.0f) * 6.2832f; + const Real flashRadiusMax = TheGlobalData->m_pcannonFlashRadius; + const Real flashRadiusMin = flashRadiusMax * 0.4f; + const Real flashRadius = flashRadiusMin + ((Real)((wh >> 22) & 0xFFu) / 255.0f) * (flashRadiusMax - flashRadiusMin); + Coord3D flashPos = m_currentTargetPosition; + flashPos.x += flashRadius * WWMath::Cosf( flashAngle ); + flashPos.y += flashRadius * WWMath::Sinf( flashAngle ); + // Keep the flash well overhead: a low light grazes its own shadow map across + // the whole footprint (a visible dark square of acne on the ground) and blows + // out nearby foliage sitting inside the near-full attenuation zone. + flashPos.z += 80.0f; + RGBColor flashColor; + // Cool and local: deep blue, so flashes read as lightning beside the beam + // instead of scene-wide warm brightness pulses. Bright enough to register on + // sunlit ground now that shadows/cutouts/decals are gated out entirely. + flashColor.red = 0.70f * intensity; + flashColor.green = 1.40f * intensity; + flashColor.blue = 4.20f * intensity; + TheDisplay->createLightPulse( &flashPos, &flashColor, 110.0f, 260.0f, + TheGlobalData->m_pcannonFlashFadeIn, TheGlobalData->m_pcannonFlashFadeOut, + TRUE, 0.003f, 0.45f * intensity ); + DEBUG_LOG(("PCANNON flash pulse frame=%d radius=%.0f pos=(%.0f,%.0f,%.0f) fadeIn=%d fadeOut=%d", + now, flashRadius, flashPos.x, flashPos.y, flashPos.z, + TheGlobalData->m_pcannonFlashFadeIn, TheGlobalData->m_pcannonFlashFadeOut)); + } + + // TheSuperHackers @feature bobtista 17/07/2026 Light continuous camera rumble while + // the beam fires, like the Battle Master's fire shake but re-impulsed periodically + // so it sustains gently instead of building to the clamp. View-only (attenuates by + // the local camera's distance, decays on its own), so no logic/CRC/replay effect. + static const Bool noShake = GgcFlags::Enabled(GgcFlag_PCannonNoShake); + if( !noShake && TheTacticalView != NULL && (now % 6) == 0 ) + { + TheTacticalView->shake( &m_currentTargetPosition, View::SHAKE_SUBTLE ); + } + } + TheDisplay->updateTrackingLight( &beamLightPos, &beamColor, dramaticBeamLight ? 90.0f : 190.0f, dramaticBeamLight ? 290.0f : 560.0f, TRUE, 0.0015f, beamShadowStrength, snapBlend, + (UnsignedInt)getObject()->getID() ); + } damageRadius = logicalLaserRadius * data->m_damageRadiusScalar; scorchRadius = logicalLaserRadius * data->m_scorchMarkScalar; #if defined(RETAIL_COMPATIBLE_CRC) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index 78b256e5b88..2e11c2d124b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -103,7 +103,7 @@ static Real heightToSpeed(Real height) { // don't bother trying to remember how far we've fallen; instead, // back-calc it from our speed & gravity... v = sqrt(2*g*h) - return sqrt(fabs(2.0f * TheGlobalData->m_gravity * height)); + return WWMath::Sqrtf(WWMath::Fabs(2.0f * TheGlobalData->m_gravity * height)); } //------------------------------------------------------------------------------------------------- @@ -513,7 +513,7 @@ Bool PhysicsBehavior::handleBounce(Real oldZ, Real newZ, Real groundZ, Coord3D* Real vz = getVelocity()->z; if (oldZ > groundZ && vz < 0.0f) { - desiredAccelZ = fabs(vz) * stiffness; + desiredAccelZ = WWMath::Fabs(vz) * stiffness; } bounceForce->x = 0.0f; @@ -555,7 +555,7 @@ Bool PhysicsBehavior::handleBounce(Real oldZ, Real newZ, Real groundZ, Coord3D* inline Bool isVerySmall3D(const Coord3D& v) { const Real THRESH = 0.01f; - return (fabs(v.x) < THRESH && fabs(v.y) < THRESH && fabs(v.z) < THRESH); + return (WWMath::Fabs(v.x) < THRESH && WWMath::Fabs(v.y) < THRESH && WWMath::Fabs(v.z) < THRESH); } //------------------------------------------------------------------------------------------------- @@ -655,9 +655,9 @@ UpdateSleepTime PhysicsBehavior::update() // when vel gets tiny, just clamp to zero const Real THRESH = 0.001f; - if (fabsf(m_vel.x) < THRESH) m_vel.x = 0.0f; - if (fabsf(m_vel.y) < THRESH) m_vel.y = 0.0f; - if (fabsf(m_vel.z) < THRESH) m_vel.z = 0.0f; + if (WWMath::Fabsf(m_vel.x) < THRESH) m_vel.x = 0.0f; + if (WWMath::Fabsf(m_vel.y) < THRESH) m_vel.y = 0.0f; + if (WWMath::Fabsf(m_vel.z) < THRESH) m_vel.z = 0.0f; m_velMag = INVALID_VEL_MAG; @@ -689,9 +689,9 @@ UpdateSleepTime PhysicsBehavior::update() // Check when to clear the stunned status if (getIsStunned()) { - if ( (fabs(m_vel.x) < STUN_RELIEF_EPSILON && - fabs(m_vel.y) < STUN_RELIEF_EPSILON && - fabs(m_vel.z) < STUN_RELIEF_EPSILON) + if ( (WWMath::Fabs(m_vel.x) < STUN_RELIEF_EPSILON && + WWMath::Fabs(m_vel.y) < STUN_RELIEF_EPSILON && + WWMath::Fabs(m_vel.z) < STUN_RELIEF_EPSILON) || obj->isSignificantlyAboveTerrain() == FALSE ) { @@ -737,8 +737,8 @@ UpdateSleepTime PhysicsBehavior::update() if (offset != 0.0f) { Vector3 xvec = mtx.Get_X_Vector(); - Real xy = sqrtf(sqr(xvec.X) + sqr(xvec.Y)); - Real pitchAngle = atan2(xvec.Z, xy); + Real xy = WWMath::Sqrtf(sqr(xvec.X) + sqr(xvec.Y)); + Real pitchAngle = WWMath::Atan2f(xvec.Z, xy); Real remainingAngle = (offset > 0) ? ((PI/2) - pitchAngle) : (-(PI/2) + pitchAngle); Real s = Sin(remainingAngle); pitchRateToUse *= s; @@ -864,8 +864,8 @@ UpdateSleepTime PhysicsBehavior::update() // going down hills don't injure themselves (unless the hill is really steep) const Real MIN_ANGLE_TAN = 3.0f; // roughly 71 degrees const Real TINY_DELTA = 0.01f; - if ((fabs(m_vel.x) <= TINY_DELTA || fabs(activeVelZ / m_vel.x) >= MIN_ANGLE_TAN) && - (fabs(m_vel.y) <= TINY_DELTA || fabs(activeVelZ / m_vel.y) >= MIN_ANGLE_TAN)) + if ((WWMath::Fabs(m_vel.x) <= TINY_DELTA || WWMath::Fabs(activeVelZ / m_vel.x) >= MIN_ANGLE_TAN) && + (WWMath::Fabs(m_vel.y) <= TINY_DELTA || WWMath::Fabs(activeVelZ / m_vel.y) >= MIN_ANGLE_TAN)) { Real damageAmt = netSpeed * getMass() * d->m_fallHeightDamageFactor; @@ -944,7 +944,7 @@ Real PhysicsBehavior::getVelocityMagnitude() const { if (m_velMag == INVALID_VEL_MAG) { - m_velMag = (Real)sqrtf( sqr(m_vel.x) + sqr(m_vel.y) + sqr(m_vel.z) ); + m_velMag = (Real)WWMath::Sqrtf( sqr(m_vel.x) + sqr(m_vel.y) + sqr(m_vel.z) ); } return m_velMag; } @@ -966,7 +966,7 @@ Real PhysicsBehavior::getForwardSpeed2D() const Real speedSquared = vx*vx + vy*vy; // DEBUG_ASSERTCRASH( speedSquared != 0, ("zero speedSquared will overflow sqrtf()!") );// lorenzen... sanity check - Real speed = (Real)sqrtf( speedSquared ); + Real speed = (Real)WWMath::Sqrtf( speedSquared ); if (dot >= 0.0f) return speed; @@ -989,7 +989,7 @@ Real PhysicsBehavior::getForwardSpeed3D() const Real dot = vx + vy + vz; - Real speed = (Real)sqrtf( vx*vx + vy*vy + vz*vz ); + Real speed = (Real)WWMath::Sqrtf( vx*vx + vy*vy + vz*vz ); if (dot >= 0.0f) return speed; @@ -1012,7 +1012,7 @@ Bool PhysicsBehavior::wasPreviouslyOverlapped(Object *obj) const //------------------------------------------------------------------------------------------------- void PhysicsBehavior::scrubVelocityZ( Real desiredVelocity ) { - if (fabs(desiredVelocity) < 0.001f) + if (WWMath::Fabs(desiredVelocity) < 0.001f) { m_vel.z = 0; } @@ -1036,7 +1036,7 @@ void PhysicsBehavior::scrubVelocity2D( Real desiredVelocity ) } else { - Real curVelocity = sqrtf(m_vel.x*m_vel.x + m_vel.y*m_vel.y); + Real curVelocity = WWMath::Sqrtf(m_vel.x*m_vel.x + m_vel.y*m_vel.y); if (desiredVelocity > curVelocity) { return; @@ -1119,9 +1119,9 @@ void PhysicsBehavior::doBounceSound(const Coord3D& prevPos) //Real vel = fabs(getVelocity()->z); // can't use velocity, because it's already been updated this frame, and will be zero... (srj) - Real vel = fabs(prevPos.z - getObject()->getPosition()->z); + Real vel = WWMath::Fabs(prevPos.z - getObject()->getPosition()->z); - Real mass = fabs(getMass()); + Real mass = WWMath::Fabs(getMass()); if (vel > NORMAL_VEL_Z) { vel = NORMAL_VEL_Z; } @@ -1321,7 +1321,7 @@ void PhysicsBehavior::onCollide( Object *other, const Coord3D *loc, const Coord3 m_lastCollidee = other->getID(); - Real dist = sqrtf(distSqr); + Real dist = WWMath::Sqrtf(distSqr); Real overlap = usRadius + themRadius - dist; // if objects are coincident, dist is zero, so force would be infinite -- clearly @@ -1462,7 +1462,7 @@ static Bool perpsLogicallyEqual( Real perpOne, Real perpTwo ) { // Equality with a wiggle fudge. const Real PERP_RANGE = 0.15f; - return fabs( perpOne - perpTwo ) <= PERP_RANGE; + return WWMath::Fabs( perpOne - perpTwo ) <= PERP_RANGE; } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PointDefenseLaserUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PointDefenseLaserUpdate.cpp index 0bbe34ac99b..be7fd49bfdc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PointDefenseLaserUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PointDefenseLaserUpdate.cpp @@ -167,7 +167,7 @@ void PointDefenseLaserUpdate::fireWhenReady() bonus.clear(); Real fireRange = data->m_weaponTemplate->getAttackRange( bonus ); Object *me = getObject(); - Real fDist = sqrt( ThePartitionManager->getDistanceSquared( me, target, FROM_CENTER_2D ) ); + Real fDist = WWMath::Sqrtf( ThePartitionManager->getDistanceSquared( me, target, FROM_CENTER_2D ) ); if( fDist < fireRange ) { //We are currently in range! @@ -290,7 +290,7 @@ Object* PointDefenseLaserUpdate::scanClosestTarget() continue; } - Real fDist = sqrt( ThePartitionManager->getDistanceSquared( me, other, FROM_CENTER_2D ) ); + Real fDist = WWMath::Sqrtf( ThePartitionManager->getDistanceSquared( me, other, FROM_CENTER_2D ) ); if( fDist <= fireRange ) { @@ -317,7 +317,7 @@ Object* PointDefenseLaserUpdate::scanClosestTarget() pos.add( *other->getPosition() ); //Recalculate the distance. - fDist = sqrt( ThePartitionManager->getDistanceSquared( me, other, FROM_CENTER_2D ) ); + fDist = WWMath::Sqrtf( ThePartitionManager->getDistanceSquared( me, other, FROM_CENTER_2D ) ); } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SlavedUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SlavedUpdate.cpp index fa7883d6335..d05d5b79a26 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SlavedUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SlavedUpdate.cpp @@ -200,7 +200,11 @@ UpdateSleepTime SlavedUpdate::update() { Real health = body->getHealth(); Real maxHealth = body->getMaxHealth(); - healthPercentage = (Int)(health / maxHealth * 100.0f); + // TheSuperHackers @bugfix bobtista 10/06/2026 Guard against zero max health: health/0 is + // Inf/NaN and (Int) of it is platform-divergent UB (arm64 vs x86), which would flip this + // repair-vs-attack branch differently per machine and desync lockstep. + if( maxHealth > 0.0f ) + healthPercentage = (Int)(health / maxHealth * 100.0f); } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpectreGunshipDeploymentUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpectreGunshipDeploymentUpdate.cpp index c23caf3d715..da367841b88 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpectreGunshipDeploymentUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpectreGunshipDeploymentUpdate.cpp @@ -218,7 +218,7 @@ Bool SpectreGunshipDeploymentUpdate::initiateIntentToDoSpecialPower(const Specia newGunship->setPosition( &creationCoord ); //ORIENTATION - Real orient = atan2( m_initialTargetPosition.y - creationCoord.y, m_initialTargetPosition.x - creationCoord.x); + Real orient = WWMath::Atan2f( m_initialTargetPosition.y - creationCoord.y, m_initialTargetPosition.x - creationCoord.x); newGunship->setOrientation( orient ); // ID diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp index 8f09c707af2..c1de266f811 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp @@ -670,7 +670,7 @@ UpdateSleepTime StealthUpdate::update() m_disguiseHalfpointReached = true; } //Opacity ranges from full to none at midpoint and full again at the end - Real opacity = fabs( 1.0f - (factor * 2.0f) ); + Real opacity = WWMath::Fabs( 1.0f - (factor * 2.0f) ); Real overrideOpacity = opacity < 1.0f ? 0.0f : 1.0f; draw->setEffectiveOpacity( opacity, overrideOpacity ); if( !m_disguiseTransitionFrames && !m_transitioningToDisguise ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/TensileFormationUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/TensileFormationUpdate.cpp index 08b1e976360..4c6192c6ba8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/TensileFormationUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/TensileFormationUpdate.cpp @@ -348,7 +348,7 @@ UpdateSleepTime TensileFormationUpdate::update() else draw->clearModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_MOVING)); - if ( fabs( pos->z - newPos.z ) > 0.2f && m_life < 100) + if ( WWMath::Fabs( pos->z - newPos.z ) > 0.2f && m_life < 100) draw->setModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_FREEFALL)); else draw->clearModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_FREEFALL)); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ToppleUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ToppleUpdate.cpp index 137521dcf9b..b34a19153e2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ToppleUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ToppleUpdate.cpp @@ -130,7 +130,7 @@ static Real angleClosestTo(Real a1, Real a2, Real desired) { a1 = normalizeAngle(a1); a2 = normalizeAngle(a2); - return (fabs(stdAngleDiff(desired, a1)) < fabs(stdAngleDiff(desired, a2))) ? a1 : a2; + return (WWMath::Fabs(stdAngleDiff(desired, a1)) < WWMath::Fabs(stdAngleDiff(desired, a2))) ? a1 : a2; } //------------------------------------------------------------------------------------------------- @@ -185,7 +185,7 @@ void ToppleUpdate::applyTopplingForce( const Coord3D* toppleDirection, Real topp // yeah, it assumes the models are constructed appropriately, but is a cheap way // of minimizing the problem. (srj) Real curAngleX = normalizeAngle(getObject()->getOrientation()); - Real toppleAngle = normalizeAngle(atan2(m_toppleDirection.y, m_toppleDirection.x)); + Real toppleAngle = normalizeAngle(WWMath::Atan2f(m_toppleDirection.y, m_toppleDirection.x)); if (d->m_toppleLeftOrRightOnly) { // it's a fence or such, and can only topple left or right, so pick the closest @@ -201,7 +201,14 @@ void ToppleUpdate::applyTopplingForce( const Coord3D* toppleDirection, Real topp } // desired angle is toppleAngle +/- pi/2, whichever is closer to curangle Real desiredAngleX = angleClosestTo(toppleAngle + PI/2, toppleAngle - PI/2, curAngleX); - m_numAngleDeltaX = REAL_TO_INT_FLOOR(ANGULAR_LIMIT / (m_angularVelocity * 2)); + // TheSuperHackers @bugfix bobtista 10/06/2026 Guard the divisor: a zero angular velocity gives + // Inf, and REAL_TO_INT_FLOOR(Inf) is platform-divergent UB (x87 fistp vs arm64 lroundf) that + // breaks cross-platform lockstep. The < 1 clamp below is too late (the bad cast already happened). + const Real angularSpeed = m_angularVelocity * 2; + if (angularSpeed > 0.0f) + m_numAngleDeltaX = REAL_TO_INT_FLOOR(ANGULAR_LIMIT / angularSpeed); + else + m_numAngleDeltaX = 1; if (m_numAngleDeltaX < 1) m_numAngleDeltaX = 1; m_angleDeltaX = (desiredAngleX - curAngleX) / m_numAngleDeltaX; @@ -298,7 +305,7 @@ UpdateSleepTime ToppleUpdate::update() m_angularVelocity *= -d->m_bounceVelocityPercent; if( BitIsSet( m_options, TOPPLE_OPTIONS_NO_BOUNCE ) == TRUE || - fabs(m_angularVelocity) < VELOCITY_BOUNCE_LIMIT ) + WWMath::Fabs(m_angularVelocity) < VELOCITY_BOUNCE_LIMIT ) { // too slow, just stop m_angularVelocity = 0; @@ -338,7 +345,7 @@ UpdateSleepTime ToppleUpdate::update() } } } - else if( fabs(m_angularVelocity) >= VELOCITY_BOUNCE_SOUND_LIMIT ) + else if( WWMath::Fabs(m_angularVelocity) >= VELOCITY_BOUNCE_SOUND_LIMIT ) { // fast enough bounce to warrant the bounce fx if( BitIsSet( m_options, TOPPLE_OPTIONS_NO_FX ) == FALSE ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 475811e44c0..fb6582f9285 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -398,8 +398,8 @@ void WeaponTemplate::reset() // No matter what we have now, we want to convert it to frames from msec. // ShotDelay used to use parseDurationUnsignedInt, and we are expanding on that. - self->m_minDelayBetweenShots = ceilf(ConvertDurationFromMsecsToFrames((Real)self->m_minDelayBetweenShots)); - self->m_maxDelayBetweenShots = ceilf(ConvertDurationFromMsecsToFrames((Real)self->m_maxDelayBetweenShots)); + self->m_minDelayBetweenShots = WWMath::Ceilf(ConvertDurationFromMsecsToFrames((Real)self->m_minDelayBetweenShots)); + self->m_maxDelayBetweenShots = WWMath::Ceilf(ConvertDurationFromMsecsToFrames((Real)self->m_maxDelayBetweenShots)); } @@ -890,7 +890,7 @@ UnsignedInt WeaponTemplate::fireWeaponTemplate if (distSqr < minAttackRangeSqr-0.5f && !isProjectileDetonation) #endif { - DEBUG_ASSERTCRASH(distSqr > minAttackRangeSqr*0.8f, ("*** victim is closer than min attack range (%f vs %f) of this weapon -- why did we attempt to fire?",sqrtf(distSqr),sqrtf(minAttackRangeSqr))); + DEBUG_ASSERTCRASH(distSqr > minAttackRangeSqr*0.8f, ("*** victim is closer than min attack range (%f vs %f) of this weapon -- why did we attempt to fire?",WWMath::Sqrtf(distSqr),WWMath::Sqrtf(minAttackRangeSqr))); //-extraLogging #if defined(RTS_DEBUG) @@ -916,7 +916,7 @@ UnsignedInt WeaponTemplate::fireWeaponTemplate targetPos.set( *victimPos ); } Real reAngle = getWeaponRecoilAmount(); - Real reDir = reAngle != 0.0f ? (atan2(victimPos->y - sourcePos->y, victimPos->x - sourcePos->x)) : 0.0f; + Real reDir = reAngle != 0.0f ? (WWMath::Atan2f(victimPos->y - sourcePos->y, victimPos->x - sourcePos->x)) : 0.0f; VeterancyLevel v = sourceObj->getVeterancyLevel(); const FXList* fx = isProjectileDetonation ? getProjectileDetonateFX(v) : getFireFX(v); @@ -1019,7 +1019,11 @@ UnsignedInt WeaponTemplate::fireWeaponTemplate v.y = victimPos->y - sourcePos->y; v.z = victimPos->z - sourcePos->z; // don't round the result; we WANT a fractional-frame-delay in this case. - Real delayInFrames = (v.length() / getWeaponSpeed()); + // TheSuperHackers @bugfix bobtista 10/06/2026 Guard a zero weapon speed: the division would be + // Inf, which survives the < 1.0f check below and reaches REAL_TO_INT_CEIL(Inf) - platform- + // divergent UB (arm64 vs x86) producing a different damage-delay frame and a lockstep desync. + const Real weaponSpeed = getWeaponSpeed(); + Real delayInFrames = (weaponSpeed > 0.0f) ? (v.length() / weaponSpeed) : 0.0f; ObjectID damageID = getDamageDealtAtSelfPosition() ? INVALID_ID : victimID; @@ -1513,9 +1517,9 @@ void WeaponTemplate::dealDamageInternal(ObjectID sourceID, ObjectID victimID, co Coord3D shockWaveVector = damageDirection; // Guard against zero vector. Make vector straight up if that is the case - if (fabs(shockWaveVector.x) < WWMATH_EPSILON && - fabs(shockWaveVector.y) < WWMATH_EPSILON && - fabs(shockWaveVector.z) < WWMATH_EPSILON) + if (WWMath::Fabs(shockWaveVector.x) < WWMATH_EPSILON && + WWMath::Fabs(shockWaveVector.y) < WWMATH_EPSILON && + WWMath::Fabs(shockWaveVector.z) < WWMATH_EPSILON) { shockWaveVector.z = 1.0f; } @@ -1586,6 +1590,7 @@ WeaponStore::WeaponStore() WeaponStore::~WeaponStore() { deleteAllDelayedDamage(); + deleteAllDeferredWeapons(); for (size_t i = 0; i < m_weaponTemplateVector.size(); i++) { @@ -1710,6 +1715,8 @@ void WeaponStore::update() ++ddi; } } + + deleteAllDeferredWeapons(); } //------------------------------------------------------------------------------------------------- @@ -1718,6 +1725,30 @@ void WeaponStore::deleteAllDelayedDamage() m_weaponDDI.clear(); } +//------------------------------------------------------------------------------------------------- +// TheSuperHackers @bugfix bobtista 08/07/2026 See deleteWeaponDeferred. +void WeaponStore::deleteAllDeferredWeapons() +{ + for (size_t i = 0; i < m_deferredDeleteWeapons.size(); ++i) + { + deleteInstance(m_deferredDeleteWeapons[i]); + } + m_deferredDeleteWeapons.clear(); +} + +//------------------------------------------------------------------------------------------------- +// TheSuperHackers @bugfix bobtista 08/07/2026 WeaponSet::updateWeaponSet can run while one of its +// weapons is still firing further down the call stack (e.g. a veterancy promotion earned by the +// shot's own kill rebuilds the weapon set). Deleting the weapon immediately leaves dangling +// pointers up the fire call stack, so keep it alive until the end of the frame. +void WeaponStore::deleteWeaponDeferred(Weapon* weapon) +{ + if (weapon != nullptr) + { + m_deferredDeleteWeapons.push_back(weapon); + } +} + // ------------------------------------------------------------------------------------------------ void WeaponStore::resetWeaponTemplates() { @@ -1746,6 +1777,7 @@ void WeaponStore::reset() } deleteAllDelayedDamage(); + deleteAllDeferredWeapons(); resetWeaponTemplates(); } @@ -2128,11 +2160,11 @@ Bool Weapon::computeApproachTarget(const Object *source, const Object *target, c if (source->isAboveTerrain()) { // Don't do a 180 degree turn. - Real angle = atan2(-dir.y, -dir.x); + Real angle = WWMath::Atan2f(-dir.y, -dir.x); Real relAngle = source->getOrientation()- angle; if (relAngle>2*PI) relAngle -= 2*PI; if (relAngle<-2*PI) relAngle += 2*PI; - if (fabs(relAngle)getShotsPerBarrel(); } + // TheSuperHackers @bugfix bobtista 08/07/2026 Remember whether this weapon belongs to the source's + // weapon set before firing. Temp weapons (projectile detonations, collide weapons) never do, and must + // not have their shot bookkeeping redirected into the source's weapon set below. + const Bool isWeaponSetWeapon = (sourceObj->getWeaponInWeaponSlot(m_wslot) == this); + if( !m_scatterTargetsUnused.empty() ) { // If I have a set scatter pattern, I need to offset the target by a random pick from that pattern @@ -2723,59 +2760,86 @@ Bool Weapon::privateFireWeapon( m_template->fireWeaponTemplate(sourceObj, m_wslot, m_curBarrel, victimObj, victimPos, bonus, isProjectileDetonation, ignoreRanges, this, projectileID, inflictDamage ); } - m_lastFireFrame = now; - --m_ammoInClip; - --m_maxShotCount; - --m_numShotsForCurBarrel; - if (m_numShotsForCurBarrel <= 0) + // TheSuperHackers @bugfix bobtista 08/07/2026 Firing the shot can rebuild the source's weapon set + // (e.g. a veterancy promotion earned by this shot's kill), which replaces this weapon and defers its + // deletion. Finish the shot bookkeeping on the weapon now in this slot, matching the retail memory + // pool's immediate reuse of the deleted weapon's block, and bail out if the slot is now empty. + Weapon* bookkeepingWeapon = this; + if (isWeaponSetWeapon) { - ++m_curBarrel; - m_numShotsForCurBarrel = m_template->getShotsPerBarrel(); - } - - if (m_ammoInClip <= 0) - { - if (m_template->getAutoReloadsClip()) + bookkeepingWeapon = sourceObj->getWeaponInWeaponSlot(m_wslot); + if (bookkeepingWeapon == nullptr) { - reloadAmmo(sourceObj); - reloaded = true; + return false; } - else + if (bookkeepingWeapon != this) { - m_status = OUT_OF_AMMO; - m_whenWeCanFireAgain = 0x7fffffff; - //CRCDEBUG_LOG(("Just set m_whenWeCanFireAgain to %d in Weapon::privateFireWeapon 1", m_whenWeCanFireAgain)); + DEBUG_LOG(("Weapon::privateFireWeapon() - weapon set of %s was rebuilt mid-fire, finishing the shot on the replacement weapon", sourceObj->getTemplate()->getName().str())); } } + reloaded = bookkeepingWeapon->finalizeFiredShot(sourceObj, now, bonus); + } + + return reloaded; +} + +//------------------------------------------------------------------------------------------------- +Bool Weapon::finalizeFiredShot(const Object *sourceObj, UnsignedInt now, const WeaponBonus& bonus) +{ + Bool reloaded = false; + + m_lastFireFrame = now; + --m_ammoInClip; + --m_maxShotCount; + --m_numShotsForCurBarrel; + if (m_numShotsForCurBarrel <= 0) + { + ++m_curBarrel; + m_numShotsForCurBarrel = m_template->getShotsPerBarrel(); + } + + if (m_ammoInClip <= 0) + { + if (m_template->getAutoReloadsClip()) + { + reloadAmmo(sourceObj); + reloaded = true; + } else { - m_status = BETWEEN_FIRING_SHOTS; - //CRCDEBUG_LOG(("Weapon::privateFireWeapon() just set m_status to BETWEEN_FIRING_SHOTS")); - Int delay = m_template->getDelayBetweenShots(bonus); - m_whenLastReloadStarted = now; - m_whenWeCanFireAgain = now + delay; - //CRCDEBUG_LOG(("Just set m_whenWeCanFireAgain to %d (delay is %d) in Weapon::privateFireWeapon", m_whenWeCanFireAgain, delay)); + m_status = OUT_OF_AMMO; + m_whenWeCanFireAgain = 0x7fffffff; + //CRCDEBUG_LOG(("Just set m_whenWeCanFireAgain to %d in Weapon::privateFireWeapon 1", m_whenWeCanFireAgain)); + } + } + else + { + m_status = BETWEEN_FIRING_SHOTS; + //CRCDEBUG_LOG(("Weapon::privateFireWeapon() just set m_status to BETWEEN_FIRING_SHOTS")); + Int delay = m_template->getDelayBetweenShots(bonus); + m_whenLastReloadStarted = now; + m_whenWeCanFireAgain = now + delay; + //CRCDEBUG_LOG(("Just set m_whenWeCanFireAgain to %d (delay is %d) in Weapon::privateFireWeapon", m_whenWeCanFireAgain, delay)); - // if we are sharing reload times - // go through other weapons in weapon set - // set their m_whenWeCanFireAgain to this guy's delay - // set their m_status to this guy's status + // if we are sharing reload times + // go through other weapons in weapon set + // set their m_whenWeCanFireAgain to this guy's delay + // set their m_status to this guy's status - if ( sourceObj->isReloadTimeShared() ) + if ( sourceObj->isReloadTimeShared() ) + { + for (Int wt = 0; wtgetWeaponInWeaponSlot((WeaponSlotType)wt); + if (weapon) { - Weapon *weapon = sourceObj->getWeaponInWeaponSlot((WeaponSlotType)wt); - if (weapon) - { - weapon->setPossibleNextShotFrame(m_whenWeCanFireAgain); - //CRCDEBUG_LOG(("Just set m_whenWeCanFireAgain to %d in Weapon::privateFireWeapon 3", m_whenWeCanFireAgain)); - weapon->setStatus(BETWEEN_FIRING_SHOTS); - } + weapon->setPossibleNextShotFrame(m_whenWeCanFireAgain); + //CRCDEBUG_LOG(("Just set m_whenWeCanFireAgain to %d in Weapon::privateFireWeapon 3", m_whenWeCanFireAgain)); + weapon->setStatus(BETWEEN_FIRING_SHOTS); } } - } + } return reloaded; @@ -2870,7 +2934,7 @@ Bool Weapon::isWithinTargetPitch(const Object *source, const Object *victim) con const Coord3D* dst = victim->getPosition(); const Real ACCEPTABLE_DZ = 10.0f; - if (fabs(dst->z - src->z) < ACCEPTABLE_DZ) + if (WWMath::Fabs(dst->z - src->z) < ACCEPTABLE_DZ) return true; // always good enough if dz is small, regardless of pitch Real minPitch, maxPitch; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp index 546c554ad4b..b1d3b2990b3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp @@ -332,7 +332,10 @@ void WeaponSet::updateWeaponSet(const Object* obj) m_hasDamageWeapon = false; for (Int i = WEAPONSLOT_COUNT - 1; i >= PRIMARY_WEAPON ; --i) { - deleteInstance(m_weapons[i]); + // TheSuperHackers @bugfix bobtista 08/07/2026 This can run while one of these weapons is still + // firing further down the call stack (e.g. a veterancy promotion earned by the shot's own kill), + // so defer the deletion until the end of the frame to not leave dangling pointers behind. + TheWeaponStore->deleteWeaponDeferred(m_weapons[i]); m_weapons[i] = nullptr; if (set->getNth((WeaponSlotType)i)) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index d10dff653a1..7ce25c3b51d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -5378,6 +5378,8 @@ void ScriptEngine::reset() } m_sequentialScripts.clear(); + deleteDeferredSequentialScripts(); + // clear out all the lists of object types that were in the old map. for (AllObjectTypesIt it = m_allObjectTypeLists.begin(); it != m_allObjectTypeLists.end(); it = m_allObjectTypeLists.begin() ) { if (*it) { @@ -7877,13 +7879,17 @@ void ScriptEngine::setSequentialTimer(Team *team, Int frameCount) void ScriptEngine::evaluateAndProgressAllSequentialScripts() { - VecSequentialScriptPtrIt it; + // TheSuperHackers @bugfix bobtista 09/07/2026 This loop previously held an iterator into + // m_sequentialScripts across executeActions. Script actions can stop sequential scripts, which + // erases from the vector, and start sequential scripts, which appends and can reallocate it, + // both invalidating the held iterator. Drive the loop with the already tracked index instead, + // which follows the same positions the retail iterator did. size_t currIndex = 0; size_t prevIndex = ~0u; Bool itAdvanced = false; Int spinCount = 0; - for (it = m_sequentialScripts.begin(); it != m_sequentialScripts.end(); /* empty */) { + while (currIndex < m_sequentialScripts.size()) { if (currIndex == prevIndex) { ++spinCount; } else { @@ -7891,12 +7897,11 @@ void ScriptEngine::evaluateAndProgressAllSequentialScripts() } if (spinCount > MAX_SPIN_COUNT) { - SequentialScript *seqScript = (*it); + SequentialScript *seqScript = m_sequentialScripts[currIndex]; if (seqScript) { DEBUG_LOG(("Sequential script %s appears to be in an infinite loop.", seqScript->m_scriptToExecuteSequentially->getName().str())); } - ++it; ++currIndex; continue; } @@ -7904,16 +7909,16 @@ void ScriptEngine::evaluateAndProgressAllSequentialScripts() prevIndex = currIndex; itAdvanced = false; - SequentialScript *seqScript = (*it); + SequentialScript *seqScript = m_sequentialScripts[currIndex]; if (seqScript == nullptr) { - it = cleanupSequentialScript(it, false); + currIndex = cleanupSequentialScriptAtIndex(currIndex, false); continue; } Team *team = seqScript->m_teamToExecOn; Object *obj = TheGameLogic->findObjectByID(seqScript->m_objectID); if (!(obj || team)) { - it = cleanupSequentialScript(it, false); + currIndex = cleanupSequentialScriptAtIndex(currIndex, false); itAdvanced = true; continue; } @@ -8008,7 +8013,6 @@ void ScriptEngine::evaluateAndProgressAllSequentialScripts() // Check to see if executing our action told us to wait. If so, skip to the next Sequential script if (seqScript->m_dontAdvanceInstruction) { - ++it; ++currIndex; itAdvanced = true; continue; @@ -8034,12 +8038,12 @@ void ScriptEngine::evaluateAndProgressAllSequentialScripts() if (itAdvanced) { // check to make sure they aren't dead. if (obj && obj->isEffectivelyDead()) { - it = cleanupSequentialScript(it, true); + currIndex = cleanupSequentialScriptAtIndex(currIndex, true); continue; } if (aigroup && aigroup->isGroupAiDead()) { - it = cleanupSequentialScript(it, true); + currIndex = cleanupSequentialScriptAtIndex(currIndex, true); continue; } } @@ -8053,7 +8057,7 @@ void ScriptEngine::evaluateAndProgressAllSequentialScripts() appendSequentialScript(seqScript); } - it = cleanupSequentialScript(it, false); + currIndex = cleanupSequentialScriptAtIndex(currIndex, false); itAdvanced = true; } } else if (seqScript->m_framesToWait > 0) { @@ -8062,11 +8066,12 @@ void ScriptEngine::evaluateAndProgressAllSequentialScripts() } if (!itAdvanced) { - ++it; ++currIndex; } } m_currentPlayer = nullptr; + + deleteDeferredSequentialScripts(); } ScriptEngine::VecSequentialScriptPtrIt ScriptEngine::cleanupSequentialScript(VecSequentialScriptPtrIt it, Bool cleanDanglers) @@ -8077,19 +8082,23 @@ ScriptEngine::VecSequentialScriptPtrIt ScriptEngine::cleanupSequentialScript(Vec return it; } + // TheSuperHackers @bugfix bobtista 09/07/2026 Don't delete the scripts right away. This can be + // called for the script that evaluateAndProgressAllSequentialScripts is currently executing an + // action of, when that action stops or replaces sequential scripts. Deleting it here left the + // evaluation reading freed memory, so keep removed scripts alive until the evaluation is done. SequentialScript *scriptToDelete = seqScript; if (cleanDanglers) { while (seqScript) { scriptToDelete = seqScript; seqScript = seqScript->m_nextScriptInSequence; - deleteInstance(scriptToDelete); + m_deferredDeleteSequentialScripts.push_back(scriptToDelete); scriptToDelete = nullptr; } (*it) = nullptr; } else { // we want to make sure to not delete any dangling scripts. (*it) = scriptToDelete->m_nextScriptInSequence; - deleteInstance(scriptToDelete); + m_deferredDeleteSequentialScripts.push_back(scriptToDelete); scriptToDelete = nullptr; } @@ -8101,6 +8110,25 @@ ScriptEngine::VecSequentialScriptPtrIt ScriptEngine::cleanupSequentialScript(Vec return it; } +// TheSuperHackers @bugfix bobtista 09/07/2026 Index based variant for the evaluation loop, which +// cannot hold an iterator across script actions that add or remove sequential scripts. +size_t ScriptEngine::cleanupSequentialScriptAtIndex(size_t index, Bool cleanDanglers) +{ + if (index >= m_sequentialScripts.size()) { + return index; + } + + return cleanupSequentialScript(m_sequentialScripts.begin() + index, cleanDanglers) - m_sequentialScripts.begin(); +} + +void ScriptEngine::deleteDeferredSequentialScripts() +{ + for (size_t i = 0; i < m_deferredDeleteSequentialScripts.size(); ++i) { + deleteInstance(m_deferredDeleteSequentialScripts[i]); + } + m_deferredDeleteSequentialScripts.clear(); +} + Bool ScriptEngine::hasUnitCompletedSequentialScript( Object *object, const AsciiString& sequentialScriptName ) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 53d28a95aa0..a254668008a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -33,6 +33,7 @@ #include "Common/AudioHandleSpecialValues.h" #include "Common/BuildAssistant.h" #include "Common/CRCDebug.h" +#include "Common/Diagnostic/SimulationMathCrc.h" #include "Common/FramePacer.h" #include "Common/GameAudio.h" #include "Common/GameEngine.h" @@ -51,6 +52,7 @@ #include "Common/Radar.h" #include "Common/RandomValue.h" #include "Common/Recorder.h" +#include "Common/SpecialPower.h" #include "Common/StatsCollector.h" #include "Common/ThingFactory.h" #include "Common/Team.h" @@ -63,6 +65,7 @@ #include "Common/XferDeepCRC.h" #include "Common/GameSpyMiscPreferences.h" +#include "GameClient/CommandXlat.h" #include "GameClient/ControlBar.h" #include "GameClient/Drawable.h" #include "GameClient/GameClient.h" @@ -91,6 +94,7 @@ #include "GameLogic/Module/CreateModule.h" #include "GameLogic/Module/DestroyModule.h" #include "GameLogic/Module/OpenContain.h" +#include "GameLogic/Module/SpecialPowerModule.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/PolygonTrigger.h" #include "GameLogic/ScriptActions.h" @@ -112,6 +116,12 @@ #include "GameNetwork/GameSpy/PersistentStorageThread.h" #include +#include "GgcRuntimeFlags.h" + +#ifndef _WIN32 +#include +#include +#endif struct QuitGameException {}; @@ -191,6 +201,27 @@ static Waypoint * findNamedWaypoint(AsciiString name) return nullptr; } +#ifndef _WIN32 +static Bool findMapObjectWaypointLocation(AsciiString name, Coord3D *loc) +{ + if (!loc) + return false; + + for (MapObject *obj = MapObject::getFirstMapObject(); obj; obj = obj->getNext()) + { + Bool exists = false; + AsciiString waypointName = obj->getProperties()->getAsciiString(TheKey_waypointName, &exists); + if (exists && waypointName == name) + { + *loc = *obj->getLocation(); + return true; + } + } + + return false; +} +#endif + // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void setFPMode() @@ -210,9 +241,23 @@ void setFPMode() UnsignedInt newVal = curVal; newVal = (newVal & ~_MCW_RC) | (_RC_NEAR & _MCW_RC); //newVal = (newVal & ~_MCW_RC) | (_RC_CHOP & _MCW_RC); +#if defined(_M_IX86) + // TheSuperHackers @bugfix bobtista 14/06/2026 Keep the x87 control word at _PC_24 (24-bit/single) on + // 32-bit x86. The residual x87 float ops must round once to 24-bit (matching x64/arm64 SSE binary32); + // at the x87 default (_PC_53) they compute at 53-bit then round to 32-bit on store (double rounding), + // which diverges from the SSE single result. Cross-platform REPLAY testing is decisive: a _PC_24 win32 + // build tracks an x64-recorded replay to x64's own self-desync floor (~frame 2600), while dropping + // _PC_24 desyncs early (~frame 200). NOTE: the SimulationMathCrc probe is NOT a reliable predictor here + // (without _PC_24 it matches x64's CRC yet the full sim desyncs); trust replay parity, not the probe. newVal = (newVal & ~_MCW_PC) | (_PC_24 & _MCW_PC); _controlfp(newVal, _MCW_PC | _MCW_RC); +#else + // TheSuperHackers @build bobtista 14/06/2026 x87 precision control (_MCW_PC/_PC_24) exists only on + // 32-bit x86. On x64 (SSE2) the UCRT asserts on an _MCW_PC mask bit (ucrt ieee.c: mask must be a + // subset of _MCW_DN|_MCW_EM|_MCW_RC), and arm64 has no equivalent. Set only the rounding mode there. + _controlfp(newVal, _MCW_RC); +#endif } //------------------------------------------------------------------------------------------------- @@ -239,6 +284,240 @@ const char* toString(GameMode mode) } } +#if !defined(_WIN32) && (defined(RTS_DEBUG) || defined(GGC_ENABLE_GAMEPLAY_DIAGNOSTIC_ENV_HOOKS)) +static void ggcForceNonObserverLocalPlayer(GameMode mode) +{ + if (mode == GAME_REPLAY || !ThePlayerList) + return; + + Player *local = ThePlayerList->getLocalPlayer(); + if (local && !local->isPlayerObserver()) + return; + + for (Int i = 0; i < ThePlayerList->getPlayerCount(); ++i) + { + Player *candidate = ThePlayerList->getNthPlayer(i); + if (!candidate || candidate == ThePlayerList->getNeutralPlayer() || candidate->isPlayerObserver()) + continue; + + const PlayerTemplate *candidateTemplate = candidate->getPlayerTemplate(); + if (candidate->getSide() == "Civilian" || + (candidateTemplate && candidateTemplate->getName() == "FactionCivilian")) + continue; + + candidate->setPlayerType(PLAYER_HUMAN, false); + ThePlayerList->setLocalPlayer(candidate); + return; + } +} + +struct GgcSpecialPowerTriggerContext +{ + const SpecialPowerTemplate *power = nullptr; + Object *source = nullptr; +}; + +static void ggcFindSpecialPowerSource(Object *obj, void *userData) +{ + GgcSpecialPowerTriggerContext *context = static_cast(userData); + if (!context || context->source || !obj || !context->power) + return; + + if (obj->getSpecialPowerModule(context->power)) + context->source = obj; +} + +static void ggcDumpLocalObject(Object *obj, void *) +{ + if (!obj || !obj->getTemplate()) + return; + + const Coord3D *pos = obj->getPosition(); + if (!pos) + return; + + std::fprintf(stderr, + "[GGC_OBJECT_DUMP] id=%u template=%s pos=(%.2f,%.2f,%.2f)\n", + obj->getID(), + obj->getTemplate()->getName().str(), + pos->x, + pos->y, + pos->z); +} + +static Bool ggcParseWorldCoord(const char *value, Coord3D *out) +{ + if (!value || !out) + return FALSE; + + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + if (std::sscanf(value, "%f,%f,%f", &x, &y, &z) >= 2) + { + out->x = x; + out->y = y; + out->z = z; + return TRUE; + } + + return FALSE; +} + +static void ggcMaybeTriggerSpecialPowerDiagnostic() +{ + if (GgcFlags::Enabled(GgcFlag_DumpLocalObjects)) + { + static Bool dumpedObjects = FALSE; + if (!dumpedObjects && ThePlayerList) + { + Player *player = ThePlayerList->getLocalPlayer(); + if (player && !player->isPlayerObserver()) + { + player->iterateObjects(ggcDumpLocalObject, nullptr); + dumpedObjects = TRUE; + } + } + } + + const char *guiCommandName = GgcFlags::StringValue(GgcFlag_TriggerGuiCommand); + if (guiCommandName && *guiCommandName) + { + static Bool initializedGuiCommand = FALSE; + static Bool firedGuiCommand = FALSE; + static UnsignedInt triggerGuiCommandFrame = 0; + + if (firedGuiCommand) + return; + + if (!initializedGuiCommand) + { + Int delayFrames = GgcFlags::IntValue(GgcFlag_TriggerDelayFrames); + if (delayFrames < 0) + delayFrames = 0; + triggerGuiCommandFrame = TheGameLogic->getFrame() + delayFrames; + initializedGuiCommand = TRUE; + std::fprintf(stderr, "[GGC_TRIGGER_GUI_COMMAND] armed command=%s frame=%u\n", guiCommandName, triggerGuiCommandFrame); + } + + if (TheGameLogic->getFrame() < triggerGuiCommandFrame) + return; + + if (!TheControlBar || !TheInGameUI || !TheGameClient || !ThePlayerList || !TheTacticalView) + return; + + Player *player = ThePlayerList->getLocalPlayer(); + if (!player || player->isPlayerObserver()) + return; + + const CommandButton *command = TheControlBar->findCommandButton(AsciiString(guiCommandName)); + if (!command) + { + firedGuiCommand = TRUE; + std::fprintf(stderr, "[GGC_TRIGGER_GUI_COMMAND] missing command=%s\n", guiCommandName); + return; + } + + if (const SpecialPowerTemplate *power = command->getSpecialPowerTemplate()) + { + Object *unit = player->findMostReadyShortcutSpecialPowerOfType(power->getSpecialPowerType()); + if (unit) + { + if (SpecialPowerModuleInterface *module = unit->getSpecialPowerModule(power)) + module->setReadyFrame(TheGameLogic->getFrame()); + } + } + + Coord3D target = TheTacticalView->getPosition(); + ggcParseWorldCoord(GgcFlags::StringValue(GgcFlag_TriggerWorld), &target); + + TheInGameUI->setGUICommand(command); + GameMessage::Type msgType = TheGameClient->evaluateContextCommand(nullptr, &target, CommandTranslator::DO_COMMAND); + firedGuiCommand = TRUE; + std::fprintf(stderr, + "[GGC_TRIGGER_GUI_COMMAND] fired command=%s msg=%d target=(%.2f,%.2f,%.2f) frame=%u\n", + guiCommandName, + static_cast(msgType), + target.x, + target.y, + target.z, + TheGameLogic->getFrame()); + return; + } + + const char *powerName = GgcFlags::StringValue(GgcFlag_TriggerSpecialPower); + if (!powerName || !*powerName) + return; + + static Bool initialized = FALSE; + static Bool fired = FALSE; + static UnsignedInt triggerFrame = 0; + + if (fired) + return; + + if (!initialized) + { + Int delayFrames = GgcFlags::IntValue(GgcFlag_TriggerDelayFrames); + if (delayFrames < 0) + delayFrames = 0; + triggerFrame = TheGameLogic->getFrame() + delayFrames; + initialized = TRUE; + std::fprintf(stderr, "[GGC_TRIGGER_SPECIAL_POWER] armed power=%s frame=%u\n", powerName, triggerFrame); + } + + if (TheGameLogic->getFrame() < triggerFrame) + return; + + if (!ThePlayerList || !TheSpecialPowerStore || !TheTacticalView) + return; + + Player *player = ThePlayerList->getLocalPlayer(); + if (!player || player->isPlayerObserver()) + return; + + const SpecialPowerTemplate *power = TheSpecialPowerStore->findSpecialPowerTemplate(AsciiString(powerName)); + if (!power) + { + fired = TRUE; + std::fprintf(stderr, "[GGC_TRIGGER_SPECIAL_POWER] missing power=%s\n", powerName); + return; + } + + Coord3D target = TheTacticalView->getPosition(); + ggcParseWorldCoord(GgcFlags::StringValue(GgcFlag_TriggerWorld), &target); + + GgcSpecialPowerTriggerContext context; + context.power = power; + player->iterateObjects(ggcFindSpecialPowerSource, &context); + if (!context.source) + { + fired = TRUE; + std::fprintf(stderr, "[GGC_TRIGGER_SPECIAL_POWER] no source for power=%s player=%d\n", powerName, player->getPlayerIndex()); + return; + } + + SpecialPowerModuleInterface *module = context.source->getSpecialPowerModule(power); + if (!module) + { + fired = TRUE; + return; + } + + module->setReadyFrame(TheGameLogic->getFrame()); + module->doSpecialPowerAtLocation(&target, INVALID_ANGLE, COMMAND_FIRED_BY_SCRIPT); + fired = TRUE; + std::fprintf(stderr, + "[GGC_TRIGGER_SPECIAL_POWER] fired power=%s source=%u target=(%.2f,%.2f,%.2f) frame=%u\n", + powerName, + context.source->getID(), + target.x, + target.y, + target.z, + TheGameLogic->getFrame()); +} +#endif + // ------------------------------------------------------------------------------------------------ /** GameLogic class constructor */ // ------------------------------------------------------------------------------------------------ @@ -556,11 +835,25 @@ static void placeNetworkBuildingsForPlayer(Int slotNum, const GameSlot *pSlot, P Waypoint *waypoint = findNamedWaypoint(waypointName); Waypoint *rallyWaypoint = findNamedWaypoint(rallyWaypointName); +#ifndef _WIN32 + Coord3D waypointFallbackLoc; + Coord3D rallyFallbackLoc; + Bool waypointFallbackFound = waypoint == nullptr ? findMapObjectWaypointLocation(waypointName, &waypointFallbackLoc) : false; + Bool rallyFallbackFound = rallyWaypoint == nullptr ? findMapObjectWaypointLocation(rallyWaypointName, &rallyFallbackLoc) : false; +#endif +#ifdef _WIN32 DEBUG_ASSERTCRASH(waypoint, ("Player %d has no starting waypoint (Player_%d_Start)", slotNum, startPos)); if (!waypoint) return; Coord3D pos = *waypoint->getLocation(); +#else + DEBUG_ASSERTCRASH(waypoint || waypointFallbackFound, ("Player %d has no starting waypoint (Player_%d_Start)", slotNum, startPos)); + if (!waypoint && !waypointFallbackFound) + return; + + Coord3D pos = waypoint ? *waypoint->getLocation() : waypointFallbackLoc; +#endif pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ); AsciiString buildingTemplateName = pTemplate->getStartingBuilding(); @@ -588,6 +881,13 @@ static void placeNetworkBuildingsForPlayer(Int slotNum, const GameSlot *pSlot, P pos = *rallyWaypoint->getLocation(); pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ); } +#ifndef _WIN32 + else if (rallyFallbackFound) + { + pos = rallyFallbackLoc; + pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ); + } +#endif for (Int i=0; igetPlayerTemplateCount(); ++i) { const PlayerTemplate* ptTest = ThePlayerTemplateStore->getNthPlayerTemplate(i); - if (!ptTest || ptTest->getStartingBuilding().isEmpty()) + if (!ptTest +#ifndef _WIN32 + || ptTest->isObserver() +#endif + || ptTest->getStartingBuilding().isEmpty()) continue; if ( game->oldFactionsOnly() && !ptTest->isOldFaction() ) @@ -747,6 +1051,24 @@ static void populateRandomSideAndColor( GameInfo *game ) startSlots.push_back(i); } + +#ifndef _WIN32 + if (startSlots.empty()) + { + DEBUG_CRASH(("No unlocked playable factions were available for random skirmish selection.")); + for (i = 0; i < ThePlayerTemplateStore->getPlayerTemplateCount(); ++i) + { + const PlayerTemplate* ptTest = ThePlayerTemplateStore->getNthPlayerTemplate(i); + if (!ptTest || ptTest->isObserver() || ptTest->getStartingBuilding().isEmpty()) + continue; + + if ( game->oldFactionsOnly() && !ptTest->isOldFaction() ) + continue; + + startSlots.push_back(i); + } + } +#endif #endif for (i=0; igetPlayerTemplateCount(); ++i) + { + const PlayerTemplate* pt = ThePlayerTemplateStore->getNthPlayerTemplate(i); + if (!pt || pt->isObserver() || pt->getStartingBuilding().isEmpty()) + continue; + + if (game->oldFactionsOnly() && !pt->isOldFaction()) + continue; + + return i; + } + + return -1; +} +#endif + // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ static const WaypointMap s_emptyWaypoints = WaypointMap(); @@ -848,7 +1197,7 @@ static void populateRandomStartPosition( GameInfo *game ) { Coord3D p1 = c1->second; Coord3D p2 = c2->second; - startSpotDistance[i][j] = sqrt( sqr(p1.x-p2.x) + sqr(p1.y-p2.y) ); + startSpotDistance[i][j] = WWMath::Sqrtf( sqr(p1.x-p2.x) + sqr(p1.y-p2.y) ); } } else @@ -1369,7 +1718,13 @@ void GameLogic::tryStartNewGame( Bool loadingSaveGame ) DEBUG_LOG(("%s", Buf)); #endif +#ifdef _WIN32 Int localSlot = 0; +#else + Int localSlot = TheGameInfo ? TheGameInfo->getLocalSlotNum() : 0; + if (localSlot < 0) + localSlot = 0; +#endif Int progressCount = LOAD_PROGRESS_SIDE_POPULATION; if (TheGameInfo) { @@ -1402,11 +1757,34 @@ void GameLogic::tryStartNewGame( Bool loadingSaveGame ) d.setAsciiString(TheKey_playerName, playerName); d.setBool(TheKey_playerIsHuman, slot->isHuman()); d.setUnicodeString(TheKey_playerDisplayName, slot->getName()); +#ifdef _WIN32 const PlayerTemplate* pt; if (slot->getPlayerTemplate() >= 0) pt = ThePlayerTemplateStore->getNthPlayerTemplate(slot->getPlayerTemplate()); else pt = ThePlayerTemplateStore->findPlayerTemplate( TheNameKeyGenerator->nameToKey("FactionObserver") ); +#else + const PlayerTemplate* pt = nullptr; + if (slot->getPlayerTemplate() == PLAYERTEMPLATE_OBSERVER) + { + pt = ThePlayerTemplateStore->findPlayerTemplate( TheNameKeyGenerator->nameToKey("FactionObserver") ); + } + else if (slot->getPlayerTemplate() >= 0) + { + pt = ThePlayerTemplateStore->getNthPlayerTemplate(slot->getPlayerTemplate()); + } + else + { + const Int fallbackTemplate = findFirstPlayablePlayerTemplateIndex(TheGameInfo); + if (fallbackTemplate >= 0) + { + DEBUG_CRASH(("Slot %d still had unresolved random playerTemplate %d; using playable template %d.", + i, slot->getPlayerTemplate(), fallbackTemplate)); + slot->setPlayerTemplate(fallbackTemplate); + pt = ThePlayerTemplateStore->getNthPlayerTemplate(fallbackTemplate); + } + } +#endif if (pt) { d.setAsciiString(TheKey_playerFaction, KEYNAME(pt->getNameKey())); @@ -1476,7 +1854,11 @@ void GameLogic::tryStartNewGame( Bool loadingSaveGame ) d.setInt(TheKey_multiplayerStartIndex, slot->getStartPos()); // d.setBool(TheKey_multiplayerIsLocal, slot->isLocalPlayer()); // d.setBool(TheKey_multiplayerIsLocal, slot->getIP() == game->getLocalIP()); +#ifdef _WIN32 d.setBool(TheKey_multiplayerIsLocal, slot->isHuman() && (slot->getName().compare(TheGameInfo->getSlot(TheGameInfo->getLocalSlotNum())->getName().str()) == 0)); +#else + d.setBool(TheKey_multiplayerIsLocal, slot->isHuman() && i == localSlot); +#endif /* if (slot->getIP() == game->getLocalIP()) @@ -1497,9 +1879,13 @@ void GameLogic::tryStartNewGame( Bool loadingSaveGame ) } } +#ifdef _WIN32 AsciiString slotNameAscii; slotNameAscii.translate(slot->getName()); if (slot->isHuman() && TheGameInfo->getSlotNum(slotNameAscii) == TheGameInfo->getLocalSlotNum()) { +#else + if (slot->isHuman() && i == localSlot) { +#endif localSlot = i; } TheSidesList->addSide(&d); @@ -1554,6 +1940,9 @@ void GameLogic::tryStartNewGame( Bool loadingSaveGame ) // update the player list to match the new map. TheTeamFactory->reset(); ThePlayerList->newGame(); +#if !defined(_WIN32) && (defined(RTS_DEBUG) || defined(GGC_ENABLE_GAMEPLAY_DIAGNOSTIC_ENV_HOOKS)) + ggcForceNonObserverLocalPlayer(m_gameMode); +#endif // update the loadscreen updateLoadProgress(LOAD_PROGRESS_POST_PLAYER_LIST_RESET); @@ -2085,6 +2474,11 @@ void GameLogic::tryStartNewGame( Bool loadingSaveGame ) // update the loadscreen updateLoadProgress(LOAD_PROGRESS_POST_PRELOAD_ASSETS); + // TheSuperHackers @fix Mauller 08/05/2026 Apply the aspect-ratio-scaled default view before defaulting the camera + TheTacticalView->setDefaultView( + DEG_TO_RADF(TheGlobalData->m_cameraPitch), + DEG_TO_RADF(TheGlobalData->m_cameraYaw), + 1.0f); TheTacticalView->setAngleToDefault(); TheTacticalView->setPitchToDefault(); TheTacticalView->setZoomToDefault(); @@ -2236,6 +2630,13 @@ void GameLogic::tryStartNewGame( Bool loadingSaveGame ) Sleep(100); } + while(m_loadScreen && !m_loadScreen->isReadyForGameStart()) + { + updateLoadProgress(101); + TheAudio->update(); + TheFramePacer->update(); + } + // if we're in a load game, don't fade yet if(loadingSaveGame == FALSE && TheTransitionHandler != nullptr && m_loadScreen) { @@ -2353,6 +2754,10 @@ void GameLogic::tryStartNewGame( Bool loadingSaveGame ) // If we are now starting a multiplayer or skirmish game, let us set the local players selectionto be the command center // We'll ask the Recorder, so we survive replays + // TheSuperHackers @bugfix bobtista 13/07/2026 Run this in replay playback too. Skipping it + // desynced every replay, because the auto-selection consumes AI group ids that the recorded + // commands then allocate differently. The client-side selection is skipped instead, which + // keeps the normal player HUD from covering the replay player list. if( TheRecorder->isMultiplayer() ) { // Iterate through each player's objects, and ask if the object @@ -2456,7 +2861,10 @@ static void findAndSelectCommandCenter(Object *obj, void* alreadyFound) if (!((*(Bool*)alreadyFound)) && obj->isKindOf(KINDOF_COMMANDCENTER) ) { ((*(Bool*)alreadyFound)) = TRUE; - TheGameLogic->selectObject(obj, TRUE, obj->getControllingPlayer()->getPlayerMask(), obj->isLocallyControlled()); + // TheSuperHackers @bugfix bobtista 13/07/2026 Do not select the drawable in replay playback, + // because replays use the observer control bar. + const Bool affectClient = obj->isLocallyControlled() && TheGameLogic->getGameMode() != GAME_REPLAY; + TheGameLogic->selectObject(obj, TRUE, obj->getControllingPlayer()->getPlayerMask(), affectClient); } } @@ -3757,6 +4165,49 @@ void GameLogic::update() TheTerrainLogic->UPDATE(); } +#if RUN_MATH_BENCHMARK_REPLAY400_FLAG + static bool s_benchmarkRun = false; + + if (!s_benchmarkRun && m_frame == 400) + { + SimulationMathCrc::runBenchmark(10000); + s_benchmarkRun = true; + } +#endif + + // TheSuperHackers @feature bobtista 13/07/2026 Optional per-frame fingerprint of the sleepy update + // scheduler. The pop order of equal-priority modules depends on the heap operation history, which the + // game CRC does not cover, so a record-versus-playback divergence can stay invisible until it reorders + // two updates and desyncs the replay much later. Comparing these lines between the recording session's + // log and a replay simulation of it pinpoints the exact frame the scheduler first skews. The layout + // hash follows the heap array order; the content values are order-independent, so a divergent layout + // with equal content isolates a pure ordering difference. + static Int s_logSleepyFingerprint = -1; + if (s_logSleepyFingerprint == -1) + { + s_logSleepyFingerprint = GgcFlags::Enabled(GgcFlag_LogSleepyFingerprint) ? 1 : 0; + } + if (s_logSleepyFingerprint == 1 && isInGame() && !isInShellGame()) + { + UnsignedInt layoutHash = 2166136261u; + UnsignedInt contentSum = 0; + UnsignedInt contentXor = 0; + for (size_t hfi = 0; hfi < m_sleepyUpdates.size(); ++hfi) + { + UpdateModule *hm = m_sleepyUpdates[hfi]; + const Object *hobj = hm->friend_getObject(); + UnsignedInt hid = hobj ? (UnsignedInt)hobj->getID() : 0xFFFFFFFFu; + UnsignedInt hpri = hm->friend_getPriority(); + layoutHash = (layoutHash ^ hid) * 16777619u; + layoutHash = (layoutHash ^ hpri) * 16777619u; + UnsignedInt hcombined = (hid * 2654435761u) ^ hpri; + contentSum += hcombined; + contentXor ^= hcombined; + } + DEBUG_LOG(("GGC-HEAPFP frame=%d n=%d layout=%08X csum=%08X cxor=%08X", + m_frame, (Int)m_sleepyUpdates.size(), layoutHash, contentSum, contentXor)); + } + // force CRC calculation, so we can keep a cache of the last N CRCs. We do this right where the recorder // would be getting the CRC anyway, so replays can get the CRCs from the exact instant in time as the original. Bool isMPGameOrReplay = (TheRecorder && TheRecorder->isMultiplayer() && getGameMode() != GAME_SHELL && getGameMode() != GAME_NONE); @@ -3805,6 +4256,10 @@ void GameLogic::update() processCommandList( TheCommandList ); } +#if !defined(_WIN32) && (defined(RTS_DEBUG) || defined(GGC_ENABLE_GAMEPLAY_DIAGNOSTIC_ENV_HOOKS)) + ggcMaybeTriggerSpecialPowerDiagnostic(); +#endif + #ifdef ALLOW_NONSLEEPY_UPDATES { for (std::list::const_iterator it = m_normalUpdates.begin(); it != m_normalUpdates.end(); ++it) 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); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpyGameInfo.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpyGameInfo.cpp index b8386b756e2..68c8a2f0a30 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpyGameInfo.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpyGameInfo.cpp @@ -563,8 +563,8 @@ void GameSpyLaunchGame() TheGlobalData->m_useFpsLimit = false; // Set the random seed - InitGameLogicRandom( TheGameSpyGame->getSeed() ); - DEBUG_LOG(("InitGameLogicRandom( %d )", TheGameSpyGame->getSeed())); + InitRandom( TheGameSpyGame->getSeed() ); + DEBUG_LOG(("InitRandom( %d )", TheGameSpyGame->getSeed())); if (TheNAT != nullptr) { delete TheNAT; @@ -748,4 +748,3 @@ AsciiString GameSpyGameInfo::generateGameResultsPacket() return results; } - 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/Include/SDL3Device/GameClient/SDL3Keyboard.h b/GeneralsMD/Code/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Keyboard.h index 90ef60152d2..c7c72e2f03c 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Keyboard.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Keyboard.h @@ -41,7 +41,6 @@ class SDL3Keyboard : public Keyboard KeyboardIO m_buffer[MAX_BUFFERED_KEYS]; UnsignedInt m_nextGetIndex; UnsignedInt m_nextFreeIndex; - Bool m_capsState; }; #endif diff --git a/GeneralsMD/Code/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Mouse.h b/GeneralsMD/Code/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Mouse.h index a59476b6ab5..df2827c9890 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Mouse.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Mouse.h @@ -16,6 +16,10 @@ #include "GameClient/Mouse.h" +class Image; +class TextureClass; +class SurfaceClass; + class SDL3Mouse : public Mouse { public: @@ -25,8 +29,18 @@ class SDL3Mouse : public Mouse virtual void init() override; virtual void reset() override; virtual void initCursorResources() override; + virtual void draw() override; virtual void setCursor(MouseCursor cursor) override; virtual void setPosition(Int x, Int y) override; + virtual void setVisibility(Bool visible) override; + // TheSuperHackers @bugfix bobtista 11/07/2026 Reconcile the engine's mouse + // position with the real OS cursor, directly in the current mouse state (no + // buffered event, no warp, no caching — reads the live cursor each call, so + // it is correct whenever it runs). SDL sends no motion event when the window + // appears under a stationary cursor, so without this the engine believed + // (0,0) and edge-scrolled the camera into the map's top-left corner on + // loads that drop straight into gameplay with an untouched mouse. + virtual void syncPositionToSystemCursor() override; void addSDL3MotionEvent(const SDL_MouseMotionEvent &event); void addSDL3ButtonEvent(const SDL_MouseButtonEvent &event); @@ -39,10 +53,27 @@ class SDL3Mouse : public Mouse private: void pushEvent(const MouseIO &event); + const Image *getCursorImage(MouseCursor cursor); + TextureClass *getCursorTexture(MouseCursor cursor, Int frame); + SDL_Cursor *getSDLColorCursor(MouseCursor cursor, Int frame); + SDL_Cursor *createSDLANICursor(MouseCursor cursor, Int frame); + SDL_Cursor *createSDLColorCursor(TextureClass *texture, const ICoord2D &hotSpot); + Int getCursorTextureFrame(MouseCursor cursor); + void drawFallbackCursor(MouseCursor cursor); + void logCursorLookup(MouseCursor cursor, const Image *image, TextureClass *texture); + void syncSystemCursorVisibility(); MouseIO m_buffer[NUM_MOUSE_EVENTS]; UnsignedInt m_nextGetIndex; UnsignedInt m_nextFreeIndex; + const Image *m_cursorImages[NUM_MOUSE_CURSORS]; + TextureClass *m_cursorTextures[NUM_MOUSE_CURSORS][MAX_2D_CURSOR_ANIM_FRAMES]; + SDL_Cursor *m_sdlCursors[NUM_MOUSE_CURSORS][MAX_2D_CURSOR_ANIM_FRAMES]; + Bool m_sdlCursorAniAttempted[NUM_MOUSE_CURSORS][MAX_2D_CURSOR_ANIM_FRAMES]; + MouseCursor m_lastAppliedSDLCursor; + Int m_lastAppliedSDLFrame; + Real m_currentAnimFrame; + UnsignedInt m_lastAnimTime; }; #endif diff --git a/GeneralsMD/Code/GameEngineDevice/Include/SDL3GameEngine.h b/GeneralsMD/Code/GameEngineDevice/Include/SDL3GameEngine.h index 25bf38fb8c9..a7ee8d38ea8 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/SDL3GameEngine.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/SDL3GameEngine.h @@ -48,12 +48,23 @@ class SDL3GameEngine : public GameEngine private: void pollSDL3Events(); void handleKeyboardEvent(const SDL_KeyboardEvent &event); + void handleTextInputEvent(const SDL_TextInputEvent &event); void handleMouseMotionEvent(const SDL_MouseMotionEvent &event); void handleMouseButtonEvent(const SDL_MouseButtonEvent &event); void handleMouseWheelEvent(const SDL_MouseWheelEvent &event); void handleWindowEvent(const SDL_WindowEvent &event); + void applyPendingWindowResize(); + void updatePresentMode(); + void updateTextInputState(); SDL_Window *m_sdlWindow; + Bool m_textInputActive; + Bool m_resizePending; + Bool m_resizeReadyToApply; + Bool m_letterboxActive; + Int m_pendingWidth; + Int m_pendingHeight; + Int m_resizeStableCount; }; #endif diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DBibBuffer.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DBibBuffer.h index 32d1c3c97ab..284844c75d0 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DBibBuffer.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DBibBuffer.h @@ -50,13 +50,12 @@ #include "WWLib/always.h" #include "WW3D2/rendobj.h" #include "WW3D2/w3d_file.h" -#include "WW3D2/dx8vertexbuffer.h" -#include "WW3D2/dx8indexbuffer.h" #include "WW3D2/shader.h" #include "WW3D2/vertmaterial.h" #include "Lib/BaseType.h" #include "Common/GameType.h" #include "Common/AsciiString.h" +#include "WW3D2/renderbufferclasses.h" //----------------------------------------------------------------------------- // Forward References @@ -106,9 +105,9 @@ friend class BaseHeightMapRenderObjClass; enum { INITIAL_BIB_VERTEX=256, INITIAL_BIB_INDEX=384, MAX_BIBS=1000}; - DX8VertexBufferClass *m_vertexBib; ///0 = casts a + // perspective shadow that darkens by that amount. + Bool m_castsShadows; + Real m_shadowBias; + Real m_shadowStrength; + Real m_targetShadowStrength; + + // TheSuperHackers @feature bobtista 15/07/2026 Opt-out of per-object LightEnvironment + // gathering. A light that already illuminates the scene through the dedicated shadowed + // point-light path would double-light receivers via the light environment - unshadowed, + // and clipping bright foliage texels to white speckles under MODULATE2X materials. + Bool m_excludeFromLightEnv; + Bool m_decayRange; Bool m_decayColor; UnsignedInt m_curDecayFrameCount; UnsignedInt m_curIncreaseFrameCount; UnsignedInt m_decayFrameCount; UnsignedInt m_increaseFrameCount; + // TheSuperHackers @bugfix bobtista 17/07/2026 Shadow-casting pulses advance their fade once per + // logic frame instead of once per rendered frame, so the ramp/decay is framerate-independent. + // Without this a load-settle or high-fps burst plays the whole pulse in a few render frames, + // flashing the cast shadow on and off. + UnsignedInt m_lastFadeLogicFrame; Real m_targetRange; Vector3 m_targetAmbient; Vector3 m_targetDiffuse; @@ -71,6 +91,15 @@ friend class HeightMapRenderObjClass; void setEnabled(Bool enabled) { m_enabled = enabled; m_decayRange = false; m_decayFrameCount = 0; m_decayColor = false; m_increaseFrameCount = 0;}; Bool isEnabled() {return m_enabled;}; + void setCastsShadows(Bool b) { m_castsShadows = b; } + Bool getCastsShadows() const { return m_castsShadows; } + void setExcludeFromLightEnv(Bool b) { m_excludeFromLightEnv = b; } + Bool getExcludeFromLightEnv() const { return m_excludeFromLightEnv; } + void setShadowBias(Real b) { m_shadowBias = b; } + Real getShadowBias() const { return m_shadowBias; } + void setShadowStrength(Real s) { m_shadowStrength = s; m_targetShadowStrength = s; } + Real getShadowStrength() const { return m_shadowStrength; } + /// 0 frameIncreaseTime means it starts out full size/intensity, 0 decay time means it lasts forever. void setFrameFade(UnsignedInt frameIncreaseTime, UnsignedInt decayFrameTime); diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DMirror.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DMirror.h index a8fa6899376..4e92e318afe 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DMirror.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DMirror.h @@ -27,12 +27,11 @@ #include "WWLib/always.h" #include "WW3D2/rendobj.h" #include "WW3D2/w3d_file.h" -#include "WW3D2/dx8vertexbuffer.h" -#include "WW3D2/dx8indexbuffer.h" #include "WW3D2/shader.h" #include "WW3D2/vertmaterial.h" #include "Lib/BaseType.h" #include "Common/GameType.h" +#include "WW3D2/renderbufferclasses.h" /// Custom render object that draws mirrors, water, and skies. /** @@ -71,7 +70,7 @@ class MirrorRenderObjClass : public RenderObjClass void toggleCloudLayer(Bool state) { m_useCloudLayer=state;} /// // for timeGetTime() - real on Windows, win32 shim on other platforms + SDL3Keyboard::SDL3Keyboard() : m_nextGetIndex(0), - m_nextFreeIndex(0), - m_capsState(false) + m_nextFreeIndex(0) { reset(); } @@ -52,7 +53,10 @@ void SDL3Keyboard::update() Bool SDL3Keyboard::getCapsState() { - return m_capsState; + // TheSuperHackers @bugfix bobtista 13/07/2026 Ask SDL for the live toggle state like the + // DirectInput keyboard asked GetKeyState. The manually tracked flag started false, so a + // Caps Lock already on at launch or toggled while unfocused reported the wrong state. + return (SDL_GetModState() & SDL_KMOD_CAPS) != 0; } void SDL3Keyboard::addSDL3KeyEvent(const SDL_KeyboardEvent &event) @@ -63,16 +67,18 @@ void SDL3Keyboard::addSDL3KeyEvent(const SDL_KeyboardEvent &event) return; } - UnsignedShort state = (event.down != 0) ? KEY_STATE_DOWN : KEY_STATE_UP; + // TheSuperHackers @bugfix bobtista 09/06/2026 Ignore SDL's hardware key-repeat events. + // The engine's Keyboard::checkKeyRepeat() already synthesizes auto-repeat (it must, because + // the Win32 DirectInput backend delivers no repeats). Forwarding SDL's repeats on top of + // that made every held key repeat at roughly double speed - e.g. one backspace tap deleting + // several characters. Let the engine be the single source of repeat. if (event.repeat != 0) { - state |= KEY_STATE_AUTOREPEAT; - } - if (event.scancode == SDL_SCANCODE_CAPSLOCK && event.down != 0) - { - m_capsState = !m_capsState; + return; } + UnsignedShort state = (event.down != 0) ? KEY_STATE_DOWN : KEY_STATE_UP; + pushKey(key, state); } @@ -97,7 +103,11 @@ void SDL3Keyboard::pushKey(KeyDefType key, UnsignedShort state) slot.key = key; slot.status = KeyboardIO::STATUS_UNUSED; slot.state = state; - slot.keyDownTimeMsec = SDL_GetTicks(); + // TheSuperHackers @bugfix bobtista 09/06/2026 Stamp the key with timeGetTime() - the same + // clock Keyboard::checkKeyRepeat() compares against. SDL_GetTicks() counts from SDL init while + // timeGetTime() counts from system boot, so mixing them made elapsed time enormous and tripped + // auto-repeat on the very first frame of every press (e.g. one backspace deleting two chars). + slot.keyDownTimeMsec = timeGetTime(); m_nextFreeIndex = (m_nextFreeIndex + 1) % MAX_BUFFERED_KEYS; if (m_nextFreeIndex == m_nextGetIndex) diff --git a/GeneralsMD/Code/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Mouse.cpp b/GeneralsMD/Code/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Mouse.cpp index 0196cf4187b..b2d618e7208 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Mouse.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/SDL3Device/GameClient/SDL3Mouse.cpp @@ -12,22 +12,120 @@ #if defined(SAGE_USE_SDL3) +#include +#include +#include +#include +#include +#include +#include + +#include "Common/FileSystem.h" +#include "GgcRuntimeFlags.h" #include "SDL3GameEngine.h" +#include "GameClient/Display.h" +#include "GameClient/Image.h" +#include "GameClient/InGameUI.h" +#include "WW3D2/assetmgr.h" +#include "WW3D2/ddsfile.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/surfaceclass.h" +#include "WW3D2/texture.h" + +static void MapDisplayPointToSDLPoint(Int displayX, Int displayY, float *rawX, float *rawY); + +// Retail cursor art marks transparent texels with magenta (255,0,255); tolerances +// absorb texture compression. Channels at or below the noise threshold count as black. +static const UnsignedByte kKeyColorMinRedBlue = 230; +static const UnsignedByte kKeyColorMaxGreen = 40; +static const UnsignedByte kChannelNoiseFloor = 8; +// W3D cursor textures top out at 256x256; larger dimensions indicate a bad ANI entry. +static const Uint32 kMaxCursorDimension = 256; + +static void MapSDLPointToDisplayPoint(float rawX, float rawY, Uint32 windowID, Int *displayX, Int *displayY); SDL3Mouse::SDL3Mouse() : m_nextGetIndex(0), - m_nextFreeIndex(0) + m_nextFreeIndex(0), + m_lastAppliedSDLCursor(INVALID_MOUSE_CURSOR), + m_lastAppliedSDLFrame(-1), + m_currentAnimFrame(0.0f), + m_lastAnimTime(0) { + for (Int i = 0; i < NUM_MOUSE_CURSORS; ++i) + { + m_cursorImages[i] = nullptr; + for (Int j = 0; j < MAX_2D_CURSOR_ANIM_FRAMES; ++j) + { + m_cursorTextures[i][j] = nullptr; + m_sdlCursors[i][j] = nullptr; + m_sdlCursorAniAttempted[i][j] = false; + } + } reset(); } SDL3Mouse::~SDL3Mouse() { + SDL_ShowCursor(); + for (Int i = 0; i < NUM_MOUSE_CURSORS; ++i) + { + for (Int j = 0; j < MAX_2D_CURSOR_ANIM_FRAMES; ++j) + { + if (m_sdlCursors[i][j] != nullptr) + { + SDL_DestroyCursor(m_sdlCursors[i][j]); + m_sdlCursors[i][j] = nullptr; + } + m_sdlCursorAniAttempted[i][j] = false; + REF_PTR_RELEASE(m_cursorTextures[i][j]); + } + } } void SDL3Mouse::init() { Mouse::init(); + // SDL mouse events report window-local absolute coordinates, matching + // Win32 window messages rather than DirectInput-style deltas. + m_inputMovesAbsolute = TRUE; + // SDL does not have the Win32/DX8 cursor paths. Draw SAGE cursor images + // through the frame renderer instead. + m_currentRedrawMode = RM_POLYGON; + setCursor(ARROW); + syncSystemCursorVisibility(); + syncPositionToSystemCursor(); +} + +void SDL3Mouse::syncPositionToSystemCursor() +{ + if (TheSDL3Window == nullptr) + { + return; + } + + float globalX = 0.0f; + float globalY = 0.0f; + SDL_GetGlobalMouseState(&globalX, &globalY); + int windowX = 0; + int windowY = 0; + if (!SDL_GetWindowPosition(TheSDL3Window, &windowX, &windowY)) + { + return; + } + + Int displayX = 0; + Int displayY = 0; + MapSDLPointToDisplayPoint(globalX - static_cast(windowX), + globalY - static_cast(windowY), + SDL_GetWindowID(TheSDL3Window), + &displayX, &displayY); + // Write the belief directly (base setPosition), deliberately NOT the SDL3 + // override: warping the real cursor here would be circular, and a buffered + // synthetic event proved unreliable (it can be dropped by a reset or + // overwritten before it is consumed). + Mouse::setPosition(displayX, displayY); } void SDL3Mouse::reset() @@ -54,27 +152,139 @@ void SDL3Mouse::reset() void SDL3Mouse::initCursorResources() { + for (Int i = 0; i < NUM_MOUSE_CURSORS; ++i) + { + m_cursorImages[i] = nullptr; + for (Int j = 0; j < MAX_2D_CURSOR_ANIM_FRAMES; ++j) + { + if (m_sdlCursors[i][j] != nullptr) + { + SDL_DestroyCursor(m_sdlCursors[i][j]); + m_sdlCursors[i][j] = nullptr; + } + m_sdlCursorAniAttempted[i][j] = false; + REF_PTR_RELEASE(m_cursorTextures[i][j]); + } + } + m_lastAppliedSDLCursor = INVALID_MOUSE_CURSOR; + m_lastAppliedSDLFrame = -1; + m_currentRedrawMode = RM_POLYGON; + syncSystemCursorVisibility(); +} + +void SDL3Mouse::draw() +{ + syncSystemCursorVisibility(); + + if (m_visible && m_currentCursor != NONE) + { + const Int frame = getCursorTextureFrame(m_currentCursor); + SDL_Cursor *sdlCursor = getSDLColorCursor(m_currentCursor, frame); + const Image *image = nullptr; + TextureClass *texture = nullptr; + if (sdlCursor == nullptr) + { + image = getCursorImage(m_currentCursor); + texture = getCursorTexture(m_currentCursor, frame); + } + logCursorLookup(m_currentCursor, image, texture); + // TheSuperHackers @bugfix bobtista 12/06/2026 Draw the image cursor whenever there is no SDL + // hardware cursor. The previous "&& texture == nullptr" made a successfully-loaded texture + // suppress the image draw and fall through to the (no-op) software-cursor path, leaving no + // cursor drawn. The texture is otherwise unused on this path. + (void)texture; + if (sdlCursor == nullptr && image != nullptr && TheDisplay != nullptr) + { + const ICoord2D &hotSpot = m_cursorInfo[m_currentCursor].hotSpotPosition; + TheDisplay->drawImage( + image, + m_currMouse.pos.x - hotSpot.x, + m_currMouse.pos.y - hotSpot.y, + m_currMouse.pos.x + image->getImageWidth() - hotSpot.x, + m_currMouse.pos.y + image->getImageHeight() - hotSpot.y); + } + else + { + drawFallbackCursor(m_currentCursor); + } + } + + drawCursorText(); + + if (m_visible) + { + drawTooltip(); + } } void SDL3Mouse::setCursor(MouseCursor cursor) { + Mouse::setCursor(cursor); m_currentCursor = cursor; + m_currentRedrawMode = RM_POLYGON; + m_currentAnimFrame = 0.0f; + m_lastAnimTime = SDL_GetTicks(); + if (GgcFlags::Enabled(GgcFlag_CursorDiag) && cursor > NONE && cursor < NUM_MOUSE_CURSORS) + { + static MouseCursor s_lastLoggedCursor = INVALID_MOUSE_CURSOR; + if (cursor == s_lastLoggedCursor) + { + syncSystemCursorVisibility(); + return; + } + s_lastLoggedCursor = cursor; + FILE *file = std::fopen("ggc_cursor_diag.txt", "a"); + if (file != nullptr) + { + std::fprintf(file, + "setCursor cursor=%d name=%s texture=%s image=%s frames=%d directions=%d hotspot=%d,%d\n", + static_cast(cursor), + m_cursorInfo[cursor].cursorName.str(), + m_cursorInfo[cursor].textureName.str(), + m_cursorInfo[cursor].imageName.str(), + m_cursorInfo[cursor].numFrames, + m_cursorInfo[cursor].numDirections, + m_cursorInfo[cursor].hotSpotPosition.x, + m_cursorInfo[cursor].hotSpotPosition.y); + std::fclose(file); + } + } + syncSystemCursorVisibility(); } void SDL3Mouse::setPosition(Int x, Int y) { Mouse::setPosition(x, y); - if (TheSDL3Window != NULL) + if (TheSDL3Window != NULL && !GgcFlags::Enabled(GgcFlag_DisableMouseWarp)) { - SDL_WarpMouseInWindow(TheSDL3Window, static_cast(x), static_cast(y)); + float rawX = static_cast(x); + float rawY = static_cast(y); + MapDisplayPointToSDLPoint(x, y, &rawX, &rawY); + SDL_WarpMouseInWindow(TheSDL3Window, rawX, rawY); } } +void SDL3Mouse::setVisibility(Bool visible) +{ + Mouse::setVisibility(visible); + syncSystemCursorVisibility(); +} + void SDL3Mouse::capture() { - if (TheSDL3Window != NULL) + // TheSuperHackers @bugfix bobtista 27/06/2026 Win32Mouse calls ClipCursor(&windowRect); + // SDL3's analog is SDL_SetWindowMouseGrab. capture() is only reached when the options-driven + // Mouse::canCapture() flow allows it, so grab whenever the window exists. This restores + // edge-of-screen scrolling, which requires the cursor to be captured. GGC_DISABLE_MOUSE_GRAB=1 + // is a debug escape hatch to turn the grab off. + if (TheSDL3Window != NULL && !GgcFlags::Enabled(GgcFlag_DisableMouseGrab)) + { + SDL_SetWindowMouseGrab(TheSDL3Window, true); + onCursorCaptured(SDL_GetWindowMouseGrab(TheSDL3Window) ? TRUE : FALSE); + } + else { - SDL_SetWindowRelativeMouseMode(TheSDL3Window, true); + onCursorCaptured(FALSE); } } @@ -82,8 +292,9 @@ void SDL3Mouse::releaseCapture() { if (TheSDL3Window != NULL) { - SDL_SetWindowRelativeMouseMode(TheSDL3Window, false); + SDL_SetWindowMouseGrab(TheSDL3Window, false); } + onCursorCaptured(FALSE); } UnsignedByte SDL3Mouse::getMouseEvent(MouseIO *result, Bool flush) @@ -101,6 +312,137 @@ UnsignedByte SDL3Mouse::getMouseEvent(MouseIO *result, Bool flush) return MOUSE_OK; } +static SDL_Window *GetMouseEventWindow(Uint32 windowID) +{ + SDL_Window *window = SDL_GetWindowFromID(windowID); + if (window == nullptr) + { + window = TheSDL3Window; + } + return window; +} + +static Bool GetSDLWindowSize(SDL_Window *window, Int *width, Int *height) +{ + if (window == nullptr || width == nullptr || height == nullptr) + { + return FALSE; + } + + int windowWidth = 0; + int windowHeight = 0; + // TheSuperHackers @bugfix bobtista 05/06/2026 Mouse events are in logical window + // points, so the display mapping must divide by the window size in points too. + // Do not fall back to SDL_GetWindowSizeInPixels: on a Retina/HiDPI display that + // returns physical pixels (~2x), which would scale the cursor position wrong. + SDL_GetWindowSize(window, &windowWidth, &windowHeight); + if (windowWidth <= 0 || windowHeight <= 0) + { + return FALSE; + } + + *width = windowWidth; + *height = windowHeight; + return TRUE; +} + +static void GetLetterboxContentRect(Int windowWidth, Int windowHeight, Int *offsetX, Int *offsetY, Int *contentWidth, Int *contentHeight) +{ + *offsetX = 0; + *offsetY = 0; + *contentWidth = windowWidth; + *contentHeight = windowHeight; + if (g_renderBackend != NULL && g_renderBackend->Is_Present_Letterbox_Active()) + { + *offsetX = g_renderBackend->Get_Present_Offset_X(); + *offsetY = g_renderBackend->Get_Present_Offset_Y(); + const Int cw = g_renderBackend->Get_Present_Content_Width(); + const Int ch = g_renderBackend->Get_Present_Content_Height(); + if (cw > 0 && ch > 0) + { + *contentWidth = cw; + *contentHeight = ch; + } + } +} + +static void MapSDLPointToDisplayPoint(float rawX, float rawY, Uint32 windowID, Int *displayX, Int *displayY) +{ + if (displayX == nullptr || displayY == nullptr) + { + return; + } + + SDL_Window *window = GetMouseEventWindow(windowID); + Int windowWidth = 0; + Int windowHeight = 0; + if (TheDisplay == nullptr || !GetSDLWindowSize(window, &windowWidth, &windowHeight)) + { + *displayX = static_cast(std::lround(rawX)); + *displayY = static_cast(std::lround(rawY)); + return; + } + + const Int displayWidth = TheDisplay->getWidth(); + const Int displayHeight = TheDisplay->getHeight(); + if (displayWidth <= 0 || displayHeight <= 0) + { + *displayX = static_cast(std::lround(rawX)); + *displayY = static_cast(std::lround(rawY)); + return; + } + + // TheSuperHackers @feature bobtista 08/06/2026 When the present is letterboxed (multiplayer), the + // game is drawn in a centered sub-rect of the window. Subtract the bar offset and map against the + // content size so pointer coordinates land in the rendered area; clicks in the bars map outside + // the display bounds and are ignored downstream. + Int offsetX; + Int offsetY; + Int contentWidth; + Int contentHeight; + GetLetterboxContentRect(windowWidth, windowHeight, &offsetX, &offsetY, &contentWidth, &contentHeight); + + *displayX = static_cast(std::lround((rawX - static_cast(offsetX)) * static_cast(displayWidth) / static_cast(contentWidth))); + *displayY = static_cast(std::lround((rawY - static_cast(offsetY)) * static_cast(displayHeight) / static_cast(contentHeight))); +} + +static void MapDisplayPointToSDLPoint(Int displayX, Int displayY, float *rawX, float *rawY) +{ + if (rawX == nullptr || rawY == nullptr) + { + return; + } + + Int windowWidth = 0; + Int windowHeight = 0; + if (TheDisplay == nullptr || !GetSDLWindowSize(TheSDL3Window, &windowWidth, &windowHeight)) + { + *rawX = static_cast(displayX); + *rawY = static_cast(displayY); + return; + } + + const Int displayWidth = TheDisplay->getWidth(); + const Int displayHeight = TheDisplay->getHeight(); + if (displayWidth <= 0 || displayHeight <= 0) + { + *rawX = static_cast(displayX); + *rawY = static_cast(displayY); + return; + } + + // Mirror the letterbox mapping in MapSDLPointToDisplayPoint: scale by the content size and add the + // bar offset so a warped cursor lands in the rendered sub-rect. + Int offsetX; + Int offsetY; + Int contentWidth; + Int contentHeight; + GetLetterboxContentRect(windowWidth, windowHeight, &offsetX, &offsetY, &contentWidth, &contentHeight); + + *rawX = static_cast(displayX) * static_cast(contentWidth) / static_cast(displayWidth) + static_cast(offsetX); + *rawY = static_cast(displayY) * static_cast(contentHeight) / static_cast(displayHeight) + static_cast(offsetY); +} + void SDL3Mouse::addSDL3MotionEvent(const SDL_MouseMotionEvent &event) { MouseIO io; @@ -110,8 +452,7 @@ void SDL3Mouse::addSDL3MotionEvent(const SDL_MouseMotionEvent &event) io.leftEvent = MOUSE_EVENT_NONE; io.rightEvent = MOUSE_EVENT_NONE; io.middleEvent = MOUSE_EVENT_NONE; - io.pos.x = static_cast(event.x); - io.pos.y = static_cast(event.y); + MapSDLPointToDisplayPoint(event.x, event.y, event.windowID, &io.pos.x, &io.pos.y); io.deltaPos.x = static_cast(event.xrel); io.deltaPos.y = static_cast(event.yrel); io.wheelPos = 0; @@ -128,14 +469,21 @@ void SDL3Mouse::addSDL3ButtonEvent(const SDL_MouseButtonEvent &event) io.leftEvent = MOUSE_EVENT_NONE; io.rightEvent = MOUSE_EVENT_NONE; io.middleEvent = MOUSE_EVENT_NONE; - io.pos.x = static_cast(event.x); - io.pos.y = static_cast(event.y); + MapSDLPointToDisplayPoint(event.x, event.y, event.windowID, &io.pos.x, &io.pos.y); io.deltaPos.x = 0; io.deltaPos.y = 0; io.wheelPos = 0; io.time = SDL_GetTicks(); MouseButtonState state = (event.down != 0) ? MBS_Down : MBS_Up; + // TheSuperHackers @bugfix bobtista 13/07/2026 Report every second press as a double click, like + // the WM_*BUTTONDBLCLK messages the Win32 window class received. SDL counts consecutive clicks + // with the OS double-click time and radius; without this no MBS_DoubleClick was ever produced, + // so select-all-of-type and the right double click guard command were dead. + if (event.down != 0 && event.clicks > 1 && (event.clicks % 2) == 0) + { + state = MBS_DoubleClick; + } if (event.button == SDL_BUTTON_LEFT) { io.leftState = state; @@ -170,6 +518,877 @@ void SDL3Mouse::addSDL3WheelEvent(const SDL_MouseWheelEvent &event) pushEvent(io); } +const Image *SDL3Mouse::getCursorImage(MouseCursor cursor) +{ + if (cursor <= NONE || cursor >= NUM_MOUSE_CURSORS || TheMappedImageCollection == nullptr) + { + return nullptr; + } + + if (m_cursorImages[cursor] == nullptr && !m_cursorInfo[cursor].imageName.isEmpty()) + { + m_cursorImages[cursor] = TheMappedImageCollection->findImageByName(m_cursorInfo[cursor].imageName); + } + + return m_cursorImages[cursor]; +} + +TextureClass *SDL3Mouse::getCursorTexture(MouseCursor cursor, Int frame) +{ + if (cursor <= NONE || cursor >= NUM_MOUSE_CURSORS) + { + return nullptr; + } + + Int frames = m_cursorInfo[cursor].numFrames; + if (frames <= 0) + { + frames = 1; + } + if (frames > MAX_2D_CURSOR_ANIM_FRAMES) + { + frames = MAX_2D_CURSOR_ANIM_FRAMES; + } + if (frame < 0 || frame >= frames) + { + frame = 0; + } + if (m_cursorTextures[cursor][frame] == nullptr && !m_cursorInfo[cursor].textureName.isEmpty()) + { + char textureName[128]; + if (frames == 1) + { + snprintf(textureName, sizeof(textureName), "%s.tga", m_cursorInfo[cursor].textureName.str()); + } + else + { + snprintf(textureName, sizeof(textureName), "%s%04d.tga", m_cursorInfo[cursor].textureName.str(), frame); + } + + WW3DAssetManager *assetManager = WW3DAssetManager::Get_Instance(); + if (assetManager != nullptr) + { + m_cursorTextures[cursor][frame] = assetManager->Get_Texture(textureName, MIP_LEVELS_1); + } + } + + return m_cursorTextures[cursor][frame]; +} + +SDL_Cursor *SDL3Mouse::getSDLColorCursor(MouseCursor cursor, Int frame) +{ + if (cursor <= NONE || cursor >= NUM_MOUSE_CURSORS) + { + return nullptr; + } + if (frame < 0 || frame >= MAX_2D_CURSOR_ANIM_FRAMES) + { + frame = 0; + } + + if (m_sdlCursors[cursor][frame] == nullptr && !m_sdlCursorAniAttempted[cursor][frame]) + { + m_sdlCursorAniAttempted[cursor][frame] = true; + m_sdlCursors[cursor][frame] = createSDLANICursor(cursor, frame); + } + + if (m_sdlCursors[cursor][frame] == nullptr && GgcFlags::Enabled(GgcFlag_SdlTextureCursor)) + { + TextureClass *texture = getCursorTexture(cursor, frame); + if (texture != nullptr) + { + m_sdlCursors[cursor][frame] = createSDLColorCursor(texture, m_cursorInfo[cursor].hotSpotPosition); + } + } + + return m_sdlCursors[cursor][frame]; +} + +static Uint16 ReadLE16(const Uint8 *p) +{ + return static_cast(static_cast(p[0]) | (static_cast(p[1]) << 8)); +} + +static Uint32 ReadLE32(const Uint8 *p) +{ + return static_cast(p[0]) | + (static_cast(p[1]) << 8) | + (static_cast(p[2]) << 16) | + (static_cast(p[3]) << 24); +} + +static Sint32 ReadLE32S(const Uint8 *p) +{ + return static_cast(ReadLE32(p)); +} + +static Bool LoadBinaryFile(const char *path, std::vector &data) +{ + if (TheFileSystem == nullptr) + { + return FALSE; + } + + File *sageFile = TheFileSystem->openFile(path, File::READ | File::BINARY); + if (sageFile == nullptr) + { + return FALSE; + } + const Int size = sageFile->size(); + char *buffer = sageFile->readEntireAndClose(); + if (buffer == nullptr || size <= 0) + { + delete[] buffer; + return FALSE; + } + data.assign(reinterpret_cast(buffer), reinterpret_cast(buffer) + size); + delete[] buffer; + return TRUE; +} + +static Bool FindANIIconChunkInRange( + const std::vector &data, + size_t begin, + size_t end, + size_t *iconOffset, + size_t *iconSize) +{ + size_t pos = begin; + while (pos + 8 <= end && pos + 8 <= data.size()) + { + const Uint8 *chunk = data.data() + pos; + const Uint32 chunkSize = ReadLE32(chunk + 4); + const size_t payload = pos + 8; + const size_t next = payload + chunkSize + (chunkSize & 1U); + if (payload + chunkSize > end || payload + chunkSize > data.size()) + { + return FALSE; + } + + if (std::memcmp(chunk, "icon", 4) == 0) + { + *iconOffset = payload; + *iconSize = chunkSize; + return TRUE; + } + if (std::memcmp(chunk, "LIST", 4) == 0 && chunkSize >= 4) + { + if (FindANIIconChunkInRange(data, payload + 4, payload + chunkSize, iconOffset, iconSize)) + { + return TRUE; + } + } + pos = next; + } + return FALSE; +} + +static Bool FindANIIconChunk(const std::vector &data, size_t *iconOffset, size_t *iconSize) +{ + if (data.size() < 12 || std::memcmp(data.data(), "RIFF", 4) != 0 || std::memcmp(data.data() + 8, "ACON", 4) != 0) + { + return FALSE; + } + const size_t riffEnd = std::min(data.size(), 8 + ReadLE32(data.data() + 4)); + return FindANIIconChunkInRange(data, 12, riffEnd, iconOffset, iconSize); +} + +static SDL_Cursor *CreateCursorFromCURData(const Uint8 *cur, size_t curSize, const char *diagName) +{ + if (curSize < 22 || ReadLE16(cur) != 0 || ReadLE16(cur + 2) != 2 || ReadLE16(cur + 4) < 1) + { + return nullptr; + } + + const Uint8 *entry = cur + 6; + const Uint32 width = entry[0] == 0 ? kMaxCursorDimension : entry[0]; + const Uint32 listedHeight = entry[1] == 0 ? kMaxCursorDimension : entry[1]; + const Uint16 hotX = ReadLE16(entry + 4); + const Uint16 hotY = ReadLE16(entry + 6); + const Uint32 imageSize = ReadLE32(entry + 8); + const Uint32 imageOffset = ReadLE32(entry + 12); + if (imageOffset >= curSize || imageSize > curSize - imageOffset || imageSize < 40) + { + return nullptr; + } + + const Uint8 *dib = cur + imageOffset; + const Uint32 headerSize = ReadLE32(dib); + if (headerSize < 40 || headerSize > imageSize) + { + return nullptr; + } + const Sint32 dibWidth = ReadLE32S(dib + 4); + const Sint32 dibHeight = ReadLE32S(dib + 8); + const Uint16 planes = ReadLE16(dib + 12); + const Uint16 bitCount = ReadLE16(dib + 14); + const Uint32 compression = ReadLE32(dib + 16); + const Uint32 clrUsed = ReadLE32(dib + 32); + if (dibWidth <= 0 || dibHeight == 0 || planes != 1 || (compression != 0 && compression != 3)) + { + return nullptr; + } + + const Uint32 actualWidth = static_cast(dibWidth); + const Uint32 actualHeight = static_cast((dibHeight < 0 ? -dibHeight : dibHeight) / 2); + if (actualWidth == 0 || actualHeight == 0 || actualWidth > kMaxCursorDimension || actualHeight > kMaxCursorDimension) + { + return nullptr; + } + + Uint32 paletteEntries = 0; + if (bitCount <= 8) + { + paletteEntries = clrUsed != 0 ? clrUsed : (1U << bitCount); + } + const size_t paletteOffset = headerSize; + const size_t xorOffset = paletteOffset + paletteEntries * 4; + const Uint32 xorStride = ((actualWidth * bitCount + 31) / 32) * 4; + const Uint32 andStride = ((actualWidth + 31) / 32) * 4; + const size_t xorSize = static_cast(xorStride) * actualHeight; + const size_t andOffset = xorOffset + xorSize; + const size_t andSize = static_cast(andStride) * actualHeight; + if (xorOffset > imageSize || xorSize > imageSize - xorOffset) + { + return nullptr; + } + + std::vector pixels(static_cast(actualWidth) * actualHeight * 4, 0); + for (Uint32 y = 0; y < actualHeight; ++y) + { + const Uint32 sourceY = dibHeight > 0 ? (actualHeight - 1 - y) : y; + const Uint8 *xorRow = dib + xorOffset + static_cast(sourceY) * xorStride; + const Uint8 *andRow = (andOffset + andSize <= imageSize) ? (dib + andOffset + static_cast(sourceY) * andStride) : nullptr; + for (Uint32 x = 0; x < actualWidth; ++x) + { + Uint8 r = 0; + Uint8 g = 0; + Uint8 b = 0; + Uint8 a = 255; + if (bitCount == 32) + { + const Uint8 *p = xorRow + x * 4; + b = p[0]; + g = p[1]; + r = p[2]; + a = p[3]; + } + else if (bitCount == 24) + { + const Uint8 *p = xorRow + x * 3; + b = p[0]; + g = p[1]; + r = p[2]; + } + else if (bitCount == 8 || bitCount == 4 || bitCount == 1) + { + Uint8 index = 0; + if (bitCount == 8) + { + index = xorRow[x]; + } + else if (bitCount == 4) + { + const Uint8 packed = xorRow[x / 2]; + index = (x & 1) ? (packed & 0x0F) : (packed >> 4); + } + else + { + index = (xorRow[x / 8] >> (7 - (x & 7))) & 1; + } + if (index >= paletteEntries) + { + continue; + } + const Uint8 *color = dib + paletteOffset + index * 4; + b = color[0]; + g = color[1]; + r = color[2]; + } + else + { + return nullptr; + } + + if (andRow != nullptr && (andRow[x / 8] & (0x80 >> (x & 7)))) + { + a = 0; + } + + Uint8 *dest = pixels.data() + (static_cast(y) * actualWidth + x) * 4; + dest[0] = r; + dest[1] = g; + dest[2] = b; + dest[3] = a; + } + } + + SDL_Surface *surface = SDL_CreateSurfaceFrom( + static_cast(actualWidth), + static_cast(actualHeight), + SDL_PIXELFORMAT_RGBA32, + pixels.data(), + static_cast(actualWidth * 4)); + SDL_Cursor *cursor = surface != nullptr ? SDL_CreateColorCursor(surface, hotX, hotY) : nullptr; + const char *sdlError = cursor == nullptr ? SDL_GetError() : ""; + SDL_DestroySurface(surface); + + if (GgcFlags::Enabled(GgcFlag_CursorDiag)) + { + FILE *file = std::fopen("ggc_cursor_diag.txt", "a"); + if (file != nullptr) + { + std::fprintf(file, + "aniCursorBuild file=%s size=%ux%u listed=%ux%u bpp=%u hot=%u,%u cursor=%p error=%s\n", + diagName, + actualWidth, + actualHeight, + width, + listedHeight, + bitCount, + hotX, + hotY, + static_cast(cursor), + sdlError); + std::fclose(file); + } + } + + return cursor; +} + +SDL_Cursor *SDL3Mouse::createSDLANICursor(MouseCursor cursor, Int frame) +{ + if (cursor <= NONE || cursor >= NUM_MOUSE_CURSORS || m_cursorInfo[cursor].textureName.isEmpty()) + { + return nullptr; + } + + char path[256]; + std::vector fileData; + if (m_cursorInfo[cursor].numDirections > 1) + { + if (frame < 0 || frame >= m_cursorInfo[cursor].numDirections) + { + frame = 0; + } + snprintf(path, sizeof(path), "Data/Cursors/%s%d.ANI", m_cursorInfo[cursor].textureName.str(), frame); + } + else + { + snprintf(path, sizeof(path), "Data/Cursors/%s.ANI", m_cursorInfo[cursor].textureName.str()); + } + if (!LoadBinaryFile(path, fileData)) + { + if (m_cursorInfo[cursor].numDirections > 1) + { + snprintf(path, sizeof(path), "Data/Cursors/%s%d.ani", m_cursorInfo[cursor].textureName.str(), frame); + } + else + { + snprintf(path, sizeof(path), "Data/Cursors/%s.ani", m_cursorInfo[cursor].textureName.str()); + } + if (!LoadBinaryFile(path, fileData)) + { + return nullptr; + } + } + + size_t iconOffset = 0; + size_t iconSize = 0; + if (!FindANIIconChunk(fileData, &iconOffset, &iconSize)) + { + return nullptr; + } + + return CreateCursorFromCURData(fileData.data() + iconOffset, iconSize, path); +} + +static UnsignedByte Scale4To8(UnsignedInt value) +{ + value &= 0xF; + return static_cast((value << 4) | value); +} + +static UnsignedByte Scale5To8(UnsignedInt value) +{ + value &= 0x1F; + return static_cast((value << 3) | (value >> 2)); +} + +static UnsignedByte Scale6To8(UnsignedInt value) +{ + value &= 0x3F; + return static_cast((value << 2) | (value >> 4)); +} + +static Bool ReadSurfacePixelRGBA( + const SurfaceClass::SurfaceDescription &desc, + const UnsignedByte *pixel, + UnsignedByte *r, + UnsignedByte *g, + UnsignedByte *b, + UnsignedByte *a) +{ + switch (desc.Format) + { + case WW3D_FORMAT_A8R8G8B8: + *b = pixel[0]; + *g = pixel[1]; + *r = pixel[2]; + *a = pixel[3]; + return TRUE; + case WW3D_FORMAT_X8R8G8B8: + case WW3D_FORMAT_R8G8B8: + *b = pixel[0]; + *g = pixel[1]; + *r = pixel[2]; + *a = 255; + return TRUE; + case WW3D_FORMAT_A4R4G4B4: + { + UnsignedShort raw = 0; + // TheSuperHackers @bugfix bobtista 28/05/2026 Avoid an unaligned dereference; std::memcpy is portable on platforms with strict alignment. + std::memcpy(&raw, pixel, sizeof(raw)); + const UnsignedInt value = raw; + *a = Scale4To8(value >> 12); + *r = Scale4To8(value >> 8); + *g = Scale4To8(value >> 4); + *b = Scale4To8(value); + return TRUE; + } + case WW3D_FORMAT_A1R5G5B5: + { + UnsignedShort raw = 0; + // TheSuperHackers @bugfix bobtista 28/05/2026 Avoid an unaligned dereference; std::memcpy is portable on platforms with strict alignment. + std::memcpy(&raw, pixel, sizeof(raw)); + const UnsignedInt value = raw; + *a = (value & 0x8000) ? 255 : 0; + *r = Scale5To8(value >> 10); + *g = Scale5To8(value >> 5); + *b = Scale5To8(value); + return TRUE; + } + case WW3D_FORMAT_R5G6B5: + { + UnsignedShort raw = 0; + // TheSuperHackers @bugfix bobtista 28/05/2026 Avoid an unaligned dereference; std::memcpy is portable on platforms with strict alignment. + std::memcpy(&raw, pixel, sizeof(raw)); + const UnsignedInt value = raw; + *a = 255; + *r = Scale5To8(value >> 11); + *g = Scale6To8(value >> 5); + *b = Scale5To8(value); + return TRUE; + } + default: + return FALSE; + } +} + +SDL_Cursor *SDL3Mouse::createSDLColorCursor(TextureClass *texture, const ICoord2D &hotSpot) +{ + if (texture == nullptr) + { + return nullptr; + } + + if (!texture->Is_Initialized()) + { + texture->Init(); + } + + SurfaceClass::SurfaceDescription desc; + texture->Get_Level_Description(desc, 0); + if (desc.Width == 0 || desc.Height == 0 || desc.Width > kMaxCursorDimension || desc.Height > kMaxCursorDimension) + { + return nullptr; + } + + std::vector decodedPixels; + const UnsignedByte *src = nullptr; + int pitch = 0; + UnsignedInt bytesPerPixel = Get_Bytes_Per_Pixel(desc.Format); +#if !defined(GGC_RENDER_BACKEND_BGFX) + SurfaceClass *fallbackSurface = nullptr; + Bool lockedSurface = FALSE; +#endif + switch (desc.Format) + { + case WW3D_FORMAT_DXT1: + case WW3D_FORMAT_DXT2: + case WW3D_FORMAT_DXT3: + case WW3D_FORMAT_DXT4: + case WW3D_FORMAT_DXT5: + { + decodedPixels.resize(desc.Width * desc.Height * 4); + DDSFileClass ddsFile(texture->Get_Full_Path(), 0); + if (!ddsFile.Is_Available() || !ddsFile.Load() || ddsFile.Get_Width(0) != desc.Width || + ddsFile.Get_Height(0) != desc.Height) + { + return nullptr; + } + for (UnsignedInt y = 0; y < desc.Height; ++y) + { + for (UnsignedInt x = 0; x < desc.Width; ++x) + { + const UnsignedInt argb = ddsFile.Get_Pixel(0, x, y); + Uint8 *destPixel = decodedPixels.data() + y * desc.Width * 4 + x * 4; + destPixel[0] = static_cast(argb & 0xFF); + destPixel[1] = static_cast((argb >> 8) & 0xFF); + destPixel[2] = static_cast((argb >> 16) & 0xFF); + destPixel[3] = static_cast((argb >> 24) & 0xFF); + } + } + desc.Format = WW3D_FORMAT_A8R8G8B8; + src = decodedPixels.data(); + pitch = desc.Width * 4; + bytesPerPixel = 4; + break; + } + default: + { + const std::vector &mips = texture->Get_CPU_Texture_Mips(); + if (!mips.empty()) { + const TextureBaseClass::TextureMipSnapshot &mip = mips[0]; + const UnsignedInt mipBytesPerPixel = Get_Bytes_Per_Pixel(mip.Format); + if (mip.Format != WW3D_FORMAT_UNKNOWN && + mip.Width != 0 && + mip.Height != 0 && + mipBytesPerPixel != 0 && + mip.Pitch >= mip.Width * mipBytesPerPixel && + mip.Data.size() >= static_cast(mip.Pitch) * mip.Height) + { + desc.Format = mip.Format; + desc.Width = mip.Width; + desc.Height = mip.Height; + src = mip.Data.data(); + pitch = mip.Pitch; + bytesPerPixel = mipBytesPerPixel; + } + } + + if (src == nullptr) { +#if !defined(GGC_RENDER_BACKEND_BGFX) + fallbackSurface = texture->Get_Surface_Level(); + if (fallbackSurface == nullptr) { + return nullptr; + } + fallbackSurface->Get_Description(desc); + bytesPerPixel = fallbackSurface->Get_Bytes_Per_Pixel(); + src = static_cast(fallbackSurface->Lock(&pitch)); + lockedSurface = TRUE; +#else + return nullptr; +#endif + } + break; + } + } + + if (src == nullptr) + { +#if !defined(GGC_RENDER_BACKEND_BGFX) + REF_PTR_RELEASE(fallbackSurface); +#endif + return nullptr; + } + + const UnsignedInt cursorPitch = desc.Width * 4; + std::vector pixels(cursorPitch * desc.Height, 0); + std::vector sourcePixels(cursorPitch * desc.Height, 0); + UnsignedInt visiblePixels = 0; + UnsignedInt alphaPixels = 0; + UnsignedInt nonKeyPixels = 0; + + if (pixels.empty() || sourcePixels.empty()) + { +#if !defined(GGC_RENDER_BACKEND_BGFX) + if (lockedSurface) + { + fallbackSurface->Unlock(); + } + REF_PTR_RELEASE(fallbackSurface); +#endif + return nullptr; + } + + for (UnsignedInt y = 0; y < desc.Height; ++y) + { + for (UnsignedInt x = 0; x < desc.Width; ++x) + { + UnsignedByte r = 0; + UnsignedByte g = 0; + UnsignedByte b = 0; + UnsignedByte a = 0; + const UnsignedByte *sourcePixel = src + y * pitch + x * bytesPerPixel; + if (!ReadSurfacePixelRGBA(desc, sourcePixel, &r, &g, &b, &a)) + { + a = 0; + } + const Bool isKeyColor = (r > kKeyColorMinRedBlue && g < kKeyColorMaxGreen && b > kKeyColorMinRedBlue); + Uint8 *destPixel = sourcePixels.data() + y * cursorPitch + x * 4; + destPixel[0] = r; + destPixel[1] = g; + destPixel[2] = b; + destPixel[3] = a; + if (a > kChannelNoiseFloor) + { + ++alphaPixels; + } + if (!isKeyColor && (r > kChannelNoiseFloor || g > kChannelNoiseFloor || b > kChannelNoiseFloor)) + { + ++nonKeyPixels; + } + } + } + + const Bool useAlpha = (alphaPixels > 0); + for (UnsignedInt y = 0; y < desc.Height; ++y) + { + for (UnsignedInt x = 0; x < desc.Width; ++x) + { + const Uint8 *sourcePixel = sourcePixels.data() + y * cursorPitch + x * 4; + Uint8 *destPixel = pixels.data() + y * cursorPitch + x * 4; + const UnsignedByte r = sourcePixel[0]; + const UnsignedByte g = sourcePixel[1]; + const UnsignedByte b = sourcePixel[2]; + const UnsignedByte a = sourcePixel[3]; + const Bool isKeyColor = (r > kKeyColorMinRedBlue && g < kKeyColorMaxGreen && b > kKeyColorMinRedBlue); + const Bool visible = !isKeyColor && (useAlpha ? (a > kChannelNoiseFloor) : (r > kChannelNoiseFloor || g > kChannelNoiseFloor || b > kChannelNoiseFloor)); + destPixel[0] = r; + destPixel[1] = g; + destPixel[2] = b; + destPixel[3] = visible ? (useAlpha ? a : 255) : 0; + if (!visible) + { + continue; + } + + ++visiblePixels; + } + } + +#if !defined(GGC_RENDER_BACKEND_BGFX) + if (lockedSurface) + { + fallbackSurface->Unlock(); + } + REF_PTR_RELEASE(fallbackSurface); +#endif + + Int hotX = hotSpot.x; + Int hotY = hotSpot.y; + if (hotX < 0 || hotX >= static_cast(desc.Width)) + { + hotX = 0; + } + if (hotY < 0 || hotY >= static_cast(desc.Height)) + { + hotY = 0; + } + + SDL_Surface *cursorSurface = SDL_CreateSurfaceFrom( + static_cast(desc.Width), + static_cast(desc.Height), + SDL_PIXELFORMAT_RGBA32, + pixels.data(), + static_cast(cursorPitch)); + SDL_Cursor *cursor = cursorSurface != nullptr ? SDL_CreateColorCursor(cursorSurface, hotX, hotY) : nullptr; + const char *sdlError = cursor == nullptr ? SDL_GetError() : ""; + SDL_DestroySurface(cursorSurface); + + if (GgcFlags::Enabled(GgcFlag_CursorDiag)) + { + FILE *file = std::fopen("ggc_cursor_diag.txt", "a"); + if (file != nullptr) + { + std::fprintf(file, + "cursorTextureBuild file=%s format=%d size=%ux%u alphaPixels=%u nonKeyPixels=%u visiblePixels=%u cursor=%p error=%s\n", + texture->Get_Texture_Name().str(), + static_cast(desc.Format), + desc.Width, + desc.Height, + alphaPixels, + nonKeyPixels, + visiblePixels, + static_cast(cursor), + sdlError); + std::fclose(file); + } + } + + return cursor; +} + +Int SDL3Mouse::getCursorTextureFrame(MouseCursor cursor) +{ + if (cursor <= NONE || cursor >= NUM_MOUSE_CURSORS) + { + return 0; + } + + const Int directions = m_cursorInfo[cursor].numDirections; + if (directions > 1) + { + if (TheInGameUI != nullptr && TheInGameUI->isScrolling()) + { + Coord2D offset = TheInGameUI->getScrollAmount(); + if (offset.x != 0.0f || offset.y != 0.0f) + { + offset.normalize(); + const Real theta = std::fmod(std::atan2(offset.y, offset.x) + TWO_PI, TWO_PI); + Int direction = static_cast(theta / (TWO_PI / static_cast(directions)) + 0.5f); + if (direction >= directions) + { + direction = 0; + } + return direction; + } + } + return 0; + } + + Int frames = m_cursorInfo[cursor].numFrames; + if (frames <= 1) + { + return 0; + } + if (frames > MAX_2D_CURSOR_ANIM_FRAMES) + { + frames = MAX_2D_CURSOR_ANIM_FRAMES; + } + + UnsignedInt now = SDL_GetTicks(); + if (m_lastAnimTime == 0) + { + m_lastAnimTime = now; + } + + const Real fps = m_cursorInfo[cursor].fps; + if (fps > 0.0f) + { + m_currentAnimFrame += (now - m_lastAnimTime) * (fps / (Real)MSEC_PER_SECOND); + while (m_currentAnimFrame >= frames) + { + m_currentAnimFrame -= frames; + } + } + m_lastAnimTime = now; + + return static_cast(m_currentAnimFrame); +} + +void SDL3Mouse::drawFallbackCursor(MouseCursor cursor) +{ + if (!GgcFlags::Enabled(GgcFlag_SdlSoftwareCursor)) + { + return; + } + + if (TheDisplay == nullptr) + { + return; + } + + // Optional diagnostic renderer. + if (cursor != NORMAL && cursor != ARROW) + { + return; + } + + const Int x = m_currMouse.pos.x; + const Int y = m_currMouse.pos.y; + const UnsignedInt shadow = 0xFF000000; + const UnsignedInt white = 0xFFFFFFFF; + + TheDisplay->drawLine(x + 1, y + 1, x + 1, y + 20, 1.0f, shadow); + TheDisplay->drawLine(x + 1, y + 1, x + 13, y + 13, 1.0f, shadow); + TheDisplay->drawLine(x + 13, y + 13, x + 7, y + 13, 1.0f, shadow); + TheDisplay->drawLine(x + 7, y + 13, x + 11, y + 21, 1.0f, shadow); + TheDisplay->drawLine(x + 11, y + 21, x + 7, y + 23, 1.0f, shadow); + TheDisplay->drawLine(x + 7, y + 23, x + 3, y + 15, 1.0f, shadow); + TheDisplay->drawLine(x + 3, y + 15, x + 1, y + 20, 1.0f, shadow); + + TheDisplay->drawLine(x, y, x, y + 19, 1.0f, white); + TheDisplay->drawLine(x, y, x + 12, y + 12, 1.0f, white); + TheDisplay->drawLine(x + 12, y + 12, x + 6, y + 12, 1.0f, white); + TheDisplay->drawLine(x + 6, y + 12, x + 10, y + 20, 1.0f, white); + TheDisplay->drawLine(x + 10, y + 20, x + 6, y + 22, 1.0f, white); + TheDisplay->drawLine(x + 6, y + 22, x + 2, y + 14, 1.0f, white); + TheDisplay->drawLine(x + 2, y + 14, x, y + 19, 1.0f, white); +} + +void SDL3Mouse::logCursorLookup(MouseCursor cursor, const Image *image, TextureClass *texture) +{ + if (!GgcFlags::Enabled(GgcFlag_CursorDiag)) + { + return; + } + + static MouseCursor s_lastCursor = INVALID_MOUSE_CURSOR; + static const Image *s_lastImage = reinterpret_cast(-1); + static TextureClass *s_lastTexture = reinterpret_cast(-1); + if (cursor == s_lastCursor && image == s_lastImage && texture == s_lastTexture) + { + return; + } + s_lastCursor = cursor; + s_lastImage = image; + s_lastTexture = texture; + + FILE *file = std::fopen("ggc_cursor_diag.txt", "a"); + if (file == nullptr) + { + return; + } + std::fprintf(file, + "cursor=%d name=%s imageName=%s textureName=%s image=%p texture=%p textureFile=%s init=%d size=%dx%d sdl=%p\n", + static_cast(cursor), + m_cursorInfo[cursor].cursorName.str(), + m_cursorInfo[cursor].imageName.str(), + m_cursorInfo[cursor].textureName.str(), + static_cast(image), + static_cast(texture), + texture != nullptr ? texture->Get_Texture_Name().str() : "", + texture != nullptr ? texture->Is_Initialized() : 0, + texture != nullptr ? texture->Get_Width() : 0, + texture != nullptr ? texture->Get_Height() : 0, + static_cast(getSDLColorCursor(cursor, getCursorTextureFrame(cursor)))); + std::fclose(file); +} + +void SDL3Mouse::syncSystemCursorVisibility() +{ + if (GgcFlags::Enabled(GgcFlag_SdlOsCursor)) + { + SDL_ShowCursor(); + return; + } + + if (!m_visible || m_currentCursor == NONE) + { + SDL_HideCursor(); + return; + } + + const Int frame = getCursorTextureFrame(m_currentCursor); + SDL_Cursor *cursor = getSDLColorCursor(m_currentCursor, frame); + if (cursor != nullptr) + { + if (m_currentCursor != m_lastAppliedSDLCursor || frame != m_lastAppliedSDLFrame) + { + SDL_SetCursor(cursor); + m_lastAppliedSDLCursor = m_currentCursor; + m_lastAppliedSDLFrame = frame; + } + SDL_ShowCursor(); + return; + } + + SDL_HideCursor(); +} + void SDL3Mouse::pushEvent(const MouseIO &event) { m_buffer[m_nextFreeIndex] = event; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/SDL3GameEngine.cpp b/GeneralsMD/Code/GameEngineDevice/Source/SDL3GameEngine.cpp index 4e326dc81ec..9167dccb420 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/SDL3GameEngine.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/SDL3GameEngine.cpp @@ -12,12 +12,37 @@ #if defined(SAGE_USE_SDL3) +#include +#include +#include +#if defined(_WIN32) +// TheSuperHackers @build bobtista 08/06/2026 strings.h is POSIX; MSVC has no such header. +// strcasecmp lives in as _stricmp on Windows. +#include +#define strcasecmp _stricmp +#else +#include +#endif + #include "Common/AudioRequest.h" +#include "Common/Debug.h" #include "Common/GameAudio.h" +#include "Common/GlobalData.h" +#include "Common/MessageStream.h" +#include "GgcRuntimeFlags.h" +#include "GameClient/Display.h" +#include "GameClient/Gadget.h" +#include "GameClient/GameWindow.h" +#include "GameClient/GameWindowManager.h" +#include "GameClient/HeaderTemplate.h" +#include "GameClient/InGameUI.h" #include "GameClient/Keyboard.h" #include "GameClient/Mouse.h" #include "GameClient/ParticleSys.h" +#include "GameClient/Shell.h" +#include "GameClient/View.h" #include "GameLogic/GameLogic.h" +#include "GameNetwork/LANAPICallbacks.h" #include "GameNetwork/NetworkInterface.h" #if defined(SAGE_USE_OPENAL) #include "OpenALAudioDevice/OpenALAudioManager.h" @@ -33,12 +58,43 @@ #include "W3DDevice/GameClient/W3DGameClient.h" #include "W3DDevice/GameClient/W3DParticleSys.h" #include "W3DDevice/GameLogic/W3DGameLogic.h" +#include "WW3D2/IRenderBackend.h" +#include "WW3D2/RenderBackend.h" + +// TheSuperHackers @feature bobtista 15/06/2026 LOCAL DEV AID (uncommitted): request +// a bgfx scene-framebuffer rebuild so the HDR hotkey can toggle RGBA8<->RGBA16F live. +extern "C" void GGC_RequestBgfxFramebufferRebuild(); extern Mouse *TheMouse; extern Keyboard *TheKeyboard; +namespace +{ + +// Dev-hotkey toggle-on values for the bgfx post effects. Toggling an effect on +// re-asserts these so the effect is visible even when the INI left it at zero. +const Real kToggleVignetteStrength = 0.4f; +const Real kToggleChromaAmount = 0.8f; +const Real kToggleFilmGrainStrength = 0.1f; +const Real kToggleSpecularStrength = 3.0f; +const Real kToggleRimStrength = 0.3f; +const Real kToggleRimPower = 3.0f; +const Real kToggleEmissiveBoostScale = 2.0f; +const Real kPostSaturationOn = 1.015f; +const Real kBloomThresholdStep = 0.05f; +const Real kBloomThresholdMin = 0.10f; + +} // namespace + SDL3GameEngine::SDL3GameEngine() : - m_sdlWindow(NULL) + m_sdlWindow(NULL), + m_textInputActive(false), + m_resizePending(false), + m_resizeReadyToApply(false), + m_letterboxActive(false), + m_pendingWidth(0), + m_pendingHeight(0), + m_resizeStableCount(0) { } @@ -61,6 +117,59 @@ void SDL3GameEngine::update() { pollSDL3Events(); GameEngine::update(); + + // TheSuperHackers @bugfix bobtista 13/07/2026 Idle while minimized like the Win32 engine did + // when iconic, instead of simulating and rendering at full speed in the background. Multiplayer + // keeps running its logic, and TheLAN stays serviced so lobby peers still see us. + if (m_sdlWindow != NULL) + { + while ((SDL_GetWindowFlags(m_sdlWindow) & SDL_WINDOW_MINIMIZED) != 0) + { + SDL_Delay(5); + serviceWindowsOS(); + + if (TheLAN != NULL) + { + TheLAN->setIsActive(isActive()); + TheLAN->update(); + } + + if (getQuitting() || TheGameLogic->isInInternetGame() || TheGameLogic->isInLanGame()) + { + break; + } + } + } + + static bool autoExitInitialized = false; + static Uint64 autoExitStartTicks = 0; + static int autoExitSeconds = 0; + if (!autoExitInitialized) + { + autoExitInitialized = true; + autoExitStartTicks = SDL_GetTicks(); + autoExitSeconds = GgcFlags::IntValue(GgcFlag_AutoExitSeconds); + } + if (autoExitSeconds > 0 && SDL_GetTicks() - autoExitStartTicks >= static_cast(autoExitSeconds) * MSEC_PER_SECOND) + { + // TheSuperHackers @bugfix bobtista 12/07/2026 Was std::exit(0), which + // bypassed engine shutdown: the texture-loader thread then died at + // atexit and unregistered into the already-destructed exception-handler + // thread list (0xC0000005 on every win64 harness exit). Quit through + // the engine like the window close path, so auto-exit runs exercise + // the same teardown players get. + setQuitting(true); + } + + // TheSuperHackers @bugfix bobtista 08/06/2026 Apply a settled window resize between + // frames; the full relayout destroys live GameWindow trees and crashes mid event-poll. + if (m_resizeReadyToApply) + { + m_resizeReadyToApply = false; + applyPendingWindowResize(); + } + + updatePresentMode(); } void SDL3GameEngine::serviceWindowsOS() @@ -78,20 +187,44 @@ void SDL3GameEngine::setIsActive(Bool isActive) m_isActive = isActive; } +// TheSuperHackers @bugfix bobtista 13/07/2026 Route window close through the message stream like +// the Win32 WM_CLOSE handler: in a match this opens the quit menu (and a repeat triggers the +// self destruct and a sequenced quit) instead of instantly killing the process without a clean +// multiplayer disconnect. +static void requestApplicationQuit(GameEngine *engine) +{ + if (engine != NULL && !engine->getQuitting()) + { + if (TheMessageStream != NULL && TheMessageStream->isReadyForMessages()) + { + TheMessageStream->appendMessage(GameMessage::MSG_META_DEMO_INSTANT_QUIT); + } + else + { + engine->setQuitting(TRUE); + } + } +} + void SDL3GameEngine::pollSDL3Events() { + updateTextInputState(); + SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_EVENT_QUIT: - setQuitting(true); + requestApplicationQuit(this); break; case SDL_EVENT_WINDOW_CLOSE_REQUESTED: case SDL_EVENT_WINDOW_FOCUS_GAINED: case SDL_EVENT_WINDOW_FOCUS_LOST: + case SDL_EVENT_WINDOW_MOUSE_ENTER: + case SDL_EVENT_WINDOW_MOUSE_LEAVE: + case SDL_EVENT_WINDOW_RESIZED: handleWindowEvent(event.window); break; @@ -100,6 +233,10 @@ void SDL3GameEngine::pollSDL3Events() handleKeyboardEvent(event.key); break; + case SDL_EVENT_TEXT_INPUT: + handleTextInputEvent(event.text); + break; + case SDL_EVENT_MOUSE_MOTION: handleMouseMotionEvent(event.motion); break; @@ -117,6 +254,184 @@ void SDL3GameEngine::pollSDL3Events() break; } } + + // TheSuperHackers @bugfix bobtista 08/06/2026 Sync the engine resolution only once the + // window size has been stable for a few polls; macOS fullscreen animates through + // intermediate sizes and applying one mid-transition resets the device on a stale size. + if (m_resizePending && m_sdlWindow != NULL) + { + int curW = 0; + int curH = 0; + SDL_GetWindowSize(m_sdlWindow, &curW, &curH); + if (curW == m_pendingWidth && curH == m_pendingHeight) + { + const Int kStablePolls = 8; + if (++m_resizeStableCount >= kStablePolls) + { + m_resizePending = false; + m_resizeStableCount = 0; + m_resizeReadyToApply = true; + } + } + else + { + m_pendingWidth = curW; + m_pendingHeight = curH; + m_resizeStableCount = 0; + } + } +} + +// TheSuperHackers @bugfix bobtista 25/06/2026 Defined in MainMenu.cpp; rebuilds the resolution +// confirmation dialog at the current resolution after a resize settles. +extern void RecreateResolutionDialogIfActive(); + +void SDL3GameEngine::applyPendingWindowResize() +{ + m_resizePending = false; + + // Read the actual, current window content size rather than the resize event's payload: macOS + // emits several events while a fullscreen transition settles, and we only want the final size. + int actualW = 0; + int actualH = 0; + if (m_sdlWindow != NULL) + { + SDL_GetWindowSize(m_sdlWindow, &actualW, &actualH); + // TheSuperHackers @bugfix bobtista 25/06/2026 In exclusive fullscreen, macOS transiently + // reports the pixel drawable size (not logical points) from SDL_GetWindowSize while the + // mode-switch settles, which corrupts the UI resolution for ~1s. The requested fullscreen + // mode is stable from the moment it is set, so prefer it to skip the transient size. + if (TheDisplay != NULL && !TheDisplay->getWindowed()) + { + const SDL_DisplayMode *fullscreenMode = SDL_GetWindowFullscreenMode(m_sdlWindow); + if (fullscreenMode != NULL && fullscreenMode->w > 0 && fullscreenMode->h > 0) + { + actualW = fullscreenMode->w; + actualH = fullscreenMode->h; + } + } + } + Int newWidth = actualW; + Int newHeight = actualH; + + // When the backend is letterboxing (multiplayer), the engine renders at the centered content + // size, not the full window: the camera aspect, 2D coordinate range and mouse bounds must match + // the content rect, with the bars left to the backend. The backend has already recomputed the + // content rect this frame (in Begin_Scene), so read it back as the target resolution. + if (g_renderBackend != NULL && g_renderBackend->Is_Present_Letterbox_Active()) + { + const Int contentW = g_renderBackend->Get_Present_Content_Width(); + const Int contentH = g_renderBackend->Get_Present_Content_Height(); + if (contentW > 0 && contentH > 0) + { + newWidth = contentW; + newHeight = contentH; + } + } + + if (TheDisplay == NULL || newWidth <= 0 || newHeight <= 0) + { + return; + } + + // Nothing to do if the engine is already at this size. + if ((Int)TheDisplay->getWidth() == newWidth && (Int)TheDisplay->getHeight() == newHeight) + { + return; + } + + DEBUG_LOG(("SDL3GameEngine::applyPendingWindowResize from %dx%d to %dx%d (windowed=%d)", + TheDisplay->getWidth(), TheDisplay->getHeight(), newWidth, newHeight, TheDisplay->getWindowed())); + + // Engine-side resolution sync only. applyExternalResize updates the render device's view of the + // resolution and the 2D coordinate range without touching the SDL window (bgfx already tracks + // the real size). This fixes the mouse mapping (display now equals the window) and the rendered + // viewport. + if (!TheDisplay->applyExternalResize(newWidth, newHeight)) + { + DEBUG_LOG(("SDL3GameEngine::applyPendingWindowResize applyExternalResize FAILED, size unchanged")); + return; + } + + if (TheWritableGlobalData != NULL) + { + TheWritableGlobalData->m_xResolution = newWidth; + TheWritableGlobalData->m_yResolution = newHeight; + } + + // Full relayout, matching the options-menu resolution-change sequence. This is safe here because + // applyPendingWindowResize() runs from SDL3GameEngine::update() (between frames), not from the SDL + // event poll: recreateWindowLayouts()/recreateControlBar() destroy and rebuild live GameWindow + // trees, which only crashed when invoked mid event-poll. + if (TheHeaderTemplateManager != NULL) + { + TheHeaderTemplateManager->onResolutionChanged(); + } + if (TheMouse != NULL) + { + TheMouse->onResolutionChanged(); + } + if (TheShell != NULL) + { + // TheSuperHackers @bugfix bobtista 09/06/2026 Do not rebuild the shell menu layouts + // while a real game is running. The menus are hidden behind the match and their backing + // state (e.g. TheLAN->GetMyGame()) may already be gone, so re-running their init callbacks + // would dereference null. Starting a multiplayer match enables letterboxing, which lands + // here mid-match. The shell is relaid out on the resize that fires when the match ends. + const Bool inRealGame = + (TheGameLogic != NULL && TheGameLogic->isInGame() && !TheGameLogic->isInShellGame()); + if (!inRealGame) + { + TheShell->recreateWindowLayouts(); + } + } + if (TheInGameUI != NULL) + { + // TheSuperHackers @bugfix bobtista 20/07/2026 Rebuild the whole in-game HUD for the new + // resolution (control bar, scheme, shortcut bar, radar, and the money/superweapon/timer caches + // that do not self-correct). Retail never changed resolution mid-match, so this path is new. + TheInGameUI->onResolutionChanged(); + } + if (TheTacticalView != NULL) + { + TheTacticalView->setDefaultView( + DEG_TO_RADF(TheGlobalData->m_cameraPitch), + DEG_TO_RADF(TheGlobalData->m_cameraYaw), + 1.0f); + TheTacticalView->setZoomToDefault(); + } + + // TheSuperHackers @bugfix bobtista 25/06/2026 The resolution-confirm dialog is a runtime modal, + // not part of the shell layouts recreated above, so rebuild it here too when it is open - + // otherwise it stays laid out at the pre-resize resolution and renders mis-scaled. + RecreateResolutionDialogIfActive(); + + DEBUG_LOG(("SDL3GameEngine::applyPendingWindowResize done at %dx%d", newWidth, newHeight)); +} + +void SDL3GameEngine::updatePresentMode() +{ + // TheSuperHackers @feature bobtista 08/06/2026 Letterboxing the present to a fixed 16:9 + // keeps every player's viewable area identical regardless of window/display aspect; the + // plumbing is kept intact but is not auto-forced for multiplayer. + if (g_renderBackend == NULL) + { + return; + } + + const Bool wantLetterbox = FALSE; + + if (wantLetterbox == m_letterboxActive) + { + return; + } + + m_letterboxActive = wantLetterbox; + g_renderBackend->Set_Present_Letterbox(wantLetterbox != FALSE, 16.0f, 9.0f); + + // Resync the engine display resolution, UI layout and mouse bounds to the new content rect on the + // next frame, once the backend has recomputed the letterbox layout in Begin_Scene. + m_resizeReadyToApply = true; } void SDL3GameEngine::handleKeyboardEvent(const SDL_KeyboardEvent &event) @@ -126,6 +441,235 @@ void SDL3GameEngine::handleKeyboardEvent(const SDL_KeyboardEvent &event) { keyboard->addSDL3KeyEvent(event); } + + // TheSuperHackers @feature bobtista 15/06/2026 Dev hotkeys to A/B the bgfx post + // effects live without restarting or editing INI. Hold Ctrl+Alt and press + // W (wipe), G (color grade), B (bloom), or D (desaturate). Each toggle also + // sets demonstrative parameter values so the change is clearly visible. + if (event.down != 0 && event.repeat == 0 && TheWritableGlobalData != NULL + && (event.mod & SDL_KMOD_CTRL) != 0 && (event.mod & SDL_KMOD_ALT) != 0) + { + GlobalData *gd = TheWritableGlobalData; + switch (event.scancode) + { + case SDL_SCANCODE_W: + // Cycle: OFF -> static center -> follow mouse -> OFF. + if (!gd->m_bgfxWipeEnabled) + { + gd->m_bgfxWipeEnabled = TRUE; + gd->m_bgfxWipeFollowMouse = FALSE; + gd->m_bgfxWipeSplit = 0.5f; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Wipe ON (center)"); } + } + else if (!gd->m_bgfxWipeFollowMouse) + { + gd->m_bgfxWipeFollowMouse = TRUE; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Wipe ON (follow mouse)"); } + } + else + { + gd->m_bgfxWipeEnabled = FALSE; + gd->m_bgfxWipeFollowMouse = FALSE; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Wipe OFF"); } + } + break; + case SDL_SCANCODE_G: + gd->m_bgfxColorGrade = !gd->m_bgfxColorGrade; + gd->m_bgfxColorGradeStrength = 1.0f; + gd->m_bgfxColorGradeTemperature = 2.5f; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Color Grade %s", gd->m_bgfxColorGrade ? L"ON" : L"OFF"); } + break; + case SDL_SCANCODE_B: + // Bloom is highlight-only and needs HDR to have over-bright content + // to catch, so enabling bloom also turns HDR on (and rebuilds the + // framebuffer for the format switch). + gd->m_bgfxBloom = !gd->m_bgfxBloom; + gd->m_bgfxBloomIntensity = 0.6f; + if (gd->m_bgfxBloom && !gd->m_bgfxHdr) + { + gd->m_bgfxHdr = TRUE; + GGC_RequestBgfxFramebufferRebuild(); + } + if (TheInGameUI != NULL) { TheInGameUI->message(L"Bloom %s threshold %d (HDR %s)", gd->m_bgfxBloom ? L"ON" : L"OFF", (Int)(gd->m_bgfxBloomThreshold * 100.0f), gd->m_bgfxHdr ? L"ON" : L"OFF"); } + break; + case SDL_SCANCODE_D: + // Saturation lives in the baseline post chain; make sure that master is + // on so the toggle is never silently swallowed. + gd->m_bgfxPostProcessing = TRUE; + gd->m_bgfxPostSaturation = (gd->m_bgfxPostSaturation > 0.5f) ? 0.0f : kPostSaturationOn; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Desaturate %s", (gd->m_bgfxPostSaturation < 0.5f) ? L"ON" : L"OFF"); } + break; + case SDL_SCANCODE_H: + // HDR changes the scene render-target format, so toggling it requires + // rebuilding the framebuffer (unlike the per-frame shader effects). + gd->m_bgfxHdr = !gd->m_bgfxHdr; + GGC_RequestBgfxFramebufferRebuild(); + if (TheInGameUI != NULL) { TheInGameUI->message(L"HDR %s", gd->m_bgfxHdr ? L"ON" : L"OFF"); } + break; + case SDL_SCANCODE_MINUS: + // Lower the bloom threshold (more blooms) to find the sweet spot live. + gd->m_bgfxBloomThreshold = (gd->m_bgfxBloomThreshold > kBloomThresholdMin + kBloomThresholdStep) ? (gd->m_bgfxBloomThreshold - kBloomThresholdStep) : kBloomThresholdMin; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Bloom threshold %d", (Int)(gd->m_bgfxBloomThreshold * 100.0f)); } + break; + case SDL_SCANCODE_EQUALS: + // Raise the bloom threshold (less blooms). + gd->m_bgfxBloomThreshold = gd->m_bgfxBloomThreshold + kBloomThresholdStep; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Bloom threshold %d", (Int)(gd->m_bgfxBloomThreshold * 100.0f)); } + break; + case SDL_SCANCODE_V: + gd->m_bgfxVignette = !gd->m_bgfxVignette; + gd->m_bgfxVignetteStrength = kToggleVignetteStrength; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Vignette %s", gd->m_bgfxVignette ? L"ON" : L"OFF"); } + break; + case SDL_SCANCODE_C: + gd->m_bgfxChromaticAberration = !gd->m_bgfxChromaticAberration; + gd->m_bgfxChromaticAberrationAmount = kToggleChromaAmount; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Chromatic Aberration %s", gd->m_bgfxChromaticAberration ? L"ON" : L"OFF"); } + break; + case SDL_SCANCODE_F: + gd->m_bgfxFilmGrain = !gd->m_bgfxFilmGrain; + gd->m_bgfxFilmGrainStrength = kToggleFilmGrainStrength; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Film Grain %s", gd->m_bgfxFilmGrain ? L"ON" : L"OFF"); } + break; + case SDL_SCANCODE_O: + // SSAO allocates depth + AO targets at framebuffer-creation time, so + // toggling it needs a rebuild like HDR. + gd->m_bgfxSSAO = !gd->m_bgfxSSAO; + GGC_RequestBgfxFramebufferRebuild(); + if (TheInGameUI != NULL) { TheInGameUI->message(L"SSAO %s", gd->m_bgfxSSAO ? L"ON" : L"OFF"); } + break; + case SDL_SCANCODE_M: + // Cycle scene MSAA 0 -> 2 -> 4 -> 8; the sample count is read at + // framebuffer creation, so rebuild on change. + if (gd->m_bgfxMsaa >= 8) { gd->m_bgfxMsaa = 0; } + else if (gd->m_bgfxMsaa >= 4) { gd->m_bgfxMsaa = 8; } + else if (gd->m_bgfxMsaa >= 2) { gd->m_bgfxMsaa = 4; } + else { gd->m_bgfxMsaa = 2; } + GGC_RequestBgfxFramebufferRebuild(); + if (TheInGameUI != NULL) + { + if (gd->m_bgfxMsaa > 0) { TheInGameUI->message(L"MSAA %dx", gd->m_bgfxMsaa); } + else { TheInGameUI->message(L"MSAA OFF"); } + } + break; + case SDL_SCANCODE_R: + // Cycle render scale 1.0 -> 1.25 -> 1.5 -> 2.0; read at framebuffer + // creation, so rebuild on change. + if (gd->m_bgfxRenderScale >= 1.99f) { gd->m_bgfxRenderScale = 1.0f; } + else if (gd->m_bgfxRenderScale >= 1.49f) { gd->m_bgfxRenderScale = 2.0f; } + else if (gd->m_bgfxRenderScale >= 1.24f) { gd->m_bgfxRenderScale = 1.5f; } + else { gd->m_bgfxRenderScale = 1.25f; } + GGC_RequestBgfxFramebufferRebuild(); + if (TheInGameUI != NULL) { TheInGameUI->message(L"Render scale %d%%", (Int)(gd->m_bgfxRenderScale * 100.0f + 0.5f)); } + break; + case SDL_SCANCODE_P: + gd->m_bgfxSpecular = !gd->m_bgfxSpecular; + gd->m_bgfxSpecularStrength = kToggleSpecularStrength; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Specular %s", gd->m_bgfxSpecular ? L"ON" : L"OFF"); } + break; + case SDL_SCANCODE_L: + gd->m_bgfxRimLight = !gd->m_bgfxRimLight; + gd->m_bgfxRimStrength = kToggleRimStrength; + gd->m_bgfxRimPower = kToggleRimPower; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Rim Light %s", gd->m_bgfxRimLight ? L"ON" : L"OFF"); } + break; + case SDL_SCANCODE_K: + gd->m_bgfxEmissiveBoost = !gd->m_bgfxEmissiveBoost; + gd->m_bgfxEmissiveBoostScale = kToggleEmissiveBoostScale; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Emissive Boost %s", gd->m_bgfxEmissiveBoost ? L"ON" : L"OFF"); } + break; + case SDL_SCANCODE_Y: + // Sun shadow map allocates a light-POV depth target at framebuffer + // creation, so toggling it needs a rebuild like HDR/SSAO. + gd->m_bgfxShadowMaps = !gd->m_bgfxShadowMaps; + GGC_RequestBgfxFramebufferRebuild(); + if (TheInGameUI != NULL) { TheInGameUI->message(L"Sun Shadows %s", gd->m_bgfxShadowMaps ? L"ON" : L"OFF"); } + break; + case SDL_SCANCODE_N: + // TheSuperHackers @feature bobtista 23/06/2026 Toggle perspective point-light + // shadow map (e.g. nuke fireball). No rebuild needed; the pass is gated per-frame. + gd->m_bgfxDynamicLightShadows = !gd->m_bgfxDynamicLightShadows; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Dynamic Light Shadows %s", gd->m_bgfxDynamicLightShadows ? L"ON" : L"OFF"); } + break; + case SDL_SCANCODE_T: + // Force nearest/point texture filtering (old blocky look) vs the smooth + // linear/trilinear renderer baseline. Applies at bind time, no rebuild. + gd->m_bgfxPointFilter = !gd->m_bgfxPointFilter; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Texture Filter: %s", gd->m_bgfxPointFilter ? L"Blocky (point)" : L"Smooth (linear)"); } + break; + case SDL_SCANCODE_X: + // Master toggle for the always-on baseline post (FXAA + sharpen + + // saturation + contrast). Lets the baked-in look be A/B'd as a whole. + gd->m_bgfxPostProcessing = !gd->m_bgfxPostProcessing; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Baseline Post %s", gd->m_bgfxPostProcessing ? L"ON" : L"OFF"); } + break; + case SDL_SCANCODE_LEFTBRACKET: + gd->m_bgfxPostProcessing = TRUE; + gd->m_bgfxPostFxaaAmount = (gd->m_bgfxPostFxaaAmount > 0.05f) ? (gd->m_bgfxPostFxaaAmount - 0.05f) : 0.0f; + if (TheInGameUI != NULL) { TheInGameUI->message(L"FXAA %d%%", (Int)(gd->m_bgfxPostFxaaAmount * 100.0f + 0.5f)); } + break; + case SDL_SCANCODE_RIGHTBRACKET: + gd->m_bgfxPostProcessing = TRUE; + gd->m_bgfxPostFxaaAmount = (gd->m_bgfxPostFxaaAmount < 0.95f) ? (gd->m_bgfxPostFxaaAmount + 0.05f) : 1.0f; + if (TheInGameUI != NULL) { TheInGameUI->message(L"FXAA %d%%", (Int)(gd->m_bgfxPostFxaaAmount * 100.0f + 0.5f)); } + break; + case SDL_SCANCODE_SEMICOLON: + gd->m_bgfxPostProcessing = TRUE; + gd->m_bgfxPostSharpenAmount = (gd->m_bgfxPostSharpenAmount > 0.02f) ? (gd->m_bgfxPostSharpenAmount - 0.02f) : 0.0f; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Sharpen %d%%", (Int)(gd->m_bgfxPostSharpenAmount * 100.0f + 0.5f)); } + break; + case SDL_SCANCODE_APOSTROPHE: + gd->m_bgfxPostProcessing = TRUE; + gd->m_bgfxPostSharpenAmount = (gd->m_bgfxPostSharpenAmount < 0.98f) ? (gd->m_bgfxPostSharpenAmount + 0.02f) : 1.0f; + if (TheInGameUI != NULL) { TheInGameUI->message(L"Sharpen %d%%", (Int)(gd->m_bgfxPostSharpenAmount * 100.0f + 0.5f)); } + break; + default: + break; + } + } + + // TheSuperHackers @bugfix bobtista 09/06/2026 SDL does not emit a TEXT_INPUT event for Return, + // and the text-entry gadget only commits on GWM_IME_CHAR(VK_RETURN) - the form the Windows IME + // delivers. Bridge Return here so chat and other text fields submit on Enter. Only text-entry + // gadgets act on GWM_IME_CHAR(VK_RETURN); the regular key path still drives buttons and lists. + if (event.down != 0 && event.repeat == 0 && TheWindowManager != NULL && + (event.scancode == SDL_SCANCODE_RETURN || event.scancode == SDL_SCANCODE_KP_ENTER)) + { + GameWindow *focus = TheWindowManager->winGetFocus(); + if (focus != NULL) + { + TheWindowManager->winSendInputMsg(focus, GWM_IME_CHAR, VK_RETURN, 0); + } + } +} + +void SDL3GameEngine::handleTextInputEvent(const SDL_TextInputEvent &event) +{ + if (TheWindowManager == NULL || event.text == NULL) + { + return; + } + + GameWindow *window = TheWindowManager->winGetFocus(); + if (window == NULL) + { + return; + } + + const char *text = event.text; + size_t remaining = SDL_strlen(text); + while (remaining > 0) + { + Uint32 codepoint = SDL_StepUTF8(&text, &remaining); + if (codepoint == SDL_INVALID_UNICODE_CODEPOINT) + { + continue; + } + if (codepoint >= 32 || codepoint == '\n') + { + TheWindowManager->winSendInputMsg(window, GWM_IME_CHAR, static_cast(codepoint), 0); + } + } } void SDL3GameEngine::handleMouseMotionEvent(const SDL_MouseMotionEvent &event) @@ -159,15 +703,102 @@ void SDL3GameEngine::handleWindowEvent(const SDL_WindowEvent &event) { if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) { - setQuitting(true); + requestApplicationQuit(this); } else if (event.type == SDL_EVENT_WINDOW_FOCUS_GAINED) { setIsActive(true); + if (TheKeyboard != NULL) + { + TheKeyboard->resetKeys(); + } + if (TheMouse != NULL) + { + TheMouse->regainFocus(); + if (SDL_GetMouseFocus() == m_sdlWindow) + { + TheMouse->onCursorMovedInside(); + } + else if (TheMouse->isCursorInside()) + { + TheMouse->onCursorMovedOutside(); + } + } } else if (event.type == SDL_EVENT_WINDOW_FOCUS_LOST) { setIsActive(false); + if (TheKeyboard != NULL) + { + TheKeyboard->resetKeys(); + } + if (TheMouse != NULL) + { + TheMouse->loseFocus(); + if (TheMouse->isCursorInside()) + { + TheMouse->onCursorMovedOutside(); + } + } + } + else if (event.type == SDL_EVENT_WINDOW_MOUSE_ENTER) + { + if (TheMouse != NULL) + { + TheMouse->onCursorMovedInside(); + // TheSuperHackers @bugfix bobtista 11/07/2026 The window frequently + // appears under an already-stationary cursor (save loads launched from + // a harness or shortcut), in which case SDL never sends a motion event. + // Reconcile the engine's position with the real cursor; reading the + // live OS state makes this safe to repeat. + TheMouse->syncPositionToSystemCursor(); + } + } + else if (event.type == SDL_EVENT_WINDOW_MOUSE_LEAVE) + { + if (TheMouse != NULL && TheMouse->isCursorInside()) + { + TheMouse->onCursorMovedOutside(); + } + } + else if (event.type == SDL_EVENT_WINDOW_RESIZED) + { + // data1/data2 carry the new window size in logical points, matching the units used by + // SDL_GetWindowSize() and the mouse mapping. Restart the settle timer so we only apply once + // the size stops changing. + m_pendingWidth = event.data1; + m_pendingHeight = event.data2; + m_resizePending = true; + m_resizeStableCount = 0; + } +} + +void SDL3GameEngine::updateTextInputState() +{ + if (m_sdlWindow == NULL) + { + return; + } + + Bool wantsTextInput = FALSE; + if (TheWindowManager != NULL && isActive()) + { + GameWindow *focus = TheWindowManager->winGetFocus(); + if (focus != NULL) + { + const UnsignedInt style = focus->winGetStyle(); + wantsTextInput = ((style & GWS_ENTRY_FIELD) != 0 || (style & GWS_COMBO_BOX) != 0); + } + } + + if (wantsTextInput && !m_textInputActive) + { + m_textInputActive = SDL_StartTextInput(m_sdlWindow) ? TRUE : FALSE; + } + else if (!wantsTextInput && m_textInputActive) + { + SDL_StopTextInput(m_sdlWindow); + m_textInputActive = FALSE; } } @@ -228,6 +859,13 @@ WebBrowser *SDL3GameEngine::createWebBrowser() AudioManager *SDL3GameEngine::createAudioManager(Bool dummy) { #if defined(SAGE_USE_OPENAL) + // TheSuperHackers @bugfix bobtista 30/04/2026 GGC_NO_AUDIO=1 forces the dummy audio + // manager even when not headless (useful on any SDL3+OpenAL platform); only truthy + // values are honored so a leftover GGC_NO_AUDIO=0 does not silently disable audio. + if (GgcFlags::Enabled(GgcFlag_NoAudio)) + { + dummy = TRUE; + } return NEW OpenALAudioManager(dummy); #endif return NULL; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DBufferManager.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DBufferManager.cpp index 37743247ef6..3cfcc4b92e6 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DBufferManager.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DBufferManager.cpp @@ -25,29 +25,31 @@ #include "Common/Debug.h" #include "W3DDevice/GameClient/W3DBufferManager.h" +#include "WW3D2/dx8fvf.h" +#include "WW3D2/renderbufferclasses.h" W3DBufferManager *TheW3DBufferManager=nullptr; //singleton static int FVFTypeIndexList[W3DBufferManager::MAX_FVF]= { - D3DFVF_XYZ, - D3DFVF_XYZ|D3DFVF_DIFFUSE, - D3DFVF_XYZ|D3DFVF_TEX1, - D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1, - D3DFVF_XYZ|D3DFVF_TEX2, - D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX2, - D3DFVF_XYZ|D3DFVF_NORMAL, - D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_DIFFUSE, - D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1, - D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_DIFFUSE|D3DFVF_TEX1, - D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX2, - D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_DIFFUSE|D3DFVF_TEX2, - D3DFVF_XYZRHW, - D3DFVF_XYZRHW|D3DFVF_DIFFUSE, - D3DFVF_XYZRHW|D3DFVF_TEX1, - D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1, - D3DFVF_XYZRHW|D3DFVF_TEX2, - D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX2 + RENDER_VERTEX_FORMAT_XYZ, + RENDER_VERTEX_FORMAT_XYZD, + RENDER_VERTEX_FORMAT_XYZUV1, + RENDER_VERTEX_FORMAT_XYZDUV1, + RENDER_VERTEX_FORMAT_XYZUV2, + RENDER_VERTEX_FORMAT_XYZDUV2, + RENDER_VERTEX_FORMAT_XYZN, + RENDER_VERTEX_FORMAT_XYZND, + RENDER_VERTEX_FORMAT_XYZNUV1, + RENDER_VERTEX_FORMAT_XYZNDUV1, + RENDER_VERTEX_FORMAT_XYZNUV2, + RENDER_VERTEX_FORMAT_XYZNDUV2, + RENDER_VERTEX_FORMAT_XYZRHW, + RENDER_VERTEX_FORMAT_XYZRHWD, + RENDER_VERTEX_FORMAT_XYZRHWUV1, + RENDER_VERTEX_FORMAT_XYZRHWDUV1, + RENDER_VERTEX_FORMAT_XYZRHWUV2, + RENDER_VERTEX_FORMAT_XYZRHWDUV2 }; Int W3DBufferManager::getDX8Format(VBM_FVF_TYPES format) @@ -141,7 +143,7 @@ void W3DBufferManager::freeAllBuffers() W3DVertexBuffer *vb = m_W3DVertexBuffers[i]; while (vb) { DEBUG_ASSERTCRASH(vb->m_usedSlots == nullptr, ("Freeing Non-Empty Vertex Buffer")); - REF_PTR_RELEASE(vb->m_DX8VertexBuffer); + REF_PTR_RELEASE(vb->m_renderVertexBuffer); m_numEmptyVertexBuffersAllocated--; vb=vb->m_nextVB; //get next vertex buffer of this type } @@ -151,7 +153,7 @@ void W3DBufferManager::freeAllBuffers() W3DIndexBuffer *ib = m_W3DIndexBuffers; while (ib) { DEBUG_ASSERTCRASH(ib->m_usedSlots == nullptr, ("Freeing Non-Empty Index Buffer")); - REF_PTR_RELEASE(ib->m_DX8IndexBuffer); + REF_PTR_RELEASE(ib->m_renderIndexBuffer); m_numEmptyIndexBuffersAllocated--; ib=ib->m_nextIB; //get next vertex buffer of this type } @@ -168,7 +170,7 @@ void W3DBufferManager::ReleaseResources() W3DVertexBuffer *vb = m_W3DVertexBuffers[i]; while (vb) { - REF_PTR_RELEASE(vb->m_DX8VertexBuffer); + REF_PTR_RELEASE(vb->m_renderVertexBuffer); vb=vb->m_nextVB; //get next vertex buffer of this type } } @@ -176,7 +178,7 @@ void W3DBufferManager::ReleaseResources() W3DIndexBuffer *ib = m_W3DIndexBuffers; while (ib) { - REF_PTR_RELEASE(ib->m_DX8IndexBuffer); + REF_PTR_RELEASE(ib->m_renderIndexBuffer); ib=ib->m_nextIB; //get next vertex buffer of this type } } @@ -187,10 +189,10 @@ Bool W3DBufferManager::ReAcquireResources() { W3DVertexBuffer *vb = m_W3DVertexBuffers[i]; while (vb) - { DEBUG_ASSERTCRASH( vb->m_DX8VertexBuffer == nullptr, ("ReAcquire of existing vertex buffer")); - vb->m_DX8VertexBuffer=NEW_REF(DX8VertexBufferClass,(FVFTypeIndexList[vb->m_format],vb->m_size,DX8VertexBufferClass::USAGE_DEFAULT)); - DEBUG_ASSERTCRASH( vb->m_DX8VertexBuffer, ("Failed ReAcquire of vertex buffer")); - if (!vb->m_DX8VertexBuffer) + { DEBUG_ASSERTCRASH( vb->m_renderVertexBuffer == nullptr, ("ReAcquire of existing vertex buffer")); + vb->m_renderVertexBuffer=NEW_REF(RenderVertexBufferClass,(FVFTypeIndexList[vb->m_format],vb->m_size,Render_Buffer_Usage_Default())); + DEBUG_ASSERTCRASH( vb->m_renderVertexBuffer, ("Failed ReAcquire of vertex buffer")); + if (!vb->m_renderVertexBuffer) return FALSE; vb=vb->m_nextVB; //get next vertex buffer of this type } @@ -198,10 +200,10 @@ Bool W3DBufferManager::ReAcquireResources() W3DIndexBuffer *ib = m_W3DIndexBuffers; while (ib) - { DEBUG_ASSERTCRASH( ib->m_DX8IndexBuffer == nullptr, ("ReAcquire of existing index buffer")); - ib->m_DX8IndexBuffer=NEW_REF(DX8IndexBufferClass,(ib->m_size,DX8IndexBufferClass::USAGE_DEFAULT)); - DEBUG_ASSERTCRASH( ib->m_DX8IndexBuffer, ("Failed ReAcquire of index buffer")); - if (!ib->m_DX8IndexBuffer) + { DEBUG_ASSERTCRASH( ib->m_renderIndexBuffer == nullptr, ("ReAcquire of existing index buffer")); + ib->m_renderIndexBuffer=NEW_REF(RenderIndexBufferClass,(ib->m_size,Render_Buffer_Usage_Default())); + DEBUG_ASSERTCRASH( ib->m_renderIndexBuffer, ("Failed ReAcquire of index buffer")); + if (!ib->m_renderIndexBuffer) return FALSE; ib=ib->m_nextIB; //get next vertex buffer of this type } @@ -310,7 +312,7 @@ W3DBufferManager::W3DVertexBufferSlot * W3DBufferManager::allocateSlotStorage(VB Int vbSize=__max(DEFAULT_VERTEX_BUFFER_SIZE,size); - pVB->m_DX8VertexBuffer=NEW_REF(DX8VertexBufferClass,(FVFTypeIndexList[fvfType],vbSize,DX8VertexBufferClass::USAGE_DEFAULT)); + pVB->m_renderVertexBuffer=NEW_REF(RenderVertexBufferClass,(FVFTypeIndexList[fvfType],vbSize,Render_Buffer_Usage_Default())); pVB->m_format=fvfType; pVB->m_startFreeIndex=size; pVB->m_size=vbSize; @@ -430,7 +432,7 @@ W3DBufferManager::W3DIndexBufferSlot * W3DBufferManager::allocateSlotStorage(Int Int ibSize=__max(DEFAULT_INDEX_BUFFER_SIZE,size); - pIB->m_DX8IndexBuffer=NEW_REF(DX8IndexBufferClass,(ibSize,DX8IndexBufferClass::USAGE_DEFAULT)); + pIB->m_renderIndexBuffer=NEW_REF(RenderIndexBufferClass,(ibSize,Render_Buffer_Usage_Default())); pIB->m_startFreeIndex=size; pIB->m_size=ibSize; ibSlot=&m_W3DIndexBufferEmptySlots[m_numEmptyIndexSlotsAllocated]; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp index 39eafb73785..07513c3408f 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp @@ -33,10 +33,12 @@ // USER INCLUDES ////////////////////////////////////////////////////////////// #include "WWLib/always.h" +#include "GgcRuntimeFlags.h" #include "GameClient/View.h" #include "WW3D2/camera.h" #include "WW3D2/light.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/renderbufferclasses.h" #include "WW3D2/hlod.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" @@ -45,7 +47,6 @@ #include "WW3D2/dx8renderer.h" #include "Lib/BaseType.h" #include "W3DDevice/GameClient/HeightMap.h" -#include "d3dx8math.h" #include "Common/GlobalData.h" #include "W3DDevice/GameClient/W3DProjectedShadow.h" #include "WW3D2/statistics.h" @@ -56,6 +57,11 @@ #include "GameClient/Drawable.h" #include "W3DDevice/GameClient/Module/W3DModelDraw.h" #include "W3DDevice/GameClient/W3DShadow.h" +#include "Common/ThingTemplate.h" + +#include +#include +#include /** @todo: We're going to have a pool of a couple rendertargets to use @@ -78,8 +84,8 @@ W3DProjectedShadowManager *TheW3DProjectedShadowManager=nullptr; //global single ProjectedShadowManager *TheProjectedShadowManager; //global singleton with simpler interface. extern const FrustumClass *shadowCameraFrustum; //defined in W3DShadow. ///@todo: Externs from volumetric shadow renderer - these need to be moved into W3DBufferManager -extern LPDIRECT3DVERTEXBUFFER8 shadowVertexBufferD3D; ///getTemplate() != nullptr + ? draw->getTemplate()->getName().str() + : "(null-drawable)"; +} + +static void LogProjectedShadowPath(const char *event, + RenderObjClass *robj, + Drawable *draw, + const Shadow::ShadowTypeInfo *shadowInfo, + ShadowType resolvedType, + const char *textureName, + Bool allowWorldAlign, + Bool allowSunDirection, + Real sizeX, + Real sizeY, + Real offsetX, + Real offsetY) +{ + if (!ShadowPathDiagEnabled()) + return; + + if (FILE *diag = std::fopen("ggc_shadow_path_diag.txt", "a")) + { + const ShadowType requestedType = shadowInfo != nullptr ? shadowInfo->m_type : SHADOW_NONE; + std::fprintf(diag, + "%s projected resolved=%s resolvedMask=0x%x requested=%s requestedMask=0x%x texture=%s robj=%s drawable=%u template=%s worldAlign=%d sunDirection=%d size=(%.2f,%.2f) offset=(%.2f,%.2f)\n", + event, + ShadowTypeDebugName(resolvedType), + static_cast(resolvedType), + ShadowTypeDebugName(requestedType), + static_cast(requestedType), + textureName != nullptr ? textureName : "(null-texture)", + robj != nullptr && robj->Get_Name() != nullptr ? robj->Get_Name() : "(null-robj)", + draw != nullptr ? static_cast(draw->getID()) : 0, + DrawableTemplateName(draw), + allowWorldAlign ? 1 : 0, + allowSunDirection ? 1 : 0, + sizeX, + sizeY, + offsetX, + offsetY); + std::fclose(diag); + } +} + +static bool ShouldSkipDefaultBlobShadows() +{ + return GgcFlags::Enabled(GgcFlag_BgfxEnableDiagnosticOverrides) + && GgcFlags::Enabled(GgcFlag_BgfxSkipBlobShadows); +} class W3DShadowTexture; //forward reference class W3DShadowTextureManager; //forward reference @@ -147,7 +239,7 @@ class W3DShadowTexture : public RefCountClass, public HashableClass public: W3DShadowTexture() - { m_lastLightPosition.Set(0,0,0); m_lastObjectOrientation.Make_Identity(); + { m_texture=nullptr; m_lastLightPosition.Set(1.0e30f,1.0e30f,1.0e30f); m_lastObjectOrientation.Make_Identity(); m_shadowUV[0].Set(1.0f,0.0f,0.0f); //u runs along world x axis m_shadowUV[1].Set(0.0f,-1.0f,0.0f); //v runs along world -y axis } @@ -188,6 +280,41 @@ class W3DShadowTexture : public RefCountClass, public HashableClass Vector3 m_shadowUV[2]; ///world-space vectors defining the u and v texture coordinate axis. }; +static bool IsDefaultInfantryBlobShadowName(const char *name) +{ + return name != nullptr + && (_stricmp(name, "shadowi.tga") == 0 + || _stricmp(name, "shadowi.dds") == 0 + || _stricmp(name, "shadowi") == 0); +} + +static bool IsDefaultInfantryBlobShadowDecal(W3DShadowTexture *texture, ShadowType type) +{ + return type == SHADOW_DECAL + && texture != nullptr + && IsDefaultInfantryBlobShadowName(texture->Get_Name()); +} + +static RenderBackendProjectedDecalMode GetProjectedDecalMode(W3DShadowTexture *texture, ShadowType type) +{ + if (IsDefaultInfantryBlobShadowDecal(texture, type)) + { + return RB_PROJECTED_DECAL_BLOB_SHADOW; + } + + switch (type) + { + case SHADOW_ADDITIVE_DECAL: + return RB_PROJECTED_DECAL_ADDITIVE; + case SHADOW_ALPHA_DECAL: + return RB_PROJECTED_DECAL_ALPHA; + case SHADOW_DECAL: + return RB_PROJECTED_DECAL_MULTIPLY; + default: + return RB_PROJECTED_DECAL_NONE; + } +} + /* ** An Iterator to get to all loaded W3DShadowGeometries in a W3DShadowGeometryManager */ @@ -261,41 +388,49 @@ Bool W3DProjectedShadowManager::ReAcquireResources() DEBUG_ASSERTCRASH(m_dynamicRenderTarget == nullptr, ("Acquire of existing shadow render target")); 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(); + 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")); + shadowDecalIndexBuffer = NEW_REF(RenderIndexBufferClass, (SHADOW_DECAL_INDEX_SIZE, Render_Buffer_Usage_Dynamic())); + if (shadowDecalIndexBuffer == nullptr) + return FALSE; + + if (shadowDecalVertexBuffer == nullptr) + { + shadowDecalVertexBuffer = NEW_REF(RenderVertexBufferClass, (SHADOW_DECAL_FVF, SHADOW_DECAL_VERTEX_SIZE, Render_Buffer_Usage_Dynamic())); + if (shadowDecalVertexBuffer == nullptr) + return FALSE; + } - if (FAILED(m_pDev->CreateIndexBuffer - ( - SHADOW_DECAL_INDEX_SIZE*sizeof(WORD), - D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC, - D3DFMT_INDEX16, - D3DPOOL_DEFAULT, - &shadowDecalIndexBufferD3D - ))) + // TheSuperHackers @bugfix bobtista 31/05/2026 Dedicated buffers for the decal-list pass. + decalListIndexBuffer = NEW_REF(RenderIndexBufferClass, (SHADOW_DECAL_INDEX_SIZE, Render_Buffer_Usage_Dynamic())); + if (decalListIndexBuffer == 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 (decalListVertexBuffer == nullptr) + { + decalListVertexBuffer = NEW_REF(RenderVertexBufferClass, (SHADOW_DECAL_FVF, SHADOW_DECAL_VERTEX_SIZE, Render_Buffer_Usage_Dynamic())); + if (decalListVertexBuffer == nullptr) + return FALSE; + } + + // TheSuperHackers @bugfix bobtista 01/06/2026 Dedicated buffers for the infantry blob batch. + blobDecalIndexBuffer = NEW_REF(RenderIndexBufferClass, (SHADOW_DECAL_INDEX_SIZE, Render_Buffer_Usage_Dynamic())); + if (blobDecalIndexBuffer == nullptr) + return FALSE; + + if (blobDecalVertexBuffer == nullptr) + { + blobDecalVertexBuffer = NEW_REF(RenderVertexBufferClass, (SHADOW_DECAL_FVF, SHADOW_DECAL_VERTEX_SIZE, Render_Buffer_Usage_Dynamic())); + if (blobDecalVertexBuffer == nullptr) return FALSE; } @@ -306,12 +441,12 @@ 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); + REF_PTR_RELEASE(decalListIndexBuffer); + REF_PTR_RELEASE(decalListVertexBuffer); + REF_PTR_RELEASE(blobDecalIndexBuffer); + REF_PTR_RELEASE(blobDecalVertexBuffer); } void W3DProjectedShadowManager::invalidateCachedLightPositions() @@ -354,7 +489,7 @@ Int W3DProjectedShadowManager::renderProjectedTerrainShadow(W3DProjectedShadow * Bool flipForBlend; - #define SHADOW_VOLUME_FVF D3DFVF_XYZ + #define SHADOW_VOLUME_FVF RENDER_VERTEX_FORMAT_XYZ if (TheTerrainRenderObject) { @@ -368,9 +503,9 @@ Int W3DProjectedShadowManager::renderProjectedTerrainShadow(W3DProjectedShadow * Real mapScaleInv=1.0f/MAP_XY_FACTOR; SHADOW_VOLUME_VERTEX* pvVertices; UnsignedShort *pvIndices; - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); - if (!m_pDev) return 0; + if (!g_renderBackend || g_renderBackend->Is_Device_Lost()) + return 0; //Get terrain cell index for area with shadow Int startX=REAL_TO_INT_FLOOR(((cx - dx)*mapScaleInv)); @@ -392,53 +527,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 ? RB_LOCK_DISCARD : RB_LOCK_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 ? RB_LOCK_DISCARD : RB_LOCK_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. The caller has just installed the + // TexProjectClass material pass; keep its projected texture stages + // intact so bgfx can render building floor emblems onto the terrain. + 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); + g_renderBackend->Override_Terrain_Blend(false); 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, 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); + 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()) + if (g_renderBackend->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 01/05/2026 Submit projected + // terrain shadows as regular material draws in bgfx. Routing these + // flat receiver quads through the shadow-volume path drops the + // projector texture entirely; the normal uber path has the texture + // transform and blend state needed for floor emblems. + g_renderBackend->Draw_Triangles(nShadowStartBatchIndex, numPolys, 0, numVerts); } - m_pDev->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); //should reject background pixels - m_pDev->SetRenderState( D3DRS_STENCILENABLE, FALSE ); -// m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); - m_pDev->SetRenderState( D3DRS_LIGHTING, TRUE); + g_renderBackend->Override_Alpha_Test(false, 0, RB_CMP_ALWAYS); //disable atest + g_renderBackend->Set_Stencil_Enable(false); + g_renderBackend->Set_Lighting_Enable(true); nShadowVertsInBuf += numVerts; nShadowStartBatchVertex=nShadowVertsInBuf; @@ -529,149 +669,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; - } - - 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 ); - 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 -} - -//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); - DX8Wrapper::Set_Material(vmat); - REF_PTR_RELEASE(vmat); - DX8Wrapper::Set_Shader(ShaderClass::_PresetOpaqueShader); - - Matrix3D tm(1); //identity - DX8Wrapper::Set_Transform(D3DTS_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) { @@ -682,118 +679,86 @@ void W3DProjectedShadowManager::flushDecals(W3DShadowTexture *texture, ShadowTyp return; } - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); - if (!m_pDev) return; //no D3D Device to render + if (DecalDiagEnabled()) + { + TextureClass *tex = texture ? texture->getTexture() : nullptr; + std::fprintf(stderr, + "[GGC_DECAL] flush type=%s name=%s tex=%s verts=%d polys=%d startV=%d startI=%d\n", + ShadowTypeDebugName(type), + texture ? texture->Get_Name() : "(null-shadow-texture)", + tex ? tex->Get_Full_Path().str() : "(null-texture)", + nShadowDecalVertsInBatch, + nShadowDecalPolysInBatch, + nShadowDecalStartBatchVertex, + nShadowDecalStartBatchIndex); + } + + if (!g_renderBackend || g_renderBackend->Is_Device_Lost()) + return; //no D3D Device to render 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()); + // Decal textures are authored with transparent/black padding outside the + // useful image. The load path marks them clamp, but bgfx samples from the + // current stage state at submit time, so make the intended state explicit. + g_renderBackend->Set_Texture_Clamp_Mode(0, true, true); -// 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; } -// DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0x60); -// DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_GREATEREQUAL); - //_PresetAlphaSpriteShader - - DX8Wrapper::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); -*/ - + g_renderBackend->Apply_Render_State_Changes(); //force update of view and projection matrices - m_pDev->SetIndices(shadowDecalIndexBufferD3D,nShadowDecalStartBatchVertex); - m_pDev->SetTransform(D3DTS_WORLD,(_D3DMATRIX *)&mWorld); + // 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->SetStreamSource(0,shadowDecalVertexBufferD3D,sizeof(SHADOW_DECAL_VERTEX)); - m_pDev->SetVertexShader(SHADOW_DECAL_FVF); + g_renderBackend->Set_Index_Buffer(shadowDecalIndexBuffer, nShadowDecalStartBatchVertex); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD, reinterpret_cast(mWorld)); -//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 ); -*/ -/* 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 ); -*/ -//m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); //useful to see bounds + g_renderBackend->Set_Vertex_Buffer(shadowDecalVertexBuffer, 0); + g_renderBackend->Set_Vertex_Shader(SHADOW_DECAL_FVF); - if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) + if (g_renderBackend->Is_Triangle_Draw_Enabled()) { Debug_Statistics::Record_DX8_Polys_And_Vertices(nShadowDecalPolysInBatch,nShadowDecalVertsInBatch,ShaderClass::_PresetOpaqueShader); - m_pDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,nShadowDecalVertsInBatch,nShadowDecalStartBatchIndex,nShadowDecalPolysInBatch); + // TheSuperHackers @bugfix bobtista 30/04/2026 Skip only the + // default blob shadow texture. SHADOW_DECAL also carries authored + // ground decals such as faction floor emblems, which still need to + // render in the bgfx path. + const RenderBackendProjectedDecalMode projectedDecalMode = GetProjectedDecalMode(texture, type); + const bool isDefaultBlobShadowDecal = projectedDecalMode == RB_PROJECTED_DECAL_BLOB_SHADOW; + if (isDefaultBlobShadowDecal && ShouldSkipDefaultBlobShadows()) + { + g_renderBackend->Skip_Next_Bgfx_Submit(); + } + g_renderBackend->Set_Projected_Decal_Mode(projectedDecalMode); + g_renderBackend->Draw_Triangles(nShadowDecalStartBatchIndex, nShadowDecalPolysInBatch, 0, nShadowDecalVertsInBatch); + g_renderBackend->Set_Projected_Decal_Mode(RB_PROJECTED_DECAL_NONE); } -// m_pDev->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); //should reject background pixels -// m_pDev->SetRenderState( D3DRS_STENCILENABLE, FALSE ); -//m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); - - - //Restore multiplicative sprite shader -// m_pDev->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_SRCCOLOR); //restore W3D state -// m_pDev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO); - -/* m_pDev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); - m_pDev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); - m_pDev->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_CURRENT); - m_pDev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); - m_pDev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); - m_pDev->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_CURRENT); -*/ + g_renderBackend->Set_Vertex_Buffer(nullptr, 0); + g_renderBackend->Set_Index_Buffer(nullptr, 0); nShadowDecalStartBatchVertex=nShadowDecalVertsInBuf; nShadowDecalStartBatchIndex=nShadowDecalIndicesInBuf; nShadowDecalPolysInBatch=0; //reset number of polys in texture batch 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 @@ -816,9 +781,9 @@ void W3DProjectedShadowManager::queueDecal(W3DProjectedShadow *shadow) if (TheTerrainRenderObject) { - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); - - if (!m_pDev) return; //no D3D Device to render + if (!g_renderBackend || g_renderBackend->Is_Device_Lost()) { + return; + } WorldHeightMap *hmap=TheTerrainRenderObject->getMap(); borderSize=hmap->getBorderSizeInline(); @@ -996,24 +961,40 @@ void W3DProjectedShadowManager::queueDecal(W3DProjectedShadow *shadow) Int numVerts = vertsPerRow *vertsPerColumn; //number of terrain vertices Int numIndex=(endX - startX) * (endY-startY)*6; //6 indices per terrain cell (2 triangles). + if (DecalDiagEnabled()) + { + std::fprintf(stderr, + "[GGC_DECAL] queue type=%s name=%s robj=%s visible=%d pos=(%.2f,%.2f,%.2f) cells=%d,%d..%d,%d verts=%d indices=%d diffuse=0x%08x size=(%.2f,%.2f)\n", + ShadowTypeDebugName(shadow->m_type), + shadow->m_shadowTexture[0] ? shadow->m_shadowTexture[0]->Get_Name() : "(null-shadow-texture)", + robj ? robj->Get_Name() : "(none)", + robj ? (robj->Is_Really_Visible() ? 1 : 0) : 1, + objPos.X, + objPos.Y, + objPos.Z, + startX, + startY, + endX, + endY, + numVerts, + numIndex, + static_cast(shadow->m_diffuse), + shadow->m_decalSizeX, + shadow->m_decalSizeY); + } + SHADOW_DECAL_VERTEX* pvVertices; UnsignedShort *pvIndices; 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); @@ -1028,9 +1009,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) ? RB_LOCK_DISCARD : RB_LOCK_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; @@ -1038,7 +1043,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; @@ -1048,77 +1053,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; @@ -1147,14 +1129,20 @@ void W3DProjectedShadowManager::queueSimpleDecal(W3DProjectedShadow *shadow) if (TheTerrainRenderObject) { - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); - - if (!m_pDev) return; //no D3D Device to render + if (!g_renderBackend || g_renderBackend->Is_Device_Lost()) { + return; + } objPos=shadow->m_robj->Get_Position(); objXform=shadow->m_robj->Get_Transform(); Real groundHeight=TheTerrainRenderObject->getHeightMapHeight(objPos.X, objPos.Y, &normal); Vector3 groundNormal(normal.x,normal.y,normal.z); + // TheSuperHackers @bugfix bobtista 31/05/2026 Normalize the ground normal and + // the derived v axis. getHeightMapHeight returns an unnormalized normal, and + // the cross product below is only unit-length if both inputs are; without this + // the blob quad gets a wrong-length/zero v axis and collapses, so the simple + // infantry blob shadow renders nothing. + groundNormal.Normalize(); //Find new tu vector parallel to terrain by projecting existing x_vector onto //terrain normal and subtracting the result. @@ -1164,6 +1152,13 @@ void W3DProjectedShadowManager::queueSimpleDecal(W3DProjectedShadow *shadow) uVector.Normalize(); //Find new tv vector parallel to terrain by crossing new tu vector with terrain normal. Vector3::Cross_Product(uVector,groundNormal,&vVector); + vVector.Normalize(); + + // Blob decal size can carry a negative Y (used as a V-flip in the legacy + // projected path). For the simple quad use the magnitude for geometry so a + // negative size cannot fold the quad onto itself. + const Real simpleSizeX = fabs(shadow->m_decalSizeX); + const Real simpleSizeY = fabs(shadow->m_decalSizeY); Int numVerts = 4; //number of decal vertices Int numIndex=6; //(2 triangles). @@ -1174,93 +1169,110 @@ 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. + + // TheSuperHackers @bugfix bobtista 01/06/2026 Drape the blob quad over the + // terrain by sampling the heightmap at each corner and pinning that corner's + // Z to its own ground height (plus the lift), instead of emitting one flat + // quad tilted to the center normal. A flat quad's downhill corners sink below + // sloped terrain and get depth-rejected, so the blob vanishes when zoomed out + // (worse for larger quads that span more undulation); this is the clipping the + // original TODO describes. + Coord3D cornerNormal; 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) ? RB_LOCK_DISCARD : RB_LOCK_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 * simpleSizeY * -0.5f - uVector * simpleSizeX * 0.5f; + vertex.Z = TheTerrainRenderObject->getHeightMapHeight(vertex.X, vertex.Y, &cornerNormal) + 1.0f; + 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 * simpleSizeY; + vertex.Z = TheTerrainRenderObject->getHeightMapHeight(vertex.X, vertex.Y, &cornerNormal) + 1.0f; + 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 * simpleSizeX; + vertex.Z = TheTerrainRenderObject->getHeightMapHeight(vertex.X, vertex.Y, &cornerNormal) + 1.0f; + 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 * simpleSizeY; + vertex.Z = TheTerrainRenderObject->getHeightMapHeight(vertex.X, vertex.Y, &cornerNormal) + 1.0f; + 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) ? RB_LOCK_DISCARD : RB_LOCK_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; @@ -1313,11 +1325,21 @@ Int W3DProjectedShadowManager::renderShadows(RenderInfoClass & rinfo) nShadowDecalVertsInBuf = 0xffff; nShadowDecalIndicesInBuf = 0xffff; - if (TheGlobalData->m_useShadowDecals) + // TheSuperHackers @feature bobtista 16/06/2026 Suppress blob/decal shadows while the + // bgfx sun shadow map is active: infantry and other cutout casters now cast real + // silhouettes into the map, so the decals would double up. + if (TheGlobalData->m_useShadowDecals && !TheGlobalData->m_bgfxShadowMaps) { // Render the object TheDX8MeshRenderer.Set_Camera(&rinfo.Camera); + // TheSuperHackers @bugfix bobtista 01/06/2026 Point the buffer globals at the blob + // buffers while a blob run is active so interleaved blob and non-blob batches never + // alias one buffer; the pending batch is always flushed before a switch. + RenderVertexBufferClass *savedShadowVB = shadowDecalVertexBuffer; + RenderIndexBufferClass *savedShadowIB = shadowDecalIndexBuffer; + bool blobBuffersActive = false; + //keep track of active decal texture so we can render all decals at once. W3DShadowTexture *lastShadowDecalTexture=nullptr; ShadowType lastShadowType = SHADOW_NONE; @@ -1326,12 +1348,27 @@ Int W3DProjectedShadowManager::renderShadows(RenderInfoClass & rinfo) { if (shadow->m_isEnabled && !shadow->m_isInvisibleEnabled) { + if (shadow->m_type != SHADOW_DECAL) + shadow->update(); if (shadow->m_type & SHADOW_DECAL) { + // TheSuperHackers @bugfix bobtista 01/06/2026 Decide whether this + // decal will be routed to queueSimpleDecal (blob buffers) or to + // queueDecal (shared buffers) before we touch the buffer globals, + // mirroring the queueSimpleDecal gate below. + bool useBlobBuffer = false; +#if defined(GGC_RENDER_BACKEND_BGFX) + if (shadow->m_robj != nullptr + && IsDefaultInfantryBlobShadowDecal(shadow->m_shadowTexture[0], shadow->m_type)) + { + useBlobBuffer = true; + } +#endif + if (lastShadowDecalTexture == nullptr) - lastShadowDecalTexture=m_shadowList->m_shadowTexture[0]; + lastShadowDecalTexture=shadow->m_shadowTexture[0]; if (lastShadowType == SHADOW_NONE) - lastShadowType = m_shadowList->m_type; + lastShadowType = shadow->m_type; if (shadow->m_shadowTexture[0] != lastShadowDecalTexture || shadow->m_type != lastShadowType) @@ -1339,10 +1376,54 @@ Int W3DProjectedShadowManager::renderShadows(RenderInfoClass & rinfo) lastShadowDecalTexture=shadow->m_shadowTexture[0]; lastShadowType=shadow->m_type; } + + // TheSuperHackers @bugfix bobtista 01/06/2026 Point the buffer + // globals at the buffer set this run needs. The pending batch was + // already flushed above on the texture change, so switching here + // never splits a batch across buffers. Force a fresh DISCARD on the + // newly-selected buffer so its batch starts at offset 0. + if (useBlobBuffer != blobBuffersActive) + { + if (useBlobBuffer) + { + shadowDecalVertexBuffer = blobDecalVertexBuffer; + shadowDecalIndexBuffer = blobDecalIndexBuffer; + } + else + { + shadowDecalVertexBuffer = savedShadowVB; + shadowDecalIndexBuffer = savedShadowIB; + } + blobBuffersActive = useBlobBuffer; + nShadowDecalVertsInBuf = 0xffff; + nShadowDecalIndicesInBuf = 0xffff; + nShadowDecalStartBatchVertex = 0; + nShadowDecalStartBatchIndex = 0; + } ///@todo: may need to fix this if shadows are large enough to be seen while object is not visible - if (shadow->m_robj->Is_Really_Visible()) - { //queueSimpleDecal(shadow); - queueDecal(shadow); //only draw shadow if casting object is visible + if (shadow->m_robj == nullptr || shadow->m_robj->Is_Really_Visible() || shadow->m_type == SHADOW_DECAL) + { + // TheSuperHackers @bugfix bobtista 31/05/2026 Default infantry + // blob shadows are tiny (~14 units). queueDecal projects them + // onto the heightmap receiver mesh, where they only cover a + // cell or two; the covered-cell count shifts with camera zoom + // and terrain LOD, so the blobs flicker, partially render, or + // vanish when zoomed out. queueSimpleDecal emits a fixed + // 4-vertex ground quad sized to the blob with full [0,1] UV, + // which is zoom-independent. Route default infantry blobs there + // (bgfx only); everything else keeps the heightmap projection. +#if defined(GGC_RENDER_BACKEND_BGFX) + // useBlobBuffer was computed above with the identical gate and + // selected the dedicated blob buffers, so reuse it here. + if (useBlobBuffer) + { + queueSimpleDecal(shadow); + } + else +#endif + { + queueDecal(shadow); //only draw shadow if casting object is visible + } projectionCount++; } continue; @@ -1379,8 +1460,9 @@ Int W3DProjectedShadowManager::renderShadows(RenderInfoClass & rinfo) TexProjectClass *projector=shadow->getShadowProjector(); //terrain is always visible and affected by all shadows so must render + g_renderBackend->Invalidate_Cached_Render_States(); 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(); @@ -1428,10 +1510,27 @@ Int W3DProjectedShadowManager::renderShadows(RenderInfoClass & rinfo) } flushDecals(lastShadowDecalTexture,lastShadowType); //make sure there are not any unrendered decals left over. + + // TheSuperHackers @bugfix bobtista 01/06/2026 Restore the shadow-decal buffer + // globals after the m_shadowList pass so the dedicated blob buffers are never + // left selected for the later decal-list pass (which captures these globals). + shadowDecalVertexBuffer = savedShadowVB; + shadowDecalIndexBuffer = savedShadowIB; + TheDX8MeshRenderer.Flush(); //draw all the shadow receiving objects } if (m_decalList) { + // TheSuperHackers @bugfix bobtista 31/05/2026 Point queueDecal/flushDecals + // at the dedicated decal-list buffers so this pass does not re-lock the + // buffer the still-pending blob/projection shadow submits depend on. + RenderVertexBufferClass *savedDecalVB = shadowDecalVertexBuffer; + RenderIndexBufferClass *savedDecalIB = shadowDecalIndexBuffer; + shadowDecalVertexBuffer = decalListVertexBuffer; + shadowDecalIndexBuffer = decalListIndexBuffer; + nShadowDecalVertsInBuf = 0xffff; //force a fresh DISCARD on the decal-list buffer + nShadowDecalIndicesInBuf = 0xffff; + //keep track of active decal texture so we can render all decals at once. W3DShadowTexture *lastShadowDecalTexture=nullptr; ShadowType lastShadowType = SHADOW_NONE; @@ -1441,9 +1540,9 @@ Int W3DProjectedShadowManager::renderShadows(RenderInfoClass & rinfo) if (shadow->m_isEnabled && !shadow->m_isInvisibleEnabled) { if (lastShadowDecalTexture == nullptr) - lastShadowDecalTexture=m_decalList->m_shadowTexture[0]; + lastShadowDecalTexture=shadow->m_shadowTexture[0]; if (lastShadowType == SHADOW_NONE) - lastShadowType = m_decalList->m_type; + lastShadowType = shadow->m_type; if (shadow->m_shadowTexture[0] != lastShadowDecalTexture || shadow->m_type != lastShadowType) @@ -1461,7 +1560,18 @@ Int W3DProjectedShadowManager::renderShadows(RenderInfoClass & rinfo) } flushDecals(lastShadowDecalTexture,lastShadowType); //make sure there are not any unrendered decals left over. + + // TheSuperHackers @bugfix bobtista 01/06/2026 Restore the shadow-decal buffer + // globals after the decal-list pass. Without this restore the globals stay + // pointed at decalListVertexBuffer/decalListIndexBuffer, so on every later + // frame the m_shadowList blob pass locks the decal-list buffer too. The + // reveal/radius decal pass then re-locks that shared buffer and overwrites the + // still-pending blob shadow submits in the bgfx deferred-submit model, making + // infantry blob shadows vanish whenever a reveal targeting decal is active. + shadowDecalVertexBuffer = savedDecalVB; + shadowDecalIndexBuffer = savedDecalIB; } + return projectionCount; } @@ -1477,6 +1587,7 @@ Shadow* W3DProjectedShadowManager::addDecal(Shadow::ShadowTypeInfo *shadowInfo) Bool allowSunDirection=FALSE; Char texture_name[ARRAY_SIZE(shadowInfo->m_ShadowName)]; + texture_name[0] = '\0'; if (!shadowInfo) return nullptr; //right now we require hardware render-to-texture support @@ -1540,6 +1651,9 @@ Shadow* W3DProjectedShadowManager::addDecal(Shadow::ShadowTypeInfo *shadowInfo) shadow->m_flags = allowSunDirection; shadow->init(); + LogProjectedShadowPath("addDecal-free", nullptr, nullptr, shadowInfo, + shadowType, texture_name, allowWorldAlign, allowSunDirection, + decalSizeX, decalSizeY, 0.0f, 0.0f); // add to our shadow list through the shadow next links, insert next to other shadows using same texture @@ -1582,6 +1696,7 @@ Shadow* W3DProjectedShadowManager::addDecal(RenderObjClass *robj, Shadow::Shadow Bool allowSunDirection=FALSE; Char texture_name[ARRAY_SIZE(shadowInfo->m_ShadowName)]; + texture_name[0] = '\0'; if (!robj || !shadowInfo) return nullptr; //right now we require hardware render-to-texture support @@ -1663,6 +1778,9 @@ Shadow* W3DProjectedShadowManager::addDecal(RenderObjClass *robj, Shadow::Shadow shadow->m_flags = allowSunDirection; shadow->init(); + LogProjectedShadowPath("addDecal-robj", robj, nullptr, shadowInfo, + shadowType, texture_name, allowWorldAlign, allowSunDirection, + decalSizeX, decalSizeY, decalOffsetX, decalOffsetY); // add to our shadow list through the shadow next links, insert next to other shadows using same texture @@ -1704,10 +1822,28 @@ W3DProjectedShadow* W3DProjectedShadowManager::addShadow(RenderObjClass *robj, S Bool allowSunDirection=FALSE; Char texture_name[ARRAY_SIZE(shadowInfo->m_ShadowName)]; + texture_name[0] = '\0'; - if (!m_dynamicRenderTarget || !robj || !TheGlobalData->m_useShadowDecals) - return nullptr; //right now we require hardware render-to-texture support + if (!robj || !TheGlobalData->m_useShadowDecals) + { + LogProjectedShadowPath("addShadow-skip", robj, draw, shadowInfo, + SHADOW_NONE, texture_name, allowWorldAlign, allowSunDirection, + decalSizeX, decalSizeY, decalOffsetX, decalOffsetY); + return nullptr; + } + + const bool canUseStaticDecalWithoutRenderTarget = + shadowInfo != nullptr + && shadowInfo->m_type == SHADOW_DECAL + && IsDefaultInfantryBlobShadowName(shadowInfo->m_ShadowName); + if (!m_dynamicRenderTarget && !canUseStaticDecalWithoutRenderTarget) + { + LogProjectedShadowPath("addShadow-skip", robj, draw, shadowInfo, + SHADOW_NONE, texture_name, allowWorldAlign, allowSunDirection, + decalSizeX, decalSizeY, decalOffsetX, decalOffsetY); + return nullptr; + } if (shadowInfo) @@ -1852,6 +1988,9 @@ W3DProjectedShadow* W3DProjectedShadowManager::addShadow(RenderObjClass *robj, S shadow->m_flags = allowSunDirection; shadow->init(); + LogProjectedShadowPath("addShadow", robj, draw, shadowInfo, + shadowType, texture_name, allowWorldAlign, allowSunDirection, + decalSizeX, decalSizeY, decalOffsetX, decalOffsetY); // add to our shadow list through the shadow next links, insert next to other shadows using same texture @@ -2140,16 +2279,24 @@ void W3DProjectedShadow::updateTexture(Vector3 &lightPos) m_shadowProjector->Compute_Texture(m_robj,context); - //Need to copy generated texture into permanent texture. - SurfaceClass *oldSurface=m_shadowTexture[0]->getTexture()->Get_Surface_Level(); - SurfaceClass *newSurface=TheW3DProjectedShadowManager->getRenderTarget()->Get_Surface_Level(); - - //Copy shadow from temporary video-memory surface into a permanent texture - oldSurface->Copy(0,0,0,0,DEFAULT_RENDER_TARGET_WIDTH,DEFAULT_RENDER_TARGET_HEIGHT,newSurface); - REF_PTR_RELEASE(newSurface); - REF_PTR_RELEASE(oldSurface); - m_shadowTexture[0]->updateBounds(TheW3DShadowManager->getLightPosWorld(0),m_robj); //update local shadow bounds - } + //Need to copy generated texture into permanent texture. +#if defined(GGC_RENDER_BACKEND_BGFX) + g_renderBackend->Copy_Render_Target_To_Texture( + m_shadowTexture[0]->getTexture(), + TheW3DProjectedShadowManager->getRenderTarget()); +#else + SurfaceClass *oldSurface=m_shadowTexture[0]->getTexture()->Get_Surface_Level(); + SurfaceClass *newSurface=TheW3DProjectedShadowManager->getRenderTarget()->Get_Surface_Level(); + + //Copy shadow from temporary video-memory surface into a permanent texture + oldSurface->Copy(0,0,0,0,DEFAULT_RENDER_TARGET_WIDTH,DEFAULT_RENDER_TARGET_HEIGHT,newSurface); + REF_PTR_RELEASE(newSurface); + REF_PTR_RELEASE(oldSurface); + g_renderBackend->Copy_Render_Target_To_Texture(m_shadowTexture[0]->getTexture(), TheW3DProjectedShadowManager->getRenderTarget()); +#endif + m_shadowProjector->Set_Texture(m_shadowTexture[0]->getTexture()); + m_shadowTexture[0]->updateBounds(TheW3DShadowManager->getLightPosWorld(0),m_robj); //update local shadow bounds + } else if (m_type == SHADOW_DECAL) { //decal shadows use artist supplied textures. We just need to tweak the uv coordinates to match @@ -2363,7 +2510,7 @@ Bool W3DShadowTextureManager::addTexture(W3DShadowTexture *newTexture) void W3DShadowTextureManager::invalidateCachedLightPositions() { // step through each of our shadow textures and update previous light position. - Vector3 idVec(0,0,0); + Vector3 idVec(1.0e30f,1.0e30f,1.0e30f); W3DShadowTextureManagerIterator it( *this ); for( it.First(); !it.Is_Done(); it.Next() ) diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DShadow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DShadow.cpp index efa56604fb5..eb2416efc2c 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DShadow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DShadow.cpp @@ -33,16 +33,16 @@ // USER INCLUDES ////////////////////////////////////////////////////////////// #include "WWLib/always.h" +#include "GgcRuntimeFlags.h" #include "GameClient/View.h" #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" #include "Lib/BaseType.h" #include "W3DDevice/GameClient/HeightMap.h" -#include "d3dx8math.h" #include "Common/GlobalData.h" #include "W3DDevice/GameClient/W3DVolumetricShadow.h" #include "W3DDevice/GameClient/W3DProjectedShadow.h" @@ -51,6 +51,8 @@ #include "Common/Debug.h" #include "Common/PerfTimer.h" +#include + #define SUN_DISTANCE_FROM_GROUND 10000.0f //distance of sun (our only light source). // Global Variables and Functions ///////////////////////////////////////////// @@ -75,6 +77,15 @@ void DoShadows(RenderInfoClass & rinfo, Bool stencilPass) //USE_PERF_TIMER(shadowsRender) shadowCameraFrustum=&rinfo.Camera.Get_Frustum(); Int projectionCount=0; +#if defined(GGC_RENDER_BACKEND_BGFX) + // The bgfx backend submits these draws into its main sequential view so + // the darken pass lands after terrain/projected decals but before opaque + // meshes. The legacy D3D path keeps its original post-mesh stencil pass. + const Bool bgfxPreMeshStencilVolumes = + !GgcFlags::Enabled(GgcFlag_BgfxLegacyPostMeshStencilShadows); +#else + const Bool bgfxPreMeshStencilVolumes = FALSE; +#endif //Projected shadows render first because they may fill the stencil buffer //which will be used by the shadow volumes @@ -84,8 +95,18 @@ void DoShadows(RenderInfoClass & rinfo, Bool stencilPass) projectionCount=TheW3DProjectedShadowManager->renderShadows(rinfo); } + if (stencilPass == FALSE && bgfxPreMeshStencilVolumes && TheW3DVolumetricShadowManager) + { + if (TheW3DShadowManager->isShadowScene()) + TheW3DVolumetricShadowManager->renderShadows(projectionCount); + if (TheW3DShadowManager) + TheW3DShadowManager->queueShadows(FALSE); + } + if (stencilPass == TRUE && TheW3DVolumetricShadowManager) { + if (bgfxPreMeshStencilVolumes) + return; // TheW3DShadowManager->loadTerrainShadows(); @@ -181,6 +202,15 @@ Shadow *W3DShadowManager::addShadow( RenderObjClass *robj, Shadow::ShadowTypeInf { ShadowType type = SHADOW_VOLUME; + // TheSuperHackers @feature bobtista 15/07/2026 The bgfx sun shadow map replaces the legacy + // stencil volumes and blob/projected decals; creating them anyway doubles every unit shadow + // (and pays the CPU silhouette cost). GGC_ENABLE_LEGACY_STENCIL_SHADOWS restores them for A/B. + static const Bool forceLegacyShadows = GgcFlags::Enabled(GgcFlag_EnableLegacyStencilShadows); + if (!forceLegacyShadows && TheGlobalData != NULL && TheGlobalData->m_bgfxShadowMaps) + { + return nullptr; + } + if (shadowInfo) type = shadowInfo->m_type; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp index 33b42a8c03e..9a92ae58261 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DVolumetricShadow.cpp @@ -36,19 +36,26 @@ // SYSTEM INCLUDES //////////////////////////////////////////////////////////// #include +#include +#include +#include +#include +#include +#include // USER INCLUDES ////////////////////////////////////////////////////////////// #include "WWLib/always.h" +#include "GgcRuntimeFlags.h" #include "GameClient/View.h" #include "WW3D2/camera.h" #include "WW3D2/light.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/renderbufferclasses.h" #include "WW3D2/hlod.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" #include "Lib/BaseType.h" #include "W3DDevice/GameClient/HeightMap.h" -#include "d3dx8math.h" #include "Common/GlobalData.h" #include "Common/DrawModule.h" #include "W3DDevice/GameClient/W3DVolumetricShadow.h" @@ -57,6 +64,7 @@ #include "GameLogic/TerrainLogic.h" #include "WW3D2/dx8caps.h" #include "GameClient/Drawable.h" +#include "Common/ThingTemplate.h" #ifdef USE_WWSHADE #include "wwshade/shdmesh.h" #include "wwshade/shdsubmesh.h" @@ -89,11 +97,121 @@ const Real cosAngleToCare = cos ((0.2 * PI) / 180.0); //1.5 degree difference //#define SV_DEBUG //#define SV_DEBUG_BOUNDS +static bool ShadowPathDiagEnabled() +{ + return GgcFlags::Enabled(GgcFlag_ShadowPathDiag); +} + +static const char *DrawableTemplateName(const Drawable *draw) +{ + return draw != nullptr && draw->getTemplate() != nullptr + ? draw->getTemplate()->getName().str() + : "(null-drawable)"; +} + +static void LogVolumetricShadowPath(const char *event, + RenderObjClass *robj, + Drawable *draw, + const Shadow::ShadowTypeInfo *shadowInfo, + int renderedCount, + const char *reason) +{ + if (!ShadowPathDiagEnabled()) + return; + + if (FILE *diag = std::fopen("ggc_shadow_path_diag.txt", "a")) + { + std::fprintf(diag, + "%s volume requestedMask=0x%x robj=%s drawable=%u template=%s rendered=%d useVolumes=%d hasStencil=%d shadowSize=(%.3f,%.3f) reason=%s\n", + event, + shadowInfo != nullptr ? static_cast(shadowInfo->m_type) : 0, + robj != nullptr && robj->Get_Name() != nullptr ? robj->Get_Name() : "(null-robj)", + draw != nullptr ? static_cast(draw->getID()) : 0, + DrawableTemplateName(draw), + renderedCount, + TheGlobalData != nullptr && TheGlobalData->m_useShadowVolumes ? 1 : 0, + g_renderBackend != nullptr && g_renderBackend->Has_Stencil() ? 1 : 0, + shadowInfo != nullptr ? shadowInfo->m_sizeX : 0.0f, + shadowInfo != nullptr ? shadowInfo->m_sizeY : 0.0f, + reason != nullptr ? reason : ""); + std::fclose(diag); + } +} + +static bool ShouldSkipBgfxStaticVolumeShadow(const Drawable *draw) +{ + if (g_renderBackend == nullptr) + return false; + +#if !defined(GGC_BGFX_RENDERER_METAL) + if (!GgcFlags::Enabled(GgcFlag_BgfxSkipStaticVolumeShadows)) + return false; +#endif + + const ThingTemplate *tmplate = draw != nullptr ? draw->getTemplate() : nullptr; + if (tmplate == nullptr) + { +#if defined(GGC_BGFX_RENDERER_METAL) + // TheSuperHackers @bugfix bobtista 14/06/2026 Metal lacks hardware depth clamp + // for the legacy open stencil volumes. Render objects without a template cannot + // be classified as mobile units, and static/effect volumes are the cases that + // produce giant bgfx/Metal stencil fans in combat saves. + return true; +#else + return false; +#endif + } + +#if defined(GGC_BGFX_RENDERER_METAL) + const bool isKnownVolumeCaster = + tmplate->isKindOf(KINDOF_AIRCRAFT) + || tmplate->isKindOf(KINDOF_DRONE) + || tmplate->isKindOf(KINDOF_VEHICLE) + || tmplate->isKindOf(KINDOF_BOAT) + || tmplate->isKindOf(KINDOF_STRUCTURE); + + return !isKnownVolumeCaster; +#else + return !tmplate->isKindOf(KINDOF_VEHICLE) + && !tmplate->isKindOf(KINDOF_AIRCRAFT) + && !tmplate->isKindOf(KINDOF_BOAT); +#endif +} + +static bool BgfxUseShadowVolumeZFail() +{ + if (g_renderBackend == nullptr || !g_renderBackend->Needs_Closed_Shadow_Volumes()) + return false; + + const char *algo = GgcFlags::StringValue(GgcFlag_BgfxStencilAlgo); + return algo == nullptr + || std::strcmp(algo, "zfail") == 0 + || std::strcmp(algo, "zfail-swap") == 0; +} + +static bool BgfxSwapShadowVolumeZFailOps() +{ + const char *algo = GgcFlags::StringValue(GgcFlag_BgfxStencilAlgo); + return algo != nullptr + && (std::strcmp(algo, "zfail-swap") == 0 + || std::strcmp(algo, "zpass-swap") == 0); +} + +static bool BgfxFlipShadowVolumeCapWinding() +{ + return GgcFlags::Enabled(GgcFlag_BgfxFlipCapWinding); +} + +static bool BgfxUseSaturatedShadowVolumeIncrement() +{ + return GgcFlags::Enabled(GgcFlag_BgfxStencilIncrSat); +} + struct SHADOW_STATIC_VOLUME_VERTEX //vertex structure passed to D3D { float x,y,z; }; -#define SHADOW_STATIC_VOLUME_FVF D3DFVF_XYZ +#define SHADOW_STATIC_VOLUME_FVF RENDER_VERTEX_FORMAT_XYZ #ifdef SV_DEBUG //in debug mode, dynamic shadows are rendered with random diffuse color struct SHADOW_DYNAMIC_VOLUME_VERTEX //vertex structure passed to D3D @@ -101,20 +219,50 @@ struct SHADOW_STATIC_VOLUME_VERTEX //vertex structure passed to D3D float x,y,z; DWORD diffuse; }; - #define SHADOW_DYNAMIC_VOLUME_FVF D3DFVF_XYZ|D3DFVF_DIFFUSE + #define SHADOW_DYNAMIC_VOLUME_FVF RENDER_VERTEX_FORMAT_XYZD #else typedef struct SHADOW_STATIC_VOLUME_VERTEX SHADOW_DYNAMIC_VOLUME_VERTEX; - #define SHADOW_DYNAMIC_VOLUME_FVF D3DFVF_XYZ + #define SHADOW_DYNAMIC_VOLUME_FVF RENDER_VERTEX_FORMAT_XYZ #endif -LPDIRECT3DVERTEXBUFFER8 shadowVertexBufferD3D=nullptr; ///Needs_Closed_Shadow_Volumes()) + return SHADOW_VOLUME_BGFX_VERTEX_CAPACITY; + return SHADOW_VERTEX_SIZE; +#endif +} + +static int ShadowDynamicIndexCapacity() +{ +#if defined(GGC_RENDER_BACKEND_BGFX) + return SHADOW_VOLUME_BGFX_INDEX_CAPACITY; +#else + if (g_renderBackend != nullptr && g_renderBackend->Needs_Closed_Shadow_Volumes()) + return SHADOW_VOLUME_BGFX_INDEX_CAPACITY; + return SHADOW_INDEX_SIZE; +#endif +} //Rough bounding box around visible portion of the terrain //useful for quick culling @@ -125,7 +273,7 @@ static Real beX; static Real beY; static Real beZ; -static LPDIRECT3DVERTEXBUFFER8 lastActiveVertexBuffer=nullptr; +static RenderVertexBufferClass *lastActiveVertexBuffer=nullptr; /** A simple structure to hold random geometry (vertices, polygons, etc.). We'll use this * to store shadow volumes. */ @@ -1328,10 +1476,7 @@ void W3DVolumetricShadow::RenderMeshVolume(Int meshIndex, Int lightIndex, const Geometry *geometry; Int numVerts, numPolys, numIndex; - //Get D3D Device used by W3D for quicker access. - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); - - if (!m_pDev) + if (!g_renderBackend || g_renderBackend->Is_Device_Lost()) return; geometry = m_shadowVolume[lightIndex][ meshIndex ]; @@ -1351,16 +1496,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. + if (vbSlot->m_VB->m_renderVertexBuffer != lastActiveVertexBuffer) + { lastActiveVertexBuffer=vbSlot->m_VB->m_renderVertexBuffer; + g_renderBackend->Set_Vertex_Buffer(vbSlot->m_VB->m_renderVertexBuffer, 0); } DEBUG_ASSERTCRASH(vbSlot->m_size >= numVerts,("Overflowing Shadow Vertex Buffer Slot")); @@ -1371,14 +1514,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_renderIndexBuffer, vbSlot->m_start); - if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) + if (g_renderBackend->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) @@ -1388,13 +1537,9 @@ void W3DVolumetricShadow::RenderDynamicMeshVolume(Int meshIndex, Int lightIndex, SHADOW_DYNAMIC_VOLUME_VERTEX* pvVertices; UnsignedShort *pvIndices; - //Get D3D Device used by W3D for quicker access. - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); - - if (!m_pDev) + if (!g_renderBackend || g_renderBackend->Is_Device_Lost()) return; - geometry = m_shadowVolume[lightIndex][ meshIndex ]; // @@ -1413,72 +1558,83 @@ 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; + // Wrap-around (DISCARD) when the buffer can't fit this batch. + const int vertexCapacity = ShadowDynamicVertexCapacity(); + const int indexCapacity = ShadowDynamicIndexCapacity(); + if (numVerts > vertexCapacity || numIndex > indexCapacity) + { + LogVolumetricShadowPath("render-dynamic-skip", m_robj, nullptr, nullptr, + numPolys, "shadow-volume-batch-exceeds-dynamic-buffer"); + return; } - else - { if (shadowVertexBufferD3D->Lock(nShadowVertsInBuf*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX),numVerts*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX), (unsigned char**)&pvVertices,D3DLOCK_NOOVERWRITE) != D3D_OK) - return; + + const bool wrapVerts = (nShadowVertsInBuf > (vertexCapacity - numVerts)); + if (wrapVerts) { + nShadowVertsInBuf = 0; + nShadowStartBatchVertex = 0; } -#ifdef SV_DEBUG - srand(0x1345465); -#endif - if(pvVertices) { + const unsigned vbFlags = wrapVerts ? RB_LOCK_DISCARD : RB_LOCK_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; + const bool wrapIndices = (nShadowIndicesInBuf > (indexCapacity - 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; - } - - - 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; + const unsigned ibFlags = wrapIndices ? RB_LOCK_DISCARD : RB_LOCK_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)); + } } - if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) + // 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; + + if (g_renderBackend->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; @@ -1525,10 +1681,7 @@ void W3DVolumetricShadow::RenderMeshVolumeBounds(Int meshIndex, Int lightIndex, static Vector3 verts[8]; - //Get D3D Device used by W3D for quicker access. - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); - - if (!m_pDev) + if (!g_renderBackend || g_renderBackend->Is_Device_Lost()) return; Vector3 meshPosition; @@ -1563,70 +1716,74 @@ 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 int vertexCapacity = ShadowDynamicVertexCapacity(); + const int indexCapacity = ShadowDynamicIndexCapacity(); + if (numVerts > vertexCapacity || numIndex > indexCapacity) + { + return; } - else - { if (shadowVertexBufferD3D->Lock(nShadowVertsInBuf*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX),numVerts*sizeof(SHADOW_DYNAMIC_VOLUME_VERTEX), (unsigned char**)&pvVertices,D3DLOCK_NOOVERWRITE) != D3D_OK) - return; + + const bool wrapVerts = (nShadowVertsInBuf > (vertexCapacity - numVerts)); + if (wrapVerts) { + nShadowVertsInBuf = 0; + nShadowStartBatchVertex = 0; } - 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 ? RB_LOCK_DISCARD : RB_LOCK_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; + const bool wrapIndices = (nShadowIndicesInBuf > (indexCapacity - 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; - } - - - 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; @@ -1646,6 +1803,7 @@ W3DVolumetricShadow::W3DVolumetricShadow() m_geometry = nullptr; m_shadowLengthScale = 0.0f; m_extraExtrusionPadding = 0.0f; + m_useVerticalShadowProjection = FALSE; m_robj = nullptr; m_isEnabled = TRUE; m_isInvisibleEnabled = FALSE; @@ -1705,6 +1863,14 @@ W3DVolumetricShadow::~W3DVolumetricShadow() } +Bool W3DVolumetricShadow::shouldUseClosedShadowVolume() const +{ + if (g_renderBackend != nullptr && g_renderBackend->Needs_Closed_Shadow_Volumes()) + return TRUE; + + return FALSE; +} + void W3DVolumetricShadow::SetGeometry( W3DShadowGeometry *geometry ) { @@ -1789,25 +1955,45 @@ void W3DVolumetricShadow::Update() groundHeight=TheTerrainLogic->getGroundHeight(pos.X,pos.Y); //logic knows about bridges so use if available. else groundHeight=TheTerrainRenderObject->getHeightMapHeight(pos.X,pos.Y, nullptr); - if (fabs(pos.Z - groundHeight) >= AIRBORNE_UNIT_GROUND_DELTA) + if (fabs(pos.Z - groundHeight) >= AIRBORNE_UNIT_GROUND_DELTA) { Real extent = MAX_SHADOW_LENGTH_EXTRA_AIRBORNE_SCALE_FACTOR * m_robjExtent; - if (WWMath::Fabs(pos.X - bcX) > (beX + extent) || - WWMath::Fabs(pos.Y - bcY) > (beY + extent) || - WWMath::Fabs(pos.Z - bcZ) > (beZ + extent)) + if (WWMath::Fabsf_Legacy(pos.X - bcX) > (beX + extent) || + WWMath::Fabsf_Legacy(pos.Y - bcY) > (beY + extent) || + WWMath::Fabsf_Legacy(pos.Z - bcZ) > (beZ + extent)) return; //shadow can't be visible so no point in updating. //this unit is above ground, extend shadow volume to reach lowest point on the terrain plus extra bit to make //sure shadow goes under ground. - updateVolumes(fabs(pos.Z - TheTerrainRenderObject->getMinHeight()) + SHADOW_EXTRUSION_BUFFER); + Real airborneZOffset = fabs(pos.Z - TheTerrainRenderObject->getMinHeight()) + SHADOW_EXTRUSION_BUFFER; +#if defined(GGC_BGFX_RENDERER_METAL) + if (g_renderBackend != nullptr) + { + // TheSuperHackers @bugfix bobtista 14/06/2026 Metal ignores hardware + // depth clamp, so open stencil volumes extruded to the map minimum turn + // elevated casters into oversized screen-space streaks. Keep the floor + // near local ground for Metal casters instead. + airborneZOffset = fabs(pos.Z - groundHeight) + SHADOW_EXTRUSION_BUFFER; + } +#else + if (g_renderBackend != nullptr + && m_shadowLengthScale > 10.0f + && g_renderBackend->Needs_Closed_Shadow_Volumes()) + { + airborneZOffset = fabs(pos.Z - groundHeight) - SHADOW_EXTRUSION_BUFFER; + if (airborneZOffset < 0.0f) + airborneZOffset = 0.0f; + } +#endif + updateVolumes(airborneZOffset); } else { //normal object that is not floating above ground so we don't need to extend the shadow lower than the object's //base since it should be sitting directly at ground level. - if (WWMath::Fabs(pos.X - bcX) > (beX + m_robjExtent) || - WWMath::Fabs(pos.Y - bcY) > (beY + m_robjExtent) || - WWMath::Fabs(pos.Z - bcZ) > (beZ + m_robjExtent)) + if (WWMath::Fabsf_Legacy(pos.X - bcX) > (beX + m_robjExtent) || + WWMath::Fabsf_Legacy(pos.Y - bcY) > (beY + m_robjExtent) || + WWMath::Fabsf_Legacy(pos.Z - bcZ) > (beZ + m_robjExtent)) return; //shadow can't be visible so no point in updating. //check if this object has never had it's extrusion length updated. Will only be true for @@ -1948,7 +2134,7 @@ void W3DVolumetricShadow::updateMeshVolume(Int meshIndex, Int lightIndex, const Vector3 vb = (Vector3 &)objectToWorld[0]; va.Normalize(); vb.Normalize(); - Real cosAngle = WWMath::Fabs(Vector3::Dot_Product(va,vb)); + Real cosAngle = WWMath::Fabsf_Legacy(Vector3::Dot_Product(va,vb)); if (cosAngle >= cosAngleToCare) { @@ -1957,7 +2143,7 @@ void W3DVolumetricShadow::updateMeshVolume(Int meshIndex, Int lightIndex, const vb = (Vector3 &)objectToWorld[1]; va.Normalize(); vb.Normalize(); - cosAngle = WWMath::Fabs(Vector3::Dot_Product(va,vb)); + cosAngle = WWMath::Fabsf_Legacy(Vector3::Dot_Product(va,vb)); if (cosAngle >= cosAngleToCare) { @@ -1965,7 +2151,7 @@ void W3DVolumetricShadow::updateMeshVolume(Int meshIndex, Int lightIndex, const vb = (Vector3 &)objectToWorld[2]; va.Normalize(); vb.Normalize(); - cosAngle = WWMath::Fabs(Vector3::Dot_Product(va,vb)); + cosAngle = WWMath::Fabsf_Legacy(Vector3::Dot_Product(va,vb)); if (cosAngle < cosAngleToCare) isMeshRotating=true; } @@ -2007,9 +2193,26 @@ void W3DVolumetricShadow::updateMeshVolume(Int meshIndex, Int lightIndex, const // get the object meshXform->Get_Translation(&objectCenter); //current mesh position + const Bool useVerticalShadowProjection = +#if defined(GGC_BGFX_RENDERER_METAL) + m_useVerticalShadowProjection; +#else + FALSE; +#endif + + if (useVerticalShadowProjection) + { + // TheSuperHackers @bugfix bobtista 14/06/2026 Air casters should keep + // compact stencil shadows directly beneath them. The legacy sun-direction + // volume exposes long side walls for elevated casters on bgfx/Metal. + lightPosWorld.X = objectCenter.X; + lightPosWorld.Y = objectCenter.Y; + lightPosWorld.Z = objectCenter.Z + 100000.0f; + } + // check if object has a limit/clamp on shadow length and adjust light // position of necessary. - if (m_shadowLengthScale) + if (m_shadowLengthScale && !useVerticalShadowProjection) { //Find light's distance from origin in xy plane Real lightXYDistance = sqrt(lightPosWorld.X*lightPosWorld.X + lightPosWorld.Y * lightPosWorld.Y); Real newZ=lightXYDistance*m_shadowLengthScale; @@ -2552,6 +2755,146 @@ 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; + int i; + int j; + for (i = 0; i < N; ++i) + { + int nextIndex = (i + 1) % N; + signedArea += xy[i*2] * xy[nextIndex*2+1]; + signedArea -= xy[nextIndex*2] * xy[i*2+1]; + } + const bool polygonCCW = (signedArea > 0.0f); + + std::vector poly(N); + for (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 (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 (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; +} + +#if defined(RTS_DEBUG) +// 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")); +} +#endif + void W3DVolumetricShadow::constructVolume( Vector3 *lightPosObject,Real shadowExtrudeDistance, Int volumeIndex, Int meshIndex ) { Geometry *shadowVolume; @@ -2766,8 +3109,65 @@ void W3DVolumetricShadow::constructVolume( Vector3 *lightPosObject,Real shadowEx #endif } + if (shouldUseClosedShadowVolume()) + { + for (i = 0; i < geomMesh->GetNumPolygon(); ++i) + { + PolyNeighbor *polyNeighbor = geomMesh->GetPolyNeighbor(i); + if (polyNeighbor == nullptr || !BitIsSet(polyNeighbor->status, POLY_VISIBLE)) + continue; + + Short poly[3]; + geomMesh->GetPolygonIndex(i, poly); + Short capIndex[3]; + for (k = 0; k < 3; ++k) + { + const Vector3& v = geomMesh->GetVertex(poly[k]); + shadowVolume->SetVertex(vertexCount + k, &v); + extrude2 = v - *lightPosObject; + extrude2 *= shadowExtrudeDistance; + extrude2 += v; + shadowVolume->SetVertex(vertexCount + 3 + k, &extrude2); + } + + if (BgfxFlipShadowVolumeCapWinding()) + { + capIndex[0] = vertexCount; + capIndex[1] = vertexCount + 2; + capIndex[2] = vertexCount + 1; + } + else + { + capIndex[0] = vertexCount; + capIndex[1] = vertexCount + 1; + capIndex[2] = vertexCount + 2; + } + shadowVolume->SetPolygonIndex(polygonCount++, capIndex); + + if (BgfxFlipShadowVolumeCapWinding()) + { + capIndex[0] = vertexCount + 3; + capIndex[1] = vertexCount + 4; + capIndex[2] = vertexCount + 5; + } + else + { + capIndex[0] = vertexCount + 5; + capIndex[1] = vertexCount + 4; + capIndex[2] = vertexCount + 3; + } + shadowVolume->SetPolygonIndex(polygonCount++, capIndex); + + vertexCount += 6; + } + } + shadowVolume->SetNumActivePolygon(polygonCount); shadowVolume->SetNumActiveVertex(vertexCount); + +#if defined(RTS_DEBUG) + AuditShadowVolumeEdges(shadowVolume, vertexCount, polygonCount, "constructVolume(dynamic)"); +#endif } // constructVolumeVB ========================================================== @@ -2826,6 +3226,12 @@ void W3DVolumetricShadow::constructVolumeVB( Vector3 *lightPosObject,Real shadow } + geomMesh = m_geometry->getMesh(meshIndex); + if (geomMesh == nullptr) + { + return; + } + //*****************************************************************************************/ //Do an initial pass through silhouette data to determine the actual vertex/polygon counts. //This number can't be determined any other way since it depends on degree of vertex sharing @@ -2927,6 +3333,19 @@ void W3DVolumetricShadow::constructVolumeVB( Vector3 *lightPosObject,Real shadow maxStripLength=__max(maxStripLength,stripLength); #endif } + + if (g_renderBackend != nullptr && g_renderBackend->Needs_Closed_Shadow_Volumes()) + { + for (i = 0; i < geomMesh->GetNumPolygon(); ++i) + { + PolyNeighbor *polyNeighbor = geomMesh->GetPolyNeighbor(i); + if (polyNeighbor != nullptr && BitIsSet(polyNeighbor->status, POLY_VISIBLE)) + { + vertexCount += 6; + polygonCount += 2; + } + } + } } //*********************************************************************************************** @@ -2963,15 +3382,13 @@ void W3DVolumetricShadow::constructVolumeVB( Vector3 *lightPosObject,Real shadow return; } - geomMesh = m_geometry->getMesh(meshIndex); - - DX8VertexBufferClass::AppendLockClass lockVtxBuffer(vbSlot->m_VB->m_DX8VertexBuffer,vbSlot->m_start,vertexCount); + RenderVertexBufferClass::AppendLockClass lockVtxBuffer(vbSlot->m_VB->m_renderVertexBuffer,vbSlot->m_start,vertexCount); VertexFormatXYZ *vb = (VertexFormatXYZ*)lockVtxBuffer.Get_Vertex_Array(); if (vb == nullptr) return; - DX8IndexBufferClass::AppendLockClass lockIdxBuffer(ibSlot->m_IB->m_DX8IndexBuffer,ibSlot->m_start,polygonCount*3); + RenderIndexBufferClass::AppendLockClass lockIdxBuffer(ibSlot->m_IB->m_renderIndexBuffer,ibSlot->m_start,polygonCount*3); UnsignedShort *ib = (UnsignedShort*)lockIdxBuffer.Get_Index_Array(); if (ib == nullptr) @@ -3108,6 +3525,62 @@ void W3DVolumetricShadow::constructVolumeVB( Vector3 *lightPosObject,Real shadow } } + if (shouldUseClosedShadowVolume()) + { + for (i = 0; i < geomMesh->GetNumPolygon(); ++i) + { + PolyNeighbor *polyNeighbor = geomMesh->GetPolyNeighbor(i); + if (polyNeighbor == nullptr || !BitIsSet(polyNeighbor->status, POLY_VISIBLE)) + continue; + + Short poly[3]; + geomMesh->GetPolygonIndex(i, poly); + for (k = 0; k < 3; ++k) + { + const Vector3& v = geomMesh->GetVertex(poly[k]); + *vb++ = *(VertexFormatXYZ *)&v; + extrude2 = v - *lightPosObject; + extrude2 *= shadowExtrudeDistance; + extrude2 += v; + *vb++ = *(VertexFormatXYZ *)&extrude2; + } + + if (BgfxFlipShadowVolumeCapWinding()) + { + ib[0] = vertexCount; + ib[1] = vertexCount + 4; + ib[2] = vertexCount + 2; + } + else + { + ib[0] = vertexCount; + ib[1] = vertexCount + 2; + ib[2] = vertexCount + 4; + } + ib += 3; + polygonCount++; + + if (BgfxFlipShadowVolumeCapWinding()) + { + ib[0] = vertexCount + 1; + ib[1] = vertexCount + 3; + ib[2] = vertexCount + 5; + } + else + { + ib[0] = vertexCount + 5; + ib[1] = vertexCount + 3; + ib[2] = vertexCount + 1; + } + ib += 3; + polygonCount++; + + vertexCount += 6; + } + shadowVolume->SetNumActivePolygon(polygonCount); + shadowVolume->SetNumActiveVertex(vertexCount); + } + // DEBUG_ASSERTLOG(polygonCount == vertexCount, ("WARNING***Shadow volume mesh not optimal: %s",m_geometry->Get_Name())); } @@ -3133,6 +3606,8 @@ Bool W3DVolumetricShadow::allocateShadowVolume( Int volumeIndex, Int meshIndex ) { // poolify shadowVolume = NEW Geometry; // create the new geometry + if (m_useVerticalShadowProjection) + shadowVolume->SetFlags(shadowVolume->GetFlags() | SHADOW_DYNAMIC); // we now have one more valid geometry volume m_shadowVolumeCount[meshIndex]++; } @@ -3175,6 +3650,16 @@ Bool W3DVolumetricShadow::allocateShadowVolume( Int volumeIndex, Int meshIndex ) //is known. if (shadowVolume->GetFlags() & SHADOW_DYNAMIC) { + if (shouldUseClosedShadowVolume()) + { + W3DShadowGeometryMesh *geomMesh = m_geometry != nullptr ? m_geometry->getMesh(meshIndex) : nullptr; + if (geomMesh != nullptr) + { + numPolygons += geomMesh->GetNumPolygon() * 2; + numVertices += geomMesh->GetNumPolygon() * 6; + } + } + //for dynamic shadow casters, we need to allocate the maximum amount of vertices that could ever be required. // if (m_shadowVolumeVB[ volumeIndex ][meshIndex]) // TheW3DBufferManager->releaseSlot(m_shadowVolumeVB[ volumeIndex ][meshIndex]); @@ -3332,15 +3817,7 @@ void W3DVolumetricShadow::resetSilhouette( Int meshIndex ) // ============================================================================ void W3DVolumetricShadowManager::renderStencilShadows() { - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); - - if (!m_pDev) - return; //need device to render anything. - - struct _TRANSLITVERTEX { - D3DXVECTOR4 p; - DWORD color; // diffuse color - } v[4]; + LogVolumetricShadowPath("renderStencilShadows", nullptr, nullptr, nullptr, 0, nullptr); Int xpos, ypos, width, height; @@ -3348,51 +3825,21 @@ void W3DVolumetricShadowManager::renderStencilShadows() width=TheTacticalView->getWidth(); height=TheTacticalView->getHeight(); - v[0].p = D3DXVECTOR4( xpos+width, ypos+height, 0.0f, 1.0f ); - v[1].p = D3DXVECTOR4( xpos+width, 0, 0.0f, 1.0f ); - v[2].p = D3DXVECTOR4( xpos, ypos+height, 0.0f, 1.0f ); - v[3].p = D3DXVECTOR4( xpos, 0, 0.0f, 1.0f ); - v[0].color = TheW3DShadowManager->getShadowColor(); - v[1].color = TheW3DShadowManager->getShadowColor(); - v[2].color = TheW3DShadowManager->getShadowColor(); - v[3].color = TheW3DShadowManager->getShadowColor(); - - //draw polygons like this is very inefficient but for only 2 triangles, it's - //not worth bothering with index/vertex buffers. - m_pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE); - - // Use alpha blending to draw the transparent shadow - m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); -// m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); -// m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); - m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR); - m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO ); - - - // Set stencil states - m_pDev->SetRenderState( D3DRS_ZENABLE, TRUE ); - m_pDev->SetRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS); - - // Only write where stencil val >= 1 (count indicates # of shadows that - // overlap that pixel) - m_pDev->SetRenderState( D3DRS_STENCILENABLE, TRUE ); - m_pDev->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_LESSEQUAL ); //reference value is less or equal to stencil - m_pDev->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_KEEP ); - //Upper bits of stencil could be used for storing occluded models which are player colored. So we mask out those - //pixels and only use the lower bits for shadow calculations. - m_pDev->SetRenderState( D3DRS_STENCILMASK, ~TheW3DShadowManager->getStencilShadowMask()); - m_pDev->SetRenderState( D3DRS_STENCILREF, 0x1 ); - - - m_pDev->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT); - - if (DX8Wrapper::_Is_Triangle_Draw_Enabled()) - m_pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, v, sizeof(_TRANSLITVERTEX)); - - m_pDev->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); - m_pDev->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); + g_renderBackend->Apply_Stencil_Shadow_Darken( + TheW3DShadowManager->getShadowColor(), + ~TheW3DShadowManager->getStencilShadowMask(), + 0x1, + xpos, + ypos, + width, + height); + +#if !defined(GGC_RENDER_BACKEND_BGFX) + g_renderBackend->Set_Shade_Mode(RB_SHADE_GOURAUD); + g_renderBackend->Set_Alpha_Blend_Enable(false); // turn off the stencil buffer - m_pDev->SetRenderState( D3DRS_STENCILENABLE, FALSE ); + g_renderBackend->Set_Stencil_Enable(false); +#endif } @@ -3400,6 +3847,8 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) { W3DVolumetricShadow *shadow; Int numRenderedShadows = 0; + LogVolumetricShadowPath("renderShadows-begin", nullptr, nullptr, nullptr, 0, + forceStencilFill ? "forceStencilFill" : nullptr); AABoxClass bbox; @@ -3416,12 +3865,12 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) beY = bbox.Extent.Y; beZ = bbox.Extent.Z; - if (m_shadowList && TheGlobalData->m_useShadowVolumes) + // TheSuperHackers @feature bobtista 15/06/2026 Suppress stencil volumes while the + // bgfx sun shadow map is active so the two systems do not double-darken. + if (m_shadowList && TheGlobalData->m_useShadowVolumes && !TheGlobalData->m_bgfxShadowMaps) { - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); - - if (!m_pDev) + if (!g_renderBackend || g_renderBackend->Is_Device_Lost()) return; //need device to render anything. //According to Nvidia there's a D3D bug that happens if you don't start with a @@ -3431,80 +3880,73 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) //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); - m_pDev->SetRenderState( D3DRS_ZENABLE, TRUE ); - m_pDev->SetRenderState(D3DRS_ZWRITEENABLE , FALSE); - m_pDev->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); - m_pDev->SetRenderState(D3DRS_FOGENABLE, FALSE); + g_renderBackend->Set_Depth_Func(RB_CMP_LESS_EQUAL); + g_renderBackend->Set_Depth_Test_Enable(true); + g_renderBackend->Set_Depth_Write_Enable(false); + g_renderBackend->Set_Alpha_Test_Enable(false); + g_renderBackend->Set_Fog_Enable(false); // setup the TMU to default - m_pDev->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT); - m_pDev->SetRenderState(D3DRS_LIGHTING, FALSE); - m_pDev->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - m_pDev->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); - m_pDev->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG2); - m_pDev->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - m_pDev->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, 0 ); - - m_pDev->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_DISABLE); - m_pDev->SetTextureStageState( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); - m_pDev->SetTextureStageState( 1, D3DTSS_TEXCOORDINDEX, 1 ); - m_pDev->SetTexture(0,nullptr); - m_pDev->SetTexture(1,nullptr); + g_renderBackend->Set_Shade_Mode(RB_SHADE_FLAT); + g_renderBackend->Set_Lighting_Enable(false); + g_renderBackend->Configure_Shadow_Volume_Fill_Texture_Stages(); + g_renderBackend->Bind_Texture_Immediate(0, nullptr); + g_renderBackend->Bind_Texture_Immediate(1, nullptr); DWORD oldColorWriteEnable=0x12345678; #ifdef SV_DEBUG - m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE , TRUE); - m_pDev->SetRenderState( D3DRS_STENCILENABLE, FALSE ); - m_pDev->SetRenderState( D3DRS_SRCBLEND, /*D3DBLEND_DESTCOLOR*/D3DBLEND_ONE ); - m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO ); - m_pDev->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); + g_renderBackend->Set_Alpha_Blend_Enable(true); + g_renderBackend->Set_Stencil_Enable(false); + g_renderBackend->Set_Blend_Factors(RB_BLEND_ONE, RB_BLEND_ZERO); + g_renderBackend->Set_Depth_Func(RB_CMP_LESS_EQUAL); #else //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); + if (g_renderBackend->Supports_Color_Write_Mask()) + { oldColorWriteEnable = g_renderBackend->Get_Color_Write_Mask(); + g_renderBackend->Set_Color_Write_Mask(0); } else { //device does not support disabling writes to color buffer so fake it through alpha blending - m_pDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ZERO ); - m_pDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); - m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE , TRUE); + g_renderBackend->Set_Blend_Factors(RB_BLEND_ZERO, RB_BLEND_ONE); + g_renderBackend->Set_Alpha_Blend_Enable(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 ); - - m_pDev->SetVertexShader(SHADOW_DYNAMIC_VOLUME_FVF); - - m_pDev->SetRenderState(D3DRS_CULLMODE,D3DCULL_CW); -// m_pDev->SetRenderState(D3DRS_ZBIAS,1); ///@todo: See if this helps or makes things worse. - //m_pDev->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME); + 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); + const bool bgfxZFailVolumes = BgfxUseShadowVolumeZFail(); + const bool bgfxSwapZFailOps = BgfxSwapShadowVolumeZFailOps(); + g_renderBackend->Set_Stencil_ZFail_Op(bgfxZFailVolumes + ? (bgfxSwapZFailOps ? RB_STENCIL_OP_DECR : RB_STENCIL_OP_INCR) + : RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Fail_Op(RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Pass_Op(bgfxZFailVolumes + ? RB_STENCIL_OP_KEEP + : (bgfxSwapZFailOps + ? RB_STENCIL_OP_DECR_SAT + : (BgfxUseSaturatedShadowVolumeIncrement() ? RB_STENCIL_OP_INCR_SAT : RB_STENCIL_OP_INCR))); + + g_renderBackend->Set_Vertex_Shader(SHADOW_DYNAMIC_VOLUME_FVF); + + g_renderBackend->Set_Cull_Mode(RB_CULL_CW); lastActiveVertexBuffer=nullptr; //reset @@ -3534,7 +3976,7 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) } // Set vertex format to that used by static shadow volumes - m_pDev->SetVertexShader(W3DBufferManager::getDX8Format(W3DBufferManager::VBM_FVF_XYZ)); + g_renderBackend->Set_Vertex_Shader(W3DBufferManager::getDX8Format(W3DBufferManager::VBM_FVF_XYZ)); //Empty queue of static shadow volumes to render. W3DBufferManager::W3DVertexBuffer *nextVb; @@ -3551,14 +3993,21 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) } // change the stencil op to decrement - m_pDev->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_DECRSAT); + g_renderBackend->Set_Stencil_ZFail_Op(bgfxZFailVolumes + ? (bgfxSwapZFailOps ? RB_STENCIL_OP_INCR : RB_STENCIL_OP_DECR) + : RB_STENCIL_OP_KEEP); + g_renderBackend->Set_Stencil_Pass_Op(bgfxZFailVolumes + ? RB_STENCIL_OP_KEEP + : (bgfxSwapZFailOps + ? (BgfxUseSaturatedShadowVolumeIncrement() ? RB_STENCIL_OP_INCR_SAT : RB_STENCIL_OP_INCR) + : 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)) { @@ -3570,7 +4019,7 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) } } - m_pDev->SetVertexShader(SHADOW_DYNAMIC_VOLUME_FVF); + g_renderBackend->Set_Vertex_Shader(SHADOW_DYNAMIC_VOLUME_FVF); //flush any dynamic shadow volumes shadowDynamicTask=m_dynamicShadowVolumesToRender; while (shadowDynamicTask) @@ -3586,13 +4035,14 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) nextVb->m_renderTaskList=nullptr; } - m_pDev->SetRenderState(D3DRS_CULLMODE,D3DCULL_CW); -// m_pDev->SetRenderState(D3DRS_ZBIAS,0); ///@todo: See if this helps or makes things worse. - //m_pDev->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); + g_renderBackend->Set_Cull_Mode(RB_CULL_CW); + // 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 @@ -3601,29 +4051,35 @@ void W3DVolumetricShadowManager::renderShadows( Bool forceStencilFill ) ///@todo: Put this check back in after water is fixed so it doesn't require shadow rendering to fix alpha. // if (numRenderedShadows) renderStencilShadows(); + LogVolumetricShadowPath("renderShadows-end", nullptr, nullptr, nullptr, + numRenderedShadows, "volumes"); - m_pDev->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); - m_pDev->SetRenderState(D3DRS_ALPHABLENDENABLE , FALSE); - m_pDev->SetRenderState(D3DRS_LIGHTING, FALSE); + g_renderBackend->Set_Shade_Mode(RB_SHADE_GOURAUD); + g_renderBackend->Set_Alpha_Blend_Enable(false); + g_renderBackend->Set_Lighting_Enable(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(); + LogVolumetricShadowPath("renderShadows-end", nullptr, nullptr, nullptr, + 0, "forceStencilFill"); - DX8Wrapper::Invalidate_Cached_Render_States(); + g_renderBackend->Invalidate_Cached_Render_States(); } } @@ -3720,12 +4176,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. @@ -3737,31 +4189,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(RenderIndexBufferClass, (ShadowDynamicIndexCapacity(), Render_Buffer_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(RenderVertexBufferClass, (SHADOW_DYNAMIC_VOLUME_FVF, ShadowDynamicVertexCapacity(), Render_Buffer_Usage_Dynamic())); + if (shadowVertexBuffer == nullptr) return FALSE; } @@ -3798,8 +4233,20 @@ void W3DVolumetricShadowManager::reset() // ============================================================================ W3DVolumetricShadow* W3DVolumetricShadowManager::addShadow(RenderObjClass *robj, Shadow::ShadowTypeInfo *shadowInfo, Drawable *draw) { - if (!DX8Wrapper::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) + { + LogVolumetricShadowPath("addShadow-skip", robj, draw, shadowInfo, 0, + "no-stencil-no-robj-or-disabled"); return nullptr; //right now we require a stencil buffer + } + if (ShouldSkipBgfxStaticVolumeShadow(draw)) + { + LogVolumetricShadowPath("addShadow-skip", robj, draw, shadowInfo, 0, + "bgfx-dynamic-volume-only"); + return nullptr; + } W3DShadowGeometry *sg=nullptr; if (!robj) @@ -3808,7 +4255,11 @@ W3DVolumetricShadow* W3DVolumetricShadowManager::addShadow(RenderObjClass *robj, const char *name=robj->Get_Name(); if (!name) + { + LogVolumetricShadowPath("addShadow-skip", robj, draw, shadowInfo, 0, + "no-render-object-name"); return nullptr; + } sg=m_W3DShadowGeometryManager->Get_Geom(name); @@ -3818,17 +4269,31 @@ W3DVolumetricShadow* W3DVolumetricShadowManager::addShadow(RenderObjClass *robj, //try loading again sg=m_W3DShadowGeometryManager->Get_Geom(name); if (sg==nullptr) + { + LogVolumetricShadowPath("addShadow-skip", robj, draw, shadowInfo, 0, + "missing-shadow-geometry"); return nullptr; //could not create the shadow geometry + } } W3DVolumetricShadow *shadow = NEW W3DVolumetricShadow; // poolify // sanity if( shadow == nullptr ) + { + LogVolumetricShadowPath("addShadow-skip", robj, draw, shadowInfo, 0, + "allocation-failed"); return nullptr; + } shadow->setRenderObject(robj); shadow->SetGeometry(sg); +#if defined(GGC_BGFX_RENDERER_METAL) + const ThingTemplate *tmplate = draw != nullptr ? draw->getTemplate() : nullptr; + shadow->setUseVerticalShadowProjection(tmplate != nullptr + && (tmplate->isKindOf(KINDOF_AIRCRAFT) + || tmplate->isKindOf(KINDOF_DRONE))); +#endif SphereClass sphere; robj->Get_Obj_Space_Bounding_Sphere(sphere); shadow->setRenderObjExtent(sphere.Radius*MAX_SHADOW_LENGTH_SCALE_FACTOR); @@ -3846,6 +4311,7 @@ W3DVolumetricShadow* W3DVolumetricShadowManager::addShadow(RenderObjClass *robj, // add to our shadow list through the shadow next links shadow->m_next = m_shadowList; m_shadowList = shadow; + LogVolumetricShadowPath("addShadow", robj, draw, shadowInfo, 0, nullptr); return shadow; } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DAssetManager.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DAssetManager.cpp index 899dfcb3f74..6d0cea9c809 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DAssetManager.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DAssetManager.cpp @@ -44,7 +44,13 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include +#include +#include +#include +#include +#include #include "W3DDevice/GameClient/W3DAssetManager.h" +#include "GgcRuntimeFlags.h" #include "WW3D2/proto.h" #include "WW3D2/rendobj.h" #include @@ -54,10 +60,10 @@ #include "WW3D2/meshmdl.h" #include "WW3D2/part_emt.h" #include "WW3D2/vertmaterial.h" -#include "WW3D2/dx8wrapper.h" #include "WW3D2/texture.h" #include "WW3D2/surfaceclass.h" #include "WW3D2/textureloader.h" +#include "WW3D2/ww3dcolor.h" #include "WW3D2/ww3dformat.h" #include "WW3D2/colorspace.h" #include @@ -69,6 +75,74 @@ #include "Common/GlobalData.h" #include "Common/GameCommon.h" +namespace +{ +static Bool W3DAssetDiagEnabled() +{ + static Int enabled = -1; + if (enabled == -1) + { + enabled = GgcFlags::Enabled(GgcFlag_W3dAssetDiag) ? 1 : 0; + } + return enabled != 0; +} + +static FILE *W3DAssetDiagFile() +{ + static FILE *fp = nullptr; + if (fp == nullptr && W3DAssetDiagEnabled()) + { + fp = fopen("ggc_w3d_asset_diag.txt", "wt"); + } + return fp; +} + +static SurfaceClass *Create_Texture_Mip_Surface(TextureClass *texture, unsigned int level) +{ + if (texture == nullptr) { + return nullptr; + } + + const std::vector &mips = texture->Get_CPU_Texture_Mips(); + if (level < mips.size()) { + const TextureBaseClass::TextureMipSnapshot &mip = mips[level]; + const unsigned bytes_per_pixel = Get_Bytes_Per_Pixel(mip.Format); + const unsigned row_size = mip.Width * bytes_per_pixel; + if (mip.Format != WW3D_FORMAT_UNKNOWN && + mip.Width != 0 && + mip.Height != 0 && + bytes_per_pixel != 0 && + mip.Pitch >= row_size && + mip.Data.size() >= static_cast(mip.Pitch) * mip.Height) + { + SurfaceClass *surface = NEW_REF(SurfaceClass, (mip.Width, mip.Height, mip.Format)); + surface->Copy(mip.Data.data(), mip.Pitch); + return surface; + } + } + +#if !defined(GGC_RENDER_BACKEND_BGFX) + return texture->Get_Surface_Level(level); +#else + return nullptr; +#endif +} + +static void W3DAssetDiagLog(const char *fmt, ...) +{ + FILE *fp = W3DAssetDiagFile(); + if (fp == nullptr) + { + return; + } + + va_list args; + va_start(args, fmt); + vfprintf(fp, fmt, args); + va_end(args); + fflush(fp); +} +} //--------------------------------------------------------------------- // Constants @@ -152,7 +226,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 +264,7 @@ TextureClass *W3DAssetManager::Get_Texture( ) { #ifdef DUMP_PERF_STATS - __int64 startTime64,endTime64; + std::int64_t startTime64, endTime64; GetPrecisionTimer(&startTime64); #endif @@ -667,7 +741,10 @@ TextureClass * W3DAssetManager::Recolor_Texture_One_Time(TextureClass *texture, psize=Get_Bytes_Per_Pixel(desc.Format); DEBUG_ASSERTCRASH( psize == 2 || psize == 4, ("Can't Recolor Texture %s", name) ); - oldsurf=texture->Get_Surface_Level(); + oldsurf=Create_Texture_Mip_Surface(texture, 0); + if (oldsurf == nullptr) { + return nullptr; + } newsurf=NEW_REF(SurfaceClass,(desc.Width,desc.Height,desc.Format)); newsurf->Copy(0,0,0,0,desc.Width,desc.Height,oldsurf); @@ -699,7 +776,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,18 +791,20 @@ RenderObjClass * W3DAssetManager::Create_Render_Obj( ) { #ifdef DUMP_PERF_STATS - __int64 startTime64,endTime64; + std::int64_t startTime64, endTime64; GetPrecisionTimer(&startTime64); #endif - Bool reallyscale = (WWMath::Fabs(scale - ident_scale) > scale_epsilon); + Bool reallyscale = (WWMath::Fabsf_Legacy(scale - ident_scale) > scale_epsilon); Bool reallycolor = (color & 0xFFFFFF) != 0; //black is not a valid color and assumes no custom coloring. Bool reallytexture = (oldTexture != nullptr && newTexture != nullptr); // base case, no scale or color if (!reallyscale && !reallycolor && !reallytexture) { + W3DAssetDiagLog("create-base-delegate name=%s scale=%g color=%08x texture=%d\n", name, scale, color, reallytexture ? 1 : 0); RenderObjClass *robj=WW3DAssetManager::Create_Render_Obj(name); + W3DAssetDiagLog("create-base-result name=%s robj=%p\n", name, robj); #ifdef DUMP_PERF_STATS GetPrecisionTimer(&endTime64); Total_Create_Render_Obj_Time += endTime64-startTime64; @@ -762,6 +841,7 @@ RenderObjClass * W3DAssetManager::Create_Render_Obj( // Try to find a prototype PrototypeClass * proto = Find_Prototype(name); + W3DAssetDiagLog("create-custom-start name=%s scale=%g color=%08x protoBefore=%p\n", name, scale, color, proto); Set_WW3D_Load_On_Demand(true); // Auto Load. if (WW3D_Load_On_Demand && proto == nullptr) @@ -771,20 +851,25 @@ 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); } // If we can't find it, try the parent directory - if ( Load_3D_Assets( filename ) == false ) + bool loaded = Load_3D_Assets( filename ); + W3DAssetDiagLog("create-custom-load name=%s filename=%s loaded=%d\n", name, filename, loaded ? 1 : 0); + if ( loaded == false ) { StringClass new_filename = StringClass("..\\") + filename; - Load_3D_Assets(new_filename); + bool parentLoaded = Load_3D_Assets(new_filename); + W3DAssetDiagLog("create-custom-load-parent name=%s filename=%s loaded=%d\n", name, new_filename.str(), parentLoaded ? 1 : 0); } proto = Find_Prototype(name); // try again + W3DAssetDiagLog("create-custom-after-load name=%s protoAfter=%p\n", name, proto); } if (proto == nullptr) @@ -802,6 +887,7 @@ RenderObjClass * W3DAssetManager::Create_Render_Obj( } rendobj = proto->Create(); + W3DAssetDiagLog("create-custom-proto-create name=%s proto=%p robj=%p\n", name, proto, rendobj); if (!rendobj) { @@ -960,7 +1046,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 +1055,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 @@ -992,7 +1078,9 @@ bool W3DAssetManager::Load_3D_Assets( const char * filename ) return TRUE; //this file has already been loaded. } + W3DAssetDiagLog("load-begin filename=%s\n", filename); bool result = WW3DAssetManager::Load_3D_Assets(filename); + W3DAssetDiagLog("load-end filename=%s result=%d\n", filename, result ? 1 : 0); #if defined(RTS_DEBUG) if (result && TheGlobalData->m_preloadReport) @@ -1020,7 +1108,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 +1117,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" ); @@ -1330,9 +1418,9 @@ static inline void Munge_Texture_Name(char *newname, const char *oldname, const RenderObjClass * W3DAssetManager::Create_Render_Obj(const char * name,float scale, const Vector3 &hsv_shift) { Bool isGranny = false; - Bool reallyscale = (WWMath::Fabs(scale - ident_scale) > scale_epsilon); - Bool reallyhsv_shift = (WWMath::Fabs(hsv_shift.X - ident_HSV.X) > H_epsilon || - WWMath::Fabs(hsv_shift.Y - ident_HSV.Y) > S_epsilon || WWMath::Fabs(hsv_shift.Z - ident_HSV.Z) > V_epsilon); + Bool reallyscale = (WWMath::Fabsf(scale - ident_scale) > scale_epsilon); + Bool reallyhsv_shift = (WWMath::Fabsf(hsv_shift.X - ident_HSV.X) > H_epsilon || + WWMath::Fabsf(hsv_shift.Y - ident_HSV.Y) > S_epsilon || WWMath::Fabsf(hsv_shift.Z - ident_HSV.Z) > V_epsilon); // base case, no scale or hue shifting if (!reallyscale && !reallyhsv_shift) return WW3DAssetManager::Create_Render_Obj(name); @@ -1371,13 +1459,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 @@ -1429,8 +1518,8 @@ TextureClass * W3DAssetManager::Get_Texture_With_HSV_Shift(const char * filename { WWPROFILE( "W3DAssetManager::Get_Texture with HSV shift" ); - Bool is_hsv_shift = (WWMath::Fabs(hsv_shift.X - ident_HSV.X) > H_epsilon || - WWMath::Fabs(hsv_shift.Y - ident_HSV.Y) > S_epsilon || WWMath::Fabs(hsv_shift.Z - ident_HSV.Z) > V_epsilon); + Bool is_hsv_shift = (WWMath::Fabsf(hsv_shift.X - ident_HSV.X) > H_epsilon || + WWMath::Fabsf(hsv_shift.Y - ident_HSV.Y) > S_epsilon || WWMath::Fabsf(hsv_shift.Z - ident_HSV.Z) > V_epsilon); if (!is_hsv_shift) { @@ -1502,9 +1591,9 @@ void W3DAssetManager::Recolor_Vertices(unsigned int *color, int count, const Vec for (i=0; i(rgba),hsv_shift); - color[i]=DX8Wrapper::Convert_Color_Clamp(rgba); + color[i]=WW3DColor::To_ARGB_Clamp(rgba); } } @@ -1537,7 +1626,10 @@ TextureClass * W3DAssetManager::Recolor_Texture_One_Time(TextureClass *texture, // if texture is monochrome and no value shifting // return nullptr - smallsurf=texture->Get_Surface_Level((TextureClass::MipCountType)texture->Get_Mip_Level_Count()-1); + smallsurf=Create_Texture_Mip_Surface(texture, texture->Get_Mip_Level_Count() - 1); + if (smallsurf == nullptr) { + return nullptr; + } if (hsv_shift.Z==0.0f && smallsurf->Is_Monochrome()) { REF_PTR_RELEASE(smallsurf); @@ -1545,7 +1637,10 @@ TextureClass * W3DAssetManager::Recolor_Texture_One_Time(TextureClass *texture, } REF_PTR_RELEASE(smallsurf); - oldsurf=texture->Get_Surface_Level(); + oldsurf=Create_Texture_Mip_Surface(texture, 0); + if (oldsurf == nullptr) { + return nullptr; + } newsurf=NEW_REF(SurfaceClass,(desc.Width,desc.Height,desc.Format)); newsurf->Copy(0,0,0,0,desc.Width,desc.Height,oldsurf); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBibBuffer.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBibBuffer.cpp index 5f47d7362d7..5e4b22578cb 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBibBuffer.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBibBuffer.cpp @@ -56,7 +56,7 @@ #include "W3DDevice/GameClient/HeightMap.h" #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" @@ -104,8 +104,8 @@ void W3DBibBuffer::loadBibsInVertexAndIndexBuffers() VertexFormatXYZDUV1 *vb; UnsignedShort *ib; // Lock the buffers. - DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBib, D3DLOCK_DISCARD); - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexBib, D3DLOCK_DISCARD); + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBib, RB_LOCK_DISCARD); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexBib, RB_LOCK_DISCARD); vb=(VertexFormatXYZDUV1*)lockVtxBuffer.Get_Vertex_Array(); ib = lockIdxBuffer.Get_Index_Array(); // Add to the index buffer & vertex buffer. @@ -259,8 +259,13 @@ void W3DBibBuffer::freeBibBuffers() //============================================================================= void W3DBibBuffer::allocateBibBuffers() { - m_vertexBib=NEW_REF(DX8VertexBufferClass,(DX8_FVF_XYZDUV1,m_vertexBibSize+4,DX8VertexBufferClass::USAGE_DYNAMIC)); - m_indexBib=NEW_REF(DX8IndexBufferClass,(m_indexBibSize+4, DX8IndexBufferClass::USAGE_DYNAMIC)); + m_vertexBib=NEW_REF(RenderVertexBufferClass,( + RENDER_VERTEX_FORMAT_XYZDUV1, + m_vertexBibSize+4, + Render_Buffer_Usage_Dynamic())); + m_indexBib=NEW_REF(RenderIndexBufferClass,( + m_indexBibSize+4, + Render_Buffer_Usage_Dynamic())); m_curNumBibVertices=0; m_curNumBibIndices=0; } @@ -423,19 +428,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..1b57290a9d5 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBridgeBuffer.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DBridgeBuffer.cpp @@ -65,7 +65,7 @@ #include "W3DDevice/GameClient/W3DShaderManager.h" #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 +131,10 @@ are already set. */ void W3DBridge::renderBridge(Bool wireframe) { if (m_visible && m_numPolygons && m_numVertex) { - if (!wireframe) DX8Wrapper::Set_Texture(0,m_bridgeTexture); - // Draw all the bridges. - DX8Wrapper::Draw_Triangles( m_firstIndex, m_numPolygons, m_firstVertex, m_numVertex); + if (!wireframe) { + g_renderBackend->Set_Texture(0, m_bridgeTexture); + } + g_renderBackend->Draw_Triangles(m_firstIndex, m_numPolygons, m_firstVertex, m_numVertex); } } @@ -246,6 +247,13 @@ Bool W3DBridge::load(BodyDamageType curDamageState) strlcat(right, ".BRIDGE_RIGHT", ARRAY_SIZE(right)); m_bridgeTexture = pMgr->Get_Texture(textureFile, MIP_LEVELS_3); + if (m_bridgeTexture != nullptr && g_renderBackend->Has_Shader_Pipeline()) { + // Bridge textures are compact atlases whose lower mips can collapse + // black padding into visible deck pixels under bgfx/Metal. Keep the + // authored level-0 texels stable; the bgfx backend binds a one-mip + // sibling whenever the stage mip filter is disabled. + m_bridgeTexture->Get_Filter().Set_Mip_Mapping(TextureFilterClass::FILTER_TYPE_NONE); + } m_leftMtx.Make_Identity(); m_rightMtx.Make_Identity(); m_sectionMtx.Make_Identity(); @@ -693,8 +701,8 @@ void W3DBridgeBuffer::loadBridgesInVertexAndIndexBuffers(RefRenderObjListIterato VertexFormatXYZNDUV1 *vb; UnsignedShort *ib; // Lock the buffers. - DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBridge, D3DLOCK_DISCARD); - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexBridge, D3DLOCK_DISCARD); + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBridge, RB_LOCK_DISCARD); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexBridge, RB_LOCK_DISCARD); vb=(VertexFormatXYZNDUV1*)lockVtxBuffer.Get_Vertex_Array(); ib = lockIdxBuffer.Get_Index_Array(); @@ -766,8 +774,13 @@ void W3DBridgeBuffer::allocateBridgeBuffers() { if (TheGlobalData->m_headless) return; - m_vertexBridge=NEW_REF(DX8VertexBufferClass,(DX8_FVF_XYZNDUV1,MAX_BRIDGE_VERTEX+4,DX8VertexBufferClass::USAGE_DYNAMIC)); - m_indexBridge=NEW_REF(DX8IndexBufferClass,(MAX_BRIDGE_INDEX+4, DX8IndexBufferClass::USAGE_DYNAMIC)); + m_vertexBridge=NEW_REF(RenderVertexBufferClass,( + RENDER_VERTEX_FORMAT_XYZNDUV1, + MAX_BRIDGE_VERTEX+4, + Render_Buffer_Usage_Dynamic())); + m_indexBridge=NEW_REF(RenderIndexBufferClass,( + MAX_BRIDGE_INDEX+4, + Render_Buffer_Usage_Dynamic())); m_vertexMaterial=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); #ifdef USE_BRIDGE_NORMALS m_vertexMaterial= NEW VertexMaterialClass(); @@ -1152,19 +1165,22 @@ void W3DBridgeBuffer::drawBridges(CameraClass * camera, Bool wireframe, TextureC return; } - DX8Wrapper::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); -#ifdef RTS_DEBUG - //DX8Wrapper::Set_Shader(detailShader); // shows alpha clipping. -#endif - - DX8Wrapper::Apply_Render_State_Changes(); + // TheSuperHackers @refactor bobtista 10/04/2026 Route high-level calls + // through the IRenderBackend abstraction. + g_renderBackend->Set_Material(m_vertexMaterial); + g_renderBackend->Set_Index_Buffer(m_indexBridge,0); + g_renderBackend->Set_Vertex_Buffer(m_vertexBridge,0); + g_renderBackend->Set_Shader(detailAlphaShader); + if (g_renderBackend->Has_Shader_Pipeline()) { + g_renderBackend->Set_Texture_Coord_Source(0, RB_TEXCOORD_MESH_UV, 0); + g_renderBackend->Clear_Texture_Transform(0); + g_renderBackend->Set_Texture_Transform_Mode(0, 0, false); + g_renderBackend->Set_Texture_Mip_Filter(0, RB_TEXTURE_SAMPLE_NONE); + } + g_renderBackend->Apply_Render_State_Changes(); - if (!wireframe && cloudTexture) - { //Force a cloud texture projection into stage 1 + if (!wireframe && cloudTexture && !g_renderBackend->Has_Shader_Pipeline()) + { W3DShaderManager::setTexture(1,cloudTexture); W3DShaderManager::setShader(W3DShaderManager::ST_CLOUD_TEXTURE,1); } @@ -1176,30 +1192,23 @@ void W3DBridgeBuffer::drawBridges(CameraClass * camera, Bool wireframe, TextureC } if (!wireframe && cloudTexture) - //Force a cloud texture projection into stage 1 W3DShaderManager::resetShader(W3DShaderManager::ST_CLOUD_TEXTURE); - //Render shroud pass over all the bridges 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(); - //Apply custom shroud projection shader. + 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(); W3DShaderManager::setTexture(0,TheTerrainRenderObject->getShroud()->getShroudTexture()); W3DShaderManager::setShader(W3DShaderManager::ST_SHROUD_TEXTURE, 0); for (curBridge=0; curBridge())); + m_indexEdging=NEW_REF(RenderIndexBufferClass,( + 2*MAX_EDGE_INDEX+4, + Render_Buffer_Usage_Dynamic())); m_curNumEdgingVertices=0; m_curNumEdgingIndices=0; //m_edgeTexture = MSGNEW("TextureClass") TextureClass("EdgingTemplate.tga","EdgingTemplate.tga", MIP_LEVELS_3); @@ -354,80 +360,55 @@ 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->Override_Alpha_Test(true, 0x7B, RB_CMP_LESS_EQUAL); + 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->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. - 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->Set_Alpha_Blend_Enable(false); + g_renderBackend->Set_Alpha_Test_Enable(false); //test pixels if transparent(clipped) before rendering. + 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(); -#if 1 - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAARG1, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_CURRENT ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_TEXTURE ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_ALPHAARG1, D3DTA_CURRENT ); - 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 ); -#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); - DX8Wrapper::Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); + 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(); + g_renderBackend->Configure_Custom_Edging_Cloud_Texture_Stages(); + 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) { - 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(); - - 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); - DX8Wrapper::Draw_Triangles( m_curEdgingIndexOffset, m_curNumEdgingIndices/3, 0, m_curNumEdgingVertices); + 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(); + + 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/W3DDebugIcons.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDebugIcons.cpp index f41be9aa6bf..3098b18a40b 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDebugIcons.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDebugIcons.cpp @@ -51,7 +51,13 @@ #include "Common/GlobalData.h" #include "GameLogic/GameLogic.h" #include "Common/MapObject.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" +// TheSuperHackers @build bobtista 30/05/2026 Restore DynamicVBAccess/DynamicIBAccess, +// BUFFER_TYPE_DYNAMIC and ShaderClass declarations needed by the RTS_DEBUG path after +// the "neutral dynamic buffer type at draw sites" refactor removed transitive includes. +#include "WW3D2/vertexbuffer.h" +#include "WW3D2/indexbuffer.h" +#include "WW3D2/shader.h" #if defined(RTS_DEBUG) @@ -215,14 +221,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; @@ -233,8 +241,8 @@ void W3DDebugIcons::Render(RenderInfoClass & rinfo) for (k=0; kSet_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/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 4e755562d15..6dfa43309bd 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -34,11 +34,13 @@ static void drawFramerateBar(); // SYSTEM INCLUDES //////////////////////////////////////////////////////////// +#include #include #include #include #include #include +#include // USER INCLUDES ////////////////////////////////////////////////////////////// #include "Common/FramePacer.h" @@ -58,6 +60,7 @@ static void drawFramerateBar(); #include "GameClient/Drawable.h" #include "GameClient/GameText.h" #include "GameClient/GraphDraw.h" +#include "GameClient/Image.h" #include "GameClient/Line2D.h" #include "GameClient/Mouse.h" #include "GameClient/GlobalLanguage.h" @@ -77,6 +80,7 @@ static void drawFramerateBar(); #include "W3DDevice/GameClient/W3DScene.h" #include "W3DDevice/GameClient/W3DTerrainTracks.h" #include "W3DDevice/GameClient/W3DWater.h" +#include "WW3D2/statistics.h" #include "W3DDevice/GameClient/W3DVideoBuffer.h" #include "W3DDevice/GameClient/W3DShaderManager.h" #include "W3DDevice/GameClient/W3DDebugDisplay.h" @@ -86,12 +90,15 @@ static void drawFramerateBar(); #include "WWMath/wwmath.h" #include "WWLib/registry.h" #include "WW3D2/ww3d.h" +#include "WW3D2/BgfxRenderProfile.h" #include "WW3D2/predlod.h" #include "WW3D2/part_emt.h" #include "WW3D2/part_ldr.h" -#include "WW3D2/dx8caps.h" +#include "WW3D2/renderdebugstats.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" @@ -102,6 +109,7 @@ static void drawFramerateBar(); #include "WW3D2/meshmdl.h" #include "WW3D2/rddesc.h" #include "WWLib/TARGA.h" +#include "GgcRuntimeFlags.h" #include "GameLogic/ScriptEngine.h" // For TheScriptEngine - jkmcd #include "GameLogic/GameLogic.h" @@ -122,6 +130,54 @@ static Real theLightYOffset = 0.07f; static Int theFlashCount = 0; #endif +static RectClass makeAtlasSafeUVRect(const Image *image) +{ + const Region2D *uv = image->getUV(); + RectClass uvRect(uv->lo.x, uv->lo.y, uv->hi.x, uv->hi.y); + + // TheSuperHackers @bugfix bobtista 17/07/2026 Only inset atlas sub-rects on the shader + // pipeline. The legacy fixed-function backend sampled the raw UV rect in retail; applying + // the half-texel inset there shifted UI sampling on the DX8 build. + if (g_renderBackend == nullptr || !g_renderBackend->Has_Shader_Pipeline()) + { + return uvRect; + } + + if (BitIsSet(image->getStatus(), IMAGE_STATUS_RAW_TEXTURE)) + { + return uvRect; + } + + const ICoord2D *textureSize = image->getTextureSize(); + if (textureSize == nullptr || textureSize->x <= 0 || textureSize->y <= 0) + { + return uvRect; + } + + const bool isAtlasSubRect = + uvRect.Left > 0.0f || uvRect.Top > 0.0f || + uvRect.Right < 1.0f || uvRect.Bottom < 1.0f; + if (!isAtlasSubRect) + { + return uvRect; + } + + const float halfU = 0.5f / static_cast(textureSize->x); + const float halfV = 0.5f / static_cast(textureSize->y); + if (uvRect.Width() > halfU * 2.0f) + { + uvRect.Left += halfU; + uvRect.Right -= halfU; + } + if (uvRect.Height() > halfV * 2.0f) + { + uvRect.Top += halfV; + uvRect.Bottom -= halfV; + } + + return uvRect; +} + //***************************************************************************************** //***************************************************************************************** //**** Start Statistical Dump ************************************************************* @@ -386,6 +442,11 @@ W3DDisplay::W3DDisplay() Int i; m_initialized = false; + for (i = 0; i < MAX_TRACKING_LIGHTS; ++i) + { + m_trackingLights[i].emitterId = 0; + m_trackingLights[i].light = nullptr; + } m_assetManager = nullptr; m_3DScene = nullptr; m_2DScene = nullptr; @@ -488,50 +549,68 @@ inline Bool isResolutionSupported(const ResolutionDescClass &res) return res.Width >= DEFAULT_DISPLAY_WIDTH && res.BitDepth >= minBitDepth; } -/*Return number of screen modes supported by the current device*/ -Int W3DDisplay::getDisplayModeCount() -{ #if defined(SAGE_USE_SDL3) +// TheSuperHackers @bugfix bobtista 11/06/2026 Offer a curated set of standard resolutions clamped +// to the desktop size (raw SDL fullscreen modes are non-standard on HiDPI and omit 800x600); +// setDisplayMode already snaps requests to the nearest real fullscreen mode. +struct OptionsResolution { int w; int h; }; + +static int Build_Options_Resolution_List(OptionsResolution * out, int cap) +{ extern SDL_Window *TheSDL3Window; + int maxW = 1920; + int maxH = 1080; if (TheSDL3Window != nullptr) { - if (!getWindowed()) - { - return 1; - } - const SDL_DisplayID display = SDL_GetDisplayForWindow(TheSDL3Window); - const SDL_DisplayMode *desktop = SDL_GetDesktopDisplayMode(display); - int maxW = desktop ? desktop->w : 1920; - int maxH = desktop ? desktop->h : 1080; - int count = 0; - SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes(display, &count); - if (modes == nullptr) + const SDL_DisplayMode *desktop = SDL_GetDesktopDisplayMode(SDL_GetDisplayForWindow(TheSDL3Window)); + if (desktop != nullptr) { - return 0; + maxW = desktop->w; + maxH = desktop->h; } - int unique = 0; - for (int i = 0; i < count; ++i) + } + static const OptionsResolution standard[] = { + { 800, 600 }, { 1024, 768 }, { 1152, 864 }, { 1280, 720 }, { 1280, 800 }, + { 1280, 960 }, { 1280, 1024 }, { 1360, 768 }, { 1366, 768 }, { 1440, 900 }, + { 1600, 900 }, { 1600, 1200 }, { 1680, 1050 }, { 1920, 1080 }, { 1920, 1200 }, + { 2560, 1440 }, { 2560, 1600 }, { 3840, 2160 } + }; + const int standardCount = sizeof(standard) / sizeof(standard[0]); + int n = 0; + bool hasNative = false; + for (int i = 0; i < standardCount && n < cap; ++i) + { + if (standard[i].w >= DEFAULT_DISPLAY_WIDTH && standard[i].h >= DEFAULT_DISPLAY_HEIGHT + && standard[i].w <= maxW && standard[i].h <= maxH) { - if (modes[i]->w < 800 || modes[i]->h < 600 || modes[i]->w > maxW || modes[i]->h > maxH) - { - continue; - } - bool dup = false; - for (int j = 0; j < i; ++j) - { - if (modes[j]->w == modes[i]->w && modes[j]->h == modes[i]->h) - { - dup = true; - break; - } - } - if (!dup) + out[n] = standard[i]; + ++n; + if (standard[i].w == maxW && standard[i].h == maxH) { - ++unique; + hasNative = true; } } - SDL_free(modes); - return unique; + } + if (!hasNative && n < cap && maxW >= DEFAULT_DISPLAY_WIDTH && maxH >= DEFAULT_DISPLAY_HEIGHT) + { + out[n].w = maxW; + out[n].h = maxH; + ++n; + } + DEBUG_LOG(("Options resolution list: %d entries (desktop %dx%d)", n, maxW, maxH)); + return n; +} +#endif + +/*Return number of screen modes supported by the current device*/ +Int W3DDisplay::getDisplayModeCount() +{ +#if defined(SAGE_USE_SDL3) + extern SDL_Window *TheSDL3Window; + if (TheSDL3Window != nullptr) + { + OptionsResolution list[32]; + return Build_Options_Resolution_List(list, 32); } #endif const RenderDeviceDescClass &devDesc=WW3D::Get_Render_Device_Desc(0); @@ -555,52 +634,14 @@ void W3DDisplay::getDisplayModeDescription(Int modeIndex, Int *xres, Int *yres, extern SDL_Window *TheSDL3Window; if (TheSDL3Window != nullptr) { - if (!getWindowed()) + OptionsResolution list[32]; + int n = Build_Options_Resolution_List(list, 32); + if (modeIndex >= 0 && modeIndex < n) { - *xres = getWidth(); - *yres = getHeight(); + *xres = list[modeIndex].w; + *yres = list[modeIndex].h; *bitDepth = 32; - return; - } - const SDL_DisplayID display = SDL_GetDisplayForWindow(TheSDL3Window); - const SDL_DisplayMode *desktop = SDL_GetDesktopDisplayMode(display); - int maxW = desktop ? desktop->w : 1920; - int maxH = desktop ? desktop->h : 1080; - int count = 0; - SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes(display, &count); - if (modes != nullptr) - { - int unique = 0; - for (int i = 0; i < count; ++i) - { - if (modes[i]->w < 800 || modes[i]->h < 600 || modes[i]->w > maxW || modes[i]->h > maxH) - { - continue; - } - bool dup = false; - for (int j = 0; j < i; ++j) - { - if (modes[j]->w == modes[i]->w && modes[j]->h == modes[i]->h) - { - dup = true; - break; - } - } - if (!dup) - { - if (unique == modeIndex) - { - *xres = modes[i]->w; - *yres = modes[i]->h; - *bitDepth = SDL_BITSPERPIXEL(modes[i]->format); - SDL_free(modes); - return; - } - ++unique; - } - } } - SDL_free(modes); return; } #endif @@ -629,7 +670,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 */ @@ -643,11 +684,34 @@ Bool W3DDisplay::setDisplayMode( UnsignedInt xres, UnsignedInt yres, UnsignedInt #if defined(SAGE_USE_SDL3) extern SDL_Window *TheSDL3Window; - if (TheSDL3Window != nullptr && windowed) + if (TheSDL3Window != nullptr) { - SDL_SetWindowFullscreen(TheSDL3Window, false); - SDL_SetWindowSize(TheSDL3Window, xres, yres); - SDL_SyncWindow(TheSDL3Window); + if (windowed) + { + SDL_SetWindowFullscreen(TheSDL3Window, false); + SDL_SetWindowSize(TheSDL3Window, xres, yres); + SDL_SyncWindow(TheSDL3Window); + } + else + { + // TheSuperHackers @feature bobtista 07/06/2026 Real fullscreen at non-native + // resolutions. Startup and the old code only ever used SDL_SetWindowFullscreenMode + // with nullptr (desktop fullscreen), which forces native resolution. Select the + // closest real fullscreen mode for the requested size and switch to exclusive + // fullscreen so non-native resolutions actually mode-switch and fill the screen. + const SDL_DisplayID display = SDL_GetDisplayForWindow(TheSDL3Window); + SDL_DisplayMode mode; + if (SDL_GetClosestFullscreenDisplayMode(display, (int)xres, (int)yres, 0.0f, false, &mode)) + { + SDL_SetWindowFullscreenMode(TheSDL3Window, &mode); + } + else + { + SDL_SetWindowFullscreenMode(TheSDL3Window, nullptr); + } + SDL_SetWindowFullscreen(TheSDL3Window, true); + SDL_SyncWindow(TheSDL3Window); + } } #endif if (WW3D_ERROR_OK == WW3D::Set_Device_Resolution(xres,yres,bitdepth,windowed,true)) @@ -665,6 +729,26 @@ Bool W3DDisplay::setDisplayMode( UnsignedInt xres, UnsignedInt yres, UnsignedInt return FALSE; //did not change to a new mode. } +// TheSuperHackers @bugfix bobtista 08/06/2026 Sync the engine-side resolution (render device, 2D +// coordinate range, display dimensions) to an OS-driven window size change WITHOUT touching the SDL +// window. macOS fullscreen settles to the usable area below the menu bar/notch (e.g. 982 -> 949) +// after the window is shown, and a windowed drag-resize changes it live. bgfx already tracks the +// real window size, so re-driving SDL via setDisplayMode here would re-enter the fullscreen +// transition and destabilize it (it grabbed transition-intermediate sizes and crashed). The +// windowed flag and bit depth are kept as-is; only the dimensions follow the window. +Bool W3DDisplay::applyExternalResize( UnsignedInt xres, UnsignedInt yres ) +{ + // Update only the engine-side coordinate state. Deliberately do NOT call Set_Device_Resolution: + // the bgfx backend already resizes its backbuffer and scene render targets to the live window + // size every frame in Begin_Scene, so a device reset here is redundant - and calling it from the + // event-poll context (mid-frame, outside the options-menu/game-start flow it was written for) + // crashes. Updating the 2D coordinate range and the display dimensions is enough for the mouse + // mapping, UI layout and camera to follow the window. + Render2DClass::Set_Screen_Resolution(RectClass(0, 0, xres, yres)); + Display::setDisplayMode(xres, yres, getBitDepth(), getWindowed()); + return TRUE; +} + /** Set width of display */ //============================================================================= void W3DDisplay::setWidth( UnsignedInt width ) @@ -916,7 +1000,6 @@ void W3DDisplay::init() WW3D::Set_Collision_Box_Display_Mask(0x00); ///m_windowed ); @@ -976,14 +1059,31 @@ void W3DDisplay::init() extern SDL_Window *TheSDL3Window; if (TheSDL3Window != nullptr) { - int wPts = 0, hPts = 0; - SDL_GetWindowSize(TheSDL3Window, &wPts, &hPts); - if (wPts > 0 && hPts > 0) + // TheSuperHackers @bugfix bobtista 25/06/2026 In windowed mode the SDL window is created at + // a fixed default size before the saved Option Preferences (or -xres/-yres) are parsed, so + // size it to the resolution set above (TheGlobalData->m_xResolution) rather than overwriting + // that resolution with the default window size - otherwise the saved windowed resolution + // never persists across launches. Fullscreen still adopts the actual drawable size. + if (TheGlobalData->m_windowed) + { + SDL_SetWindowSize(TheSDL3Window, getWidth(), getHeight()); + // TheSuperHackers @bugfix bobtista 25/06/2026 Re-center after resizing: SDL_SetWindowSize + // keeps the top-left corner, so growing from the default window size leaves the window + // off-center (and partly off-screen at larger resolutions). + SDL_SetWindowPosition(TheSDL3Window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + SDL_SyncWindow(TheSDL3Window); + } + else { - setWidth(wPts); - setHeight(hPts); - TheWritableGlobalData->m_xResolution = wPts; - TheWritableGlobalData->m_yResolution = hPts; + int wPts = 0, hPts = 0; + SDL_GetWindowSize(TheSDL3Window, &wPts, &hPts); + if (wPts > 0 && hPts > 0) + { + setWidth(wPts); + setHeight(hPts); + TheWritableGlobalData->m_xResolution = wPts; + TheWritableGlobalData->m_yResolution = hPts; + } } } #endif @@ -1022,6 +1122,11 @@ void W3DDisplay::init() return; } + // TheSuperHackers @fix bobtista 21/04/2026 Legacy half-pixel UV bias causes sub-pixel misalignment on shader backends (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 keeps text aligned on the legacy fixed-function backend. + //Check if level was never set and default to setting most suitable for system. if (TheGameLODManager->getStaticLODLevel() == STATIC_GAME_LOD_UNKNOWN) { @@ -1297,76 +1402,76 @@ void W3DDisplay::gatherDebugStats() } else if (statMode == gameOverhead) { gameOverheadMS = ms; statMode = console; - DX8Wrapper::stats.m_disableTerrain = true; - DX8Wrapper::stats.m_disableOverhead = true; - DX8Wrapper::stats.m_disableWater = true; - DX8Wrapper::stats.m_disableObjects = true; - DX8Wrapper::stats.m_disableConsole = false; - DX8Wrapper::stats.m_debugLinesToShow = 1; + g_renderDebugStats.m_disableTerrain = true; + g_renderDebugStats.m_disableOverhead = true; + g_renderDebugStats.m_disableWater = true; + g_renderDebugStats.m_disableObjects = true; + g_renderDebugStats.m_disableConsole = false; + g_renderDebugStats.m_debugLinesToShow = 1; } else if (statMode == console) { consoleMS = ms; statMode = threeDOverhead; - DX8Wrapper::stats.m_disableTerrain = true; - DX8Wrapper::stats.m_disableOverhead = true; - DX8Wrapper::stats.m_disableWater = true; - DX8Wrapper::stats.m_disableObjects = true; - DX8Wrapper::stats.m_disableConsole = true; - DX8Wrapper::stats.m_debugLinesToShow = 1; + g_renderDebugStats.m_disableTerrain = true; + g_renderDebugStats.m_disableOverhead = true; + g_renderDebugStats.m_disableWater = true; + g_renderDebugStats.m_disableObjects = true; + g_renderDebugStats.m_disableConsole = true; + g_renderDebugStats.m_debugLinesToShow = 1; } else if (statMode == threeDOverhead) { threeDOverheadMS = ms; statMode = terrain; - DX8Wrapper::stats.m_disableTerrain = false; - DX8Wrapper::stats.m_disableOverhead = true; - DX8Wrapper::stats.m_disableWater = true; - DX8Wrapper::stats.m_disableObjects = true; - DX8Wrapper::stats.m_disableConsole = true; - DX8Wrapper::stats.m_debugLinesToShow = 1; + g_renderDebugStats.m_disableTerrain = false; + g_renderDebugStats.m_disableOverhead = true; + g_renderDebugStats.m_disableWater = true; + g_renderDebugStats.m_disableObjects = true; + g_renderDebugStats.m_disableConsole = true; + g_renderDebugStats.m_debugLinesToShow = 1; } else if (statMode == terrain) { terrainMS = ms; statMode = objects; - DX8Wrapper::stats.m_disableOverhead = true; - DX8Wrapper::stats.m_disableTerrain = true; - DX8Wrapper::stats.m_disableWater = true; - DX8Wrapper::stats.m_disableObjects = false; - DX8Wrapper::stats.m_disableConsole = true; - DX8Wrapper::stats.m_debugLinesToShow = 1; + g_renderDebugStats.m_disableOverhead = true; + g_renderDebugStats.m_disableTerrain = true; + g_renderDebugStats.m_disableWater = true; + g_renderDebugStats.m_disableObjects = false; + g_renderDebugStats.m_disableConsole = true; + g_renderDebugStats.m_debugLinesToShow = 1; } else if (statMode == objects) { objectMS = ms; statMode = overlap; - DX8Wrapper::stats.m_disableOverhead = false; - DX8Wrapper::stats.m_disableTerrain = false; - DX8Wrapper::stats.m_disableWater = false; - DX8Wrapper::stats.m_disableObjects = false; - DX8Wrapper::stats.m_disableConsole = true; - DX8Wrapper::stats.m_sleepTime = (int)(terrainMS); - DX8Wrapper::stats.m_debugLinesToShow = 1; + g_renderDebugStats.m_disableOverhead = false; + g_renderDebugStats.m_disableTerrain = false; + g_renderDebugStats.m_disableWater = false; + g_renderDebugStats.m_disableObjects = false; + g_renderDebugStats.m_disableConsole = true; + g_renderDebugStats.m_sleepTime = (int)(terrainMS); + g_renderDebugStats.m_debugLinesToShow = 1; } else if (statMode == overlap) { overlapMS = ms; statMode = normal; - DX8Wrapper::stats.m_disableOverhead = false; - DX8Wrapper::stats.m_disableTerrain = false; - DX8Wrapper::stats.m_disableWater = false; - DX8Wrapper::stats.m_disableObjects = false; - DX8Wrapper::stats.m_disableConsole = true; - DX8Wrapper::stats.m_sleepTime = 0; - DX8Wrapper::stats.m_debugLinesToShow = 1; + g_renderDebugStats.m_disableOverhead = false; + g_renderDebugStats.m_disableTerrain = false; + g_renderDebugStats.m_disableWater = false; + g_renderDebugStats.m_disableObjects = false; + g_renderDebugStats.m_disableConsole = true; + g_renderDebugStats.m_sleepTime = 0; + g_renderDebugStats.m_debugLinesToShow = 1; } else if (statMode == normal) { overlapMS = (ms + ((int)terrainMS) - overlapMS ); statMode = disabled; extendedStats = SHOW_STATS_TIME; // Done collecting stats. Re-enable stuff - DX8Wrapper::stats.m_disableConsole = false; - DX8Wrapper::stats.m_debugLinesToShow = -1; - } else if (!DX8Wrapper::stats.m_showingStats) { + g_renderDebugStats.m_disableConsole = false; + g_renderDebugStats.m_debugLinesToShow = -1; + } else if (!g_renderDebugStats.m_showingStats) { // start collecting extended info. - DX8Wrapper::stats.m_showingStats = true; - DX8Wrapper::stats.m_disableOverhead = false; - DX8Wrapper::stats.m_disableTerrain = true; - DX8Wrapper::stats.m_disableWater = true; - DX8Wrapper::stats.m_disableObjects = true; - DX8Wrapper::stats.m_disableConsole = true; - DX8Wrapper::stats.m_debugLinesToShow = 1; + g_renderDebugStats.m_showingStats = true; + g_renderDebugStats.m_disableOverhead = false; + g_renderDebugStats.m_disableTerrain = true; + g_renderDebugStats.m_disableWater = true; + g_renderDebugStats.m_disableObjects = true; + g_renderDebugStats.m_disableConsole = true; + g_renderDebugStats.m_debugLinesToShow = 1; statMode = sync; gameOverheadMS = 0.0f; threeDOverheadMS = 0.0f; @@ -1728,9 +1833,9 @@ void W3DDisplay::drawDebugStats() int linesOfStrings = DisplayStringCount; #ifdef EXTENDED_STATS - if (DX8Wrapper::stats.m_debugLinesToShow > -1) + if (g_renderDebugStats.m_debugLinesToShow > -1) { - linesOfStrings = DX8Wrapper::stats.m_debugLinesToShow; + linesOfStrings = g_renderDebugStats.m_debugLinesToShow; } #endif @@ -1901,6 +2006,9 @@ void W3DDisplay::step() //DECLARE_PERF_TIMER(W3DDisplay_draw) void W3DDisplay::draw() { + PROFILER_SECTION; + GGCRenderProfile::EndFrame(); + GGC_RPROFILE(FRAME_DRAW); //USE_PERF_TIMER(W3DDisplay_draw) extern HWND ApplicationHWnd; @@ -1963,7 +2071,7 @@ void W3DDisplay::draw() #ifdef EXTENDED_STATS else { - DX8Wrapper::stats.m_showingStats = false; + g_renderDebugStats.m_showingStats = false; } #endif @@ -2046,28 +2154,41 @@ void W3DDisplay::draw() do { // update all views of the world - recomputes data which will affect drawing - if (DX8Wrapper::_Get_D3D_Device8() && (DX8Wrapper::_Get_D3D_Device8()->TestCooperativeLevel()) == D3D_OK) + const Bool renderDeviceReady = (g_renderBackend != nullptr && !g_renderBackend->Is_Device_Lost()); + if (renderDeviceReady) { //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) - updateViews(); - 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) - //THE RESULT IS THAT PARTICLESYSTEMS LINKED TO BONES IN DRAWABLES.OBJECTS - //MOVE WITH THE CLIENT TRANSFORMS, NOW. - //REVOLUTIONARY! - //-LORENZEN + { + PROFILER_SECTION_NAME("update views"); + GGC_RPROFILE(UPDATE_VIEWS); + updateViews(); + } + { + PROFILER_SECTION_NAME("particle update"); + GGC_RPROFILE(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) + //THE RESULT IS THAT PARTICLESYSTEMS LINKED TO BONES IN DRAWABLES.OBJECTS + //MOVE WITH THE CLIENT TRANSFORMS, NOW. + //REVOLUTIONARY! + //-LORENZEN + } - if (TheWaterRenderObj && TheGlobalData->m_waterType == 2) - TheWaterRenderObj->updateRenderTargetTextures(primaryW3DView->get3DCamera()); //do a render into each texture + { + PROFILER_SECTION_NAME("render to texture"); + GGC_RPROFILE(RTT); + if (TheWaterRenderObj && TheGlobalData->m_waterType == 2) + TheWaterRenderObj->updateRenderTargetTextures(primaryW3DView->get3DCamera()); //do a render into each texture //Can't render into textures while rendering to screen so these textures need to be updated //before we enter main rendering loop. if (TheW3DProjectedShadowManager) TheW3DProjectedShadowManager->updateRenderTargetTextures(); + } } Debug_Statistics::End_Statistics(); //record number of polygons rendered in RenderTargetTextures. @@ -2102,8 +2223,13 @@ void W3DDisplay::draw() Debug_Statistics::Record_DX8_Polys_And_Vertices(numRenderTargetPolygons,numRenderTargetVertices,ShaderClass::_PresetOpaqueShader); // draw all views of the world - drawViews(); + { + GGC_RPROFILE(DRAW_VIEWS); + drawViews(); + } + { + GGC_RPROFILE(UI_DRAW); // draw the user interface TheInGameUI->DRAW(); @@ -2112,6 +2238,7 @@ void W3DDisplay::draw() // draw the mouse if( TheMouse ) TheMouse->DRAW(); + } if ( m_videoStream && m_videoBuffer ) { @@ -2184,7 +2311,10 @@ void W3DDisplay::draw() } #endif // render is all done! - WW3D::End_Render(); + { + GGC_RPROFILE(END_RENDER); + WW3D::End_Render(); + } } else { @@ -2204,7 +2334,7 @@ void W3DDisplay::draw() } while (freezeTime && !TheTacticalView->isCameraMovementFinished()); #ifdef EXTENDED_STATS - if (DX8Wrapper::stats.m_disableOverhead) { + if (g_renderDebugStats.m_disableOverhead) { goto AGAIN; } #endif @@ -2285,8 +2415,8 @@ Bool W3DDisplay::isLetterBoxed() void W3DDisplay::createLightPulse( const Coord3D *pos, const RGBColor *color, Real innerRadius, Real attenuationWidth, UnsignedInt increaseFrameTime, - UnsignedInt decayFrameTime//, Bool donut - ) + UnsignedInt decayFrameTime, + Bool castsShadows, Real shadowBias, Real shadowStrength ) { if (m_3DScene == nullptr) return; @@ -2304,11 +2434,157 @@ void W3DDisplay::createLightPulse( const Coord3D *pos, const RGBColor *color, theDynamicLight->setFrameFade(increaseFrameTime, decayFrameTime); theDynamicLight->setDecayRange(); theDynamicLight->setDecayColor(); - //theDynamicLight->setDonut(donut); + theDynamicLight->setCastsShadows(castsShadows); + theDynamicLight->setShadowBias(shadowBias); + theDynamicLight->setShadowStrength(shadowStrength); + // TheSuperHackers @bugfix bobtista 15/07/2026 A shadow-casting pulse lights receivers through + // the dedicated shadowed point-light path in the uber shader; it must not ALSO enter the + // per-object LightEnvironment, which double-lights receivers unshadowed and clips bright + // foliage texels into white speckles under MODULATE2X materials. Set unconditionally: pooled + // lights are reused and would otherwise carry a stale flag. + theDynamicLight->setExcludeFromLightEnv(castsShadows); // (gth) CNC3 enable far attenuation. C&C3 defaults to disabled. Must enable to match Generals. MW 8-06-03 theDynamicLight->Set_Flag(LightClass::FAR_ATTENUATION,true); } +// TheSuperHackers @feature bobtista 23/06/2026 Reposition a single persistent dynamic light each +// frame for a moving emitter (particle-cannon beam), instead of spawning a fresh pulse per frame. +// The light's colour and shadow strength ease toward the requested beam intensity, with a longer +// decay window kept armed so the light tails off if the emitter stops refreshing it. This avoids both +// per-frame flicker from overlapping pulses and building receiver snaps at beam start/end. +W3DDynamicLight *W3DDisplay::getTrackingLight( const W3DDynamicLight *exclude ) const +{ + for (Int tl = 0; tl < MAX_TRACKING_LIGHTS; ++tl) + { + W3DDynamicLight *light = m_trackingLights[tl].light; + if (light != nullptr && light != exclude && light->isEnabled() && light->getCastsShadows()) + { + return light; + } + } + return nullptr; +} + +Bool W3DDisplay::isTrackingLight( const W3DDynamicLight *light ) const +{ + for (Int tl = 0; tl < MAX_TRACKING_LIGHTS; ++tl) + { + if (m_trackingLights[tl].light == light) + { + return TRUE; + } + } + return FALSE; +} + +void W3DDisplay::updateTrackingLight( const Coord3D *pos, const RGBColor *color, + Real innerRadius, Real attenuationWidth, + Bool castsShadows, Real shadowBias, Real shadowStrength, Bool snapBlend, + UnsignedInt emitterId ) +{ + if (m_3DScene == nullptr) + return; + // TheSuperHackers @bugfix bobtista 16/07/2026 One light PER EMITTER: two cannons firing at + // once fought over a single light, snapping every drama element between the beams per frame. + // Find this emitter's live slot, else claim a dead one. + Int slot = -1; + for (Int tl = 0; tl < MAX_TRACKING_LIGHTS; ++tl) + { + if (m_trackingLights[tl].light != nullptr + && m_trackingLights[tl].emitterId == emitterId + && m_trackingLights[tl].light->isEnabled()) + { + slot = tl; + break; + } + } + if (slot == -1) + { + for (Int tl = 0; tl < MAX_TRACKING_LIGHTS; ++tl) + { + if (m_trackingLights[tl].light == nullptr || !m_trackingLights[tl].light->isEnabled()) + { + slot = tl; + break; + } + } + } + if (slot == -1) + { + return; // every slot is busy with another live emitter + } + const Bool hadActiveTrackingLight = (m_trackingLights[slot].light != nullptr + && m_trackingLights[slot].light->isEnabled() + && m_trackingLights[slot].emitterId == emitterId); + if (m_trackingLights[slot].light == nullptr || !m_trackingLights[slot].light->isEnabled()) + { + m_trackingLights[slot].light = m_3DScene->getADynamicLight(); + } + m_trackingLights[slot].emitterId = emitterId; + W3DDynamicLight *light = m_trackingLights[slot].light; + Vector3 desiredColor( color->red, color->green, color->blue ); + Real desiredShadowStrength = shadowStrength; + Vector3 currentColor( 0.0f, 0.0f, 0.0f ); + Real currentShadowStrength = 0.0f; + if (hadActiveTrackingLight) + { + light->Get_Diffuse( ¤tColor ); + currentShadowStrength = light->getShadowStrength(); + } + // TheSuperHackers @feature bobtista 14/07/2026 snapBlend keeps a touch of smoothing but lets + // per-frame flicker from the emitter come through mostly intact (dramatic beam lighting). + const Real upBlend = snapBlend ? 0.65f : 0.02f; + const Real downSmooth = snapBlend ? 0.70f : 0.96f; + const Real shadowUpBlend = snapBlend ? 0.65f : 0.08f; + const Real shadowDownSmooth = snapBlend ? 0.75f : 0.98f; + if (desiredColor.X >= currentColor.X) { desiredColor.X = currentColor.X + (desiredColor.X - currentColor.X) * upBlend; } + else if (desiredColor.X < currentColor.X * downSmooth) { desiredColor.X = currentColor.X * downSmooth; } + if (desiredColor.Y >= currentColor.Y) { desiredColor.Y = currentColor.Y + (desiredColor.Y - currentColor.Y) * upBlend; } + else if (desiredColor.Y < currentColor.Y * downSmooth) { desiredColor.Y = currentColor.Y * downSmooth; } + if (desiredColor.Z >= currentColor.Z) { desiredColor.Z = currentColor.Z + (desiredColor.Z - currentColor.Z) * upBlend; } + else if (desiredColor.Z < currentColor.Z * downSmooth) { desiredColor.Z = currentColor.Z * downSmooth; } + if (desiredShadowStrength >= currentShadowStrength) + { + desiredShadowStrength = currentShadowStrength + (desiredShadowStrength - currentShadowStrength) * shadowUpBlend; + } + else if (desiredShadowStrength < currentShadowStrength * shadowDownSmooth) + { + desiredShadowStrength = currentShadowStrength * shadowDownSmooth; + } + light->setEnabled(true); + light->Set_Ambient( desiredColor ); + light->Set_Diffuse( desiredColor ); + light->Set_Position( Vector3( pos->x, pos->y, pos->z ) ); + // Same rule as createLightPulse: a shadow-casting tracking light illuminates through the + // dedicated shadowed point-light path only (see @bugfix 15/07/2026 there). + light->setExcludeFromLightEnv(castsShadows); + light->Set_Far_Attenuation_Range( innerRadius, innerRadius + attenuationWidth ); + // Keep a decay window armed every refresh. If the emitter stops calling updateTrackingLight, the + // current eased colour/shadow strength tails off instead of disappearing on the next render frame. + light->setFrameFade(0, 45); + light->setCastsShadows(castsShadows); + light->setShadowBias(shadowBias); + light->setShadowStrength(desiredShadowStrength); + light->setDecayRange(); + light->setDecayColor(); + light->Set_Flag(LightClass::FAR_ATTENUATION, true); +} + +void W3DDisplay::clearTrackingLight( void ) +{ + for (Int tl = 0; tl < MAX_TRACKING_LIGHTS; ++tl) + { + if (m_trackingLights[tl].light != nullptr) + { + m_trackingLights[tl].light->setFrameFade(0, 45); + m_trackingLights[tl].light->setDecayRange(); + m_trackingLights[tl].light->setDecayColor(); + m_trackingLights[tl].light = nullptr; + m_trackingLights[tl].emitterId = 0; + } + } +} + void W3DDisplay::toggleLetterBox() { m_letterBoxEnabled = !m_letterBoxEnabled; @@ -2860,19 +3136,39 @@ void W3DDisplay::drawImage( const Image *image, Int startX, Int startY, // but it not derived on the W3DDisplay // !! - const Region2D *uv = image->getUV(); - TextureClass *tex = nullptr; if (BitIsSet(image->getStatus(), IMAGE_STATUS_RAW_TEXTURE)) tex = (TextureClass *)(image->getRawTextureData()); else tex = WW3DAssetManager::Get_Instance()->Get_Texture(image->getFilename().str(), MIP_LEVELS_1); + if (GgcFlags::Enabled(GgcFlag_MapPreviewDiag) && image->getFilename().endsWith(".tga")) + { + AsciiString imageName = image->getName(); + AsciiString filename = image->getFilename(); + if (imageName.startsWith("maps_") || imageName.startsWith("userdata_maps_") || + filename.startsWith("maps_") || filename.startsWith("userdata_maps_")) + { + FILE *f = fopen("ggc_map_preview_diag.txt", "a"); + if (f != nullptr) + { + fprintf(f, "drawImage name='%s' filename='%s' status=0x%x tex=%p texInit=%d texFmt=%d texPath='%s' imageSize=%dx%d texSize=%dx%d screen=(%d,%d)-(%d,%d) mode=%d color=0x%08x\n", + imageName.str(), filename.str(), image->getStatus(), tex, + tex ? tex->Is_Initialized() : 0, + tex ? static_cast(tex->Get_Texture_Format()) : -1, + tex ? tex->Get_Full_Path().str() : "(null)", + image->getImageSize()->x, image->getImageSize()->y, + image->getTextureSize()->x, image->getTextureSize()->y, + startX, startY, endX, endY, static_cast(mode), color); + fclose(f); + } + } + } Bool grayscale = (mode == DRAW_IMAGE_GRAYSCALE); setup2DRenderState(tex, mode, grayscale); RectClass screen_rect(startX,startY,endX,endY); - RectClass uv_rect(uv->lo.x,uv->lo.y,uv->hi.x,uv->hi.y); + RectClass uv_rect = makeAtlasSafeUVRect(image); if (m_isClippedEnabled) { //need to clip this quad to clip rectangle @@ -3006,28 +3302,39 @@ VideoBuffer* W3DDisplay::createVideoBuffer() // first try to use the native format - WW3DFormat displayFormat = DX8Wrapper::getBackBufferFormat(); +#if defined(__APPLE__) + // bgfx uploads D3D-style X8R8G8B8 video buffers as BGRA8, which preserves + // the BGR0 frames FFmpeg produces on little-endian macOS. Avoid 16-bit + // R5G6B5 here; it is both slower in swscale and currently renders with + // swapped-looking colors through the bgfx texture path. + if ( g_renderBackend && g_renderBackend->Supports_Texture_Format( WW3D_FORMAT_X8R8G8B8 )) + { + format = VideoBuffer::TYPE_X8R8G8B8; + } +#endif - if ( DX8Wrapper::Get_Current_Caps()->Support_Texture_Format( displayFormat )) + WW3DFormat displayFormat = g_renderBackend->Get_Back_Buffer_Format(); + + if ( format == VideoBuffer::TYPE_UNKNOWN && g_renderBackend && g_renderBackend->Supports_Texture_Format( displayFormat )) { format = W3DVideoBuffer::W3DFormatToType( displayFormat ); } if ( format == VideoBuffer::TYPE_UNKNOWN ) { - if ( DX8Wrapper::Get_Current_Caps()->Support_Texture_Format( WW3D_FORMAT_X8R8G8B8 )) + if ( g_renderBackend && g_renderBackend->Supports_Texture_Format( WW3D_FORMAT_X8R8G8B8 )) { format = VideoBuffer::TYPE_X8R8G8B8; } - else if ( DX8Wrapper::Get_Current_Caps()->Support_Texture_Format( WW3D_FORMAT_R8G8B8 )) + else if ( g_renderBackend && g_renderBackend->Supports_Texture_Format( WW3D_FORMAT_R8G8B8 )) { format = VideoBuffer::TYPE_R8G8B8; } - else if ( DX8Wrapper::Get_Current_Caps()->Support_Texture_Format( WW3D_FORMAT_R5G6B5 )) + else if ( g_renderBackend && g_renderBackend->Supports_Texture_Format( WW3D_FORMAT_R5G6B5 )) { format = VideoBuffer::TYPE_R5G6B5; } - else if ( DX8Wrapper::Get_Current_Caps()->Support_Texture_Format( WW3D_FORMAT_X1R5G5B5 )) + else if ( g_renderBackend && g_renderBackend->Supports_Texture_Format( WW3D_FORMAT_X1R5G5B5 )) { format = VideoBuffer::TYPE_X1R5G5B5; } @@ -3038,8 +3345,10 @@ VideoBuffer* W3DDisplay::createVideoBuffer() } } // on low mem machines, render every video in 16bit +#if !defined(__APPLE__) if (TheGameLODManager && (!TheGameLODManager->didMemPass() || W3DShaderManager::getChipset() == DC_GEFORCE2)) format = VideoBuffer::TYPE_R5G6B5; +#endif W3DVideoBuffer *buffer = NEW W3DVideoBuffer( format ); @@ -3092,7 +3401,10 @@ void W3DDisplay::drawVideoBuffer( VideoBuffer *buffer, Int startX, Int startY, I { W3DVideoBuffer *vbuffer = (W3DVideoBuffer*) buffer; - setup2DRenderState(vbuffer->texture(), DRAW_IMAGE_ALPHA, FALSE); + // Video buffers are opaque frames. The legacy D3D X8 formats sampled the + // unused alpha byte as 1.0; drawing them solid keeps the bgfx path from + // depending on undefined X-channel contents. + setup2DRenderState(vbuffer->texture(), DRAW_IMAGE_SOLID, FALSE); m_2DRender->Add_Quad( RectClass( startX, startY, endX, endY ), vbuffer->Rect( 0, 0, 1, 1) ); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDynamicLight.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDynamicLight.cpp index e7b7ce9b02d..846097b751a 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDynamicLight.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDynamicLight.cpp @@ -28,6 +28,7 @@ #include #include "W3DDevice/GameClient/W3DDynamicLight.h" +#include "GameLogic/GameLogic.h" W3DDynamicLight::W3DDynamicLight(): LightClass(LightClass::POINT) @@ -35,6 +36,12 @@ LightClass(LightClass::POINT) m_priorEnable = false; m_enabled = true; + m_castsShadows = FALSE; + m_shadowBias = 0.0f; + m_shadowStrength = 1.0f; + m_targetShadowStrength = 1.0f; + m_excludeFromLightEnv = FALSE; + m_lastFadeLogicFrame = 0xFFFFFFFF; } @@ -47,6 +54,17 @@ void W3DDynamicLight::On_Frame_Update() if (!m_enabled) { return; } + // TheSuperHackers @bugfix bobtista 17/07/2026 Advance a shadow-casting pulse's fade at most once + // per logic frame so its ramp/decay tracks real time, not render framerate. Without this a + // load-settle or high-fps burst runs the whole pulse in a handful of render frames, flashing the + // cast shadow on and off. Vanilla (non-shadow) lights keep their original per-render-frame fade. + if (m_castsShadows && TheGameLogic != NULL) { + UnsignedInt logicFrame = TheGameLogic->getFrame(); + if (logicFrame == m_lastFadeLogicFrame) { + return; + } + m_lastFadeLogicFrame = logicFrame; + } Real factor = 1.0f; if (m_curIncreaseFrameCount>0 && m_increaseFrameCount>0) { // increasing @@ -72,6 +90,7 @@ void W3DDynamicLight::On_Frame_Update() if (m_decayColor) { this->Ambient = m_targetAmbient*factor; this->Diffuse = m_targetDiffuse*factor; + m_shadowStrength = m_targetShadowStrength*factor; } } @@ -81,6 +100,7 @@ void W3DDynamicLight::setFrameFade(UnsignedInt frameIncreaseTime, UnsignedInt de m_curDecayFrameCount = decayFrameTime; m_curIncreaseFrameCount = frameIncreaseTime; m_increaseFrameCount = frameIncreaseTime; + m_lastFadeLogicFrame = 0xFFFFFFFF; m_targetAmbient = Ambient; m_targetDiffuse = Diffuse; m_targetRange = FarAttenEnd; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DFileSystem.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DFileSystem.cpp index 80097fe1ead..6485bab17a5 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DFileSystem.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DFileSystem.cpp @@ -47,9 +47,12 @@ #include "Common/GlobalData.h" #include "Common/MapObject.h" #include "Common/Registry.h" +#include "GgcRuntimeFlags.h" #include "W3DDevice/GameClient/W3DFileSystem.h" #include +#include +#include // DEFINES //////////////////////////////////////////////////////////////////////////////////////// @@ -68,6 +71,44 @@ typedef enum FILE_TYPE_DDS, } GameFileType; +namespace +{ +static Bool W3DFileDiagEnabled() +{ + static Int enabled = -1; + if (enabled == -1) + { + enabled = GgcFlags::Enabled(GgcFlag_W3dFileDiag) ? 1 : 0; + } + return enabled != 0; +} + +static FILE *W3DFileDiagFile() +{ + static FILE *fp = nullptr; + if (fp == nullptr && W3DFileDiagEnabled()) + { + fp = fopen("ggc_w3d_file_diag.txt", "wt"); + } + return fp; +} + +static void W3DFileDiagProbe(const char *stage, const char *requested, const char *path, Bool exists) +{ + FILE *fp = W3DFileDiagFile(); + if (fp == nullptr) + { + return; + } + fprintf(fp, "%s requested=%s path=%s exists=%d\n", + stage ? stage : "", + requested ? requested : "", + path ? path : "", + exists); + fflush(fp); +} +} + //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- GameFileClass::GameFileClass( char const *filename ) @@ -187,6 +228,10 @@ char const * GameFileClass::Set_Name( char const *filename ) // see if the file exists m_fileExists = TheFileSystem->doesFileExist( m_filePath ); + if (W3DFileDiagEnabled() && fileType == FILE_TYPE_W3D) + { + W3DFileDiagProbe("localized", filename, m_filePath, m_fileExists); + } @@ -215,6 +260,10 @@ char const * GameFileClass::Set_Name( char const *filename ) // see if the file exists m_fileExists = TheFileSystem->doesFileExist( m_filePath ); + if (W3DFileDiagEnabled() && fileType == FILE_TYPE_W3D) + { + W3DFileDiagProbe("main", filename, m_filePath, m_fileExists); + } } @@ -311,9 +360,18 @@ char const * GameFileClass::Set_Name( char const *filename ) // see if the file exists m_fileExists = TheFileSystem->doesFileExist( m_filePath ); + if (W3DFileDiagEnabled() && fileType == FILE_TYPE_W3D) + { + W3DFileDiagProbe("user", filename, m_filePath, m_fileExists); + } } + if (W3DFileDiagEnabled() && fileType == FILE_TYPE_W3D && m_fileExists == FALSE) + { + W3DFileDiagProbe("missing-final", filename, m_filePath, m_fileExists); + } + return m_filename; } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DInGameUI.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DInGameUI.cpp index d1fb63d2af4..01066fc2c10 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DInGameUI.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DInGameUI.cpp @@ -49,6 +49,7 @@ #include "W3DDevice/Common/W3DConvert.h" #include "WW3D2/ww3d.h" #include "WW3D2/hanim.h" +#include "WW3D2/renderdebugstats.h" #include "Common/UnitTimings.h" //Contains the DO_UNIT_TIMINGS define jba. @@ -56,8 +57,8 @@ #ifdef RTS_DEBUG #include "W3DDevice/GameClient/HeightMap.h" -#include "WW3D2/dx8indexbuffer.h" -#include "WW3D2/dx8vertexbuffer.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/renderbufferclasses.h" #include "WW3D2/vertmaterial.h" class DebugHintObject : public RenderObjClass { @@ -87,10 +88,10 @@ class DebugHintObject : public RenderObjClass Int m_myColor; // argb Int m_mySize; - DX8IndexBufferClass *m_indexBuffer; + RenderIndexBufferClass *m_indexBuffer; ShaderClass m_shaderClass; //shader or rendering state for heightmap VertexMaterialClass *m_vertexMaterialClass; - DX8VertexBufferClass *m_vertexBufferTile; //First vertex buffer. + RenderVertexBufferClass *m_vertexBufferTile; //First vertex buffer. void initData(); }; @@ -172,18 +173,21 @@ void DebugHintObject::initData() { freeMapResources(); //free old data and ib/vb - m_indexBuffer = NEW_REF(DX8IndexBufferClass,(3)); + m_indexBuffer = NEW_REF(RenderIndexBufferClass,(3)); // Fill up the IB { - DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); UnsignedShort *ib=lockIdxBuffer.Get_Index_Array(); ib[0]=0; ib[1]=1; ib[2]=2; } - m_vertexBufferTile = NEW_REF(DX8VertexBufferClass,(DX8_FVF_XYZDUV1,3,DX8VertexBufferClass::USAGE_DEFAULT)); + m_vertexBufferTile = NEW_REF(RenderVertexBufferClass,( + RENDER_VERTEX_FORMAT_XYZDUV1, + 3, + Render_Buffer_Usage_Default())); //go with a preset material for now. m_vertexMaterialClass = VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); @@ -205,7 +209,7 @@ void DebugHintObject::setLocAndColorAndSize(const Coord3D *loc, Int argb, Int si if (m_vertexBufferTile) { - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexBufferTile); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(m_vertexBufferTile); VertexFormatXYZDUV1 *vb = (VertexFormatXYZDUV1*)lockVtxBuffer.Get_Vertex_Array(); Real x1 = m_mySize * 0.866; // cos(30) @@ -240,18 +244,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 @@ -421,7 +427,7 @@ void W3DInGameUI::draw() // repaint all our windows #ifdef EXTENDED_STATS - if (!DX8Wrapper::stats.m_disableConsole) { + if (!g_renderDebugStats.m_disableConsole) { #endif #ifdef DO_UNIT_TIMINGS @@ -733,4 +739,3 @@ void W3DInGameUI::drawPlaceAngle( View *view ) //TheDisplay->drawLine( start.x, start.y, end.x, end.y, width, color ); } - diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DRoadBuffer.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DRoadBuffer.cpp index 15c3d347d76..adb74823645 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DRoadBuffer.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DRoadBuffer.cpp @@ -62,7 +62,7 @@ #include "W3DDevice/GameClient/WorldHeightMap.h" #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 +159,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); } @@ -174,7 +176,16 @@ void RoadType::loadTexture(AsciiString path, Int ID) /// @todo - delay loading textures and only load textures referenced by map. WW3DAssetManager *pMgr = W3DAssetManager::Get_Instance(); +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @bugfix bobtista 17/06/2026 Road shape is encoded in the texture's alpha + // channel and the road is alpha-blended over the terrain. With mipmaps the alpha averages down + // at distance, so far-away roads fade to fully transparent and appear cut off (the cut shifts as + // the camera scrolls). bgfx mips the alpha more aggressively than the DX8 path, so keep road + // textures unmipped on bgfx to preserve the road silhouette into the distance. + m_roadTexture = pMgr->Get_Texture(path.str(), MIP_LEVELS_1); +#else m_roadTexture = pMgr->Get_Texture(path.str(), MIP_LEVELS_3); +#endif //Hack to disable texture reduction //m_roadTexture = pMgr->Get_Texture(path.str(), MIP_LEVELS_3, WW3D_FORMAT_UNKNOWN,true,TextureBaseClass::TEX_REGULAR, false); @@ -183,8 +194,13 @@ void RoadType::loadTexture(AsciiString path, Int ID) m_roadTexture->Get_Filter().Set_U_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_REPEAT); m_roadTexture->Get_Filter().Set_V_Addr_Mode(TextureFilterClass::TEXTURE_ADDRESS_REPEAT); - m_vertexRoad=NEW_REF(DX8VertexBufferClass,(DX8_FVF_XYZDUV1,TheGlobalData->m_maxRoadVertex+4, (s_dynamic?DX8VertexBufferClass::USAGE_DYNAMIC:DX8VertexBufferClass::USAGE_DEFAULT))); - m_indexRoad=NEW_REF(DX8IndexBufferClass,(TheGlobalData->m_maxRoadIndex+4, (s_dynamic?DX8IndexBufferClass::USAGE_DYNAMIC:DX8IndexBufferClass::USAGE_DEFAULT))); + m_vertexRoad=NEW_REF(RenderVertexBufferClass,( + RENDER_VERTEX_FORMAT_XYZDUV1, + TheGlobalData->m_maxRoadVertex+4, + (s_dynamic?Render_Buffer_Usage_Dynamic():Render_Buffer_Usage_Default()))); + m_indexRoad=NEW_REF(RenderIndexBufferClass,( + TheGlobalData->m_maxRoadIndex+4, + (s_dynamic?Render_Buffer_Usage_Dynamic():Render_Buffer_Usage_Default()))); m_numRoadVertices=0; m_numRoadIndices=0; @@ -1236,8 +1252,8 @@ void W3DRoadBuffer::loadRoadsInVertexAndIndexBuffers() this->m_roadTypes[m_curRoadType].setNumIndices(0); return; } - DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_roadTypes[m_curRoadType].getIB(), s_dynamic?D3DLOCK_DISCARD:0); - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_roadTypes[m_curRoadType].getVB(), s_dynamic?D3DLOCK_DISCARD:0); + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(m_roadTypes[m_curRoadType].getIB(), s_dynamic?RB_LOCK_DISCARD:0); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(m_roadTypes[m_curRoadType].getVB(), s_dynamic?RB_LOCK_DISCARD:0); vb=(VertexFormatXYZDUV1*)lockVtxBuffer.Get_Vertex_Array(); ib = lockIdxBuffer.Get_Index_Array(); // Add to the index buffer & vertex buffer. @@ -1315,8 +1331,8 @@ void W3DRoadBuffer::loadLitRoadsInVertexAndIndexBuffers(RefRenderObjListIterator VertexFormatXYZDUV1 *vb; UnsignedShort *ib; // Lock the buffers. - DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_roadTypes[m_curRoadType].getIB()); - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(m_roadTypes[m_curRoadType].getVB()); + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(m_roadTypes[m_curRoadType].getIB()); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(m_roadTypes[m_curRoadType].getVB()); vb=(VertexFormatXYZDUV1*)lockVtxBuffer.Get_Vertex_Array(); ib = lockIdxBuffer.Get_Index_Array(); // Add to the index buffer & vertex buffer. @@ -2896,7 +2912,7 @@ void W3DRoadBuffer::insertCurveSegmentAt(Int ndx1, Int ndx2) line1.Set(Vector3(pr1->X, pr1->Y, 0), Vector3(pr2->X, pr2->Y, 0)); line2.Set(Vector3(pr3->X, pr3->Y, 0), Vector3(pr4->X, pr4->Y, 0)); } - Real angle = WWMath::Acos(curSin); + Real angle = WWMath::Acos_Legacy(curSin); Real count = angle / (PI/6.0f); // number of 30 degree steps. if (count<0.9 || m_roads[ndx1].m_pt1.isAngled) { miter(ndx1, ndx2); @@ -3310,6 +3326,18 @@ void W3DRoadBuffer::drawRoads(CameraClass * camera, TextureClass *cloudTexture, //Find number of passes required to render current shader devicePasses=W3DShaderManager::getShaderPasses(st); +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @info bobtista 16/07/2026 Keep roads on the single base pass. The + // original reason (no TCI_CAMERASPACEPOSITION emulation) is obsolete - the backend + // emulates it now - but the gate stays because cloud shadows and the sun shadow already + // reach roads single-pass through the uber shader's cloud path; running the NOISE + // multipass as well would apply the cloud texture twice, and the NOISE12 second pass + // needs the unemulated ALPHAREPLICATE argument modifier. The lightmap (noise2) stage is + // equally unavailable for terrain on this backend. + st = W3DShaderManager::ST_ROAD_BASE; + devicePasses = 1; +#endif + W3DShaderManager::setTexture(1,cloudTexture); //cloud W3DShaderManager::setTexture(2,noiseTexture); //noise/lightmap @@ -3331,21 +3359,20 @@ 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(); } - #ifdef RTS_DEBUG - //DX8Wrapper::Set_Shader(detailShader); // shows clipping. - #endif for (Int pass=0; pass < devicePasses; pass++) { 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 +3390,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 m_curRoadType = 0; } - - diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp index d22790fd6d7..7080ddaf60d 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScene.cpp @@ -32,6 +32,7 @@ /////////////////////////////////////////////////////////////////////////////// // SYSTEM INCLUDES //////////////////////////////////////////////////////////// +#include #include // USER INCLUDES ////////////////////////////////////////////////////////////// @@ -46,8 +47,10 @@ #include "GameClient/Drawable.h" #include "GameClient/ParticleSys.h" #include "GameClient/Color.h" +#include "GameClient/Display.h" #include "GameClient/View.h" #include "W3DDevice/GameClient/HeightMap.h" +#include "W3DDevice/GameClient/W3DDisplay.h" #include "W3DDevice/GameClient/W3DScene.h" #include "W3DDevice/GameClient/W3DDynamicLight.h" #include "W3DDevice/GameClient/W3DShadow.h" @@ -57,15 +60,22 @@ #include "WW3D2/camera.h" #include "WW3D2/dx8renderer.h" #include "WW3D2/sortingrenderer.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/BgfxRenderProfile.h" +#include "WW3D2/indexbuffer.h" +#include "WW3D2/vertexbuffer.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/light.h" #include "WW3D2/matpass.h" #include "WW3D2/shader.h" #include "WW3D2/dx8caps.h" #include "WW3D2/colorspace.h" +#include "WW3D2/renderdebugstats.h" +#include "WW3D2/ww3dcolor.h" #include "WW3D2/shdlib.h" +#include "GgcRuntimeFlags.h" + /////////////////////////////////////////////////////////////////////////////// // DEFINITIONS //////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// @@ -75,6 +85,111 @@ extern void DoTrees(RenderInfoClass & rinfo); extern void DoShadows(RenderInfoClass & rinfo, Bool stencilPass); extern void DoParticles(RenderInfoClass & rinfo); +namespace +{ +struct SceneDiagCounters +{ + Int visibilityTotal = 0; + Int visibilityFrustumVisible = 0; + Int visibilityHiddenRobj = 0; + Int visibilityDrawableHidden = 0; + Int visibilityDrawableShrouded = 0; + Int visibilityDrawableVisible = 0; + Int visibilityTerrain = 0; + Int mainLoopVisible = 0; + Int mainLoopDirectRender = 0; + Int mainLoopDelayed = 0; + Int renderOneCalls = 0; + Int renderOneNoDrawable = 0; + Int renderOneDrawable = 0; + Int renderOneHiddenSkip = 0; + Int renderOneClear = 0; + Int renderOneFogged = 0; + Int renderOneShrouded = 0; +}; + +static SceneDiagCounters g_sceneDiag; + +static Bool SceneDiagEnabled() +{ + static Int enabled = -1; + if (enabled == -1) + { + enabled = GgcFlags::Enabled(GgcFlag_SceneDiag) ? 1 : 0; + } + return enabled != 0; +} + +#if defined(GGC_RENDER_BACKEND_BGFX) +// TheSuperHackers @feature bobtista 17/06/2026 Defined in BgfxBackend. Fills dir3 with the clamped +// toward-sun direction (z > 0) and returns the armed flag. Used here to keep only off-camera +// casters whose shadow actually reaches the camera view. (MeshClass::Render uses the companion +// GGC_GetBgfxSunShadowCullBox so those kept meshes are not re-dropped by the per-mesh cull.) +extern "C" int GGC_GetBgfxSunShadowCullBox(float * center3, float * radius); +#endif + +static FILE *SceneDiagFile() +{ + static FILE *fp = nullptr; + if (fp == nullptr && SceneDiagEnabled()) + { + fp = fopen("ggc_scene_diag.txt", "wt"); + } + return fp; +} + +static void SceneDiagReset() +{ + if (!SceneDiagEnabled()) + { + return; + } + g_sceneDiag = SceneDiagCounters(); +} + +static void SceneDiagWrite(Bool drawTerrainOnly, Int numPotentialOccluders, Int numPotentialOccludees, + Int numNonOccluderOrOccludee, Int translucentObjectsCount) +{ + FILE *fp = SceneDiagFile(); + if (fp == nullptr) + { + return; + } + const Int frame = TheGameLogic ? TheGameLogic->getFrame() : -1; + fprintf(fp, + "frame=%d stencil=%d terrainOnly=%d potOcc=%d potOccee=%d nonOcc=%d trans=%d " + "visTotal=%d visFrustum=%d visHiddenRobj=%d visDrawableHidden=%d visDrawableShrouded=%d visDrawableVisible=%d visTerrain=%d " + "mainVisible=%d mainDirect=%d mainDelayed=%d " + "renderOne=%d renderOneDrawable=%d renderOneNoDrawable=%d renderHiddenSkip=%d renderClear=%d renderFogged=%d renderShrouded=%d\n", + frame, + g_renderBackend && g_renderBackend->Has_Stencil() ? 1 : 0, + drawTerrainOnly, + numPotentialOccluders, + numPotentialOccludees, + numNonOccluderOrOccludee, + translucentObjectsCount, + g_sceneDiag.visibilityTotal, + g_sceneDiag.visibilityFrustumVisible, + g_sceneDiag.visibilityHiddenRobj, + g_sceneDiag.visibilityDrawableHidden, + g_sceneDiag.visibilityDrawableShrouded, + g_sceneDiag.visibilityDrawableVisible, + g_sceneDiag.visibilityTerrain, + g_sceneDiag.mainLoopVisible, + g_sceneDiag.mainLoopDirectRender, + g_sceneDiag.mainLoopDelayed, + g_sceneDiag.renderOneCalls, + g_sceneDiag.renderOneDrawable, + g_sceneDiag.renderOneNoDrawable, + g_sceneDiag.renderOneHiddenSkip, + g_sceneDiag.renderOneClear, + g_sceneDiag.renderOneFogged, + g_sceneDiag.renderOneShrouded); + fflush(fp); +} + +} + // No texturing, no zbuffer reading/writing, primary gradient, no // blending, no fogging - mostly for use in solid-colored opaque objects. #define SC_PLAYER_COLOR ( SHADE_CNST(ShaderClass::PASS_ALWAYS, ShaderClass::DEPTH_WRITE_DISABLE, ShaderClass::COLOR_WRITE_ENABLE, \ @@ -111,6 +226,9 @@ RTS3DScene::RTS3DScene() #else m_shroudMaterialPass = NEW_REF(W3DShroudMaterialPassClass,()); #endif + m_objectShroudMaterialPass = NEW_REF(W3DShroudMaterialPassClass,()); + m_objectShroudMaterialPass->enableTransparentObjectPass(TRUE); + m_objectShroudMaterialPass->Enable_On_Translucent_Meshes(true); m_maskMaterialPass = NEW_REF(W3DMaskMaterialPassClass,()); m_customPassMode = SCENE_PASS_DEFAULT; @@ -216,6 +334,7 @@ RTS3DScene::~RTS3DScene() REF_PTR_RELEASE(m_scratchLight); REF_PTR_RELEASE(m_shroudMaterialPass); + REF_PTR_RELEASE(m_objectShroudMaterialPass); REF_PTR_RELEASE(m_maskMaterialPass); @@ -451,31 +570,62 @@ void RTS3DScene::Visibility_Check(CameraClass * camera) else { - // Loop over all top-level RenderObjects in this scene. If the bounding sphere is not in front - // of all the frustum planes, it is invisible. - for (it.First(); !it.Is_Done(); it.Next()) { + // Loop over all top-level RenderObjects in this scene. If the bounding sphere is not in front + // of all the frustum planes, it is invisible. + for (it.First(); !it.Is_Done(); it.Next()) { - robj = it.Peek_Obj(); + robj = it.Peek_Obj(); + if (SceneDiagEnabled()) + { + g_sceneDiag.visibilityTotal++; + if (robj->Class_ID() == RenderObjClass::CLASSID_TILEMAP) + { + g_sceneDiag.visibilityTerrain++; + } + } - if (robj->Is_Force_Visible()) { - robj->Set_Visible(true); - } else if (robj->Is_Hidden()) { - robj->Set_Visible(false); - } else { + if (robj->Is_Force_Visible()) { + robj->Set_Visible(true); + } else if (robj->Is_Hidden()) { + if (SceneDiagEnabled()) + { + g_sceneDiag.visibilityHiddenRobj++; + } + robj->Set_Visible(false); + } else { - bool isVisible=!camera->Cull_Sphere(robj->Get_Bounding_Sphere()); + bool isVisible=!camera->Cull_Sphere(robj->Get_Bounding_Sphere()); + if (SceneDiagEnabled() && isVisible) + { + g_sceneDiag.visibilityFrustumVisible++; + } - if (isVisible) - { + if (isVisible) + { //need to keep track of occluders and occludees for subsequent code. drawInfo = (DrawableInfo *)robj->Get_User_Data(); if (drawInfo && (draw=drawInfo->m_drawable) != nullptr) { if (draw->isDrawableEffectivelyHidden() || draw->getFullyObscuredByShroud()) { + if (SceneDiagEnabled()) + { + if (draw->isDrawableEffectivelyHidden()) + { + g_sceneDiag.visibilityDrawableHidden++; + } + if (draw->getFullyObscuredByShroud()) + { + g_sceneDiag.visibilityDrawableShrouded++; + } + } isVisible = FALSE; robj->Set_Visible(isVisible); - } + } + else if (SceneDiagEnabled()) + { + g_sceneDiag.visibilityDrawableVisible++; + } //assume normal rendering. drawInfo->m_flags = DrawableInfo::ERF_IS_NORMAL; //clear any rendering flags that may be in effect. @@ -575,6 +725,83 @@ void RTS3DScene::renderSpecificDrawables(RenderInfoClass &rinfo, Int numDrawable } } +// TheSuperHackers @performance bobtista 04/06/2026 Optional sub-pixel mesh-skip. +// With GGC_BGFX_CULL_SUBPIXEL set, world drawables whose projected on-screen size is +// below GGC_BGFX_CULL_MIN_PX (default 2.0) have only their base mesh render submission +// skipped. The object stays in the scene, so shroud/picking/selection/decals/health +// bars are all preserved. Default OFF, shader pipeline only, never culls selected units. +static Bool SubpixelCullEnabled() +{ + static int cached = -1; + if (cached < 0) + { + cached = GgcFlags::Enabled(GgcFlag_BgfxCullSubpixel) ? 1 : 0; + } + return cached != 0; +} + +// Cull objects whose projected radius is under this many pixels; 2 px keeps distant +// infantry visible while dropping draws that cannot produce a stable pixel. +static const Real kSubpixelCullMinPxDefault = 2.0f; + +static Real SubpixelCullMinPx() +{ + static Real cached = -1.0f; + if (cached < 0.0f) + { + const char *env = GgcFlags::StringValue(GgcFlag_BgfxCullMinPx); + cached = (env != nullptr) ? (Real)atof(env) : kSubpixelCullMinPxDefault; + if (cached < 0.0f) + { + cached = 0.0f; + } + } + return cached; +} + +static Int g_subpixelTested = 0; +static Int g_subpixelCulled = 0; + +static Bool ShouldSubpixelCullMesh(RenderInfoClass &rinfo, Drawable *draw, const SphereClass &sph) +{ + if (!SubpixelCullEnabled()) + { + return FALSE; + } + if (draw == nullptr) + { + return FALSE; + } + if (g_renderBackend == nullptr || !g_renderBackend->Has_Shader_Pipeline()) + { + return FALSE; + } + if (draw->isSelected()) + { + return FALSE; + } + const Real screenWidth = (Real)TheDisplay->getWidth(); + if (screenWidth <= 0.0f) + { + return FALSE; + } + const Vector3 toCenter = sph.Center - rinfo.Camera.Get_Position(); + const Real dist = toCenter.Length(); + if (dist <= sph.Radius) + { + return FALSE; + } + const Real ndcRadius = rinfo.Camera.Compute_Projected_Sphere_Radius(dist, sph.Radius); + const Real pixelDiameter = (ndcRadius < 0.0f ? -ndcRadius : ndcRadius) * screenWidth; + ++g_subpixelTested; + const Bool cull = (pixelDiameter < SubpixelCullMinPx()); + if (cull) + { + ++g_subpixelCulled; + } + return cull; +} + //============================================================================ // RTS3DScene::renderOneObject //============================================================================= @@ -582,6 +809,10 @@ void RTS3DScene::renderSpecificDrawables(RenderInfoClass &rinfo, Int numDrawable //============================================================================= void RTS3DScene::renderOneObject(RenderInfoClass &rinfo, RenderObjClass *robj, Int localPlayerIndex) { + if (SceneDiagEnabled()) + { + g_sceneDiag.renderOneCalls++; + } Drawable *draw = nullptr; DrawableInfo *drawInfo = nullptr; Bool drawableHidden=FALSE; @@ -616,6 +847,10 @@ void RTS3DScene::renderOneObject(RenderInfoClass &rinfo, RenderObjClass *robj, I Vector3 ambient = Get_Ambient_Light(); if (draw && (drawableHidden=draw->isDrawableEffectivelyHidden()) != TRUE) { + if (SceneDiagEnabled()) + { + g_sceneDiag.renderOneDrawable++; + } #ifdef NOT_IN_USE const Vector3* drawAmbient = draw->getAmbientLight(); if (drawAmbient) @@ -639,7 +874,7 @@ void RTS3DScene::renderOneObject(RenderInfoClass &rinfo, RenderObjClass *robj, I ss = OBJECTSHROUD_PARTIAL_CLEAR; } } - if (!robj->Peek_Scene()) + if (!robj->Peek_Scene()) return; //this object was removed by the getShroudedStatus() call. } else @@ -655,7 +890,6 @@ void RTS3DScene::renderOneObject(RenderInfoClass &rinfo, RenderObjClass *robj, I ss = OBJECTSHROUD_SHROUDED; //we will assume that drawables without objects are 'particle' like and therefore don't need drawing if fogged/shrouded. } } - if (draw->isKindOf(KINDOF_INFANTRY)) { //ambient = m_infantryAmbient; //has no effect - see comment on m_infantryAmbient @@ -743,13 +977,25 @@ void RTS3DScene::renderOneObject(RenderInfoClass &rinfo, RenderObjClass *robj, I { //either no drawable or it is hidden if (drawableHidden) + { + if (SceneDiagEnabled()) + { + g_sceneDiag.renderOneHiddenSkip++; + } return; //don't bother with anything else + } + if (SceneDiagEnabled()) + { + g_sceneDiag.renderOneNoDrawable++; + } //Render object without a drawable. Must be either some fluff/debug object or a ghostObject. if (ss == OBJECTSHROUD_FOGGED) { //Must be ghost object because we don't fog normal things. Fogged objects always have a predefined //lighting environment applied which emulates the look of fog. + // TheSuperHackers @bugfix bobtista 01/07/2026 Ghost objects are darkened solely by the + // fogged light environment, like retail DX8; a shroud overlay on top double-darkens them. rinfo.light_environment = &m_foggedLightEnv; robj->Render(rinfo); rinfo.light_environment = nullptr; @@ -763,7 +1009,12 @@ void RTS3DScene::renderOneObject(RenderInfoClass &rinfo, RenderObjClass *robj, I } } - if (!drawableHidden) + // TheSuperHackers @performance bobtista 04/06/2026 Cull sub-pixel drawables: skip the per-object + // render entirely (the shroud/fog material passes inside the !cullMeshDraw block below are gated + // off too, not just the base mesh). Only the trailing light_environment reset and material-pass + // pops still run, so a culled object is not drawn this pass - including its shroud overlay. + const Bool cullMeshDraw = ShouldSubpixelCullMesh(rinfo, draw, sph); + if (!drawableHidden && !cullMeshDraw) { //standard scene lights RefRenderObjListIterator it2(&LightList); @@ -787,13 +1038,18 @@ void RTS3DScene::renderOneObject(RenderInfoClass &rinfo, RenderObjClass *robj, I if (!pDyna->isEnabled()) { continue; } + // TheSuperHackers @feature bobtista 15/07/2026 Lights that illuminate through the + // dedicated shadowed point-light path skip the light environment (see W3DDynamicLight). + if (pDyna->getExcludeFromLightEnv()) { + continue; + } SphereClass lSph = pDyna->Get_Bounding_Sphere(); if (pDyna->Get_Type() == LightClass::POINT && !Spheres_Intersect(sph, lSph)) { continue; } lightEnv.Add_Light(*(LightClass*)dynaLightIt.Peek_Obj()); } - } + } lightEnv.Pre_Render_Update(rinfo.Camera.Get_Transform()); rinfo.light_environment = &lightEnv; @@ -804,18 +1060,57 @@ void RTS3DScene::renderOneObject(RenderInfoClass &rinfo, RenderObjClass *robj, I if (!TheGlobalData->m_shroudOn) ss = OBJECTSHROUD_CLEAR; #endif - if (m_customPassMode == SCENE_PASS_DEFAULT) { if (ss <= OBJECTSHROUD_CLEAR) { + const Bool scheduleClearPass = + draw + && draw->isKindOf(KINDOF_STRUCTURE) + && g_renderBackend + && g_renderBackend->Requires_Delayed_Object_Shroud_Pass(); + if (SceneDiagEnabled()) + { + g_sceneDiag.renderOneClear++; + } robj->Render(rinfo); + if (scheduleClearPass) + { + rinfo.Push_Override_Flags(RenderInfoClass::RINFO_OVERRIDE_ADDITIONAL_PASSES_ONLY); + rinfo.Push_Material_Pass(m_objectShroudMaterialPass); + robj->Render(rinfo); + rinfo.Pop_Material_Pass(); + rinfo.Pop_Override_Flags(); + } } else { - rinfo.Push_Material_Pass(m_shroudMaterialPass); - robj->Render(rinfo); - rinfo.Pop_Material_Pass(); + if (SceneDiagEnabled()) + { + if (ss == OBJECTSHROUD_FOGGED) + { + g_sceneDiag.renderOneFogged++; + } + else + { + g_sceneDiag.renderOneShrouded++; + } + } + if (g_renderBackend && g_renderBackend->Requires_Delayed_Object_Shroud_Pass()) + { + robj->Render(rinfo); + rinfo.Push_Override_Flags(RenderInfoClass::RINFO_OVERRIDE_ADDITIONAL_PASSES_ONLY); + rinfo.Push_Material_Pass(m_objectShroudMaterialPass); + robj->Render(rinfo); + rinfo.Pop_Material_Pass(); + rinfo.Pop_Override_Flags(); + } + else + { + rinfo.Push_Material_Pass(m_shroudMaterialPass); + robj->Render(rinfo); + rinfo.Pop_Material_Pass(); + } } } else if (m_maskMaterialPass) @@ -853,13 +1148,16 @@ void RTS3DScene::Flush(RenderInfoClass & rinfo) if (m_customPassMode == SCENE_PASS_DEFAULT && Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE) DoShadows(rinfo, false); //draw all non-stencil shadows (decals) since they fall under other objects. - TheDX8MeshRenderer.Flush(); //draw all non-translucent objects. + { + GGC_RPROFILE(MESH_FLUSH); + TheDX8MeshRenderer.Flush(); //draw all non-translucent objects. + } //draw all non-translucent objects which were separated because they are hidden and need custom rendering. #ifdef USE_NON_STENCIL_OCCLUSION flushOccludedObjects(rinfo); #else - if (DX8Wrapper::Has_Stencil()) + if (g_renderBackend->Has_Stencil()) flushOccludedObjectsIntoStencil(rinfo); #endif @@ -883,9 +1181,15 @@ void RTS3DScene::Flush(RenderInfoClass & rinfo) //don't draw transparent in this mode because they interfere with destination alpha if (m_customPassMode == SCENE_PASS_DEFAULT && Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE) + { + GGC_RPROFILE(PARTICLES); DoParticles(rinfo); //queue up particles for rendering. + } - SortingRendererClass::Flush(); //draw sorted translucent polygons like particles. + { + GGC_RPROFILE(SORT_FLUSH); + SortingRendererClass::Flush(); //draw sorted translucent polygons like particles. + } } TheDX8MeshRenderer.Clear_Pending_Delete_Lists(); } @@ -968,32 +1272,50 @@ void RTS3DScene::updatePlayerColorPasses() #define ZBias 0.0001f +// TheSuperHackers @info bobtista 28/04/2026 Legacy z-bias units (0..16) used +// to push wireframe overlay draws toward the camera so they stay visible +// over the underlying solid pass. Was a raw 7 in the legacy renderer path. +#define WIREFRAME_OVERLAY_ZBIAS 7 + //DECLARE_PERF_TIMER(NonTerrainRender) void RTS3DScene::Render(RenderInfoClass & rinfo) { + GGC_RPROFILE(RENDER_TOTAL); //USE_PERF_TIMER(NonTerrainRender) - DX8Wrapper::Set_Fog(FogEnabled, FogColor, FogStart, FogEnd); + SceneDiagReset(); + 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) { if (m_customPassMode == SCENE_PASS_DEFAULT) - { - //Regular rendering pass with no effects - updatePlayerColorPasses();///@todo: this probably doesn't need to be done each frame. - updateFixedLightEnvironments(rinfo); - Customized_Render(rinfo); - Flush(rinfo); - } + { + //Regular rendering pass with no effects + updatePlayerColorPasses();///@todo: this probably doesn't need to be done each frame. + updateFixedLightEnvironments(rinfo); + Customized_Render(rinfo); + Flush(rinfo); + SceneDiagWrite(m_drawTerrainOnly, m_numPotentialOccluders, m_numPotentialOccludees, + m_numNonOccluderOrOccludee, m_translucentObjectsCount); + if (SubpixelCullEnabled() && g_subpixelTested > 0 + && (TheGameLogic->getFrame() % 30) == 0) + { + // Visible on release builds too (DEBUG_LOG is a no-op there), gated on the + // opt-in env so it never prints for normal users. Mirrors BGFX_PERF. + fprintf(stderr, "BGFX subpixel-cull: %d of %d drawables skipped (<%.1f px)\n", + (int)g_subpixelCulled, (int)g_subpixelTested, SubpixelCullMinPx()); + fflush(stderr); + } + } else if (m_customPassMode == SCENE_PASS_ALPHA_MASK) { //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(RB_COLOR_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); @@ -1002,7 +1324,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(RB_COLOR_RGB); ShaderClass::Invalidate(); } @@ -1016,9 +1338,9 @@ 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 - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,D3DCOLORWRITEENABLE_ALPHA); - DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 0); + g_renderBackend->Clear(true, false, Vector3(0.0f,0.0f,0.0f),1.0f); // Clear color but not z + g_renderBackend->Set_Color_Write_Mask(RB_COLOR_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; @@ -1030,10 +1352,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(RB_COLOR_RGB); 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. @@ -1042,16 +1364,14 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) rinfo.Camera.Set_Zbuffer_Range(nearZ, farZ-ZBias); rinfo.Camera.Apply(); -// DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 4); - Customized_Render(rinfo); //render wireframe where z-test passes + 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(); -// DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 0); - WW3D::Enable_Texturing(old_enable); + WW3D::Enable_Texturing(old_enable); WW3D::Enable_Coloring(0); ShaderClass::Invalidate(); @@ -1061,32 +1381,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(RB_COLOR_RGB); 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(WIREFRAME_OVERLAY_ZBIAS); 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); - DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 7); + g_renderBackend->Set_Fill_Mode(RB_FILL_WIREFRAME); + g_renderBackend->Set_Z_Bias(WIREFRAME_OVERLAY_ZBIAS); 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(); @@ -1102,6 +1422,7 @@ void RTS3DScene::Render(RenderInfoClass & rinfo) //============================================================================= void RTS3DScene::Customized_Render( RenderInfoClass &rinfo ) { + GGC_RPROFILE(TRAVERSAL); #ifdef DIRTY_CONDITION_FLAGS StDrawableDirtyStuffLocker lockDirtyStuff; #endif @@ -1110,6 +1431,10 @@ void RTS3DScene::Customized_Render( RenderInfoClass &rinfo ) m_translucentObjectsCount = 0; //start of new frame so no translucent objects m_occludedObjectsCount = 0; + // TheSuperHackers @performance bobtista 04/06/2026 Per-pass sub-pixel cull counters. + g_subpixelTested = 0; + g_subpixelCulled = 0; + const Int localPlayerIndex = rts::getObservedOrLocalPlayerIndex_Safe(); #define USE_LIGHT_ENV 1 @@ -1164,7 +1489,7 @@ void RTS3DScene::Customized_Render( RenderInfoClass &rinfo ) return; } #ifdef EXTENDED_STATS - if (DX8Wrapper::stats.m_disableObjects) { + if (g_renderDebugStats.m_disableObjects) { return; } #endif @@ -1180,6 +1505,10 @@ void RTS3DScene::Customized_Render( RenderInfoClass &rinfo ) continue; //we already rendered terrain if (robj->Is_Really_Visible()) { + if (SceneDiagEnabled()) + { + g_sceneDiag.mainLoopVisible++; + } DrawableInfo *drawInfo = (DrawableInfo *)robj->Get_User_Data(); Drawable *draw=nullptr; if (drawInfo) @@ -1189,10 +1518,66 @@ void RTS3DScene::Customized_Render( RenderInfoClass &rinfo ) #else if (!(draw && drawInfo->m_flags & (DrawableInfo::ERF_DELAYED_RENDER|DrawableInfo::ERF_POTENTIAL_OCCLUDER|DrawableInfo::ERF_IS_NON_OCCLUDER_OR_OCCLUDEE))) //in this mode we delay almost all objects in order to do correct sorting with stencil. #endif + { + if (SceneDiagEnabled()) + { + g_sceneDiag.mainLoopDirectRender++; + } renderOneObject(rinfo, robj, localPlayerIndex); + } + else if (SceneDiagEnabled()) + { + g_sceneDiag.mainLoopDelayed++; + } } } +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @feature bobtista 18/06/2026 Render off-camera sun-shadow casters here: + // the color view GPU-clips them (no visible pixels) but the shadow-map piggyback still + // fans them into the sun shadow map, so their ground shadows stay in the visible footprint. + if (!ShaderClass::Is_Backface_Culling_Inverted() && + Get_Extra_Pass_Polygon_Mode() == EXTRA_PASS_DISABLE) + { + float shCullC[3] = { 0.0f, 0.0f, 0.0f }; + float shCullR = 0.0f; + if (GGC_GetBgfxSunShadowCullBox(shCullC, &shCullR) != 0 && shCullR > 0.0f) + { + RefRenderObjListIterator castIt(&RenderList); + for (castIt.First(); !castIt.Is_Done(); castIt.Next()) + { + RenderObjClass *crobj = castIt.Peek_Obj(); + if (crobj->Class_ID() == RenderObjClass::CLASSID_TILEMAP) + { + continue; + } + DrawableInfo *cdi = (DrawableInfo *)crobj->Get_User_Data(); + Drawable *cdraw = cdi ? cdi->m_drawable : nullptr; + if (cdraw == nullptr + || cdraw->isDrawableEffectivelyHidden() + || cdraw->getFullyObscuredByShroud()) + { + continue; + } + const SphereClass &cbs = crobj->Get_Bounding_Sphere(); + if (!rinfo.Camera.Cull_Sphere(cbs)) + { + continue; + } + const float sdx = cbs.Center.X - shCullC[0]; + const float sdy = cbs.Center.Y - shCullC[1]; + const float sdz = cbs.Center.Z - shCullC[2]; + const float sreach = shCullR + cbs.Radius; + if ((sdx * sdx + sdy * sdy + sdz * sdz) > sreach * sreach) + { + continue; + } + crobj->Render(rinfo); + } + } + } +#endif + //Tell shadow manager to render shadows at the end of this frame //Don't draw shadows if there is no terrain present. if (TheW3DShadowManager && terrainObject && !ShaderClass::Is_Backface_Culling_Inverted() && @@ -1263,80 +1648,135 @@ 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 Route shader/material state through g_renderBackend. + 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 - - LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8(); - - if (!m_pDev) - return; //need device to render anything. - - //draw polygons like this is very inefficient but for only 2 triangles, it's - //not worth bothering with index/vertex buffers. - m_pDev->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE); + g_renderBackend->Apply_Render_State_Changes(); //force update all render states // Set stencil states - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, TRUE ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZENABLE, TRUE ); + g_renderBackend->Set_Stencil_Enable(true); + g_renderBackend->Set_Depth_Test_Enable(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. - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC, D3DCMP_NEVER ); //fail all access to the frame buffer to improve memory bandwidth + 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. + 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) + if (g_renderBackend->Supports_Color_Write_Mask()) { - DX8Wrapper::_Get_D3D_Device8()->GetRenderState(D3DRS_COLORWRITEENABLE, &oldColorWriteEnable); - DX8Wrapper::Set_DX8_Render_State(D3DRS_COLORWRITEENABLE,0); + oldColorWriteEnable = g_renderBackend->Get_Color_Write_Mask(); + g_renderBackend->Set_Color_Write_Mask(0); } 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)); + if (g_renderBackend->Is_Triangle_Draw_Enabled()) +#if defined(GGC_RENDER_BACKEND_BGFX) + { + // TheSuperHackers @bugfix bobtista 30/04/2026 Explicitly route the player-color stencil wash + // to the effect-overlay view; the backend's bounds+stencil heuristic also caught UI quads. + g_renderBackend->Begin_Effect_Overlay(); + + Matrix4x4 view,proj; + Matrix4x4 identity(true); + + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW,view); + g_renderBackend->Get_Transform(RB_TRANSFORM_PROJECTION,proj); + g_renderBackend->Set_World_Identity(); + g_renderBackend->Set_View_Identity(); + g_renderBackend->Set_Transform(RB_TRANSFORM_PROJECTION,identity); + + const Real displayHalfWidth = (Real)TheDisplay->getWidth() * 0.5f; + const Real displayHalfHeight = (Real)TheDisplay->getHeight() * -0.5f; + + DynamicVBAccessClass vb(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,4); + { + DynamicVBAccessClass::WriteLockClass lock(&vb); + VertexFormatXYZNDUV2 *verts=lock.Get_Formatted_Vertex_Array(); + if (verts != nullptr) + { + for (Int i=0; i<4; i++) + { + verts[i].x=(v[i].p.X / displayHalfWidth) - 1.0f; + verts[i].y=(v[i].p.Y / displayHalfHeight) + 1.0f; + verts[i].z=0.0f; + verts[i].nx=0.0f; + verts[i].ny=0.0f; + verts[i].nz=1.0f; + verts[i].diffuse=v[i].color; + verts[i].u1=0.0f; + verts[i].v1=0.0f; + verts[i].u2=0.0f; + verts[i].v2=0.0f; + } + } + } + + DynamicIBAccessClass ib(BUFFER_TYPE_DYNAMIC,6); + { + DynamicIBAccessClass::WriteLockClass lock(&ib); + unsigned short *indices=lock.Get_Index_Array(); + if (indices != nullptr) + { + indices[0]=0; + indices[1]=1; + indices[2]=2; + indices[3]=2; + indices[4]=1; + indices[5]=3; + } + } + + g_renderBackend->Set_Vertex_Buffer(vb); + g_renderBackend->Set_Index_Buffer(ib,0); + g_renderBackend->Draw_Triangles(0,2,0,4); + g_renderBackend->Set_Transform(RB_TRANSFORM_VIEW,view); + g_renderBackend->Set_Transform(RB_TRANSFORM_PROJECTION,proj); + + g_renderBackend->End_Effect_Overlay(); + } +#else + g_renderBackend->Draw_Screen_Color_Quad(color, xpos, ypos, width, height); +#endif // 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 ); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC, D3DCMP_ALWAYS); + 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); + 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); } @@ -1392,16 +1832,16 @@ void RTS3DScene::flushOccludedObjectsIntoStencil(RenderInfoClass & rinfo) lastPlayerObject[index]++; //increment to next object } - DX8Wrapper::Set_DX8_Render_State(D3DRS_STENCILENABLE, 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_Enable(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 //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. @@ -1422,16 +1862,16 @@ void RTS3DScene::flushOccludedObjectsIntoStencil(RenderInfoClass & rinfo) Object *object=draw->getObject(); Int color=object->getControllingPlayer()->getPlayerColor(); - RGB_To_HSV(hsv,Vector3(((color>>16)&0xff)/255.0f,((color>>8)&0xff)/255.0f,(color &0xff)/255.0f)); - hsv.Z*=TheGlobalData->m_occludedLuminanceScale; - HSV_To_RGB(rgb,hsv); - visiblePlayerColors[numVisiblePlayerColors++]=DX8Wrapper::Convert_Color(rgb,0.5f); - } + RGB_To_HSV(hsv,Vector3(((color>>16)&0xff)/255.0f,((color>>8)&0xff)/255.0f,(color &0xff)/255.0f)); + hsv.Z*=TheGlobalData->m_occludedLuminanceScale; + HSV_To_RGB(rgb,hsv); + visiblePlayerColors[numVisiblePlayerColors++]=WW3DColor::To_ARGB(rgb,0.5f); + } 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 +1884,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 +1905,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); + 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 + 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 +1959,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) @@ -1560,7 +2000,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*/ @@ -1575,18 +2015,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 ); - 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_Enable(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); + 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 +2054,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. } //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) @@ -1665,7 +2105,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()); } //============================================================================= @@ -1715,6 +2155,14 @@ W3DDynamicLight * RTS3DScene::getADynamicLight() { pLight = (W3DDynamicLight*)dynaLightIt.Peek_Obj(); if (!pLight->isEnabled()) { + // TheSuperHackers @bugfix bobtista 17/07/2026 Reset the shadow-related fields on + // recycle. The nuke/beam shadow producers set them but other claimants (police + // coplight, searchlights) only fill color/position, so a recycled ex-shadow light + // stayed excluded from every light environment and competed for a shadow slot. + pLight->setCastsShadows(false); + pLight->setExcludeFromLightEnv(false); + pLight->setShadowBias(0.0f); + pLight->setShadowStrength(0.0f); pLight->setEnabled(true); return(pLight); } @@ -1726,6 +2174,171 @@ W3DDynamicLight * RTS3DScene::getADynamicLight() return(pLight); } +//============================================================================= +// RTS3DScene::getStrongestShadowCastingDynamicLight +//============================================================================= +// TheSuperHackers @feature bobtista 23/06/2026 Returns the brightest enabled CastsShadows +// dynamic light (by current diffuse magnitude), or NULL. Drives the single point-shadow map. +//============================================================================= +W3DDynamicLight * RTS3DScene::getStrongestShadowCastingDynamicLight(const W3DDynamicLight *exclude) +{ + W3DDynamicLight *best = NULL; + Real bestMag = 0.0f; + RefRenderObjListIterator dynaLightIt(&m_dynamicLightList); + for (dynaLightIt.First(); !dynaLightIt.Is_Done(); dynaLightIt.Next()) + { + W3DDynamicLight *light = (W3DDynamicLight *)dynaLightIt.Peek_Obj(); + if (light == NULL || light == exclude || !light->isEnabled() || !light->getCastsShadows()) + { + continue; + } + Vector3 diffuse; + light->Get_Diffuse(&diffuse); + const Real mag = diffuse.X + diffuse.Y + diffuse.Z; + if (mag > bestMag) + { + bestMag = mag; + best = light; + } + } + return best; +} + +#if defined(GGC_RENDER_BACKEND_BGFX) +// TheSuperHackers @feature bobtista 23/06/2026 Engine accessor mirroring the GGC_GetBgfx* family: +// the bgfx backend (Core) calls this to find the strongest shadow-casting dynamic light in the live +// scene so it can render a perspective shadow map from that light's POV. Fills outPosRange with the +// light's world position (xyz) and far-attenuation range (w), and outDiffuseBias with its current +// diffuse magnitude (x) and shadow bias (y). Returns 1 when an active caster light exists, 0 otherwise. +// TheSuperHackers @bugfix bobtista 15/07/2026 The persistent tracking light (particle-cannon +// beam) always owns the primary slot when it is an active caster. Picking purely by brightness +// let a transient flash pulse steal the slot for its 8-frame life, flickering the beam's cast +// shadows every flash. +static W3DDynamicLight * ggcGetPrimaryPointShadowLight(RTS3DScene *scene) +{ + W3DDisplay *display = static_cast(TheDisplay); + if (display != NULL) + { + W3DDynamicLight *tracking = display->getTrackingLight(); + if (tracking != NULL && tracking->isEnabled() && tracking->getCastsShadows()) + { + return tracking; + } + } + return scene->getStrongestShadowCastingDynamicLight(); +} + +extern "C" int GGC_GetBgfxPointShadowLight(float * outPosRange, float * outDiffuseBias, float * outShadowStrength) +{ + RTS3DScene *scene = W3DDisplay::m_3DScene; + if (scene == NULL) + { + return 0; + } + W3DDynamicLight *light = ggcGetPrimaryPointShadowLight(scene); + if (light == NULL) + { + return 0; + } + + const Vector3 pos = light->Get_Position(); + double farStart = 0.0; + double farEnd = 0.0; + light->Get_Far_Attenuation_Range(farStart, farEnd); + Vector3 diffuse; + light->Get_Diffuse(&diffuse); + + if (outPosRange != NULL) + { + outPosRange[0] = pos.X; + outPosRange[1] = pos.Y; + outPosRange[2] = pos.Z; + outPosRange[3] = static_cast(farEnd); + } + // outDiffuseBias: rgb = the light's current diffuse colour (drives the dedicated + // shadowed point-light term in fs_uber), w = shadow depth bias. + if (outDiffuseBias != NULL) + { + outDiffuseBias[0] = diffuse.X; + outDiffuseBias[1] = diffuse.Y; + outDiffuseBias[2] = diffuse.Z; + outDiffuseBias[3] = light->getShadowBias(); + } + // Per-light shadow strength: 0 = glow only (no shadow map), >0 = casts a shadow that darkens + // occluded surfaces by that amount. Lets a nuke shadow and a particle-cannon glow coexist. + if (outShadowStrength != NULL) + { + *outShadowStrength = light->getShadowStrength(); + } + return 1; +} + +// TheSuperHackers @feature bobtista 14/07/2026 Second-strongest shadow-casting dynamic light for +// the second point-shadow slot: transient lightning-flash pulses cast their own brief shadow +// while the beam's primary shadow stays put. Same output layout as GGC_GetBgfxPointShadowLight. +extern "C" int GGC_GetBgfxPointShadowLight2(float * outPosRange, float * outDiffuseBias, float * outShadowStrength) +{ + RTS3DScene *scene = W3DDisplay::m_3DScene; + if (scene == NULL) + { + return 0; + } + W3DDynamicLight *first = ggcGetPrimaryPointShadowLight(scene); + if (first == NULL) + { + return 0; + } + // TheSuperHackers @bugfix bobtista 16/07/2026 Reserve the two shadow slots for the persistent + // beam tracking lights so both cannons cast stable shadows simultaneously (no snap/promotion). + // A transient flash pulse only takes slot 2 when there is no second beam - otherwise a pulse + // brighter than the second beam would displace it for a frame, snapping that shadow's + // direction and reading as glitching. + W3DDisplay *display = static_cast(TheDisplay); + W3DDynamicLight *light = (display != NULL) ? display->getTrackingLight(first) : NULL; + if (light == NULL) + { + light = scene->getStrongestShadowCastingDynamicLight(first); + } + if (light == NULL) + { + return 0; + } + + const Vector3 pos = light->Get_Position(); + double farStart = 0.0; + double farEnd = 0.0; + light->Get_Far_Attenuation_Range(farStart, farEnd); + Vector3 diffuse; + light->Get_Diffuse(&diffuse); + + if (outPosRange != NULL) + { + outPosRange[0] = pos.X; + outPosRange[1] = pos.Y; + outPosRange[2] = pos.Z; + outPosRange[3] = static_cast(farEnd); + } + if (outDiffuseBias != NULL) + { + outDiffuseBias[0] = diffuse.X; + outDiffuseBias[1] = diffuse.Y; + outDiffuseBias[2] = diffuse.Z; + outDiffuseBias[3] = light->getShadowBias(); + } + if (outShadowStrength != NULL) + { + // TheSuperHackers @bugfix bobtista 16/07/2026 Only persistent beam lights darken their + // occluded surfaces (matching the primary slot, so a second cannon's shadows exist from + // ignition instead of popping in on promotion). Transient flash pulses stay adds-only: + // their shadows read as the absence of the brightening, never active darkness. + W3DDisplay *display = static_cast(TheDisplay); + const Bool isBeam = (display != NULL && display->isTrackingLight(light)); + *outShadowStrength = isBeam ? light->getShadowStrength() : 0.0f; + } + return 1; +} +#endif + //============================================================================= // RTS3DScene::removeDynamicLight //============================================================================= @@ -2019,4 +2632,3 @@ void RTS3DScene::Visibility_Check(CameraClass * camera) * */ - diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp index 8af10e2efe2..f71f3b27b65 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DShroud.cpp @@ -30,7 +30,9 @@ #include "Lib/BaseType.h" #include "WW3D2/camera.h" #include "WWLib/simplevec.h" -#include "WW3D2/dx8wrapper.h" +#include "GgcRuntimeFlags.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/surfaceclass.h" #include "Common/MapObject.h" #include "Common/PerfTimer.h" #include "W3DDevice/GameClient/HeightMap.h" @@ -40,8 +42,12 @@ #include "W3DDevice/GameClient/W3DShroud.h" #include "WW3D2/textureloader.h" #include "Common/GlobalData.h" +#include "GameLogic/GameLogic.h" #include "GameLogic/PartitionManager.h" +#include +#include + //----------------------------------------------------------------------------- @@ -67,6 +73,16 @@ #define DEFAULT_TERRAIN_SIZE 1024 //assumed size of largest terrain possible (in vertices) #define DEFAULT_VISIBLE_TERRAIN 96 //assumed size of visible terrain cells. +//----------------------------------------------------------------------------- + +static TextureClass *Create_Writable_Shroud_Texture(unsigned width, unsigned height, WW3DFormat format) +{ + SurfaceClass *surface = NEW_REF(SurfaceClass, (width, height, format)); + TextureClass *texture = MSGNEW("TextureClass") TextureClass(surface, MIP_LEVELS_1); + REF_PTR_RELEASE(surface); + return texture; +} + //----------------------------------------------------------------------------- W3DShroud::W3DShroud() { @@ -80,6 +96,11 @@ 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_dirtyMinX = 0; + m_dirtyMinY = 0; + m_dirtyMaxX = 0; + m_dirtyMaxY = 0; m_cellWidth=DEFAULT_SHROUD_CELL_SIZE; m_cellHeight=DEFAULT_SHROUD_CELL_SIZE; @@ -93,8 +114,7 @@ W3DShroud::~W3DShroud() { ReleaseResources(); - if (m_pSrcTexture) - m_pSrcTexture->Release(); + REF_PTR_RELEASE(m_pSrcTexture); delete [] m_finalFogData; delete [] m_currentFogData; @@ -155,26 +175,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 +224,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; @@ -238,10 +255,10 @@ Bool W3DShroud::ReAcquireResources() // Since we control the video memory copy, we can do partial updates more efficiently. Or do shift blits. #if defined(RTS_DEBUG) if (TheGlobalData && TheGlobalData->m_fogOfWarOn) - m_pDstTexture = MSGNEW("TextureClass") TextureClass(m_dstTextureWidth,m_dstTextureHeight,WW3D_FORMAT_A4R4G4B4,MIP_LEVELS_1, TextureClass::POOL_DEFAULT); + m_pDstTexture = Create_Writable_Shroud_Texture(m_dstTextureWidth,m_dstTextureHeight,WW3D_FORMAT_A4R4G4B4); else #endif - m_pDstTexture = MSGNEW("TextureClass") TextureClass(m_dstTextureWidth,m_dstTextureHeight,WW3D_FORMAT_R5G6B5,MIP_LEVELS_1, TextureClass::POOL_DEFAULT); + m_pDstTexture = Create_Writable_Shroud_Texture(m_dstTextureWidth,m_dstTextureHeight,WW3D_FORMAT_R5G6B5); DEBUG_ASSERTCRASH( m_pDstTexture != nullptr, ("Failed ReAcquire of shroud texture")); @@ -264,7 +281,10 @@ W3DShroudLevel W3DShroud::getShroudLevel(Int x, Int y) { DEBUG_ASSERTCRASH( m_pSrcTexture != nullptr, ("Reading empty shroud")); - if (x < m_numCellsX && y < m_numCellsY) + // TheSuperHackers @bugfix bobtista 12/06/2026 Guard the lower bound and a null source buffer. + // River-water shading converts world positions to shroud cells without the draw-origin offset, + // so map-edge vertices produce negative indices. Out-of-range cells return 0, like the upper bound. + if (m_srcTextureData != nullptr && x >= 0 && y >= 0 && x < m_numCellsX && y < m_numCellsY) { UnsignedShort pixel=*(UnsignedShort *)((Byte *)m_srcTextureData + x*2 + y*m_srcTexturePitch); @@ -288,8 +308,16 @@ void W3DShroud::setShroudLevel(Int x, Int y, W3DShroudLevel level, Bool textureO if (!m_pSrcTexture) return; - if (x < m_numCellsX && y < m_numCellsY) + // TheSuperHackers @bugfix bobtista 12/06/2026 Guard the lower bound here too: a negative cell index + // would WRITE out of bounds (heap corruption) and drive the dirty-rect min/max negative. Matches + // the guard added to getShroudLevel. + if (x >= 0 && y >= 0 && x < m_numCellsX && y < m_numCellsY) { + m_shroudDirty = TRUE; + if (x < m_dirtyMinX) { m_dirtyMinX = x; } + if (y < m_dirtyMinY) { m_dirtyMinY = y; } + if (x >= m_dirtyMaxX) { m_dirtyMaxX = x + 1; } + if (y >= m_dirtyMaxY) { m_dirtyMaxY = y + 1; } if (level < TheGlobalData->m_shroudAlpha) level = TheGlobalData->m_shroudAlpha; @@ -351,7 +379,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; @@ -476,33 +504,72 @@ 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; } } } +// TheSuperHackers @bugfix bobtista 03/07/2026 Convert a shroud level to the +// source texture pixel format, matching the conversion in fillBorderShroudData. +// Used to hand the border shroud color to render backends that mirror the +// destination texture instead of blitting it (fillBorderShroudData only fills +// the DX8 destination surface). +UnsignedShort W3DShroud::computeShroudPixel(W3DShroudLevel level) +{ + if (level < TheGlobalData->m_shroudAlpha) + level = TheGlobalData->m_shroudAlpha; + +#if defined(RTS_DEBUG) + if (TheGlobalData && TheGlobalData->m_fogOfWarOn) + { + Int redVal = TheGlobalData->m_shroudColor.red; + Int greenVal = TheGlobalData->m_shroudColor.green; + Int blueVal = TheGlobalData->m_shroudColor.blue; + Int alphaVal = 255 - level; + + return ((blueVal>>4)&0xf) | (((greenVal>>4)&0xf)<<4) | (((redVal>>4)&0xf)<<8) | (((alphaVal>>4)&0xf)<<12); + } +#endif + + UnsignedInt bluepixel = (UnsignedInt)((Real)level*((Real)(TheGlobalData->m_shroudColor.getAsInt()&0xff)/255.0f)); + UnsignedInt greenpixel = (UnsignedInt)((Real)level*((Real)((TheGlobalData->m_shroudColor.getAsInt()&0xff00)>>8)/255.0f)); + UnsignedInt redpixel = (UnsignedInt)((Real)level*((Real)((TheGlobalData->m_shroudColor.getAsInt()&0xff0000)>>16)/255.0f)); + + if (level == 255) + { //unshrouded pixels should be fully lit + redpixel = 255; + greenpixel = 255; + bluepixel = 255; + } + return ( ((bluepixel&0xf8) >> 3) | ((greenpixel&0xfc)<<3) | ((redpixel&0xf8)<<8)); +} + /**Set the shroud color within the border area of the map*/ void W3DShroud::setBorderShroudLevel(W3DShroudLevel level) { @@ -526,7 +593,9 @@ 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 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) @@ -682,12 +751,6 @@ void W3DShroud::render(CameraClass *cam) m_pDstTexture->Get_Filter().Set_Min_Filter(m_shroudFilter); } - //Update video memory texture with sysmem copy - SurfaceClass* pDestSurface; - { - pDestSurface=m_pDstTexture->Get_Surface_Level(0); - } - RECT srcRect; POINT dstPoint={1,1}; //first row/column is reserved for border. @@ -706,20 +769,140 @@ void W3DShroud::render(CameraClass *cam) //color in order to keep map border in the state we want. m_clearDstTexture=FALSE; +#if !defined(GGC_RENDER_BACKEND_BGFX) + SurfaceClass *pDestSurface=m_pDstTexture->Get_Surface_Level(0); fillBorderShroudData(m_boderShroudLevel, pDestSurface); + REF_PTR_RELEASE (pDestSurface); +#endif } +#if !defined(GGC_RENDER_BACKEND_BGFX) { + SurfaceClass *pDestSurface=m_pDstTexture->Get_Surface_Level(0); //USE_PERF_TIMER(shroudCopy) - DX8Wrapper::_Copy_DX8_Rects( - m_pSrcTexture, - &srcRect, - 1, - pDestSurface->Peek_D3D_Surface(), - &dstPoint); + // 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); } +#endif - REF_PTR_RELEASE (pDestSurface); + // TheSuperHackers @feature bobtista 17/04/2026 Push shroud pixel data to + // the bgfx backend so it can mirror the POOL_DEFAULT destination texture. + // DX8 only needs the surface copy above when the shroud changed. Bgfx cannot + // sample the DX8 destination surface, so keep its mirror synchronized with the + // current render window even when no individual shroud cell changed this frame. + const Bool shouldCaptureForBgfx = g_renderBackend != nullptr && g_renderBackend->Has_Shader_Pipeline(); + if (g_renderBackend != nullptr && m_pSrcTexture != nullptr && m_pDstTexture != nullptr && (m_shroudDirty || shouldCaptureForBgfx)) + { + m_shroudDirty = FALSE; + m_dirtyMinX = m_numCellsX; + m_dirtyMinY = m_numCellsY; + m_dirtyMaxX = 0; + m_dirtyMaxY = 0; + SurfaceClass::SurfaceDescription srcDesc; + m_pSrcTexture->Get_Description(srcDesc); + if (GgcFlags::Enabled(GgcFlag_ShroudDiag)) + { + static int s_shroudDiagCount = 0; + int shroudDiagLimit = 32; + if (const char *limitEnv = GgcFlags::StringValue(GgcFlag_ShroudDiagLimit)) + { + const int parsedLimit = std::atoi(limitEnv); + if (parsedLimit > 0) + { + shroudDiagLimit = parsedLimit; + } + } + if (s_shroudDiagCount < shroudDiagLimit) + { + const unsigned short *pixels = reinterpret_cast(m_srcTextureData); + const unsigned pitchPixels = m_srcTexturePitch / sizeof(unsigned short); + // FNV-1a hash over the visible shroud pixels. + const unsigned kFnv1aBasis = 2166136261u; + const unsigned kFnv1aPrime = 16777619u; + unsigned checksum = kFnv1aBasis; + unsigned minPixel = 0xffff; + unsigned maxPixel = 0; + unsigned blackCount = 0; + unsigned whiteCount = 0; + unsigned darkCount = 0; + unsigned brightCount = 0; + for (Int yy = visStartY; yy < visEndY; ++yy) + { + for (Int xx = visStartX; xx < visEndX; ++xx) + { + const unsigned pixel = pixels[yy * pitchPixels + xx]; + checksum ^= pixel & 0xff; + checksum *= kFnv1aPrime; + checksum ^= (pixel >> 8) & 0xff; + checksum *= kFnv1aPrime; + if (pixel < minPixel) minPixel = pixel; + if (pixel > maxPixel) maxPixel = pixel; + if (pixel == 0x0000) ++blackCount; + if (pixel == 0xffff) ++whiteCount; + const unsigned r = (pixel >> 11) & 0x1f; + const unsigned g = (pixel >> 5) & 0x3f; + const unsigned b = pixel & 0x1f; + const unsigned lum = r * 2 + g + b * 2; + // R5G6B5 luma proxy in [0,157]: below ~15% reads as dark, above ~89% as bright. + if (lum < 24) ++darkCount; + if (lum > 140) ++brightCount; + } + } + if (FILE *diag = std::fopen("ggc_shroud_diag.txt", "a")) + { + std::fprintf(diag, + "upload=%d frame=%u srcRect=(%d,%d)-(%d,%d) dst=%ux%u srcFmt=%d pitch=%u checksum=0x%08x min=0x%04x max=0x%04x black=%u white=%u dark=%u bright=%u origin=(%.2f,%.2f) cell=(%.2f,%.2f)\n", + s_shroudDiagCount, + TheGameLogic != nullptr ? TheGameLogic->getFrame() : 0, + srcRect.left, srcRect.top, srcRect.right, srcRect.bottom, + m_dstTextureWidth, m_dstTextureHeight, static_cast(srcDesc.Format), + m_srcTexturePitch, checksum, minPixel, maxPixel, blackCount, whiteCount, + darkCount, brightCount, m_drawOriginX, m_drawOriginY, + m_cellWidth, m_cellHeight); + std::fclose(diag); + } + ++s_shroudDiagCount; + } + } + // TheSuperHackers @bugfix bobtista 03/07/2026 Pass the border shroud + // pixel so the backend can fill the destination texels outside the + // playable area with it. The terrain beyond the map boundary samples + // that border ring, and the bgfx mirror previously left it white, + // rendering the map edge fully lit instead of fading to black. + g_renderBackend->Capture_Shroud_Texture( + m_pDstTexture, + m_srcTextureData, + m_dstTextureWidth, m_dstTextureHeight, + visEndX - visStartX, visEndY - visStartY, + visStartX, visStartY, + dstPoint.x, dstPoint.y, + m_srcTexturePitch, + srcDesc.Format, + computeShroudPixel(m_boderShroudLevel)); + } } #define FOG_INTERPOLATION_RATE (255.0f/1000.0f) //take one second to go from black to fully lit. @@ -786,8 +969,16 @@ void W3DShroudMaterialPassClass::Install_Materials() const { if (TheTerrainRenderObject->getShroud()) { - W3DShaderManager::setTexture(0,TheTerrainRenderObject->getShroud()->getShroudTexture()); + W3DShaderManager::setTexture(0,TheTerrainRenderObject->getShroud()->getShroudTexture()); W3DShaderManager::setShader(W3DShaderManager::ST_SHROUD_TEXTURE, 0); + if (g_renderBackend) + { + if (m_isTransparentObjectPass) + { + g_renderBackend->Set_Object_Shroud_Alpha_Mask_Texture(m_contextTexture); + } + g_renderBackend->Set_Object_Shroud_Texture_Pass_Active(m_isTransparentObjectPass); + } } } @@ -795,6 +986,11 @@ void W3DShroudMaterialPassClass::Install_Materials() const ///Restore render states that W3D doesn't know about. void W3DShroudMaterialPassClass::UnInstall_Materials() const { + if (g_renderBackend) + { + g_renderBackend->Set_Object_Shroud_Texture_Pass_Active(false); + g_renderBackend->Set_Object_Shroud_Alpha_Mask_Texture(nullptr); + } W3DShaderManager::resetShader(W3DShaderManager::ST_SHROUD_TEXTURE); } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp index 796279e802f..45982de863d 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DStatusCircle.cpp @@ -9,16 +9,16 @@ ** ** 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 +** 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 . +** along with this program. If not, see . */ //////////////////////////////////////////////////////////////////////////////// // // -// (c) 2001-2003 Electronic Arts Inc. // +// (c) 2001-2003 Electronic Arts Inc. // // // //////////////////////////////////////////////////////////////////////////////// @@ -33,7 +33,8 @@ #include #include #include -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/dx8fvf.h" +#include "WW3D2/RenderBackend.h" #include "WW3D2/shader.h" #include "Common/GlobalData.h" #include "Common/MapObject.h" @@ -150,10 +151,10 @@ Int W3DStatusCircle::initData() freeMapResources(); //free old data and ib/vb m_numTriangles = NUM_TRI; - m_indexBuffer=NEW_REF(DX8IndexBufferClass,(m_numTriangles*3)); + m_indexBuffer=NEW_REF(RenderIndexBufferClass,(m_numTriangles*3)); // Fill up the IB - DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); + RenderIndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer); UnsignedShort *ib=lockIdxBuffer.Get_Index_Array(); for (i=0; i<3*m_numTriangles; i+=3) @@ -165,8 +166,14 @@ Int W3DStatusCircle::initData() ib+=3; //skip the 3 indices we just filled } - m_vertexBufferCircle=NEW_REF(DX8VertexBufferClass,(DX8_FVF_XYZDUV1,m_numTriangles*3,DX8VertexBufferClass::USAGE_DEFAULT)); - m_vertexBufferScreen=NEW_REF(DX8VertexBufferClass,(DX8_FVF_XYZDUV1,2*3,DX8VertexBufferClass::USAGE_DEFAULT)); + m_vertexBufferCircle=NEW_REF(RenderVertexBufferClass,( + RENDER_VERTEX_FORMAT_XYZDUV1, + m_numTriangles*3, + Render_Buffer_Usage_Default())); + m_vertexBufferScreen=NEW_REF(RenderVertexBufferClass,( + RENDER_VERTEX_FORMAT_XYZDUV1, + 2*3, + Render_Buffer_Usage_Default())); //go with a preset material for now. m_vertexMaterialClass=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE); @@ -184,17 +191,17 @@ Int W3DStatusCircle::updateCircleVB() { Int i, k; Real shade; - DX8VertexBufferClass *pVB = m_vertexBufferCircle; + RenderVertexBufferClass *pVB = m_vertexBufferCircle; if (m_vertexBufferCircle ) { m_needUpdate = false; - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(pVB); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(pVB); VertexFormatXYZDUV1 *vb = (VertexFormatXYZDUV1*)lockVtxBuffer.Get_Vertex_Array(); const Real theZ = 0.0f; const Real theRadius = 0.02f; const Int theAlpha = 127; - Int diffuse = m_diffuse + (theAlpha<<24); // b g<<8 r<<16 a<<24. + Int diffuse = m_diffuse + (theAlpha<<24); // b g<<8 r<<16 a<<24. Int limit = m_numTriangles; float curAngle = 0; float deltaAngle = 2*PI/limit; @@ -203,7 +210,7 @@ Int W3DStatusCircle::updateCircleVB() shade=0.7f*255.0f; for (k=0; k<3; k++) { - vb->z= theZ; + vb->z= theZ; if (k==0) { vb->x= 0; vb->y= 0; @@ -239,11 +246,11 @@ Int W3DStatusCircle::updateCircleVB() Int W3DStatusCircle::updateScreenVB(Int diffuse) { - DX8VertexBufferClass *pVB = m_vertexBufferScreen; + RenderVertexBufferClass *pVB = m_vertexBufferScreen; if (m_vertexBufferScreen ) { m_needUpdate = false; - DX8VertexBufferClass::WriteLockClass lockVtxBuffer(pVB); + RenderVertexBufferClass::WriteLockClass lockVtxBuffer(pVB); VertexFormatXYZDUV1 *vb = (VertexFormatXYZDUV1*)lockVtxBuffer.Get_Vertex_Array(); vb->x = -1; @@ -316,12 +323,15 @@ void W3DStatusCircle::Render(RenderInfoClass & rinfo) if (m_needUpdate) { updateCircleVB(); } + // TheSuperHackers @refactor bobtista 10/04/2026 Route the fade + // blend-op overrides through the IRenderBackend interface. + //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 +339,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 +350,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 +360,37 @@ 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: - DX8Wrapper::Set_DX8_Render_State(D3DRS_BLENDOP, D3DBLENDOP_REVSUBTRACT ); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); - DX8Wrapper::Set_DX8_Render_State(D3DRS_BLENDOP, D3DBLENDOP_ADD ); + // 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)); + 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); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); + 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); - DX8Wrapper::Draw_Triangles( 0,2, 0, (2*3)); +#if defined(GGC_RENDER_BACKEND_BGFX) + g_renderBackend->Draw_Screen_Multiply_Quad(diffuse, 0, 0, + TheGlobalData->m_xResolution, TheGlobalData->m_yResolution); +#else + g_renderBackend->Set_Blend_Factors(RB_BLEND_ZERO, RB_BLEND_SRC_COLOR); + g_renderBackend->Draw_Triangles( 0,2, 0, (2*3)); +#endif break; } ShaderClass::Invalidate(); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DWebBrowser.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DWebBrowser.cpp index 1b7f2915ad1..2191c116c83 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" @@ -32,8 +36,6 @@ #include "GameClient/Image.h" #include "GameClient/GameWindow.h" #include "WWMath/vector2i.h" -#include -#include "WW3D2/dx8wrapper.h" #include "WW3D2/dx8webbrowser.h" W3DWebBrowser::W3DWebBrowser() : WebBrowser() { @@ -76,3 +78,5 @@ void W3DWebBrowser::closeBrowserWindow(GameWindow *win) { DX8WebBrowser::DestroyBrowser(win->winGetInstanceData()->m_decoratedNameString.str()); } + +#endif // _WIN32 (W3DWebBrowser Win impl) diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3dWaypointBuffer.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3dWaypointBuffer.cpp index 3869cee9856..545a06b1a0c 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3dWaypointBuffer.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3dWaypointBuffer.cpp @@ -76,7 +76,6 @@ #include "W3DDevice/GameClient/HeightMap.h" #include "WW3D2/camera.h" -#include "WW3D2/dx8wrapper.h" #include "WW3D2/dx8renderer.h" #include "WW3D2/mesh.h" #include "WW3D2/meshmdl.h" @@ -532,4 +531,3 @@ void W3DWaypointBuffer::drawWaypoints(RenderInfoClass &rinfo) } } - diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DGhostObject.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DGhostObject.cpp index 7884d96f18b..d9b389922eb 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DGhostObject.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DGhostObject.cpp @@ -126,6 +126,12 @@ void W3DRenderObjectSnapshot::update(RenderObjClass *robj, DrawableInfo *drawInf { REF_PTR_RELEASE(m_robj); + if( robj == nullptr ) + { + m_robj = nullptr; + return; + } + if( cloneParentRobj == TRUE ) { m_robj = robj->Clone(); @@ -157,6 +163,10 @@ void W3DRenderObjectSnapshot::update(RenderObjClass *robj, DrawableInfo *drawInf // ------------------------------------------------------------------------------------------------ Bool W3DRenderObjectSnapshot::addToScene() { + if (!m_robj) + { + return false; + } if (!m_robj->Is_In_Scene()) { W3DDisplay::m_3DScene->Add_Render_Object(m_robj); @@ -169,6 +179,10 @@ Bool W3DRenderObjectSnapshot::addToScene() // ------------------------------------------------------------------------------------------------ Bool W3DRenderObjectSnapshot::removeFromScene() { + if (!m_robj) + { + return false; + } return m_robj->Remove(); } @@ -205,14 +219,19 @@ void W3DRenderObjectSnapshot::xfer( Xfer *xfer ) DEBUG_ASSERTCRASH( m_robj, ("W3DRenderObjectSnapshot::xfer - invalid m_robj") ); // transform on the main render object - Matrix3D transform; - transform = m_robj->Get_Transform(); + // TheSuperHackers @bugfix bobtista 10/07/2026 Initialize to identity so the defensive + // null-m_robj path does not xfer an uninitialized Matrix3D (stack garbage) into the save stream. + Matrix3D transform(true); + if( m_robj ) + { + transform = m_robj->Get_Transform(); + } xfer->xferUser( &transform, sizeof( Matrix3D ) ); - if( xfer->getXferMode() == XFER_LOAD ) + if( m_robj && xfer->getXferMode() == XFER_LOAD ) m_robj->Set_Transform( transform ); // how many sub objects of data will follow - Int subObjectCount = m_robj->Get_Num_Sub_Objects(); + Int subObjectCount = m_robj ? m_robj->Get_Num_Sub_Objects() : 0; xfer->xferInt( &subObjectCount ); Bool visible; @@ -240,7 +259,7 @@ void W3DRenderObjectSnapshot::xfer( Xfer *xfer ) xfer->xferAsciiString( &subObjectName ); // find this sub object on the object - subObject = m_robj->Get_Sub_Object_By_Name( subObjectName.str() ); + subObject = m_robj ? m_robj->Get_Sub_Object_By_Name( subObjectName.str() ) : nullptr; } @@ -281,7 +300,10 @@ void W3DRenderObjectSnapshot::xfer( Xfer *xfer ) } // tell W3D that the transforms for our sub objects are all OK cause we've done them ourselves - m_robj->Set_Sub_Object_Transforms_Dirty( FALSE ); + if( m_robj ) + { + m_robj->Set_Sub_Object_Transforms_Dirty( FALSE ); + } } @@ -781,7 +803,10 @@ void W3DGhostObject::xfer( Xfer *xfer ) // read shroudedness previous and set xfer->xferUser( &status, sizeof( ObjectShroudStatus ) ); - m_partitionData->friend_setShroudednessPrevious( playerIndex, status ); + if( m_partitionData ) + { + m_partitionData->friend_setShroudednessPrevious( playerIndex, status ); + } } } } @@ -1214,10 +1239,13 @@ void W3DGhostObjectManager::xfer( Xfer *xfer ) ("W3DGhostObjectManager::xfer - Could not create ghost object for object '%s'", object->getTemplate()->getName().str()) ); // link the ghost object and logical object together through partition/ghostObject dat - DEBUG_ASSERTCRASH( object->friend_getPartitionData()->getGhostObject() == nullptr, - ("W3DGhostObjectManager::xfer - Ghost object already on object '%s'", object->getTemplate()->getName().str()) ); + if( object->friend_getPartitionData() ) + { + DEBUG_ASSERTCRASH( object->friend_getPartitionData()->getGhostObject() == nullptr, + ("W3DGhostObjectManager::xfer - Ghost object already on object '%s'", object->getTemplate()->getName().str()) ); - object->friend_getPartitionData()->friend_setGhostObject( ghostObject ); + object->friend_getPartitionData()->friend_setGhostObject( ghostObject ); + } } else { @@ -1225,11 +1253,21 @@ void W3DGhostObjectManager::xfer( Xfer *xfer ) ghostObject = addGhostObject( nullptr, nullptr ); // register ghost object object with partition system and fill out partition data - ThePartitionManager->registerGhostObject( ghostObject ); + if( ghostObject ) + { + ThePartitionManager->registerGhostObject( ghostObject ); + } } // read ghost object data - xfer->xferSnapshot( ghostObject ); + if( ghostObject ) + { + xfer->xferSnapshot( ghostObject ); + } + else + { + throw INI_INVALID_DATA; + } } } } 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 diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/CMakeLists.txt b/GeneralsMD/Code/Libraries/Source/WWVegas/CMakeLists.txt index b7d4208261a..f526bdd07d2 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/CMakeLists.txt +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/CMakeLists.txt @@ -3,11 +3,22 @@ add_library(z_wwcommon INTERFACE) target_link_libraries(z_wwcommon INTERFACE core_wwcommon - d3d8lib - milesstub stlport ) +if(NOT GGC_RENDER_BACKEND STREQUAL "bgfx") + target_link_libraries(z_wwcommon INTERFACE + d3d8lib + ) +endif() + +# 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} ) diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index 52e3470e6fd..3dbbc43b269 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -240,11 +240,18 @@ target_sources(z_ww3d2 PRIVATE ${WW3D2_SRC}) target_compile_definitions(z_ww3d2 PRIVATE $<$:WINVER=0x0500> + GGC_ALLOW_DX8WRAPPER ) target_precompile_headers(z_ww3d2 PRIVATE [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 - [["WW3D2/dx8wrapper.h"]] +) + +if(NOT GGC_RENDER_BACKEND STREQUAL "bgfx") + target_precompile_headers(z_ww3d2 PRIVATE [["WW3D2/dx8wrapper.h"]]) +endif() + +target_precompile_headers(z_ww3d2 PRIVATE [["WWLib/always.h"]] [["WWLib/STLUtils.h"]] [["WWLib/win.h"]] diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp index 9ce8fb48662..bc63b26617c 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/assetmgr.cpp @@ -78,6 +78,7 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "assetmgr.h" +#include "GgcRuntimeFlags.h" #include #include "WWLib/bittype.h" @@ -106,13 +107,11 @@ #include "WWLib/wwstring.h" #include "WWDebug/wwmemlog.h" #include "dazzle.h" -#include "WW3D2/dx8wrapper.h" #include "WW3D2/dx8renderer.h" #include "WW3D2/metalmap.h" #include "WW3D2/w3dexclusionlist.h" #include #include -#include #include "WWDebug/wwprofile.h" #include "WW3D2/assetstatus.h" #include "WW3D2/ringobj.h" @@ -120,6 +119,48 @@ #include "WW3D2/shdlib.h" +#include +#include +#include + +namespace +{ +static bool WW3DLoadDiagEnabled() +{ + static int enabled = -1; + if (enabled == -1) + { + enabled = GgcFlags::Enabled(GgcFlag_Ww3dLoadDiag) ? 1 : 0; + } + return enabled != 0; +} + +static FILE *WW3DLoadDiagFile() +{ + static FILE *fp = nullptr; + if (fp == nullptr && WW3DLoadDiagEnabled()) + { + fp = fopen("ggc_ww3d_load_diag.txt", "wt"); + } + return fp; +} + +static void WW3DLoadDiagLog(const char *fmt, ...) +{ + FILE *fp = WW3DLoadDiagFile(); + if (fp == nullptr) + { + return; + } + + va_list args; + va_start(args, fmt); + vfprintf(fp, fmt, args); + va_end(args); + fflush(fp); +} +} + /* ** Static member variable which keeps track of the single instanced asset manager */ @@ -266,7 +307,7 @@ WW3DAssetManager::~WW3DAssetManager() static void Create_Number_String(StringClass& number, unsigned value) { - unsigned miljoonat=value/(1024*1028); + unsigned miljoonat=value/(1024*1024); unsigned tuhannet=(value/1024)%1024; unsigned ykkoset=value%1024; if (miljoonat) { @@ -305,51 +346,8 @@ static void Log_Textures(bool inited,unsigned& total_count, unsigned& total_mem) TextureClass * tex=ite.Peek_Value(); if (tex->Is_Initialized()!=inited) continue; - D3DSURFACE_DESC desc; - IDirect3DTexture8* d3d_texture=tex->Peek_D3D_Texture(); - if (!d3d_texture) continue; - DX8_ErrorCode(d3d_texture->GetLevelDesc(0,&desc)); - - StringClass tex_format="Unknown"; - switch (desc.Format) { - case D3DFMT_A8R8G8B8: tex_format="D3DFMT_A8R8G8B8"; break; - case D3DFMT_R8G8B8: tex_format="D3DFMT_R8G8B8"; break; - case D3DFMT_A4R4G4B4: tex_format="D3DFMT_A4R4G4B4"; break; - case D3DFMT_A1R5G5B5: tex_format="D3DFMT_A1R5G5B5"; break; - case D3DFMT_R5G6B5: tex_format="D3DFMT_R5G6B5"; break; - case D3DFMT_L8: tex_format="D3DFMT_L8"; break; - case D3DFMT_A8: tex_format="D3DFMT_A8"; break; - case D3DFMT_P8: tex_format="D3DFMT_P8"; break; - case D3DFMT_X8R8G8B8: tex_format="D3DFMT_X8R8G8B8"; break; - case D3DFMT_X1R5G5B5: tex_format="D3DFMT_X1R5G5B5"; break; - case D3DFMT_R3G3B2: tex_format="D3DFMT_R3G3B2"; break; - case D3DFMT_A8R3G3B2: tex_format="D3DFMT_A8R3G3B2"; break; - case D3DFMT_X4R4G4B4: tex_format="D3DFMT_X4R4G4B4"; break; - case D3DFMT_A8P8: tex_format="D3DFMT_A8P8"; break; - case D3DFMT_A8L8: tex_format="D3DFMT_A8L8"; break; - case D3DFMT_A4L4: tex_format="D3DFMT_A4L4"; break; - case D3DFMT_V8U8: tex_format="D3DFMT_V8U8"; break; - case D3DFMT_L6V5U5: tex_format="D3DFMT_L6V5U5"; break; - case D3DFMT_X8L8V8U8: tex_format="D3DFMT_X8L8V8U8"; break; - case D3DFMT_Q8W8V8U8: tex_format="D3DFMT_Q8W8V8U8"; break; - case D3DFMT_V16U16: tex_format="D3DFMT_V16U16"; break; - case D3DFMT_W11V11U10: tex_format="D3DFMT_W11V11U10"; break; - case D3DFMT_UYVY: tex_format="D3DFMT_UYVY"; break; - case D3DFMT_YUY2: tex_format="D3DFMT_YUY2"; break; - case D3DFMT_DXT1: tex_format="D3DFMT_DXT1"; break; - case D3DFMT_DXT2: tex_format="D3DFMT_DXT2"; break; - case D3DFMT_DXT3: tex_format="D3DFMT_DXT3"; break; - case D3DFMT_DXT4: tex_format="D3DFMT_DXT4"; break; - case D3DFMT_DXT5: tex_format="D3DFMT_DXT5"; break; - case D3DFMT_D16_LOCKABLE: tex_format="D3DFMT_D16_LOCKABLE"; break; - case D3DFMT_D32: tex_format="D3DFMT_D32"; break; - case D3DFMT_D15S1: tex_format="D3DFMT_D15S1"; break; - case D3DFMT_D24S8: tex_format="D3DFMT_D24S8"; break; - case D3DFMT_D16: tex_format="D3DFMT_D16"; break; - case D3DFMT_D24X8: tex_format="D3DFMT_D24X8"; break; - case D3DFMT_D24X4S4: tex_format="D3DFMT_D24X4S4"; break; - default: break; - } + StringClass tex_format; + Get_WW3D_Format_Name(tex->Get_Texture_Format(), tex_format); unsigned texmem=tex->Get_Texture_Memory_Usage(); total_mem+=texmem; @@ -359,8 +357,8 @@ static void Log_Textures(bool inited,unsigned& total_count, unsigned& total_mem) WWDEBUG_SAY(("%32s %4d * %4d (%15s), init %d, size: %14s bytes, refs: %d", tex->Get_Texture_Name().str(), - desc.Width, - desc.Height, + tex->Get_Width(), + tex->Get_Height(), tex_format.str(), tex->Is_Initialized(), number.str(), @@ -627,8 +625,11 @@ bool WW3DAssetManager::Load_3D_Assets( const char * filename ) result = WW3DAssetManager::Load_3D_Assets( *file ); } else { WWDEBUG_SAY(("Missing asset '%s'.", filename)); + WW3DLoadDiagLog("file-unavailable name=%s\n", filename); } _TheFileFactory->Return_File( file ); + } else { + WW3DLoadDiagLog("file-get-fail name=%s\n", filename); } return result; @@ -650,13 +651,26 @@ bool WW3DAssetManager::Load_3D_Assets( const char * filename ) bool WW3DAssetManager::Load_3D_Assets(FileClass & w3dfile) { WWPROFILE( "WW3DAssetManager::Load_3D_Assets" ); + WW3DLoadDiagLog("file-load-begin name=%s available=%d\n", w3dfile.File_Name(), w3dfile.Is_Available() ? 1 : 0); if (!w3dfile.Open()) { + WW3DLoadDiagLog("file-open-fail name=%s\n", w3dfile.File_Name()); return false; } + WW3DLoadDiagLog("file-open-ok name=%s size=%d\n", w3dfile.File_Name(), w3dfile.Size()); ChunkLoadClass cload(&w3dfile); + int chunk_count = 0; while (cload.Open_Chunk()) { + ++chunk_count; + if (chunk_count <= 80) + { + WW3DLoadDiagLog("chunk name=%s index=%d id=0x%08x len=%u\n", + w3dfile.File_Name(), + chunk_count, + cload.Cur_Chunk_ID(), + cload.Cur_Chunk_Length()); + } switch (cload.Cur_Chunk_ID()) { @@ -671,7 +685,17 @@ bool WW3DAssetManager::Load_3D_Assets(FileClass & w3dfile) break; default: - Load_Prototype(cload); + { + bool protoLoaded = Load_Prototype(cload); + if (chunk_count <= 80) + { + WW3DLoadDiagLog("prototype-load name=%s index=%d id=0x%08x result=%d\n", + w3dfile.File_Name(), + chunk_count, + cload.Cur_Chunk_ID(), + protoLoaded ? 1 : 0); + } + } break; } @@ -679,6 +703,7 @@ bool WW3DAssetManager::Load_3D_Assets(FileClass & w3dfile) } w3dfile.Close(); + WW3DLoadDiagLog("file-load-end name=%s chunks=%d\n", w3dfile.File_Name(), chunk_count); return true; } @@ -799,7 +824,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); @@ -822,6 +848,7 @@ RenderObjClass * WW3DAssetManager::Create_Render_Obj(const char * name) if (++warning_count <= 20) { WWDEBUG_SAY(("WARNING: Failed to create Render Object: %s",name)); } + WW3DLoadDiagLog("render-obj-fail name=%s\n", name); AssetStatusClass::Peek_Instance()->Report_Missing_RObj(name); } return nullptr; // Failed to find a prototype @@ -1724,5 +1751,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/boxrobj.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/boxrobj.cpp index 2f5dc5f5e9d..827588cc7cc 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/boxrobj.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/boxrobj.cpp @@ -97,7 +97,9 @@ #include "rinfo.h" #include "WW3D2/coltest.h" #include "WW3D2/inttest.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/ww3dcolor.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "WW3D2/dx8indexbuffer.h" #include "WW3D2/dx8vertexbuffer.h" #include "WW3D2/dx8fvf.h" @@ -458,9 +460,9 @@ void BoxRenderObjClass::render_box(RenderInfoClass & rinfo,const Vector3 & cente /* ** Dump the box vertices into the sorting dynamic vertex buffer. */ - DWORD color = DX8Wrapper::Convert_Color(Color,Opacity); + DWORD color = WW3DColor::To_ARGB(Color, Opacity); - int buffer_type = BUFFER_TYPE_DYNAMIC_DX8; + int buffer_type = BUFFER_TYPE_DYNAMIC; DynamicVBAccessClass vbaccess(buffer_type,dynamic_fvf_type,NUM_BOX_VERTS); { @@ -504,14 +506,14 @@ 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); - 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); } } @@ -701,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); } @@ -1085,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); } @@ -1384,5 +1386,3 @@ RenderObjClass * BoxPrototypeClass::Create() ** Global instance of the box loader */ BoxLoaderClass _BoxLoader; - - diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp index a42e7e4fe43..5b1f513275a 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/camera.cpp @@ -74,7 +74,13 @@ #include "camera.h" #include "ww3d.h" #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 "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" /*********************************************************************************************** @@ -738,19 +744,19 @@ void CameraClass::Apply() bool windowed; WW3D::Get_Render_Target_Resolution(width,height,bits,windowed); - D3DVIEWPORT8 vp; - vp.X = (DWORD)(Viewport.Min.X * (float)width); - vp.Y = (DWORD)(Viewport.Min.Y * (float)height); - vp.Width = (DWORD)((Viewport.Max.X - Viewport.Min.X) * (float)width); - vp.Height = (DWORD)((Viewport.Max.Y - Viewport.Min.Y) * (float)height); - vp.MinZ = ZBufferMin; - vp.MaxZ = ZBufferMax; - DX8Wrapper::Set_Viewport(&vp); + RenderBackendViewport vp; + vp.x = static_cast(Viewport.Min.X * static_cast(width)); + vp.y = static_cast(Viewport.Min.Y * static_cast(height)); + vp.width = static_cast((Viewport.Max.X - Viewport.Min.X) * static_cast(width)); + vp.height = static_cast((Viewport.Max.Y - Viewport.Min.Y) * static_cast(height)); + vp.min_z = ZBufferMin; + vp.max_z = ZBufferMax; + g_renderBackend->Set_Viewport(vp); 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) @@ -769,13 +775,13 @@ void CameraClass::Get_Clip_Planes(float & znear,float & zfar) const float CameraClass::Get_Horizontal_FOV() const { float width = ViewPlane.Max.X - ViewPlane.Min.X; - return 2*WWMath::Atan2(width,2.0); + return 2*WWMath::Atan2_Legacy(width,2.0); } float CameraClass::Get_Vertical_FOV() const { float height = ViewPlane.Max.Y - ViewPlane.Min.Y; - return 2*WWMath::Atan2(height,2.0); + return 2*WWMath::Atan2_Legacy(height,2.0); } float CameraClass::Get_Aspect_Ratio() const diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp index 27fc3cd0230..3f2079edcfc 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dazzle.cpp @@ -57,7 +57,9 @@ #include "WWLib/inisup.h" #include "WWSaveLoad/persistfactory.h" #include "WW3D2/ww3dids.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/ww3dcolor.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "WW3D2/dx8vertexbuffer.h" #include "WW3D2/dx8indexbuffer.h" #include "WW3D2/sortingrenderer.h" @@ -389,7 +391,7 @@ void LensflareTypeClass::Generate_Vertex_Buffers( if (col[0]>1.0f) col[0]=1.0f; if (col[1]>1.0f) col[1]=1.0f; if (col[2]>1.0f) col[2]=1.0f; - unsigned color=DX8Wrapper::Convert_Color(col,1.0f); + unsigned color=WW3DColor::To_ARGB(col,1.0f); vertex->x=x+ix; vertex->y=y-iy; @@ -919,7 +921,7 @@ void DazzleRenderObjClass::Render(RenderInfoClass & rinfo) if ( Is_Not_Hidden_At_All() && _dazzle_rendering_enabled && - !DX8Wrapper::Is_Render_To_Texture() ) + (!g_renderBackend || !g_renderBackend->Is_Render_To_Texture()) ) { // First check if the dazzle is blinking and is "off" bool is_on = true; @@ -944,8 +946,8 @@ void DazzleRenderObjClass::Render(RenderInfoClass & rinfo) // visibility = _VisibilityHandler->Compute_Dazzle_Visibility(rinfo,this,position); Matrix4x4 view_transform,projection_transform; - DX8Wrapper::Get_Transform(D3DTS_VIEW,view_transform); - DX8Wrapper::Get_Transform(D3DTS_PROJECTION,projection_transform); + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, view_transform); + g_renderBackend->Get_Transform(RB_TRANSFORM_PROJECTION, projection_transform); Vector3 camera_loc(rinfo.Camera.Get_Position()); Vector3 camera_dir(-view_transform[2][0],-view_transform[2][1],-view_transform[2][2]); // Matrix3D cam(rinfo.Camera.Get_Transform()); @@ -1021,9 +1023,9 @@ void DazzleRenderObjClass::Render_Dazzle(CameraClass* camera) Matrix4x4 view_transform; Matrix4x4 world_transform; Matrix4x4 projection_transform; - DX8Wrapper::Get_Transform(D3DTS_VIEW,view_transform); - DX8Wrapper::Get_Transform(D3DTS_WORLD,world_transform); - DX8Wrapper::Get_Transform(D3DTS_PROJECTION,projection_transform); + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, view_transform); + g_renderBackend->Get_Transform(RB_TRANSFORM_WORLD, world_transform); + g_renderBackend->Get_Transform(RB_TRANSFORM_PROJECTION, projection_transform); old_view_transform=view_transform; old_world_transform=world_transform; old_projection_transform=projection_transform; @@ -1070,7 +1072,7 @@ void DazzleRenderObjClass::Render_Dazzle(CameraClass* camera) lens_max_verts=4*lensflare->lic.flare_count; } - DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,vertex_count*2+lens_max_verts); + DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,vertex_count*2+lens_max_verts); { DynamicVBAccessClass::WriteLockClass lock(&vb_access); VertexFormatXYZNDUV2* verts=lock.Get_Formatted_Vertex_Array(); @@ -1099,7 +1101,7 @@ void DazzleRenderObjClass::Render_Dazzle(CameraClass* camera) if (col[1]>1.0f) col[1]=1.0f; if (col[2]>1.0f) col[2]=1.0f; - unsigned color=DX8Wrapper::Convert_Color(col,1.0f); + unsigned color=WW3DColor::To_ARGB(col,1.0f); dl=current_vloc+(dazzle_dxt-dazzle_dyt)*current_dazzle_size; reinterpret_cast(vertex->x)=dl; @@ -1142,7 +1144,7 @@ void DazzleRenderObjClass::Render_Dazzle(CameraClass* camera) if (col[1]>1.0f) col[1]=1.0f; if (col[2]>1.0f) col[2]=1.0f; - unsigned color=DX8Wrapper::Convert_Color(col,1.0f); + unsigned color=WW3DColor::To_ARGB(col,1.0f); Vector3 offset; @@ -1201,9 +1203,9 @@ 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); + DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC,poly_count*3); { DynamicIBAccessClass::WriteLockClass lock(&ib_access); unsigned short* inds=lock.Get_Index_Array(); @@ -1219,34 +1221,41 @@ 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)); + + // 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) { - DX8Wrapper::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->Set_Index_Buffer(ib_access, dazzle_vertex_count); + 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) { - DX8Wrapper::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->Set_Index_Buffer(ib_access, 0); + 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) { - DX8Wrapper::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->Set_Index_Buffer(ib_access, dazzle_vertex_count+halo_vertex_count); + 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->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); } // ---------------------------------------------------------------------------- @@ -1576,7 +1585,7 @@ void DazzleLayerClass::Render(CameraClass* camera) camera->Apply(); - DX8Wrapper::Set_Material(nullptr); + g_renderBackend->Set_Material(nullptr); for (unsigned type=0;type + +#ifndef DDSCAPS2_CUBEMAP +#define DDSCAPS2_CUBEMAP 0x00000200L +#endif + +#ifndef DDSCAPS2_VOLUME +#define DDSCAPS2_VOLUME 0x00200000L +#endif + +// ---------------------------------------------------------------------------- + +static constexpr unsigned Make_DDS_FourCC(char a, char b, char c, char d) +{ + return static_cast(a) | + (static_cast(b) << 8) | + (static_cast(c) << 16) | + (static_cast(d) << 24); +} + +static WW3DFormat DDS_FourCC_To_WW3D_Format(unsigned fourcc) +{ + switch (fourcc) { + case Make_DDS_FourCC('D', 'X', 'T', '1'): + return WW3D_FORMAT_DXT1; + case Make_DDS_FourCC('D', 'X', 'T', '2'): + return WW3D_FORMAT_DXT2; + case Make_DDS_FourCC('D', 'X', 'T', '3'): + return WW3D_FORMAT_DXT3; + case Make_DDS_FourCC('D', 'X', 'T', '4'): + return WW3D_FORMAT_DXT4; + case Make_DDS_FourCC('D', 'X', 'T', '5'): + return WW3D_FORMAT_DXT5; + default: + return WW3D_FORMAT_UNKNOWN; + } +} // ---------------------------------------------------------------------------- @@ -87,7 +120,7 @@ DDSFileClass::DDSFileClass(const char* name,unsigned reduction_factor) return; } - Format=D3DFormat_To_WW3DFormat((D3DFORMAT)SurfaceDesc.PixelFormat.FourCC); + Format=DDS_FourCC_To_WW3D_Format(SurfaceDesc.PixelFormat.FourCC); WWASSERT( Format==WW3D_FORMAT_DXT1 || Format==WW3D_FORMAT_DXT2 || @@ -327,37 +360,6 @@ WWINLINE static unsigned short ARGB8888_To_RGB565(unsigned argb_) } -// ---------------------------------------------------------------------------- -// -// Copy mipmap level to D3D surface. The copying is performed using another -// Copy_Level_To_Surface function (see below). -// -// ---------------------------------------------------------------------------- - -void DDSFileClass::Copy_Level_To_Surface(unsigned level,IDirect3DSurface8* d3d_surface,const Vector3& hsv_shift) -{ - WWASSERT(d3d_surface); - // Verify that the destination surface size matches the source surface size - D3DSURFACE_DESC surface_desc; - DX8_ErrorCode(d3d_surface->GetDesc(&surface_desc)); - - // First lock the surface - D3DLOCKED_RECT locked_rect; - DX8_ErrorCode(d3d_surface->LockRect(&locked_rect,nullptr,0)); - - Copy_Level_To_Surface( - level, - D3DFormat_To_WW3DFormat(surface_desc.Format), - surface_desc.Width, - surface_desc.Height, - reinterpret_cast(locked_rect.pBits), - locked_rect.Pitch, - hsv_shift); - - // Finally, unlock the surface - DX8_ErrorCode(d3d_surface->UnlockRect()); -} - // ---------------------------------------------------------------------------- // // Copy one mipmap level of texture to a memory surface. Surface type conversion diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ddsfile.h b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ddsfile.h index 72da3fec4bb..48720e8e873 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ddsfile.h +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ddsfile.h @@ -25,9 +25,6 @@ #include "WWLib/wwstring.h" #include "WWMath/vector3.h" -struct IDirect3DSurface8; -struct IDirect3DVolume8; - // ---------------------------------------------------------------------------- // // This structure represents the old DX7 color key structure. It is needed @@ -138,7 +135,7 @@ struct LegacyDDSURFACEDESC2 { }; unsigned AlphaBitDepth; unsigned Reserved; - void* Surface; + unsigned Surface; union { LegacyDDCOLORKEY CKDestOverlay; @@ -152,6 +149,8 @@ struct LegacyDDSURFACEDESC2 { unsigned TextureStage; }; +static_assert(sizeof(LegacyDDSURFACEDESC2) == 124, "DDS surface descriptor must match on-disk size."); + enum DDSType { @@ -215,7 +214,6 @@ class DDSFileClass DDSType Get_Type() const { return Type; } // Copy pixels to the destination surface. - void Copy_Level_To_Surface(unsigned level,IDirect3DSurface8* d3d_surface,const Vector3& hsv_shift=Vector3(0.0f,0.0f,0.0f)); void Copy_Level_To_Surface( unsigned level, WW3DFormat dest_format, diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/decalmsh.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/decalmsh.cpp index 4448ba801db..c5c571ac7c0 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/decalmsh.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/decalmsh.cpp @@ -62,8 +62,8 @@ #include "WW3D2/dx8indexbuffer.h" #include "WWLib/simplevec.h" #include "WW3D2/texture.h" -#include "WW3D2/dx8wrapper.h" -#include "WW3D2/dx8caps.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #define DISABLE_CLIPPING 0 @@ -298,12 +298,12 @@ void RigidDecalMeshClass::Render() ** transform between the time that the mesh is rendered and the time that the decal ** mesh is rendered... It shouldn't happen though. */ - DX8Wrapper::Set_Transform(D3DTS_WORLD,Parent->Get_Transform()); + g_renderBackend->Set_Transform(RB_TRANSFORM_WORLD,Parent->Get_Transform()); /* ** Copy the vertices into the dynamic vb */ - DynamicVBAccessClass dynamic_vb(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,Verts.Count()); + DynamicVBAccessClass dynamic_vb(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,Verts.Count()); { DynamicVBAccessClass::WriteLockClass lock(&dynamic_vb); VertexFormatXYZNDUV2 * vertex = lock.Get_Formatted_Vertex_Array(); @@ -333,7 +333,7 @@ void RigidDecalMeshClass::Render() /* ** Copy the indices into the dynamic ib */ - DynamicIBAccessClass dynamic_ib(BUFFER_TYPE_DYNAMIC_DX8,Polys.Count() * 3); + DynamicIBAccessClass dynamic_ib(BUFFER_TYPE_DYNAMIC,Polys.Count() * 3); { DynamicIBAccessClass::WriteLockClass lock(&dynamic_ib); unsigned short * indices = lock.Get_Index_Array(); @@ -354,9 +354,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); @@ -383,9 +383,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()) && @@ -424,7 +424,7 @@ bool RigidDecalMeshClass::Create_Decal // on hardware "polygon offset" we could remove this code and we could make decals non-sorting Vector3 zbias_offset(0.0f,0.0f,0.0f); - if (!DX8Wrapper::Get_Current_Caps()->Support_ZBias()) { + if (!g_renderBackend->Supports_Z_Bias()) { const float ZBIAS_DISTANCE = 0.01f; generator->Get_Transform().Get_Z_Vector(&zbias_offset); Matrix3D invtm; @@ -656,10 +656,10 @@ bool RigidDecalMeshClass::Delete_Decal(uint32 id) /* ** Remove all materials used by this decal (remember to release refs!) */ - for (int fi=decal->FaceStartIndex; fiFaceCount; fi++) { + for (int fi=decal->FaceStartIndex; fiFaceStartIndex+decal->FaceCount; fi++) { REF_PTR_RELEASE(Textures[fi]); } - for (int vi=decal->VertexStartIndex; viVertexCount; vi++) { + for (int vi=decal->VertexStartIndex; viVertexStartIndex+decal->VertexCount; vi++) { REF_PTR_RELEASE(VertexMaterials[vi]); } Shaders.Delete_Range(decal->FaceStartIndex,decal->FaceCount); @@ -788,7 +788,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 @@ -801,7 +801,7 @@ void SkinDecalMeshClass::Render() /* ** Copy the vertices into the dynamic vb */ - DynamicVBAccessClass dynamic_vb(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,ParentVertexIndices.Count()); + DynamicVBAccessClass dynamic_vb(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,ParentVertexIndices.Count()); { DynamicVBAccessClass::WriteLockClass lock(&dynamic_vb); VertexFormatXYZNDUV2 * vertex = lock.Get_Formatted_Vertex_Array(); @@ -831,7 +831,7 @@ void SkinDecalMeshClass::Render() /* ** Copy the indices into the dynamic ib */ - DynamicIBAccessClass dynamic_ib(BUFFER_TYPE_DYNAMIC_DX8,Polys.Count() * 3); + DynamicIBAccessClass dynamic_ib(BUFFER_TYPE_DYNAMIC,Polys.Count() * 3); { DynamicIBAccessClass::WriteLockClass lock(&dynamic_ib); unsigned short * indices = lock.Get_Index_Array(); @@ -852,9 +852,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); @@ -881,9 +881,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()) && @@ -1072,10 +1072,10 @@ bool SkinDecalMeshClass::Delete_Decal(uint32 id) /* ** Remove all materials used by this decal (remember to release refs!) */ - for (int fi = decal->FaceStartIndex; fi < decal->FaceCount; fi++) { + for (int fi = decal->FaceStartIndex; fi < decal->FaceStartIndex + decal->FaceCount; fi++) { REF_PTR_RELEASE(Textures[fi]); } - for (int vi=decal->VertexStartIndex; viVertexCount; vi++) { + for (int vi=decal->VertexStartIndex; viVertexStartIndex+decal->VertexCount; vi++) { REF_PTR_RELEASE(VertexMaterials[vi]); } Shaders.Delete_Range(decal->FaceStartIndex,decal->FaceCount); diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp index 499285d8f13..e183f2babbb 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp @@ -51,6 +51,8 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "hrawanim.h" +#include +#include "GgcRuntimeFlags.h" #include "motchan.h" #include "WWLib/chunkio.h" #include "assetmgr.h" @@ -494,7 +496,20 @@ void HRawAnimClass::Get_Translation(Vector3& trans, int pividx, float frame ) co motion->Z->Get_Vector((int)frame1,&(trans1[2])); } - Vector3::Lerp( trans0, trans1, ratio, &trans ); + // TheSuperHackers @bugfix bobtista 25/05/2026 Snap to the floor-frame value when a raw-anim + // translation channel steps more than 1 unit per integer frame: state-flip channels (e.g. + // blinking-light pivots) must not lerp. GGC_DISABLE_RAW_ANIM_STEP_SNAP opts out. + static const bool s_stepSnapDisabled = GgcFlags::Enabled(GgcFlag_DisableRawAnimStepSnap); + const float kTranslationStepThreshold = 1.0f; + for (int axis = 0; axis < 3; ++axis) { + const float delta = trans1[axis] - trans0[axis]; + if (!s_stepSnapDisabled && (delta > kTranslationStepThreshold || delta < -kTranslationStepThreshold)) { + trans[axis] = trans0[axis]; + } + else { + trans[axis] = trans0[axis] + ratio * delta; + } + } } /*********************************************************************************************** diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/lightenvironment.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/lightenvironment.cpp index b95d49611e9..170640b30eb 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/lightenvironment.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/lightenvironment.cpp @@ -107,7 +107,7 @@ void LightEnvironmentClass::InputLightStruct::Init_From_Point_Or_Spot_Light if (light.Get_Flag(LightClass::FAR_ATTENUATION)) { - if (WWMath::Fabs(atten_end - atten_start) < WWMATH_EPSILON) { + if (WWMath::Fabsf_Legacy(atten_end - atten_start) < WWMATH_EPSILON) { /* ** Start and end are equal, attenuation is a "step" function @@ -424,4 +424,3 @@ void LightEnvironmentClass::Calculate_Fill_Light() Add_Fill_Light(); } - diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/linegrp.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/linegrp.cpp index e61142bc226..b911cc04424 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/linegrp.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/linegrp.cpp @@ -41,7 +41,9 @@ #include "linegrp.h" #include "WW3D2/texture.h" #include "vertmaterial.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/ww3dcolor.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "WWMath/wwmath.h" #include "rinfo.h" #include "camera.h" @@ -257,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()); @@ -269,9 +271,9 @@ void LineGroupClass::Render(RenderInfoClass &rinfo) const bool sort = (Shader.Get_Dst_Blend_Func() != ShaderClass::DSTBLEND_ZERO) && (Shader.Get_Alpha_Test() == ShaderClass::ALPHATEST_DISABLE) && (WW3D::Is_Sorting_Enabled()); // the 3 offsets in view space - const static Vector3 offset_a = Vector3(WWMath::Cos(WWMATH_PI / 2), WWMath::Sin(WWMATH_PI /2 ), 0); - const static Vector3 offset_b = Vector3(WWMath::Cos(7 * WWMATH_PI / 6), WWMath::Sin(7 * WWMATH_PI / 6), 0); - const static Vector3 offset_c = Vector3(WWMath::Cos(11 * WWMATH_PI / 6), WWMath::Sin(11 * WWMATH_PI / 6), 0); + const static Vector3 offset_a = Vector3(WWMath::Cosf_Legacy(WWMATH_PI / 2), WWMath::Sinf_Legacy(WWMATH_PI /2 ), 0); + const static Vector3 offset_b = Vector3(WWMath::Cosf_Legacy(7 * WWMATH_PI / 6), WWMath::Sinf_Legacy(7 * WWMATH_PI / 6), 0); + const static Vector3 offset_c = Vector3(WWMath::Cosf_Legacy(11 * WWMATH_PI / 6), WWMath::Sinf_Legacy(11 * WWMATH_PI / 6), 0); static Vector3 offset[3]; @@ -281,10 +283,10 @@ void LineGroupClass::Render(RenderInfoClass &rinfo) // Save off the view matrix Matrix4x4 view; - DX8Wrapper::Get_Transform(D3DTS_VIEW, view); + g_renderBackend->Get_Transform(RB_TRANSFORM_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)) { @@ -296,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; @@ -319,7 +321,7 @@ void LineGroupClass::Render(RenderInfoClass &rinfo) // construct the tetrahedra in the index buffers // assume first vertex is the apex, followed by offset[0-3] - DynamicIBAccessClass iba(sort?BUFFER_TYPE_DYNAMIC_SORTING:BUFFER_TYPE_DYNAMIC_DX8,num_indices); + DynamicIBAccessClass iba(sort?BUFFER_TYPE_DYNAMIC_SORTING:BUFFER_TYPE_DYNAMIC,num_indices); { DynamicIBAccessClass::WriteLockClass lock(&iba); @@ -386,7 +388,7 @@ void LineGroupClass::Render(RenderInfoClass &rinfo) // make the vertex buffers - DynamicVBAccessClass vba(sort ? BUFFER_TYPE_DYNAMIC_SORTING : BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,num_vertices); + DynamicVBAccessClass vba(sort ? BUFFER_TYPE_DYNAMIC_SORTING : BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,num_vertices); { DynamicVBAccessClass::WriteLockClass lock(&vba); @@ -417,7 +419,7 @@ void LineGroupClass::Render(RenderInfoClass &rinfo) vb->x = end.X; vb->y = end.Y; vb->z = end.Z; - vb->diffuse = DX8Wrapper::Convert_Color(taildiffuse); + vb->diffuse = WW3DColor::To_ARGB(taildiffuse); vb->u1 = ucoord; vb->v1 = 1.0f; vb++; @@ -427,7 +429,7 @@ void LineGroupClass::Render(RenderInfoClass &rinfo) vb->x = loc.X; vb->y = loc.Y; vb->z = loc.Z; - vb->diffuse = DX8Wrapper::Convert_Color(diffuse); + vb->diffuse = WW3DColor::To_ARGB(diffuse); vb->u1 = ucoord; vb->v1 = 0.0f; vb++; @@ -440,7 +442,7 @@ void LineGroupClass::Render(RenderInfoClass &rinfo) vb->x = loc.X; vb->y = loc.Y; vb->z = loc.Z; - vb->diffuse = DX8Wrapper::Convert_Color(diffuse); + vb->diffuse = WW3DColor::To_ARGB(diffuse); vb->u1 = ucoord; vb->v1 = 0.0f; vb++; @@ -454,7 +456,7 @@ void LineGroupClass::Render(RenderInfoClass &rinfo) vb->x = loc.X; vb->y = loc.Y; vb->z = loc.Z; - vb->diffuse = DX8Wrapper::Convert_Color(taildiffuse); + vb->diffuse = WW3DColor::To_ARGB(taildiffuse); vb->u1 = ucoord; vb->v1 = 1.0f; vb++; @@ -465,17 +467,17 @@ 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 - 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/mapper.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/mapper.cpp index b08d50c2a89..e947b911592 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/mapper.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/mapper.cpp @@ -41,7 +41,8 @@ #include "WWLib/chunkio.h" #include "WW3D2/w3derr.h" #include "meshmatdesc.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "WWDebug/wwdebug.h" #include "WW3D2/matinfo.h" #include "WW3D2/rendobj.h" @@ -51,8 +52,6 @@ Random4Class rand4; -inline DWORD F2DW( FLOAT f ) { return *((DWORD*)&f); } - // HY 1/26/01 // Rewritten to use DX 8 texture matrices @@ -90,13 +89,13 @@ void ScaleTextureMapperClass::Apply(int uv_array_index) // Set up the texture matrix Matrix4x4 m; Calculate_Texture_Matrix(m); - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE) (D3DTS_TEXTURE0+Stage),m); + g_renderBackend->Set_Texture_Transform(Stage, m); // Disable Texgen - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_PASSTHRU | uv_array_index); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_MESH_UV, uv_array_index); // Tell rasterizer to expect 2D texture coordinates - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 2, false); } void ScaleTextureMapperClass::Calculate_Texture_Matrix(Matrix4x4 &tex_matrix) @@ -168,8 +167,8 @@ void LinearOffsetTextureMapperClass::Calculate_Texture_Matrix(Matrix4x4 &tex_mat // If ClampFix is TRUE we clamp the offsets between -Scale and +Scale with no wraparound. // This works well for clamped textures. if (!ClampFix) { - offset_u = offset_u - WWMath::Floor(offset_u); - offset_v = offset_v - WWMath::Floor(offset_v); + offset_u = offset_u - WWMath::Floorf(offset_u); + offset_v = offset_v - WWMath::Floorf(offset_v); } else { offset_u = WWMath::Clamp(offset_u, -Scale.X, Scale.X); offset_v = WWMath::Clamp(offset_v, -Scale.Y, Scale.Y); @@ -228,13 +227,13 @@ void GridTextureMapperClass::Apply(int uv_array_index) // Set up the texture matrix Matrix4x4 m; Calculate_Texture_Matrix(m); - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE) (D3DTS_TEXTURE0+Stage), m); + g_renderBackend->Set_Texture_Transform(Stage, m); // Disable Texgen - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU | uv_array_index); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_MESH_UV, uv_array_index); // Tell rasterizer to expect 2D texture coordinates - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 2, false); } void GridTextureMapperClass::Reset() @@ -372,8 +371,8 @@ void RotateTextureMapperClass::Calculate_Texture_Matrix(Matrix4x4 &tex_matrix) // Set up the rotation matrix float c,s; - c=WWMath::Cos(CurrentAngle); - s=WWMath::Sin(CurrentAngle); + c=WWMath::Cosf_Legacy(CurrentAngle); + s=WWMath::Sinf_Legacy(CurrentAngle); tex_matrix.Make_Identity(); // subtract center @@ -514,8 +513,8 @@ void StepLinearOffsetTextureMapperClass::Calculate_Texture_Matrix(Matrix4x4 &tex // If ClampFix is TRUE we clamp the offsets between -Scale and +Scale with no wraparound. // This works well for clamped textures. if (!ClampFix) { - CurrentStep.U -= WWMath::Floor(CurrentStep.U); - CurrentStep.V -= WWMath::Floor(CurrentStep.V); + CurrentStep.U -= WWMath::Floorf(CurrentStep.U); + CurrentStep.V -= WWMath::Floorf(CurrentStep.V); } else { CurrentStep.U = WWMath::Clamp(CurrentStep.U, -Scale.X, Scale.X); CurrentStep.V = WWMath::Clamp(CurrentStep.V, -Scale.Y, Scale.Y); @@ -623,13 +622,13 @@ void ClassicEnvironmentMapperClass::Apply(int uv_array_index) // Set up the texture matrix Matrix4x4 m; Calculate_Texture_Matrix(m); - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE) (D3DTS_TEXTURE0+Stage),m); + g_renderBackend->Set_Texture_Transform(Stage, m); // Get camera normals - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACENORMAL); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_CAMERA_SPACE_NORMAL); // Tell rasterizer to expect 2D matrices - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 2, false); } @@ -649,13 +648,13 @@ void EnvironmentMapperClass::Apply(int uv_array_index) // Set up the texture matrix Matrix4x4 m; Calculate_Texture_Matrix(m); - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE) (D3DTS_TEXTURE0+Stage),m); + g_renderBackend->Set_Texture_Transform(Stage, m); // Get camera reflection vector - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_CAMERA_SPACE_REFLECTION); // Tell rasterizer to expect 2D matrices - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 2, false); } @@ -705,16 +704,16 @@ void EdgeMapperClass::Apply(int uv_array_index) // Set up the texture matrix Matrix4x4 m; Calculate_Texture_Matrix(m); - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE) (D3DTS_TEXTURE0+Stage),m); + g_renderBackend->Set_Texture_Transform(Stage, m); // Get camera reflection vector if (UseReflect) - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_CAMERA_SPACE_REFLECTION); else - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACENORMAL); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_CAMERA_SPACE_NORMAL); // Tell rasterizer to expect 2D matrices - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 2, false); } @@ -732,7 +731,7 @@ void EdgeMapperClass::Calculate_Texture_Matrix(Matrix4x4 &tex_matrix) LastUsedSyncTime=now; VOffset+=delta*VSpeed; - VOffset-=WWMath::Floor(VOffset); + VOffset-=WWMath::Floorf(VOffset); // takes the Z component and // uses it to index the texture @@ -794,7 +793,7 @@ void WSEnvMapperClass::Calculate_Texture_Matrix(Matrix4x4 &tex_matrix) } // multiply by inverse of view transform Matrix4x4 mat; - DX8Wrapper::Get_Transform(D3DTS_VIEW,mat); + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, mat); Matrix4x4 mat2( mat[0].X, mat[1].X, mat[2].X, 0.0f, mat[0].Y, mat[1].Y, mat[2].Y, 0.0f, mat[0].Z, mat[1].Z, mat[2].Z, 0.0f, @@ -807,13 +806,13 @@ void WSClassicEnvironmentMapperClass::Apply(int uv_array_index) // Set up the texture matrix Matrix4x4 m; Calculate_Texture_Matrix(m); - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE) (D3DTS_TEXTURE0+Stage),m); + g_renderBackend->Set_Texture_Transform(Stage, m); // Get camera normals - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACENORMAL); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_CAMERA_SPACE_NORMAL); // Tell rasterizer to expect 2D matrices - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 2, false); } @@ -822,13 +821,13 @@ void WSEnvironmentMapperClass::Apply(int uv_array_index) // Set up the texture matrix Matrix4x4 m; Calculate_Texture_Matrix(m); - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE) (D3DTS_TEXTURE0+Stage),m); + g_renderBackend->Set_Texture_Transform(Stage, m); // Get camera reflection - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_CAMERA_SPACE_REFLECTION); // Tell rasterizer to expect 2D matrices - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 2, false); } @@ -837,13 +836,13 @@ void GridClassicEnvironmentMapperClass::Apply(int uv_array_index) // Set up the texture matrix Matrix4x4 m; Calculate_Texture_Matrix(m); - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE) (D3DTS_TEXTURE0+Stage),m); + g_renderBackend->Set_Texture_Transform(Stage, m); // Get camera normals - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACENORMAL); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_CAMERA_SPACE_NORMAL); // Tell rasterizer to expect 2D matrices - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 2, false); } void GridClassicEnvironmentMapperClass::Calculate_Texture_Matrix(Matrix4x4 &tex_matrix) @@ -866,13 +865,13 @@ void GridEnvironmentMapperClass::Apply(int uv_array_index) // Set up the texture matrix Matrix4x4 m; Calculate_Texture_Matrix(m); - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE) (D3DTS_TEXTURE0+Stage),m); + g_renderBackend->Set_Texture_Transform(Stage, m); // Get camera space reflection - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_CAMERA_SPACE_REFLECTION); // Tell rasterizer to expect 2D matrices - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 2, false); } void GridEnvironmentMapperClass::Calculate_Texture_Matrix(Matrix4x4 &tex_matrix) @@ -895,13 +894,13 @@ void ScreenMapperClass::Apply(int uv_array_index) // Set up the texture matrix Matrix4x4 m; Calculate_Texture_Matrix(m); - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE) (D3DTS_TEXTURE0+Stage),m); + g_renderBackend->Set_Texture_Transform(Stage, m); // Get camera space position - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACEPOSITION); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_CAMERA_SPACE_POSITION); // Tell rasterizer what to expect - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_PROJECTED | D3DTTFF_COUNT3); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 3, true); } void ScreenMapperClass::Calculate_Texture_Matrix(Matrix4x4 &tex_matrix) @@ -918,8 +917,8 @@ void ScreenMapperClass::Calculate_Texture_Matrix(Matrix4x4 &tex_matrix) // If ClampFix is TRUE we clamp the offsets between -Scale and +Scale with no wraparound. // This works well for clamped textures. if (!ClampFix) { - offset_u = offset_u - WWMath::Floor(offset_u); - offset_v = offset_v - WWMath::Floor(offset_v); + offset_u = offset_u - WWMath::Floorf(offset_u); + offset_v = offset_v - WWMath::Floorf(offset_v); } else { offset_u = WWMath::Clamp(offset_u, -Scale.X, Scale.X); offset_v = WWMath::Clamp(offset_v, -Scale.Y, Scale.Y); @@ -927,7 +926,7 @@ void ScreenMapperClass::Calculate_Texture_Matrix(Matrix4x4 &tex_matrix) // multiply by projection matrix // followed by scale and translation - DX8Wrapper::Get_Transform(D3DTS_PROJECTION, tex_matrix); + g_renderBackend->Get_Transform(RB_TRANSFORM_PROJECTION, tex_matrix); tex_matrix[0] *= Scale.X; // entire row since we're pre-multiplying tex_matrix[1] *= Scale.Y; Vector4 last(tex_matrix[3]); // this gets the w @@ -1075,10 +1074,7 @@ void BumpEnvTextureMapperClass::Apply(int uv_array_index) s=ScaleFactor * WWMath::Fast_Sin(CurrentAngle); // Set the Bump Environment Matrix - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_BUMPENVMAT00, F2DW(c)); - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_BUMPENVMAT01, F2DW(-s)); - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_BUMPENVMAT10, F2DW(s)); - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_BUMPENVMAT11, F2DW(c)); + g_renderBackend->Set_Texture_Bump_Env_Matrix(Stage, c, -s, s, c); } /* @@ -1154,7 +1150,7 @@ void GridWSEnvMapperClass::Calculate_Texture_Matrix(Matrix4x4 &tex_matrix) { // multiply by inverse of view transform Matrix4x4 mat; - DX8Wrapper::Get_Transform(D3DTS_VIEW,mat); + g_renderBackend->Get_Transform(RB_TRANSFORM_VIEW, mat); Matrix4x4 mv ( mat[0].X, mat[1].X, mat[2].X, 0.0f, mat[0].Y, mat[1].Y, mat[2].Y, 0.0f, mat[0].Z, mat[1].Z, mat[2].Z, 0.0f, @@ -1231,13 +1227,13 @@ void GridWSClassicEnvironmentMapperClass::Apply(int uv_array_index) // Set up the texture matrix Matrix4x4 m; Calculate_Texture_Matrix(m); - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE) (D3DTS_TEXTURE0+Stage),m); + g_renderBackend->Set_Texture_Transform(Stage, m); // Get camera normals - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACENORMAL); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_CAMERA_SPACE_NORMAL); // Tell rasterizer to expect 2D matrices - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 2, false); } /*********************************************************************************************** @@ -1275,11 +1271,11 @@ void GridWSEnvironmentMapperClass::Apply(int uv_array_index) // Set up the texture matrix Matrix4x4 m; Calculate_Texture_Matrix(m); - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE) (D3DTS_TEXTURE0+Stage),m); + g_renderBackend->Set_Texture_Transform(Stage, m); // Get camera space reflection - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_CAMERA_SPACE_REFLECTION); // Tell rasterizer to expect 2D matrices - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 2, false); } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/matrixmapper.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/matrixmapper.cpp index 7264a7dd3c6..589ba2857a2 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/matrixmapper.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/matrixmapper.cpp @@ -51,7 +51,7 @@ #include "matrixmapper.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" /*********************************************************************************************** @@ -233,9 +233,9 @@ void MatrixMapperClass::Apply(int uv_array_index) /* ** Orthographic projection */ - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE)(D3DTS_TEXTURE0 + Stage),ViewToPixel); - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACEPOSITION); - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2); + g_renderBackend->Set_Texture_Transform(Stage, ViewToPixel); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_CAMERA_SPACE_POSITION); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 2, false); break; case PERSPECTIVE_PROJECTION: /* @@ -244,9 +244,9 @@ void MatrixMapperClass::Apply(int uv_array_index) m[0]=ViewToPixel[0]; m[1]=ViewToPixel[1]; m[2]=ViewToPixel[3]; - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE)(D3DTS_TEXTURE0 + Stage),m); - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACEPOSITION); - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_PROJECTED|D3DTTFF_COUNT3); + g_renderBackend->Set_Texture_Transform(Stage, m); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_CAMERA_SPACE_POSITION); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 3, true); break; case DEPTH_GRADIENT: /* @@ -257,9 +257,9 @@ void MatrixMapperClass::Apply(int uv_array_index) */ m[0].Set(0,0,0,GradientUCoord); m[1]=ViewToPixel[2]; - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE)(D3DTS_TEXTURE0 + Stage),m); - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACEPOSITION); - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2); + g_renderBackend->Set_Texture_Transform(Stage, m); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_CAMERA_SPACE_POSITION); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 2, false); break; case NORMAL_GRADIENT: /* @@ -270,9 +270,9 @@ void MatrixMapperClass::Apply(int uv_array_index) */ m[0].Set(0,0,0,GradientUCoord); m[1].Set(ViewSpaceProjectionNormal.X,ViewSpaceProjectionNormal.Y,ViewSpaceProjectionNormal.Z, 0); - DX8Wrapper::Set_Transform((D3DTRANSFORMSTATETYPE)(D3DTS_TEXTURE0 + Stage),m); - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_CAMERASPACENORMAL); - DX8Wrapper::Set_DX8_Texture_Stage_State(Stage,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2); + g_renderBackend->Set_Texture_Transform(Stage, m); + g_renderBackend->Set_Texture_Coord_Source(Stage, RB_TEXCOORD_CAMERA_SPACE_NORMAL); + g_renderBackend->Set_Texture_Transform_Mode(Stage, 2, false); break; } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/mesh.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/mesh.cpp index 0da89d80964..509f59f77aa 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 "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "assetmgr.h" #include "WW3D2/w3derr.h" #include "WWDebug/wwdebug.h" @@ -655,6 +657,14 @@ int MeshClass::Get_Num_Polys() const * HISTORY: * * 12/10/98 GTH : Created. * *=============================================================================================*/ +#if defined(GGC_RENDER_BACKEND_BGFX) +// TheSuperHackers @feature bobtista 17/06/2026 Defined in BgfxBackend. Returns nonzero when the sun +// shadow map is armed this frame, filling a world cull sphere (center3 + radius) that covers the +// shadow cascades. MeshClass::Render keeps meshes intersecting it even when they are outside the +// camera frustum, so a caster that has scrolled off-screen still casts into the visible ground. +extern "C" int GGC_GetBgfxSunShadowCullBox(float * center3, float * radius); +#endif + void MeshClass::Render(RenderInfoClass & rinfo) { WWPROFILE("Mesh::Render"); @@ -688,8 +698,35 @@ void MeshClass::Render(RenderInfoClass & rinfo) const FrustumClass & frustum=rinfo.Camera.Get_Frustum(); - if ( Model->Get_Flag(MeshGeometryClass::SKIN) || - CollisionMath::Overlap_Test(frustum,Get_Bounding_Box())!=CollisionMath::OUTSIDE ) + bool meshInView = Model->Get_Flag(MeshGeometryClass::SKIN) || + CollisionMath::Overlap_Test(frustum,Get_Bounding_Box())!=CollisionMath::OUTSIDE; +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @feature bobtista 17/06/2026 Also render a mesh that is outside the camera + // frustum but inside the sun-shadow cull sphere, so off-screen casters still cast into the + // visible ground. Such a mesh is GPU-clipped from the color view (it produces no visible + // pixels); it exists only to fill the shadow cascades via the caster piggyback. + if (!meshInView) + { + float sc[3]; + float sr = 0.0f; + if (GGC_GetBgfxSunShadowCullBox(sc, &sr) != 0 && sr > 0.0f) + { + const AABoxClass & bb = Get_Bounding_Box(); + const float adx = bb.Center.X - sc[0]; + const float ady = bb.Center.Y - sc[1]; + const float adz = bb.Center.Z - sc[2]; + const float dx = (adx < 0.0f ? -adx : adx) - bb.Extent.X; + const float dy = (ady < 0.0f ? -ady : ady) - bb.Extent.Y; + const float dz = (adz < 0.0f ? -adz : adz) - bb.Extent.Z; + float distSq = 0.0f; + if (dx > 0.0f) { distSq += dx * dx; } + if (dy > 0.0f) { distSq += dy * dy; } + if (dz > 0.0f) { distSq += dz * dz; } + if (distSq <= sr * sr) { meshInView = true; } + } + } +#endif + if ( meshInView ) { bool rendered_something = false; @@ -827,7 +864,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)) { @@ -851,11 +888,12 @@ void MeshClass::Render_Material_Pass(MaterialPassClass * pass,IndexBufferClass * mat->Set_Emissive(m_materialPassEmissiveOverride*oldEmissive); } } + pass->Set_Context_Texture(Model->Peek_Single_Texture(0, 0), 0); 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()) { @@ -876,6 +914,7 @@ void MeshClass::Render_Material_Pass(MaterialPassClass * pass,IndexBufferClass * } //MW: Need uninstall custom materials in case they leave D3D in unknown state pass->UnInstall_Materials(); + pass->Set_Context_Texture(nullptr, 0); } else if ((pass->Get_Cull_Volume() != nullptr) && (MaterialPassClass::Is_Per_Polygon_Culling_Enabled())) { @@ -902,7 +941,7 @@ void MeshClass::Render_Material_Pass(MaterialPassClass * pass,IndexBufferClass * if (temp_apt.Count() > 0) { - int buftype = BUFFER_TYPE_DYNAMIC_DX8; + int buftype = BUFFER_TYPE_DYNAMIC; if (Model->Get_Flag(MeshGeometryClass::SORT) && WW3D::Is_Sorting_Enabled()) { buftype = BUFFER_TYPE_DYNAMIC_SORTING; } @@ -943,18 +982,20 @@ void MeshClass::Render_Material_Pass(MaterialPassClass * pass,IndexBufferClass * ** Render */ int vertex_offset = Model->PolygonRendererList.Peek_Head()->Get_Vertex_Offset(); + pass->Set_Context_Texture(Model->Peek_Single_Texture(0, 0), 0); 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, max_v-min_v+1); //MW: Need uninstall custom materials in case they leave D3D in unknown state pass->UnInstall_Materials(); + pass->Set_Context_Texture(nullptr, 0); } } else { @@ -977,11 +1018,12 @@ void MeshClass::Render_Material_Pass(MaterialPassClass * pass,IndexBufferClass * mat->Set_Emissive(m_materialPassEmissiveOverride*oldEmissive); } } + pass->Set_Context_Texture(Model->Peek_Single_Texture(0, 0), 0); 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()) { @@ -1003,6 +1045,7 @@ void MeshClass::Render_Material_Pass(MaterialPassClass * pass,IndexBufferClass * } //MW: Need uninstall custom materials in case they leave D3D in unknown state pass->UnInstall_Materials(); + pass->Set_Context_Texture(nullptr, 0); } } @@ -1595,5 +1638,3 @@ int MeshClass::Get_Draw_Call_Count() const - - diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshgeometry.h b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshgeometry.h index 84161f703f5..0c79df106df 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshgeometry.h +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshgeometry.h @@ -116,6 +116,7 @@ class MeshGeometryClass : public RefCountClass, public MultiListObjectClass PRELIT_LIGHTMAP_MULTI_TEXTURE = 0x00008000, ALLOW_NPATCHES = 0x00010000, + COPLANAR_NORMAL_BIAS = 0x00020000, }; void Reset_Geometry(int polycount,int vertcount); diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshmatdesc.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshmatdesc.cpp index 5fe1781ecf0..bafb597f367 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshmatdesc.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshmatdesc.cpp @@ -40,9 +40,10 @@ #include "WW3D2/texture.h" #include "vertmaterial.h" #include "WWLib/realcrc.h" -#include "WW3D2/dx8wrapper.h" -#include "WW3D2/dx8caps.h" +#include "WW3D2/ww3dcolor.h" #include "meshmdl.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" /************************************************************************************************** @@ -751,12 +752,12 @@ void MeshMatDescClass::Post_Load_Process(bool lighting_enabled,MeshModelClass * unsigned * emissive_array = ColorArray[1]->Get_Array(); for (int vidx=0; vidxSet_Ambient_Color_Source(VertexMaterialClass::MATERIAL); mtl->Set_Diffuse_Color_Source(VertexMaterialClass::COLOR1); @@ -797,12 +798,12 @@ void MeshMatDescClass::Post_Load_Process(bool lighting_enabled,MeshModelClass * // ambient are different but is probably the most reasonable thing to do. Why set // diffuse and ambient differently anyway?) if (diffuse_used && ambient_used && !emissive_used) { - Vector4 diffuse=DX8Wrapper::Convert_Color(diffuse_array[vidx]); + Vector4 diffuse=WW3DColor::From_ARGB(diffuse_array[vidx]); diffuse.X *= mtl_diffuse.X; diffuse.Y *= mtl_diffuse.Y; diffuse.Z *= mtl_diffuse.Z; diffuse.W *= mtl_opacity; - diffuse_array[vidx]=DX8Wrapper::Convert_Color(diffuse); + diffuse_array[vidx]=WW3DColor::To_ARGB(diffuse); mtl->Set_Ambient_Color_Source(VertexMaterialClass::COLOR1); mtl->Set_Diffuse_Color_Source(VertexMaterialClass::COLOR1); @@ -811,12 +812,12 @@ void MeshMatDescClass::Post_Load_Process(bool lighting_enabled,MeshModelClass * // If only ambient is used apply ambient to color channel and set ambient source to color 1 if (!diffuse_used && ambient_used && !emissive_used) { - Vector4 diffuse=DX8Wrapper::Convert_Color(diffuse_array[vidx]); + Vector4 diffuse=WW3DColor::From_ARGB(diffuse_array[vidx]); diffuse.X *= mtl_ambient.X; diffuse.Y *= mtl_ambient.Y; diffuse.Z *= mtl_ambient.Z; diffuse.W *= mtl_opacity; - diffuse_array[vidx]=DX8Wrapper::Convert_Color(diffuse); + diffuse_array[vidx]=WW3DColor::To_ARGB(diffuse); mtl->Set_Ambient_Color_Source(VertexMaterialClass::COLOR1); mtl->Set_Diffuse_Color_Source(VertexMaterialClass::MATERIAL); @@ -825,12 +826,12 @@ void MeshMatDescClass::Post_Load_Process(bool lighting_enabled,MeshModelClass * // If only emissive is used apply emissive to color channel, set diffuse source to color 1, and turn off lighting if (!diffuse_used && !ambient_used && emissive_used) { - Vector4 diffuse=DX8Wrapper::Convert_Color(diffuse_array[vidx]); + Vector4 diffuse=WW3DColor::From_ARGB(diffuse_array[vidx]); diffuse.X *= mtl_emissive.X; diffuse.Y *= mtl_emissive.Y; diffuse.Z *= mtl_emissive.Z; diffuse.W *= mtl_opacity; - diffuse_array[vidx]=DX8Wrapper::Convert_Color(diffuse); + diffuse_array[vidx]=WW3DColor::To_ARGB(diffuse); mtl->Set_Ambient_Color_Source(VertexMaterialClass::MATERIAL); mtl->Set_Diffuse_Color_Source(VertexMaterialClass::COLOR1); @@ -858,13 +859,13 @@ void MeshMatDescClass::Post_Load_Process(bool lighting_enabled,MeshModelClass * // HY: Earth and beyond uses a different fallback from Renegade with regards to bump environment maps // we keep the pass but change it to an unbumped environment if ( (Shader[pass].Get_Primary_Gradient() == ShaderClass::GRADIENT_BUMPENVMAP) && - (!DX8Wrapper::Is_Initted() || DX8Wrapper::Get_Current_Caps()->Support_Bump_Envmap() == false) ) + (!g_renderBackend || g_renderBackend->Supports_Bump_Envmap() == false) ) { kill_pass = true; } if ( (Shader[pass].Get_Primary_Gradient() == ShaderClass::GRADIENT_BUMPENVMAPLUMINANCE) && - (!DX8Wrapper::Is_Initted() || DX8Wrapper::Get_Current_Caps()->Support_Bump_Envmap_Luminance() == false) ) + (!g_renderBackend || g_renderBackend->Supports_Bump_Envmap_Luminance() == false) ) { kill_pass = true; } @@ -957,7 +958,7 @@ void MeshMatDescClass::Configure_Material(VertexMaterialClass * mtl,int pass,boo bool MeshMatDescClass::Do_Mappers_Need_Normals() { - if (DX8Wrapper::Is_Initted() && DX8Wrapper::Get_Current_Caps()->Support_NPatches() && WW3D::Get_NPatches_Level()>1) return true; + if (g_renderBackend && g_renderBackend->Supports_NPatches() && WW3D::Get_NPatches_Level()>1) return true; for (int pass=0; passSupport_NPatches()) return; + if (!g_renderBackend || !g_renderBackend->Supports_NPatches()) return; if (!Get_Flag(MeshGeometryClass::ALLOW_NPATCHES)) return; if (GapFiller) return; diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshmdlio.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshmdlio.cpp index 42a9abf3052..e58bfb709a5 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshmdlio.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/meshmdlio.cpp @@ -88,7 +88,9 @@ #include "assetmgr.h" #include "WWLib/simplevec.h" #include "WWLib/realcrc.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/ww3dcolor.h" + +#include #ifdef _UNIX #include "osdep/osdep.h" @@ -96,6 +98,169 @@ #define MESH_SINGLE_MATERIAL_HACK 0 // (gth) forces all multi-material meshes to use their first material only. (NOT RECOMMENDED, TESTING ONLY!) #define MESH_FORCE_STATIC_SORT_HACK 0 // (gth) forces all sorting meshes to use static sort level 1 instead. + +static bool Same_Position(const Vector3 & a, const Vector3 & b) +{ + const float dx = a.X - b.X; + const float dy = a.Y - b.Y; + const float dz = a.Z - b.Z; + return dx * dx + dy * dy + dz * dz < 0.000001f; +} + +static bool Triangles_Share_Positions(const Vector3 * verts, const TriIndex & a, const TriIndex & b) +{ + bool matched[3] = { false, false, false }; + for (int ai = 0; ai < 3; ++ai) { + bool found = false; + for (int bi = 0; bi < 3; ++bi) { + if (!matched[bi] && Same_Position(verts[a[ai]], verts[b[bi]])) { + matched[bi] = true; + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; +} + +static bool Compute_Triangle_Plane(const Vector3 * verts, const TriIndex & tri, Vector3 * normal, float * dist) +{ + const Vector3 & p0 = verts[tri[0]]; + const Vector3 & p1 = verts[tri[1]]; + const Vector3 & p2 = verts[tri[2]]; + Vector3 e0 = p1 - p0; + Vector3 e1 = p2 - p0; + Vector3::Cross_Product(e0, e1, normal); + if (normal->Length2() < 0.000001f) { + return false; + } + normal->Normalize(); + *dist = Vector3::Dot_Product(*normal, p0); + return true; +} + +static bool Compute_Averaged_Vertex_Normal(const Vector3 * norms, const TriIndex & tri, Vector3 * normal) +{ + *normal = norms[tri[0]] + norms[tri[1]] + norms[tri[2]]; + if (normal->Length2() < 0.000001f) { + return false; + } + normal->Normalize(); + return true; +} + +static bool Triangle_Is_On_Plane(const Vector3 * verts, const TriIndex & tri, const Vector3 & normal, float dist) +{ + for (int i = 0; i < 3; ++i) { + if (fabsf(Vector3::Dot_Product(normal, verts[tri[i]]) - dist) > 0.001f) { + return false; + } + } + return true; +} + +static int Dominant_Axis(const Vector3 & normal) +{ + const float ax = fabsf(normal.X); + const float ay = fabsf(normal.Y); + const float az = fabsf(normal.Z); + if (ax >= ay && ax >= az) { + return 0; + } + if (ay >= az) { + return 1; + } + return 2; +} + +static void Project_Vertex(const Vector3 & v, int drop_axis, float * u, float * w) +{ + if (drop_axis == 0) { + *u = v.Y; + *w = v.Z; + } else if (drop_axis == 1) { + *u = v.X; + *w = v.Z; + } else { + *u = v.X; + *w = v.Y; + } +} + +static bool Projected_Triangle_Bounds_Overlap(const Vector3 * verts, + const TriIndex & a, + const TriIndex & b, + const Vector3 & plane_normal) +{ + const int drop_axis = Dominant_Axis(plane_normal); + float amin_u = 0.0f, amin_w = 0.0f, amax_u = 0.0f, amax_w = 0.0f; + float bmin_u = 0.0f, bmin_w = 0.0f, bmax_u = 0.0f, bmax_w = 0.0f; + for (int i = 0; i < 3; ++i) { + float u = 0.0f; + float w = 0.0f; + Project_Vertex(verts[a[i]], drop_axis, &u, &w); + if (i == 0 || u < amin_u) amin_u = u; + if (i == 0 || u > amax_u) amax_u = u; + if (i == 0 || w < amin_w) amin_w = w; + if (i == 0 || w > amax_w) amax_w = w; + Project_Vertex(verts[b[i]], drop_axis, &u, &w); + if (i == 0 || u < bmin_u) bmin_u = u; + if (i == 0 || u > bmax_u) bmax_u = u; + if (i == 0 || w < bmin_w) bmin_w = w; + if (i == 0 || w > bmax_w) bmax_w = w; + } + return amin_u <= bmax_u + 0.001f + && amax_u + 0.001f >= bmin_u + && amin_w <= bmax_w + 0.001f + && amax_w + 0.001f >= bmin_w; +} + +static bool Has_Coplanar_Opposite_Triangle_Pairs(MeshGeometryClass * mesh) +{ + const int poly_count = mesh->Get_Polygon_Count(); + if (poly_count < 2) { + return false; + } + + const TriIndex * polys = mesh->Get_Polygon_Array(); + const Vector3 * verts = mesh->Get_Vertex_Array(); + const Vector3 * norms = mesh->Get_Vertex_Normal_Array(); + for (int i = 0; i < poly_count; ++i) { + Vector3 ni; + float di = 0.0f; + if (!Compute_Triangle_Plane(verts, polys[i], &ni, &di)) { + continue; + } + for (int j = i + 1; j < poly_count; ++j) { + Vector3 nj; + float dj = 0.0f; + if (!Compute_Triangle_Plane(verts, polys[j], &nj, &dj)) { + continue; + } + const bool same_positions = Triangles_Share_Positions(verts, polys[i], polys[j]); + const bool opposite_face_planes = + Vector3::Dot_Product(ni, nj) < -0.999f && fabsf(di + dj) < 0.001f; + if (same_positions && opposite_face_planes) { + return true; + } + Vector3 vni; + Vector3 vnj; + if (opposite_face_planes + && Triangle_Is_On_Plane(verts, polys[j], ni, di) + && Projected_Triangle_Bounds_Overlap(verts, polys[i], polys[j], ni) + && Compute_Averaged_Vertex_Normal(norms, polys[i], &vni) + && Compute_Averaged_Vertex_Normal(norms, polys[j], &vnj) + && Vector3::Dot_Product(vni, vnj) < -0.9f) { + return true; + } + } + } + return false; +} + /** ** MeshLoadContextClass ** This class is just used as a temporary scratchpad while a mesh is being @@ -898,7 +1063,7 @@ WW3DErrorType MeshModelClass::read_vertex_colors(ChunkLoadClass & cload,MeshLoad Vector4 col; col.Set((float)color.R / 255.0f,(float)color.G / 255.0f,(float)color.B / 255.0f, 1.0f); - dcg[i]=DX8Wrapper::Convert_Color(col); + dcg[i]=WW3DColor::To_ARGB(col); } } CurMatDesc->Set_DCG_Source(context->CurPass,VertexMaterialClass::COLOR1); @@ -1264,7 +1429,7 @@ WW3DErrorType MeshModelClass::read_dcg(ChunkLoadClass & cload,MeshLoadContextCla cload.Read(&color,sizeof(color)); Vector4 col; W3dUtilityClass::Convert_Color(color,&col); - dcg[i]=DX8Wrapper::Convert_Color(col); + dcg[i]=WW3DColor::To_ARGB(col); } } else if (context->PrelitChunkID==W3D_CHUNK_PRELIT_VERTEX) { @@ -1274,9 +1439,9 @@ WW3DErrorType MeshModelClass::read_dcg(ChunkLoadClass & cload,MeshLoadContextCla for (int i=0; iGet_Color_Array(0); for (int i=0; i 59 + && DefMatDesc->Get_UV_Array(0, 0) != nullptr) { + Vector2 *uv = DefMatDesc->Get_UV_Array(0, 0); + const int side_vertices[][4] = { + { 44, 45, 46, 47 }, + { 48, 49, 50, 51 }, + { 52, 53, 54, 55 }, + { 56, 57, 58, 59 }, + }; + const float u0 = 44.0f / 256.0f; + const float u1 = 68.0f / 256.0f; + const float v0 = 216.0f / 256.0f; + const float v1 = 240.0f / 256.0f; + + for (int face = 0; face < 4; ++face) { + uv[side_vertices[face][0]].Set(u0, v0); + uv[side_vertices[face][1]].Set(u0, v1); + uv[side_vertices[face][2]].Set(u1, v1); + uv[side_vertices[face][3]].Set(u1, v0); + } + } +#endif + #if 0 // we want to allow this now due to usage of the static sort // Ensure no sorting, multipass meshes (for they are abomination...) @@ -1656,6 +1851,13 @@ void MeshModelClass::post_process() } } +#if defined(GGC_RENDER_BACKEND_BGFX) + // TheSuperHackers @performance bobtista 17/07/2026 The coplanar scan is O(polys^2) at + // load and only feeds the bgfx normal-bias (and instancing exclusion); the DX8 backend + // never consumes the flag, so skip the cost there. + Set_Flag(MeshGeometryClass::COPLANAR_NORMAL_BIAS, Has_Coplanar_Opposite_Triangle_Pairs(this)); +#endif + // turn off backface culling if the mesh is supposed to be two-sided if (Get_Flag(MeshGeometryClass::TWO_SIDED)) { diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/motchan.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/motchan.cpp index 174d98e517e..e7afd0ce75b 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/motchan.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/motchan.cpp @@ -861,7 +861,7 @@ AdaptiveDeltaMotionChannelClass::AdaptiveDeltaMotionChannelClass() : //ratio = ((ratio + 1.0f) / 128.0f); ratio/=((float) FILTER_TABLE_GEN_SIZE); - filtertable[i + FILTER_TABLE_GEN_START] = 1.0f - WWMath::Sin( DEG_TO_RAD(90.0f * ratio)); + filtertable[i + FILTER_TABLE_GEN_START] = 1.0f - WWMath::Sinf_Legacy( DEG_TO_RAD(90.0f * ratio)); } table_valid = true; diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_buf.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_buf.cpp index 29f037e0e16..e1ead2849c9 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_buf.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_buf.cpp @@ -35,6 +35,7 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "part_buf.h" +#include "GgcRuntimeFlags.h" #include "part_emt.h" #include "ww3d.h" #include "rinfo.h" @@ -47,9 +48,9 @@ #include "WWMath/sphere.h" #include "WWDebug/wwprofile.h" #include +#include #include "WWMath/vp.h" #include "WW3D2/texture.h" -#include "WW3D2/dx8wrapper.h" #include "WWMath/vector3.h" // A random permutation of the numbers 0 to 15 - used for LOD particle decimation. @@ -817,6 +818,10 @@ int ParticleBufferClass::Get_Particle_Count() const void ParticleBufferClass::Render(RenderInfoClass & rinfo) { WWPROFILE("ParticleBuffer::Render"); + static const bool s_probeNoParticleRender = GgcFlags::Enabled(GgcFlag_ProbeNoParticleRender); + if (s_probeNoParticleRender) { + return; + } unsigned int sort_level = SORT_LEVEL_NONE; diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp index ca8d9045bcc..abea139ba3e 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp @@ -670,7 +670,7 @@ void ParticleEmitterClass::Initialize_Particle(NewParticleStruct * newpart, Vector3 outwards; float pos_l2 = rand_pos.Length2(); if (pos_l2) { - outwards = rand_pos * (OutwardVel * WWMath::Inv_Sqrt(pos_l2)); + outwards = rand_pos * (OutwardVel * WWMath::Inv_Sqrt_Legacy(pos_l2)); } else { outwards.X = OutwardVel; outwards.Y = 0.0f; diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp index 36592e83276..2ce648d0cd9 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/render2d.cpp @@ -47,13 +47,12 @@ #include "WW3D2/texture.h" #include "WWMath/matrix4.h" #include "WWMath/matrix3d.h" -#include "WW3D2/dx8wrapper.h" #include "WW3D2/dx8indexbuffer.h" #include "WW3D2/dx8vertexbuffer.h" #include "WW3D2/sortingrenderer.h" #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" @@ -222,8 +221,8 @@ Vector2 Render2DClass::Convert_Vert( const Vector2 & v ) out.Y = (out.Y - 1.0f) * (Get_Screen_Resolution().Height() * -0.5f); // Round to nearest pixel - out.X = WWMath::Floor( out.X + 0.5f ); - out.Y = WWMath::Floor( out.Y + 0.5f ); + out.X = WWMath::Floorf( out.X + 0.5f ); + out.Y = WWMath::Floorf( out.Y + 0.5f ); // Bias if ( WW3D::Is_Screen_UV_Biased() ) { // Global bais setting @@ -606,8 +605,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 @@ -615,25 +614,25 @@ void Render2DClass::Render() int width, height, bits; bool windowed; WW3D::Get_Device_Resolution( width, height, bits, windowed ); - D3DVIEWPORT8 vp = { 0 }; - vp.X = 0; - vp.Y = 0; - vp.Width = width; - vp.Height = height; - vp.MinZ = 0; - vp.MaxZ = 1; - DX8Wrapper::Set_Viewport(&vp); - DX8Wrapper::Set_Texture(0,Texture); + RenderBackendViewport vp; + vp.x = 0; + vp.y = 0; + vp.width = width; + vp.height = height; + vp.min_z = 0.0f; + vp.max_z = 1.0f; + g_renderBackend->Set_Viewport(vp); + 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()); + DynamicVBAccessClass vb(BUFFER_TYPE_DYNAMIC,dynamic_fvf_type,Vertices.Count()); { DynamicVBAccessClass::WriteLockClass Lock(&vb); const FVFInfoClass &fi=vb.FVF_Info(); @@ -650,7 +649,7 @@ void Render2DClass::Render() } } - DynamicIBAccessClass ib(BUFFER_TYPE_DYNAMIC_DX8,Indices.Count()); + DynamicIBAccessClass ib(BUFFER_TYPE_DYNAMIC,Indices.Count()); { DynamicIBAccessClass::WriteLockClass Lock(&ib); unsigned short *mem=Lock.Get_Index_Array(); @@ -658,45 +657,35 @@ 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. - if (DX8Wrapper::Get_Current_Caps()->Support_Dot3()) - { //Override W3D states with customizations for grayscale - DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, 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); - DX8Wrapper::Set_DX8_Texture_Stage_State( 0, D3DTSS_COLOROP, D3DTOP_MULTIPLYADD); - - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG1, D3DTA_CURRENT); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLORARG2, D3DTA_TFACTOR); - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DOTPRODUCT3); - } - else - { //doesn't have DOT3 blend mode so fake it another way. - DX8Wrapper::Set_DX8_Render_State(D3DRS_TEXTUREFACTOR, 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); - - // TheSuperHackers @bugfix Stubbjax 08/01/2026 Fix possible greyscale rendering issues on hardware without DOT3 support. - DX8Wrapper::Set_DX8_Texture_Stage_State( 1, D3DTSS_COLOROP, D3DTOP_DISABLE); - } + g_renderBackend->Set_Shader(ShaderClass::_PresetOpaqueShader); + g_renderBackend->Apply_Render_State_Changes(); //force update of all regular W3D states. + g_renderBackend->Set_Grayscale_Mode(true); + g_renderBackend->Configure_Grayscale_Texture_Stages(); } 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. + g_renderBackend->Set_Grayscale_Mode(false); + } + // TheSuperHackers @bugfix bobtista 23/04/2026 Unbind slot 0 after 2D UI + // draws so the UI atlas cannot leak into subsequent 3D draws via the + // shader pipeline's sampler cache. + if (g_renderBackend->Has_Shader_Pipeline()) + { + g_renderBackend->Set_Texture(0, nullptr); + } } @@ -854,4 +843,3 @@ Vector2 Render2DTextClass::Get_Text_Extents( const WCHAR * text ) return extent; } - diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/scene.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/scene.cpp index f76899d71ce..6437ef091a7 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/scene.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/scene.cpp @@ -61,14 +61,18 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#include #include "scene.h" +#include "GgcRuntimeFlags.h" #include "WWMath/plane.h" #include "camera.h" +#include "lightenvironment.h" #include "ww3d.h" #include "rinfo.h" #include "WWLib/chunkio.h" #include "WW3D2/dx8renderer.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" #include "WW3D2/sortingrenderer.h" #include "WW3D2/coltest.h" @@ -215,7 +219,7 @@ void SceneClass::Render(RenderInfoClass & rinfo) // Any stuff that needs to get done before anything else Pre_Render_Processing(rinfo); - DX8Wrapper::Set_Fog(FogEnabled, FogColor, FogStart, FogEnd); + g_renderBackend->Set_Fog(FogEnabled, FogColor, FogStart, FogEnd); if (Get_Extra_Pass_Polygon_Mode()==EXTRA_PASS_DISABLE) { Customized_Render(rinfo); @@ -223,20 +227,22 @@ void SceneClass::Render(RenderInfoClass & rinfo) else { bool old_enable=WW3D::Is_Texturing_Enabled(); - DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 0); + // TheSuperHackers @refactor bobtista 21/04/2026 Route fill mode + + // Z-bias through g_renderBackend so bgfx sees the state. + g_renderBackend->Set_Z_Bias(0); Customized_Render(rinfo); 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: - DX8Wrapper::Clear(true, false, Vector3(0.0f,0.0f,0.0f)); // Clear color but not z + g_renderBackend->Clear(true, false, Vector3(0.0f,0.0f,0.0f)); // Clear color but not z 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; } @@ -557,10 +563,10 @@ void SimpleSceneClass::Customized_Render(RenderInfoClass & rinfo) WWASSERT(rinfo.light_environment==nullptr); int count=0; // Turn off lights in case we have none - DX8Wrapper::Set_Light(0,nullptr); - DX8Wrapper::Set_Light(1,nullptr); - DX8Wrapper::Set_Light(2,nullptr); - DX8Wrapper::Set_Light(3,nullptr); + g_renderBackend->Clear_Light(0); + g_renderBackend->Clear_Light(1); + g_renderBackend->Clear_Light(2); + g_renderBackend->Clear_Light(3); // (gth) WWShade only works with light environments. We need to upgrade LightEnvironment to // support real point lights, etc. It will likely just evolve into "the n most important" lights @@ -570,7 +576,7 @@ void SimpleSceneClass::Customized_Render(RenderInfoClass & rinfo) { if (count<4) { - DX8Wrapper::Set_Light(count,*(LightClass*)it.Peek_Obj()); + g_renderBackend->Set_Light(count,*(LightClass*)it.Peek_Obj()); } else { // Simple scene only supports 4 global lights @@ -596,6 +602,10 @@ void SimpleSceneClass::Customized_Render(RenderInfoClass & rinfo) rinfo.light_environment=&lenv; } + static const bool s_probeNoSceneObjectRender = GgcFlags::Enabled(GgcFlag_ProbeNoSceneObjectRender); + if (s_probeNoSceneObjectRender) { + return; + } // loop through all render objects in the list: for (it.First(&RenderList); !it.Is_Done(); it.Next()) { diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/shader.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/shader.cpp index ec878aca073..1ece5c1c110 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/shader.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/shader.cpp @@ -41,16 +41,18 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "shader.h" +#include "ww3d.h" #include "w3d_file.h" #include "WWDebug/wwdebug.h" -#include "WW3D2/dx8wrapper.h" -#include "WW3D2/dx8caps.h" +#include "WW3D2/RenderBackend.h" +#include "WW3D2/IRenderBackend.h" +static const unsigned char kDefaultAlphaTestReference = 0x60; + bool ShaderClass::ShaderDirty=true; unsigned long ShaderClass::CurrentShader=0; -unsigned long _PolygonCullMode = D3DCULL_CW; - +static CullMode _PolygonCullMode = RB_CULL_CW; /* ** Definitions of the preset shaders: @@ -365,32 +367,32 @@ class Blend { public: - Blend(D3DBLEND f, bool ab) + Blend(BlendFactor f, bool ab) { func = f; useAlpha = ab; } - D3DBLEND func; + BlendFactor func; bool useAlpha; }; const Blend srcBlendLUT[ShaderClass::SRCBLEND_MAX] = { - Blend(D3DBLEND_ZERO, false), - Blend(D3DBLEND_ONE, false), - Blend(D3DBLEND_SRCALPHA, true), - Blend(D3DBLEND_DESTCOLOR, true) + Blend(RB_BLEND_ZERO, false), + Blend(RB_BLEND_ONE, false), + Blend(RB_BLEND_SRC_ALPHA, true), + Blend(RB_BLEND_INV_SRC_ALPHA, true) }; const Blend dstBlendLUT[ShaderClass::DSTBLEND_MAX] = { - Blend(D3DBLEND_ZERO, false), - Blend(D3DBLEND_ONE, false), - Blend(D3DBLEND_SRCCOLOR, false), - Blend(D3DBLEND_INVSRCCOLOR, false), - Blend(D3DBLEND_SRCALPHA, true), - Blend(D3DBLEND_INVSRCALPHA, true) + Blend(RB_BLEND_ZERO, false), + Blend(RB_BLEND_ONE, false), + Blend(RB_BLEND_SRC_COLOR, false), + Blend(RB_BLEND_INV_SRC_COLOR, false), + Blend(RB_BLEND_SRC_ALPHA, true), + Blend(RB_BLEND_INV_SRC_ALPHA, true) }; @@ -410,7 +412,9 @@ void ShaderClass::Apply() { unsigned long diff; - unsigned int TextureOpCaps=DX8Wrapper::Get_Current_Caps()->Get_DX8_Caps().TextureOpCaps; + auto supports_texture_op = [](RenderBackendTextureOpCapability capability) -> bool { + return g_renderBackend && g_renderBackend->Supports_Texture_Op(capability); + }; if (ShaderDirty) { @@ -430,19 +434,19 @@ void ShaderClass::Apply() if(diff & (ShaderClass::MASK_COLORMASK | ShaderClass::MASK_SRCBLEND | ShaderClass::MASK_DSTBLEND | ShaderClass::MASK_ALPHATEST)) { - ULONG planeMask = 0xffffff; + unsigned long planeMask = 0xffffff; if(Get_Color_Mask() != ShaderClass::COLOR_WRITE_ENABLE) planeMask = 0; - D3DBLEND sf; - D3DBLEND df; + BlendFactor sf; + BlendFactor df; bool blendAlpha = false; if(!planeMask) { - sf = D3DBLEND_ZERO; - df = D3DBLEND_ONE; + sf = RB_BLEND_ZERO; + df = RB_BLEND_ONE; } else { @@ -452,36 +456,33 @@ void ShaderClass::Apply() blendAlpha |= dstBlendLUT[ int(Get_Dst_Blend_Func()) ].useAlpha; } - BOOL blendOn = FALSE; + bool blendOn = false; - if(sf != D3DBLEND_ONE || df != D3DBLEND_ZERO) + if(sf != RB_BLEND_ONE || df != RB_BLEND_ZERO) { - DX8Wrapper::Set_DX8_Render_State(D3DRS_SRCBLEND,sf); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DESTBLEND,df); - blendOn = TRUE; + g_renderBackend->Set_Blend_Factors(sf, df); + blendOn = true; } - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHABLENDENABLE,blendOn); + g_renderBackend->Set_Alpha_Blend_Enable(blendOn); - BOOL alphaTest = FALSE; + bool alphaTest = false; if(Get_Alpha_Test() == ShaderClass::ALPHATEST_ENABLE) { - unsigned char alphareference = 0x60; // Alpha reference value that produces best results with mip-mapped textures. + unsigned char alphareference = kDefaultAlphaTestReference; - if(sf == D3DBLEND_INVSRCALPHA) + if(sf == RB_BLEND_INV_SRC_ALPHA) { - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,0xff - alphareference); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_LESSEQUAL); + g_renderBackend->Set_Alpha_Test(true, 0xff - alphareference, RB_CMP_LESS_EQUAL); } else { - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAREF,alphareference); - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHAFUNC,D3DCMP_GREATEREQUAL); + g_renderBackend->Set_Alpha_Test(true, alphareference, RB_CMP_GREATER_EQUAL); } blendAlpha = true; - alphaTest = TRUE; + alphaTest = true; } - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE,alphaTest); + g_renderBackend->Set_Alpha_Test_Enable(alphaTest); diff &= ~(ShaderClass::MASK_COLORMASK | ShaderClass::MASK_SRCBLEND | ShaderClass::MASK_DSTBLEND | ShaderClass::MASK_ALPHATEST); if(!diff) @@ -492,39 +493,39 @@ void ShaderClass::Apply() { // Whenever fog is enabled or disabled, the entire shader is invalidated. This is why we // can defer the "fog enabled" check inside the "fog settings changed" check. - if (DX8Wrapper::Get_Current_Caps()->Is_Fog_Allowed() && DX8Wrapper::Get_Fog_Enable()) { + if (g_renderBackend && g_renderBackend->Supports_Fog() && g_renderBackend->Get_Fog_Enable()) { - BOOL fm = FALSE; - D3DCOLOR fogColor = DX8Wrapper::Get_Fog_Color(); + bool fm = false; + unsigned int fogColor = g_renderBackend->Get_Fog_Color(); switch(Get_Fog_Func()) { case ShaderClass::FOG_ENABLE: - fm = TRUE; + fm = true; break; case ShaderClass::FOG_SCALE_FRAGMENT: fogColor = 0; - fm = TRUE; + fm = true; break; case ShaderClass::FOG_WHITE: fogColor = 0xffffff; - fm = TRUE; + fm = true; break; case ShaderClass::FOG_DISABLE: - fm = FALSE; + fm = false; break; } - DX8Wrapper::Set_DX8_Render_State(D3DRS_FOGENABLE,fm); + g_renderBackend->Set_Fog_Enable(fm); - if(fm) - { - DX8Wrapper::Set_DX8_Render_State(D3DRS_FOGCOLOR,fogColor); - } + if(fm) + { + g_renderBackend->Set_Fog_Color(fogColor); + } - } else { - DX8Wrapper::Set_DX8_Render_State(D3DRS_FOGENABLE,FALSE); - } + } else { + g_renderBackend->Set_Fog_Enable(false); + } diff &= ~(ShaderClass::MASK_FOG); if(!diff) @@ -533,24 +534,23 @@ void ShaderClass::Apply() // Defaults - D3DTEXTUREOP PricOp = D3DTOP_SELECTARG1; - DWORD PricArg1 = D3DTA_DIFFUSE; - DWORD PricArg2 = D3DTA_DIFFUSE; + RenderBackendTextureOperation PricOp = RB_TEXOP_SELECTARG1; + RenderBackendTextureArgument PricArg1 = RB_TEXARG_DIFFUSE; + RenderBackendTextureArgument PricArg2 = RB_TEXARG_DIFFUSE; - D3DTEXTUREOP PriaOp = D3DTOP_SELECTARG1; - DWORD PriaArg1 = D3DTA_DIFFUSE; - DWORD PriaArg2 = D3DTA_DIFFUSE; + RenderBackendTextureOperation PriaOp = RB_TEXOP_SELECTARG1; + RenderBackendTextureArgument PriaArg1 = RB_TEXARG_DIFFUSE; + RenderBackendTextureArgument PriaArg2 = RB_TEXARG_DIFFUSE; - D3DTEXTUREOP SeccOp = D3DTOP_DISABLE; - DWORD SeccArg1 = D3DTA_TEXTURE; - DWORD SeccArg2 = D3DTA_CURRENT; + RenderBackendTextureOperation SeccOp = RB_TEXOP_DISABLE; + RenderBackendTextureArgument SeccArg1 = RB_TEXARG_TEXTURE; + RenderBackendTextureArgument SeccArg2 = RB_TEXARG_CURRENT; - D3DTEXTUREOP SecaOp = D3DTOP_DISABLE; - DWORD SecaArg1 = D3DTA_TEXTURE; - DWORD SecaArg2 = D3DTA_CURRENT; + RenderBackendTextureOperation SecaOp = RB_TEXOP_DISABLE; + RenderBackendTextureArgument SecaArg1 = RB_TEXARG_TEXTURE; + RenderBackendTextureArgument SecaArg2 = RB_TEXARG_CURRENT; - bool voodoo3=(DX8Wrapper::Get_Current_Caps()->Get_Vendor()==DX8Caps::VENDOR_3DFX) && - (DX8Wrapper::Get_Current_Caps()->Get_Device()==DX8Caps::DEVICE_3DFX_VOODOO_3); + bool voodoo3 = g_renderBackend && g_renderBackend->Is_Legacy_Voodoo3(); int pri_mask=ShaderClass::MASK_PRIGRADIENT|ShaderClass::MASK_TEXTURING; int sec_mask=ShaderClass::MASK_POSTDETAILALPHAFUNC|ShaderClass::MASK_POSTDETAILCOLORFUNC|ShaderClass::MASK_TEXTURING; @@ -569,86 +569,86 @@ void ShaderClass::Apply() { case ShaderClass::GRADIENT_DISABLE: //Decal - PricOp = D3DTOP_SELECTARG1; - PricArg1 = D3DTA_TEXTURE; - PricArg2 = D3DTA_CURRENT; - PriaOp = D3DTOP_SELECTARG1; - PriaArg1 = D3DTA_TEXTURE; - PriaArg2 = D3DTA_CURRENT; + PricOp = RB_TEXOP_SELECTARG1; + PricArg1 = RB_TEXARG_TEXTURE; + PricArg2 = RB_TEXARG_CURRENT; + PriaOp = RB_TEXOP_SELECTARG1; + PriaArg1 = RB_TEXARG_TEXTURE; + PriaArg2 = RB_TEXARG_CURRENT; break; default: case ShaderClass::GRADIENT_MODULATE: - PricOp = D3DTOP_MODULATE; - PricArg1 = D3DTA_TEXTURE; - PricArg2 = D3DTA_DIFFUSE; - PriaOp = D3DTOP_MODULATE; - PriaArg1 = D3DTA_TEXTURE; - PriaArg2 = D3DTA_DIFFUSE; + PricOp = RB_TEXOP_MODULATE; + PricArg1 = RB_TEXARG_TEXTURE; + PricArg2 = RB_TEXARG_DIFFUSE; + PriaOp = RB_TEXOP_MODULATE; + PriaArg1 = RB_TEXARG_TEXTURE; + PriaArg2 = RB_TEXARG_DIFFUSE; break; case ShaderClass::GRADIENT_ADD: //Modulate Alpha - if(!(TextureOpCaps & D3DTEXOPCAPS_ADD)) - PricOp = D3DTOP_MODULATE; + if(!supports_texture_op(RB_TEXTURE_OP_ADD)) + PricOp = RB_TEXOP_MODULATE; else - PricOp = D3DTOP_ADD; - PricArg1 = D3DTA_TEXTURE; - PricArg2 = D3DTA_DIFFUSE; - PriaOp = D3DTOP_MODULATE; - PriaArg1 = D3DTA_TEXTURE; - PriaArg2 = D3DTA_DIFFUSE; + PricOp = RB_TEXOP_ADD; + PricArg1 = RB_TEXARG_TEXTURE; + PricArg2 = RB_TEXARG_DIFFUSE; + PriaOp = RB_TEXOP_MODULATE; + PriaArg1 = RB_TEXARG_TEXTURE; + PriaArg2 = RB_TEXARG_DIFFUSE; break; // Bump map is a hack currently as we only have two stages in use! case ShaderClass::GRADIENT_BUMPENVMAP: - if(TextureOpCaps & D3DTEXOPCAPS_BUMPENVMAP) + if(supports_texture_op(RB_TEXTURE_OP_BUMPENVMAP)) { - PricOp=D3DTOP_BUMPENVMAP; - PricArg1=D3DTA_TEXTURE; - PricArg2=D3DTA_DIFFUSE; - PriaOp = D3DTOP_DISABLE; - PriaArg1 = D3DTA_TEXTURE; - PriaArg2 = D3DTA_CURRENT; + PricOp=RB_TEXOP_BUMPENVMAP; + PricArg1=RB_TEXARG_TEXTURE; + PricArg2=RB_TEXARG_DIFFUSE; + PriaOp = RB_TEXOP_DISABLE; + PriaArg1 = RB_TEXARG_TEXTURE; + PriaArg2 = RB_TEXARG_CURRENT; } else { - PricOp = D3DTOP_SELECTARG1; - PricArg1 = D3DTA_DIFFUSE; - PricArg2 = D3DTA_DIFFUSE; - PriaOp = D3DTOP_SELECTARG1; - PriaArg1 = D3DTA_DIFFUSE; - PriaArg2 = D3DTA_DIFFUSE; + PricOp = RB_TEXOP_SELECTARG1; + PricArg1 = RB_TEXARG_DIFFUSE; + PricArg2 = RB_TEXARG_DIFFUSE; + PriaOp = RB_TEXOP_SELECTARG1; + PriaArg1 = RB_TEXARG_DIFFUSE; + PriaArg2 = RB_TEXARG_DIFFUSE; } break; // Bump map is a hack currently as we only have two stages in use! case ShaderClass::GRADIENT_BUMPENVMAPLUMINANCE: - if(TextureOpCaps & D3DTEXOPCAPS_BUMPENVMAPLUMINANCE) + if(supports_texture_op(RB_TEXTURE_OP_BUMPENVMAPLUMINANCE)) { - PricOp=D3DTOP_BUMPENVMAPLUMINANCE; - PricArg1=D3DTA_TEXTURE; - PricArg2=D3DTA_DIFFUSE; - PriaOp = D3DTOP_DISABLE; - PriaArg1 = D3DTA_TEXTURE; - PriaArg2 = D3DTA_CURRENT; + PricOp=RB_TEXOP_BUMPENVMAPLUMINANCE; + PricArg1=RB_TEXARG_TEXTURE; + PricArg2=RB_TEXARG_DIFFUSE; + PriaOp = RB_TEXOP_DISABLE; + PriaArg1 = RB_TEXARG_TEXTURE; + PriaArg2 = RB_TEXARG_CURRENT; } else { - PricOp = D3DTOP_SELECTARG1; - PricArg1 = D3DTA_DIFFUSE; - PricArg2 = D3DTA_DIFFUSE; - PriaOp = D3DTOP_SELECTARG1; - PriaArg1 = D3DTA_DIFFUSE; - PriaArg2 = D3DTA_DIFFUSE; + PricOp = RB_TEXOP_SELECTARG1; + PricArg1 = RB_TEXARG_DIFFUSE; + PricArg2 = RB_TEXARG_DIFFUSE; + PriaOp = RB_TEXOP_SELECTARG1; + PriaArg1 = RB_TEXARG_DIFFUSE; + PriaArg2 = RB_TEXARG_DIFFUSE; } break; case ShaderClass::GRADIENT_MODULATE2X: //Modulate Alpha - if(!(TextureOpCaps & D3DTOP_MODULATE2X)) - PricOp = D3DTOP_MODULATE; + if(!supports_texture_op(RB_TEXTURE_OP_MODULATE2X)) + PricOp = RB_TEXOP_MODULATE; else - PricOp = D3DTOP_MODULATE2X; - PricArg1 = D3DTA_TEXTURE; - PricArg2 = D3DTA_DIFFUSE; - PriaOp = D3DTOP_MODULATE; - PriaArg1 = D3DTA_TEXTURE; - PriaArg2 = D3DTA_DIFFUSE; + PricOp = RB_TEXOP_MODULATE2X; + PricArg1 = RB_TEXARG_TEXTURE; + PricArg2 = RB_TEXARG_DIFFUSE; + PriaOp = RB_TEXOP_MODULATE; + PriaArg1 = RB_TEXARG_TEXTURE; + PriaArg2 = RB_TEXARG_DIFFUSE; break; } @@ -658,29 +658,29 @@ void ShaderClass::Apply() switch(Get_Primary_Gradient()) { case ShaderClass::GRADIENT_DISABLE: - PricOp = D3DTOP_DISABLE; - PricArg1 = D3DTA_TEXTURE; - PricArg2 = D3DTA_CURRENT; - PriaOp = D3DTOP_DISABLE; - PriaArg1 = D3DTA_TEXTURE; - PriaArg2 = D3DTA_CURRENT; + PricOp = RB_TEXOP_DISABLE; + PricArg1 = RB_TEXARG_TEXTURE; + PricArg2 = RB_TEXARG_CURRENT; + PriaOp = RB_TEXOP_DISABLE; + PriaArg1 = RB_TEXARG_TEXTURE; + PriaArg2 = RB_TEXARG_CURRENT; break; default: case ShaderClass::GRADIENT_MODULATE: - PricOp = D3DTOP_SELECTARG2; - PricArg1 = D3DTA_TEXTURE; - PricArg2 = D3DTA_DIFFUSE; - PriaOp = D3DTOP_SELECTARG2; - PriaArg1 = D3DTA_TEXTURE; - PriaArg2 = D3DTA_DIFFUSE; + PricOp = RB_TEXOP_SELECTARG2; + PricArg1 = RB_TEXARG_TEXTURE; + PricArg2 = RB_TEXARG_DIFFUSE; + PriaOp = RB_TEXOP_SELECTARG2; + PriaArg1 = RB_TEXARG_TEXTURE; + PriaArg2 = RB_TEXARG_DIFFUSE; break; case ShaderClass::GRADIENT_ADD: - PricOp = D3DTOP_SELECTARG2; - PricArg1 = D3DTA_TEXTURE; - PricArg2 = D3DTA_DIFFUSE; - PriaOp = D3DTOP_SELECTARG2; - PriaArg1 = D3DTA_TEXTURE; - PriaArg2 = D3DTA_DIFFUSE; + PricOp = RB_TEXOP_SELECTARG2; + PricArg1 = RB_TEXARG_TEXTURE; + PricArg2 = RB_TEXARG_DIFFUSE; + PriaOp = RB_TEXOP_SELECTARG2; + PriaArg1 = RB_TEXARG_TEXTURE; + PriaArg2 = RB_TEXARG_DIFFUSE; break; } } @@ -697,11 +697,11 @@ void ShaderClass::Apply() break; case ShaderClass::DETAILCOLOR_DETAIL: - if(TextureOpCaps & D3DTEXOPCAPS_SELECTARG1) + if(supports_texture_op(RB_TEXTURE_OP_SELECTARG1)) { - SeccOp = D3DTOP_SELECTARG1; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; + SeccOp = RB_TEXOP_SELECTARG1; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; } else { SNAPSHOT_SAY(("Warning: Using unsupported texture op: SELECTARG1")); @@ -709,11 +709,11 @@ void ShaderClass::Apply() break; case ShaderClass::DETAILCOLOR_SCALE: - if(TextureOpCaps & D3DTEXOPCAPS_MODULATE) + if(supports_texture_op(RB_TEXTURE_OP_MODULATE)) { - SeccOp = D3DTOP_MODULATE; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; + SeccOp = RB_TEXOP_MODULATE; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; } else { SNAPSHOT_SAY(("Warning: Using unsupported texture op: MODULATE")); @@ -721,15 +721,15 @@ void ShaderClass::Apply() break; case ShaderClass::DETAILCOLOR_INVSCALE: - if(TextureOpCaps & D3DTEXOPCAPS_ADDSMOOTH) + if(supports_texture_op(RB_TEXTURE_OP_ADDSMOOTH)) { - SeccOp = D3DTOP_ADDSMOOTH; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; - } else if(TextureOpCaps & D3DTEXOPCAPS_ADD) { - SeccOp = D3DTOP_ADD; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; + SeccOp = RB_TEXOP_ADDSMOOTH; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; + } else if(supports_texture_op(RB_TEXTURE_OP_ADD)) { + SeccOp = RB_TEXOP_ADD; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; } else { SNAPSHOT_SAY(("Warning: Using unsupported texture op: ADDSMOOTH")); @@ -737,11 +737,11 @@ void ShaderClass::Apply() break; case ShaderClass::DETAILCOLOR_ADD: - if(TextureOpCaps & D3DTEXOPCAPS_ADD) + if(supports_texture_op(RB_TEXTURE_OP_ADD)) { - SeccOp = D3DTOP_ADD; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; + SeccOp = RB_TEXOP_ADD; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; } else { SNAPSHOT_SAY(("Warning: Using unsupported texture op: ADD")); @@ -749,11 +749,11 @@ void ShaderClass::Apply() break; case ShaderClass::DETAILCOLOR_SUB: - if(TextureOpCaps & D3DTEXOPCAPS_SUBTRACT) + if(supports_texture_op(RB_TEXTURE_OP_SUBTRACT)) { - SeccOp = D3DTOP_SUBTRACT; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; + SeccOp = RB_TEXOP_SUBTRACT; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; } else { SNAPSHOT_SAY(("Warning: Using unsupported texture op: SUBTRACT")); @@ -761,11 +761,11 @@ void ShaderClass::Apply() break; case ShaderClass::DETAILCOLOR_SUBR: - if(TextureOpCaps & D3DTEXOPCAPS_SUBTRACT) + if(supports_texture_op(RB_TEXTURE_OP_SUBTRACT)) { - SeccOp = D3DTOP_SUBTRACT; - SeccArg1 = D3DTA_CURRENT; - SeccArg2 = D3DTA_TEXTURE; + SeccOp = RB_TEXOP_SUBTRACT; + SeccArg1 = RB_TEXARG_CURRENT; + SeccArg2 = RB_TEXARG_TEXTURE; } else { SNAPSHOT_SAY(("Warning: Using unsupported texture op: SUBTRACT")); @@ -773,11 +773,11 @@ void ShaderClass::Apply() break; case ShaderClass::DETAILCOLOR_BLEND: - if(TextureOpCaps & D3DTEXOPCAPS_BLENDTEXTUREALPHA) + if(supports_texture_op(RB_TEXTURE_OP_BLENDTEXTUREALPHA)) { - SeccOp = D3DTOP_BLENDTEXTUREALPHA; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; + SeccOp = RB_TEXOP_BLENDTEXTUREALPHA; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; } else { SNAPSHOT_SAY(("Warning: Using unsupported texture op: BLENDTEXTUREALPHA")); @@ -785,11 +785,11 @@ void ShaderClass::Apply() break; case ShaderClass::DETAILCOLOR_DETAILBLEND: - if(TextureOpCaps & D3DTEXOPCAPS_BLENDCURRENTALPHA) + if(supports_texture_op(RB_TEXTURE_OP_BLENDCURRENTALPHA)) { - SeccOp = D3DTOP_BLENDCURRENTALPHA; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; + SeccOp = RB_TEXOP_BLENDCURRENTALPHA; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; } else { SNAPSHOT_SAY(("Warning: Using unsupported texture op: BLENDCURRENTALPHA")); @@ -797,46 +797,46 @@ void ShaderClass::Apply() break; case ShaderClass::DETAILCOLOR_ADDSIGNED: - if (TextureOpCaps & D3DTEXOPCAPS_ADDSIGNED) { - SeccOp = D3DTOP_ADDSIGNED; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; - } else if (TextureOpCaps & D3DTEXOPCAPS_ADD) { - SeccOp = D3DTOP_ADD; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; + if (supports_texture_op(RB_TEXTURE_OP_ADDSIGNED)) { + SeccOp = RB_TEXOP_ADDSIGNED; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; + } else if (supports_texture_op(RB_TEXTURE_OP_ADD)) { + SeccOp = RB_TEXOP_ADD; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; } else { SNAPSHOT_SAY(("Warning: Using unsupported texture op: ADDSIGNED")); } break; case ShaderClass::DETAILCOLOR_ADDSIGNED2X: - if (TextureOpCaps & D3DTEXOPCAPS_ADDSIGNED2X) { - SeccOp = D3DTOP_ADDSIGNED2X; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; - } else if (TextureOpCaps & D3DTEXOPCAPS_ADDSIGNED) { - SeccOp = D3DTOP_ADDSIGNED; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; - } else if (TextureOpCaps & D3DTEXOPCAPS_ADD) { - SeccOp = D3DTOP_ADD; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; + if (supports_texture_op(RB_TEXTURE_OP_ADDSIGNED2X)) { + SeccOp = RB_TEXOP_ADDSIGNED2X; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; + } else if (supports_texture_op(RB_TEXTURE_OP_ADDSIGNED)) { + SeccOp = RB_TEXOP_ADDSIGNED; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; + } else if (supports_texture_op(RB_TEXTURE_OP_ADD)) { + SeccOp = RB_TEXOP_ADD; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; } else { SNAPSHOT_SAY(("Warning: Using unsupported texture op: ADDSIGNED2X")); } break; case ShaderClass::DETAILCOLOR_SCALE2X: - if(TextureOpCaps & D3DTEXOPCAPS_MODULATE2X) { - SeccOp = D3DTOP_MODULATE2X; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; - } else if(TextureOpCaps & D3DTEXOPCAPS_MODULATE) { - SeccOp = D3DTOP_MODULATE; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; + if(supports_texture_op(RB_TEXTURE_OP_MODULATE2X)) { + SeccOp = RB_TEXOP_MODULATE2X; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; + } else if(supports_texture_op(RB_TEXTURE_OP_MODULATE)) { + SeccOp = RB_TEXOP_MODULATE; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; } else { SNAPSHOT_SAY(("Warning: Using unsupported texture op: MODULATE2X")); @@ -844,14 +844,14 @@ void ShaderClass::Apply() break; case ShaderClass::DETAILCOLOR_MODALPHAADDCOLOR: - if (DX8Wrapper::Get_Current_Caps()->Support_ModAlphaAddClr()) { - SeccOp = D3DTOP_MODULATEALPHA_ADDCOLOR; - SeccArg1 = D3DTA_CURRENT; - SeccArg2 = D3DTA_TEXTURE; - } else if (TextureOpCaps & D3DTEXOPCAPS_ADD) { - SeccOp = D3DTOP_ADD; - SeccArg1 = D3DTA_TEXTURE; - SeccArg2 = D3DTA_CURRENT; + if (supports_texture_op(RB_TEXTURE_OP_MODULATEALPHA_ADDCOLOR)) { + SeccOp = RB_TEXOP_MODULATEALPHA_ADDCOLOR; + SeccArg1 = RB_TEXARG_CURRENT; + SeccArg2 = RB_TEXARG_TEXTURE; + } else if (supports_texture_op(RB_TEXTURE_OP_ADD)) { + SeccOp = RB_TEXOP_ADD; + SeccArg1 = RB_TEXARG_TEXTURE; + SeccArg2 = RB_TEXARG_CURRENT; } else { SNAPSHOT_SAY(("Warning: Using unsupported texture op: MODULATEALPHA_ADDCOLOR")); } @@ -865,11 +865,11 @@ void ShaderClass::Apply() break; case ShaderClass::DETAILALPHA_DETAIL: - if(TextureOpCaps & D3DTEXOPCAPS_SELECTARG1) + if(supports_texture_op(RB_TEXTURE_OP_SELECTARG1)) { - SecaOp = D3DTOP_SELECTARG1; - SecaArg1 = D3DTA_TEXTURE; - SecaArg2 = D3DTA_CURRENT; + SecaOp = RB_TEXOP_SELECTARG1; + SecaArg1 = RB_TEXARG_TEXTURE; + SecaArg2 = RB_TEXARG_CURRENT; } else { SNAPSHOT_SAY(("Warning: Using unsupported texture op: SELECTARG1")); @@ -877,11 +877,11 @@ void ShaderClass::Apply() break; case ShaderClass::DETAILALPHA_SCALE: - if(TextureOpCaps & D3DTEXOPCAPS_MODULATE) + if(supports_texture_op(RB_TEXTURE_OP_MODULATE)) { - SecaOp = D3DTOP_MODULATE; - SecaArg1 = D3DTA_TEXTURE; - SecaArg2 = D3DTA_CURRENT; + SecaOp = RB_TEXOP_MODULATE; + SecaArg1 = RB_TEXARG_TEXTURE; + SecaArg2 = RB_TEXARG_CURRENT; } else { SNAPSHOT_SAY(("Warning: Using unsupported texture op: MODULATE")); @@ -889,11 +889,11 @@ void ShaderClass::Apply() break; case ShaderClass::DETAILALPHA_INVSCALE: - if(TextureOpCaps & D3DTEXOPCAPS_ADDSMOOTH) + if(supports_texture_op(RB_TEXTURE_OP_ADDSMOOTH)) { - SecaOp = D3DTOP_ADDSMOOTH; - SecaArg1 = D3DTA_TEXTURE; - SecaArg2 = D3DTA_CURRENT; + SecaOp = RB_TEXOP_ADDSMOOTH; + SecaArg1 = RB_TEXARG_TEXTURE; + SecaArg2 = RB_TEXARG_CURRENT; } else { SNAPSHOT_SAY(("Warning: Using unsupported texture op: ADDSMOOTH")); @@ -902,12 +902,12 @@ void ShaderClass::Apply() } // if color is enabled and alpha is disabled set to pass alpha through - if ((SeccOp!=D3DTOP_DISABLE) && (SecaOp==D3DTOP_DISABLE)) { - SecaOp = D3DTOP_SELECTARG2; - SecaArg2 = D3DTA_CURRENT; - } else if ((SeccOp==D3DTOP_DISABLE) && (SecaOp!=D3DTOP_DISABLE)) { - SeccOp = D3DTOP_SELECTARG2; - SeccArg2 = D3DTA_CURRENT; + if ((SeccOp!=RB_TEXOP_DISABLE) && (SecaOp==RB_TEXOP_DISABLE)) { + SecaOp = RB_TEXOP_SELECTARG2; + SecaArg2 = RB_TEXARG_CURRENT; + } else if ((SeccOp==RB_TEXOP_DISABLE) && (SecaOp!=RB_TEXOP_DISABLE)) { + SeccOp = RB_TEXOP_SELECTARG2; + SeccArg2 = RB_TEXARG_CURRENT; } } } @@ -918,47 +918,46 @@ void ShaderClass::Apply() if (diff & pri_mask) { // for voodoo3 supported blend modes, the stage 0 color and alpha are both diffuse // or both not, so we can check for color diffuse only - if ( voodoo3 && (PricArg2==D3DTA_DIFFUSE) && - ( (SecaOp!=D3DTOP_DISABLE) || (SeccOp!=D3DTOP_DISABLE) ) + if ( voodoo3 && (PricArg2==RB_TEXARG_DIFFUSE) && + ( (SecaOp!=RB_TEXOP_DISABLE) || (SeccOp!=RB_TEXOP_DISABLE) ) ) { // Special Voodoo3 code // If stage 0 has a diffuse input // and stage 1 has an input put the diffuse in stage 2 - DWORD tex_arg=D3DTA_CURRENT; + RenderBackendTextureArgument tex_arg=RB_TEXARG_CURRENT; if(Get_Texturing() == ShaderClass::TEXTURING_ENABLE) { - tex_arg=D3DTA_TEXTURE; + tex_arg=RB_TEXARG_TEXTURE; } // this is for the bad case of using // stage 0 for diffuse only - if ((PricOp==D3DTOP_SELECTARG1)&&(PricArg1==D3DTA_DIFFUSE)) { + if ((PricOp==RB_TEXOP_SELECTARG1)&&(PricArg1==RB_TEXARG_DIFFUSE)) { WWDEBUG_SAY(("Wasted Stage 0 in shader-vertex diffuse only")); // set stage 0 to disable - DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLOROP,D3DTOP_DISABLE); - DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_ALPHAOP,D3DTOP_DISABLE); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_DISABLE); // set stage 1 to accept diffuse - if (SeccArg2==D3DTA_CURRENT) SeccArg2=D3DTA_DIFFUSE; - if (SecaArg2==D3DTA_CURRENT) SecaArg2=D3DTA_DIFFUSE; + if (SeccArg2==RB_TEXARG_CURRENT) SeccArg2=RB_TEXARG_DIFFUSE; + if (SecaArg2==RB_TEXARG_CURRENT) SecaArg2=RB_TEXARG_DIFFUSE; // and nuke stage 2 kill_stage_2=true; } else { // set stage 0 to pass through what it needs - DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLOROP,D3DTOP_SELECTARG1); - DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLORARG1,tex_arg); - DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_ALPHAOP,D3DTOP_SELECTARG1); - DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_ALPHAARG1,tex_arg); - - // set stage 2 to do the diffuse op - // bypass the wrapper since it only supports 2 texture stages - DX8CALL(SetTextureStageState(2,D3DTSS_COLOROP,PricOp)); - DX8CALL(SetTextureStageState(2,D3DTSS_COLORARG1,D3DTA_CURRENT)); - DX8CALL(SetTextureStageState(2,D3DTSS_COLORARG2,D3DTA_DIFFUSE)); - DX8CALL(SetTextureStageState(2,D3DTSS_ALPHAOP,PriaOp)); - DX8CALL(SetTextureStageState(2,D3DTSS_ALPHAARG1,D3DTA_CURRENT)); - DX8CALL(SetTextureStageState(2,D3DTSS_ALPHAARG2,D3DTA_DIFFUSE)); - DX8CALL(SetTextureStageState(2,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_PASSTHRU)); - DX8CALL(SetTexture(2,nullptr)); + g_renderBackend->Set_Texture_Color_Operation(0, RB_TEXOP_SELECTARG1); + g_renderBackend->Set_Texture_Color_Argument(0, 1, tex_arg); + g_renderBackend->Set_Texture_Alpha_Operation(0, RB_TEXOP_SELECTARG1); + g_renderBackend->Set_Texture_Alpha_Argument(0, 1, tex_arg); + + // set stage 2 to do the diffuse op + g_renderBackend->Set_Texture_Color_Operation(2, PricOp); + g_renderBackend->Set_Texture_Color_Argument(2, 1, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Color_Argument(2, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Alpha_Operation(2, PriaOp); + g_renderBackend->Set_Texture_Alpha_Argument(2, 1, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Alpha_Argument(2, 2, RB_TEXARG_DIFFUSE); + g_renderBackend->Set_Texture_Coord_Source(2, RB_TEXCOORD_MESH_UV, 0); + g_renderBackend->Bind_Texture_Immediate(2,nullptr); kill_stage_2=false; ShaderDirty=true; } @@ -968,76 +967,72 @@ void ShaderClass::Apply() #if 0 if (WW3D::Is_Coloring_Enabled()) { - cArg2=aArg2=D3DTA_TFACTOR; - cOp=aOp=D3DTOP_SELECTARG2; + cArg2=aArg2=RB_TEXARG_TFACTOR; + cOp=aOp=RB_TEXOP_SELECTARG2; } #endif - DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLOROP,PricOp); - DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLORARG1,PricArg1); - DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_COLORARG2,PricArg2); - DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_ALPHAOP,PriaOp); - DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_ALPHAARG1,PriaArg1); - DX8Wrapper::Set_DX8_Texture_Stage_State(0,D3DTSS_ALPHAARG2,PriaArg2); + g_renderBackend->Set_Texture_Color_Operation(0, PricOp); + g_renderBackend->Set_Texture_Color_Argument(0, 1, PricArg1); + g_renderBackend->Set_Texture_Color_Argument(0, 2, PricArg2); + g_renderBackend->Set_Texture_Alpha_Operation(0, PriaOp); + g_renderBackend->Set_Texture_Alpha_Argument(0, 1, PriaArg1); + g_renderBackend->Set_Texture_Alpha_Argument(0, 2, PriaArg2); kill_stage_2=true; } diff &= ~(ShaderClass::MASK_PRIGRADIENT); } if (diff & sec_mask) { - DX8Wrapper::Set_DX8_Texture_Stage_State(1,D3DTSS_COLOROP,SeccOp); - DX8Wrapper::Set_DX8_Texture_Stage_State(1,D3DTSS_COLORARG1,SeccArg1); - DX8Wrapper::Set_DX8_Texture_Stage_State(1,D3DTSS_COLORARG2,SeccArg2); - DX8Wrapper::Set_DX8_Texture_Stage_State(1,D3DTSS_ALPHAOP,SecaOp); - DX8Wrapper::Set_DX8_Texture_Stage_State(1,D3DTSS_ALPHAARG1,SecaArg1); - DX8Wrapper::Set_DX8_Texture_Stage_State(1,D3DTSS_ALPHAARG2,SecaArg2); + g_renderBackend->Set_Texture_Color_Operation(1, SeccOp); + g_renderBackend->Set_Texture_Color_Argument(1, 1, SeccArg1); + g_renderBackend->Set_Texture_Color_Argument(1, 2, SeccArg2); + g_renderBackend->Set_Texture_Alpha_Operation(1, SecaOp); + g_renderBackend->Set_Texture_Alpha_Argument(1, 1, SecaArg1); + g_renderBackend->Set_Texture_Alpha_Argument(1, 2, SecaArg2); diff &= ~(ShaderClass::MASK_POSTDETAILCOLORFUNC); diff &= ~(ShaderClass::MASK_POSTDETAILALPHAFUNC); diff &= ~(ShaderClass::MASK_TEXTURING); } - // Make sure to disable stage 2 for voodoos since we don't have state tracking for - // stage 2 - // bypass the wrapper since it only supports 2 texture stages - if (voodoo3 && kill_stage_2) { - if ((SeccOp!=D3DTOP_DISABLE)&&(SecaOp!=D3DTOP_DISABLE)) { - DX8CALL(SetTextureStageState(2,D3DTSS_COLOROP,D3DTOP_SELECTARG1)); - DX8CALL(SetTextureStageState(2,D3DTSS_COLORARG1,D3DTA_CURRENT)); - DX8CALL(SetTextureStageState(2,D3DTSS_ALPHAOP,D3DTOP_SELECTARG1)); - DX8CALL(SetTextureStageState(2,D3DTSS_ALPHAARG1,D3DTA_CURRENT)); - } else { - DX8CALL(SetTextureStageState(2,D3DTSS_COLOROP,D3DTOP_DISABLE)); - DX8CALL(SetTextureStageState(2,D3DTSS_ALPHAOP,D3DTOP_DISABLE)); + // Make sure to disable stage 2 for voodoos since stage 2 is only used + // by this compatibility path. + if (voodoo3 && kill_stage_2) { + if ((SeccOp!=RB_TEXOP_DISABLE)&&(SecaOp!=RB_TEXOP_DISABLE)) { + g_renderBackend->Set_Texture_Color_Operation(2, RB_TEXOP_SELECTARG1); + g_renderBackend->Set_Texture_Color_Argument(2, 1, RB_TEXARG_CURRENT); + g_renderBackend->Set_Texture_Alpha_Operation(2, RB_TEXOP_SELECTARG1); + g_renderBackend->Set_Texture_Alpha_Argument(2, 1, RB_TEXARG_CURRENT); + } else { + g_renderBackend->Set_Texture_Color_Operation(2, RB_TEXOP_DISABLE); + g_renderBackend->Set_Texture_Alpha_Operation(2, RB_TEXOP_DISABLE); + } + g_renderBackend->Set_Texture_Coord_Source(2, RB_TEXCOORD_MESH_UV, 0); + g_renderBackend->Bind_Texture_Immediate(2,nullptr); } - DX8CALL(SetTextureStageState(2,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_PASSTHRU)); - DX8CALL(SetTexture(2,nullptr)); - } if(!diff) return; - DX8Wrapper::Set_DX8_Render_State(D3DRS_SPECULARENABLE,BOOL(Get_Secondary_Gradient())); + g_renderBackend->Set_Specular_Enable(Get_Secondary_Gradient()); // DEPTH COMPARE FUNCTION - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZFUNC,D3DCMPFUNC(int(Get_Depth_Compare())+1)); + g_renderBackend->Set_Depth_Func(static_cast(int(Get_Depth_Compare())+1)); // DEPTH MASK - DX8Wrapper::Set_DX8_Render_State(D3DRS_ZWRITEENABLE,BOOL(Get_Depth_Mask())); + g_renderBackend->Set_Depth_Write_Enable(Get_Depth_Mask()); - // DITHERING -// DX8Wrapper::Set_DX8_Render_State(D3DRS_DITHERENABLE,BOOL(Get_Dither_Mask())); - - // CULLMODE - DX8Wrapper::Set_DX8_Render_State(D3DRS_CULLMODE,Get_Cull_Mode() ? _PolygonCullMode : D3DCULL_NONE); + // CULLMODE + g_renderBackend->Set_Cull_Mode(Get_Cull_Mode() ? _PolygonCullMode : RB_CULL_NONE); // NPATCHES if (diff&ShaderClass::MASK_NPATCHENABLE) { float level=1.0f; if (Get_NPatch_Enable()) level=float(WW3D::Get_NPatches_Level()); - DX8Wrapper::Set_DX8_Render_State(D3DRS_PATCHSEGMENTS,*((DWORD*)&level)); + g_renderBackend->Set_Patch_Segments(level); } // Enable/disable alpha test - DX8Wrapper::Set_DX8_Render_State(D3DRS_ALPHATESTENABLE,BOOL(Get_Alpha_Test())); + g_renderBackend->Set_Alpha_Test_Enable(Get_Alpha_Test() == ShaderClass::ALPHATEST_ENABLE); // Enable/disable stencil test // Not supported yet @@ -1059,9 +1054,9 @@ void ShaderClass::Apply() void ShaderClass::Invert_Backface_Culling(bool onoff) { if (onoff == true) { - _PolygonCullMode = D3DCULL_CCW; + _PolygonCullMode = RB_CULL_CCW; } else { - _PolygonCullMode = D3DCULL_CW; + _PolygonCullMode = RB_CULL_CW; } Invalidate(); } @@ -1158,7 +1153,7 @@ int ShaderClass::Guess_Sort_Level() const *=============================================================================================*/ bool ShaderClass::Is_Backface_Culling_Inverted() { - return (_PolygonCullMode == D3DCULL_CCW); + return (_PolygonCullMode == RB_CULL_CCW); } const StringClass& ShaderClass::Get_Description(StringClass& str) const @@ -1259,4 +1254,3 @@ const StringClass& ShaderClass::Get_Description(StringClass& str) const } return str; } - diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/shader.h b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/shader.h index 5dae6d0969e..cf7384ab780 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/shader.h +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/shader.h @@ -284,7 +284,7 @@ class ShaderClass inline void Reset(); - DepthCompareType Get_Depth_Compare() const { return (DepthCompareType)(ShaderBits&MASK_DEPTHCOMPARE>>SHIFT_DEPTHCOMPARE); } + DepthCompareType Get_Depth_Compare() const { return (DepthCompareType)((ShaderBits&MASK_DEPTHCOMPARE)>>SHIFT_DEPTHCOMPARE); } DepthMaskType Get_Depth_Mask() const { return (DepthMaskType)((ShaderBits&MASK_DEPTHMASK)>>SHIFT_DEPTHMASK); } ColorMaskType Get_Color_Mask() const { return (ColorMaskType)((ShaderBits&MASK_COLORMASK)>>SHIFT_COLORMASK); } DetailAlphaFuncType Get_Post_Detail_Alpha_Func() const { return (DetailAlphaFuncType)((ShaderBits&MASK_POSTDETAILALPHAFUNC)>>SHIFT_POSTDETAILALPHAFUNC); } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/vertmaterial.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/vertmaterial.cpp index 4467e88bb8f..095d19bc071 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/vertmaterial.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/vertmaterial.cpp @@ -45,40 +45,70 @@ #include "WW3D2/w3derr.h" #include "WWLib/INI.h" #include "WWLib/XSTRAW.h" -#include "WW3D2/dx8wrapper.h" +#include "WW3D2/ww3d.h" +#include "WW3D2/RenderBackend.h" static unsigned int unique=1; VertexMaterialClass* VertexMaterialClass::Presets[VertexMaterialClass::PRESET_COUNT]; -#ifdef DYN_MAT8 -class DynD3DMATERIAL8 +static RenderBackendMaterialState Make_Render_Backend_Material_State(const VertexMaterialSettings & material) { - W3DMPO_CODE(DynD3DMATERIAL8) -public: - D3DMATERIAL8 Mat; -}; -#define Material (&MaterialDyn->Mat) -#define SRCMATPTR(src) (&(src)->MaterialDyn->Mat) -#else -#define Material (MaterialOld) -#define SRCMATPTR(src) ((src)->MaterialOld) -#endif + RenderBackendMaterialState state; + state.diffuse[0] = material.Diffuse.r; + state.diffuse[1] = material.Diffuse.g; + state.diffuse[2] = material.Diffuse.b; + state.diffuse[3] = material.Diffuse.a; + state.ambient[0] = material.Ambient.r; + state.ambient[1] = material.Ambient.g; + state.ambient[2] = material.Ambient.b; + state.ambient[3] = material.Ambient.a; + state.specular[0] = material.Specular.r; + state.specular[1] = material.Specular.g; + state.specular[2] = material.Specular.b; + state.specular[3] = material.Specular.a; + state.emissive[0] = material.Emissive.r; + state.emissive[1] = material.Emissive.g; + state.emissive[2] = material.Emissive.b; + state.emissive[3] = material.Emissive.a; + state.power = material.Power; + return state; +} + +static RenderBackendMaterialColorSource Convert_Color_Source(VertexMaterialClass::ColorSourceType source) +{ + switch (source) + { + case VertexMaterialClass::COLOR1: + return RB_MATERIAL_COLOR_SOURCE_COLOR1; + case VertexMaterialClass::COLOR2: + return RB_MATERIAL_COLOR_SOURCE_COLOR2; + default: + return RB_MATERIAL_COLOR_SOURCE_MATERIAL; + } +} + +static VertexMaterialClass::ColorSourceType Normalize_Color_Source(VertexMaterialClass::ColorSourceType source) +{ + switch (source) + { + case VertexMaterialClass::COLOR1: + case VertexMaterialClass::COLOR2: + return source; + default: + return VertexMaterialClass::MATERIAL; + } +} /* ** VertexMaterialClass Implementation */ VertexMaterialClass::VertexMaterialClass(): -#ifdef DYN_MAT8 - MaterialDyn(nullptr), -#else - MaterialOld(nullptr), -#endif Flags(0), - AmbientColorSource(D3DMCS_MATERIAL), - EmissiveColorSource(D3DMCS_MATERIAL), - DiffuseColorSource(D3DMCS_MATERIAL), + AmbientColorSource(MATERIAL), + EmissiveColorSource(MATERIAL), + DiffuseColorSource(MATERIAL), UseLighting(false), UniqueID(0), CRCDirty(true) @@ -91,12 +121,7 @@ VertexMaterialClass::VertexMaterialClass(): UVSource[i] = i; } -#ifdef DYN_MAT8 - MaterialDyn=W3DNEW DynD3DMATERIAL8; -#else - MaterialOld=W3DNEW D3DMATERIAL8; -#endif - memset(Material,0,sizeof(D3DMATERIAL8)); + memset(&Material,0,sizeof(Material)); Set_Ambient(1.0f,1.0f,1.0f); Set_Diffuse(1.0f,1.0f,1.0f); @@ -104,11 +129,7 @@ VertexMaterialClass::VertexMaterialClass(): } VertexMaterialClass::VertexMaterialClass(const VertexMaterialClass & src) : -#ifdef DYN_MAT8 - MaterialDyn(nullptr), -#else - MaterialOld(nullptr), -#endif + Material(src.Material), Flags(src.Flags), AmbientColorSource(src.AmbientColorSource), EmissiveColorSource(src.EmissiveColorSource), @@ -132,12 +153,6 @@ VertexMaterialClass::VertexMaterialClass(const VertexMaterialClass & src) : UVSource[i] = src.UVSource[i]; } -#ifdef DYN_MAT8 - MaterialDyn=W3DNEW DynD3DMATERIAL8; -#else - MaterialOld=W3DNEW D3DMATERIAL8; -#endif - memcpy(Material, SRCMATPTR(&src), sizeof(D3DMATERIAL8)); } void VertexMaterialClass::Make_Unique() @@ -160,11 +175,6 @@ VertexMaterialClass::~VertexMaterialClass() } } -#ifdef DYN_MAT8 - delete MaterialDyn; -#else - delete MaterialOld; -#endif } VertexMaterialClass & VertexMaterialClass::operator = (const VertexMaterialClass &src) @@ -195,7 +205,7 @@ VertexMaterialClass & VertexMaterialClass::operator = (const VertexMaterialClass UVSource[stage] = src.UVSource[stage]; } - *Material = *SRCMATPTR(&src); + Material = src.Material; } return *this; } @@ -207,7 +217,7 @@ unsigned long VertexMaterialClass::Compute_CRC() const // don't include the name when determining whether two vertex materials match // crc = CRC_Memory(reinterpret_cast(Name.Peek_Buffer()),sizeof(char)*strlen(Name),crc); - crc = CRC_Memory(reinterpret_cast(Material),sizeof(D3DMATERIAL8),crc); + crc = CRC_Memory(reinterpret_cast(&Material),sizeof(Material),crc); crc = CRC_Memory(reinterpret_cast(&Flags),sizeof(Flags),crc); crc = CRC_Memory(reinterpret_cast(&DiffuseColorSource),sizeof(DiffuseColorSource),crc); crc = CRC_Memory(reinterpret_cast(&AmbientColorSource),sizeof(AmbientColorSource),crc); @@ -230,23 +240,23 @@ unsigned long VertexMaterialClass::Compute_CRC() const void VertexMaterialClass::Get_Ambient(Vector3 * set) const { assert(set); - *set=Vector3(Material->Ambient.r,Material->Ambient.g,Material->Ambient.b); + *set=Vector3(Material.Ambient.r,Material.Ambient.g,Material.Ambient.b); } void VertexMaterialClass::Set_Ambient(const Vector3 & color) { CRCDirty=true; - Material->Ambient.r=color.X; - Material->Ambient.g=color.Y; - Material->Ambient.b=color.Z; + Material.Ambient.r=color.X; + Material.Ambient.g=color.Y; + Material.Ambient.b=color.Z; } void VertexMaterialClass::Set_Ambient(float r,float g,float b) { CRCDirty=true; - Material->Ambient.r=r; - Material->Ambient.g=g; - Material->Ambient.b=b; + Material.Ambient.r=r; + Material.Ambient.g=g; + Material.Ambient.b=b; } // Diffuse Get and Sets @@ -254,23 +264,23 @@ void VertexMaterialClass::Set_Ambient(float r,float g,float b) void VertexMaterialClass::Get_Diffuse(Vector3 * set) const { assert(set); - *set=Vector3(Material->Diffuse.r,Material->Diffuse.g,Material->Diffuse.b); + *set=Vector3(Material.Diffuse.r,Material.Diffuse.g,Material.Diffuse.b); } void VertexMaterialClass::Set_Diffuse(const Vector3 & color) { CRCDirty=true; - Material->Diffuse.r=color.X; - Material->Diffuse.g=color.Y; - Material->Diffuse.b=color.Z; + Material.Diffuse.r=color.X; + Material.Diffuse.g=color.Y; + Material.Diffuse.b=color.Z; } void VertexMaterialClass::Set_Diffuse(float r,float g,float b) { CRCDirty=true; - Material->Diffuse.r=r; - Material->Diffuse.g=g; - Material->Diffuse.b=b; + Material.Diffuse.r=r; + Material.Diffuse.g=g; + Material.Diffuse.b=b; } // Specular Get and Sets @@ -278,23 +288,23 @@ void VertexMaterialClass::Set_Diffuse(float r,float g,float b) void VertexMaterialClass::Get_Specular(Vector3 * set) const { assert(set); - *set=Vector3(Material->Specular.r,Material->Specular.g,Material->Specular.b); + *set=Vector3(Material.Specular.r,Material.Specular.g,Material.Specular.b); } void VertexMaterialClass::Set_Specular(const Vector3 & color) { CRCDirty=true; - Material->Specular.r=color.X; - Material->Specular.g=color.Y; - Material->Specular.b=color.Z; + Material.Specular.r=color.X; + Material.Specular.g=color.Y; + Material.Specular.b=color.Z; } void VertexMaterialClass::Set_Specular(float r,float g,float b) { CRCDirty=true; - Material->Specular.r=r; - Material->Specular.g=g; - Material->Specular.b=b; + Material.Specular.r=r; + Material.Specular.g=g; + Material.Specular.b=b; } // Emissive Get and Sets @@ -302,112 +312,82 @@ void VertexMaterialClass::Set_Specular(float r,float g,float b) void VertexMaterialClass::Get_Emissive(Vector3 * set) const { assert(set); - *set=Vector3(Material->Emissive.r,Material->Emissive.g,Material->Emissive.b); + *set=Vector3(Material.Emissive.r,Material.Emissive.g,Material.Emissive.b); } void VertexMaterialClass::Set_Emissive(const Vector3 & color) { CRCDirty=true; - Material->Emissive.r=color.X; - Material->Emissive.g=color.Y; - Material->Emissive.b=color.Z; + Material.Emissive.r=color.X; + Material.Emissive.g=color.Y; + Material.Emissive.b=color.Z; } void VertexMaterialClass::Set_Emissive(float r,float g,float b) { CRCDirty=true; - Material->Emissive.r=r; - Material->Emissive.g=g; - Material->Emissive.b=b; + Material.Emissive.r=r; + Material.Emissive.g=g; + Material.Emissive.b=b; } float VertexMaterialClass::Get_Shininess() const { - return Material->Power; + return Material.Power; } void VertexMaterialClass::Set_Shininess(float shin) { CRCDirty=true; - Material->Power=shin; + Material.Power=shin; } float VertexMaterialClass::Get_Opacity() const { - return Material->Diffuse.a; + return Material.Diffuse.a; } void VertexMaterialClass::Set_Opacity(float o) { CRCDirty=true; - Material->Diffuse.a=o; + Material.Diffuse.a=o; } void VertexMaterialClass::Set_Ambient_Color_Source(ColorSourceType src) { CRCDirty=true; - switch (src) - { - case COLOR1: AmbientColorSource = D3DMCS_COLOR1; break; - case COLOR2: AmbientColorSource = D3DMCS_COLOR2; break; - default: AmbientColorSource = D3DMCS_MATERIAL; break; - } + AmbientColorSource = Normalize_Color_Source(src); } void VertexMaterialClass::Set_Emissive_Color_Source(ColorSourceType src) { CRCDirty=true; - switch (src) - { - case COLOR1: EmissiveColorSource = D3DMCS_COLOR1; break; - case COLOR2: EmissiveColorSource = D3DMCS_COLOR2; break; - default: EmissiveColorSource = D3DMCS_MATERIAL; break; - } + EmissiveColorSource = Normalize_Color_Source(src); } void VertexMaterialClass::Set_Diffuse_Color_Source(ColorSourceType src) { CRCDirty=true; - switch (src) - { - case COLOR1: DiffuseColorSource = D3DMCS_COLOR1; break; - case COLOR2: DiffuseColorSource = D3DMCS_COLOR2; break; - default: DiffuseColorSource = D3DMCS_MATERIAL; break; - } + DiffuseColorSource = Normalize_Color_Source(src); } VertexMaterialClass::ColorSourceType VertexMaterialClass::Get_Ambient_Color_Source() { - switch(AmbientColorSource) - { - case D3DMCS_COLOR1: return COLOR1; - case D3DMCS_COLOR2: return COLOR2; - default: return MATERIAL; - } + return AmbientColorSource; } VertexMaterialClass::ColorSourceType VertexMaterialClass::Get_Emissive_Color_Source() { - switch(EmissiveColorSource) - { - case D3DMCS_COLOR1: return COLOR1; - case D3DMCS_COLOR2: return COLOR2; - default: return MATERIAL; - } + return EmissiveColorSource; } VertexMaterialClass::ColorSourceType VertexMaterialClass::Get_Diffuse_Color_Source() { - switch(DiffuseColorSource) - { - case D3DMCS_COLOR1: return COLOR1; - case D3DMCS_COLOR2: return COLOR2; - default: return MATERIAL; - } + return DiffuseColorSource; } void VertexMaterialClass::Set_UV_Source(int stage,int array_index) @@ -948,23 +928,23 @@ void VertexMaterialClass::Apply() const { int i; - DX8Wrapper::Set_DX8_Material(Material); + g_renderBackend->Apply_Material_State(Make_Render_Backend_Material_State(Material)); if (WW3D::Is_Coloring_Enabled()) - DX8Wrapper::Set_DX8_Render_State(D3DRS_LIGHTING,FALSE); + g_renderBackend->Set_Lighting_Enable(false); else - DX8Wrapper::Set_DX8_Render_State(D3DRS_LIGHTING,UseLighting); - DX8Wrapper::Set_DX8_Render_State(D3DRS_AMBIENTMATERIALSOURCE,AmbientColorSource); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DIFFUSEMATERIALSOURCE,DiffuseColorSource); - DX8Wrapper::Set_DX8_Render_State(D3DRS_EMISSIVEMATERIALSOURCE,EmissiveColorSource); + g_renderBackend->Set_Lighting_Enable(UseLighting); + g_renderBackend->Set_Material_Color_Source(Convert_Color_Source(AmbientColorSource), + Convert_Color_Source(DiffuseColorSource), + Convert_Color_Source(EmissiveColorSource)); // set to default values if no mappers for (i=0; iApply(UVSource[i]); } else { - DX8Wrapper::Set_DX8_Texture_Stage_State(i,D3DTSS_TEXCOORDINDEX,D3DTSS_TCI_PASSTHRU | UVSource[i]); - DX8Wrapper::Set_DX8_Texture_Stage_State(i,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_DISABLE); + g_renderBackend->Set_Texture_Coord_Source(i, RB_TEXCOORD_MESH_UV, UVSource[i]); + g_renderBackend->Set_Texture_Transform_Mode(i, 0, false); } } } @@ -972,7 +952,7 @@ void VertexMaterialClass::Apply() const void VertexMaterialClass::Apply_Null() { int i; - static D3DMATERIAL8 default_settings = + static VertexMaterialSettings default_settings = { { 1.0f, 1.0f, 1.0f, 1.0f }, // diffuse { 1.0f, 1.0f, 1.0f, 1.0f }, // ambient @@ -981,17 +961,17 @@ void VertexMaterialClass::Apply_Null() 1.0f // power }; - DX8Wrapper::Set_DX8_Render_State(D3DRS_LIGHTING,FALSE); - DX8Wrapper::Set_DX8_Material(&default_settings); + g_renderBackend->Set_Lighting_Enable(false); + g_renderBackend->Apply_Material_State(Make_Render_Backend_Material_State(default_settings)); - DX8Wrapper::Set_DX8_Render_State(D3DRS_AMBIENTMATERIALSOURCE,D3DMCS_MATERIAL); - DX8Wrapper::Set_DX8_Render_State(D3DRS_DIFFUSEMATERIALSOURCE,D3DMCS_MATERIAL); - DX8Wrapper::Set_DX8_Render_State(D3DRS_EMISSIVEMATERIALSOURCE,D3DMCS_MATERIAL); + g_renderBackend->Set_Material_Color_Source(RB_MATERIAL_COLOR_SOURCE_MATERIAL, + RB_MATERIAL_COLOR_SOURCE_MATERIAL, + RB_MATERIAL_COLOR_SOURCE_MATERIAL); // set to default values if no mappers for (i=0; iSet_Texture_Coord_Source(i, RB_TEXCOORD_MESH_UV, i); + g_renderBackend->Set_Texture_Transform_Mode(i, 0, false); } } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/vertmaterial.h b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/vertmaterial.h index 21119028946..e6d4d833617 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/vertmaterial.h +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/vertmaterial.h @@ -47,13 +47,24 @@ class ChunkLoadClass; class ChunkSaveClass; +class DX8Wrapper; -#define DYN_MAT8 -#ifdef DYN_MAT8 -class DynD3DMATERIAL8; -#else -struct _D3DMATERIAL8; -#endif +struct VertexMaterialColor +{ + float r; + float g; + float b; + float a; +}; + +struct VertexMaterialSettings +{ + VertexMaterialColor Diffuse; + VertexMaterialColor Ambient; + VertexMaterialColor Specular; + VertexMaterialColor Emissive; + float Power; +}; /** ** VertexMaterialClass @@ -64,7 +75,7 @@ class VertexMaterialClass : public RefCountClass { W3DMPO_CODE(VertexMaterialClass) - friend DX8Wrapper; + friend class DX8Wrapper; public: /* @@ -86,9 +97,9 @@ class VertexMaterialClass : public RefCountClass }; enum ColorSourceType { - MATERIAL = 0, // D3DMCS_MATERIAL - the color source should be taken from the material setting - COLOR1, // D3DMCS_COLOR1 - the color should be taken from per-vertex color array 1 (aka D3DFVF_DIFFUSE) - COLOR2, // D3DMCS_COLOR2 - the color should be taken from per-vertex color array 2 (aka D3DFVF_SPECULAR) + MATERIAL = 0, // color source should be taken from the material setting + COLOR1, // per-vertex diffuse color array + COLOR2, // per-vertex specular color array }; enum PresetType @@ -234,17 +245,11 @@ class VertexMaterialClass : public RefCountClass void Make_Unique(); private: - // We're using the pointer instead of the actual structure - // so we don't have to include the d3d header - HY -#ifdef DYN_MAT8 - DynD3DMATERIAL8 * MaterialDyn; -#else - _D3DMATERIAL8 * MaterialOld; -#endif + VertexMaterialSettings Material; unsigned int Flags; - unsigned int AmbientColorSource; - unsigned int EmissiveColorSource; - unsigned int DiffuseColorSource; + ColorSourceType AmbientColorSource; + ColorSourceType EmissiveColorSource; + ColorSourceType DiffuseColorSource; StringClass Name; TextureMapperClass * Mapper[MeshBuilderClass::MAX_STAGES]; unsigned int UVSource[MeshBuilderClass::MAX_STAGES]; @@ -255,11 +260,11 @@ class VertexMaterialClass : public RefCountClass private: /* - ** Apply the render states to D3D + ** Apply the render states to the active render backend */ void Apply() const; /* - ** Apply the render states corresponding to a nullptr vertex material to D3D + ** Apply the render states corresponding to a nullptr vertex material */ static void Apply_Null(); unsigned long Compute_CRC() const; diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp index 5f1c1ac6692..4382d29b3df 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp @@ -109,12 +109,13 @@ #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" #include "WWLib/cpudetect.h" -#include "WW3D2/dx8texman.h" -#include "WW3D2/formconv.h" +#include "WW3D2/TextureResourceManager.h" +#include "WW3D2/dx8formatconv.h" #include "WW3D2/animatedsoundmgr.h" #include "WW3D2/static_sort_list.h" #include "WW3D2/shdlib.h" @@ -274,8 +275,16 @@ WW3DErrorType WW3D::Init(void *hwnd, char *defaultpal, bool lite) ** Initialize d3d, this also enumerates the available devices and resolutions. */ Init_D3D_To_WW3_Conversion(); - WWDEBUG_SAY(("Init DX8Wrapper")); - if (!DX8Wrapper::Init(_Hwnd, lite)) { + + // TheSuperHackers @refactor bobtista 05/06/2026 Construct the render + // backend here, at the start of WW3D init, so g_renderBackend is live for + // the entire device lifecycle. Backend lifetime is no longer owned by + // DX8Wrapper's Create_Device/Release_Device path; the per-window rendering + // context is still (re)created there via Initialize/Shutdown. + Init_Render_Backend(); + + WWDEBUG_SAY(("Init render system")); + if (!g_renderBackend->Init_Render_System(_Hwnd, lite)) { return(WW3D_ERROR_INITIALIZATION_FAILED); } WWDEBUG_SAY(("Allocate Debug Resources")); @@ -309,8 +318,8 @@ WW3DErrorType WW3D::Init(void *hwnd, char *defaultpal, bool lite) */ if (!lite) { AnimatedSoundMgrClass::Initialize (); - IsInitted = true; } + IsInitted = true; WWDEBUG_SAY(("WW3D Init completed")); return WW3D_ERROR_OK; } @@ -363,9 +372,9 @@ WW3DErrorType WW3D::Shutdown() WW3DAssetManager::Get_Instance()->Free_Assets(); } - DX8TextureManagerClass::Shutdown(); + TextureResourceManagerClass::Shutdown(); if (!Lite) { - DX8Wrapper::Shutdown(); + g_renderBackend->Shutdown_Render_System(); } /* @@ -378,6 +387,10 @@ WW3DErrorType WW3D::Shutdown() */ AnimatedSoundMgrClass::Shutdown (); + // TheSuperHackers @refactor bobtista 05/06/2026 Destroy the render backend + // last, after all device teardown, mirroring the construction in WW3D::Init. + Shutdown_Render_Backend(); + IsInitted = false; return WW3D_ERROR_OK; } @@ -397,7 +410,7 @@ WW3DErrorType WW3D::Shutdown() *=============================================================================================*/ WW3DErrorType WW3D::Set_Render_Device( const char * dev_name, int width, int height, int bits, int windowed, bool resize_window ) { - bool success = DX8Wrapper::Set_Render_Device(dev_name,width,height,bits,windowed,resize_window); + bool success = g_renderBackend->Set_Render_Device(dev_name,width,height,bits,windowed,resize_window); if (success) { return WW3D_ERROR_OK; } else { @@ -420,7 +433,7 @@ WW3DErrorType WW3D::Set_Render_Device( const char * dev_name, int width, int hei *=============================================================================================*/ WW3DErrorType WW3D::Set_Any_Render_Device() { - bool success = DX8Wrapper::Set_Any_Render_Device(); + bool success = g_renderBackend->Set_Any_Render_Device(); if (success) { return WW3D_ERROR_OK; } else { @@ -443,7 +456,7 @@ WW3DErrorType WW3D::Set_Any_Render_Device() *=============================================================================================*/ WW3DErrorType WW3D::Set_Render_Device(int dev, int width, int height, int bits, int windowed, bool resize_window, bool reset_device, bool restore_assets ) { - bool success = DX8Wrapper::Set_Render_Device(dev,width,height,bits,windowed,resize_window,reset_device, restore_assets ); + bool success = g_renderBackend->Set_Render_Device(dev,width,height,bits,windowed,resize_window,reset_device, restore_assets ); if (success) { return WW3D_ERROR_OK; } else { @@ -466,7 +479,7 @@ WW3DErrorType WW3D::Set_Render_Device(int dev, int width, int height, int bits, *=============================================================================================*/ WW3DErrorType WW3D::Set_Next_Render_Device() { - bool success = DX8Wrapper::Set_Next_Render_Device(); + bool success = g_renderBackend->Set_Next_Render_Device(); if (success) { return WW3D_ERROR_OK; } else { @@ -505,7 +518,7 @@ void *WW3D::Get_Window() *=============================================================================================*/ bool WW3D::Is_Windowed() { - return DX8Wrapper::Is_Windowed(); + return g_renderBackend->Is_Windowed(); } /*********************************************************************************************** @@ -525,7 +538,7 @@ bool WW3D::Is_Windowed() *=============================================================================================*/ WW3DErrorType WW3D::Toggle_Windowed () { - bool success = DX8Wrapper::Toggle_Windowed(); + bool success = g_renderBackend->Toggle_Windowed(); if (success) { return WW3D_ERROR_OK; } else { @@ -549,7 +562,7 @@ WW3DErrorType WW3D::Toggle_Windowed () *=============================================================================================*/ int WW3D::Get_Render_Device() { - return DX8Wrapper::Get_Render_Device(); + return g_renderBackend->Get_Render_Device(); } @@ -568,7 +581,7 @@ int WW3D::Get_Render_Device() *=============================================================================================*/ const RenderDeviceDescClass & WW3D::Get_Render_Device_Desc(int deviceidx) { - return DX8Wrapper::Get_Render_Device_Desc(deviceidx); + return g_renderBackend->Get_Render_Device_Desc(deviceidx); } @@ -588,7 +601,7 @@ const RenderDeviceDescClass & WW3D::Get_Render_Device_Desc(int deviceidx) *=============================================================================================*/ int WW3D::Get_Render_Device_Count() { - return DX8Wrapper::Get_Render_Device_Count(); + return g_renderBackend->Get_Render_Device_Count(); } @@ -607,7 +620,7 @@ int WW3D::Get_Render_Device_Count() *=============================================================================================*/ const char * WW3D::Get_Render_Device_Name(int device_index) { - return DX8Wrapper::Get_Render_Device_Name(device_index); + return g_renderBackend->Get_Render_Device_Name(device_index); } @@ -625,7 +638,7 @@ const char * WW3D::Get_Render_Device_Name(int device_index) *=============================================================================================*/ WW3DErrorType WW3D::Set_Device_Resolution(int width,int height,int bits,int windowed, bool resize_window) { - bool success = DX8Wrapper::Set_Device_Resolution(width,height,bits,windowed,resize_window); + bool success = g_renderBackend->Set_Device_Resolution(width,height,bits,windowed,resize_window); if (success) { return WW3D_ERROR_OK; @@ -650,7 +663,7 @@ WW3DErrorType WW3D::Set_Device_Resolution(int width,int height,int bits,int wind *=============================================================================================*/ void WW3D::Get_Render_Target_Resolution(int & set_w,int & set_h,int & set_bits,bool & set_windowed) { - DX8Wrapper::Get_Render_Target_Resolution(set_w,set_h,set_bits,set_windowed); + g_renderBackend->Get_Render_Target_Resolution(set_w,set_h,set_bits,set_windowed); } @@ -669,7 +682,7 @@ void WW3D::Get_Render_Target_Resolution(int & set_w,int & set_h,int & set_bits,b *=============================================================================================*/ void WW3D::Get_Device_Resolution(int & set_w,int & set_h,int & set_bits,bool & set_windowed) { - DX8Wrapper::Get_Device_Resolution(set_w,set_h,set_bits,set_windowed); + g_renderBackend->Get_Device_Resolution(set_w,set_h,set_bits,set_windowed); } @@ -688,7 +701,7 @@ void WW3D::Get_Device_Resolution(int & set_w,int & set_h,int & set_bits,bool & s *=============================================================================================*/ WW3DErrorType WW3D::Registry_Save_Render_Device( const char * sub_key ) { - bool success = DX8Wrapper::Registry_Save_Render_Device(sub_key); + bool success = g_renderBackend->Registry_Save_Render_Device(sub_key); if (success) { return WW3D_ERROR_OK; } else { @@ -710,7 +723,7 @@ WW3DErrorType WW3D::Registry_Save_Render_Device( const char * sub_key ) *=============================================================================================*/ WW3DErrorType WW3D::Registry_Save_Render_Device( const char *sub_key, int device, int width, int height, int depth, bool windowed, int texture_depth ) { - bool success = DX8Wrapper::Registry_Save_Render_Device(sub_key,device,width,height,depth,windowed,texture_depth); + bool success = g_renderBackend->Registry_Save_Render_Device(sub_key,device,width,height,depth,windowed,texture_depth); if (success) { return WW3D_ERROR_OK; } else { @@ -733,7 +746,7 @@ WW3DErrorType WW3D::Registry_Save_Render_Device( const char *sub_key, int device *=============================================================================================*/ WW3DErrorType WW3D::Registry_Load_Render_Device( const char * sub_key, bool resize_window ) { - bool success = DX8Wrapper::Registry_Load_Render_Device(sub_key,resize_window); + bool success = g_renderBackend->Registry_Load_Render_Device(sub_key,resize_window); if (success) { return WW3D_ERROR_OK; } else { @@ -743,7 +756,7 @@ WW3DErrorType WW3D::Registry_Load_Render_Device( const char * sub_key, bool resi bool WW3D::Registry_Load_Render_Device( const char * sub_key, char *device, int device_len, int &width, int &height, int &depth, int &windowed, int &texture_depth) { - return DX8Wrapper::Registry_Load_Render_Device(sub_key,device,device_len,width,height,depth,windowed,texture_depth); + return g_renderBackend->Registry_Load_Render_Device(sub_key,device,device_len,width,height,depth,windowed,texture_depth); } void WW3D::_Invalidate_Mesh_Cache() @@ -805,23 +818,23 @@ WW3DErrorType WW3D::Begin_Render(bool clear,bool clearz,const Vector3 & color, f WWPROFILE("WW3D::Begin_Render"); WWASSERT(IsInitted); - HRESULT hr; SNAPSHOT_SAY(("==========================================")); SNAPSHOT_SAY(("========== WW3D::Begin_Render ============")); SNAPSHOT_SAY(("==========================================\n")); - if (DX8Wrapper::_Get_D3D_Device8() && (hr=DX8Wrapper::_Get_D3D_Device8()->TestCooperativeLevel()) != D3D_OK) + RenderBackendDeviceStatus device_status = (g_renderBackend != nullptr) ? g_renderBackend->Get_Device_Status() : RB_DEVICE_OK; + if (device_status != RB_DEVICE_OK) { // If the device was lost, do not render until we get it back - if( D3DERR_DEVICELOST == hr ) + if( RB_DEVICE_LOST == device_status ) return WW3D_ERROR_GENERIC; //other app has the device // Check if the device needs to be reset - if( D3DERR_DEVICENOTRESET == hr ) + if( RB_DEVICE_NOT_RESET == device_status ) { WWDEBUG_SAY(("WW3D::Begin_Render is resetting the device.")); - DX8Wrapper::Reset_Device(); + g_renderBackend->Reset_Device(); } return WW3D_ERROR_GENERIC; @@ -849,22 +862,27 @@ WW3DErrorType WW3D::Begin_Render(bool clear,bool clearz,const Vector3 & color, f // If we want to clear the screen, we need to set the viewport to include the entire screen: if (clear || clearz) { - D3DVIEWPORT8 vp; + RenderBackendViewport vp; int width, height, bits; bool windowed; WW3D::Get_Render_Target_Resolution(width, height, bits, windowed); - vp.X = 0; - vp.Y = 0; - vp.Width = width; - vp.Height = height; - vp.MinZ = 0.0f; - vp.MaxZ = 1.0f; - DX8Wrapper::Set_Viewport(&vp); - DX8Wrapper::Clear(clear, clearz, color, dest_alpha); + vp.x = 0; + vp.y = 0; + vp.width = width; + vp.height = height; + vp.min_z = 0.0f; + vp.max_z = 1.0f; + g_renderBackend->Set_Viewport(vp); + g_renderBackend->Clear(clear, clearz, color, dest_alpha); } - // Notify D3D that we are beginning to render the frame - DX8Wrapper::Begin_Scene(); + // 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(); + } return WW3D_ERROR_OK; } @@ -961,25 +979,27 @@ WW3DErrorType WW3D::Render(SceneClass * scene,CameraClass * cam,bool clear,bool // Clear the viewport if (clear || clearz) { - DX8Wrapper::Clear(clear, clearz, color); + g_renderBackend->Clear(clear, clearz, color); } - // set the rendering mode + // TheSuperHackers @refactor bobtista 21/04/2026 Route fill mode through + // g_renderBackend so bgfx sees the state (previous raw DX8Wrapper calls + // were invisible to the bgfx backend). switch(scene->Get_Polygon_Mode()) { case SceneClass::POINT: - DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_POINT); + g_renderBackend->Set_Fill_Mode(RB_FILL_POINT); break; case SceneClass::LINE: - DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_WIREFRAME); + g_renderBackend->Set_Fill_Mode(RB_FILL_WIREFRAME); break; case SceneClass::FILL: - DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_SOLID); + g_renderBackend->Set_Fill_Mode(RB_FILL_SOLID); break; } // 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 @@ -1027,11 +1047,11 @@ WW3DErrorType WW3D::Render( rinfo.Camera.Apply(); // set the rendering mode - DX8Wrapper::Set_DX8_Render_State(D3DRS_FILLMODE,D3DFILL_SOLID); + g_renderBackend->Set_Fill_Mode(RB_FILL_SOLID); // 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 @@ -1105,9 +1125,13 @@ 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("DX8Wrapper::End_Scene"); - DX8Wrapper::End_Scene(flip_frame); + WWPROFILE("g_renderBackend::End_Scene"); + g_renderBackend->End_Scene(flip_frame); } FrameCount++; @@ -1126,7 +1150,10 @@ WW3DErrorType WW3D::End_Render(bool flip_frame) // (gth) I've found some cases where its not safe to rely on our "shadow" copy (of // matrices for example) across multiple frames. So even though this is slightly // less "optimal", lets just reset the caches each frame. - DX8Wrapper::Invalidate_Cached_Render_States(); + if (g_renderBackend != nullptr) + { + g_renderBackend->Invalidate_Cached_Render_States(); + } return WW3D_ERROR_OK; } @@ -1146,7 +1173,10 @@ WW3DErrorType WW3D::End_Render(bool flip_frame) *=============================================================================================*/ void WW3D::Flip_To_Primary() { - DX8Wrapper::Flip_To_Primary(); + if (g_renderBackend != nullptr) + { + g_renderBackend->Flip_To_Primary(); + } } @@ -1217,7 +1247,10 @@ void WW3D::Sync(bool step) *=============================================================================================*/ void WW3D::Set_Ext_Swap_Interval(long swap) { - DX8Wrapper::Set_Swap_Interval(swap); + if (g_renderBackend != nullptr) + { + g_renderBackend->Set_Swap_Interval((int)swap); + } } @@ -1235,7 +1268,7 @@ void WW3D::Set_Ext_Swap_Interval(long swap) *=============================================================================================*/ long WW3D::Get_Ext_Swap_Interval() { - return DX8Wrapper::Get_Swap_Interval(); + return (g_renderBackend != nullptr) ? g_renderBackend->Get_Swap_Interval() : 0; } @@ -1292,12 +1325,12 @@ int WW3D::Get_Collision_Box_Display_Mask() void WW3D::Normalize_Coordinates(int x, int y, float &fx, float &fy) { // clip the coordinates back into the resolution of the screen - x = Bound(x, 0, DX8Wrapper::Get_Device_Resolution_Width()); - y = Bound(y, 0, DX8Wrapper::Get_Device_Resolution_Height()); + x = Bound(x, 0, g_renderBackend->Get_Device_Resolution_Width()); + y = Bound(y, 0, g_renderBackend->Get_Device_Resolution_Height()); // now that the coordinates are clipped convert them to their normalized values. - fx = (float)x / DX8Wrapper::Get_Device_Resolution_Width(); - fy = (float)y / DX8Wrapper::Get_Device_Resolution_Height(); + fx = (float)x / g_renderBackend->Get_Device_Resolution_Width(); + fy = (float)y / g_renderBackend->Get_Device_Resolution_Height(); } @@ -1363,37 +1396,20 @@ void WW3D::Make_Screen_Shot( const char * filename_base , const float gamma, con gamma_lut[i] = (unsigned char) (256.0f * powf(i / 256.0f, recip)); } - // TheSuperHackers @bugfix xezon 21/05/2025 Get the back buffer and create a copy of the surface. - // Originally this code took the front buffer and tried to lock it. This does not work when the - // render view clips outside the desktop boundaries. It crashed the game. - SurfaceClass* surface = DX8Wrapper::_Get_DX8_Back_Buffer(); - - SurfaceClass::SurfaceDescription surfaceDesc; - surface->Get_Description(surfaceDesc); - - SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (DX8Wrapper::_Create_DX8_Surface(surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format))); - DX8Wrapper::_Copy_DX8_Rects(surface->Peek_D3D_Surface(), nullptr, 0, surfaceCopy->Peek_D3D_Surface(), nullptr); - - surface->Release_Ref(); - surface = nullptr; - - struct Rect - { - int Pitch; - void* pBits; - } lrect; - - lrect.pBits = surfaceCopy->Lock(&lrect.Pitch); - if (lrect.pBits == nullptr) + RenderBackendImage capture; + if (g_renderBackend == nullptr || !g_renderBackend->Capture_Back_Buffer_Image(0, capture)) { - surfaceCopy->Release_Ref(); + if (format == BMP && g_renderBackend != nullptr && g_renderBackend->Request_Native_Screen_Shot(filename)) + { + return; + } return; } unsigned int x,y,index,index2,width,height; - width = surfaceDesc.Width; - height = surfaceDesc.Height; + width = capture.Width; + height = capture.Height; unsigned char *image=W3DNEWARRAY unsigned char[3*width*height]; @@ -1404,18 +1420,14 @@ void WW3D::Make_Screen_Shot( const char * filename_base , const float gamma, con // index for image index=3*(x+y*width); // index for fb - index2=y*lrect.Pitch+4*x; + index2=y*capture.Pitch+4*x; - image[index] = gamma_lut[*((unsigned char *) lrect.pBits + index2+2)]; - image[index+1] = gamma_lut[*((unsigned char *) lrect.pBits + index2+1)]; - image[index+2] = gamma_lut[*((unsigned char *) lrect.pBits + index2+0)]; + image[index] = gamma_lut[*(capture.Bytes.data() + index2+2)]; + image[index+1] = gamma_lut[*(capture.Bytes.data() + index2+1)]; + image[index+2] = gamma_lut[*(capture.Bytes.data() + index2+0)]; } } - surfaceCopy->Unlock(); - surfaceCopy->Release_Ref(); - surfaceCopy = nullptr; - switch (format) { case TGA: { @@ -1713,37 +1725,16 @@ void WW3D::Update_Movie_Capture() WWPROFILE("WW3D::Update_Movie_Capture"); WWDEBUG_SAY(( "Updating")); - // TheSuperHackers @bugfix xezon 21/05/2025 Get the back buffer and create a copy of the surface. - // Originally this code took the front buffer and tried to lock it. This does not work when the - // render view clips outside the desktop boundaries. It crashed the game. - SurfaceClass* surface = DX8Wrapper::_Get_DX8_Back_Buffer(); - - SurfaceClass::SurfaceDescription surfaceDesc; - surface->Get_Description(surfaceDesc); - - SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (DX8Wrapper::_Create_DX8_Surface(surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format))); - DX8Wrapper::_Copy_DX8_Rects(surface->Peek_D3D_Surface(), nullptr, 0, surfaceCopy->Peek_D3D_Surface(), nullptr); - - surface->Release_Ref(); - surface = nullptr; - - struct Rect + RenderBackendImage capture; + if (g_renderBackend == nullptr || !g_renderBackend->Capture_Back_Buffer_Image(0, capture)) { - int Pitch; - void* pBits; - } lrect; - - lrect.pBits = surfaceCopy->Lock(&lrect.Pitch); - if (lrect.pBits == nullptr) - { - surfaceCopy->Release_Ref(); return; } unsigned int x,y,index,index2,width,height; - width = surfaceDesc.Width; - height = surfaceDesc.Height; + width = capture.Width; + height = capture.Height; char *image=(char *)Movie->GetBuffer(); @@ -1754,18 +1745,14 @@ void WW3D::Update_Movie_Capture() // index for image index=3*(x+(height-y-1)*width); // index for fb - index2=y*lrect.Pitch+4*x; + index2=y*capture.Pitch+4*x; - image[index]=*((char *) lrect.pBits + index2+0); - image[index+1]=*((char *) lrect.pBits + index2+1); - image[index+2]=*((char *) lrect.pBits + index2+2); + image[index]=*((char *) capture.Bytes.data() + index2+0); + image[index+1]=*((char *) capture.Bytes.data() + index2+1); + image[index+2]=*((char *) capture.Bytes.data() + index2+2); } } - surfaceCopy->Unlock(); - surfaceCopy->Release_Ref(); - surfaceCopy = nullptr; - Movie->Grab(image); #endif } @@ -1811,7 +1798,22 @@ void WW3D::Set_Texture_Reduction( int value, int minDim ) if (_TextureReduction != value || _TextureMinDim != minDim) { _TextureReduction=value; _TextureMinDim=minDim; - _Invalidate_Textures(); + // TheSuperHackers @bugfix bobtista 16/07/2026 On shader-pipeline backends + // TextureBaseClass::Invalidate is a deliberate no-op (no device loss to recover + // from), which made runtime texture-detail changes inert until restart. Queue a + // reload at the new reduction instead; the loader re-derives the mip chain and + // the backend rebuilds from the refreshed CPU snapshot. + if (g_renderBackend != nullptr && g_renderBackend->Has_Shader_Pipeline()) { + if (WW3DAssetManager::Get_Instance()) { + TextureLoader::Flush_Pending_Load_Tasks(); + HashTemplateIterator ite(WW3DAssetManager::Get_Instance()->Texture_Hash()); + for (ite.First();!ite.Is_Done();ite.Next()) { + ite.Peek_Value()->Reload_For_Reduction(); + } + } + } else { + _Invalidate_Textures(); + } } } @@ -2022,55 +2024,62 @@ void WW3D::Update_Pixel_Center() void WW3D::Set_Texture_Bitdepth(int bitdepth) { - DX8Wrapper::Set_Texture_Bitdepth(bitdepth); + if (g_renderBackend != nullptr) { + g_renderBackend->Set_Texture_Bitdepth(bitdepth); + } } int WW3D::Get_Texture_Bitdepth() { - return DX8Wrapper::Get_Texture_Bitdepth(); + return (g_renderBackend != nullptr) ? g_renderBackend->Get_Texture_Bitdepth() : 16; } void WW3D::Set_MSAA_Mode(MultiSampleModeEnum mode) { + RenderBackendMSAAMode backend_mode; switch (mode) { default: case MULTISAMPLE_MODE_NONE: - DX8Wrapper::Set_MSAA_Mode(D3DMULTISAMPLE_NONE); + backend_mode = RB_MSAA_NONE; break; case MULTISAMPLE_MODE_2X: - DX8Wrapper::Set_MSAA_Mode(D3DMULTISAMPLE_2_SAMPLES); + backend_mode = RB_MSAA_2X; break; case MULTISAMPLE_MODE_4X: - DX8Wrapper::Set_MSAA_Mode(D3DMULTISAMPLE_4_SAMPLES); + backend_mode = RB_MSAA_4X; break; case MULTISAMPLE_MODE_8X: - DX8Wrapper::Set_MSAA_Mode(D3DMULTISAMPLE_8_SAMPLES); + backend_mode = RB_MSAA_8X; break; } + + if (g_renderBackend != nullptr) { + g_renderBackend->Set_MSAA_Mode(backend_mode); + } } WW3D::MultiSampleModeEnum WW3D::Get_MSAA_Mode() { - D3DMULTISAMPLE_TYPE type = DX8Wrapper::Get_MSAA_Mode(); + RenderBackendMSAAMode mode = (g_renderBackend != nullptr) ? g_renderBackend->Get_MSAA_Mode() : RB_MSAA_NONE; - switch (type) { + switch (mode) { default: - case D3DMULTISAMPLE_NONE: + case RB_MSAA_NONE: return MULTISAMPLE_MODE_NONE; - case D3DMULTISAMPLE_2_SAMPLES: + case RB_MSAA_2X: return MULTISAMPLE_MODE_2X; - case D3DMULTISAMPLE_4_SAMPLES: + case RB_MSAA_4X: return MULTISAMPLE_MODE_4X; - case D3DMULTISAMPLE_8_SAMPLES: + case RB_MSAA_8X: return MULTISAMPLE_MODE_8X; } @@ -2116,5 +2125,5 @@ void WW3D::Reset_Current_Static_Sort_Lists_To_Default() void WW3D::Set_Gamma(float gamma,float bright,float contrast,bool calibrate) { - DX8Wrapper::Set_Gamma(gamma,bright,contrast,calibrate); + g_renderBackend->Set_Gamma(gamma,bright,contrast,calibrate,true); } diff --git a/GeneralsMD/Code/Main/CMakeLists.txt b/GeneralsMD/Code/Main/CMakeLists.txt index 880f4ea402a..c44560ec0a9 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 @@ -34,15 +34,20 @@ endif() if(SAGE_USE_SDL3) target_link_libraries(z_generals PRIVATE sdl3lib) + # TheSuperHackers @build bobtista 07/06/2026 SDL3Main.cpp uses a cross-platform int main(), + # but on Windows the exe is built with the GUI subsystem whose default entry is WinMainCRTStartup + # (expects WinMain). Point the entry at mainCRTStartup so the CRT calls main() directly while + # staying in the WINDOWS subsystem (no console window), avoiding an SDL_main/WinMain shim. + if(WIN32 AND MSVC) + target_link_options(z_generals PRIVATE "/ENTRY:mainCRTStartup") + endif() endif() # TheSuperHackers @build bobtista 22/04/2026 -# In standalone bgfx mode all D3D8/D3DX8 symbol references resolve to -# in-tree implementations: StubD3D8Device.cpp replaces Direct3DCreate8 -# and the IDirect3D* vtables, D3DXStandaloneStubs.cpp replaces the -# D3DX matrix/texture helpers. Neither d3d8.lib nor d3dx8.lib is -# needed in the link. Ref-popup keeps both. -if(NOT GGC_BGFX_STANDALONE) +# Standalone bgfx owns the device through bgfx and supplies the remaining +# D3DX helper symbols in-tree, so neither d3d8.lib nor d3dx8.lib is needed. +# Ref-popup keeps both for the real DX8 reference path. +if(NOT GGC_RENDER_BACKEND STREQUAL "bgfx") target_link_libraries(z_generals PRIVATE d3d8 d3dx8) endif() @@ -75,10 +80,14 @@ target_include_directories(z_generals PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ) +# TheSuperHackers @build bobtista 07/06/2026 SDL3 is the entry point whenever SAGE_USE_SDL3 is +# set, including on Windows (win32-generalsmd-sdl3-bgfx). The Win32 WinMain entry is only used for +# the non-SDL3 Windows build. Previously SDL3Main was gated on NOT WIN32, so the Windows SDL3 build +# linked WinMain and left TheSDL3Window (defined in SDL3Main.cpp) unresolved. target_sources(z_generals PRIVATE - $<$:WinMain.cpp> - $<$:WinMain.h> - $<$>,$>:SDL3Main.cpp> + $<$,$>>:WinMain.cpp> + $<$,$>>:WinMain.h> + $<$:SDL3Main.cpp> ) # RC files optional for MinGW builds diff --git a/GeneralsMD/Code/Main/SDL3Main.cpp b/GeneralsMD/Code/Main/SDL3Main.cpp index 07c1bd0e674..ea2db393942 100644 --- a/GeneralsMD/Code/Main/SDL3Main.cpp +++ b/GeneralsMD/Code/Main/SDL3Main.cpp @@ -16,11 +16,32 @@ #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 (GgcFlags::Enabled(GgcFlag_Trace)) { \ + 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 "GgcRuntimeFlags.h" #include "SDL3GameEngine.h" // Version constants generated by cmake at configure time. #include "BuildVersion.h" @@ -31,15 +52,32 @@ namespace const char * const kWindowTitle = "Command & Conquer Generals Zero Hour"; const int kDefaultWindowWidth = 800; const int kDefaultWindowHeight = 600; + const int kMinWindowWidth = kDefaultWindowWidth; + const int kMinWindowHeight = kDefaultWindowHeight; } +#if !defined(_WIN32) +// TheSuperHackers @build bobtista 07/06/2026 On Windows the CRT already declares and populates +// __argc/__argv (stdlib.h) before main() runs (see /ENTRY:mainCRTStartup); only POSIX needs these. int __argc = 0; char **__argv = NULL; +#endif + +// 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"; const char *g_csfFile = "data/%s/Generals.csf"; +// TheSuperHackers @build bobtista 09/06/2026 Debug.cpp references gAppPrefix from inside +// DEBUG_LOGGING-gated code; WinMain.cpp defines it on Win, so provide it here so a logging +// build of the SDL3 entry point links. +const char *gAppPrefix = ""; // Stack-dump shims. The Win build provides these in core_debug; the engine // references them from RTS_DEBUG / IG_DEBUG_STACKTRACE-gated code so just @@ -57,47 +95,157 @@ void DumpExceptionInfo(unsigned int /*u*/, EXCEPTION_POINTERS * /*e*/) {} void OSDisplaySetBusyState(Bool /*busyDisplay*/, Bool /*busySystem*/) {} SDL_Window *TheSDL3Window = NULL; -void *ApplicationHWnd = NULL; +// TheSuperHackers @build bobtista 07/06/2026 Define with the same HWND type every consumer +// extern-declares it as. On macOS HWND is a void* shim so this is unchanged; on Windows HWND is +// the real type, and defining it as void* here left the symbol unresolved at link. +HWND ApplicationHWnd = NULL; +#if defined(_WIN32) +// TheSuperHackers @build bobtista 13/06/2026 ATL's _Module.Init (GameEngine ctor, Win-only) needs +// the real module handle. WinMain.cpp supplies this on the non-SDL3 Win build; the SDL3 entry point +// must define and populate it too (set in main() via GetModuleHandle), else the Win SDL3 link fails. +HINSTANCE ApplicationHInstance = NULL; +#endif +// 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(); int main(int argc, char **argv) { +#if !defined(_WIN32) __argc = argc; __argv = argv; +#endif + + GgcFlags::DumpTableIfRequested(); + + GGC_TRACE("main entered argc=%d", argc); +#if defined(_WIN32) + ApplicationHInstance = GetModuleHandle(NULL); +#endif + + // 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. 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 > 0) + { + s_compatCommandLineStorage += ' '; + } + const char *a = argv[i]; + bool needsQuote = false; + for (const char *p = a; *p != '\0'; ++p) + { + if (*p == ' ') + { + needsQuote = true; + break; + } + } + if (needsQuote) + { + s_compatCommandLineStorage += '"'; + } + s_compatCommandLineStorage += a; + if (needsQuote) + { + s_compatCommandLineStorage += '"'; + } + } + g_compatCommandLine = s_compatCommandLineStorage.c_str(); + + // TheSuperHackers @bugfix bobtista 09/06/2026 WinMain initializes the debug log via + // initMemoryManager(); the SDL3 entry point with the null memory manager never does, so a + // logging build produced no output. Initialize it here so DEBUG_LOG reaches the log file and + // console. Expands to nothing when debug logging is compiled out. + DEBUG_INIT(DEBUG_FLAGS_DEFAULT); + + 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__) - windowFlags |= SDL_WINDOW_METAL; -#endif int windowW = kDefaultWindowWidth; int windowH = kDefaultWindowHeight; + bool wantWindowed = false; + int requestedW = 0; + int requestedH = 0; + for (int argi = 1; argi < argc; ++argi) + { + if (strcmp(argv[argi], "-win") == 0) + { + wantWindowed = true; + } + else if (strcmp(argv[argi], "-xres") == 0 && argi + 1 < argc) + { + requestedW = atoi(argv[argi + 1]); + } + else if (strcmp(argv[argi], "-yres") == 0 && argi + 1 < argc) + { + requestedH = atoi(argv[argi + 1]); + } + } { const SDL_DisplayID primaryDisplay = SDL_GetPrimaryDisplay(); const SDL_DisplayMode *desktopMode = SDL_GetDesktopDisplayMode(primaryDisplay); - if (desktopMode != nullptr && desktopMode->w > 0 && desktopMode->h > 0) + if (!wantWindowed && desktopMode != nullptr && desktopMode->w > 0 && desktopMode->h > 0) { windowW = desktopMode->w; windowH = desktopMode->h; } } - bool wantWindowed = false; - for (int argi = 1; argi < argc; ++argi) + // TheSuperHackers @bugfix bobtista 28/05/2026 Honor -xres/-yres when -win is set so '-win -xres 1600 -yres 1200' produces a 1600x1200 window instead of 800x600. + if (wantWindowed) { - if (strcmp(argv[argi], "-win") == 0) + if (requestedW > 0) { - wantWindowed = true; - windowW = kDefaultWindowWidth; - windowH = kDefaultWindowHeight; - break; + windowW = requestedW; + } + if (requestedH > 0) + { + windowH = requestedH; } } + Uint32 windowFlags = SDL_WINDOW_RESIZABLE; +#if defined(__APPLE__) + windowFlags |= SDL_WINDOW_METAL; + // TheSuperHackers @bugfix bobtista 08/06/2026 Do NOT create the window hidden on macOS for + // fullscreen. A real fullscreen Space (which hides the menu bar + Dock) is entered via + // -[NSWindow toggleFullScreen:], and AppKit only animates a VISIBLE, active window into a Space; + // toggling a hidden window degrades to a borderless window confined to the usable area. The + // fullscreen transition below happens fast enough that there is no meaningful windowed flash. +#else + // TheSuperHackers @bugfix bobtista 28/05/2026 Hide the window during fullscreen bring-up so it doesn't briefly appear at the requested resolution before SDL_SetWindowFullscreen takes effect; windowed runs show the window immediately. + if (!wantWindowed) + { + windowFlags |= SDL_WINDOW_HIDDEN; + } +#endif + GGC_TRACE("calling SDL_CreateWindow"); TheSDL3Window = SDL_CreateWindow(kWindowTitle, windowW, windowH, windowFlags); if (TheSDL3Window == NULL) { @@ -105,14 +253,57 @@ int main(int argc, char **argv) SDL_Quit(); return 1; } +#if !defined(__APPLE__) if (!wantWindowed) { SDL_SetWindowFullscreenMode(TheSDL3Window, nullptr); SDL_SetWindowFullscreen(TheSDL3Window, true); SDL_SyncWindow(TheSDL3Window); } +#endif + GGC_TRACE("SDL_CreateWindow OK window=%p", (void*)TheSDL3Window); + + // TheSuperHackers @feature bobtista 08/06/2026 Enforce a minimum content size so a drag + // cannot shrink the window below what the fixed-resolution UI can lay out. SDL_SetWindowAspectRatio + // is avoided: on macOS its windowWillResize: delegate traps AppKit's live-resize loop. + SDL_SetWindowMinimumSize(TheSDL3Window, kMinWindowWidth, kMinWindowHeight); + + ApplicationHWnd = (HWND)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()); + } + + // TheSuperHackers @bugfix bobtista 08/06/2026 Enter a REAL macOS fullscreen Space (hides the menu + // bar + Dock, covers the whole display). Per SDL3's Cocoa backend this needs a resizable window + // (set above), desktop/NULL fullscreen mode (so it is not "exclusive"), and the window VISIBLE and + // raised when toggled - a window that is still hidden makes SDL_SetWindowFullscreen a no-op and + // falls back to a borderless window confined to the usable area (menu bar + Dock left on top). + if (!wantWindowed) + { + SDL_ShowWindow(TheSDL3Window); + SDL_RaiseWindow(TheSDL3Window); + SDL_SetWindowFullscreenMode(TheSDL3Window, nullptr); + SDL_SetWindowFullscreen(TheSDL3Window, true); + SDL_SyncWindow(TheSDL3Window); + } +#endif // TheSuperHackers @build bobtista 30/04/2026 Mirror the early-init the // Win path does in WinMain.cpp: build TheVersion, then run command-line @@ -122,9 +313,46 @@ 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 + // a free instance slot inside initialize() instead of failing. + 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!")); + } - Int result = GameMain(); +#if defined(__APPLE__) + if (TheSDL3MetalView != NULL) + { + SDL_Metal_DestroyView(TheSDL3MetalView); + TheSDL3MetalView = NULL; + TheSDL3MetalLayer = NULL; + } +#endif SDL_DestroyWindow(TheSDL3Window); TheSDL3Window = NULL; diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 0d37cab5933..4ff7091a980 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -567,11 +567,11 @@ LRESULT CALLBACK WndProc( HWND hWnd, UINT message, if( TheWin32Mouse == nullptr ) return 0; - // ignore when window is not active - if( !isWinMainActive ) - return 0; + // ignore when window is not active + if( !isWinMainActive ) + return 0; - Int x = (Int)LOWORD( lParam ); + Int x = (Int)LOWORD( lParam ); Int y = (Int)HIWORD( lParam ); RECT rect; diff --git a/GeneralsMD/Code/Tools/GUIEdit/Include/GUIEditDisplay.h b/GeneralsMD/Code/Tools/GUIEdit/Include/GUIEditDisplay.h index ddd7b84bafb..37f27b5e709 100644 --- a/GeneralsMD/Code/Tools/GUIEdit/Include/GUIEditDisplay.h +++ b/GeneralsMD/Code/Tools/GUIEdit/Include/GUIEditDisplay.h @@ -107,7 +107,8 @@ class GUIEditDisplay : public Display // methods that we need to stub virtual void setTimeOfDay( TimeOfDay tod ) override {} virtual void createLightPulse( const Coord3D *pos, const RGBColor *color, Real innerRadius, Real attenuationWidth, - UnsignedInt increaseFrameTime, UnsignedInt decayFrameTime ) override {} + UnsignedInt increaseFrameTime, UnsignedInt decayFrameTime, + Bool castsShadows = FALSE, Real shadowBias = 0.0f ) override {} virtual void setShroudLevel(Int x, Int y, CellShroudStatus setting) override {} virtual void setBorderShroudLevel(UnsignedByte level) override {} virtual void clearShroud() override {} 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() diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/GlobalLightOptions.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/GlobalLightOptions.cpp index 02b654bcf9d..929638d6b31 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/GlobalLightOptions.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/GlobalLightOptions.cpp @@ -54,8 +54,8 @@ static void calcNewLight(Int lr, Int fb, Vector3 *newLight) newLight->Set(0,0,-1); Real yAngle = PI*(lr-90)/180; Real xAngle = PI*(fb-90)/180; - Real zAngle = xAngle * WWMath::Sin(yAngle); - xAngle *= WWMath::Cos(yAngle); + Real zAngle = xAngle * WWMath::Sinf(yAngle); + xAngle *= WWMath::Cosf(yAngle); newLight->Rotate_Y(yAngle); newLight->Rotate_X(xAngle); newLight->Rotate_Z(zAngle); @@ -94,8 +94,8 @@ void GlobalLightOptions::updateEditFields() void GlobalLightOptions::showLightFeedback(Int lightIndex) { Vector3 light(0,0,0); - light.X = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*cos(m_angleAzimuth[lightIndex]/180.0f*PI);// -WWMath::Sin(PI*(m_angleLR[lightIndex]-90)/180); - light.Y = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*sin(m_angleAzimuth[lightIndex]/180.0f*PI);//-WWMath::Sin(PI*(m_angleFB[lightIndex]-90)/180); + light.X = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*cos(m_angleAzimuth[lightIndex]/180.0f*PI);// -WWMath::Sinf(PI*(m_angleLR[lightIndex]-90)/180); + light.Y = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*sin(m_angleAzimuth[lightIndex]/180.0f*PI);//-WWMath::Sinf(PI*(m_angleFB[lightIndex]-90)/180); light.Z = cos (PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI); WbView3d * pView = CWorldBuilderDoc::GetActive3DView(); @@ -109,8 +109,8 @@ void GlobalLightOptions::showLightFeedback(Int lightIndex) void GlobalLightOptions::applyAngle(Int lightIndex) { Vector3 light(0,0,0); - light.X = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*cos(m_angleAzimuth[lightIndex]/180.0f*PI);// -WWMath::Sin(PI*(m_angleLR[lightIndex]-90)/180); - light.Y = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*sin(m_angleAzimuth[lightIndex]/180.0f*PI);//-WWMath::Sin(PI*(m_angleFB[lightIndex]-90)/180); + light.X = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*cos(m_angleAzimuth[lightIndex]/180.0f*PI);// -WWMath::Sinf(PI*(m_angleLR[lightIndex]-90)/180); + light.Y = sin(PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI)*sin(m_angleAzimuth[lightIndex]/180.0f*PI);//-WWMath::Sinf(PI*(m_angleFB[lightIndex]-90)/180); light.Z = cos (PI/2.0f+m_angleElevation[lightIndex]/180.0f*PI); CString str; diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/ObjectPreview.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/ObjectPreview.cpp index 66850fee457..9f83a578d8a 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/ObjectPreview.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/ObjectPreview.cpp @@ -46,6 +46,7 @@ #include "GameClient/Color.h" #include "W3DDevice/GameClient/W3DAssetManager.h" +#include "WW3D2/texturecompatibilityinterop.h" #include "WW3D2/dx8wrapper.h" #include "WWLib/TARGA.h" @@ -251,7 +252,7 @@ static UnsignedByte * generatePreview( const ThingTemplate *tt ) DX8Wrapper::Set_Render_Target((IDirect3DSurface8 *)nullptr); SurfaceClass *surface = objectTexture->Get_Surface_Level(); - UnsignedByte *data = saveSurface(surface->Peek_D3D_Surface()); + UnsignedByte *data = saveSurface(Peek_Legacy_Surface(*surface)); REF_PTR_RELEASE(surface); @@ -317,4 +318,3 @@ void ObjectPreview::DrawMyTexture(CDC *pDc, int top, int left, Int width, Int he delete(pBI); } - diff --git a/INSTALLING.md b/INSTALLING.md new file mode 100644 index 00000000000..76ab7bf10d2 --- /dev/null +++ b/INSTALLING.md @@ -0,0 +1,301 @@ +# Installing and Playing + +These are experimental rolling builds of *Command & Conquer: Generals — Zero Hour* for Windows, macOS, and Linux. + +The builds are automatically updated from the development branch. They are not stable upstream releases, so keep +`BUILD_INFO.txt` when reporting a problem — it identifies the exact commit you downloaded. + +You must own *Command & Conquer: Generals* and *Zero Hour*. The downloads contain the game engine and required runtime +libraries, but no EA game assets. + +## Supported Platforms + +| Platform | Requirements | Download | +| --- | --- | --- | +| Windows | 64-bit Windows | `GeneralsZH-win64.zip` | +| macOS | Apple Silicon and macOS 15 or newer | `GeneralsZH-macos-arm64.zip` | +| Linux | x86_64, GLIBC 2.38 or newer, Vulkan | `GeneralsZH-linux-x64.zip` | + +There are no Intel Mac or 32-bit builds. + +Download the files from the +[latest bgfx rolling release](https://github.com/bobtista/GeneralsGameCode/releases/tag/latest-bgfx). + +Windows users should normally download the full `GeneralsZH-win64.zip`. The `exe-only` archives are intended only for +updating an existing matching installation whose SDL3, OpenAL, and FFmpeg libraries are already present. + +The Windows debug archives contain debugging symbols and are intended for diagnosing bugs. + +## Required Game Data + +You need data from both games: + +- *Command & Conquer: Generals* +- *Command & Conquer: Generals — Zero Hour* + +Copy all `.big` archives from both installations. + +You also need these loose data directories when present: + +- `Data/Scripts/` +- `Data/Cursors/` +- `Data/Movies/` +- `Data//Movies/` + +These contain skirmish and multiplayer scripts, mouse cursors, and videos that are not stored in the `.big` archives. + +When merging loose files from both games, let the Zero Hour versions win if the same file exists in both places. + +Do not overwrite the build's supplied `Data/INI/Bgfx.ini`. + +For ways to obtain the retail files through an existing installation, SteamCMD, or CrossOver, see +[Getting the Game Files](https://github.com/bobtista/GeneralsGameCode/blob/bobtista/topic/trunk/docs/BUILD/GETTING_THE_GAME_FILES.md). +After obtaining the game, return here and copy the loose directories listed above as well as the `.big` archives. + +## Windows Installation + +1. Install the + [Microsoft Visual C++ 2022 x64 Redistributable](https://aka.ms/vs/17/release/vc_redist.x64.exe). +2. Extract `GeneralsZH-win64.zip`. It creates a `GeneralsZH-win64` folder. +3. Copy the retail `.big` archives and loose data into that folder. +4. Launch the game. + +From PowerShell: + +```powershell +cd "C:\path\to\GeneralsZH-win64" +.\generalszh.exe -win +``` + +The `-win` option starts the game windowed. Omit it for fullscreen. + +You can alternatively copy the contents of `GeneralsZH-win64` into an existing complete Zero Hour installation, +provided the base Generals installation is also available to the engine. + +## macOS Installation + +The published build requires an Apple Silicon Mac running macOS 15 or newer. + +Extract the archive: + +```bash +mkdir -p ~/Games +unzip ~/Downloads/GeneralsZH-macos-arm64.zip -d ~/Games +cd ~/Games/GeneralsZH-macos-arm64 +``` + +Copy the retail `.big` archives and loose data into this folder, then launch: + +```bash +./run.sh -win +``` + +The executable is ad-hoc signed but not Apple-notarized. If macOS blocks it, first confirm that you downloaded it from +this repository. Then use either: + +- **System Settings → Privacy & Security → Open Anyway**, or +- the following command, applied only to the extracted game folder: + +```bash +xattr -dr com.apple.quarantine ~/Games/GeneralsZH-macos-arm64 +``` + +Run `./run.sh -win` again afterward. + +No Homebrew packages are required to run the downloaded build. + +## Linux Installation + +The prebuilt Linux executable requires a glibc-based x86_64 distribution with GLIBC 2.38 or newer. + +Check your installed version: + +```bash +ldd --version +``` + +On Debian or Ubuntu with an AMD or Intel GPU, install the common runtime dependencies with: + +```bash +sudo apt update +sudo apt install libvulkan1 mesa-vulkan-drivers libfreetype6 libfontconfig1 +``` + +For NVIDIA, install the Vulkan support supplied with the proprietary NVIDIA driver instead of relying on Mesa. + +Extract the archive: + +```bash +mkdir -p ~/Games +unzip ~/Downloads/GeneralsZH-linux-x64.zip -d ~/Games +cd ~/Games/GeneralsZH-linux-x64 +``` + +If you already have the game installed through Wine, Proton, Steam, or Lutris, import its data automatically: + +```bash +./import-from-wine.sh +``` + +Useful options include: + +```bash +# Show what would be copied without changing anything +./import-from-wine.sh --dry-run + +# Also import options, saves, maps, and replays +./import-from-wine.sh --with-saves + +# Search an additional game folder, Steam library, or Wine prefix +./import-from-wine.sh --prefix "/path/to/game-or-prefix" + +# Overwrite files already imported +./import-from-wine.sh --force +``` + +Launch the game: + +```bash +./run.sh -win +``` + +## Keeping the Archives Elsewhere + +On macOS and Linux, the engine can mount the retail `.big` archives from separate directories: + +```bash +CNC_ZH_INSTALLPATH="/path/to/Zero Hour" \ +CNC_GENERALS_INSTALLPATH="/path/to/Generals" \ +./run.sh -win +``` + +These variables redirect `.big` archive loading only. They do not redirect loose files. Keep `Data/Scripts`, +`Data/Cursors`, and `Data/Movies` in the engine's runtime folder. + +## Mods + +Some data-only mods can run as overlays. Compatibility varies. + +Mods that patch the retail executable, require their own Windows launcher, or depend on the original 32-bit engine will +not work without adaptation. + +Place a mod's `.big` files in a named directory under the per-user data folder: + +| Platform | Example for a mod named `Shockwave` | +| --- | --- | +| Windows | `Documents\Command and Conquer Generals Zero Hour Data\Shockwave\` | +| macOS | `~/Library/Application Support/Command and Conquer Generals Zero Hour Data/Shockwave/` | +| Linux | `$XDG_DATA_HOME/Command and Conquer Generals Zero Hour Data/Shockwave/`, or `~/.local/share/Command and Conquer Generals Zero Hour Data/Shockwave/` | + +Launch on macOS or Linux with: + +```bash +./run.sh -win -mod Shockwave +``` + +Launch on Windows with: + +```powershell +.\generalszh.exe -win -mod Shockwave +``` + +All multiplayer participants must use compatible game and mod data. + +## Renderer Configuration + +Each build includes `Data/INI/Bgfx.ini`. The supplied file enables the modern sun shadow map and disables classic +stencil shadows. Leave this file in place when copying the retail data. + +## Troubleshooting + +### The Game Closes Immediately or Assets Are Missing + +Confirm that `.big` archives from both Generals and Zero Hour are available. + +Also confirm that you copied the loose Scripts, Cursors, and Movies directories. + +### Skirmish AI Does Nothing + +Confirm that this file exists: + +```text +Data/Scripts/SkirmishScripts.scb +``` + +The skirmish scripts are not stored in the `.big` archives. + +### macOS Says the Developer Cannot Be Verified + +Use **Open Anyway** or clear quarantine as described in the macOS installation section. + +### Linux Reports a GLIBC Version Error + +The prebuilt executable requires GLIBC 2.38 or newer. Upgrade to a newer distribution or build the project from source. + +### Linux Reports a Vulkan or Device-Creation Error + +Install `vulkan-tools`, then check the driver with: + +```bash +vulkaninfo --summary +``` + +Install the appropriate Vulkan driver for your GPU. + +### Windows Reports a Missing DLL + +Install the Microsoft Visual C++ x64 redistributable and use the full `GeneralsZH-win64.zip`, not an `exe-only` +archive. + +### A Mod Appears Not to Load + +Check the terminal output for: + +```text +[ggc] mod not found +``` + +Confirm that the name passed to `-mod` matches the directory name exactly. + +## Reporting a Problem + +Open an issue in the [fork's issue tracker](https://github.com/bobtista/GeneralsGameCode/issues) and include: + +- `BUILD_INFO.txt` +- Operating system and version +- CPU architecture +- GPU and driver version +- The exact launch command +- Whether a mod was enabled +- What you expected to happen +- What actually happened +- A save or replay when relevant + +On macOS or Linux, capture terminal output with: + +```bash +./run.sh -win 2>&1 | tee ggc.log +``` + +Attach `ggc.log` to the report. + +If you reproduce a Windows problem using the debug archive, attach any generated `DebugLogFileD.txt`, crash dumps, and +stack-dump files. + +For installation questions rather than bugs, use +[GitHub Discussions](https://github.com/bobtista/GeneralsGameCode/discussions). + +## Building from Source + +See +[Building the Game Yourself](https://github.com/bobtista/GeneralsGameCode/blob/bobtista/topic/trunk/README.md#building-the-game-yourself). + +## Legal + +EA has not endorsed or supported these builds. All trademarks belong to their respective owners. + +You must own legitimate copies of *Command & Conquer: Generals* and *Zero Hour*. No retail game assets are distributed +with these builds. + +GeneralsGameCode is distributed under the GNU General Public License version 3. See `LICENSE.md` for the complete +license. diff --git a/README.md b/README.md index 71ef5135ad0..71cfeba7049 100644 --- a/README.md +++ b/README.md @@ -47,12 +47,18 @@ 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). +For the experimental rolling Windows x64, macOS Apple Silicon, and Linux x86_64 bgfx builds from this fork, see +[Installing and Playing](INSTALLING.md). + +**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 +67,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 +85,27 @@ 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/ +# copy retail Data/Cursors/*.ani into ~/TheSuperHackers/GeneralsZH/Data/Cursors/ +# copy retail Data/Scripts/*.scb into ~/TheSuperHackers/GeneralsZH/Data/Scripts/ +~/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/cmake/bgfx.cmake b/cmake/bgfx.cmake new file mode 100644 index 00000000000..7a899648618 --- /dev/null +++ b/cmake/bgfx.cmake @@ -0,0 +1,192 @@ +# 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 + # TheSuperHackers @bugfix bobtista 30/04/2026 Bumped from 668550d + # to current HEAD to pick up bgfx Metal fixes — most importantly + # #3683 (depth/stencil store action on the main swap chain w/ MSAA) + # and #3685 (dynamic buffer alignment on Metal). Older bgfx pin + # produced a malformed Metal pipeline descriptor which Apple's AGX + # driver crashed on while compiling the BlitVertexFastClear and + # EndOfTile helper shaders. + GIT_TAG c480227693fccc749c36994d175bace20ba2fce2 + # Nested submodules (bgfx, bx, bimg) are cloned recursively by FetchContent. + GIT_SUBMODULES_RECURSE TRUE + # TheSuperHackers @bugfix bobtista Bounds-check the write into the fixed Metal + # per-frame uniform buffer. bgfx advances the offset by the program's full CB size + # every submit, so a heavy scene (China Nuke general challenge, ~5000+ draws/frame) + # overran the 8MB arena and wrote past its end, crashing or hanging the render thread. + # Idempotent so reconfigure on an already-patched tree is a no-op. + # TheSuperHackers @build bobtista 13/07/2026 --ignore-whitespace so the patch applies + # when core.autocrlf gives the patch file CRLF endings on Windows checkouts. + PATCH_COMMAND sh -c "git -C bgfx apply --reverse --check --ignore-whitespace '${CMAKE_CURRENT_LIST_DIR}/patches/bgfx-metal-uniform-buffer.patch' 2>/dev/null || git -C bgfx apply --ignore-whitespace '${CMAKE_CURRENT_LIST_DIR}/patches/bgfx-metal-uniform-buffer.patch'" +) + +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() + +# TheSuperHackers @refactor bobtista 11/04/2026 bgfx 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. +# +# Shader output follows GGC_BGFX_RENDERER. DX11 remains the Windows default; +# Metal is the macOS default. +# +# 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 on +# Windows or vs_passthrough_metal / vs_passthrough_spirv elsewhere. +# +# 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() + + cmake_parse_arguments(_ggc_sc "" "NAME;DEFINES" "" ${ARGN}) + + 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() + + # NAME compiles the same source under a different output/symbol name + # (variant builds), DEFINES passes preprocessor definitions to shaderc. + if(_ggc_sc_NAME) + set(_sc_name "${_ggc_sc_NAME}") + endif() + set(_define_args "") + if(_ggc_sc_DEFINES) + set(_define_args --define "${_ggc_sc_DEFINES}") + endif() + + if(GGC_BGFX_RENDERER STREQUAL "metal") + set(_shader_suffix "metal") + set(_shader_platform "osx") + # TheSuperHackers @bugfix bobtista 30/04/2026 The bare "metal" + # profile compiles to MSL 1.0, which Apple Silicon's AGX driver + # has effectively deprecated on macOS Tahoe (pipeline-state + # compiles fault inside MTLCompiler). Target metal30-14 + # (MSL 3.0 / macOS 14) which the M1/M2/M3/M4 family all support + # and the runtime accepts cleanly. metal22-11 also works on + # older systems if M-family compatibility ever matters. + set(_shader_profile "metal30-14") + elseif(GGC_BGFX_RENDERER STREQUAL "vulkan") + set(_shader_suffix "spirv") + set(_shader_platform "linux") + set(_shader_profile "spirv") + else() + set(_shader_suffix "dx11") + set(_shader_platform "windows") + set(_shader_profile "s_5_0") + endif() + + set(_out_header "${GGC_BGFX_SHADERS_OUT_DIR}/${_sc_name}_${_shader_suffix}.bin.h") + set(_varname "${_sc_name}_${_shader_suffix}") + 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 "${_shader_platform}" + --profile "${_shader_profile}" + --type "${_shader_type}" + --varyingdef "${_varying_def}" + ${_define_args} + -O 3 + DEPENDS "${_sc_abs}" "${_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 + ) + set_property(GLOBAL APPEND PROPERTY GGC_BGFX_SHADER_HEADERS "${_out_header}") +endfunction() diff --git a/cmake/compilers.cmake b/cmake/compilers.cmake index c5c4d5118c2..1e143200a37 100644 --- a/cmake/compilers.cmake +++ b/cmake/compilers.cmake @@ -49,8 +49,21 @@ if (NOT IS_VS6_BUILD) add_compile_options(/MP) # Enforce strict __cplusplus version add_compile_options(/Zc:__cplusplus) + # TheSuperHackers @build bobtista 10/06/2026 Emit SSE2 scalar floating-point instead of x87 + # on 32-bit builds. x87 keeps 80-bit extended-precision intermediates and setFPMode()'s + # _PC_24 control word forces single-precision rounding, so double-precision math (e.g. + # gm_atan2) diverges from the macOS/arm64 NEON build and breaks cross-platform deterministic + # lockstep. SSE2 doubles are true IEEE-754 64-bit, matching arm64. (x64 already uses SSE2.) + if (CMAKE_SIZEOF_VOID_P EQUAL 4) + add_compile_options(/arch:SSE2) + endif() + # Keep MSVC from contracting/reassociating FP so it matches clang -ffp-contract=off. + add_compile_options(/fp:precise) else() add_compile_options(-Wsuggest-override) + # Prevent FMA contraction (a*b+c -> fmadd) which skips intermediate + # rounding and breaks cross-platform deterministic math parity with MSVC (/fp:precise). + add_compile_options(-ffp-contract=off) endif() else() if(RTS_BUILD_OPTION_VC6_FULL_DEBUG) diff --git a/cmake/config-build.cmake b/cmake/config-build.cmake index bfd28004de0..c5782aa8fd3 100644 --- a/cmake/config-build.cmake +++ b/cmake/config-build.cmake @@ -11,6 +11,7 @@ option(RTS_BUILD_OPTION_VC6_FULL_DEBUG "Build VC6 with full debug info." OFF) option(RTS_BUILD_OPTION_FFMPEG "Enable FFmpeg support" OFF) option(SAGE_USE_SDL3 "Use SDL3 for GeneralsMD windowing and input (cross-platform alternative to the Win32 path; macOS and Windows)." OFF) option(SAGE_USE_OPENAL "Use OpenAL for GeneralsMD audio (cross-platform alternative to the Windows audio path; macOS and Windows)." OFF) +option(GGC_DIAGNOSTIC_TOOLS "Compile diagnostic- and probe-tier GGC_* runtime flags into the game (see GgcRuntimeFlags.h)." ON) if(NOT RTS_BUILD_ZEROHOUR AND NOT RTS_BUILD_GENERALS) set(RTS_BUILD_ZEROHOUR TRUE) @@ -28,6 +29,7 @@ add_feature_info(Vc6FullDebug RTS_BUILD_OPTION_VC6_FULL_DEBUG "Building VC6 with add_feature_info(FFmpegSupport RTS_BUILD_OPTION_FFMPEG "Building with FFmpeg support") add_feature_info(SDL3Windowing SAGE_USE_SDL3 "Using SDL3 for GeneralsMD windowing and input") add_feature_info(OpenALAudio SAGE_USE_OPENAL "Using OpenAL for GeneralsMD audio") +add_feature_info(GgcDiagnosticTools GGC_DIAGNOSTIC_TOOLS "Compiling diagnostic/probe GGC_* runtime flags") set(RTS_BUILD_OUTPUT_SUFFIX "" CACHE STRING "Suffix appended to output names of installable targets") @@ -62,6 +64,10 @@ else() target_compile_options(core_config INTERFACE ${RTS_FLAGS}) endif() +if(GGC_DIAGNOSTIC_TOOLS) + target_compile_definitions(core_config INTERFACE GGC_DIAGNOSTIC_TOOLS) +endif() + # This disables a lot of warnings steering developers to use windows only functions/function names. if(MSVC) target_compile_definitions(core_config INTERFACE _CRT_NONSTDC_NO_WARNINGS _CRT_SECURE_NO_WARNINGS $<$:_DEBUG_CRT>) diff --git a/cmake/config-memory.cmake b/cmake/config-memory.cmake index 5daa6f110a4..96024e1a694 100644 --- a/cmake/config-memory.cmake +++ b/cmake/config-memory.cmake @@ -21,7 +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 @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") diff --git a/cmake/dx8.cmake b/cmake/dx8.cmake index dd08f56119a..0c17626eabd 100644 --- a/cmake/dx8.cmake +++ b/cmake/dx8.cmake @@ -1,7 +1,17 @@ -FetchContent_Declare( - dx8 - GIT_REPOSITORY https://github.com/TheSuperHackers/min-dx8-sdk.git - GIT_TAG 7bddff8c01f5fb931c3cb73d4aa8e66d303d97bc -) +if(GGC_RENDER_BACKEND STREQUAL "bgfx") + add_library(d3d8lib INTERFACE) + target_include_directories(d3d8lib INTERFACE + ${CMAKE_SOURCE_DIR}/Core/Libraries/Source/WWVegas/WW3D2/dx8sdk) + target_compile_definitions(d3d8lib INTERFACE -DBUILD_WITH_D3D8) + if(WIN32 OR "${CMAKE_SYSTEM}" MATCHES "Windows") + target_link_libraries(d3d8lib INTERFACE dinput8 dxguid) + endif() +else() + FetchContent_Declare( + dx8 + GIT_REPOSITORY https://github.com/TheSuperHackers/min-dx8-sdk.git + GIT_TAG 7bddff8c01f5fb931c3cb73d4aa8e66d303d97bc + ) -FetchContent_MakeAvailable(dx8) + FetchContent_MakeAvailable(dx8) +endif() diff --git a/cmake/gamemath.cmake b/cmake/gamemath.cmake new file mode 100644 index 00000000000..57dfaf4adfc --- /dev/null +++ b/cmake/gamemath.cmake @@ -0,0 +1,16 @@ +# FORCE is required to guarantee cross-platform bit-exact determinism. +# Intrinsics would use platform-specific SIMD, breaking CRC parity between architectures. +set(GM_ENABLE_INTRINSICS OFF CACHE BOOL "Disable intrinsics for cross-arch determinism" FORCE) +set(GM_ENABLE_TESTS OFF CACHE BOOL "Disable GameMath tests" FORCE) + +FetchContent_Declare( + gamemath + GIT_REPOSITORY https://github.com/TheSuperHackers/GameMath.git + GIT_TAG 59f7ccd494f7e7c916a784ac26ef266f9f09d78d +) + +FetchContent_MakeAvailable(gamemath) + +# Ensure GameMath includes are available to ALL targets +# to prevent one-definition-rule violations and ensure USE_DETERMINISTIC_MATH activates consistently. +include_directories(${gamemath_SOURCE_DIR}/include) 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() diff --git a/cmake/openal.cmake b/cmake/openal.cmake index da363a2d44c..ed6b5788f6e 100644 --- a/cmake/openal.cmake +++ b/cmake/openal.cmake @@ -1,7 +1,7 @@ # cmake/openal.cmake # # TheSuperHackers @build bobtista 28/04/2026 OpenAL Soft dependency for the -# GeneralsMD macOS-native audio branch. +# GeneralsMD OpenAL audio backend (macOS-native build and the SDL3 Windows build). if(SAGE_USE_OPENAL) message(STATUS "Configuring OpenAL Soft for GeneralsMD audio") diff --git a/cmake/patches/bgfx-metal-uniform-buffer.patch b/cmake/patches/bgfx-metal-uniform-buffer.patch new file mode 100644 index 00000000000..cb8fc37cc32 --- /dev/null +++ b/cmake/patches/bgfx-metal-uniform-buffer.patch @@ -0,0 +1,24 @@ +diff --git a/src/renderer_mtl.cpp b/src/renderer_mtl.cpp +index c4da6d70f..285c8471f 100644 +--- a/src/renderer_mtl.cpp ++++ b/src/renderer_mtl.cpp +@@ -1905,8 +1905,18 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames + ? m_uniformBufferFragmentOffset + : m_uniformBufferVertexOffset + ; ++ // TheSuperHackers @bugfix bobtista Guard the fixed uniform arena. bgfx advances ++ // the per-draw offset by the program's full constant-buffer size every submit, so ++ // a heavy frame can push offset past UNIFORM_BUFFER_SIZE. Writing there is an ++ // out-of-bounds store into unmapped/adjacent memory (render-thread crash or hang). ++ // Drop the write instead; the affected draw renders with stale uniforms for one frame. ++ const uint32_t writeSize = _numRegs*16; ++ if (offset + _loc + writeSize > UNIFORM_BUFFER_SIZE) ++ { ++ return; ++ } + uint8_t* dst = (uint8_t*)m_uniformBuffer->contents(); +- bx::memCopy(&dst[offset + _loc], _val, _numRegs*16); ++ bx::memCopy(&dst[offset + _loc], _val, writeSize); + } + + void setShaderUniform4f(uint8_t _flags, uint32_t _loc, const void* _val, uint32_t _numRegs) diff --git a/cmake/render-backend.cmake b/cmake/render-backend.cmake new file mode 100644 index 00000000000..e7a8f2c3973 --- /dev/null +++ b/cmake/render-backend.cmake @@ -0,0 +1,94 @@ +# cmake/render-backend.cmake +# +# TheSuperHackers @refactor bobtista 10/04/2026 Selects the rendering backend +# for WW3D2 at configure time. +# +# 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 support is still landing; 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() + +# TheSuperHackers @refactor bobtista 03/06/2026 The bgfx backend no longer +# creates a real D3D8 device or a DX8 reference popup; it always renders to the +# main window directly. The former GGC_BGFX_STANDALONE toggle is gone - the +# GGC_RENDER_BACKEND_BGFX compile define now drives every former-standalone +# code path, and the dx8/d3d8 runtime is linked only for GGC_RENDER_BACKEND=dx8. + +if(GGC_RENDER_BACKEND STREQUAL "bgfx") + if(NOT DEFINED GGC_BGFX_RENDERER) + if(APPLE) + set(GGC_BGFX_RENDERER "metal" CACHE STRING "bgfx renderer for GGC_RENDER_BACKEND=bgfx") + elseif(UNIX) + set(GGC_BGFX_RENDERER "vulkan" CACHE STRING "bgfx renderer for GGC_RENDER_BACKEND=bgfx") + else() + set(GGC_BGFX_RENDERER "dx11" CACHE STRING "bgfx renderer for GGC_RENDER_BACKEND=bgfx") + endif() + endif() + set_property(CACHE GGC_BGFX_RENDERER PROPERTY STRINGS dx11 metal vulkan) + + if(NOT GGC_BGFX_RENDERER STREQUAL "dx11" AND + NOT GGC_BGFX_RENDERER STREQUAL "metal" AND + NOT GGC_BGFX_RENDERER STREQUAL "vulkan") + message(FATAL_ERROR + "Invalid GGC_BGFX_RENDERER: '${GGC_BGFX_RENDERER}'. " + "Must be one of: dx11, metal, vulkan.") + endif() + + if(GGC_BGFX_RENDERER STREQUAL "metal") + add_compile_definitions(GGC_BGFX_RENDERER_METAL=1) + elseif(GGC_BGFX_RENDERER STREQUAL "vulkan") + add_compile_definitions(GGC_BGFX_RENDERER_VULKAN=1) + else() + add_compile_definitions(GGC_BGFX_RENDERER_DX11=1) + endif() + message(STATUS "bgfx renderer target: ${GGC_BGFX_RENDERER}") +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() 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) diff --git a/scripts/analysis/w3d_feature_scan.py b/scripts/analysis/w3d_feature_scan.py new file mode 100644 index 00000000000..fa05a143b0b --- /dev/null +++ b/scripts/analysis/w3d_feature_scan.py @@ -0,0 +1,395 @@ +#!/usr/bin/env python3 +# TheSuperHackers @feature bobtista 16/07/2026 W3D content-coverage scanner. +# Walks .big archives, parses every .w3d mesh's materials/shaders/textures, and +# cross-references the features shipped art actually uses against the bgfx +# backend's support matrix. Output: usage histograms plus a hit list of +# used-but-unsupported (or recently-fixed, needs-visual-check) features with +# example assets. Companion to the dx8wrapper parity audits. +# +# Usage: +# python3 scripts/analysis/w3d_feature_scan.py --big-dir ~/TheSuperHackers/GeneralsZH \ +# [--out report.md] [--json report.json] + +import argparse +import json +import os +import struct +import sys +from collections import Counter, defaultdict + +# --- W3D chunk IDs (Core/Tools/WW3D/max2w3d/w3d_file.h) ---------------------- +W3D_CHUNK_MESH = 0x00000000 +W3D_CHUNK_MATERIAL_INFO = 0x00000028 +W3D_CHUNK_SHADERS = 0x00000029 +W3D_CHUNK_VERTEX_MATERIALS = 0x0000002A +W3D_CHUNK_VERTEX_MATERIAL = 0x0000002B +W3D_CHUNK_VERTEX_MATERIAL_NAME = 0x0000002C +W3D_CHUNK_VERTEX_MATERIAL_INFO = 0x0000002D +W3D_CHUNK_VERTEX_MAPPER_ARGS0 = 0x0000002E +W3D_CHUNK_VERTEX_MAPPER_ARGS1 = 0x0000002F +W3D_CHUNK_TEXTURES = 0x00000030 +W3D_CHUNK_TEXTURE = 0x00000031 +W3D_CHUNK_TEXTURE_NAME = 0x00000032 +W3D_CHUNK_TEXTURE_INFO = 0x00000033 +W3D_CHUNK_MATERIAL_PASS = 0x00000038 +W3D_CHUNK_TEXTURE_STAGE = 0x00000048 +W3D_CHUNK_STAGE_TEXCOORD_IDS = 0x0000004A +W3D_CHUNK_MESH_HEADER3 = 0x0000001F + +# Chunks whose subchunks we recurse into. Wrapper chunks have the MSB set on +# their size field in most W3D files, but several containers ship without it, +# so recurse by ID. +CONTAINER_CHUNKS = { + W3D_CHUNK_MESH, + W3D_CHUNK_VERTEX_MATERIALS, + W3D_CHUNK_VERTEX_MATERIAL, + W3D_CHUNK_TEXTURES, + W3D_CHUNK_TEXTURE, + W3D_CHUNK_MATERIAL_PASS, + W3D_CHUNK_TEXTURE_STAGE, +} + +# --- Enum names (w3d_file.h / shader.h) -------------------------------------- +DETAIL_COLOR = [ + "DISABLE", "DETAIL", "SCALE", "INVSCALE", "ADD", "SUB", "SUBR", "BLEND", + "DETAILBLEND", "ADDSIGNED", "ADDSIGNED2X", "SCALE2X", "MODALPHAADDCOLOR", +] +DETAIL_ALPHA = ["DISABLE", "DETAIL", "SCALE", "INVSCALE"] +PRI_GRADIENT = ["DISABLE", "MODULATE", "ADD", "BUMPENVMAP", "BUMPENVMAPLUMINANCE", "MODULATE2X"] +SEC_GRADIENT = ["DISABLE", "ENABLE"] +SRC_BLEND = ["ZERO", "ONE", "SRC_ALPHA", "ONE_MINUS_SRC_ALPHA"] +DST_BLEND = ["ZERO", "ONE", "SRC_COLOR", "ONE_MINUS_SRC_COLOR", "SRC_ALPHA", + "ONE_MINUS_SRC_ALPHA", "SRC_COLOR_PREFOG"] +DEPTH_COMPARE = ["NEVER", "LESS", "EQUAL", "LEQUAL", "GREATER", "NOTEQUAL", "GEQUAL", "ALWAYS"] +MAPPING = [ + "UV", "ENVIRONMENT", "CHEAP_ENVIRONMENT", "SCREEN", "LINEAR_OFFSET", + "SILHOUETTE", "SCALE", "GRID", "ROTATE", "SINE_LINEAR_OFFSET", + "STEP_LINEAR_OFFSET", "ZIGZAG_LINEAR_OFFSET", "WS_CLASSIC_ENV", + "WS_ENVIRONMENT", "GRID_CLASSIC_ENV", "GRID_ENVIRONMENT", "RANDOM", + "EDGE", "BUMPENV", +] + +def enum_name(table, value): + if 0 <= value < len(table): + return table[value] + return "UNKNOWN_%d" % value + +# --- bgfx support matrix ------------------------------------------------------ +# Derived from BgfxBackend.cpp BuildTssOpsForShader/Supports_Texture_Op, fs_uber.sc, +# mapper.cpp/matrixmapper.cpp, and textureloader.cpp's staging-format whitelist, +# as of the 16/07/2026 parity fix batch. "check" = recently fixed, wants a visual +# confirmation on a real asset; "hit" = used feature with no bgfx path; +# "info" = deliberate divergence worth knowing about. +VERDICTS = { + ("post_detail_color", "ADDSIGNED"): "check(fixed 16/07: fix/bgfx-detail-color-funcs)", + ("post_detail_color", "ADDSIGNED2X"): "check(fixed 16/07: fix/bgfx-detail-color-funcs)", + ("post_detail_color", "SCALE2X"): "check(fixed 16/07: fix/bgfx-detail-color-funcs)", + ("post_detail_color", "MODALPHAADDCOLOR"): "check(fixed 16/07: fix/bgfx-detail-color-funcs)", + ("post_detail_color", "SUBR"): "check(fixed 16/07: was inverted)", + ("pri_gradient", "BUMPENVMAP"): "hit(no bump-env: flat diffuse fallback)", + ("pri_gradient", "BUMPENVMAPLUMINANCE"): "hit(no bump-env: flat diffuse fallback)", + ("sec_gradient", "ENABLE"): "info(specular bit ignored; opt-in matFx Blinn-Phong instead)", + ("mapping", "BUMPENV"): "hit(bump-env mapper feeds unimplemented bump shader)", + ("mapping", "SILHOUETTE"): "hit(mapper unimplemented in engine, falls back to UV)", + ("dst_blend", "SRC_COLOR_PREFOG"): "info(fog-dependent blend; fog unimplemented on bgfx)", + ("texture_flag", "BUMPMAP_TYPE"): "hit(bump formats blocked by staging whitelist)", +} + +# textureloader.cpp Is_CPU_Texture_Snapshot_Staging_Format whitelist (16/07/2026): +SUPPORTED_TEX_FORMATS = { + "DXT1", "DXT2", "DXT3", "DXT4", "DXT5", + "A8R8G8B8", "X8R8G8B8", "R8G8B8", "A4R4G4B4", "R5G6B5", "A1R5G5B5", +} + +# --- BIG archive walker ------------------------------------------------------- +def read_big_index(path): + with open(path, "rb") as f: + data = f.read() + if data[:4] not in (b"BIGF", b"BIG4"): + return None, None + count = struct.unpack(">I", data[8:12])[0] + entries = [] + off = 16 + for _ in range(count): + o, s = struct.unpack(">II", data[off:off + 8]) + off += 8 + end = data.index(b"\x00", off) + name = data[off:end].decode("latin1") + off = end + 1 + entries.append((name, o, s)) + return data, entries + +# --- W3D parser ---------------------------------------------------------------- +class MeshRecord: + def __init__(self, asset, mesh_name): + self.asset = asset + self.mesh_name = mesh_name + self.pass_count = 1 + self.shaders = [] # list of dicts + self.materials = [] # list of dicts + self.textures = [] # list of dicts + self.stage_counts = [] # textures stages per pass + self.uv_channels = set() + +def parse_chunks(buf, start, end, handler, depth=0): + off = start + while off + 8 <= end: + cid, size = struct.unpack_from(" end or size > (end - payload): + return # malformed; stop this level + handler(cid, buf, payload, nxt, depth) + if cid in CONTAINER_CHUNKS: + parse_chunks(buf, payload, nxt, handler, depth + 1) + off = nxt + +def base_name(path): + # .big entries and W3D texture references use Windows separators; normalize + # before taking the basename so this works on POSIX hosts. + return path.replace("\\", "/").rsplit("/", 1)[-1] + +def cstr(buf, start, end): + raw = buf[start:end] + z = raw.find(b"\x00") + if z >= 0: + raw = raw[:z] + return raw.decode("latin1", "replace") + +def parse_w3d(asset_name, buf, records): + state = {"mesh": None, "vm": None, "tex": None, "pass_stage_count": 0} + + def handler(cid, b, s, e, depth): + if cid == W3D_CHUNK_MESH: + if state["mesh"] is not None: + finish_mesh() + state["mesh"] = MeshRecord(asset_name, "?") + m = state["mesh"] + if m is None: + return + if cid == W3D_CHUNK_MESH_HEADER3 and e - s >= 48: + m.mesh_name = cstr(b, s + 4 + 4, s + 4 + 4 + 32) + elif cid == W3D_CHUNK_MATERIAL_INFO and e - s >= 16: + m.pass_count = struct.unpack_from("= 4: + attrs = struct.unpack_from("> 16 + state["vm"]["stage1_mapping"] = (attrs & 0x0000FF00) >> 8 + elif cid == W3D_CHUNK_VERTEX_MAPPER_ARGS0 and state["vm"] is not None: + state["vm"]["args0"] = cstr(b, s, e) + elif cid == W3D_CHUNK_VERTEX_MAPPER_ARGS1 and state["vm"] is not None: + state["vm"]["args1"] = cstr(b, s, e) + elif cid == W3D_CHUNK_TEXTURE: + state["tex"] = {"name": "?", "attributes": 0, "anim_frames": 1, "anim_fps": 0.0} + m.textures.append(state["tex"]) + elif cid == W3D_CHUNK_TEXTURE_NAME and state["tex"] is not None: + state["tex"]["name"] = cstr(b, s, e) + elif cid == W3D_CHUNK_TEXTURE_INFO and state["tex"] is not None and e - s >= 12: + attrs, _anim, frames, fps = struct.unpack_from("0 usage is caught via mappers + + def finish_mesh(): + if state["mesh"] is not None: + records.append(state["mesh"]) + + parse_chunks(buf, 0, len(buf), handler) + finish_mesh() + +# --- Texture format resolution --------------------------------------------------- +def dds_format(data): + if data[:4] != b"DDS " or len(data) < 128: + return None + fourcc = data[84:88] + if fourcc in (b"DXT1", b"DXT2", b"DXT3", b"DXT4", b"DXT5"): + return fourcc.decode() + flags, rgb_bits = struct.unpack_from(" (big, format-resolver payload offset/size) + bigs = sorted( + f for f in os.listdir(args.big_dir) + if f.lower().endswith(".big") and os.path.isfile(os.path.join(args.big_dir, f)) + ) + parsed_assets = 0 + for bigname in bigs: + path = os.path.join(args.big_dir, bigname) + data, entries = read_big_index(path) + if data is None: + continue + for name, off, size in entries: + low = name.lower() + if low.endswith(".w3d"): + try: + parse_w3d("%s:%s" % (bigname, name), data[off:off + size], records) + parsed_assets += 1 + except Exception as ex: + print("parse error %s %s: %s" % (bigname, name, ex), file=sys.stderr) + elif low.endswith((".dds", ".tga")): + tex_files[base_name(low)] = (path, off, size) + + # Aggregate + hist = defaultdict(Counter) + examples = defaultdict(dict) + + def bump(category, name, asset): + hist[category][name] += 1 + examples[category].setdefault(name, asset) + + anim_textures = Counter() + for m in records: + for sh in m.shaders: + bump("post_detail_color", enum_name(DETAIL_COLOR, sh["post_detail_color"]), m.asset) + bump("post_detail_alpha", enum_name(DETAIL_ALPHA, sh["post_detail_alpha"]), m.asset) + bump("pri_gradient", enum_name(PRI_GRADIENT, sh["pri_gradient"]), m.asset) + bump("sec_gradient", enum_name(SEC_GRADIENT, sh["sec_gradient"]), m.asset) + bump("src_blend", enum_name(SRC_BLEND, sh["src_blend"]), m.asset) + bump("dst_blend", enum_name(DST_BLEND, sh["dst_blend"]), m.asset) + bump("depth_compare", enum_name(DEPTH_COMPARE, sh["depth_compare"]), m.asset) + bump("alpha_test", "ENABLE" if sh["alpha_test"] else "DISABLE", m.asset) + for vm in m.materials: + bump("mapping", enum_name(MAPPING, vm["stage0_mapping"]), m.asset) + if vm["stage1_mapping"]: + bump("mapping", enum_name(MAPPING, vm["stage1_mapping"]), m.asset) + for tx in m.textures: + attrs = tx["attributes"] + if attrs & 0x0004: + bump("texture_flag", "NO_LOD", m.asset) + if attrs & 0x0008 or attrs & 0x0010: + bump("texture_flag", "CLAMP", m.asset) + if attrs & 0x1000: + bump("texture_flag", "BUMPMAP_TYPE", m.asset) + if tx["anim_frames"] > 1: + bump("texture_flag", "ANIMATED_FRAMES", m.asset) + anim_textures[tx["name"]] += 1 + if m.pass_count > 1: + bump("multipass", "PASSES_%d" % m.pass_count, m.asset) + for c in m.stage_counts: + if c > 2: + bump("multipass", "STAGES_%d" % c, m.asset) + + # Texture format sweep over shipped image files referenced by materials + fmt_hist = Counter() + fmt_examples = {} + referenced = set() + for m in records: + for tx in m.textures: + referenced.add(base_name(tx["name"].lower())) + for name in sorted(referenced): + base = os.path.splitext(name)[0] + for ext in (".dds", ".tga"): + key = base + ext + if key in tex_files: + path, off, size = tex_files[key] + if ext == ".tga": + fmt = "TGA" + else: + with open(path, "rb") as f: + f.seek(off) + fmt = dds_format(f.read(min(size, 160))) or "OTHER" + fmt_hist[fmt] += 1 + fmt_examples.setdefault(fmt, key) + break + + # Report + lines = [] + lines.append("# W3D feature scan") + lines.append("") + lines.append("Scanned %d .w3d assets across %d .big archives; %d meshes." % + (parsed_assets, len(bigs), len(records))) + lines.append("") + lines.append("## Hit list (used features needing attention)") + lines.append("") + hits = [] + for (cat, name), verdict in VERDICTS.items(): + count = hist[cat].get(name, 0) + if count: + hits.append((verdict, cat, name, count, examples[cat][name])) + if fmt_hist.get("BUMP_UV", 0) or fmt_hist.get("OTHER", 0): + for fmt in ("BUMP_UV", "OTHER"): + if fmt_hist.get(fmt): + hits.append(("hit(texture format outside staging whitelist)", + "texture_format", fmt, fmt_hist[fmt], fmt_examples[fmt])) + if hits: + lines.append("| verdict | category | feature | uses | example |") + lines.append("|---|---|---|---|---|") + for verdict, cat, name, count, ex in sorted(hits): + lines.append("| %s | %s | %s | %d | %s |" % (verdict, cat, name, count, ex)) + else: + lines.append("No used-but-unsupported features found.") + lines.append("") + lines.append("## Usage histograms") + for cat in sorted(hist): + lines.append("") + lines.append("### %s" % cat) + for name, count in hist[cat].most_common(): + lines.append("- %s: %d (e.g. %s)" % (name, count, examples[cat][name])) + lines.append("") + lines.append("### texture_format (referenced by materials, resolved on disk)") + for fmt, count in fmt_hist.most_common(): + lines.append("- %s: %d (e.g. %s)" % (fmt, count, fmt_examples[fmt])) + lines.append("") + lines.append("### animated multi-frame textures") + for name, count in anim_textures.most_common(20): + lines.append("- %s: %d meshes" % (name, count)) + + report = "\n".join(lines) + if args.out: + with open(args.out, "w") as f: + f.write(report + "\n") + else: + print(report) + if args.json: + with open(args.json, "w") as f: + json.dump({ + "hist": {k: dict(v) for k, v in hist.items()}, + "examples": {k: dict(v) for k, v in examples.items()}, + "texture_formats": dict(fmt_hist), + }, f, indent=1) + +if __name__ == "__main__": + main() diff --git a/scripts/build/dist/Bgfx.ini b/scripts/build/dist/Bgfx.ini new file mode 100644 index 00000000000..b6a8d80967f --- /dev/null +++ b/scripts/build/dist/Bgfx.ini @@ -0,0 +1,82 @@ +; Optional bgfx render settings. Loaded after GameData.ini, client render only +; (not part of the INI CRC). All keys are optional; omitted keys use the engine +; defaults shown below. -bgfxEffects / -bgfxNoEffects on the command line +; override the ShadowMaps / DynamicLightShadows / RimLight / EmissiveBoost keys. +; +; Shipped default: the modern sun shadow map is enabled and the classic stencil +; volume shadows are disabled, so object shadows render correctly on every +; backend (Metal / D3D11 / Vulkan). Delete this file to fall back to pure engine +; defaults, or edit the keys below to taste. +GameData + + ; --- Renderer / quality --- + ; BgfxRenderer = ; default auto (Metal on macOS, D3D11 on Windows, Vulkan on Linux) + ; BgfxMSAA = 4 ; valid: 1, 2, 4, 8, 16 + ; BgfxRenderScale = 1.0 ; 3D scene resolution scale + ; BgfxSrgb = No + ; BgfxPointFilter = No ; nearest-neighbor texture filtering + + ; --- Post-processing composite (sharpen / saturation / contrast / FXAA) --- + ; BgfxPostProcessing = Yes + ; BgfxPostSharpenAmount = 0.08 + ; BgfxPostSaturation = 1.015 + ; BgfxPostContrast = 1.01 + ; BgfxPostFxaaAmount = 0.35 + ; BgfxNoPostFx = No ; skip the post-fx pass entirely + ; BgfxNoSceneFramebuffer = No ; render directly to backbuffer (disables all post) + + ; --- HDR / bloom --- + ; BgfxHdr = No ; RGBA16F targets + ACES tonemap; pairs with bloom + ; BgfxBloom = No + ; BgfxBloomThreshold = 0.7 + ; BgfxBloomIntensity = 0.5 + + ; --- Color grade --- + ; BgfxColorGrade = No + ; BgfxColorGradeStrength = 1.0 + ; BgfxColorGradeTemperature = 0.0 ; -1..1 cold..warm + ; BgfxColorGradeTint = 0.0 ; -1..1 green..magenta + + ; --- Vignette / chromatic aberration / film grain --- + ; BgfxVignette = No + ; BgfxVignetteStrength = 0.4 + ; BgfxChromaticAberration = No + ; BgfxChromaticAberrationAmount = 0.5 + ; BgfxFilmGrain = No + ; BgfxFilmGrainStrength = 0.08 + + ; --- SSAO --- + ; BgfxSSAO = No + ; BgfxSSAORadius = 12.0 ; world units; camera is far, small radii don't read + ; BgfxSSAOIntensity = 1.0 + + ; --- Lighting effects --- + ; BgfxSpecular = No + ; BgfxSpecularStrength = 3.0 + ; BgfxRimLight = No + ; BgfxRimStrength = 0.3 + ; BgfxRimPower = 3.0 + ; BgfxEmissiveBoost = No + ; BgfxEmissiveBoostScale = 2.0 + + ; --- Shadows --- + BgfxShadowMaps = Yes ; sun shadow map (default No) + ; BgfxShadowMapBias = 0.0006 + ; BgfxShadowMapStrength = 0.35 + ; BgfxShadowFullPcf = No + BgfxStencilShadows = No ; classic volume shadows (default Yes) + ; BgfxDynamicLightShadows = No ; nuke / particle-cannon lights + + ; --- Particles --- + ; BgfxSoftParticles = No + ; BgfxSoftParticleFadeScale = 80.0 + + ; --- A/B comparison wipe --- + ; BgfxWipeEnabled = No + ; BgfxWipeFollowMouse = Yes + ; BgfxWipeSplit = 0.5 + + ; --- Debug --- + ; BgfxLogStats = No + +End diff --git a/scripts/build/linux/Dockerfile b/scripts/build/linux/Dockerfile new file mode 100644 index 00000000000..33e6c6d0d36 --- /dev/null +++ b/scripts/build/linux/Dockerfile @@ -0,0 +1,46 @@ +# TheSuperHackers @build bobtista 24/07/2026 Native Linux build image for the +# GeneralsMD SDL3/bgfx client. Ubuntu 24.04 to match the GitHub Actions +# ubuntu-latest runner (same glibc/toolchain), with the X11/Wayland/Vulkan/GL +# and audio dev headers SDL3 and bgfx probe at configure time. FFmpeg is built +# minimally from source (build-ffmpeg-minimal.sh, needs nasm/curl/xz) instead of +# the system package, so it is bundled and portable across distros. +# This is a NATIVE build (gcc), unrelated to the Wine/VC6 cross-build under +# resources/dockerbuild. +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + cmake \ + ninja-build \ + git \ + ca-certificates \ + pkg-config \ + python3 \ + libx11-dev \ + libxext-dev \ + libxrandr-dev \ + libxi-dev \ + libxcursor-dev \ + libxinerama-dev \ + libxfixes-dev \ + libxrender-dev \ + libxss-dev \ + libxtst-dev \ + libxkbcommon-dev \ + libwayland-dev \ + wayland-protocols \ + libdecor-0-dev \ + libgl1-mesa-dev \ + libegl1-mesa-dev \ + libvulkan-dev \ + libfreetype6-dev \ + libfontconfig1-dev \ + libasound2-dev \ + libpulse-dev \ + nasm \ + curl \ + xz-utils \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /src diff --git a/scripts/build/linux/build-ffmpeg-minimal.sh b/scripts/build/linux/build-ffmpeg-minimal.sh new file mode 100644 index 00000000000..4822214310c --- /dev/null +++ b/scripts/build/linux/build-ffmpeg-minimal.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +set -euo pipefail + +# TheSuperHackers @build bobtista 25/07/2026 Build a minimal, self-contained +# FFmpeg for the native Linux build. Ubuntu's system FFmpeg pulls a ~150MB +# closure of codec + crypto libraries and ties the binary to one FFmpeg major +# version (libavcodec.so.60), which is absent on Arch / SteamOS. This builds a +# stripped FFmpeg (only the decoders the game needs, no external codec libs) so +# the ~6MB of shared libs can be bundled and the binary runs on any modern +# distro, including the Steam Deck. +# +# Usage: build-ffmpeg-minimal.sh [install_prefix] +# Point the build at it with: PKG_CONFIG_PATH=/lib/pkgconfig +# +# Requires: a C toolchain, make, pkg-config, nasm (x86 asm), curl, xz. + +prefix="${1:-${GGC_FFMPEG_PREFIX:-${PWD}/ffmpeg-min}}" +version="${FFMPEG_VERSION:-6.1.2}" +sha256="${FFMPEG_SHA256:-3b624649725ecdc565c903ca6643d41f33bd49239922e45c9b1442c63dca4e38}" + +# Cache hit: a previous build already installed here. +if [ -f "${prefix}/lib/pkgconfig/libavcodec.pc" ]; then + echo "Minimal FFmpeg already present at ${prefix}; skipping build." + exit 0 +fi + +work="$(mktemp -d)" +trap 'rm -rf "${work}"' EXIT + +tarball="${work}/ffmpeg-${version}.tar.xz" +echo "Downloading ffmpeg ${version}..." +curl -fsSL "https://ffmpeg.org/releases/ffmpeg-${version}.tar.xz" -o "${tarball}" +echo "${sha256} ${tarball}" | sha256sum -c - + +tar -xf "${tarball}" -C "${work}" +src="${work}/ffmpeg-${version}" +build="${work}/build" +mkdir -p "${build}" + +# --disable-everything then re-enable only the file protocol and the demuxers / +# decoders the game's cutscenes use (Bink primarily, plus common native codecs). +# No --enable-lib* means zero external codec dependencies. +cd "${build}" +"${src}/configure" \ + --prefix="${prefix}" \ + --enable-shared --disable-static \ + --enable-pic --disable-debug \ + --disable-programs --disable-doc --disable-htmlpages --disable-manpages \ + --disable-avdevice --disable-avfilter --disable-postproc --disable-network \ + --disable-everything \ + --enable-protocol=file \ + --enable-demuxer=bink,smacker,mov,avi,matroska,asf,mpegps,mpegts,ogg,wav,flv,h264,hevc,m4v \ + --enable-decoder=bink,binkaudio_dct,binkaudio_rdft,smackaud,smacker,h264,hevc,mpeg1video,mpeg2video,mpeg4,msmpeg4v1,msmpeg4v2,msmpeg4v3,wmv1,wmv2,wmv3,vc1,vp6,vp6a,vp6f,vp8,vp9,theora,flv,vorbis,aac,ac3,mp3,pcm_s16le,pcm_u8 \ + --enable-parser=h264,hevc,mpeg4video,mpegvideo,vp8,vp9,vorbis,aac,ac3 + +make -j"$(nproc)" +make install + +echo "Minimal FFmpeg installed to ${prefix} ($(du -shc "${prefix}"/lib/*.so.* 2>/dev/null | tail -1 | cut -f1))" diff --git a/scripts/build/linux/build-linux-generalsmd.sh b/scripts/build/linux/build-linux-generalsmd.sh new file mode 100755 index 00000000000..279a11330ea --- /dev/null +++ b/scripts/build/linux/build-linux-generalsmd.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +# TheSuperHackers @build bobtista 24/07/2026 Native Linux build of the GeneralsMD +# SDL3/bgfx client inside a Debian container. Builds the z_generals (Zero Hour) +# target for the linux-generalsmd-sdl3-bgfx preset. The build directory and +# FetchContent deps live in a named Docker volume so re-runs are incremental. +# +# Usage: +# scripts/build/linux/build-linux-generalsmd.sh [cmake-target] +# +# The produced ELF lands in the volume at build/${preset}/GeneralsMD/generalszh; +# copy it out with: scripts/build/linux/copy-out.sh (or docker cp from a run) + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +image="ggc-linux-build" +preset="linux-generalsmd-sdl3-bgfx" +target="${1:-z_generals}" +build_volume="ggc-linux-build" + +docker build -t "${image}" "${repo_root}/scripts/build/linux" + +docker run --rm \ + -v "${repo_root}:/src" \ + -v "${build_volume}:/src/build" \ + -w /src \ + "${image}" \ + bash -euo pipefail -c " + bash scripts/build/linux/build-ffmpeg-minimal.sh /src/build/ffmpeg-min + export PKG_CONFIG_PATH=/src/build/ffmpeg-min/lib/pkgconfig + cmake --preset ${preset} + cmake --build build/${preset} --target ${target} -j\$(nproc) + " diff --git a/scripts/build/linux/deploy-linux-generalsmd.sh b/scripts/build/linux/deploy-linux-generalsmd.sh new file mode 100755 index 00000000000..303efeac765 --- /dev/null +++ b/scripts/build/linux/deploy-linux-generalsmd.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +set -euo pipefail + +# TheSuperHackers @build bobtista 24/07/2026 Stage the native Linux GeneralsMD +# build for distribution: the ELF, its bundled non-system shared libraries, and a +# run.sh wrapper that puts them on LD_LIBRARY_PATH. System libraries (glibc, Vulkan +# loader, X11/Wayland, Mesa, ALSA/Pulse, FreeType and Fontconfig) are expected on +# the user's machine and are NOT bundled. SDL3, OpenAL and minimal FFmpeg are bundled. +# +# Usage: +# scripts/build/linux/deploy-linux-generalsmd.sh [preset] [config] +# Override the staging dir with GGC_LINUX_RUNTIME_DIR. + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +preset="${1:-linux-generalsmd-sdl3-bgfx}" +config="${2:-Release}" +stage_dir="${GGC_LINUX_RUNTIME_DIR:-${repo_root}/stage-linux/GeneralsZH-linux-x64}" +bin="${repo_root}/build/${preset}/GeneralsMD/${config}/generalszh" + +if [ ! -x "${bin}" ]; then + echo "Error: binary not found or not executable: ${bin}" >&2 + exit 1 +fi + +rm -rf "${stage_dir}" +mkdir -p "${stage_dir}/libs" +cp -v "${bin}" "${stage_dir}/generalszh" + +# Bundle the binary's shared-library closure EXCEPT the libraries that must come +# from the host: glibc / libstdc++, and the display / GPU / audio stack that +# talks to the user's drivers and display server. This captures our FetchContent +# SDL3 / OpenAL and the bundled minimal FFmpeg (avcodec/format/util/swscale), +# which has no external codec deps, so the closure stays small. freetype / +# fontconfig - AND their whole sub-stack (libpng / brotli / harfbuzz / graphite / +# bz2 / z / expat) - are left to the host: bundling them would make the host's +# (possibly newer) freetype load our older copies via LD_LIBRARY_PATH, exactly +# the cross-distro ABI mixing we are trying to avoid. They are present on every +# desktop distro (they are a hard host requirement already). +host_lib_re='^(ld-linux.*|libc\.so.*|libm\.so.*|libdl\.so.*|libpthread\.so.*|librt\.so.*|libutil\.so.*|libresolv\.so.*|libanl\.so.*|libnss_.*|libnsl\.so.*|libstdc\+\+\.so.*|libgcc_s\.so.*|libfreetype\.so.*|libfontconfig\.so.*|libexpat\.so.*|libpng.*|libbz2\.so.*|libz\.so.*|libbrotli.*|libharfbuzz.*|libgraphite.*|libX.*|libxcb.*|libxshmfence.*|libwayland.*|libvulkan\.so.*|libGL.*|libEGL.*|libGLX.*|libGLdispatch.*|libOpenGL.*|libglapi.*|libgbm.*|libdrm.*|libva.*|libvdpau.*|libasound\.so.*|libpulse.*|libpulsecommon.*|libjack.*|libpipewire.*|libglvnd.*)$' + +# Seed LD_LIBRARY_PATH from the binary's own RUNPATH (the FFmpeg prefix and the +# FetchContent build dirs), so ldd resolves the FULL transitive closure - notably +# libswresample, which libavcodec needs but the game never links directly. A +# DT_RUNPATH only resolves an object's own direct deps, so without this the +# transitive libs show up as "not found" and would be missed. +runpaths="$(readelf -d "${bin}" 2>/dev/null | sed -n 's/.*R\(UN\)\?PATH.*\[\(.*\)\]/\2/p')" +if [ -n "${runpaths}" ]; then + export LD_LIBRARY_PATH="${runpaths}:${LD_LIBRARY_PATH:-}" +fi + +ldd "${bin}" | awk '/=>/ && $3 ~ /\// { print $3 }' | while read -r lib; do + name="$(basename "${lib}")" + if [[ "${name}" =~ ${host_lib_re} ]]; then + continue + fi + cp -vL "${lib}" "${stage_dir}/libs/" +done + +# Fail loudly if any needed library could not be resolved (would ship a broken zip). +if ldd "${bin}" | grep -q "not found"; then + echo "ERROR: unresolved shared libraries:" >&2 + ldd "${bin}" | grep "not found" >&2 + exit 1 +fi + +cat > "${stage_dir}/run.sh" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +here="$(cd "$(dirname "$0")" && pwd)" +cd "${here}" +export LD_LIBRARY_PATH="${here}/libs:${LD_LIBRARY_PATH:-}" +exec "${here}/generalszh" "$@" +EOF +chmod +x "${stage_dir}/run.sh" + +# Ship the WINE-migration helper so players can pull their existing .big assets +# over without reinstalling. +cp -v "${repo_root}/scripts/build/linux/import-from-wine.sh" "${stage_dir}/import-from-wine.sh" +chmod +x "${stage_dir}/import-from-wine.sh" + +# Ship the default render settings (sun shadow map on) unless one already exists. +mkdir -p "${stage_dir}/Data/INI" +cp -n "${repo_root}/scripts/build/dist/Bgfx.ini" "${stage_dir}/Data/INI/Bgfx.ini" + +# Keep the distributed runtime self-contained and preserve the license and +# installation instructions beside the binary. +cp -f "${repo_root}/LICENSE.md" "${stage_dir}/LICENSE.md" +cp -f "${repo_root}/INSTALLING.md" "${stage_dir}/INSTALLING.md" + +echo "Staged Linux build to: ${stage_dir}" +ls -la "${stage_dir}" "${stage_dir}/libs" diff --git a/scripts/build/linux/import-from-wine.sh b/scripts/build/linux/import-from-wine.sh new file mode 100755 index 00000000000..b7e31dbc082 --- /dev/null +++ b/scripts/build/linux/import-from-wine.sh @@ -0,0 +1,348 @@ +#!/usr/bin/env bash +set -euo pipefail + +# TheSuperHackers @build bobtista 25/07/2026 Migrate an existing WINE / Proton / +# Steam / Lutris Command & Conquer Generals + Zero Hour install to this native +# build. Copies the game's *.big assets next to this binary so you can play +# without reinstalling from disc, along with the loose Data folders the archives +# do not carry. With --with-saves it also imports options.ini and your saved +# games / replays from the WINE prefix's user-data folder. +# +# Usage: +# ./import-from-wine.sh [--with-saves] [--no-data] [--prefix DIR] [--dry-run] +# [--force] +# +# --with-saves Also copy options.ini, Save/ and Replays/ into the native +# user-data folder (~/.local/share/Command and Conquer +# Generals Zero Hour Data/). +# --no-data Skip the loose Data folders and copy only the .big archives. +# --prefix DIR Add a custom search root (a WINE prefix, a Steam library, or +# the game folder itself). May be given more than once. The +# WINEPREFIX environment variable is honored too. +# --dry-run Show what would be copied without copying anything. +# --force Overwrite files that already exist here. + +here="$(cd "$(dirname "$0")" && pwd)" +data_dir="${XDG_DATA_HOME:-${HOME}/.local/share}/Command and Conquer Generals Zero Hour Data" + +with_saves=0 +with_data=1 +dry_run=0 +force=0 +declare -a extra_roots=() + +usage() +{ + cat <<'USAGE' +Migrate an existing WINE / Proton / Steam / Lutris Generals + Zero Hour install +to this native build (copies the game's *.big assets and the loose Data folders +next to this binary). + +Usage: ./import-from-wine.sh [--with-saves] [--no-data] [--prefix DIR] + [--dry-run] [--force] + + --with-saves Also import options.ini, Save/ and Replays/ from the WINE + prefix's user-data folder. + --no-data Skip the loose Data folders and copy only the .big archives. + --prefix DIR Add a custom search root (may be repeated). WINEPREFIX is + honored too. + --dry-run Show what would be copied without copying anything. + --force Overwrite files that already exist here. +USAGE +} + +while [ $# -gt 0 ]; do + case "$1" in + --with-saves) + with_saves=1 + ;; + --no-data) + with_data=0 + ;; + --dry-run) + dry_run=1 + ;; + --force) + force=1 + ;; + --prefix) + shift + [ $# -gt 0 ] || { echo "--prefix needs a directory" >&2; exit 1; } + extra_roots+=("$1") + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage + exit 1 + ;; + esac + shift +done + +# TheSuperHackers @build bobtista 28/07/2026 Steam's install root depends on the +# distro and packaging - ~/.steam/steam upstream, ~/.steam/root, and +# ~/.steam/debian-installation on Debian / Ubuntu / Mint - so glob over all of +# them rather than listing a fixed few, and read each libraryfolders.vdf so games +# installed on a second drive are found without --prefix. Echoes one resolved +# steamapps directory per line. +find_steamapps_dirs() +{ + local candidates=() + local seen_dirs="" + local d real vdf lib + + shopt -s nullglob + candidates+=("${HOME}"/.steam/*/steamapps) + candidates+=("${HOME}"/.local/share/Steam/steamapps) + candidates+=("${HOME}"/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps) + candidates+=(/home/deck/.local/share/Steam/steamapps) + # Steam Deck / external drives mount microSD and USB libraries under + # /run/media (either /run/media/