diff --git a/src/Features/LightLimitFix/ShadowCasterManager.cpp b/src/Features/LightLimitFix/ShadowCasterManager.cpp index c417077260..f6f250b1bb 100644 --- a/src/Features/LightLimitFix/ShadowCasterManager.cpp +++ b/src/Features/LightLimitFix/ShadowCasterManager.cpp @@ -3878,6 +3878,32 @@ namespace ShadowCasterManager return s_lights; } + bool IsActive() + { + // Boot-latched, not the live s_settings.Enabled: hooks install only when + // enabled at boot, and runtime toggles are restart-gated. + return s_bootEnabled && !s_externalConflict; + } + + // Engine's native kSHADOWMAPS slice from the live descriptor, for when SCM + // is inactive and its pool is empty. Sun (-> kSHADOWMAPS_ESRAM) and lights + // without a descriptor return -1 so callers skip them. + static int32_t GetEngineShadowSlot(RE::BSShadowLight* light) + { + if (!light || light->GetIsDirectionalLight()) + return -1; + if (globals::game::isVR) { + auto& rd = light->GetVRRuntimeData(); + if (rd.shadowmapDescriptors.empty()) + return -1; + return static_cast(rd.shadowmapDescriptors[0].shadowmapIndex); + } + auto& rd = light->GetRuntimeData(); + if (rd.shadowmapDescriptors.empty()) + return -1; + return static_cast(rd.shadowmapDescriptors[0].shadowmapIndex); + } + int32_t GetShadowSlot(RE::BSShadowLight* light) { // Returns the kSHADOWMAPS texture-array slot for `light`, or -1 if the @@ -3885,6 +3911,12 @@ namespace ShadowCasterManager // lights (1:1). Sun's pool slot returns -1 since the sun renders to // kSHADOWMAPS_ESRAM (a separate texture) — callers in ShadowRenderer // upload and LightLimitFix cluster builder must skip it. + // + // With SCM disabled at boot the pool is empty; without this fallback + // every caster returns -1 and the cluster builder drops it (#97). + if (!IsActive()) + return GetEngineShadowSlot(light); + const int32_t poolIdx = s_lights.FindLight(light, s_settings.ShadowLightCount); if (poolIdx < 0) return -1; diff --git a/src/Features/LightLimitFix/ShadowCasterManager.h b/src/Features/LightLimitFix/ShadowCasterManager.h index 6dcaf7437b..fd1f04f48c 100644 --- a/src/Features/LightLimitFix/ShadowCasterManager.h +++ b/src/Features/LightLimitFix/ShadowCasterManager.h @@ -610,6 +610,10 @@ namespace ShadowCasterManager /// Returns a read-only view of the active light pool for UI/visualization. const LightContainer& GetLights(); + /// True when SCM owns shadow scheduling this session (enabled at boot, no + /// external conflict). False = engine's vanilla pipeline. Boot-latched. + bool IsActive(); + /// Returns the kSHADOWMAPS texture-array slot for an active point/spot /// shadow light as a raw slice index 0..GetInstalledSlotCount()-1, or -1 /// when the light is either not active in the SCM pool OR is the sun. @@ -617,8 +621,10 @@ namespace ShadowCasterManager /// strict-light shadow-flag setup) treat the -1 sentinel as "skip" -- /// the sun renders to kSHADOWMAPS_ESRAM (a separate texture) and has no /// kSHADOWMAPS slice; inactive lights have no slot at all. - /// Uses the internal s_lights pool -- does not read the descriptor's - /// shadowmapIndex field, which may be corrupted by ReturnShadowmaps(). + /// When SCM is active, reads the s_lights pool (not the descriptor's + /// shadowmapIndex, which ReturnShadowmaps() can corrupt). When inactive the + /// pool is empty, so it falls back to the engine's own + /// shadowmapDescriptors[0].shadowmapIndex (the vanilla slice). int32_t GetShadowSlot(RE::BSShadowLight* light); /// Visit every shadow light currently demoted to non-shadow rendering via