Skip to content

feat(options): Implement game options for texture filter mode, anisotropy and MSAA selection#2482

Open
Mauller wants to merge 2 commits intoTheSuperHackers:mainfrom
Mauller:Mauller/feat-MSAA-texfilter-options
Open

feat(options): Implement game options for texture filter mode, anisotropy and MSAA selection#2482
Mauller wants to merge 2 commits intoTheSuperHackers:mainfrom
Mauller:Mauller/feat-MSAA-texfilter-options

Conversation

@Mauller
Copy link
Copy Markdown

@Mauller Mauller commented Mar 21, 2026

Merge By Rebase

Closes: #2474
Closes: #2375

This PR implements options for allowing the enablement of MSAA and the selection of different texture filtering modes and setting the anisotropic filtering level.

The following options are added for anti aliasing

AntiAliasing = 0, 2, 4, 8
The numbers relate to the MSAA sample level and Zero disables MSAA.

TextureFilter = None, Point, Bilinear, Trilinear, Anisotropic
Self explanatory, just choose one and see how the game looks

AnisotropyLevel = 2, 4, 8, 16
The anisotropy level only comes into play when Anisotropic filtering is set. 2 is also the minimum value.


Generals is broken untill the changes are replicated due to changes in texture filter init.

  • Replicate in generals

@Mauller Mauller self-assigned this Mar 21, 2026
@Mauller Mauller added Enhancement Is new feature or request Gen Relates to Generals ZH Relates to Zero Hour labels Mar 21, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 21, 2026

Greptile Summary

This PR implements user-configurable graphics quality options — MSAA anti-aliasing (Off/2×/4×/8×), texture filter mode (None/Point/Bilinear/Trilinear/Anisotropic), and anisotropic filtering level (2×/4×/8×/16×) — for the Zero Hour engine layer (GeneralsMD). Settings are persisted to Options.ini, loaded into GlobalData at startup, and applied to the W3D/DX8 render device during initialisation. The MultiSampleModeEnum values are now explicit integers matching their sample counts, and a new highestBit utility rounds arbitrary INI values down to the nearest valid power-of-2 level.

Key changes:

  • WW3D/TextureFilterClass: expose Set_Anisotropy_level / Get_Anisotropy_level and thread the anisotropy level through _Init_Filters.
  • GlobalData: replaces the old m_antiAliasBoxValue integer (combo-box position) with properly-typed m_antiAliasLevel, m_textureFilteringMode, and m_textureAnisotropyLevel fields.
  • OptionPreferences: three new typed accessors that parse, validate, and return the preference values.
  • W3DDisplay::init: applies all three settings to the device and then reads back the GPU-clamped values.
  • OptionsMenu: converts between combo-box indices and MultiSampleModeEnum values correctly on both load and save.

Issue found:

  • All three clamp() guard calls in OptionsMenu.cpp (saveOptions) discard their return values, leaving the intended bounds check completely inactive. While the upstream getTextureFilterMode() / getTextureAnisotropyLevel() accessors already return validated values (making this safe today), the guard for index before the 1 << index shift and the guard before the TextureFilterModeString[val] array index are both broken and should be assigned back to their respective variables.

Confidence Score: 4/5

Safe to merge after fixing the three discarded clamp return values in OptionsMenu.cpp; all other changes are well-structured.

One P1 finding remains: the clamp() calls on lines 561, 575, and 588 of OptionsMenu.cpp all discard their return values and therefore have no effect. The guard before TextureFilterModeString[val] array indexing is currently inactive. Upstream validation happens to keep values in range today, but the broken guard is a real defect that should be fixed before merge.

GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp — the three inactive clamp guard calls in saveOptions.

Important Files Changed

Filename Overview
Core/GameEngine/Include/Common/OptionPreferences.h Adds AliasingMode enum and three new accessor declarations — clean, no issues.
Core/GameEngine/Source/Common/OptionPreferences.cpp Implements the three new preference accessors; clamp return values are correctly captured here, and highestBit is used to round to valid power-of-2 levels. No issues.
Core/Libraries/Include/Lib/BaseType.h Adds highestBit template that rounds down to the nearest power-of-2. Correct for unsigned / non-negative inputs; signed negative inputs would produce UB on the final cast, but all call sites use non-negative values.
Core/Libraries/Source/WWVegas/WW3D2/texturefilter.cpp Defines TextureFilterModeString array and updates _Init_Filters signature to accept an anisotropy level instead of hard-coding 2X. Clean change.
Core/Libraries/Source/WWVegas/WW3D2/texturefilter.h Exposes TextureFilterModeString and updates _Init_Filters declaration to take an anisotropy level. No issues.
GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h Replaces m_antiAliasBoxValue with three properly-typed fields for alias level, texture filter mode, and anisotropy level. Clean change.
GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp Initialises the three new GlobalData fields with sensible defaults and populates them from preferences during load. No issues.
GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp Anti-aliasing combo-box save/load logic is correct; however all three clamp guard calls (antialiasing, texture filter, anisotropy) discard their return values, leaving the guard inactive.
GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp Applies MSAA mode from GlobalData before device creation and re-reads it plus texture filter/anisotropy settings back after device init to capture GPU capability limits. No issues.
GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp Passes both texture filter and anisotropy level to _Init_Filters to match the updated signature. No issues.
GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp Adds AnisotropyLevel static and Set_Anisotropy_level with correct sequential <= clamping to valid enum values. No issues.
GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h Assigns explicit integer values to MultiSampleModeEnum entries (0, 2, 4, 8) and exposes the new anisotropy level getter/setter pair. No issues.

Sequence Diagram

sequenceDiagram
    participant INI as Options.ini
    participant OP as OptionPreferences
    participant GD as GlobalData
    participant OPM as OptionsMenu (GUI)
    participant W3D as WW3D
    participant TF as TextureFilterClass
    participant DX as DX8Wrapper

    Note over INI,DX: Startup / Game Init
    INI->>OP: load("Options.ini")
    OP->>GD: getAntiAliasing() → m_antiAliasLevel
    OP->>GD: getTextureFilterMode() → m_textureFilteringMode
    OP->>GD: getTextureAnisotropyLevel() → m_textureAnisotropyLevel
    GD->>W3D: Set_MSAA_Mode(m_antiAliasLevel)
    W3D->>DX: Set_Render_Device(...)
    DX->>TF: _Init_Filters(TextureFilter, AnisotropyLevel)
    W3D->>GD: Get_MSAA_Mode() → m_antiAliasLevel (GPU-clamped)
    W3D->>GD: Set_Texture_Filter / Get_Texture_Filter → m_textureFilteringMode
    W3D->>GD: Set_Anisotropy_level / Get_Anisotropy_level → m_textureAnisotropyLevel

    Note over INI,DX: Options Menu Save
    OPM->>GD: GadgetComboBox index → m_antiAliasLevel
    OPM->>INI: Write AntiAliasing, TextureFilter, AnisotropyLevel
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp
Line: 561

Comment:
**`clamp` return value discarded in all three guard calls**

`clamp` is a pure function — it returns the clamped value and does not modify its argument. All three calls below discard the return value, so the clamping has no effect whatsoever:

- Line 561: `index` is not clamped before the `1 << index` shift
- Line 575: `val` is not clamped before indexing into `TextureFilterModeString[val]`
- Line 588: `val` is not clamped before writing the anisotropy preference

The intended guard for the out-of-bounds array access on line 579 (`TextureFilterModeString[val]`) is completely inactive. In practice the upstream `get*` functions already return validated values, but the defensive intent is broken and this would become an unsafe out-of-bounds access the moment those guarantees change.

```suggestion
		index = clamp((int)OptionPreferences::MSAA_OFF, index, (int)OptionPreferences::MSAA_8X);
```

Same pattern needs to be applied on lines 575 and 588:
```cpp
val = clamp((int)TextureFilterClass::TEXTURE_FILTER_NONE, val, (int)TextureFilterClass::TEXTURE_FILTER_ANISOTROPIC);
// ...
val = clamp((int)TextureFilterClass::TEXTURE_FILTER_ANISOTROPIC_2X, val, (int)TextureFilterClass::TEXTURE_FILTER_ANISOTROPIC_16X);
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (6): Last reviewed commit: "feat(options): Implement game options fo..." | Re-trigger Greptile

@Mauller Mauller force-pushed the Mauller/feat-MSAA-texfilter-options branch from 120d3db to cfff7d1 Compare March 21, 2026 21:54
@Mauller
Copy link
Copy Markdown
Author

Mauller commented Mar 21, 2026

Generals is currently broken till i replicate across due to changes in initaialising the texture filtering

@Mauller Mauller force-pushed the Mauller/feat-MSAA-texfilter-options branch 2 times, most recently from 915e548 to 53085fc Compare March 21, 2026 22:12
Copy link
Copy Markdown

@xezon xezon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation can be polished more.


enum AliasingMode CPP_11(: Int)
{
OFF = 0,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put more into the name to avoid shadowing or macro conflicts.


UnsignedInt getAntiAliasing();
UnsignedInt getTextureFilterMode();
UnsignedInt getTextureAnisotropyLevel();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better be const.

level = WW3D::MultiSampleModeEnum::MULTISAMPLE_MODE_8X;

if (level > WW3D::MultiSampleModeEnum::MULTISAMPLE_MODE_8X)
level = WW3D::MultiSampleModeEnum::MULTISAMPLE_MODE_8X;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enums are unscoped, so MultiSampleModeEnum:: is redundant.

else if (level <= WW3D::MultiSampleModeEnum::MULTISAMPLE_MODE_4X)
level = WW3D::MultiSampleModeEnum::MULTISAMPLE_MODE_4X;
else if (level <= WW3D::MultiSampleModeEnum::MULTISAMPLE_MODE_8X)
level = WW3D::MultiSampleModeEnum::MULTISAMPLE_MODE_8X;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is a bit odd that Value 5 would become 8. I would expect 7 becomes 4.

Can be simplified and improved by doing

Int level = atoi(it->second.str());
level = clamp(WW3D::MULTISAMPLE_MODE_FIRST, level, WW3D::MULTISAMPLE_MODE_LAST);
level = highestBit(level);

Then add in BaseType.h or so

template <typename T>
T highestBit(T v)
{
    static_assert(sizeof(T) <= 8, "T must be smaller equal 8");

    UnsignedInt64 i = static_cast<UnsignedInt64>(v);

    i |= i >> 1;
    i |= i >> 2;
    i |= i >> 4;
    i |= i >> 8;
    i |= i >> 16;
    i |= i >> 32;

    return static_cast<T>(i - (i >> 1));
}

This will work for as long as the values are expected to be power of 2, which I think is always the case.

Same for AnisotropicFilterMode

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tweaked


UnsignedInt OptionPreferences::getTextureAnisotropyLevel()
{
OptionPreferences::const_iterator it = find("AnisotropyLevel");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an intuitive term for players? If I am not mistaken NVIDIA and AMD drivers call this Anisotropic.

Copy link
Copy Markdown
Author

@Mauller Mauller Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's often hidden by having the user facing texture filter mode option showing Anisotropic x2, Anisotropic x4 etc
The underlying graphics API's use Anisotropy level to set the sampling mode for Anisotropic filtering.

m_antiAliasBoxValue = 0;
m_antiAliasLevel = WW3D::MultiSampleModeEnum::MULTISAMPLE_MODE_NONE;
m_textureFilteringMode = TextureFilterClass::TextureFilterMode::TEXTURE_FILTER_BILINEAR;
m_textureAnisotropyLevel = TextureFilterClass::AnisotropicFilterMode::TEXTURE_FILTER_ANISOTROPIC_2X;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these need to be in GlobalData or is there a better place for them?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's not really a nice place to centralise them apart from global data, Texture Class is mostly handled by static functions and MSAA is handled inside dx8wrapper through ww3d.

return load("Options.ini");
}

UnsignedInt OptionPreferences::getAntiAliasing()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this return the enum type instead?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was avoiding adding ww3d and texturefilter headers to the globaldata header

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you move the enums out of TextureFilterClass then you can forward declare them.

mode = WW3D::MultiSampleModeEnum::MULTISAMPLE_MODE_4X;
break;
case OptionPreferences::AliasingMode::X8:
mode = WW3D::MultiSampleModeEnum::MULTISAMPLE_MODE_8X;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can also be simplified by using bit shift

mode = (index > 0) ? 1 << index : 0;

prefString = "Trilinear";
break;
case TextureFilterClass::TextureFilterMode::TEXTURE_FILTER_ANISOTROPIC:
prefString = "Anisotropic";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest provide an enum to string array or function near the enum declaration instead so that the string mapping could be reused elsewhere too and is closer to the enum declaration, which reduces maintenance mistakes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tweaked

@Mauller Mauller force-pushed the Mauller/feat-MSAA-texfilter-options branch 2 times, most recently from e222f4b to 09fc9af Compare March 27, 2026 18:49
@Mauller Mauller force-pushed the Mauller/feat-MSAA-texfilter-options branch from 09fc9af to a4f900f Compare March 27, 2026 19:00
@Mauller
Copy link
Copy Markdown
Author

Mauller commented Mar 27, 2026

Updated based on feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement Is new feature or request Gen Relates to Generals ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Game is missing Anti-Aliasing and Anisotropic-Filtering settings.

2 participants