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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Generals/Code/CompatLib/Include/d3dx8math.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ typedef struct D3DXVECTOR4

#define D3DX_PI 3.141592654f

D3DXMATRIX *WINAPI D3DXMatrixIdentity(D3DXMATRIX *pOut);
D3DXMATRIX *WINAPI D3DXMatrixInverse(D3DXMATRIX *pOut, FLOAT *pDeterminant, CONST D3DXMATRIX *pM);
D3DXMATRIX *WINAPI D3DXMatrixScaling(D3DXMATRIX *pOut, FLOAT sx, FLOAT sy, FLOAT sz);
D3DXMATRIX *WINAPI D3DXMatrixTranslation(D3DXMATRIX *pOut, FLOAT x, FLOAT y, FLOAT z);
Expand Down
4 changes: 4 additions & 0 deletions GeneralsMD/Code/CompatLib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ target_include_directories(d3dx8 PUBLIC
${CMAKE_SOURCE_DIR}/Core/Libraries/Source/WWVegas/WWLib
)

if(SAGE_USE_DETERMINISTIC_MATH)
target_link_libraries(d3dx8 PUBLIC gamemath)
endif()

# Link d3dx8 to d3d8lib to get DirectX8 headers (min-dx8-sdk or DXVK)
target_link_libraries(d3dx8 PUBLIC d3d8lib)

Expand Down
1 change: 1 addition & 0 deletions GeneralsMD/Code/CompatLib/Include/d3dx8math.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ typedef struct D3DXVECTOR4

#define D3DX_PI 3.141592654f

D3DXMATRIX *WINAPI D3DXMatrixIdentity(D3DXMATRIX *pOut);
D3DXMATRIX *WINAPI D3DXMatrixInverse(D3DXMATRIX *pOut, FLOAT *pDeterminant, CONST D3DXMATRIX *pM);
D3DXMATRIX *WINAPI D3DXMatrixScaling(D3DXMATRIX *pOut, FLOAT sx, FLOAT sy, FLOAT sz);
D3DXMATRIX *WINAPI D3DXMatrixTranslation(D3DXMATRIX *pOut, FLOAT x, FLOAT y, FLOAT z);
Expand Down
337 changes: 190 additions & 147 deletions GeneralsMD/Code/CompatLib/Source/d3dx8math.cpp
Original file line number Diff line number Diff line change
@@ -1,147 +1,190 @@
// GLM_ENABLE_EXPERIMENTAL is passed via target_compile_definitions in CMakeLists.txt.
// Do NOT redefine it here -- that causes -Wmacro-redefined with Clang.

#ifdef _WIN32
#include <windows.h>
#else
#include "windows_compat.h"
#endif
#include "d3dx8core.h"

#include "d3dx8math.h"

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>


static void ConvertGLMToD3DX (const glm::mat4x4 &glm, D3DXMATRIX &d3dx)
{
d3dx._11 = glm[0][0];
d3dx._12 = glm[1][0];
d3dx._13 = glm[2][0];
d3dx._14 = glm[3][0];

d3dx._21 = glm[0][1];
d3dx._22 = glm[1][1];
d3dx._23 = glm[2][1];
d3dx._24 = glm[3][1];

d3dx._31 = glm[0][2];
d3dx._32 = glm[1][2];
d3dx._33 = glm[2][2];
d3dx._34 = glm[3][2];

d3dx._41 = glm[0][3];
d3dx._42 = glm[1][3];
d3dx._43 = glm[2][3];
d3dx._44 = glm[3][3];
}

static void ConvertD3DXToGLM (const D3DXMATRIX &d3dx, glm::mat4x4 &glm)
{
glm[0][0] = d3dx._11;
glm[1][0] = d3dx._12;
glm[2][0] = d3dx._13;
glm[3][0] = d3dx._14;

glm[0][1] = d3dx._21;
glm[1][1] = d3dx._22;
glm[2][1] = d3dx._23;
glm[3][1] = d3dx._24;

glm[0][2] = d3dx._31;
glm[1][2] = d3dx._32;
glm[2][2] = d3dx._33;
glm[3][2] = d3dx._34;

glm[0][3] = d3dx._41;
glm[1][3] = d3dx._42;
glm[2][3] = d3dx._43;
glm[3][3] = d3dx._44;
}

D3DXMATRIX *WINAPI D3DXMatrixInverse(D3DXMATRIX *pOut, FLOAT *pDeterminant, CONST D3DXMATRIX *pM)
{
glm::mat4x4 m;
ConvertD3DXToGLM(*pM, m);

if (pDeterminant)
*pDeterminant = glm::determinant(m);

glm::mat4x4 inv = glm::inverse(m);
ConvertGLMToD3DX(inv, *pOut);

return pOut;
}

D3DXMATRIX *WINAPI D3DXMatrixScaling(D3DXMATRIX *pOut, FLOAT sx, FLOAT sy, FLOAT sz)
{
glm::mat4x4 m = glm::scale(glm::mat4x4(1.0f), glm::vec3(sx, sy, sz));
ConvertGLMToD3DX(m, *pOut);
return pOut;
}

D3DXMATRIX *WINAPI D3DXMatrixTranslation(D3DXMATRIX *pOut, FLOAT x, FLOAT y, FLOAT z)
{
glm::mat4x4 m = glm::translate(glm::mat4x4(1.0f), glm::vec3(x, y, z));
ConvertGLMToD3DX(m, *pOut);
return pOut;
}

D3DXMATRIX *WINAPI D3DXMatrixMultiply(D3DXMATRIX *pOut, CONST D3DXMATRIX *pM1, CONST D3DXMATRIX *pM2)
{
glm::mat4x4 m1, m2;
ConvertD3DXToGLM(*pM1, m1);
ConvertD3DXToGLM(*pM2, m2);

glm::mat4x4 m = m1 * m2;
ConvertGLMToD3DX(m, *pOut);
return pOut;
}

D3DXVECTOR4 *WINAPI D3DXVec3Transform(D3DXVECTOR4 *pOut, CONST D3DXVECTOR3 *pV, CONST D3DXMATRIX *pM)
{
glm::vec4 v(pV->x, pV->y, pV->z, 1.0f);
glm::mat4x4 m;
ConvertD3DXToGLM(*pM, m);

glm::vec4 result = m * v;
pOut->x = result.x;
pOut->y = result.y;
pOut->z = result.z;
pOut->w = result.w;
return pOut;
}

D3DXMATRIX *WINAPI D3DXMatrixTranspose(D3DXMATRIX *pOut, CONST D3DXMATRIX *pM)
{
glm::mat4x4 m;
ConvertD3DXToGLM(*pM, m);

glm::mat4x4 mTransposed;
mTransposed = glm::transpose(m);

ConvertGLMToD3DX(mTransposed, *pOut);
return pOut;
}

D3DXMATRIX *WINAPI D3DXMatrixRotationZ(D3DXMATRIX *pOut, FLOAT Angle)
{
glm::mat4x4 m = glm::rotate(glm::mat4x4(1.0f), Angle, glm::vec3(0.0f, 0.0f, 1.0f));
ConvertGLMToD3DX(m, *pOut);
return pOut;
}

D3DXVECTOR4 *WINAPI D3DXVec4Transform(D3DXVECTOR4 *pOut, CONST D3DXVECTOR4 *pV, CONST D3DXMATRIX *pM)
{
glm::vec4 v(pV->x, pV->y, pV->z, pV->w);
glm::mat4x4 m;
ConvertD3DXToGLM(*pM, m);

glm::vec4 result = m * v;
pOut->x = result.x;
pOut->y = result.y;
pOut->z = result.z;
pOut->w = result.w;
return pOut;
}
// GLM_ENABLE_EXPERIMENTAL is passed via target_compile_definitions in CMakeLists.txt.
// Do NOT redefine it here -- that causes -Wmacro-redefined with Clang.

#ifdef _WIN32
#include <windows.h>
#else
#include "windows_compat.h"
#endif
#include "d3dx8core.h"

#include "d3dx8math.h"

#if defined(__has_include) && __has_include("gmath.h")
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmacro-redefined"
#endif
#include "gmath.h"
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#define USE_DETERMINISTIC_MATH (1)
#endif

#include <cmath>
#include <glm/glm.hpp>

// GeneralsX @bugfix fbraz 27/09/2026 Direct D3DX row-major matrix operations to fix cloud shadows, UV animation, and transformations

static void ConvertGLMToD3DX (const glm::mat4x4 &glm, D3DXMATRIX &d3dx)
{
d3dx._11 = glm[0][0];
d3dx._12 = glm[1][0];
d3dx._13 = glm[2][0];
d3dx._14 = glm[3][0];

d3dx._21 = glm[0][1];
d3dx._22 = glm[1][1];
d3dx._23 = glm[2][1];
d3dx._24 = glm[3][1];

d3dx._31 = glm[0][2];
d3dx._32 = glm[1][2];
d3dx._33 = glm[2][2];
d3dx._34 = glm[3][2];

d3dx._41 = glm[0][3];
d3dx._42 = glm[1][3];
d3dx._43 = glm[2][3];
d3dx._44 = glm[3][3];
}

static void ConvertD3DXToGLM (const D3DXMATRIX &d3dx, glm::mat4x4 &glm)
{
glm[0][0] = d3dx._11;
glm[1][0] = d3dx._12;
glm[2][0] = d3dx._13;
glm[3][0] = d3dx._14;

glm[0][1] = d3dx._21;
glm[1][1] = d3dx._22;
glm[2][1] = d3dx._23;
glm[3][1] = d3dx._24;

glm[0][2] = d3dx._31;
glm[1][2] = d3dx._32;
glm[2][2] = d3dx._33;
glm[3][2] = d3dx._34;

glm[0][3] = d3dx._41;
glm[1][3] = d3dx._42;
glm[2][3] = d3dx._43;
glm[3][3] = d3dx._44;
}

D3DXMATRIX *WINAPI D3DXMatrixIdentity(D3DXMATRIX *pOut)
{
if (!pOut) return nullptr;
pOut->m[0][0] = 1.0f; pOut->m[0][1] = 0.0f; pOut->m[0][2] = 0.0f; pOut->m[0][3] = 0.0f;
pOut->m[1][0] = 0.0f; pOut->m[1][1] = 1.0f; pOut->m[1][2] = 0.0f; pOut->m[1][3] = 0.0f;
pOut->m[2][0] = 0.0f; pOut->m[2][1] = 0.0f; pOut->m[2][2] = 1.0f; pOut->m[2][3] = 0.0f;
pOut->m[3][0] = 0.0f; pOut->m[3][1] = 0.0f; pOut->m[3][2] = 0.0f; pOut->m[3][3] = 1.0f;
return pOut;
}

D3DXMATRIX *WINAPI D3DXMatrixInverse(D3DXMATRIX *pOut, FLOAT *pDeterminant, CONST D3DXMATRIX *pM)
{
if (!pOut || !pM) return nullptr;
glm::mat4x4 m;
ConvertD3DXToGLM(*pM, m);

if (pDeterminant)
*pDeterminant = glm::determinant(m);

glm::mat4x4 inv = glm::inverse(m);
ConvertGLMToD3DX(inv, *pOut);

return pOut;
}

D3DXMATRIX *WINAPI D3DXMatrixScaling(D3DXMATRIX *pOut, FLOAT sx, FLOAT sy, FLOAT sz)
{
if (!pOut) return nullptr;
pOut->m[0][0] = sx; pOut->m[0][1] = 0.0f; pOut->m[0][2] = 0.0f; pOut->m[0][3] = 0.0f;
pOut->m[1][0] = 0.0f; pOut->m[1][1] = sy; pOut->m[1][2] = 0.0f; pOut->m[1][3] = 0.0f;
pOut->m[2][0] = 0.0f; pOut->m[2][1] = 0.0f; pOut->m[2][2] = sz; pOut->m[2][3] = 0.0f;
pOut->m[3][0] = 0.0f; pOut->m[3][1] = 0.0f; pOut->m[3][2] = 0.0f; pOut->m[3][3] = 1.0f;
return pOut;
}

D3DXMATRIX *WINAPI D3DXMatrixTranslation(D3DXMATRIX *pOut, FLOAT x, FLOAT y, FLOAT z)
{
if (!pOut) return nullptr;
pOut->m[0][0] = 1.0f; pOut->m[0][1] = 0.0f; pOut->m[0][2] = 0.0f; pOut->m[0][3] = 0.0f;
pOut->m[1][0] = 0.0f; pOut->m[1][1] = 1.0f; pOut->m[1][2] = 0.0f; pOut->m[1][3] = 0.0f;
pOut->m[2][0] = 0.0f; pOut->m[2][1] = 0.0f; pOut->m[2][2] = 1.0f; pOut->m[2][3] = 0.0f;
pOut->m[3][0] = x; pOut->m[3][1] = y; pOut->m[3][2] = z; pOut->m[3][3] = 1.0f;
return pOut;
}

D3DXMATRIX *WINAPI D3DXMatrixMultiply(D3DXMATRIX *pOut, CONST D3DXMATRIX *pM1, CONST D3DXMATRIX *pM2)
{
if (!pOut || !pM1 || !pM2) return nullptr;
D3DXMATRIX temp;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
temp.m[i][j] = pM1->m[i][0] * pM2->m[0][j] +
pM1->m[i][1] * pM2->m[1][j] +
pM1->m[i][2] * pM2->m[2][j] +
pM1->m[i][3] * pM2->m[3][j];
}
}
*pOut = temp;
return pOut;
}

D3DXVECTOR4 *WINAPI D3DXVec3Transform(D3DXVECTOR4 *pOut, CONST D3DXVECTOR3 *pV, CONST D3DXMATRIX *pM)
{
if (!pOut || !pV || !pM) return nullptr;
D3DXVECTOR4 temp;
temp.x = pV->x * pM->m[0][0] + pV->y * pM->m[1][0] + pV->z * pM->m[2][0] + pM->m[3][0];
temp.y = pV->x * pM->m[0][1] + pV->y * pM->m[1][1] + pV->z * pM->m[2][1] + pM->m[3][1];
temp.z = pV->x * pM->m[0][2] + pV->y * pM->m[1][2] + pV->z * pM->m[2][2] + pM->m[3][2];
temp.w = pV->x * pM->m[0][3] + pV->y * pM->m[1][3] + pV->z * pM->m[2][3] + pM->m[3][3];
*pOut = temp;
return pOut;
}

D3DXMATRIX *WINAPI D3DXMatrixTranspose(D3DXMATRIX *pOut, CONST D3DXMATRIX *pM)
{
if (!pOut || !pM) return nullptr;
D3DXMATRIX temp;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
temp.m[i][j] = pM->m[j][i];
}
}
*pOut = temp;
return pOut;
}

D3DXMATRIX *WINAPI D3DXMatrixRotationZ(D3DXMATRIX *pOut, FLOAT Angle)
{
if (!pOut) return nullptr;
#if defined(USE_DETERMINISTIC_MATH)
FLOAT fSin = gm_sinf(Angle);
FLOAT fCos = gm_cosf(Angle);
#else
FLOAT fSin = sinf(Angle);
FLOAT fCos = cosf(Angle);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
#endif

pOut->m[0][0] = fCos; pOut->m[0][1] = fSin; pOut->m[0][2] = 0.0f; pOut->m[0][3] = 0.0f;
pOut->m[1][0] = -fSin; pOut->m[1][1] = fCos; pOut->m[1][2] = 0.0f; pOut->m[1][3] = 0.0f;
pOut->m[2][0] = 0.0f; pOut->m[2][1] = 0.0f; pOut->m[2][2] = 1.0f; pOut->m[2][3] = 0.0f;
pOut->m[3][0] = 0.0f; pOut->m[3][1] = 0.0f; pOut->m[3][2] = 0.0f; pOut->m[3][3] = 1.0f;
return pOut;
}

D3DXVECTOR4 *WINAPI D3DXVec4Transform(D3DXVECTOR4 *pOut, CONST D3DXVECTOR4 *pV, CONST D3DXMATRIX *pM)
{
if (!pOut || !pV || !pM) return nullptr;
D3DXVECTOR4 temp;
temp.x = pV->x * pM->m[0][0] + pV->y * pM->m[1][0] + pV->z * pM->m[2][0] + pV->w * pM->m[3][0];
temp.y = pV->x * pM->m[0][1] + pV->y * pM->m[1][1] + pV->z * pM->m[2][1] + pV->w * pM->m[3][1];
temp.z = pV->x * pM->m[0][2] + pV->y * pM->m[1][2] + pV->z * pM->m[2][2] + pV->w * pM->m[3][2];
temp.w = pV->x * pM->m[0][3] + pV->y * pM->m[1][3] + pV->z * pM->m[2][3] + pV->w * pM->m[3][3];
*pOut = temp;
return pOut;
}
24 changes: 24 additions & 0 deletions docs/WORKLOG/2026-09-DIARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,30 @@
> **AI-Generated Content Disclosure**: This worklog is automatically generated and maintained by AI coding agents to document daily progress, debugging sessions, and technical decisions.

## 27/09/2026
### Fix Cloud Shadows Animation & D3DX Row-Major Matrix Operations
- **Context**: In Command & Conquer: Generals and Zero Hour on Windows, animated cloud shadows scroll across the terrain when the cloud shadow setting (`UseCloudMap`) is enabled. On macOS and Linux, cloud shadows were completely static or invisible despite the option being enabled in `Options.ini` and Game LOD settings.
- **Root Cause**:
- In `GeneralsMD/Code/CompatLib/Source/d3dx8math.cpp`, `D3DXMatrixTranslation`, `D3DXMatrixMultiply`, `D3DXMatrixRotationZ`, `D3DXVec3Transform`, and `D3DXVec4Transform` were implemented via GLM matrix functions assuming OpenGL column-vector conventions ($v' = M \cdot v$).
- In GLM, `glm::translate` places translation offsets $(x, y, z)$ into column 3 (`m[3][0]=x, m[3][1]=y, m[3][2]=z`). When converted to `D3DXMATRIX`, translation was mapped to column 4 (`_14, _24, _34`) instead of row 4 (`_41, _42, _43`) required by DirectX row-vector conventions ($v' = v \cdot M$).
- In addition, `ConvertD3DXToGLM` and `ConvertGLMToD3DX` caused `m1 * m2` in GLM to evaluate $(M_1^T \cdot M_2^T)^T = M_2 \cdot M_1$, reversing matrix multiplication order.
- When `TerrainShader2Stage::updateNoise1` calculated `*destMatrix = *curViewInverse * scale; *destMatrix *= offset;`, the combined matrix had 0 offset in `_41` and `_42`. In DXVK's fixed function vertex shader generator (`d3d9_fixed_function.cpp`), `D3DTS_TEXTURE2` transforms camera-space position into texture coordinates $(u, v)$ using row-major multiplication. Because `_41` and `_42` were zero, the animated sliding offset $(m\_xOffset, m\_yOffset)$ was completely lost.
- `D3DXMatrixRotationZ` also inverted rotation direction in `pointgr.cpp` particle rendering due to column-major vs row-major sine sign mapping.
- **Changes**:
- **Direct Row-Major D3DX Implementations (`GeneralsMD/Code/CompatLib/Source/d3dx8math.cpp`)**:
- Replaced GLM helper delegation in `D3DXMatrixTranslation`, `D3DXMatrixScaling`, `D3DXMatrixMultiply`, `D3DXMatrixTranspose`, `D3DXMatrixRotationZ`, `D3DXVec3Transform`, and `D3DXVec4Transform` with direct, row-major DirectX implementations.
- Implemented `D3DXMatrixIdentity` directly.
- `D3DXMatrixTranslation` explicitly sets `m[3][0] = x`, `m[3][1] = y`, `m[3][2] = z`, `m[3][3] = 1.0f`.
- `D3DXMatrixMultiply` explicitly computes standard row-major multiplication into a temporary matrix to safely handle in-place multiplication (`*destMatrix *= offset`).
- Retained `D3DXMatrixInverse` via GLM since matrix inversion and determinant are algebraic invariants under transpose mapping.
- Updated `D3DXMatrixRotationZ` to utilize deterministic GameMath (`gm_sinf`, `gm_cosf`) when `USE_DETERMINISTIC_MATH` is enabled, linking `gamemath` in `GeneralsMD/Code/CompatLib/CMakeLists.txt` to enforce cross-platform replay determinism.
- **Header Synchronization**:
- Added `D3DXMatrixIdentity` declaration to `GeneralsMD/Code/CompatLib/Include/d3dx8math.h` and `Generals/Code/CompatLib/Include/d3dx8math.h` for full parity with base game and `W3DWater.cpp`.
- **Validation**:
- Built `d3dx8` static library cleanly with Clang.
- Built `GeneralsXZH` (Zero Hour) and `GeneralsX` (base game) targets cleanly with exit code 0.
- Deployed binaries and dylibs to `~/GeneralsX/GeneralsZH/` and `~/GeneralsX/Generals/`.
- Executed matrix transform verification suite confirming that `destMatrix` translation row `_41` and `_42` correctly receive scaled world offsets and animated sliding values (`m_xOffset`, `m_yOffset`), whereas old GLM code lost offsets into column 4.

### Render OS Brand Glyphs in Lobby & Resolve CodeRabbit Review Findings (#338)
- **Context**: Players in online multiplayer lobbies display operating system tags (`[WIN]`, `[MAC]`, `[LNX]`). To improve visual polish in the lobby roster, player names are formatted to replace text OS tags with Font Awesome 6 Brands glyphs (`0xF179` Apple, `0xF17A` Windows, `0xF17C` Linux). A dedicated brand fallback mechanism was introduced via `AlternateBrandFont` in `FontCharsClass`.
- **CodeRabbit & Security Architecture Review Triage**:
Expand Down
Loading