perf: Refactor SetupGeometry vtable hooks to use centralized Feature lifecycle pattern
#1,752 opened on Jan 24, 2026
Repository metrics
- Stars
- (1,037 stars)
- PR merge metrics
- (Avg merge 2d 4h) (63 merged PRs in 30d)
Description
Problem
Similar to #1751, multiple features hook SetupGeometry independently, creating redundant vtable hook chains in a hot path called for every render pass (thousands of times per frame).
Features hooking BSLightingShader::SetupGeometry:
- TruePBR (core)
- Hooks.cpp core extensions
- LightLimitFix
- SubsurfaceScattering
- LinearLighting
- ExtendedTranslucency
- FrameAnnotations
Features hooking BSEffectShader::SetupGeometry:
- Hooks.cpp core extensions
- LightLimitFix
- FrameAnnotations
Features hooking BSGrassShader::SetupGeometry:
- Hooks.cpp core extensions
- GrassCollision
- FrameAnnotations
Each feature patches the same vtable slot (0x6), creating a chain of virtual dispatches. With 10+ hooks, this creates unnecessary overhead.
Proposed Solution
Add new Feature lifecycle hooks following the pattern from #1751:
virtual void SetupGeometry_Lighting(RE::BSRenderPass* pass, uint32_t flags) {}
virtual void SetupGeometry_Effect(RE::BSRenderPass* pass, uint32_t flags) {}
virtual void SetupGeometry_Grass(RE::BSRenderPass* pass, uint32_t flags) {}
virtual void SetupGeometry_Water(RE::BSRenderPass* pass, uint32_t flags) {}
Centralize the vtable hooks in Hooks.cpp to dispatch to all features in a single pass, similar to the existing Prepass() pattern.
Benefits
- Eliminates 10+ chained vtable hooks
- Single virtual dispatch + feature loop instead of chain
- Consistent with #1751 architecture
- Easier feature development
- Runtime toggling preserved
Impact
Higher priority than #1751 as this affects 10+ features vs 2 features for RenderPassImmediately.
Related
- #1751 - Similar refactor for RenderPassImmediately