Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/Features/LightLimitFix/ShadowCasterManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3878,13 +3878,45 @@ 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<int32_t>(rd.shadowmapDescriptors[0].shadowmapIndex);
}
auto& rd = light->GetRuntimeData();
if (rd.shadowmapDescriptors.empty())
return -1;
return static_cast<int32_t>(rd.shadowmapDescriptors[0].shadowmapIndex);
}

int32_t GetShadowSlot(RE::BSShadowLight* light)
{
// Returns the kSHADOWMAPS texture-array slot for `light`, or -1 if the
// light has no kSHADOWMAPS slice. Pool index == texture slot for point
// 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;
Expand Down
10 changes: 8 additions & 2 deletions src/Features/LightLimitFix/ShadowCasterManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -610,15 +610,21 @@ 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.
/// Consumers (ShadowRenderer upload, LightLimitFix cluster builder,
/// 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
Expand Down
Loading