Files
UnrealEngine/Engine/Source/Runtime/Renderer/Private/Shadows/ShadowScene.cpp
2025-05-18 13:04:45 +08:00

311 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ShadowScene.h"
#include "ScenePrivate.h"
#include "Algo/ForEach.h"
#include "ShadowSceneRenderer.h"
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
#include "DynamicPrimitiveDrawing.h"
#include "LightSceneProxy.h"
#endif
TAutoConsoleVariable<int32> CVarDebugDrawLightActiveStateTracking(
TEXT("r.Shadow.Scene.DebugDrawLightActiveStateTracking"),
0,
TEXT("."),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarShadowSceneLightActiveFrameCount(
TEXT("r.Shadow.Scene.LightActiveFrameCount"),
10,
TEXT("Number of frames before a light that has been moving (updated or transform changed) goes to inactive state.\n")
TEXT(" This determines the number of frames that the MobilityFactor goes to zero over, and thus a higher number spreads invalidations out over a longer time."),
ECVF_RenderThreadSafe
);
static TAutoConsoleVariable<bool> CVarVirtualShadowMapFirstPersonClipmap(
TEXT( "r.FirstPerson.Shadow.Virtual.Clipmap" ),
true,
TEXT( "Enable/Disable support for first-person clipmap for the world-space representation." ),
ECVF_Scalability | ECVF_RenderThreadSafe
);
static bool IsFirstPersonVirtualShadowMapEnabled(EShaderPlatform ShaderPlatform)
{
const bool bCVarEnabled = CVarVirtualShadowMapFirstPersonClipmap.GetValueOnRenderThread() != 0;
const bool bGBufferBitSupported = HasFirstPersonGBufferBit(ShaderPlatform);
return bCVarEnabled && bGBufferBitSupported;
}
DECLARE_DWORD_COUNTER_STAT(TEXT("Active Light Count"), STAT_ActiveLightCount, STATGROUP_ShadowRendering);
IMPLEMENT_SCENE_EXTENSION(FShadowScene);
void FShadowScene::FUpdater::PreLightsUpdate(FRDGBuilder& GraphBuilder, const FLightSceneChangeSet& LightSceneChangeSet)
{
// Don't sync if there is no work to do.
if (!LightSceneChangeSet.RemovedLightIds.IsEmpty())
{
// Need to wait in case the update is performed several times in a row for some reason.
ShadowScene.SceneChangeUpdateTask.Wait();
// TODO: maybe make async?
{
// Oust all removed Ids.
for (int32 Id : LightSceneChangeSet.RemovedLightIds)
{
ShadowScene.ActiveLights[Id] = false;
if (ShadowScene.LightsCommonData.IsValidIndex(Id))
{
ShadowScene.LightsCommonData.RemoveAt(Id);
}
if (ShadowScene.Scene.Lights[Id].LightType == LightType_Directional)
{
int32 DirLightIndex = ShadowScene.DirectionalLights.IndexOfByPredicate([Id](const FDirectionalLightData& DirectionalLightData) { return DirectionalLightData.LightId == Id; });
if (DirLightIndex != INDEX_NONE)
{
ShadowScene.DirectionalLights.RemoveAtSwap(DirLightIndex);
}
}
}
}
}
}
void FShadowScene::FUpdater::PostLightsUpdate(FRDGBuilder& GraphBuilder, const FLightSceneChangeSet& LightSceneChangeSet)
{
// don't spawn async work for no good reason.
constexpr int32 kMinWorkSizeForAsync = 64;
int32 WorkSize = LightSceneChangeSet.SceneLightInfoUpdates.NumCommands();
// Don't sync, or kick off a new job if there is no work to do.
if (WorkSize > 0)
{
// Need to wait in case the update is performed several times in a row for some reason.
ShadowScene.SceneChangeUpdateTask.Wait();
ShadowScene.SceneChangeUpdateTask = GraphBuilder.AddSetupTask([this, LightSceneChangeSet]
{
// Track active lights (those that are or were moving, and thus need updating)
ShadowScene.ActiveLights.SetNum(FMath::Max(LightSceneChangeSet.PreUpdateMaxIndex, LightSceneChangeSet.PostUpdateMaxIndex), false);
auto UpdateLight = [&](int32 Id)
{
const FLightSceneInfoCompact& LightSceneInfoCompact = ShadowScene.Scene.Lights[Id];
FLightCommonData& LightCommonData = ShadowScene.GetOrAddLightCommon(Id);
// Mark as not rendered
LightCommonData.FirstActiveFrameNumber = INDEX_NONE;
// Only movable lights can become "active" (i.e., having moved recently and thus needing active update)
if (LightSceneInfoCompact.bIsMovable)
{
ShadowScene.ActiveLights[Id] = true;
LightCommonData.MobilityFactor = 1.0f;
}
else
{
ShadowScene.ActiveLights[Id] = false;
LightCommonData.MobilityFactor = 0.0f;
// Go straight to not active
}
};
for (int32 LightId : LightSceneChangeSet.AddedLightIds)
{
if (ShadowScene.Scene.Lights[LightId].LightType == LightType_Directional)
{
check(ShadowScene.DirectionalLights.FindByPredicate([LightId](const FDirectionalLightData& DirectionalLightData) { return DirectionalLightData.LightId == LightId; }) == nullptr);
ShadowScene.DirectionalLights.Emplace(FDirectionalLightData{LightId});
}
UpdateLight(LightId);
}
for (const auto& Item : LightSceneChangeSet.SceneLightInfoUpdates.GetRangeView<FUpdateLightTransformParameters>())
{
UpdateLight(Item.SceneInfo->Id);
}
}, WorkSize > kMinWorkSizeForAsync);
}
}
void FShadowScene::FUpdater::PreSceneUpdate(FRDGBuilder& GraphBuilder, const FScenePreUpdateChangeSet& ChangeSet, FSceneUniformBuffer& SceneUniforms)
{
// Note: if moving this to an async task, also make sure to properly sync/depend any use of `AlwaysInvalidatingPrimitives` (which is exposed in GetAlwaysInvalidatingPrimitives)
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : ChangeSet.RemovedPrimitiveSceneInfos)
{
if (PrimitiveSceneInfo->Proxy->GetShadowCacheInvalidationBehavior() == EShadowCacheInvalidationBehavior::Always)
{
ShadowScene.AlwaysInvalidatingPrimitives.RemoveSwap(PrimitiveSceneInfo);
}
if (ShadowScene.bEnableVirtualShadowMapFirstPersonClipmap && PrimitiveSceneInfo->Proxy->IsFirstPersonWorldSpaceRepresentation())
{
ShadowScene.FirstPersonWorldSpacePrimitives.RemoveSwap(PrimitiveSceneInfo);
}
}
}
void FShadowScene::FUpdater::PostSceneUpdate(FRDGBuilder& GraphBuilder, const FScenePostUpdateChangeSet& ChangeSet)
{
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : ChangeSet.AddedPrimitiveSceneInfos)
{
if (PrimitiveSceneInfo->Proxy->GetShadowCacheInvalidationBehavior() == EShadowCacheInvalidationBehavior::Always)
{
ShadowScene.AlwaysInvalidatingPrimitives.Add(PrimitiveSceneInfo);
}
if (ShadowScene.bEnableVirtualShadowMapFirstPersonClipmap && PrimitiveSceneInfo->Proxy->IsFirstPersonWorldSpaceRepresentation())
{
ShadowScene.FirstPersonWorldSpacePrimitives.Add(PrimitiveSceneInfo);
}
}
const bool bNewEnabled = IsFirstPersonVirtualShadowMapEnabled(ShadowScene.Scene.GetShaderPlatform());
// When toggled we must find all primitives marked for this path
if (bNewEnabled && !ShadowScene.bEnableVirtualShadowMapFirstPersonClipmap)
{
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : ShadowScene.Scene.Primitives)
{
if (PrimitiveSceneInfo->Proxy->IsFirstPersonWorldSpaceRepresentation())
{
ShadowScene.FirstPersonWorldSpacePrimitives.Add(PrimitiveSceneInfo);
}
}
}
ShadowScene.bEnableVirtualShadowMapFirstPersonClipmap = bNewEnabled;
if (!ShadowScene.bEnableVirtualShadowMapFirstPersonClipmap)
{
ShadowScene.FirstPersonWorldSpacePrimitives.Empty();
}
}
void FShadowScene::UpdateForRenderedFrame(FRDGBuilder& GraphBuilder)
{
SceneChangeUpdateTask.Wait();
const int32 ActiveFrameCount = FMath::Max(1, CVarShadowSceneLightActiveFrameCount.GetValueOnRenderThread());
// 1. FScene::FrameNumber is incremented before a call to render is being dispatched to the RT.
int32 SceneFrameNumber = Scene.GetFrameNumberRenderThread();
// Iterate the previously active lights and update
for (TConstSetBitIterator<> BitIt(ActiveLights); BitIt; ++BitIt)
{
int32 Id = BitIt.GetIndex();
// No need to process if we already decided it is active (i.e., it was moved again this frame)
FLightCommonData& Light = LightsCommonData[Id];
// If it was not rendered before, we record the current scene frame number
if (Light.FirstActiveFrameNumber == INDEX_NONE)
{
Light.FirstActiveFrameNumber = SceneFrameNumber;
Light.MobilityFactor = 1.0f;
}
else if(SceneFrameNumber - Light.FirstActiveFrameNumber < ActiveFrameCount)
{
// If it was updated before, but not this frame, check how many rendered frames have elapsed and transition to the inactive state
Light.MobilityFactor = 1.0f - FMath::Clamp(float(SceneFrameNumber - Light.FirstActiveFrameNumber) / float(ActiveFrameCount), 0.0f, 1.0f);
}
else
{
// It's not been updated for more than K frames transition to non-active state
ActiveLights[Id] = false;
Light.MobilityFactor = 0.0f;
}
}
SET_DWORD_STAT(STAT_ActiveLightCount, ActiveLights.CountSetBits());
}
float FShadowScene::GetLightMobilityFactor(int32 LightId) const
{
SceneChangeUpdateTask.Wait();
if (IsActive(LightId))
{
return LightsCommonData[LightId].MobilityFactor;
}
return 0.0f;
}
void FShadowScene::DebugRender(TArrayView<FViewInfo> Views)
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (CVarDebugDrawLightActiveStateTracking.GetValueOnRenderThread() != 0)
{
SceneChangeUpdateTask.Wait();
for (FViewInfo& View : Views)
{
FViewElementPDI DebugPDI(&View, nullptr, &View.DynamicPrimitiveCollector);
for (TConstSetBitIterator<> BitIt(ActiveLights); BitIt; ++BitIt)
{
int32 Id = BitIt.GetIndex();
FLightSceneInfoCompact& LightSceneInfoCompact = Scene.Lights[Id];
FLightSceneProxy* Proxy = LightSceneInfoCompact.LightSceneInfo->Proxy;
FLinearColor Color = FMath::Lerp(FLinearColor(FColor::Yellow), FLinearColor(FColor::Blue), GetLightMobilityFactor(Id));
switch (LightSceneInfoCompact.LightType)
{
case LightType_Directional:
DrawWireSphereAutoSides(&DebugPDI, Proxy->GetLightToWorld().GetOrigin(), Color, FMath::Min(100.0f, Proxy->GetRadius()), SDPG_World);
DrawWireSphereAutoSides(&DebugPDI, Proxy->GetLightToWorld().GetOrigin(), Color, FMath::Min(200.0f, Proxy->GetRadius()), SDPG_World);
break;
case LightType_Spot:
{
FTransform TransformNoScale = FTransform(Proxy->GetLightToWorld());
TransformNoScale.RemoveScaling();
DrawWireSphereCappedCone(&DebugPDI, TransformNoScale, Proxy->GetRadius(), FMath::RadiansToDegrees(Proxy->GetOuterConeAngle()), 16, 4, 8, Color, SDPG_World);
}
break;
default:
{
DrawWireSphereAutoSides(&DebugPDI, Proxy->GetPosition(), Color, Proxy->GetRadius(), SDPG_World);
}
break;
};
}
}
}
#endif
}
void FShadowScene::WaitForSceneLightsUpdateTask()
{
SceneChangeUpdateTask.Wait();
}
bool FShadowScene::ShouldCreateExtension(FScene& InScene)
{
return true;
}
void FShadowScene::InitExtension(FScene& InScene)
{
}
ISceneExtensionUpdater* FShadowScene::CreateUpdater()
{
return new FUpdater(*this);
}
ISceneExtensionRenderer* FShadowScene::CreateRenderer(FSceneRendererBase& InSceneRenderer, const FEngineShowFlags& EngineShowFlags)
{
if (FDeferredShadingSceneRenderer* DeferredShadingSceneRenderer = InSceneRenderer.GetDeferredShadingSceneRenderer())
{
return new FShadowSceneRenderer(*DeferredShadingSceneRenderer, *this);
}
return nullptr;
}