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

@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

greptile-apps bot commented Mar 21, 2026

Greptile Summary

This PR implements three new game options — anti-aliasing (MSAA level), texture filter mode, and anisotropic filtering level — wiring them from Options.ini through OptionPreferences/GlobalData into the WW3D rendering pipeline. Previous review concerns about the <= vs > clamping bug in Set_Anisotropy_level() and the stray semicolon in OptionsMenu.cpp have been addressed.

Key changes:

  • MultiSampleModeEnum values are now explicitly set to their sample counts (0, 2, 4, 8) so the INI stores human-readable numbers rather than combo-box positions.
  • _Init_Filters() signature extended to accept AnisotropicFilterMode, removing the hardcoded 2X default.
  • W3DDisplay::init() reads back the GPU-accepted MSAA level post device-creation, gracefully handling GPUs that don't support the requested sample count.
  • AliasingMode enum moved from a local definition inside OptionsMenuInit into OptionPreferences for shared access.
  • Two minor style issues remain: a no-op == guard in getAntiAliasing() that should use <= for consistency, and always-true val >= 0 conditions in the texture filter / anisotropy save blocks.

Confidence Score: 4/5

  • Safe to merge after addressing two minor style nits; the previously flagged critical clamping bug has been correctly fixed.
  • The core logic — enum value assignment, INI serialisation/deserialisation, GPU read-back after device creation, and anisotropy clamping — is all correct. Previous P0/P1 concerns from the thread are resolved. The two remaining comments are P2 style issues (a no-op == guard and always-true val >= 0 conditions) that do not affect runtime behaviour.
  • Core/GameEngine/Source/Common/OptionPreferences.cpp (no-op guard), GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp (always-true conditions)

Important Files Changed

Filename Overview
Core/GameEngine/Source/Common/OptionPreferences.cpp Adds getAntiAliasing(), getTextureFilterMode(), getTextureAnisotropyLevel() — logic is sound except for an inconsistent == guard in getAntiAliasing() that should be <=.
Core/Libraries/Source/WWVegas/WW3D2/texturefilter.cpp _Init_Filters now takes an explicit anisotropy_level argument and forwards it to _Set_Max_Anisotropy instead of hardcoding 2X. Correct.
GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp Adds Set_Anisotropy_level() with correct ≤-based clamping and a new AnisotropyLevel static; Set_Texture_Filter() now passes the stored anisotropy level through. No issues.
GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h MultiSampleModeEnum values are now explicitly assigned (0, 2, 4, 8) to match the INI values; new Set/Get_Anisotropy_level API added. Clean.
GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp Anti-aliasing saving/loading now correctly converts between combo-box position and MSAA sample count. Two always-true val >= 0 guards in the texture filter / anisotropy blocks.
GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp Applies MSAA mode from global data before device creation, then reads back the GPU-accepted mode afterwards and applies texture filter + anisotropy level. Post-init read-back pattern is sound.

Sequence Diagram

sequenceDiagram
    participant INI as Options.ini
    participant OP as OptionPreferences
    participant GD as GlobalData
    participant W3DD as W3DDisplay::init()
    participant WW3D as WW3D
    participant TF as TextureFilterClass

    INI->>OP: load("Options.ini")
    OP->>GD: getAntiAliasing() → m_antiAliasLevel
    OP->>GD: getTextureFilterMode() → m_textureFilteringMode
    OP->>GD: getTextureAnisotropyLevel() → m_textureAnisotropyLevel

    GD->>W3DD: m_antiAliasLevel
    W3DD->>WW3D: Set_MSAA_Mode(m_antiAliasLevel)
    W3DD->>WW3D: Set_Render_Device(...)
    WW3D-->>W3DD: Get_MSAA_Mode() [GPU-clamped]
    W3DD->>GD: m_antiAliasLevel ← GPU-accepted value

    W3DD->>WW3D: Set_Texture_Filter(m_textureFilteringMode)
    WW3D->>TF: _Init_Filters(TextureFilter, AnisotropyLevel)
    W3DD->>WW3D: Set_Anisotropy_level(m_textureAnisotropyLevel)
    WW3D->>TF: _Set_Max_Anisotropy(level)
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: Core/GameEngine/Source/Common/OptionPreferences.cpp
Line: 79-80

Comment:
**No-op branch, inconsistent with the `<=` pattern**

The first `if` branch is a no-op — it reads `if (level == MULTISAMPLE_MODE_NONE) level = MULTISAMPLE_MODE_NONE`, which assigns the same value it just tested for. It works correctly only because `UnsignedInt` can never be less than zero, but it is inconsistent with every other range-clamp in the file (including `getTextureAnisotropyLevel()` and `Set_Anisotropy_level()`), all of which use `<=`. Prefer `<=` here for clarity and uniformity:

```suggestion
	if (level <= WW3D::MultiSampleModeEnum::MULTISAMPLE_MODE_NONE)
		level = WW3D::MultiSampleModeEnum::MULTISAMPLE_MODE_NONE;
```

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

---

This is a comment left during a code review.
Path: GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp
Line: 586

Comment:
**`val >= 0` is an always-true condition**

`val` is declared as `Int`, and both `getTextureFilterMode()` and `getTextureAnisotropyLevel()` always return a small, bounded `UnsignedInt` (0–4 and 2/4/8/16 respectively), so this guard is never false. The same always-true pattern appears at line 616 in the anisotropy block. If the intent is simply to guard against a null `TheGlobalData`, the condition should reflect that directly:

```suggestion
	if (TheGlobalData)
```

The same applies to the matching check at line 616.

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

Last reviewed commit: "feat(options): Imple..."

@Mauller Mauller force-pushed the Mauller/feat-MSAA-texfilter-options branch from 120d3db to cfff7d1 Compare March 21, 2026 21:54
@Mauller
Copy link
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 from cfff7d1 to 915e548 Compare March 21, 2026 22:03
@Mauller Mauller force-pushed the Mauller/feat-MSAA-texfilter-options branch from 915e548 to 53085fc Compare March 21, 2026 22:12
Copy link

@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

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

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

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

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


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

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
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

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
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

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
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

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

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

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.

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