diff --git a/CMakeLists.txt b/CMakeLists.txt index e6a43be6ed..8a6df1cc2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,9 @@ include(cmake/compilers.cmake) # Debug symbol stripping for Release builds (MinGW) include(cmake/debug_strip.cmake) +# D3DX dependency verification for NO_D3DX builds (MinGW) +include(cmake/no_d3dx_verify.cmake) + include(FetchContent) # MinGW-w64 specific configuration diff --git a/Core/Libraries/Include/Lib/D3DXCompat.h b/Core/Libraries/Include/Lib/D3DXCompat.h new file mode 100644 index 0000000000..6b0e0260be --- /dev/null +++ b/Core/Libraries/Include/Lib/D3DXCompat.h @@ -0,0 +1,939 @@ +/* +** 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 . +*/ + +/** + * D3DXCompat.h - D3DX8 compatibility layer using WWMath + * + * This header provides replacements for D3DX8 math functions using the existing + * WWMath library (Westwood Math). This eliminates the need for d3dx8.dll at runtime. + * + * Usage: Define NO_D3DX before including D3DX8 headers + */ + +#pragma once + +#ifdef NO_D3DX + +// Prevent min-dx8-sdk headers from defining D3DX functions/types +// by pre-defining their include guards (Option A: Include Guard Coordination) +// This allows our compatibility layer to be the sole provider of D3DX functionality +#ifndef __D3DX8_H__ +#define __D3DX8_H__ +#endif + +#ifndef __D3DX8CORE_H__ +#define __D3DX8CORE_H__ +#endif + +#ifndef __D3DX8EFFECT_H__ +#define __D3DX8EFFECT_H__ +#endif + +#ifndef __D3DX8MATH_H__ +#define __D3DX8MATH_H__ +#endif + +#ifndef __D3DX8MATH_INL__ +#define __D3DX8MATH_INL__ +#endif + +#ifndef __D3DX8MESH_H__ +#define __D3DX8MESH_H__ +#endif + +#ifndef __D3DX8SHAPES_H__ +#define __D3DX8SHAPES_H__ +#endif + +#ifndef __D3DX8TEX_H__ +#define __D3DX8TEX_H__ +#endif + +// Include D3D8 types +#include +#include +#include +#include +#include + +//----------------------------------------------------------------------------- +// D3DX Constants +//----------------------------------------------------------------------------- + +// Default values for D3DX functions +#ifndef D3DX_DEFAULT +#define D3DX_DEFAULT ULONG_MAX +#define D3DX_DEFAULT_FLOAT FLT_MAX +#endif + +// D3DX math constants +#ifndef D3DX_PI +#define D3DX_PI ((FLOAT) 3.141592654f) +#define D3DX_1BYPI ((FLOAT) 0.318309886f) +#define D3DXToRadian(degree) ((degree) * (D3DX_PI / 180.0f)) +#define D3DXToDegree(radian) ((radian) * (180.0f / D3DX_PI)) +#endif + +// D3DX_FILTER flags for texture operations +#ifndef D3DX_FILTER_NONE +#define D3DX_FILTER_NONE (1 << 0) +#define D3DX_FILTER_POINT (2 << 0) +#define D3DX_FILTER_LINEAR (3 << 0) +#define D3DX_FILTER_TRIANGLE (4 << 0) +#define D3DX_FILTER_BOX (5 << 0) + +#define D3DX_FILTER_MIRROR_U (1 << 16) +#define D3DX_FILTER_MIRROR_V (2 << 16) +#define D3DX_FILTER_MIRROR_W (4 << 16) +#define D3DX_FILTER_MIRROR (7 << 16) +#define D3DX_FILTER_DITHER (8 << 16) +#endif + +//----------------------------------------------------------------------------- + +// WWMath headers for D3DX math function implementations. +// CppMacros.h must be included first because vector3.h -> STLUtils.h uses CPP_11. +// D3DXWrapper.h is force-included via -include flag which runs before +// precompiled headers, so we cannot rely on PCH here. +#ifdef __cplusplus + #include "Utility/CppMacros.h" + #include "vector3.h" + #include "vector4.h" + #include "matrix3d.h" + #include "matrix4.h" +#endif + +// Forward declare D3DX types (we'll define compatibility layer) +typedef struct D3DXVECTOR3 D3DXVECTOR3; +typedef struct D3DXVECTOR4 D3DXVECTOR4; +typedef struct D3DXMATRIX D3DXMATRIX; + +// D3DX8 Vector and Matrix types - map to D3D types +// Note: D3DXVECTOR3 is identical to D3DVECTOR (Direct3D 8 type) +// Note: D3DXVECTOR4 is a simple {x,y,z,w} structure +// Note: D3DXMATRIX is identical to D3DMATRIX (Direct3D 8 type) + +#ifdef __cplusplus + +struct D3DXVECTOR3 +{ + float x, y, z; + + D3DXVECTOR3() {} + D3DXVECTOR3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {} + D3DXVECTOR3(const Vector3& v) : x(v.X), y(v.Y), z(v.Z) {} + + operator Vector3() const { return Vector3(x, y, z); } + + // Array access operator for compatibility + float& operator[](int i) { return (&x)[i]; } + const float& operator[](int i) const { return (&x)[i]; } +}; + +struct D3DXVECTOR4 +{ + float x, y, z, w; + + D3DXVECTOR4() {} + D3DXVECTOR4(float _x, float _y, float _z, float _w) : x(_x), y(_y), z(_z), w(_w) {} + D3DXVECTOR4(const Vector4& v) : x(v.X), y(v.Y), z(v.Z), w(v.W) {} + + operator Vector4() const { return Vector4(x, y, z, w); } + + // Conversion to pointer for passing to D3D functions + operator const float*() const { return &x; } + operator float*() { return &x; } + + // Array access operator for compatibility + float& operator[](int i) { return (&x)[i]; } + const float& operator[](int i) const { return (&x)[i]; } +}; + +struct D3DXMATRIX : public D3DMATRIX +{ + D3DXMATRIX() {} + + // Constructor from 16 float values (row-major order) + D3DXMATRIX(float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33) + { + // D3DMATRIX uses _11, _12, ... _44 notation + _11 = m00; _12 = m01; _13 = m02; _14 = m03; + _21 = m10; _22 = m11; _23 = m12; _24 = m13; + _31 = m20; _32 = m21; _33 = m22; _34 = m23; + _41 = m30; _42 = m31; _43 = m32; _44 = m33; + } + + D3DXMATRIX(const Matrix4x4& m) + { + // Matrix4x4 and D3DMATRIX have transposed layouts - use proper conversion + // See To_D3DMATRIX in matrix4.cpp for reference + _11 = m[0][0]; _12 = m[1][0]; _13 = m[2][0]; _14 = m[3][0]; + _21 = m[0][1]; _22 = m[1][1]; _23 = m[2][1]; _24 = m[3][1]; + _31 = m[0][2]; _32 = m[1][2]; _33 = m[2][2]; _34 = m[3][2]; + _41 = m[0][3]; _42 = m[1][3]; _43 = m[2][3]; _44 = m[3][3]; + } + + operator Matrix4x4() const + { + // D3DMATRIX and Matrix4x4 have transposed layouts - use proper conversion + // See To_Matrix4x4 in matrix4.cpp for reference + Matrix4x4 result; + result[0][0] = _11; result[0][1] = _21; result[0][2] = _31; result[0][3] = _41; + result[1][0] = _12; result[1][1] = _22; result[1][2] = _32; result[1][3] = _42; + result[2][0] = _13; result[2][1] = _23; result[2][2] = _33; result[2][3] = _43; + result[3][0] = _14; result[3][1] = _24; result[3][2] = _34; result[3][3] = _44; + return result; + } + + // Casting operators + operator float*() { return reinterpret_cast(&_11); } + operator const float*() const { return reinterpret_cast(&_11); } + + // operator*= for matrix multiplication (native implementation) + D3DXMATRIX& operator*=(const D3DXMATRIX& other) + { + D3DXMATRIX temp; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + temp.m[i][j] = m[i][0] * other.m[0][j] + + m[i][1] * other.m[1][j] + + m[i][2] * other.m[2][j] + + m[i][3] * other.m[3][j]; + } + } + *this = temp; + return *this; + } +}; + +//============================================================================= +// D3DX8 Math Functions - Native Compatibility Layer +//============================================================================= + +//----------------------------------------------------------------------------- +// Vector4 Operations +//----------------------------------------------------------------------------- + +// D3DXVec4Dot - Compute dot product of two 4D vectors +inline float D3DXVec4Dot(const D3DXVECTOR4* pV1, const D3DXVECTOR4* pV2) +{ + if (!pV1 || !pV2) + return 0.0f; + + Vector4 v1(pV1->x, pV1->y, pV1->z, pV1->w); + Vector4 v2(pV2->x, pV2->y, pV2->z, pV2->w); + + return Vector4::Dot_Product(v1, v2); +} + +// D3DXVec4Transform - Transform 4D vector by 4x4 matrix +// D3D convention: row vector * matrix, i.e. [x,y,z,w] * M +inline D3DXVECTOR4* D3DXVec4Transform( + D3DXVECTOR4* pOut, + const D3DXVECTOR4* pV, + const D3DXMATRIX* pM) +{ + if (!pOut || !pV || !pM) + return pOut; + + // D3D uses row vectors: result = [x,y,z,w] * M + float x = pV->x, y = pV->y, z = pV->z, w = pV->w; + pOut->x = x * pM->_11 + y * pM->_21 + z * pM->_31 + w * pM->_41; + pOut->y = x * pM->_12 + y * pM->_22 + z * pM->_32 + w * pM->_42; + pOut->z = x * pM->_13 + y * pM->_23 + z * pM->_33 + w * pM->_43; + pOut->w = x * pM->_14 + y * pM->_24 + z * pM->_34 + w * pM->_44; + + return pOut; +} + +//----------------------------------------------------------------------------- +// Vector3 Operations +//----------------------------------------------------------------------------- + +// D3DXVec3Transform - Transform 3D vector by 4x4 matrix (homogeneous) +// D3D convention: row vector * matrix, i.e. [x,y,z,1] * M +inline D3DXVECTOR4* D3DXVec3Transform( + D3DXVECTOR4* pOut, + const D3DXVECTOR3* pV, + const D3DXMATRIX* pM) +{ + if (!pOut || !pV || !pM) + return pOut; + + // D3D uses row vectors: result = [x,y,z,1] * M + float x = pV->x, y = pV->y, z = pV->z; + pOut->x = x * pM->_11 + y * pM->_21 + z * pM->_31 + pM->_41; + pOut->y = x * pM->_12 + y * pM->_22 + z * pM->_32 + pM->_42; + pOut->z = x * pM->_13 + y * pM->_23 + z * pM->_33 + pM->_43; + pOut->w = x * pM->_14 + y * pM->_24 + z * pM->_34 + pM->_44; + + return pOut; +} + +//----------------------------------------------------------------------------- +// Matrix Operations +//----------------------------------------------------------------------------- + +// D3DXMatrixTranspose - Transpose a 4x4 matrix +inline D3DXMATRIX* D3DXMatrixTranspose( + D3DXMATRIX* pOut, + const D3DXMATRIX* pM) +{ + if (!pOut || !pM) + return pOut; + + // Native transpose: out[i][j] = in[j][i] + // Use temp to handle in-place transpose (pOut == pM) + D3DXMATRIX temp; + temp._11 = pM->_11; temp._12 = pM->_21; temp._13 = pM->_31; temp._14 = pM->_41; + temp._21 = pM->_12; temp._22 = pM->_22; temp._23 = pM->_32; temp._24 = pM->_42; + temp._31 = pM->_13; temp._32 = pM->_23; temp._33 = pM->_33; temp._34 = pM->_43; + temp._41 = pM->_14; temp._42 = pM->_24; temp._43 = pM->_34; temp._44 = pM->_44; + *pOut = temp; + + return pOut; +} + +// D3DXMatrixInverse - Compute inverse of a 4x4 matrix +// Returns nullptr if the matrix is singular (determinant near zero). +inline D3DXMATRIX* D3DXMatrixInverse( + D3DXMATRIX* pOut, + float* pDeterminant, + const D3DXMATRIX* pM) +{ + if (!pOut || !pM) + return pOut; + + const float* m = static_cast(*pM); + float v[16], t[6], det; + + // Calculate pairs for first 8 cofactors + t[0] = m[10] * m[15] - m[11] * m[14]; + t[1] = m[9] * m[15] - m[11] * m[13]; + t[2] = m[9] * m[14] - m[10] * m[13]; + t[3] = m[8] * m[15] - m[11] * m[12]; + t[4] = m[8] * m[14] - m[10] * m[12]; + t[5] = m[8] * m[13] - m[9] * m[12]; + + // Calculate first 4 cofactors + v[0] = m[5] * t[0] - m[6] * t[1] + m[7] * t[2]; + v[4] = -(m[4] * t[0] - m[6] * t[3] + m[7] * t[4]); + v[8] = m[4] * t[1] - m[5] * t[3] + m[7] * t[5]; + v[12] = -(m[4] * t[2] - m[5] * t[4] + m[6] * t[5]); + + // Calculate determinant + det = m[0] * v[0] + m[1] * v[4] + m[2] * v[8] + m[3] * v[12]; + + if (pDeterminant) + *pDeterminant = det; + + // Check for singular matrix + if (fabsf(det) < 1e-10f) + return nullptr; + + // Calculate pairs for second 8 cofactors + t[0] = m[2] * m[7] - m[3] * m[6]; + t[1] = m[1] * m[7] - m[3] * m[5]; + t[2] = m[1] * m[6] - m[2] * m[5]; + t[3] = m[0] * m[7] - m[3] * m[4]; + t[4] = m[0] * m[6] - m[2] * m[4]; + t[5] = m[0] * m[5] - m[1] * m[4]; + + v[1] = -(m[1] * (m[10] * m[15] - m[11] * m[14]) - m[2] * (m[9] * m[15] - m[11] * m[13]) + m[3] * (m[9] * m[14] - m[10] * m[13])); + v[5] = m[0] * (m[10] * m[15] - m[11] * m[14]) - m[2] * (m[8] * m[15] - m[11] * m[12]) + m[3] * (m[8] * m[14] - m[10] * m[12]); + v[9] = -(m[0] * (m[9] * m[15] - m[11] * m[13]) - m[1] * (m[8] * m[15] - m[11] * m[12]) + m[3] * (m[8] * m[13] - m[9] * m[12])); + v[13] = m[0] * (m[9] * m[14] - m[10] * m[13]) - m[1] * (m[8] * m[14] - m[10] * m[12]) + m[2] * (m[8] * m[13] - m[9] * m[12]); + + v[2] = m[13] * t[0] - m[14] * t[1] + m[15] * t[2]; + v[6] = -(m[12] * t[0] - m[14] * t[3] + m[15] * t[4]); + v[10] = m[12] * t[1] - m[13] * t[3] + m[15] * t[5]; + v[14] = -(m[12] * t[2] - m[13] * t[4] + m[14] * t[5]); + + v[3] = -(m[9] * t[0] - m[10] * t[1] + m[11] * t[2]); + v[7] = m[8] * t[0] - m[10] * t[3] + m[11] * t[4]; + v[11] = -(m[8] * t[1] - m[9] * t[3] + m[11] * t[5]); + v[15] = m[8] * t[2] - m[9] * t[4] + m[10] * t[5]; + + // Divide by determinant + det = 1.0f / det; + float* out = static_cast(*pOut); + for (int i = 0; i < 16; i++) + out[i] = v[i] * det; + + return pOut; +} + +//----------------------------------------------------------------------------- +// Utility Functions +//----------------------------------------------------------------------------- + +// D3DXGetErrorStringA - Get error string for D3D error code +inline HRESULT D3DXGetErrorStringA(HRESULT hr, char* pBuffer, UINT BufferLen) +{ + if (!pBuffer || BufferLen == 0) + return E_INVALIDARG; + + const char* errorStr = nullptr; + + switch (hr) + { + case D3D_OK: + errorStr = "No error"; + break; + case D3DERR_WRONGTEXTUREFORMAT: + errorStr = "Wrong texture format"; + break; + case D3DERR_UNSUPPORTEDCOLOROPERATION: + errorStr = "Unsupported color operation"; + break; + case D3DERR_UNSUPPORTEDCOLORARG: + errorStr = "Unsupported color argument"; + break; + case D3DERR_UNSUPPORTEDALPHAOPERATION: + errorStr = "Unsupported alpha operation"; + break; + case D3DERR_UNSUPPORTEDALPHAARG: + errorStr = "Unsupported alpha argument"; + break; + case D3DERR_TOOMANYOPERATIONS: + errorStr = "Too many operations"; + break; + case D3DERR_CONFLICTINGTEXTUREFILTER: + errorStr = "Conflicting texture filter"; + break; + case D3DERR_UNSUPPORTEDFACTORVALUE: + errorStr = "Unsupported factor value"; + break; + case D3DERR_CONFLICTINGRENDERSTATE: + errorStr = "Conflicting render state"; + break; + case D3DERR_UNSUPPORTEDTEXTUREFILTER: + errorStr = "Unsupported texture filter"; + break; + case D3DERR_CONFLICTINGTEXTUREPALETTE: + errorStr = "Conflicting texture palette"; + break; + case D3DERR_DRIVERINTERNALERROR: + errorStr = "Driver internal error"; + break; + case D3DERR_NOTFOUND: + errorStr = "Not found"; + break; + case D3DERR_MOREDATA: + errorStr = "More data"; + break; + case D3DERR_DEVICELOST: + errorStr = "Device lost"; + break; + case D3DERR_DEVICENOTRESET: + errorStr = "Device not reset"; + break; + case D3DERR_NOTAVAILABLE: + errorStr = "Not available"; + break; + case D3DERR_OUTOFVIDEOMEMORY: + errorStr = "Out of video memory"; + break; + case D3DERR_INVALIDDEVICE: + errorStr = "Invalid device"; + break; + case D3DERR_INVALIDCALL: + errorStr = "Invalid call"; + break; + case D3DERR_DRIVERINVALIDCALL: + errorStr = "Driver invalid call"; + break; + case E_OUTOFMEMORY: + errorStr = "Out of memory"; + break; + default: + errorStr = "Unknown error"; + break; + } + + // Copy error string to buffer (ensure null termination) + strncpy(pBuffer, errorStr, BufferLen - 1); + pBuffer[BufferLen - 1] = '\0'; + + return D3D_OK; +} + +// D3DXGetFVFVertexSize - Calculate vertex size from FVF flags +inline UINT D3DXGetFVFVertexSize(DWORD FVF) +{ + UINT size = 0; + + // Position formats - use mask and switch since XYZB* bits overlap XYZ/XYZRHW + // (Wine d3dx9_36/mesh.c uses the same approach) + switch (FVF & D3DFVF_POSITION_MASK) + { + case D3DFVF_XYZ: size += 12; break; // 3 floats + case D3DFVF_XYZRHW: size += 16; break; // 4 floats (x, y, z, rhw) + case D3DFVF_XYZB1: size += 16; break; // 3 floats + 1 blend weight + case D3DFVF_XYZB2: size += 20; break; // 3 floats + 2 blend weights + case D3DFVF_XYZB3: size += 24; break; // 3 floats + 3 blend weights + case D3DFVF_XYZB4: size += 28; break; // 3 floats + 4 blend weights + case D3DFVF_XYZB5: size += 32; break; // 3 floats + 5 blend weights + default: break; + } + + // Normal + if (FVF & D3DFVF_NORMAL) size += 12; // 3 floats + + // Point size + if (FVF & D3DFVF_PSIZE) size += 4; // 1 float + + // Diffuse color + if (FVF & D3DFVF_DIFFUSE) size += 4; // 1 DWORD (D3DCOLOR) + + // Specular color + if (FVF & D3DFVF_SPECULAR) size += 4; // 1 DWORD (D3DCOLOR) + + // Texture coordinates + UINT texCount = (FVF & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT; + for (UINT i = 0; i < texCount; i++) + { + DWORD texFormat = (FVF >> (16 + i * 2)) & 0x3; + switch (texFormat) + { + case D3DFVF_TEXTUREFORMAT1: size += 4; break; // 1 float + case D3DFVF_TEXTUREFORMAT2: size += 8; break; // 2 floats + case D3DFVF_TEXTUREFORMAT3: size += 12; break; // 3 floats + case D3DFVF_TEXTUREFORMAT4: size += 16; break; // 4 floats + default: size += 8; break; // Default to 2 floats + } + } + + return size; +} + +//----------------------------------------------------------------------------- +// Matrix Operations (Additional) +//----------------------------------------------------------------------------- + +// D3DXMatrixMultiply - Multiply two matrices +inline D3DXMATRIX* D3DXMatrixMultiply( + D3DXMATRIX* pOut, + const D3DXMATRIX* pM1, + const D3DXMATRIX* pM2) +{ + if (!pOut || !pM1 || !pM2) + return pOut; + + // Native implementation - handles aliasing via temp + 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; +} + +// D3DXMatrixRotationZ - Create rotation matrix around Z axis +inline D3DXMATRIX* D3DXMatrixRotationZ(D3DXMATRIX* pOut, float angle) +{ + if (!pOut) + return pOut; + + float c = cosf(angle); + float s = sinf(angle); + + pOut->_11 = c; pOut->_12 = s; pOut->_13 = 0; pOut->_14 = 0; + pOut->_21 = -s; pOut->_22 = c; pOut->_23 = 0; pOut->_24 = 0; + pOut->_31 = 0; pOut->_32 = 0; pOut->_33 = 1; pOut->_34 = 0; + pOut->_41 = 0; pOut->_42 = 0; pOut->_43 = 0; pOut->_44 = 1; + + return pOut; +} + +// D3DXMatrixScaling - Create scaling matrix +inline D3DXMATRIX* D3DXMatrixScaling(D3DXMATRIX* pOut, float sx, float sy, float sz) +{ + if (!pOut) + return pOut; + + pOut->_11 = sx; pOut->_12 = 0; pOut->_13 = 0; pOut->_14 = 0; + pOut->_21 = 0; pOut->_22 = sy; pOut->_23 = 0; pOut->_24 = 0; + pOut->_31 = 0; pOut->_32 = 0; pOut->_33 = sz; pOut->_34 = 0; + pOut->_41 = 0; pOut->_42 = 0; pOut->_43 = 0; pOut->_44 = 1; + + return pOut; +} + +// D3DXMatrixTranslation - Create translation matrix +inline D3DXMATRIX* D3DXMatrixTranslation(D3DXMATRIX* pOut, float x, float y, float z) +{ + if (!pOut) + return pOut; + + pOut->_11 = 1; pOut->_12 = 0; pOut->_13 = 0; pOut->_14 = 0; + pOut->_21 = 0; pOut->_22 = 1; pOut->_23 = 0; pOut->_24 = 0; + pOut->_31 = 0; pOut->_32 = 0; pOut->_33 = 1; pOut->_34 = 0; + pOut->_41 = x; pOut->_42 = y; pOut->_43 = z; pOut->_44 = 1; + + return pOut; +} + +// D3DXMatrixIdentity - Initialize matrix to identity +inline D3DXMATRIX* D3DXMatrixIdentity(D3DXMATRIX* pOut) +{ + if (!pOut) + return pOut; + + pOut->_11 = 1; pOut->_12 = 0; pOut->_13 = 0; pOut->_14 = 0; + pOut->_21 = 0; pOut->_22 = 1; pOut->_23 = 0; pOut->_24 = 0; + pOut->_31 = 0; pOut->_32 = 0; pOut->_33 = 1; pOut->_34 = 0; + pOut->_41 = 0; pOut->_42 = 0; pOut->_43 = 0; pOut->_44 = 1; + + return pOut; +} + +//----------------------------------------------------------------------------- +// Shader Functions (Precompiled Shaders) +//----------------------------------------------------------------------------- + +// Forward declaration +struct ID3DXBuffer; + +// Precompiled shader bytecode (generated from .psh files) +namespace D3DXCompat_Shaders { + + // Note: These are simplified PS 1.1 bytecode arrays + // Generated by scripts/compile_shaders.py from scripts/shaders/water_shader*.psh files + // May need validation/correction for production use + + // River water shader (water_shader1.psh) + static constexpr DWORD shader1_bytecode[] = { + 0xFFFF0101, 0x00000042, 0x000F0300, 0x00000042, + 0x000F0301, 0x00000042, 0x000F0302, 0x00000042, + 0x000F0303, 0x00000005, 0x000F0000, 0x000F0100, + 0x000F0300, 0x00000005, 0x000F0001, 0x000F0301, + 0x000F0302, 0x00000003, 0x00070000, 0x000F0000, + 0x000F0303, 0x40000005, 0x00080000, 0x000F0000, + 0x000F0303, 0x00000003, 0x00070000, 0x000F0000, + 0x000F0001, 0x0000FFFF, + }; + + // Water with environment mapping (water_shader2.psh) + static constexpr DWORD shader2_bytecode[] = { + 0xFFFF0101, 0x00000042, 0x000F0300, 0x00000042, + 0x000F0301, 0x00000043, 0x000F0302, 0x000F0301, + 0x00000005, 0x000F0000, 0x000F0100, 0x000F0300, + 0x00000005, 0x00070001, 0x000F0302, 0x000F0200, + 0x00000003, 0x00070000, 0x000F0000, 0x000F0001, + 0x0000FFFF, + }; + + // Trapezoid water shader (water_shader3.psh) + static constexpr DWORD shader3_bytecode[] = { + 0xFFFF0101, 0x00000042, 0x000F0300, 0x00000042, + 0x000F0301, 0x00000042, 0x000F0302, 0x00000042, + 0x000F0303, 0x00000005, 0x000F0000, 0x000F0100, + 0x000F0300, 0x00000004, 0x00070000, 0x000F0301, + 0x000F0302, 0x000F0000, 0x00000005, 0x00070000, + 0x000F0000, 0x000F0303, 0x0000FFFF, + }; + +} // namespace D3DXCompat_Shaders + +// ID3DXBuffer interface definition +struct ID3DXBuffer +{ + virtual HRESULT __stdcall QueryInterface(const IID&, void**) = 0; + virtual ULONG __stdcall AddRef() = 0; + virtual ULONG __stdcall Release() = 0; + virtual void* __stdcall GetBufferPointer() = 0; + virtual DWORD __stdcall GetBufferSize() = 0; +}; + +// ID3DXBuffer implementation for shader bytecode +class D3DXShaderBuffer : public ID3DXBuffer +{ +private: + const DWORD* m_pData; + DWORD m_Size; + mutable LONG m_RefCount; + +public: + D3DXShaderBuffer(const DWORD* pData, DWORD size) + : m_pData(pData), m_Size(size), m_RefCount(1) {} + + virtual ~D3DXShaderBuffer() {} + + // IUnknown methods + virtual HRESULT __stdcall QueryInterface(const IID& riid, void** ppvObject) + { + if (!ppvObject) + return E_POINTER; + *ppvObject = nullptr; + return E_NOINTERFACE; + } + + virtual ULONG __stdcall AddRef() + { + return InterlockedIncrement(&m_RefCount); + } + + virtual ULONG __stdcall Release() + { + LONG ref = InterlockedDecrement(&m_RefCount); + if (ref == 0) + delete this; + return ref; + } + + // ID3DXBuffer methods + virtual void* __stdcall GetBufferPointer() + { + return (void*)m_pData; + } + + virtual DWORD __stdcall GetBufferSize() + { + return m_Size; + } +}; + +// D3DXAssembleShader - Returns precompiled bytecode for known water shaders. +// Recognizes the three water shaders used by the game and returns precompiled +// bytecode instead of performing runtime assembly. +inline HRESULT D3DXAssembleShader( + const char* pSrcData, + UINT SrcDataLen, + DWORD Flags, + void* ppConstants, + ID3DXBuffer** ppCompiledShader, + ID3DXBuffer** ppCompilationErrors) +{ + if (!pSrcData || !ppCompiledShader) + return D3DERR_INVALIDCALL; + + *ppCompiledShader = nullptr; + if (ppCompilationErrors) + *ppCompilationErrors = nullptr; + + // Ensure null-terminated copy for safe string operations. + // D3DX8 API allows non-null-terminated input bounded by SrcDataLen, + // matching Wine's D3DAssemble implementation which also null-terminates. + std::string srcStr(pSrcData, SrcDataLen); + const char* src = srcStr.c_str(); + + // Identify which shader is being assembled by matching key strings. + + // Shader 1: River water (has "+mul r0.a, r0, t3" - co-issued instruction) + if (strstr(src, "+mul r0.a") != nullptr) + { + *ppCompiledShader = new D3DXShaderBuffer( + D3DXCompat_Shaders::shader1_bytecode, + sizeof(D3DXCompat_Shaders::shader1_bytecode)); + return S_OK; + } + + // Shader 2: Water with env mapping (has "texbem") + if (strstr(src, "texbem") != nullptr) + { + *ppCompiledShader = new D3DXShaderBuffer( + D3DXCompat_Shaders::shader2_bytecode, + sizeof(D3DXCompat_Shaders::shader2_bytecode)); + return S_OK; + } + + // Shader 3: Trapezoid water (has "mad" instruction) + if (strstr(src, "mad") != nullptr) + { + *ppCompiledShader = new D3DXShaderBuffer( + D3DXCompat_Shaders::shader3_bytecode, + sizeof(D3DXCompat_Shaders::shader3_bytecode)); + return S_OK; + } + + // Unknown shader - return error. + // The game will handle this gracefully and use fallback rendering. + return E_FAIL; +} + +#endif // __cplusplus + +//----------------------------------------------------------------------------- +// Texture Functions (Direct3D 8 wrappers and stubs) +// These don't need WWMath +//----------------------------------------------------------------------------- + +#ifdef __cplusplus + +// D3DXCreateTexture - Direct wrapper for IDirect3DDevice8::CreateTexture +inline HRESULT D3DXCreateTexture( + LPDIRECT3DDEVICE8 pDevice, + UINT Width, + UINT Height, + UINT MipLevels, + DWORD Usage, + D3DFORMAT Format, + D3DPOOL Pool, + LPDIRECT3DTEXTURE8* ppTexture) +{ + if (!pDevice || !ppTexture) + return D3DERR_INVALIDCALL; + + // Direct D3D8 call - no D3DX involved! + return pDevice->CreateTexture(Width, Height, MipLevels, Usage, Format, Pool, ppTexture); +} + +// D3DXCreateCubeTexture - Direct wrapper for IDirect3DDevice8::CreateCubeTexture +inline HRESULT D3DXCreateCubeTexture( + LPDIRECT3DDEVICE8 pDevice, + UINT Size, + UINT MipLevels, + DWORD Usage, + D3DFORMAT Format, + D3DPOOL Pool, + LPDIRECT3DCUBETEXTURE8* ppCubeTexture) +{ + if (!pDevice || !ppCubeTexture) + return D3DERR_INVALIDCALL; + + // Direct D3D8 call + return pDevice->CreateCubeTexture(Size, MipLevels, Usage, Format, Pool, ppCubeTexture); +} + +// D3DXCreateVolumeTexture - Direct wrapper for IDirect3DDevice8::CreateVolumeTexture +inline HRESULT D3DXCreateVolumeTexture( + LPDIRECT3DDEVICE8 pDevice, + UINT Width, + UINT Height, + UINT Depth, + UINT MipLevels, + DWORD Usage, + D3DFORMAT Format, + D3DPOOL Pool, + LPDIRECT3DVOLUMETEXTURE8* ppVolumeTexture) +{ + if (!pDevice || !ppVolumeTexture) + return D3DERR_INVALIDCALL; + + // Direct D3D8 call + return pDevice->CreateVolumeTexture(Width, Height, Depth, MipLevels, Usage, Format, Pool, ppVolumeTexture); +} + +// D3DXCreateTextureFromFileExA - Stub (zero callers in codebase) +// Returns D3DERR_NOTAVAILABLE causing fallback to MissingTexture. +inline HRESULT D3DXCreateTextureFromFileExA( + LPDIRECT3DDEVICE8 pDevice, + LPCSTR pSrcFile, + UINT Width, + UINT Height, + UINT MipLevels, + DWORD Usage, + D3DFORMAT Format, + D3DPOOL Pool, + DWORD Filter, + DWORD MipFilter, + D3DCOLOR ColorKey, + void* pSrcInfo, + PALETTEENTRY* pPalette, + LPDIRECT3DTEXTURE8* ppTexture) +{ + // NOTE: Zero usage in codebase (verified via grep). + // Returning D3DERR_NOTAVAILABLE causes fallback to MissingTexture. + // Stub function acceptable for unused functionality. + return D3DERR_NOTAVAILABLE; +} + +// D3DXLoadSurfaceFromSurface - Copy surface data using D3D8's native CopyRects +inline HRESULT D3DXLoadSurfaceFromSurface( + LPDIRECT3DSURFACE8 pDestSurface, + const PALETTEENTRY* pDestPalette, + const RECT* pDestRect, + LPDIRECT3DSURFACE8 pSrcSurface, + const PALETTEENTRY* pSrcPalette, + const RECT* pSrcRect, + DWORD Filter, + D3DCOLOR ColorKey) +{ + if (!pDestSurface || !pSrcSurface) + return D3DERR_INVALIDCALL; + + // Get D3D8 device from source surface + IDirect3DDevice8* pDevice = nullptr; + HRESULT hr = pSrcSurface->GetDevice(&pDevice); + if (FAILED(hr)) + return hr; + + // Convert destination RECT to POINT for CopyRects API + POINT destPoint = {0, 0}; + if (pDestRect) { + destPoint.x = pDestRect->left; + destPoint.y = pDestRect->top; + } + + // Use D3D8's native hardware-accelerated CopyRects + // This is the same API used by DX8Wrapper::_Copy_DX8_Rects (14 uses in codebase) + hr = pDevice->CopyRects( + pSrcSurface, + pSrcRect, // Source rect (nullptr = entire surface) + pSrcRect ? 1 : 0, // Number of rects (0 = full surface) + pDestSurface, + pDestRect ? &destPoint : nullptr // Dest point (nullptr = 0,0) + ); + + pDevice->Release(); + return hr; +} + +// D3DXFilterTexture - Stub (no-op, textures use pre-existing mipmaps) +inline HRESULT D3DXFilterTexture( + LPDIRECT3DBASETEXTURE8 pTexture, + const PALETTEENTRY* pPalette, + UINT SrcLevel, + DWORD Filter) +{ + // Stub: No-op, textures use pre-existing mipmaps + return D3D_OK; +} + +//----------------------------------------------------------------------------- +// Font Functions (Stubs for unused functionality) +//----------------------------------------------------------------------------- + +// Forward declaration for D3DX Font interface +struct ID3DXFont; +typedef struct ID3DXFont *LPD3DXFONT; + +// D3DXCreateFont - Stub (WorldBuilder only, not in MinGW build scope) +inline HRESULT D3DXCreateFont( + LPDIRECT3DDEVICE8 pDevice, + HFONT hFont, + LPD3DXFONT* ppFont) +{ + // WorldBuilder not in build scope - stub acceptable + if (ppFont) *ppFont = nullptr; + return D3DERR_NOTAVAILABLE; +} + +#endif // __cplusplus + +#endif // NO_D3DX diff --git a/Core/Libraries/Include/Lib/D3DXWrapper.h b/Core/Libraries/Include/Lib/D3DXWrapper.h new file mode 100644 index 0000000000..bbcdd2b4ff --- /dev/null +++ b/Core/Libraries/Include/Lib/D3DXWrapper.h @@ -0,0 +1,44 @@ +/* +** 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 . +*/ + +/** + * D3DXWrapper.h - Conditional D3DX8 include + * + * Include this file instead of directly. + * When NO_D3DX is defined, uses compatibility layer. + * Otherwise, uses standard D3DX8 headers. + * + * NOTE: This header is safe to force-include globally. + * For C files, it does nothing when NO_D3DX is defined. + */ + +#pragma once + +// Only provide D3DX replacements for C++ files +// C files that don't use D3DX will be unaffected +#ifdef __cplusplus + +#ifdef NO_D3DX + // Use compatibility layer - no DLL dependency + #include "D3DXCompat.h" +#else + // Use standard D3DX8 headers - requires d3dx8.dll + #include +#endif + +#endif // __cplusplus diff --git a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index d8a1a27577..48d6c3f0af 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -248,3 +248,8 @@ target_link_libraries(corei_ww3d2 INTERFACE core_wwlib core_wwmath ) + +# Apply D3DXWrapper forced include for NO_D3DX builds +if(MINGW AND DEFINED MINGW_D3DX_WRAPPER_INCLUDE) + target_compile_options(corei_ww3d2 INTERFACE ${MINGW_D3DX_WRAPPER_INCLUDE}) +endif() diff --git a/Core/Libraries/Source/WWVegas/WWMath/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WWMath/CMakeLists.txt index dca9eb68fe..667bec3b4d 100644 --- a/Core/Libraries/Source/WWVegas/WWMath/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WWMath/CMakeLists.txt @@ -93,3 +93,8 @@ target_link_libraries(core_wwmath PRIVATE # @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" + +# Apply D3DXWrapper forced include for NO_D3DX builds +if(MINGW AND DEFINED MINGW_D3DX_WRAPPER_INCLUDE) + target_compile_options(core_wwmath PRIVATE ${MINGW_D3DX_WRAPPER_INCLUDE}) +endif() diff --git a/Generals/Code/GameEngine/CMakeLists.txt b/Generals/Code/GameEngine/CMakeLists.txt index 217d7dcecb..6e29a6a37d 100644 --- a/Generals/Code/GameEngine/CMakeLists.txt +++ b/Generals/Code/GameEngine/CMakeLists.txt @@ -1102,3 +1102,8 @@ target_precompile_headers(g_gameengine PRIVATE [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 Include/Precompiled/PreRTS.h ) + +# Apply D3DXWrapper forced include for NO_D3DX builds +if(MINGW AND DEFINED MINGW_D3DX_WRAPPER_INCLUDE) + target_compile_options(g_gameengine PRIVATE ${MINGW_D3DX_WRAPPER_INCLUDE}) +endif() diff --git a/Generals/Code/GameEngineDevice/CMakeLists.txt b/Generals/Code/GameEngineDevice/CMakeLists.txt index ca1022e3e6..7089175ca9 100644 --- a/Generals/Code/GameEngineDevice/CMakeLists.txt +++ b/Generals/Code/GameEngineDevice/CMakeLists.txt @@ -208,3 +208,8 @@ target_link_libraries(g_gameenginedevice PUBLIC corei_gameenginedevice_public g_gameengine ) + +# Apply D3DXWrapper forced include for NO_D3DX builds +if(MINGW AND DEFINED MINGW_D3DX_WRAPPER_INCLUDE) + target_compile_options(g_gameenginedevice PRIVATE ${MINGW_D3DX_WRAPPER_INCLUDE}) +endif() diff --git a/Generals/Code/Main/CMakeLists.txt b/Generals/Code/Main/CMakeLists.txt index 9e14a4cbe7..7858146d22 100644 --- a/Generals/Code/Main/CMakeLists.txt +++ b/Generals/Code/Main/CMakeLists.txt @@ -91,3 +91,8 @@ endif() if(MINGW AND COMMAND add_debug_strip_target) add_debug_strip_target(g_generals) endif() + +# Verify no D3DX8 DLL dependency when NO_D3DX is enabled +if(MINGW AND COMMAND add_no_d3dx_verification) + add_no_d3dx_verification(g_generals) +endif() diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index d6990fc097..c3bd2fd095 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -1178,3 +1178,8 @@ target_precompile_headers(z_gameengine PRIVATE [["Utility/CppMacros.h"]] # Must be first, to be removed when abandoning VC6 Include/Precompiled/PreRTS.h ) + +# Apply D3DXWrapper forced include for NO_D3DX builds +if(MINGW AND DEFINED MINGW_D3DX_WRAPPER_INCLUDE) + target_compile_options(z_gameengine PRIVATE ${MINGW_D3DX_WRAPPER_INCLUDE}) +endif() diff --git a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt index db9be4f3cd..66cc73376a 100644 --- a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt @@ -221,3 +221,8 @@ target_link_libraries(z_gameenginedevice PUBLIC corei_gameenginedevice_public z_gameengine ) + +# Apply D3DXWrapper forced include for NO_D3DX builds +if(MINGW AND DEFINED MINGW_D3DX_WRAPPER_INCLUDE) + target_compile_options(z_gameenginedevice PRIVATE ${MINGW_D3DX_WRAPPER_INCLUDE}) +endif() diff --git a/GeneralsMD/Code/Main/CMakeLists.txt b/GeneralsMD/Code/Main/CMakeLists.txt index d6518f442b..c12120621e 100644 --- a/GeneralsMD/Code/Main/CMakeLists.txt +++ b/GeneralsMD/Code/Main/CMakeLists.txt @@ -80,3 +80,8 @@ endif() if(MINGW AND COMMAND add_debug_strip_target) add_debug_strip_target(z_generals) endif() + +# Verify no D3DX8 DLL dependency when NO_D3DX is enabled +if(MINGW AND COMMAND add_no_d3dx_verification) + add_no_d3dx_verification(z_generals) +endif() diff --git a/cmake/dx8.cmake b/cmake/dx8.cmake index dd08f56119..6de0f03e53 100644 --- a/cmake/dx8.cmake +++ b/cmake/dx8.cmake @@ -5,3 +5,21 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(dx8) + +# When NO_D3DX is enabled, remove d3dx8d from d3d8lib link libraries +if(MINGW AND DEFINED MINGW_NO_D3DX AND MINGW_NO_D3DX) + # Get current link libraries from d3d8lib + get_target_property(DX8_LINK_LIBS d3d8lib INTERFACE_LINK_LIBRARIES) + + if(DX8_LINK_LIBS) + # Remove d3dx8d from the list + list(REMOVE_ITEM DX8_LINK_LIBS d3dx8d) + + # Set the updated list back + set_target_properties(d3d8lib PROPERTIES + INTERFACE_LINK_LIBRARIES "${DX8_LINK_LIBS}" + ) + + message(STATUS "Removed d3dx8d from d3d8lib link libraries (NO_D3DX)") + endif() +endif() diff --git a/cmake/mingw.cmake b/cmake/mingw.cmake index c095343055..349720b0a9 100644 --- a/cmake/mingw.cmake +++ b/cmake/mingw.cmake @@ -74,17 +74,48 @@ if(MINGW) # are provided by Dependencies/Utility/Utility/comsupp_compat.h as header-only # implementations. No library linking required. - # MinGW-w64 compatibility: Create d3dx8 as an alias to d3dx8d - # MinGW-w64 only provides libd3dx8d.a (debug library), not libd3dx8.a - # The min-dx8-sdk (dx8.cmake) handles this correctly via d3d8lib interface target, - # but for compatibility with direct library references in main executables, - # we create an alias so that linking to d3dx8 automatically uses d3dx8d - if(NOT TARGET d3dx8) - add_library(d3dx8 INTERFACE IMPORTED GLOBAL) - set_target_properties(d3dx8 PROPERTIES - INTERFACE_LINK_LIBRARIES "d3dx8d" - ) - message(STATUS "Created d3dx8 -> d3dx8d alias for MinGW-w64") + # MinGW-w64 D3DX8 dependency elimination option + # Header conflicts resolved using include guard coordination (Option A) + # D3DXCompat.h pre-defines min-dx8-sdk include guards to prevent redefinitions + option(MINGW_NO_D3DX "Eliminate D3DX8.dll dependency using WWMath compatibility layer" ON) + + if(MINGW_NO_D3DX) + # Use compatibility layer + add_compile_definitions(NO_D3DX) + + # Force include our D3DX compatibility wrapper in project files only + # (Not in external dependencies like lzhl, gamespy, etc.) + # This ensures all D3DX calls go through our replacement layer + # Note: Will be applied selectively to targets that need it + set(MINGW_D3DX_WRAPPER_INCLUDE "-include;${CMAKE_SOURCE_DIR}/Core/Libraries/Include/Lib/D3DXWrapper.h") + + message(STATUS "MinGW: D3DX8 dependency eliminated (NO_D3DX enabled)") + message(STATUS " Using native D3D implementations for math functions") + message(STATUS " Using Direct3D 8 API for texture operations") + message(STATUS " D3DXWrapper.h available for selective inclusion") + + # Create empty d3dx8 INTERFACE library (like stlport pattern) + # This allows unconditional linking to d3dx8 in CMakeLists.txt files + if(NOT TARGET d3dx8) + add_library(d3dx8 INTERFACE) + message(STATUS "Created empty d3dx8 INTERFACE library (NO_D3DX)") + endif() + else() + # Legacy behavior: use D3DX8 with DLL dependency + message(STATUS "MinGW: Using D3DX8.dll (NO_D3DX disabled)") + + # MinGW-w64 compatibility: Create d3dx8 as an alias to d3dx8d + # MinGW-w64 only provides libd3dx8d.a (debug library), not libd3dx8.a + # The min-dx8-sdk (dx8.cmake) handles this correctly via d3d8lib interface target, + # but for compatibility with direct library references in main executables, + # we create an alias so that linking to d3dx8 automatically uses d3dx8d + if(NOT TARGET d3dx8) + add_library(d3dx8 INTERFACE IMPORTED GLOBAL) + set_target_properties(d3dx8 PROPERTIES + INTERFACE_LINK_LIBRARIES "d3dx8d" + ) + message(STATUS "Created d3dx8 -> d3dx8d alias for MinGW-w64") + endif() endif() message(STATUS "MinGW-w64 configuration complete") diff --git a/cmake/no_d3dx_verify.cmake b/cmake/no_d3dx_verify.cmake new file mode 100644 index 0000000000..e8efae5a4e --- /dev/null +++ b/cmake/no_d3dx_verify.cmake @@ -0,0 +1,46 @@ +# D3DX Dependency Verification for NO_D3DX builds +# +# Verifies that when NO_D3DX is enabled, executables have no d3dx8*.dll imports. +# This ensures the compatibility layer is working correctly. + +if(MINGW AND MINGW_NO_D3DX) + # Find objdump tool for checking DLL imports + find_program(MINGW_OBJDUMP + NAMES ${CMAKE_CXX_COMPILER_TARGET}-objdump + ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-objdump + objdump + DOC "MinGW objdump tool for verifying DLL imports" + ) + + if(MINGW_OBJDUMP) + message(STATUS "D3DX elimination verification enabled:") + message(STATUS " objdump: ${MINGW_OBJDUMP}") + set(NO_D3DX_VERIFY_AVAILABLE TRUE) + else() + message(WARNING "D3DX verification not available - objdump not found") + set(NO_D3DX_VERIFY_AVAILABLE FALSE) + endif() + + # Function to add D3DX verification to a target + # + # Checks that the built executable does not import d3dx8*.dll + # Fails the build if D3DX dependency is detected + # + # Usage: + # add_no_d3dx_verification(target_name) + # + function(add_no_d3dx_verification target_name) + if(NOT NO_D3DX_VERIFY_AVAILABLE) + return() + endif() + + add_custom_command(TARGET ${target_name} POST_BUILD + COMMAND bash -c + "${MINGW_OBJDUMP} -p $ | grep -i 'd3dx8' && echo 'ERROR: D3DX8 DLL dependency detected! NO_D3DX failed.' && exit 1 || echo 'SUCCESS: No D3DX8 DLL dependency (NO_D3DX working)'" + COMMENT "Verifying no D3DX8 dependency in ${target_name}" + VERBATIM + ) + + message(STATUS "D3DX elimination verification configured for target: ${target_name}") + endfunction() +endif() diff --git a/scripts/compile_shaders.py b/scripts/compile_shaders.py new file mode 100755 index 0000000000..0c69acf8c3 --- /dev/null +++ b/scripts/compile_shaders.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 +""" +Pixel Shader 1.1 Assembler - Generates C++ header with precompiled shader bytecode + +This script assembles DirectX 8 Pixel Shader 1.1 assembly code into bytecode +and generates a C++ header file that can be included in the project. + +Usage: python3 compile_shaders.py [shader2.psh ...] + +Note: This is a simple PS 1.1 assembler that handles the basic instructions +used in the water shaders. For D3D8 ps.1.1 format. +""" + +import sys +import struct +import re +from pathlib import Path + +# PS 1.1 instruction opcodes (D3D8 format) +PS_OPCODES = { + 'tex': 0x42, # TEX instruction + 'texbem': 0x43, # TEXBEM instruction + 'mul': 0x05, # MUL instruction + 'add': 0x03, # ADD instruction + 'mad': 0x04, # MAD instruction (multiply-add) +} + +# Register types +REG_TEMP = 0 # r0-r7 (temporary registers) +REG_INPUT = 1 # v0-v1 (input color registers) +REG_CONST = 2 # c0-c7 (constant registers) +REG_TEXTURE = 3 # t0-t3 (texture registers) + +def parse_register(reg_str): + """Parse a register string like 'r0', 'v0', 't0', 'c0' and return (type, index, write_mask)""" + reg_str = reg_str.strip() + + # Handle write masks like r0.rgb, r0.a + write_mask = 0xF # Default: all components (xyzw / rgba) + if '.' in reg_str: + reg_part, mask_part = reg_str.split('.') + if mask_part == 'rgb': + write_mask = 0x7 # xyz only + elif mask_part == 'a': + write_mask = 0x8 # w only + elif mask_part == 'rgba': + write_mask = 0xF # all + reg_str = reg_part + + reg_type = reg_str[0] + reg_index = int(reg_str[1:]) + + type_map = {'r': REG_TEMP, 'v': REG_INPUT, 'c': REG_CONST, 't': REG_TEXTURE} + return (type_map[reg_type], reg_index, write_mask) + +def encode_register(reg_type, reg_index, write_mask=0xF): + """Encode a register into D3D8 format""" + # Simple encoding: type in upper bits, index in lower bits + # This is a simplified version - actual D3D8 encoding is more complex + return (write_mask << 16) | (reg_type << 8) | reg_index + +def assemble_instruction(line): + """Assemble a single PS 1.1 instruction into bytecode""" + line = line.strip() + + # Skip empty lines and comments + if not line or line.startswith(';') or line.startswith('//'): + return [] + + # Handle co-issue (+instruction) + coissue = line.startswith('+') + if coissue: + line = line[1:].strip() + + # Split instruction and operands + parts = re.split(r'[,\s]+', line) + instr = parts[0].lower() + + # Handle version declaration + if instr == 'ps.1.1': + # PS 1.1 version token: 0xFFFF0101 + return [struct.pack('= 2: + reg_type, reg_idx, write_mask = parse_register(parts[1]) + bytecode.append(struct.pack('= 3: + dst_type, dst_idx, dst_mask = parse_register(parts[1]) + src_type, src_idx, src_mask = parse_register(parts[2]) + bytecode.append(struct.pack('= 4: + dst_type, dst_idx, dst_mask = parse_register(parts[1]) + src1_type, src1_idx, src1_mask = parse_register(parts[2]) + src2_type, src2_idx, src2_mask = parse_register(parts[3]) + bytecode.append(struct.pack('= 5: + dst_type, dst_idx, dst_mask = parse_register(parts[1]) + src1_type, src1_idx, src1_mask = parse_register(parts[2]) + src2_type, src2_idx, src2_mask = parse_register(parts[3]) + src3_type, src3_idx, src3_mask = parse_register(parts[4]) + bytecode.append(struct.pack(' + +namespace PrecompiledShaders {{ + +""") + + for i, shader_file in enumerate(shader_files, 1): + shader_name = Path(shader_file).stem + print(f"Assembling {shader_file}...") + + with open(shader_file, 'r') as sf: + shader_text = sf.read() + + bytecode = assemble_shader(shader_text) + + # Write bytecode array + f.write(f"// {shader_name} - from {Path(shader_file).name}\n") + f.write(f"constexpr DWORD {shader_name}_bytecode[] = {{\n") + + # Write bytecode as hex DWORDs + dwords = [bytecode[i:i+4] for i in range(0, len(bytecode), 4)] + for j, dword in enumerate(dwords): + if len(dword) == 4: + value = struct.unpack(' [shader2.psh ...]") + print("\nExample:") + print(" python3 compile_shaders.py shaders/water_shader1.psh shaders/water_shader2.psh shaders/water_shader3.psh") + sys.exit(1) + + shader_files = sys.argv[1:] + output_file = "PrecompiledShaders.h" + + # Verify all input files exist + for shader_file in shader_files: + if not Path(shader_file).exists(): + print(f"Error: Shader file '{shader_file}' not found") + sys.exit(1) + + generate_header(shader_files, output_file) + print(f"\nSuccess! Precompiled shaders written to {output_file}") + print("Include this header in your D3DXCompat.h or shader loading code.") + +if __name__ == '__main__': + main() diff --git a/scripts/shaders/water_shader1.psh b/scripts/shaders/water_shader1.psh new file mode 100644 index 0000000000..a8db099945 --- /dev/null +++ b/scripts/shaders/water_shader1.psh @@ -0,0 +1,10 @@ +ps.1.1 +tex t0 +tex t1 +tex t2 +tex t3 +mul r0,v0,t0 +mul r1, t1, t2 +add r0.rgb, r0, t3 ++mul r0.a, r0, t3 +add r0.rgb, r0, r1 diff --git a/scripts/shaders/water_shader2.psh b/scripts/shaders/water_shader2.psh new file mode 100644 index 0000000000..4a480efa59 --- /dev/null +++ b/scripts/shaders/water_shader2.psh @@ -0,0 +1,7 @@ +ps.1.1 +tex t0 +tex t1 +texbem t2, t1 +mul r0,v0,t0 +mul r1.rgb,t2,c0 +add r0.rgb, r0, r1 diff --git a/scripts/shaders/water_shader3.psh b/scripts/shaders/water_shader3.psh new file mode 100644 index 0000000000..19551f94e1 --- /dev/null +++ b/scripts/shaders/water_shader3.psh @@ -0,0 +1,8 @@ +ps.1.1 +tex t0 +tex t1 +tex t2 +tex t3 +mul r0,v0,t0 +mad r0.rgb, t1, t2, r0 +mul r0.rgb, r0, t3