W3D Shader Manager
How the engine chooses custom terrain shaders and full-screen view filters, then keeps WW3D state from drifting afterward.
W3DShaderManager is not a generic material system. It is a narrow compatibility layer that does two specific jobs for the old W3D renderer: select the best custom shader implementation the current hardware can support, and wrap full-screen view filters that need render-to-texture. W3DShaderManager.cpp keeps two separate registries — W3DShaders for terrain and effect shaders, W3DFilters for screen-space post passes.
The startup model is pure capability probing. The file header explains the contract directly: add a shader enum, create one or more implementations, sort them by preference, and let initialization pick the first version that passes hardware validation. This is old-school fixed-function-era feature negotiation, not data-driven modern shader permutation building.
Terrain shader example
TerrainShader2Stage::updateCloud advances the cloud/noise layer using WW3D::Get_Logic_Frame_Time_Seconds() and wraps the UV offsets back into [0, 1). The corresponding set function configures texture stage states, filtering, alpha operations, and clamp modes manually per pass — the place where renderer features that WW3D could not express declaratively are forced into the D3D state machine.
The view-filter path
filterSetup, filterPreRender, and filterPostRender are called around tactical-view rendering. Each filter decides whether to pre-pass into an off-screen texture, whether the normal scene render should be skipped, and whether an extra post-pass is needed. ScreenDefaultFilter is the simplest case — it looks almost like a no-op, but it matters because it keeps a copy of the already-rendered viewport available as a texture for systems like smudges:
Bool ScreenDefaultFilter::preRender(Bool &skipRender, CustomScenePassModes &scenePassMode)
{
// Right now this filter is only used for smudges, so don't bother if none are present.
if (TheSmudgeManager) {
if (((W3DSmudgeManager *)TheSmudgeManager)->getSmudgeCountLastFrame() == 0)
return FALSE;
}
W3DShaderManager::startRenderToTexture();
return true;
}
The gate on last-frame smudge count is the quiet optimization — when no smudge particle is active, the full-screen RT capture is skipped entirely. When at least one smudge drew last frame, the capture is scheduled; postRender then binds the captured texture and redraws the tactical viewport as two screen-space triangles.
The viewport detail matters. drawViewport uses TheTacticalView origin and size, not the whole back buffer. The filter system is scoped to the RTS camera region, so sidebars and other UI are not unintentionally post-processed with the world view.
Water alpha bookkeeping
startRenderToTexture has one hidden responsibility: soft-water alpha bookkeeping. It checks TheGlobalData->m_showSoftWaterEdge and clears alpha differently depending on the active filter. Motion blur and crossfade cannot destroy the previous color buffer, so those modes only clear alpha. Simpler paths clear the render target normally using the minimum water opacity:
if (TheGlobalData->m_showSoftWaterEdge) { // Motion blur and crossfade preserve the previous color; only clear alpha. // Other modes clear both, using m_minWaterOpacity as the alpha clear value. }
That coupling looks odd until you notice that shoreline blending depends on destination alpha later in the frame (see Water Renderer).
State reset discipline
setShader caches the current shader and pass so duplicate state sets can be skipped, but resetShader is just as important because these custom passes bypass part of WW3D's normal state tracking. Most shader/filter implementations end by invalidating cached render states or nulling texture stages so the rest of the renderer does not inherit half-configured custom states. Forgetting this is how a single shader bug becomes a whole-scene render corruption.
Quirks
Pre-D3D11 API throughout. The file still references
IDirect3DTexture8,LPDIRECT3DDEVICE8,DX8Wrapper::_Get_D3D_Device8(), and D3D8 enums likeD3DTOP_MODULATEdirectly. On the D3D11 port, several public entry points (setShroudTex, some filters) are stubbed in D3D11Shims.cpp rather than invoked on the legacy implementation. New D3D11 paths reimplement the feature in the terrain renderer or a dedicated pass instead of going through this file.The default screen filter is not decorative. It exists largely so systems such as smudges can sample the already-rendered scene. If you delete it thinking it's a placeholder, smudges silently stop appearing because the capture never happens.
Filter rendering is tactical-view scoped. The manager blits only the world viewport, not arbitrary UI regions. Full-screen post-processing is not supported — intentionally. Sidebar UI and the command overlay never get pulled into any filter output.
Water and post-processing are coupled through alpha clearing. If a new filter is added that also uses render-to-texture and it fails to match the alpha-clear behavior of the existing filters, the water shoreline blend will look wrong only when that filter is active. The coupling is invisible at the filter implementation site.
State reset is part of the feature. If a custom shader forgets to clean up, the next WW3D pass inherits broken texture-stage state. Hours have been spent chasing "unrelated" render corruption that traced back to an unreset sampler stage from an earlier filter pass.
W3DShadersregistry sort is preference-ordered, not priority-ordered. The code picks the first shader whose hardware validation passes. A higher-capability implementation earlier in the list with a failing probe will not fall back to a simpler path later in the list — it will just refuse to initialize that shader slot and draw nothing for that feature.