bugfix(texture): Prevent compressed texture reduction below 4x4 limit and fix binarytstream texture rendering#2771
Conversation
|
| Filename | Overview |
|---|---|
| Core/Libraries/Source/WWVegas/WW3D2/textureloader.cpp | Removes the line that clobbered the aspect-ratio Reduction with the raw hardware reduction, and adds a 4 px guard in both Begin_Compressed_Load and Load_Compressed_Mipmap to stop over-shifting. The guard in Begin_Compressed_Load uses exact equality (!=4) rather than clamping, which still allows sub-4 results when Width is above 4 and Reduction is large. |
Sequence Diagram
sequenceDiagram
participant App
participant BCL as Begin_Compressed_Load
participant DDS as DDSFileClass
participant LCM as Load_Compressed_Mipmap
App->>BCL: load texture (e.g. 16x256)
BCL->>BCL: Validate_Texture_Size fails (1:16 aspect)
BCL->>BCL: "loop i=1..n find mip where ratio le 1:8"
note over BCL: i=3 w=4 h=32 clamped valid Reduction+=3 Width=4 Height=32
note over BCL: (removed) Reduction=reduction was overwriting to 0
BCL->>BCL: "guard reducedWidth==4 skip shift mip_level_count=1"
BCL->>DDS: _Create_DX8_Texture(4,32,fmt,1 mip)
BCL-->>App: D3DTexture allocated at 4x32
App->>LCM: copy pixel data
LCM->>DDS: "open DDS at Get_Reduction()=3 mip3=4x32 block"
LCM->>LCM: "width=4 height=32"
LCM->>LCM: "Reduction loop width==4 break immediately"
LCM->>DDS: "Copy_Level_To_Surface(level=0,4,32,...)"
DDS-->>LCM: 4x32 pixels copied
LCM-->>App: texture ready
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
Core/Libraries/Source/WWVegas/WW3D2/textureloader.cpp:1481-1486
The guard skips the whole block only when a dimension is **exactly** 4, but it still allows the bulk right-shift to produce values below 4 for wider dimensions. For example, if `Width = 8` and `Reduction = 2` (texture too large for hardware, needing two mip levels of reduction), the condition passes because `8 != 4`, but `reducedWidth >>= 2` yields 2 — below the DXT block minimum. `Load_Compressed_Mipmap` avoids this by reducing incrementally and breaking before going under 4, so a texture whose `Width` came out above 4 from the hardware-limit loop could end up with a D3D surface two bytes narrower than the data being copied into it. Clamping with `std::max` makes the intent explicit and aligns the two functions.
```suggestion
reducedWidth = max(4u, reducedWidth >> Reduction);
reducedHeight = max(4u, reducedHeight >> Reduction);
mip_level_count = orig_mip_count-Reduction;//dds_file.Get_Mip_Level_Count();
```
Reviews (1): Last reviewed commit: "bugfix(texture): Prevent compressed text..." | Re-trigger Greptile
| if(reducedWidth !=4 && reducedHeight !=4) | ||
| { | ||
| reducedWidth >>= Reduction; | ||
| reducedHeight >>= Reduction; | ||
| mip_level_count = orig_mip_count-Reduction;//dds_file.Get_Mip_Level_Count(); | ||
| } |
There was a problem hiding this comment.
The guard skips the whole block only when a dimension is exactly 4, but it still allows the bulk right-shift to produce values below 4 for wider dimensions. For example, if
Width = 8 and Reduction = 2 (texture too large for hardware, needing two mip levels of reduction), the condition passes because 8 != 4, but reducedWidth >>= 2 yields 2 — below the DXT block minimum. Load_Compressed_Mipmap avoids this by reducing incrementally and breaking before going under 4, so a texture whose Width came out above 4 from the hardware-limit loop could end up with a D3D surface two bytes narrower than the data being copied into it. Clamping with std::max makes the intent explicit and aligns the two functions.
| if(reducedWidth !=4 && reducedHeight !=4) | |
| { | |
| reducedWidth >>= Reduction; | |
| reducedHeight >>= Reduction; | |
| mip_level_count = orig_mip_count-Reduction;//dds_file.Get_Mip_Level_Count(); | |
| } | |
| reducedWidth = max(4u, reducedWidth >> Reduction); | |
| reducedHeight = max(4u, reducedHeight >> Reduction); | |
| mip_level_count = orig_mip_count-Reduction;//dds_file.Get_Mip_Level_Count(); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: Core/Libraries/Source/WWVegas/WW3D2/textureloader.cpp
Line: 1481-1486
Comment:
The guard skips the whole block only when a dimension is **exactly** 4, but it still allows the bulk right-shift to produce values below 4 for wider dimensions. For example, if `Width = 8` and `Reduction = 2` (texture too large for hardware, needing two mip levels of reduction), the condition passes because `8 != 4`, but `reducedWidth >>= 2` yields 2 — below the DXT block minimum. `Load_Compressed_Mipmap` avoids this by reducing incrementally and breaking before going under 4, so a texture whose `Width` came out above 4 from the hardware-limit loop could end up with a D3D surface two bytes narrower than the data being copied into it. Clamping with `std::max` makes the intent explicit and aligns the two functions.
```suggestion
reducedWidth = max(4u, reducedWidth >> Reduction);
reducedHeight = max(4u, reducedHeight >> Reduction);
mip_level_count = orig_mip_count-Reduction;//dds_file.Get_Mip_Level_Count();
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
This fix would leave the binarystream texture with dimensions 4x4 which doesn't render. I agree that the logic might over-reduce though.
In
Begin_Compressed_Load, the hardware aspect-ratio validation loop calculatesReductionto find a hardware-compliant mip level for a non-compliant texture (or thin textures like binary stream). However, the value was immediately overwritten following the loop.This causes
Load_Compressed_Mipmapto open the DDS file at reduction=0 (full 16×256) while trying to copy into a 4×32 surface which caused a silent dimension mismatch whereDDSFileClass::Copy_Level_To_Surfacehits its unimplemented scaling branch and copies nothing, leaving the texture blank.The binary stream texture
EXBinaryStream.tgais of dimensions16×256which is a thin texture (aspect ratio exceeding 1:8) and became invisible in Generals while still appearing correctly in Zero Hour because it replaced byEXBinaryStream32.tga(32×256), which has a compliant 1:8 aspect ratio and never triggers the buggy code path.This fix removes the line that overwrites the aspect-ratio reduction and performs minimum dimension clamping (4px) to prevent over-shifting in
Begin_Compressed_LoadandLoad_Compressed_MipmapNote on original Generals behavior
The original pre-PR Generals loader also had a bug in its reduction loop: it passed the wrong variables to
Validate_Texture_Size, causing the loop to always break at i=1, producing8×128instead of the more consistent4×32. This fix produces4×32, which differs slightly from original Generals retail behavior. If exact retail compatibility is required, a precompiler flag approach might be considered.Testing
Verified that Lotus' and patriot's binary stream renders in Generals and ZH.
Played multiple skirmishes in generals and ZH and no apparent unwanted changes were noticed.