3080 lines
115 KiB
C++
3080 lines
115 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "RendererPrivate.h"
|
|
#include "ScenePrivate.h"
|
|
#include "SceneProxies/SkyLightSceneProxy.h"
|
|
#include "SceneUtils.h"
|
|
#include "PipelineStateCache.h"
|
|
#include "ShaderParameterStruct.h"
|
|
#include "LightSceneProxy.h"
|
|
#include "GPUScene.h"
|
|
#include "Rendering/NaniteResources.h"
|
|
#include "Nanite/Nanite.h"
|
|
#include "Nanite/NaniteShading.h"
|
|
#include "PixelShaderUtils.h"
|
|
#include "Lumen.h"
|
|
#include "LumenMeshCards.h"
|
|
#include "LumenSurfaceCacheFeedback.h"
|
|
#include "LumenSceneLighting.h"
|
|
#include "LumenSceneCardCapture.h"
|
|
#include "LumenTracingUtils.h"
|
|
#include "GlobalDistanceField.h"
|
|
#include "DistanceFieldAmbientOcclusion.h"
|
|
#include "HAL/LowLevelMemStats.h"
|
|
#include "PostProcess/SceneRenderTargets.h"
|
|
#include "ProfilingDebugging/CpuProfilerTrace.h"
|
|
#include "DataDrivenShaderPlatformInfo.h"
|
|
#include "StaticMeshBatch.h"
|
|
#include "LumenReflections.h"
|
|
#include "RayTracedTranslucency.h"
|
|
#include "LumenRadiosity.h"
|
|
#include "StereoRendering.h"
|
|
#include "SkyAtmosphereRendering.h"
|
|
#include "ComponentRecreateRenderStateContext.h"
|
|
#include "VT/VirtualTextureFeedbackResource.h"
|
|
|
|
int32 GLumenFastCameraMode = 0;
|
|
FAutoConsoleVariableRef CVarLumenFastCameraMode(
|
|
TEXT("r.LumenScene.FastCameraMode"),
|
|
GLumenFastCameraMode,
|
|
TEXT("Whether to update the Lumen Scene for fast camera movement - lower quality, faster updates so lighting can keep up with the camera."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GLumenSceneParallelUpdate = 1;
|
|
FAutoConsoleVariableRef CVarLumenSceneParallelUpdate(
|
|
TEXT("r.LumenScene.ParallelUpdate"),
|
|
GLumenSceneParallelUpdate,
|
|
TEXT("Whether to run the Lumen Scene update in parallel."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GLumenScenePrimitivesPerTask = 128;
|
|
FAutoConsoleVariableRef CVarLumenScenePrimitivePerTask(
|
|
TEXT("r.LumenScene.PrimitivesPerTask"),
|
|
GLumenScenePrimitivesPerTask,
|
|
TEXT("How many primitives to process per single surface cache update task."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GLumenSceneMeshCardsPerTask = 128;
|
|
FAutoConsoleVariableRef CVarLumenSceneMeshCardsPerTask(
|
|
TEXT("r.LumenScene.MeshCardsPerTask"),
|
|
GLumenSceneMeshCardsPerTask,
|
|
TEXT("How many mesh cards to process per single surface cache update task."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GLumenSurfaceCacheFreeze = 0;
|
|
FAutoConsoleVariableRef CVarLumenSceneSurfaceCacheFreeze(
|
|
TEXT("r.LumenScene.SurfaceCache.Freeze"),
|
|
GLumenSurfaceCacheFreeze,
|
|
TEXT("Freeze surface cache updates for debugging.\n"),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GLumenSurfaceCacheFreezeUpdateFrame = 0;
|
|
FAutoConsoleVariableRef CVarLumenSceneSurfaceCacheFreezeUpdateFrame(
|
|
TEXT("r.LumenScene.SurfaceCache.FreezeUpdateFrame"),
|
|
GLumenSurfaceCacheFreezeUpdateFrame,
|
|
TEXT("Keep updating the same subset of surface cache for debugging and profiling.\n"),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GLumenSceneSurfaceCacheReset = 0;
|
|
FAutoConsoleVariableRef CVarLumenSceneSurfaceCacheReset(
|
|
TEXT("r.LumenScene.SurfaceCache.Reset"),
|
|
GLumenSceneSurfaceCacheReset,
|
|
TEXT("Reset all atlases and captured cards.\n"),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GLumenSceneSurfaceCacheResetEveryNthFrame = 0;
|
|
FAutoConsoleVariableRef CVarLumenSceneSurfaceCacheResetEveryNthFrame(
|
|
TEXT("r.LumenScene.SurfaceCache.ResetEveryNthFrame"),
|
|
GLumenSceneSurfaceCacheResetEveryNthFrame,
|
|
TEXT("Continuously reset all atlases and captured cards every N-th frame.\n"),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GLumenSceneCardCapturesPerFrame = 300;
|
|
FAutoConsoleVariableRef CVarLumenSceneCardCapturesPerFrame(
|
|
TEXT("r.LumenScene.SurfaceCache.CardCapturesPerFrame"),
|
|
GLumenSceneCardCapturesPerFrame,
|
|
TEXT(""),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GLumenSceneCardCaptureFactor = 64;
|
|
FAutoConsoleVariableRef CVarLumenSceneCardCaptureFactor(
|
|
TEXT("r.LumenScene.SurfaceCache.CardCaptureFactor"),
|
|
GLumenSceneCardCaptureFactor,
|
|
TEXT("Controls how many texels can be captured per frame. Texels = SurfaceCacheTexels / Factor."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
TAutoConsoleVariable<int32> CVarLumenSceneSurfaceCacheRemovesPerFrame(
|
|
TEXT("r.LumenScene.SurfaceCache.RemovesPerFrame"),
|
|
512,
|
|
TEXT("How many mesh cards removes can be done per frame."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe);
|
|
|
|
TAutoConsoleVariable<float> CVarLumenSceneCardCaptureRefreshFraction(
|
|
TEXT("r.LumenScene.SurfaceCache.CardCaptureRefreshFraction"),
|
|
0.125f,
|
|
TEXT("Fraction of card capture budget allowed to be spent on re-capturing existing pages in order to refresh surface cache materials.\n")
|
|
TEXT("0 disables card refresh."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
TAutoConsoleVariable<int32> CVarLumenSceneCardCaptureEnableInvalidation(
|
|
TEXT("r.LumenScene.SurfaceCache.CardCaptureEnableInvalidation"),
|
|
1,
|
|
TEXT("Whether to enable manual card recapture through InvalidateSurfaceCacheForPrimitive().\n"),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
float GLumenSceneCardFixedDebugResolution = -1;
|
|
FAutoConsoleVariableRef CVarLumenSceneCardFixedDebugResolution(
|
|
TEXT("r.LumenScene.SurfaceCache.CardFixedDebugResolution"),
|
|
GLumenSceneCardFixedDebugResolution,
|
|
TEXT("Lumen card resolution"),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
float GLumenSceneCardMaxTexelDensity = .2f;
|
|
FAutoConsoleVariableRef CVarLumenSceneCardMaxTexelDensity(
|
|
TEXT("r.LumenScene.SurfaceCache.CardMaxTexelDensity"),
|
|
GLumenSceneCardMaxTexelDensity,
|
|
TEXT("Lumen card texels per world space distance"),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GLumenSceneCardMaxResolution = 512;
|
|
FAutoConsoleVariableRef CVarLumenSceneCardMaxResolution(
|
|
TEXT("r.LumenScene.SurfaceCache.CardMaxResolution"),
|
|
GLumenSceneCardMaxResolution,
|
|
TEXT("Maximum card resolution in Lumen Scene"),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GSurfaceCacheNumFramesToKeepUnusedPages = 256;
|
|
FAutoConsoleVariableRef CVarLumenSceneSurfaceCacheNumFramesToKeepUnusedPages(
|
|
TEXT("r.LumenScene.SurfaceCache.NumFramesToKeepUnusedPages"),
|
|
GSurfaceCacheNumFramesToKeepUnusedPages,
|
|
TEXT("Num frames to keep unused pages in surface cache."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GLumenSceneForceEvictHiResPages = 0;
|
|
FAutoConsoleVariableRef CVarLumenSceneForceEvictHiResPages(
|
|
TEXT("r.LumenScene.SurfaceCache.ForceEvictHiResPages"),
|
|
GLumenSceneForceEvictHiResPages,
|
|
TEXT("Evict all optional hi-res surface cache pages."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GLumenSceneRecaptureLumenSceneEveryFrame = 0;
|
|
FAutoConsoleVariableRef CVarLumenGIRecaptureLumenSceneEveryFrame(
|
|
TEXT("r.LumenScene.SurfaceCache.RecaptureEveryFrame"),
|
|
GLumenSceneRecaptureLumenSceneEveryFrame,
|
|
TEXT(""),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GLumenSceneSurfaceCacheLogUpdates = 0;
|
|
FAutoConsoleVariableRef CVarLumenSceneSurfaceCacheLogUpdates(
|
|
TEXT("r.LumenScene.SurfaceCache.LogUpdates"),
|
|
GLumenSceneSurfaceCacheLogUpdates,
|
|
TEXT("Whether to log Lumen surface cache updates.\n")
|
|
TEXT("2 - will log mesh names."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GLumenSceneSurfaceCacheResampleLighting = 1;
|
|
FAutoConsoleVariableRef CVarLumenSceneSurfaceCacheResampleLighting(
|
|
TEXT("r.LumenScene.SurfaceCache.ResampleLighting"),
|
|
GLumenSceneSurfaceCacheResampleLighting,
|
|
TEXT("Whether to resample card lighting when cards are reallocated. This is needed for Radiosity temporal accumulation but can be disabled for debugging."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarLumenSceneSurfaceCacheNaniteMultiView(
|
|
TEXT("r.LumenScene.SurfaceCache.Nanite.MultiView"),
|
|
1,
|
|
TEXT("Toggle multi view Lumen Nanite Card capture for debugging."),
|
|
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
|
|
{
|
|
Lumen::DebugResetSurfaceCache();
|
|
}),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarLumenSceneSurfaceCacheNaniteAsyncRasterization(
|
|
TEXT("r.LumenScene.SurfaceCache.Nanite.AsyncRasterization"),
|
|
0,
|
|
TEXT("Whether to use Nanite async rasterization for Mesh Card capture."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarLumenScenePropagateGlobalLightingChange(
|
|
TEXT("r.LumenScene.PropagateGlobalLightingChange"),
|
|
1,
|
|
TEXT("Whether to detect big scene lighting changes and speedup Lumen update for those frames."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarLumenSceneGPUDrivenUpdate(
|
|
TEXT("r.LumenScene.GPUDrivenUpdate"),
|
|
0,
|
|
TEXT("Whether to use GPU to update Lumen Scene. Work in progress."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarLumenSceneViewOriginDistanceThreshold(
|
|
TEXT("r.LumenScene.ViewOriginDistanceThreshold"),
|
|
100,
|
|
TEXT("Distance threshold below which views' origins are considered identical. Used for streaming request with multiple views. Default 100 (= 1 meter)"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarLumenSceneUploadEveryFrame(
|
|
TEXT("r.LumenScene.UploadEveryFrame"),
|
|
0,
|
|
TEXT("Whether to upload the entire Lumen Scene's data every frame. Useful for debugging."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarLumenSceneSurfaceCacheAllowCardDownsampleFromSelf(
|
|
TEXT("r.LumenScene.SurfaceCache.AllowCardDownsampleFromSelf"),
|
|
1,
|
|
TEXT("Whether cards are allowed to downsample from self instead of recapture when lowering resolutions."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarLumenSceneSurfaceCacheAllowCardSharing(
|
|
TEXT("r.LumenScene.SurfaceCache.AllowCardSharing"),
|
|
1,
|
|
TEXT("Whether to allow sharing Lumen cards between instances. Automatically disabled if r.LumenScene.SurfaceCache.CullUndergroundTexels is enabled."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarLumenSceneSurfaceCacheDetectCardSharingCompatibility(
|
|
TEXT("r.LumenScene.SurfaceCache.DetectCardSharingCompatibility"),
|
|
1,
|
|
TEXT("Whether to auto detect card sharing compatibility. Currently, a component is incompatible if it uses any material that has any of the following nodes: ")
|
|
TEXT("PerInstanceRandom, PerInstanceCustomData, WorldPosition, and ActorPositionWS."),
|
|
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* CVar) { FGlobalComponentRecreateRenderStateContext Context; }),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
#if ENABLE_LOW_LEVEL_MEM_TRACKER
|
|
DECLARE_LLM_MEMORY_STAT(TEXT("Lumen"), STAT_LumenLLM, STATGROUP_LLMFULL);
|
|
DECLARE_LLM_MEMORY_STAT(TEXT("Lumen"), STAT_LumenSummaryLLM, STATGROUP_LLM);
|
|
LLM_DEFINE_TAG(Lumen, NAME_None, NAME_None, GET_STATFNAME(STAT_LumenLLM), GET_STATFNAME(STAT_LumenSummaryLLM));
|
|
#endif // ENABLE_LOW_LEVEL_MEM_TRACKER
|
|
|
|
extern int32 GAllowLumenDiffuseIndirect;
|
|
extern int32 GAllowLumenReflections;
|
|
|
|
void Lumen::DebugResetSurfaceCache()
|
|
{
|
|
GLumenSceneSurfaceCacheReset = 1;
|
|
}
|
|
|
|
bool Lumen::IsSurfaceCacheFrozen()
|
|
{
|
|
return GLumenSurfaceCacheFreeze != 0;
|
|
}
|
|
|
|
bool Lumen::IsSurfaceCacheUpdateFrameFrozen()
|
|
{
|
|
return GLumenSurfaceCacheFreeze != 0 || GLumenSurfaceCacheFreezeUpdateFrame != 0;
|
|
}
|
|
|
|
int32 GetCardMaxResolution()
|
|
{
|
|
if (GLumenFastCameraMode)
|
|
{
|
|
return GLumenSceneCardMaxResolution / 2;
|
|
}
|
|
|
|
return GLumenSceneCardMaxResolution;
|
|
}
|
|
|
|
int32 GetMaxLumenSceneCardCapturesPerFrame()
|
|
{
|
|
return FMath::Max(GLumenSceneCardCapturesPerFrame * (GLumenFastCameraMode ? 2 : 1), 0);
|
|
}
|
|
|
|
namespace LumenScene
|
|
{
|
|
bool AllowSurfaceCacheCardSharing()
|
|
{
|
|
return CVarLumenSceneSurfaceCacheAllowCardSharing.GetValueOnRenderThread() != 0 && !LumenScene::CullUndergroundTexels();
|
|
}
|
|
|
|
int32 GetMaxMeshCardsToAddPerFrame()
|
|
{
|
|
return 2 * GetMaxLumenSceneCardCapturesPerFrame();
|
|
}
|
|
|
|
int32 GetMaxMeshCardsRemovesPerFrame()
|
|
{
|
|
return FMath::Max(CVarLumenSceneSurfaceCacheRemovesPerFrame.GetValueOnRenderThread(), 0);
|
|
}
|
|
}
|
|
|
|
int32 GetMaxTileCapturesPerFrame()
|
|
{
|
|
if (Lumen::IsSurfaceCacheFrozen())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (GLumenSceneRecaptureLumenSceneEveryFrame != 0)
|
|
{
|
|
return INT32_MAX;
|
|
}
|
|
|
|
return GetMaxLumenSceneCardCapturesPerFrame();
|
|
}
|
|
|
|
uint32 FLumenSceneData::GetSurfaceCacheUpdateFrameIndex() const
|
|
{
|
|
return SurfaceCacheUpdateFrameIndex;
|
|
}
|
|
|
|
void FLumenSceneData::IncrementSurfaceCacheUpdateFrameIndex()
|
|
{
|
|
if (!Lumen::IsSurfaceCacheUpdateFrameFrozen())
|
|
{
|
|
++SurfaceCacheUpdateFrameIndex;
|
|
if (SurfaceCacheUpdateFrameIndex == 0)
|
|
{
|
|
++SurfaceCacheUpdateFrameIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AddLumenStreamingViewOrigins(const FSceneViewFamily& ViewFamily, TArray<FVector, TInlineAllocator<LUMEN_MAX_VIEWS>>& OutOrigins)
|
|
{
|
|
// Add streaming view origins, only if there are futher apart than existing origins
|
|
const float DistanceThreshold = CVarLumenSceneViewOriginDistanceThreshold.GetValueOnRenderThread();
|
|
const float SqDistanceThreshold = DistanceThreshold * DistanceThreshold;
|
|
for (const FVector& StreamViewOrigin : ViewFamily.StreamingViewOrigins)
|
|
{
|
|
bool bAddOrigin = true;
|
|
for (const FVector& Origin : OutOrigins)
|
|
{
|
|
if (FVector::DistSquared(StreamViewOrigin, Origin) < SqDistanceThreshold)
|
|
{
|
|
bAddOrigin = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bAddOrigin && OutOrigins.Num() < LUMEN_MAX_VIEWS)
|
|
{
|
|
OutOrigins.Add(StreamViewOrigin);
|
|
}
|
|
}
|
|
}
|
|
|
|
DECLARE_GPU_STAT(LumenSceneUpdate);
|
|
DECLARE_GPU_STAT(UpdateLumenSceneBuffers);
|
|
|
|
IMPLEMENT_STATIC_UNIFORM_BUFFER_STRUCT(FLumenCardPassUniformParameters, "LumenCardPass", SceneTextures);
|
|
|
|
bool LumenScene::HasPrimitiveNaniteMeshBatches(const FPrimitiveSceneProxy* Proxy)
|
|
{
|
|
return Proxy
|
|
&& Proxy->ShouldRenderInMainPass()
|
|
&& (Proxy->AffectsDynamicIndirectLighting()
|
|
|| (Proxy->IsLandscapeNaniteProxy() && Proxy->GetSourceLandscapeComponentIds().Num() > 0));
|
|
}
|
|
|
|
struct FMeshCardsAdd
|
|
{
|
|
int32 PrimitiveGroupIndex;
|
|
float DistanceSquared;
|
|
};
|
|
|
|
struct FMeshCardsRemove
|
|
{
|
|
int32 PrimitiveGroupIndex;
|
|
};
|
|
|
|
struct FInstanceRange
|
|
{
|
|
int32 Offset;
|
|
int32 Num;
|
|
|
|
FInstanceRange(int32 InOffset, int32 InNum)
|
|
: Offset(InOffset)
|
|
, Num(InNum)
|
|
{}
|
|
};
|
|
|
|
struct FCardAllocationOutput
|
|
{
|
|
bool bVisible = false;
|
|
int32 ResLevel = -1;
|
|
};
|
|
|
|
// Loop over Lumen primitive culling infos and output FMeshCards adds, removes, and instance culling ranges
|
|
struct FLumenSurfaceCacheCullPrimitivesTask
|
|
{
|
|
public:
|
|
FLumenSurfaceCacheCullPrimitivesTask(
|
|
TSparseArray<FLumenPrimitiveGroupCullingInfo>& InPrimitiveCullingInfos,
|
|
const TArray<FVector, TInlineAllocator<2>>& InViewOrigins,
|
|
bool bInOrthographicCamera,
|
|
float InLumenSceneDetail,
|
|
float InMaxDistanceFromCamera,
|
|
int32 InFirstCullingInfoIndex,
|
|
int32 InNumCullingInfosPerPacket,
|
|
bool InAddTranslucentToCache)
|
|
: PrimitiveCullingInfos(InPrimitiveCullingInfos)
|
|
, ViewOrigins(InViewOrigins)
|
|
, bOrthographicCamera(bInOrthographicCamera)
|
|
, FirstCullingInfoIndex(InFirstCullingInfoIndex)
|
|
, NumCullingInfosPerPacket(InNumCullingInfosPerPacket)
|
|
, LumenSceneDetail(InLumenSceneDetail)
|
|
, MaxDistanceFromCameraSq(InMaxDistanceFromCamera * InMaxDistanceFromCamera)
|
|
, TexelDensityScale(LumenScene::GetCardTexelDensity())
|
|
, MinCardResolution(FMath::Clamp(FMath::RoundToInt(LumenScene::GetCardMinResolution(bInOrthographicCamera) / LumenSceneDetail), 1, 1024))
|
|
, FarFieldCardMaxDistanceSq(LumenScene::GetFarFieldCardMaxDistance() * LumenScene::GetFarFieldCardMaxDistance())
|
|
, FarFieldCardTexelDensity(LumenScene::GetFarFieldCardTexelDensity())
|
|
, bAddTranslucentToCache(InAddTranslucentToCache)
|
|
{
|
|
}
|
|
|
|
// Output
|
|
TArray<FMeshCardsAdd> MeshCardsAdds;
|
|
TArray<FMeshCardsRemove> MeshCardsRemoves;
|
|
TArray<FInstanceRange> InstanceCullingRanges;
|
|
|
|
void AnyThreadTask()
|
|
{
|
|
const int32 LastCullingInfoIndex = FMath::Min(FirstCullingInfoIndex + NumCullingInfosPerPacket, PrimitiveCullingInfos.GetMaxIndex());
|
|
|
|
for (int32 CullingInfoIndex = FirstCullingInfoIndex; CullingInfoIndex < LastCullingInfoIndex; ++CullingInfoIndex)
|
|
{
|
|
if (PrimitiveCullingInfos.IsAllocated(CullingInfoIndex))
|
|
{
|
|
FLumenPrimitiveGroupCullingInfo& CullingInfo = PrimitiveCullingInfos[CullingInfoIndex];
|
|
|
|
// Rough card min resolution test
|
|
float DistanceSquared = FLT_MAX; // LWC_TODO
|
|
|
|
for (const FVector& ViewOrigin : ViewOrigins)
|
|
{
|
|
DistanceSquared = FMath::Min(DistanceSquared, ComputeSquaredDistanceFromBoxToPoint(FVector(CullingInfo.WorldSpaceBoundingBox.Min), FVector(CullingInfo.WorldSpaceBoundingBox.Max), ViewOrigin)); // LWC_TODO
|
|
}
|
|
|
|
const float CardMaxDistanceSq = CullingInfo.bFarField ? FarFieldCardMaxDistanceSq : MaxDistanceFromCameraSq;
|
|
|
|
if (CullingInfo.NumInstances > 0)
|
|
{
|
|
const bool bWasVisible = CullingInfo.bVisible;
|
|
CullingInfo.bVisible = DistanceSquared <= CardMaxDistanceSq;
|
|
|
|
// May need to hide instances if was visible. May need to show instances if is visible
|
|
if (bWasVisible || CullingInfo.bVisible)
|
|
{
|
|
InstanceCullingRanges.Add(FInstanceRange(CullingInfo.InstanceCullingInfoOffset, CullingInfo.NumInstances));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const float MaxCardExtent = CullingInfo.WorldSpaceBoundingBox.GetExtent().GetMax();
|
|
float MaxCardResolution;
|
|
|
|
// Far field cards have constant resolution over entire range
|
|
if (CullingInfo.bFarField)
|
|
{
|
|
MaxCardResolution = MaxCardExtent * FarFieldCardTexelDensity;
|
|
}
|
|
else
|
|
{
|
|
MaxCardResolution = (TexelDensityScale * MaxCardExtent) / FMath::Sqrt(FMath::Max(DistanceSquared, 1.0f)) + 0.01f;
|
|
}
|
|
|
|
if (DistanceSquared <= CardMaxDistanceSq && MaxCardResolution >= (CullingInfo.bEmissiveLightSource ? 1.0f : MinCardResolution) && (CullingInfo.bOpaqueOrMasked || bAddTranslucentToCache))
|
|
{
|
|
if (!CullingInfo.bVisible && CullingInfo.bValidMeshCards)
|
|
{
|
|
FMeshCardsAdd Add;
|
|
Add.PrimitiveGroupIndex = CullingInfo.PrimitiveGroupIndex;
|
|
Add.DistanceSquared = DistanceSquared;
|
|
MeshCardsAdds.Add(Add);
|
|
}
|
|
}
|
|
else if (CullingInfo.bVisible)
|
|
{
|
|
FMeshCardsRemove Remove;
|
|
Remove.PrimitiveGroupIndex = CullingInfo.PrimitiveGroupIndex;
|
|
MeshCardsRemoves.Add(Remove);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TSparseArray<FLumenPrimitiveGroupCullingInfo>& PrimitiveCullingInfos;
|
|
TArray<FVector, TInlineAllocator<2>> ViewOrigins;
|
|
bool bOrthographicCamera;
|
|
int32 FirstCullingInfoIndex;
|
|
int32 NumCullingInfosPerPacket;
|
|
float LumenSceneDetail;
|
|
float MaxDistanceFromCameraSq;
|
|
float TexelDensityScale;
|
|
|
|
const int32 MinCardResolution;
|
|
const float FarFieldCardMaxDistanceSq;
|
|
const float FarFieldCardTexelDensity;
|
|
|
|
const bool bAddTranslucentToCache;
|
|
};
|
|
|
|
// Loop over selected instances and output FMeshCards adds and removes
|
|
struct FLumenSurfaceCacheCullInstancesTask
|
|
{
|
|
public:
|
|
FLumenSurfaceCacheCullInstancesTask(
|
|
const TSparseSpanArray<FLumenPrimitiveGroupCullingInfo>& InInstanceCullingInfos,
|
|
TConstArrayView<FInstanceRange> InInstanceRanges,
|
|
TConstArrayView<int32> InRebasedRangeOffsets,
|
|
const TArray<FVector, TInlineAllocator<2>>& InViewOrigins,
|
|
bool bInOrthographicCamera,
|
|
float InLumenSceneDetail,
|
|
float InMaxDistanceFromCamera,
|
|
int32 InFirstCullingInfoIndex,
|
|
int32 InNumCullingInfosPerPacket,
|
|
bool InAddTranslucentToCache)
|
|
: InstanceCullingInfos(InInstanceCullingInfos)
|
|
, InstanceRanges(InInstanceRanges)
|
|
, RebasedRangeOffsets(InRebasedRangeOffsets)
|
|
, ViewOrigins(InViewOrigins)
|
|
, bOrthographicCamera(bInOrthographicCamera)
|
|
, FirstCullingInfoIndex(InFirstCullingInfoIndex)
|
|
, NumCullingInfosPerPacket(InNumCullingInfosPerPacket)
|
|
, LumenSceneDetail(InLumenSceneDetail)
|
|
, MaxDistanceFromCameraSq(InMaxDistanceFromCamera * InMaxDistanceFromCamera)
|
|
, TexelDensityScale(LumenScene::GetCardTexelDensity())
|
|
, MinCardResolution(FMath::Clamp(FMath::RoundToInt(LumenScene::GetCardMinResolution(bInOrthographicCamera) / LumenSceneDetail), 1, 1024))
|
|
, FarFieldCardMaxDistanceSq(LumenScene::GetFarFieldCardMaxDistance()* LumenScene::GetFarFieldCardMaxDistance())
|
|
, FarFieldCardTexelDensity(LumenScene::GetFarFieldCardTexelDensity())
|
|
, bAddTranslucentToCache(InAddTranslucentToCache)
|
|
{
|
|
}
|
|
|
|
// Output
|
|
TArray<FMeshCardsAdd> MeshCardsAdds;
|
|
TArray<FMeshCardsRemove> MeshCardsRemoves;
|
|
|
|
void AnyThreadTask()
|
|
{
|
|
int32 InstanceRangeIndex = Algo::UpperBound(RebasedRangeOffsets, FirstCullingInfoIndex) - 1;
|
|
|
|
if (InstanceRangeIndex >= RebasedRangeOffsets.Num() - 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const int32 LastCullingInfoIndex = FMath::Min(FirstCullingInfoIndex + NumCullingInfosPerPacket, RebasedRangeOffsets.Last());
|
|
int32 NumCullingInfos = LastCullingInfoIndex - FirstCullingInfoIndex;
|
|
int32 CullingInfoIndex = FirstCullingInfoIndex - RebasedRangeOffsets[InstanceRangeIndex] + InstanceRanges[InstanceRangeIndex].Offset;
|
|
|
|
while (NumCullingInfos > 0)
|
|
{
|
|
const FLumenPrimitiveGroupCullingInfo& CullingInfo = InstanceCullingInfos[CullingInfoIndex];
|
|
check(InstanceCullingInfos.IsAllocated(CullingInfoIndex) && CullingInfo.NumInstances == 0);
|
|
|
|
// Rough card min resolution test
|
|
float DistanceSquared = FLT_MAX; // LWC_TODO
|
|
|
|
for (const FVector& ViewOrigin : ViewOrigins)
|
|
{
|
|
DistanceSquared = FMath::Min(DistanceSquared, ComputeSquaredDistanceFromBoxToPoint(FVector(CullingInfo.WorldSpaceBoundingBox.Min), FVector(CullingInfo.WorldSpaceBoundingBox.Max), ViewOrigin)); // LWC_TODO
|
|
}
|
|
|
|
const float MaxCardExtent = CullingInfo.WorldSpaceBoundingBox.GetExtent().GetMax();
|
|
float CardMaxDistanceSq;
|
|
float MaxCardResolution;
|
|
|
|
// Far field cards have constant resolution over entire range
|
|
if (CullingInfo.bFarField)
|
|
{
|
|
CardMaxDistanceSq = FarFieldCardMaxDistanceSq;
|
|
MaxCardResolution = MaxCardExtent * FarFieldCardTexelDensity;
|
|
}
|
|
else
|
|
{
|
|
CardMaxDistanceSq = MaxDistanceFromCameraSq;
|
|
MaxCardResolution = (TexelDensityScale * MaxCardExtent) / FMath::Sqrt(FMath::Max(DistanceSquared, 1.0f)) + 0.01f;
|
|
}
|
|
|
|
if (DistanceSquared <= CardMaxDistanceSq && MaxCardResolution >= (CullingInfo.bEmissiveLightSource ? 1.0f : MinCardResolution) && (CullingInfo.bOpaqueOrMasked || bAddTranslucentToCache))
|
|
{
|
|
if (!CullingInfo.bVisible && CullingInfo.bValidMeshCards)
|
|
{
|
|
FMeshCardsAdd Add;
|
|
Add.PrimitiveGroupIndex = CullingInfo.PrimitiveGroupIndex;
|
|
Add.DistanceSquared = DistanceSquared;
|
|
MeshCardsAdds.Add(Add);
|
|
}
|
|
}
|
|
else if (CullingInfo.bVisible)
|
|
{
|
|
FMeshCardsRemove Remove;
|
|
Remove.PrimitiveGroupIndex = CullingInfo.PrimitiveGroupIndex;
|
|
MeshCardsRemoves.Add(Remove);
|
|
}
|
|
|
|
--NumCullingInfos;
|
|
++CullingInfoIndex;
|
|
|
|
if (NumCullingInfos > 0 && CullingInfoIndex >= InstanceRanges[InstanceRangeIndex].Offset + InstanceRanges[InstanceRangeIndex].Num)
|
|
{
|
|
++InstanceRangeIndex;
|
|
CullingInfoIndex = InstanceRanges[InstanceRangeIndex].Offset;
|
|
}
|
|
}
|
|
}
|
|
|
|
const TSparseSpanArray<FLumenPrimitiveGroupCullingInfo>& InstanceCullingInfos;
|
|
TConstArrayView<FInstanceRange> InstanceRanges;
|
|
TConstArrayView<int32> RebasedRangeOffsets;
|
|
TArray<FVector, TInlineAllocator<2>> ViewOrigins;
|
|
bool bOrthographicCamera;
|
|
int32 FirstCullingInfoIndex;
|
|
int32 NumCullingInfosPerPacket;
|
|
float LumenSceneDetail;
|
|
float MaxDistanceFromCameraSq;
|
|
float TexelDensityScale;
|
|
|
|
const int32 MinCardResolution;
|
|
const float FarFieldCardMaxDistanceSq;
|
|
const float FarFieldCardTexelDensity;
|
|
|
|
const bool bAddTranslucentToCache;
|
|
};
|
|
|
|
struct FSurfaceCacheRemove
|
|
{
|
|
public:
|
|
int32 LumenCardIndex;
|
|
};
|
|
|
|
// Loop over Lumen mesh cards and output card updates
|
|
struct FLumenSurfaceCacheUpdateMeshCardsTask
|
|
{
|
|
public:
|
|
FLumenSurfaceCacheUpdateMeshCardsTask(
|
|
const TSparseSpanArray<FLumenMeshCards>& InLumenMeshCards,
|
|
const TSparseSpanArray<FLumenCard>& InLumenCards,
|
|
const TArray<FVector, TInlineAllocator<2>>& InViewOrigins,
|
|
bool bInOrthographicCamera,
|
|
float InSurfaceCacheResolution,
|
|
float InLumenSceneDetail,
|
|
float InMaxDistanceFromCamera,
|
|
int32 InFirstMeshCardsIndex,
|
|
int32 InNumMeshCardsPerPacket)
|
|
: LumenMeshCards(InLumenMeshCards)
|
|
, LumenCards(InLumenCards)
|
|
, ViewOrigins(InViewOrigins)
|
|
, bOrthographicCamera(bInOrthographicCamera)
|
|
, LumenSceneDetail(InLumenSceneDetail)
|
|
, FirstMeshCardsIndex(InFirstMeshCardsIndex)
|
|
, NumMeshCardsPerPacket(InNumMeshCardsPerPacket)
|
|
, MaxDistanceFromCamera(InMaxDistanceFromCamera)
|
|
, TexelDensityScale(LumenScene::GetCardTexelDensity() * InSurfaceCacheResolution)
|
|
, MaxTexelDensity(GLumenSceneCardMaxTexelDensity)
|
|
, MinCardResolution(FMath::Clamp(FMath::RoundToInt(LumenScene::GetCardMinResolution(bInOrthographicCamera) / LumenSceneDetail), 1, 1024))
|
|
, FarFieldCardMaxDistance(LumenScene::GetFarFieldCardMaxDistance())
|
|
, FarFieldCardTexelDensity(LumenScene::GetFarFieldCardTexelDensity())
|
|
{
|
|
}
|
|
|
|
// Output
|
|
TArray<FSurfaceCacheRequest> SurfaceCacheRequests;
|
|
TArray<int32> CardsToHide;
|
|
int32 Histogram[Lumen::NumDistanceBuckets] { 0 };
|
|
|
|
void AnyThreadTask()
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(LumenSurfaceCacheUpdateMeshCardsTask)
|
|
|
|
const int32 LastLumenMeshCardsIndex = FMath::Min(FirstMeshCardsIndex + NumMeshCardsPerPacket, LumenMeshCards.Num());
|
|
const int32 CardMaxResolution = GetCardMaxResolution();
|
|
|
|
for (int32 MeshCardsIndex = FirstMeshCardsIndex; MeshCardsIndex < LastLumenMeshCardsIndex; ++MeshCardsIndex)
|
|
{
|
|
if (LumenMeshCards.IsAllocated(MeshCardsIndex))
|
|
{
|
|
const FLumenMeshCards& MeshCardsInstance = LumenMeshCards[MeshCardsIndex];
|
|
|
|
const int32 MinCardResolutionForMeshCards = MeshCardsInstance.bEmissiveLightSource ? 1 : MinCardResolution;
|
|
|
|
for (uint32 CardIndex = MeshCardsInstance.FirstCardIndex; CardIndex < MeshCardsInstance.FirstCardIndex + MeshCardsInstance.NumCards; ++CardIndex)
|
|
{
|
|
const FLumenCard& LumenCard = LumenCards[CardIndex];
|
|
|
|
float ViewerDistance = FLT_MAX; // LWC_TODO
|
|
|
|
for (const FVector& ViewOrigin : ViewOrigins)
|
|
{
|
|
ViewerDistance = FMath::Min(ViewerDistance, FMath::Max(FMath::Sqrt(LumenCard.WorldOBB.ComputeSquaredDistanceToPoint(ViewOrigin)), 100.0f));
|
|
}
|
|
|
|
// Compute resolution based on its largest extent
|
|
float MaxExtent = FMath::Max(LumenCard.WorldOBB.Extent.X, LumenCard.WorldOBB.Extent.Y);
|
|
float CardMaxDistance;
|
|
float MaxProjectedSize;
|
|
|
|
// Far field cards have constant resolution over entire range
|
|
if (MeshCardsInstance.bFarField)
|
|
{
|
|
CardMaxDistance = FarFieldCardMaxDistance;
|
|
MaxProjectedSize = FarFieldCardTexelDensity * MaxExtent * LumenCard.ResolutionScale;
|
|
}
|
|
else
|
|
{
|
|
CardMaxDistance = MaxDistanceFromCamera;
|
|
MaxProjectedSize = FMath::Min(TexelDensityScale * MaxExtent * LumenCard.ResolutionScale / ViewerDistance, GLumenSceneCardMaxTexelDensity * MaxExtent);
|
|
}
|
|
|
|
if (GLumenSceneCardFixedDebugResolution > 0)
|
|
{
|
|
MaxProjectedSize = GLumenSceneCardFixedDebugResolution;
|
|
}
|
|
|
|
const int32 MaxSnappedRes = FMath::RoundUpToPowerOfTwo(FMath::Min(FMath::TruncToInt(MaxProjectedSize), CardMaxResolution));
|
|
const bool bVisible = ViewerDistance < CardMaxDistance && MaxSnappedRes >= MinCardResolutionForMeshCards;
|
|
const int32 ResLevel = FMath::FloorLog2(FMath::Max<uint32>(MaxSnappedRes, Lumen::MinCardResolution));
|
|
|
|
if (!bVisible && LumenCard.bVisible)
|
|
{
|
|
CardsToHide.Add(CardIndex);
|
|
}
|
|
else if (bVisible && ResLevel != LumenCard.DesiredLockedResLevel)
|
|
{
|
|
float Distance = ViewerDistance;
|
|
|
|
if (LumenCard.bVisible && LumenCard.DesiredLockedResLevel != ResLevel)
|
|
{
|
|
// Make reallocation less important than capturing new cards
|
|
const float ResLevelDelta = FMath::Abs((int32)LumenCard.DesiredLockedResLevel - ResLevel);
|
|
Distance += (1.0f - FMath::Clamp((ResLevelDelta + 1.0f) / 3.0f, 0.0f, 1.0f)) * 2500.0f;
|
|
}
|
|
|
|
FSurfaceCacheRequest Request;
|
|
Request.ResLevel = ResLevel;
|
|
Request.CardIndex = CardIndex;
|
|
Request.LocalPageIndex = UINT16_MAX;
|
|
Request.Distance = Distance;
|
|
SurfaceCacheRequests.Add(Request);
|
|
|
|
const int32 DistanceBin = Lumen::GetMeshCardDistanceBin(Distance);
|
|
Histogram[DistanceBin]++;
|
|
|
|
ensure(Request.IsLockedMip());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const TSparseSpanArray<FLumenMeshCards>& LumenMeshCards;
|
|
const TSparseSpanArray<FLumenCard>& LumenCards;
|
|
TArray<FVector, TInlineAllocator<2>> ViewOrigins;
|
|
bool bOrthographicCamera;
|
|
float LumenSceneDetail;
|
|
int32 FirstMeshCardsIndex;
|
|
int32 NumMeshCardsPerPacket;
|
|
float MaxDistanceFromCamera;
|
|
float TexelDensityScale;
|
|
float MaxTexelDensity;
|
|
|
|
const int32 MinCardResolution;
|
|
const float FarFieldCardMaxDistance;
|
|
const float FarFieldCardTexelDensity;
|
|
};
|
|
|
|
/**
|
|
* Make sure that all mesh rendering data is prepared before we render this primitive group
|
|
* @return Will return true it primitive group is ready to render or we need to wait until next frame
|
|
*/
|
|
bool UpdateStaticMeshes(const FLumenPrimitiveGroup& PrimitiveGroup, FLumenCardRenderer& LumenCardRenderer)
|
|
{
|
|
bool bReadyToRender = true;
|
|
|
|
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : PrimitiveGroup.Primitives)
|
|
{
|
|
if (PrimitiveSceneInfo && PrimitiveSceneInfo->Proxy->AffectsDynamicIndirectLighting())
|
|
{
|
|
if (PrimitiveSceneInfo->Proxy->StaticMeshHasPendingStreaming())
|
|
{
|
|
bReadyToRender = false;
|
|
}
|
|
|
|
if (PrimitiveGroup.bHeightfield && PrimitiveSceneInfo->Proxy->HeightfieldHasPendingStreaming())
|
|
{
|
|
bReadyToRender = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bReadyToRender;
|
|
}
|
|
|
|
bool FLumenSceneData::RecaptureCardPage(const FViewInfo& MainView, FLumenCardRenderer& LumenCardRenderer, FLumenSurfaceCacheAllocator& CaptureAtlasAllocator, FRHIGPUMask GPUMask, int32 PageTableIndex)
|
|
{
|
|
TArray<FCardPageRenderData, SceneRenderingAllocator>& CardPagesToRender = LumenCardRenderer.CardPagesToRender;
|
|
FLumenPageTableEntry& PageTableEntry = GetPageTableEntry(PageTableIndex);
|
|
const FLumenCard& Card = Cards[PageTableEntry.CardIndex];
|
|
const FLumenMeshCards& MeshCardsElement = MeshCards[Card.MeshCardsIndex];
|
|
|
|
// Can we fit this card into the temporary card capture allocator?
|
|
if (CaptureAtlasAllocator.IsSpaceAvailable(Card, PageTableEntry.ResLevel, /*bSinglePage*/ true))
|
|
{
|
|
// Allocate space in temporary allocation atlas
|
|
FLumenSurfaceCacheAllocator::FAllocation CardCaptureAllocation;
|
|
CaptureAtlasAllocator.Allocate(PageTableEntry, CardCaptureAllocation);
|
|
check(CardCaptureAllocation.PhysicalPageCoord.X >= 0);
|
|
|
|
CardPagesToRender.Add(FCardPageRenderData(
|
|
MainView,
|
|
Card,
|
|
PageTableEntry.CardUVRect,
|
|
CardCaptureAllocation.PhysicalAtlasRect,
|
|
PageTableEntry.PhysicalAtlasRect,
|
|
MeshCardsElement.PrimitiveGroupIndex,
|
|
PageTableEntry.CardIndex,
|
|
PageTableIndex,
|
|
/*bResampleLastLighting*/ true));
|
|
|
|
for (uint32 GPUIndex : GPUMask)
|
|
{
|
|
LastCapturedPageHeap[GPUIndex].Update(GetSurfaceCacheUpdateFrameIndex(), PageTableIndex);
|
|
}
|
|
LumenCardRenderer.NumCardTexelsToCapture += PageTableEntry.PhysicalAtlasRect.Area();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Process a throttled number of Lumen surface cache add requests
|
|
* It will make virtual and physical allocations, and evict old pages as required
|
|
*/
|
|
void FLumenSceneData::ProcessLumenSurfaceCacheRequests(
|
|
const FViewInfo& MainView,
|
|
float MaxCardUpdateDistanceFromCamera,
|
|
int32 MaxTileCapturesPerFrame,
|
|
FLumenCardRenderer& LumenCardRenderer,
|
|
FRHIGPUMask GPUMask,
|
|
const TArray<FSurfaceCacheRequest, SceneRenderingAllocator>& SurfaceCacheRequests)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(ProcessLumenSurfaceCacheRequests);
|
|
|
|
TArray<FCardPageRenderData, SceneRenderingAllocator>& CardPagesToRender = LumenCardRenderer.CardPagesToRender;
|
|
|
|
TArray<FVirtualPageIndex, SceneRenderingAllocator> HiResPagesToMap;
|
|
TSparseUniqueList<int32, SceneRenderingAllocator> DirtyCards;
|
|
|
|
FLumenSurfaceCacheAllocator CaptureAtlasAllocator;
|
|
CaptureAtlasAllocator.Init(GetCardCaptureAtlasSizeInPages());
|
|
|
|
for (int32 RequestIndex = 0; RequestIndex < SurfaceCacheRequests.Num(); ++RequestIndex)
|
|
{
|
|
const FSurfaceCacheRequest& Request = SurfaceCacheRequests[RequestIndex];
|
|
|
|
if (Request.IsLockedMip())
|
|
{
|
|
// Update low-res locked (always resident) pages
|
|
FLumenCard& Card = Cards[Request.CardIndex];
|
|
|
|
if (Card.DesiredLockedResLevel != Request.ResLevel)
|
|
{
|
|
// Check if we can make this allocation at all
|
|
bool bCanAlloc = true;
|
|
|
|
uint8 NewLockedAllocationResLevel = Request.ResLevel;
|
|
while (!IsPhysicalSpaceAvailable(Card, NewLockedAllocationResLevel, /*bSinglePage*/ false))
|
|
{
|
|
const int32 MaxFramesSinceLastUsed = 2;
|
|
|
|
if (!EvictOldestAllocation(/*MaxFramesSinceLastUsed*/ MaxFramesSinceLastUsed, DirtyCards))
|
|
{
|
|
bCanAlloc = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Try to decrease resolution if allocation still can't be made
|
|
while (!bCanAlloc && NewLockedAllocationResLevel > Lumen::MinResLevel)
|
|
{
|
|
--NewLockedAllocationResLevel;
|
|
bCanAlloc = IsPhysicalSpaceAvailable(Card, NewLockedAllocationResLevel, /*bSinglePage*/ false);
|
|
}
|
|
|
|
// Can we fit this card into the temporary card capture allocator?
|
|
if (!CaptureAtlasAllocator.IsSpaceAvailable(Card, NewLockedAllocationResLevel, /*bSinglePage*/ false))
|
|
{
|
|
bCanAlloc = false;
|
|
}
|
|
|
|
const FLumenMeshCards& MeshCardsElement = MeshCards[Card.MeshCardsIndex];
|
|
const FLumenPrimitiveGroup& PrimitiveGroup = PrimitiveGroups[MeshCardsElement.PrimitiveGroupIndex];
|
|
|
|
if (bCanAlloc && UpdateStaticMeshes(PrimitiveGroup, LumenCardRenderer))
|
|
{
|
|
Card.bVisible = true;
|
|
Card.DesiredLockedResLevel = Request.ResLevel;
|
|
|
|
const bool bResampleLastLighting = Card.IsAllocated();
|
|
const bool bCopyFromSelf = bAllowCardDownsampleFromSelf && Card.IsAllocated() && NewLockedAllocationResLevel <= Card.MinAllocatedResLevel;
|
|
|
|
// Free previous MinAllocatedResLevel
|
|
FreeVirtualSurface(Card, Card.MinAllocatedResLevel, Card.MinAllocatedResLevel);
|
|
|
|
// Free anything lower res than the new res level
|
|
FreeVirtualSurface(Card, Card.MinAllocatedResLevel, NewLockedAllocationResLevel - 1);
|
|
|
|
|
|
const bool bLockPages = true;
|
|
ReallocVirtualSurface(Card, Request.CardIndex, NewLockedAllocationResLevel, bLockPages);
|
|
|
|
int32 CopyCardIndex = INDEX_NONE;
|
|
bool bAxisXFlipped = false;
|
|
if (bCopyFromSelf)
|
|
{
|
|
CopyCardIndex = Request.CardIndex;
|
|
}
|
|
else if (const FLumenCardSharingInfo* CardSharingInfo = FindMatchingCardForCopy(Card.CardSharingId, Card.MinAllocatedResLevel))
|
|
{
|
|
check(Card.MinAllocatedResLevel <= CardSharingInfo->MinAllocatedResLevel);
|
|
CopyCardIndex = CardSharingInfo->CardIndex;
|
|
bAxisXFlipped = Card.bAxisXFlipped != CardSharingInfo->bAxisXFlipped;
|
|
}
|
|
|
|
// Map and update all pages
|
|
FLumenSurfaceMipMap& MipMap = Card.GetMipMap(Card.MinAllocatedResLevel);
|
|
for (int32 LocalPageIndex = 0; LocalPageIndex < MipMap.SizeInPagesX * MipMap.SizeInPagesY; ++LocalPageIndex)
|
|
{
|
|
const int32 PageIndex = MipMap.GetPageTableIndex(LocalPageIndex);
|
|
FLumenPageTableEntry& PageTableEntry = GetPageTableEntry(PageIndex);
|
|
|
|
if (!PageTableEntry.IsMapped())
|
|
{
|
|
MapSurfaceCachePage(MipMap, PageIndex, GPUMask);
|
|
check(PageTableEntry.IsMapped());
|
|
|
|
// Allocate space in temporary allocation atlas
|
|
FLumenSurfaceCacheAllocator::FAllocation CardCaptureAllocation;
|
|
CaptureAtlasAllocator.Allocate(PageTableEntry, CardCaptureAllocation);
|
|
check(CardCaptureAllocation.PhysicalPageCoord.X >= 0);
|
|
|
|
CardPagesToRender.Add(FCardPageRenderData(
|
|
MainView,
|
|
Card,
|
|
PageTableEntry.CardUVRect,
|
|
CardCaptureAllocation.PhysicalAtlasRect,
|
|
PageTableEntry.PhysicalAtlasRect,
|
|
MeshCardsElement.PrimitiveGroupIndex,
|
|
Request.CardIndex,
|
|
PageIndex,
|
|
bResampleLastLighting,
|
|
bAxisXFlipped,
|
|
CopyCardIndex));
|
|
|
|
if (CopyCardIndex >= 0)
|
|
{
|
|
LumenCardRenderer.bHasAnyCardCopy = true;
|
|
}
|
|
|
|
for (uint32 GPUIndex : GPUMask)
|
|
{
|
|
LastCapturedPageHeap[GPUIndex].Update(GetSurfaceCacheUpdateFrameIndex(), PageIndex);
|
|
}
|
|
LumenCardRenderer.NumCardTexelsToCapture += PageTableEntry.PhysicalAtlasRect.Area();
|
|
}
|
|
}
|
|
|
|
DirtyCards.Add(Request.CardIndex);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Hi-Res
|
|
if (Cards.IsAllocated(Request.CardIndex))
|
|
{
|
|
FLumenCard& Card = Cards[Request.CardIndex];
|
|
|
|
if (Card.bVisible && Card.MinAllocatedResLevel >= 0 && Request.ResLevel > Card.MinAllocatedResLevel)
|
|
{
|
|
HiResPagesToMap.Add(FVirtualPageIndex(Request.CardIndex, Request.ResLevel, Request.LocalPageIndex));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (CardPagesToRender.Num() + HiResPagesToMap.Num() >= MaxTileCapturesPerFrame)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Process hi-res optional pages after locked low res ones are done
|
|
for (const FVirtualPageIndex& VirtualPageIndex : HiResPagesToMap)
|
|
{
|
|
FLumenCard& Card = Cards[VirtualPageIndex.CardIndex];
|
|
|
|
if (VirtualPageIndex.ResLevel > Card.MinAllocatedResLevel)
|
|
{
|
|
// Make room for new physical allocations
|
|
bool bCanAlloc = true;
|
|
while (!IsPhysicalSpaceAvailable(Card, VirtualPageIndex.ResLevel, /*bSinglePage*/ true))
|
|
{
|
|
// Don't want to evict pages which may be picked up a jittering tile feedback
|
|
const int32 MaxFramesSinceLastUsed = Lumen::GetFeedbackBufferTileSize() * Lumen::GetFeedbackBufferTileSize();
|
|
|
|
if (!EvictOldestAllocation(MaxFramesSinceLastUsed, DirtyCards))
|
|
{
|
|
bCanAlloc = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Can we fit this card into the temporary card capture allocator?
|
|
if (!CaptureAtlasAllocator.IsSpaceAvailable(Card, VirtualPageIndex.ResLevel, /*bSinglePage*/ true))
|
|
{
|
|
bCanAlloc = false;
|
|
}
|
|
|
|
const FLumenMeshCards& MeshCardsElement = MeshCards[Card.MeshCardsIndex];
|
|
const FLumenPrimitiveGroup& PrimitiveGroup = PrimitiveGroups[MeshCardsElement.PrimitiveGroupIndex];
|
|
|
|
if (bCanAlloc && UpdateStaticMeshes(PrimitiveGroup, LumenCardRenderer))
|
|
{
|
|
const bool bLockPages = false;
|
|
const bool bResampleLastLighting = Card.IsAllocated();
|
|
|
|
ReallocVirtualSurface(Card, VirtualPageIndex.CardIndex, VirtualPageIndex.ResLevel, bLockPages);
|
|
|
|
FLumenSurfaceMipMap& MipMap = Card.GetMipMap(VirtualPageIndex.ResLevel);
|
|
const int32 PageIndex = MipMap.GetPageTableIndex(VirtualPageIndex.LocalPageIndex);
|
|
FLumenPageTableEntry& PageTableEntry = GetPageTableEntry(PageIndex);
|
|
|
|
if (!PageTableEntry.IsMapped())
|
|
{
|
|
MapSurfaceCachePage(MipMap, PageIndex, GPUMask);
|
|
check(PageTableEntry.IsMapped());
|
|
|
|
// Allocate space in temporary allocation atlas
|
|
FLumenSurfaceCacheAllocator::FAllocation CardCaptureAllocation;
|
|
CaptureAtlasAllocator.Allocate(PageTableEntry, CardCaptureAllocation);
|
|
check(CardCaptureAllocation.PhysicalPageCoord.X >= 0);
|
|
|
|
CardPagesToRender.Add(FCardPageRenderData(
|
|
MainView,
|
|
Card,
|
|
PageTableEntry.CardUVRect,
|
|
CardCaptureAllocation.PhysicalAtlasRect,
|
|
PageTableEntry.PhysicalAtlasRect,
|
|
MeshCardsElement.PrimitiveGroupIndex,
|
|
VirtualPageIndex.CardIndex,
|
|
PageIndex,
|
|
bResampleLastLighting));
|
|
|
|
for (uint32 GPUIndex : GPUMask)
|
|
{
|
|
LastCapturedPageHeap[GPUIndex].Update(GetSurfaceCacheUpdateFrameIndex(), PageIndex);
|
|
}
|
|
LumenCardRenderer.NumCardTexelsToCapture += PageTableEntry.PhysicalAtlasRect.Area();
|
|
DirtyCards.Add(VirtualPageIndex.CardIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reconcile removal, addition, and res change of cards. This must be done after querying CardSharingInfoMap because we
|
|
// copy cards from the physical atlas before current frame capture data is merged back so CardSharingInfoMap needs to
|
|
// reflect the state right after last frame captures
|
|
FlushPendingCardSharingInfos();
|
|
|
|
// Process any surface cache page invalidation requests
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(SceneCardCaptureInvalidation);
|
|
|
|
if (CVarLumenSceneCardCaptureEnableInvalidation.GetValueOnRenderThread() == 0)
|
|
{
|
|
for (uint32 GPUIndex = 0; GPUIndex < GNumExplicitGPUsForRendering; GPUIndex++)
|
|
{
|
|
PagesToRecaptureHeap[GPUIndex].Clear();
|
|
}
|
|
}
|
|
|
|
FBinaryHeap<uint32, uint32>& PageHeap = PagesToRecaptureHeap[GPUMask.GetFirstIndex()];
|
|
while (PageHeap.Num() > 0)
|
|
{
|
|
const uint32 PageTableIndex = PageHeap.Top();
|
|
if (RecaptureCardPage(MainView, LumenCardRenderer, CaptureAtlasAllocator, GPUMask, PageTableIndex))
|
|
{
|
|
PageHeap.Pop();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Finally process card refresh to capture any material updates, or render cards that need to be initialized for the first time on
|
|
// a given GPU in multi-GPU scenarios. Uninitialized cards on a particular GPU will have a zero captured frame index set when the
|
|
// card was allocated. A zero frame index otherwise can't occur on a card, because the constructor sets SurfaceCacheUpdateFrameIndex
|
|
// to 1, and IncrementSurfaceCacheUpdateFrameIndex skips over zero if it happens to wrap around.
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(SceneCardCaptureRefresh);
|
|
|
|
int32 NumTexelsLeftToRefresh = GetCardCaptureRefreshNumTexels();
|
|
int32 NumPagesLeftToRefesh = FMath::Min<int32>((int32)GetCardCaptureRefreshNumPages(), MaxTileCapturesPerFrame - CardPagesToRender.Num());
|
|
|
|
FBinaryHeap<uint32,uint32>& PageHeap = LastCapturedPageHeap[GPUMask.GetFirstIndex()];
|
|
|
|
bool bCanCapture = true;
|
|
while (PageHeap.Num() > 0 && bCanCapture)
|
|
{
|
|
bCanCapture = false;
|
|
|
|
const uint32 PageTableIndex = PageHeap.Top();
|
|
const uint32 CapturedSurfaceCacheFrameIndex = PageHeap.GetKey(PageTableIndex);
|
|
|
|
const int32 FramesSinceLastUpdated = GetSurfaceCacheUpdateFrameIndex() - CapturedSurfaceCacheFrameIndex;
|
|
if (FramesSinceLastUpdated > 0)
|
|
{
|
|
#if WITH_MGPU
|
|
// Limit number of re-captured texels and pages per frame, except always allow captures of uninitialized
|
|
// cards where the captured frame index is zero (don't count them against the throttled limits).
|
|
// Uninitialized cards on a particular GPU will always be at the front of the heap, due to the zero index,
|
|
// so even if the limits are set to zero, we'll still process them if needed (the limit comparisons below
|
|
// are >= 0, and will pass if nothing has been decremented from the limits yet).
|
|
if ((CapturedSurfaceCacheFrameIndex != 0) || (GNumExplicitGPUsForRendering == 1))
|
|
#endif
|
|
{
|
|
FLumenPageTableEntry& PageTableEntry = GetPageTableEntry(PageTableIndex);
|
|
check(PageTableEntry.IsMapped());
|
|
|
|
if (PageTableEntry.IsMapped())
|
|
{
|
|
const FLumenCard& Card = Cards[PageTableEntry.CardIndex];
|
|
FLumenMipMapDesc MipMapDesc;
|
|
Card.GetMipMapDesc(PageTableEntry.ResLevel, MipMapDesc);
|
|
NumTexelsLeftToRefresh -= MipMapDesc.PageResolution.X * MipMapDesc.PageResolution.Y;
|
|
NumPagesLeftToRefesh -= 1;
|
|
}
|
|
}
|
|
|
|
if (NumTexelsLeftToRefresh >= 0 && NumPagesLeftToRefesh >= 0)
|
|
{
|
|
bCanCapture = RecaptureCardPage(MainView, LumenCardRenderer, CaptureAtlasAllocator, GPUMask, PageTableIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Evict pages which weren't used recently
|
|
if (!Lumen::IsSurfaceCacheFrozen())
|
|
{
|
|
uint32 MaxFramesSinceLastUsed = FMath::Max(GSurfaceCacheNumFramesToKeepUnusedPages, 0);
|
|
while (EvictOldestAllocation(MaxFramesSinceLastUsed, DirtyCards))
|
|
{
|
|
}
|
|
}
|
|
|
|
for (int32 CardIndex : DirtyCards.Array)
|
|
{
|
|
FLumenCard& Card = Cards[CardIndex];
|
|
UpdateCardMipMapHierarchy(Card);
|
|
CardIndicesToUpdateInBuffer.Add(CardIndex);
|
|
}
|
|
}
|
|
|
|
void ProcessSceneRemoveOpsReadbackData(FLumenSceneData& LumenSceneData, const FLumenSceneReadback::FRemoveOp* RemoveOpsData)
|
|
{
|
|
if (RemoveOpsData)
|
|
{
|
|
// #lumen_todo: Temporary workaround until we optimized FLumenSurfaceCacheAllocator::Free to use fast batched removes
|
|
int32 NumMeshCardsRemoves = 0;
|
|
const int32 MaxMeshCardsRemoves = LumenScene::GetMaxMeshCardsRemovesPerFrame();
|
|
|
|
// First element encodes array size
|
|
const int32 HeaderSize = 1;
|
|
const int32 NumReadbackElements = FMath::Min<int32>(RemoveOpsData[0].PrimitiveGroupIndex, LumenSceneData.SceneReadback.GetMaxRemoveOps() - HeaderSize);
|
|
|
|
for (int32 ElementIndex = 0; ElementIndex < NumReadbackElements; ++ElementIndex)
|
|
{
|
|
if (NumMeshCardsRemoves >= MaxMeshCardsRemoves)
|
|
{
|
|
break;
|
|
}
|
|
|
|
const uint32 PrimitiveGroupIndex = RemoveOpsData[ElementIndex + HeaderSize].PrimitiveGroupIndex;
|
|
|
|
if (LumenSceneData.PrimitiveGroups.IsAllocated(PrimitiveGroupIndex))
|
|
{
|
|
const FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData.PrimitiveGroups[PrimitiveGroupIndex];
|
|
if (PrimitiveGroup.bValidMeshCards && PrimitiveGroup.MeshCardsIndex >= 0)
|
|
{
|
|
LumenSceneData.RemoveMeshCards(PrimitiveGroupIndex);
|
|
++NumMeshCardsRemoves;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProcessSceneAddOpsReadbackData(FLumenSceneData& LumenSceneData, const FLumenSceneReadback::FAddOp* AddOpsData)
|
|
{
|
|
if (AddOpsData)
|
|
{
|
|
TArray<FMeshCardsAdd, SceneRenderingAllocator> MeshCardsAdds;
|
|
|
|
// First element encodes array size
|
|
const int32 HeaderSize = 1;
|
|
const int32 NumReadbackElements = FMath::Min<int32>(AddOpsData[0].PrimitiveGroupIndex, LumenSceneData.SceneReadback.GetMaxAddOps() - HeaderSize);
|
|
|
|
for (int32 ElementIndex = 0; ElementIndex < NumReadbackElements; ++ElementIndex)
|
|
{
|
|
const FLumenSceneReadback::FAddOp AddOp = AddOpsData[ElementIndex + HeaderSize];
|
|
|
|
if (LumenSceneData.PrimitiveGroups.IsAllocated(AddOp.PrimitiveGroupIndex))
|
|
{
|
|
const FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData.PrimitiveGroups[AddOp.PrimitiveGroupIndex];
|
|
if (PrimitiveGroup.bValidMeshCards && PrimitiveGroup.MeshCardsIndex == -1)
|
|
{
|
|
FMeshCardsAdd& MeshCardsAdd = MeshCardsAdds.AddDefaulted_GetRef();
|
|
MeshCardsAdd.PrimitiveGroupIndex = AddOp.PrimitiveGroupIndex;
|
|
MeshCardsAdd.DistanceSquared = AddOp.DistanceSq;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MeshCardsAdds.Num() > 0)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(SortAdds);
|
|
|
|
struct FSortBySmallerDistance
|
|
{
|
|
FORCEINLINE bool operator()(const FMeshCardsAdd& A, const FMeshCardsAdd& B) const
|
|
{
|
|
return A.DistanceSquared < B.DistanceSquared;
|
|
}
|
|
};
|
|
|
|
MeshCardsAdds.Sort(FSortBySmallerDistance());
|
|
}
|
|
|
|
const int32 MeshCardsToAddPerFrame = LumenScene::GetMaxMeshCardsToAddPerFrame();
|
|
|
|
for (int32 MeshCardsIndex = 0; MeshCardsIndex < FMath::Min(MeshCardsAdds.Num(), MeshCardsToAddPerFrame); ++MeshCardsIndex)
|
|
{
|
|
const FMeshCardsAdd& MeshCardsAdd = MeshCardsAdds[MeshCardsIndex];
|
|
LumenSceneData.AddMeshCards(MeshCardsAdd.PrimitiveGroupIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UpdateSurfaceCachePrimitives(
|
|
FLumenSceneData& LumenSceneData,
|
|
const TArray<FVector, TInlineAllocator<2>>& LumenSceneCameraOrigins,
|
|
bool bOrthographicCamera,
|
|
float LumenSceneDetail,
|
|
float MaxCardUpdateDistanceFromCamera,
|
|
FLumenCardRenderer& LumenCardRenderer,
|
|
bool bAddTranslucentToCache)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(UpdateSurfaceCachePrimitives);
|
|
|
|
{
|
|
const bool bExecuteInParallel = FApp::ShouldUseThreadingForPerformance() && GLumenSceneParallelUpdate != 0;
|
|
const int32 NumPrimitiveTasks = ParallelForImpl::GetNumberOfThreadTasks(LumenSceneData.PrimitiveCullingInfos.GetMaxIndex(), GLumenScenePrimitivesPerTask, EParallelForFlags::None);
|
|
const int32 NumPrimitivesPerTask = FMath::DivideAndRoundUp(LumenSceneData.PrimitiveCullingInfos.GetMaxIndex(), NumPrimitiveTasks);
|
|
|
|
TArray<FLumenSurfaceCacheCullPrimitivesTask, SceneRenderingAllocator> PrimitiveTasks;
|
|
PrimitiveTasks.Reserve(NumPrimitiveTasks);
|
|
|
|
for (int32 TaskIndex = 0; TaskIndex < NumPrimitiveTasks; ++TaskIndex)
|
|
{
|
|
PrimitiveTasks.Emplace(
|
|
LumenSceneData.PrimitiveCullingInfos,
|
|
LumenSceneCameraOrigins,
|
|
bOrthographicCamera,
|
|
LumenSceneDetail,
|
|
MaxCardUpdateDistanceFromCamera,
|
|
TaskIndex * NumPrimitivesPerTask,
|
|
NumPrimitivesPerTask,
|
|
bAddTranslucentToCache);
|
|
}
|
|
|
|
ParallelFor(
|
|
NumPrimitiveTasks,
|
|
[&PrimitiveTasks](int32 Index)
|
|
{
|
|
PrimitiveTasks[Index].AnyThreadTask();
|
|
},
|
|
!bExecuteInParallel);
|
|
|
|
TArray<FInstanceRange, SceneRenderingAllocator> InstanceRanges;
|
|
TArray<int32, SceneRenderingAllocator> RebasedRangeOffsets;
|
|
int32 NumInstances = 0;
|
|
|
|
for (int32 TaskIndex = 0; TaskIndex < NumPrimitiveTasks; ++TaskIndex)
|
|
{
|
|
const FLumenSurfaceCacheCullPrimitivesTask& Task = PrimitiveTasks[TaskIndex];
|
|
|
|
InstanceRanges.Reserve(InstanceRanges.Num() + Task.InstanceCullingRanges.Num());
|
|
RebasedRangeOffsets.Reserve(RebasedRangeOffsets.Num() + Task.InstanceCullingRanges.Num());
|
|
|
|
for (const FInstanceRange& InstanceRange : Task.InstanceCullingRanges)
|
|
{
|
|
InstanceRanges.Add(InstanceRange);
|
|
RebasedRangeOffsets.Add(NumInstances);
|
|
NumInstances += InstanceRange.Num;
|
|
}
|
|
}
|
|
RebasedRangeOffsets.Add(NumInstances);
|
|
|
|
const int32 NumInstanceTasks = ParallelForImpl::GetNumberOfThreadTasks(NumInstances, GLumenScenePrimitivesPerTask, EParallelForFlags::None);
|
|
const int32 NumInstancesPerTask = FMath::DivideAndRoundUp(NumInstances, NumInstanceTasks);
|
|
|
|
TArray<FLumenSurfaceCacheCullInstancesTask, SceneRenderingAllocator> InstanceTasks;
|
|
InstanceTasks.Reserve(NumInstanceTasks);
|
|
|
|
for (int32 TaskIndex = 0; TaskIndex < NumInstanceTasks; ++TaskIndex)
|
|
{
|
|
InstanceTasks.Emplace(
|
|
LumenSceneData.InstanceCullingInfos,
|
|
InstanceRanges,
|
|
RebasedRangeOffsets,
|
|
LumenSceneCameraOrigins,
|
|
bOrthographicCamera,
|
|
LumenSceneDetail,
|
|
MaxCardUpdateDistanceFromCamera,
|
|
TaskIndex * NumInstancesPerTask,
|
|
NumInstancesPerTask,
|
|
bAddTranslucentToCache);
|
|
}
|
|
|
|
ParallelFor(
|
|
NumInstanceTasks,
|
|
[&InstanceTasks](int32 Index)
|
|
{
|
|
InstanceTasks[Index].AnyThreadTask();
|
|
},
|
|
!bExecuteInParallel);
|
|
|
|
TArray<FMeshCardsAdd, SceneRenderingAllocator> MeshCardsAdds;
|
|
|
|
for (int32 TaskIndex = 0; TaskIndex < NumPrimitiveTasks; ++TaskIndex)
|
|
{
|
|
const FLumenSurfaceCacheCullPrimitivesTask& Task = PrimitiveTasks[TaskIndex];
|
|
LumenSceneData.NumMeshCardsToAdd += Task.MeshCardsAdds.Num();
|
|
|
|
// Append requests to the global array
|
|
{
|
|
MeshCardsAdds.Reserve(MeshCardsAdds.Num() + Task.MeshCardsAdds.Num());
|
|
|
|
for (int32 RequestIndex = 0; RequestIndex < Task.MeshCardsAdds.Num(); ++RequestIndex)
|
|
{
|
|
MeshCardsAdds.Add(Task.MeshCardsAdds[RequestIndex]);
|
|
}
|
|
}
|
|
|
|
// #lumen_todo: Temporary workaround until we optimized FLumenSurfaceCacheAllocator::Free to use fast batched removes
|
|
int32 NumMeshCardsRemoves = 0;
|
|
const int32 MaxMeshCardsRemoves = LumenScene::GetMaxMeshCardsRemovesPerFrame();
|
|
|
|
for (const FMeshCardsRemove& MeshCardsRemove : Task.MeshCardsRemoves)
|
|
{
|
|
if (NumMeshCardsRemoves >= MaxMeshCardsRemoves)
|
|
{
|
|
break;
|
|
}
|
|
++NumMeshCardsRemoves;
|
|
|
|
LumenSceneData.RemoveMeshCards(MeshCardsRemove.PrimitiveGroupIndex);
|
|
}
|
|
}
|
|
|
|
for (int32 TaskIndex = 0; TaskIndex < NumInstanceTasks; ++TaskIndex)
|
|
{
|
|
const FLumenSurfaceCacheCullInstancesTask& Task = InstanceTasks[TaskIndex];
|
|
LumenSceneData.NumMeshCardsToAdd += Task.MeshCardsAdds.Num();
|
|
|
|
// Append requests to the global array
|
|
{
|
|
MeshCardsAdds.Reserve(MeshCardsAdds.Num() + Task.MeshCardsAdds.Num());
|
|
|
|
for (int32 RequestIndex = 0; RequestIndex < Task.MeshCardsAdds.Num(); ++RequestIndex)
|
|
{
|
|
MeshCardsAdds.Add(Task.MeshCardsAdds[RequestIndex]);
|
|
}
|
|
}
|
|
|
|
// #lumen_todo: Temporary workaround until we optimized FLumenSurfaceCacheAllocator::Free to use fast batched removes
|
|
int32 NumMeshCardsRemoves = 0;
|
|
const int32 MaxMeshCardsRemoves = LumenScene::GetMaxMeshCardsRemovesPerFrame();
|
|
|
|
for (const FMeshCardsRemove& MeshCardsRemove : Task.MeshCardsRemoves)
|
|
{
|
|
if (NumMeshCardsRemoves >= MaxMeshCardsRemoves)
|
|
{
|
|
break;
|
|
}
|
|
++NumMeshCardsRemoves;
|
|
|
|
LumenSceneData.RemoveMeshCards(MeshCardsRemove.PrimitiveGroupIndex);
|
|
}
|
|
}
|
|
|
|
if (MeshCardsAdds.Num() > 0)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(SortAdds);
|
|
|
|
struct FSortBySmallerDistance
|
|
{
|
|
FORCEINLINE bool operator()(const FMeshCardsAdd& A, const FMeshCardsAdd& B) const
|
|
{
|
|
return A.DistanceSquared < B.DistanceSquared;
|
|
}
|
|
};
|
|
|
|
MeshCardsAdds.Sort(FSortBySmallerDistance());
|
|
}
|
|
|
|
const int32 MeshCardsToAddPerFrame = LumenScene::GetMaxMeshCardsToAddPerFrame();
|
|
|
|
for (int32 MeshCardsIndex = 0; MeshCardsIndex < FMath::Min(MeshCardsAdds.Num(), MeshCardsToAddPerFrame); ++MeshCardsIndex)
|
|
{
|
|
const FMeshCardsAdd& MeshCardsAdd = MeshCardsAdds[MeshCardsIndex];
|
|
LumenSceneData.AddMeshCards(MeshCardsAdd.PrimitiveGroupIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UpdateSurfaceCacheMeshCards(
|
|
FLumenSceneData& LumenSceneData,
|
|
FLumenSceneData::FFeedbackData LumenFeedbackData,
|
|
const TArray<FVector, TInlineAllocator<2>>& LumenSceneCameraOrigins,
|
|
bool bOrthographicCamera,
|
|
float LumenSceneDetail,
|
|
float MaxCardUpdateDistanceFromCamera,
|
|
TArray<FSurfaceCacheRequest, SceneRenderingAllocator>& SurfaceCacheRequests,
|
|
const FViewFamilyInfo& ViewFamily)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(UpdateMeshCards);
|
|
|
|
const int32 NumMeshCardsPerTask = FMath::Max(GLumenSceneMeshCardsPerTask, 1);
|
|
const int32 NumTasks = FMath::DivideAndRoundUp(LumenSceneData.MeshCards.Num(), NumMeshCardsPerTask);
|
|
if (NumTasks == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 RequestHistogram[Lumen::NumDistanceBuckets] { 0 };
|
|
|
|
TArray<FLumenSurfaceCacheUpdateMeshCardsTask, SceneRenderingAllocator> Tasks;
|
|
Tasks.Reserve(NumTasks);
|
|
|
|
for (int32 TaskIndex = 0; TaskIndex < NumTasks; ++TaskIndex)
|
|
{
|
|
Tasks.Emplace(
|
|
LumenSceneData.MeshCards,
|
|
LumenSceneData.Cards,
|
|
LumenSceneCameraOrigins,
|
|
bOrthographicCamera,
|
|
LumenSceneData.SurfaceCacheResolution,
|
|
LumenSceneDetail,
|
|
MaxCardUpdateDistanceFromCamera,
|
|
TaskIndex * NumMeshCardsPerTask,
|
|
NumMeshCardsPerTask);
|
|
}
|
|
|
|
const bool bExecuteInParallel = FApp::ShouldUseThreadingForPerformance() && GLumenSceneParallelUpdate != 0;
|
|
|
|
ParallelFor(Tasks.Num(),
|
|
[&Tasks](int32 Index)
|
|
{
|
|
Tasks[Index].AnyThreadTask();
|
|
},
|
|
!bExecuteInParallel);
|
|
|
|
uint32 TotalSurfaceCacheRequests = 0;
|
|
for (int32 TaskIndex = 0; TaskIndex < Tasks.Num(); ++TaskIndex)
|
|
{
|
|
const FLumenSurfaceCacheUpdateMeshCardsTask& Task = Tasks[TaskIndex];
|
|
TotalSurfaceCacheRequests += Task.SurfaceCacheRequests.Num();
|
|
}
|
|
|
|
for (int32 TaskIndex = 0; TaskIndex < Tasks.Num(); ++TaskIndex)
|
|
{
|
|
const FLumenSurfaceCacheUpdateMeshCardsTask& Task = Tasks[TaskIndex];
|
|
LumenSceneData.NumLockedCardsToUpdate += Task.SurfaceCacheRequests.Num();
|
|
|
|
for (int32 i = 0; i < Lumen::NumDistanceBuckets; ++i)
|
|
{
|
|
RequestHistogram[i] += Task.Histogram[i];
|
|
}
|
|
|
|
for (int32 CardIndex : Task.CardsToHide)
|
|
{
|
|
FLumenCard& Card = LumenSceneData.Cards[CardIndex];
|
|
|
|
if (Card.bVisible)
|
|
{
|
|
LumenSceneData.RemoveCardFromAtlas(CardIndex);
|
|
Card.bVisible = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
LumenSceneData.UpdateSurfaceCacheFeedback(LumenFeedbackData, LumenSceneCameraOrigins, Tasks[0].SurfaceCacheRequests, ViewFamily, RequestHistogram);
|
|
|
|
int32 SurfaceCacheRequestsCount = 0;
|
|
int32 LastBucketRequestCount = 0;
|
|
int32 LastBucketIndex = 0;
|
|
for (; LastBucketIndex < Lumen::NumDistanceBuckets; ++LastBucketIndex)
|
|
{
|
|
SurfaceCacheRequestsCount += RequestHistogram[LastBucketIndex];
|
|
|
|
if (SurfaceCacheRequestsCount >= GetMaxLumenSceneCardCapturesPerFrame())
|
|
{
|
|
LastBucketRequestCount = GLumenSceneCardCapturesPerFrame - (SurfaceCacheRequestsCount - RequestHistogram[LastBucketIndex]);
|
|
SurfaceCacheRequestsCount = GLumenSceneCardCapturesPerFrame;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (SurfaceCacheRequestsCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SurfaceCacheRequests.Reserve(SurfaceCacheRequestsCount);
|
|
for (int32 TaskIndex = 0; TaskIndex < Tasks.Num(); ++TaskIndex)
|
|
{
|
|
const FLumenSurfaceCacheUpdateMeshCardsTask& Task = Tasks[TaskIndex];
|
|
|
|
for (int32 RequestIndex = 0; RequestIndex < Task.SurfaceCacheRequests.Num(); ++RequestIndex)
|
|
{
|
|
const FSurfaceCacheRequest& Request = Task.SurfaceCacheRequests[RequestIndex];
|
|
const int32 BucketIndex = Lumen::GetMeshCardDistanceBin(Request.Distance);
|
|
if (BucketIndex > LastBucketIndex)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (BucketIndex == LastBucketIndex)
|
|
{
|
|
if (LastBucketRequestCount == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
--LastBucketRequestCount;
|
|
}
|
|
|
|
SurfaceCacheRequests.Add(Request);
|
|
if (--SurfaceCacheRequestsCount == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (SurfaceCacheRequestsCount == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
extern void UpdateLumenScenePrimitives(FRHIGPUMask GPUMask, FScene* Scene);
|
|
|
|
void AllocateResampledCardCaptureAtlas(FRDGBuilder& GraphBuilder, FIntPoint CardCaptureAtlasSize, FResampledCardCaptureAtlas& CardCaptureAtlas)
|
|
{
|
|
CardCaptureAtlas.Size = CardCaptureAtlasSize;
|
|
|
|
CardCaptureAtlas.DirectLighting = GraphBuilder.CreateTexture(
|
|
FRDGTextureDesc::Create2D(
|
|
CardCaptureAtlasSize,
|
|
Lumen::GetDirectLightingAtlasFormat(),
|
|
FClearValueBinding::Green,
|
|
TexCreate_ShaderResource | TexCreate_NoFastClear | TexCreate_UAV),
|
|
TEXT("Lumen.ResampledCardCaptureDirectLighting"));
|
|
|
|
CardCaptureAtlas.IndirectLighting = GraphBuilder.CreateTexture(
|
|
FRDGTextureDesc::Create2D(
|
|
CardCaptureAtlasSize,
|
|
Lumen::GetIndirectLightingAtlasFormat(),
|
|
FClearValueBinding::Green,
|
|
TexCreate_ShaderResource | TexCreate_NoFastClear | TexCreate_UAV),
|
|
TEXT("Lumen.ResampledCardCaptureIndirectLighting"));
|
|
|
|
CardCaptureAtlas.NumFramesAccumulated = GraphBuilder.CreateTexture(
|
|
FRDGTextureDesc::Create2D(
|
|
CardCaptureAtlasSize,
|
|
Lumen::GetNumFramesAccumulatedAtlasFormat(),
|
|
FClearValueBinding::Black,
|
|
TexCreate_ShaderResource | TexCreate_NoFastClear | TexCreate_UAV),
|
|
TEXT("Lumen.ResampledCardCaptureNumFramesAccumulated"));
|
|
|
|
const FIntPoint CardCaptureAtlasSizeInTiles = CardCaptureAtlasSize / Lumen::CardTileSize;
|
|
CardCaptureAtlas.TileShadowDownsampleFactor = GraphBuilder.CreateBuffer(
|
|
FRDGBufferDesc::CreateBufferDesc(
|
|
sizeof(uint32),
|
|
CardCaptureAtlasSizeInTiles.X * CardCaptureAtlasSizeInTiles.Y * Lumen::CardTileShadowDownsampleFactorDwords),
|
|
TEXT("Lumen.ResampledCardCaptureTileShadowDownsampleFactorAtlas"));
|
|
}
|
|
|
|
class FResampleLightingHistoryToCardCaptureAtlasCS : public FGlobalShader
|
|
{
|
|
DECLARE_GLOBAL_SHADER(FResampleLightingHistoryToCardCaptureAtlasCS);
|
|
SHADER_USE_PARAMETER_STRUCT(FResampleLightingHistoryToCardCaptureAtlasCS, FGlobalShader);
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
|
|
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FLumenCardScene, LumenCardScene)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, DirectLightingAtlas)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, IndirectLightingAtlas)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, RadiosityNumFramesAccumulatedAtlas)
|
|
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint4>, TileShadowDownsampleFactorAtlasForResampling)
|
|
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, RWDirectLightingCardCaptureAtlas)
|
|
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, RWRadiosityCardCaptureAtlas)
|
|
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<UNORM float>, RWRadiosityNumFramesAccumulatedCardCaptureAtlas)
|
|
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint4>, RWTileShadowDownsampleFactorAtlas)
|
|
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint4>, NewCardPageResampleData)
|
|
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint>, NewCardTileResampleData)
|
|
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint4>, RectCoordBuffer)
|
|
SHADER_PARAMETER(uint32, CardCaptureAtlasWidthInTiles)
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
using FPermutationDomain = TShaderPermutationDomain<>;
|
|
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
|
|
{
|
|
return DoesPlatformSupportLumenGI(Parameters.Platform);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FResampleLightingHistoryToCardCaptureAtlasCS, "/Engine/Private/Lumen/LumenSceneLighting.usf", "ResampleLightingHistoryToCardCaptureAtlasCS", SF_Compute);
|
|
|
|
class FLumenCardResamplePS : public FGlobalShader
|
|
{
|
|
DECLARE_GLOBAL_SHADER(FLumenCardResamplePS);
|
|
SHADER_USE_PARAMETER_STRUCT(FLumenCardResamplePS, FGlobalShader);
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
|
|
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FLumenCardScene, LumenCardScene)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceAlbedoAtlas)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceOpacityAtlas)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceNormalAtlas)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceEmissiveAtlas)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SourceDepthAtlas)
|
|
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint4>, SourceCardData)
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
using FPermutationDomain = TShaderPermutationDomain<>;
|
|
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
|
|
{
|
|
return DoesPlatformSupportLumenGI(Parameters.Platform);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FLumenCardResamplePS, "/Engine/Private/Lumen/SurfaceCache/LumenSurfaceCache.usf", "LumenCardResamplePS", SF_Pixel);
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FLumenCardResampleParameters, )
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FPixelShaderUtils::FRasterizeToRectsVS::FParameters, VS)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FLumenCardResamplePS::FParameters, PS)
|
|
RENDER_TARGET_BINDING_SLOTS()
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
void ResampleLumenCards(
|
|
FRDGBuilder& GraphBuilder,
|
|
const FViewInfo& View,
|
|
const FScene* Scene,
|
|
const FLumenSceneData& LumenSceneData,
|
|
const FLumenSceneFrameTemporaries& FrameTemporaries,
|
|
TConstArrayView<FCardPageRenderData> CardPagesToRender,
|
|
bool bHasAnyCardCopy,
|
|
FCardCaptureAtlas& OutCardCaptureAtlas,
|
|
FResampledCardCaptureAtlas& OutCardCaptureLightingAtlas,
|
|
FRDGBufferSRVRef& OutCardCaptureRectBufferSRV,
|
|
int32& OutNumPagesNeedDilation)
|
|
{
|
|
if (!FrameTemporaries.PageTableBufferSRV || !FrameTemporaries.CardBufferSRV)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const bool bNeedsResample = bHasAnyCardCopy || GLumenSceneSurfaceCacheResampleLighting != 0;
|
|
const uint32 NumRects = CardPagesToRender.Num();
|
|
|
|
FRDGUploadData<FUintVector4> CardCaptureRectArray(GraphBuilder, NumRects);
|
|
FRDGUploadData<FUintVector4> CardPageResampleDataArray(GraphBuilder, bNeedsResample ? NumRects * 2 : 1);
|
|
uint32 NumCaptureTiles = 0;
|
|
OutNumPagesNeedDilation = 0;
|
|
|
|
for (uint32 Index = 0; Index < NumRects; ++Index)
|
|
{
|
|
const FCardPageRenderData& CardPageRenderData = CardPagesToRender[Index];
|
|
|
|
const FIntPoint RectSizeInTiles = CardPageRenderData.CardCaptureAtlasRect.Size() / Lumen::CardTileSize;
|
|
NumCaptureTiles += RectSizeInTiles.X * RectSizeInTiles.Y;
|
|
|
|
FUintVector4& Rect = CardCaptureRectArray[Index];
|
|
Rect.X = CardPageRenderData.CardCaptureAtlasRect.Min.X;
|
|
Rect.Y = CardPageRenderData.CardCaptureAtlasRect.Min.Y;
|
|
Rect.Z = CardPageRenderData.CardCaptureAtlasRect.Max.X;
|
|
Rect.W = CardPageRenderData.CardCaptureAtlasRect.Max.Y;
|
|
|
|
if (CardPageRenderData.NeedsRender() && CardPageRenderData.DilationMode == ELumenCardDilationMode::DilateOneTexel)
|
|
{
|
|
++OutNumPagesNeedDilation;
|
|
}
|
|
|
|
if (bNeedsResample)
|
|
{
|
|
FUintVector4& CardPageResampleData0 = CardPageResampleDataArray[Index * 2 + 0];
|
|
FUintVector4& CardPageResampleData1 = CardPageResampleDataArray[Index * 2 + 1];
|
|
CardPageResampleData0.X = CardPageRenderData.bResampleLastLighting ? CardPageRenderData.CardIndex : -1;
|
|
CardPageResampleData0.Y = CardPageRenderData.NeedsRender() ? 0 : CardPageRenderData.CopyCardIndex + 1;
|
|
CardPageResampleData0.Y |= CardPageRenderData.bAxisXFlipped ? 0x80000000 : 0;
|
|
CardPageResampleData1 = FUintVector4(
|
|
*(const uint32*)&CardPageRenderData.CardUVRect.X,
|
|
*(const uint32*)&CardPageRenderData.CardUVRect.Y,
|
|
*(const uint32*)&CardPageRenderData.CardUVRect.Z,
|
|
*(const uint32*)&CardPageRenderData.CardUVRect.W);
|
|
}
|
|
}
|
|
|
|
FRDGBufferRef CardCaptureRectBuffer = CreateUploadBuffer(GraphBuilder, TEXT("Lumen.CardCaptureRects"),
|
|
sizeof(FUintVector4), FMath::RoundUpToPowerOfTwo(NumRects),
|
|
CardCaptureRectArray);
|
|
OutCardCaptureRectBufferSRV = GraphBuilder.CreateSRV(CardCaptureRectBuffer, PF_R32G32B32A32_UINT);
|
|
|
|
if (!bNeedsResample)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FRDGBufferRef CardPageResampleDataBuffer = CreateUploadBuffer(GraphBuilder, TEXT("Lumen.CardPageResampleDataBuffer"),
|
|
sizeof(FUintVector4), FMath::RoundUpToPowerOfTwo(NumRects * 2),
|
|
CardPageResampleDataArray);
|
|
FRDGBufferSRVRef CardPageResampleDataBufferSRV = GraphBuilder.CreateSRV(CardPageResampleDataBuffer, PF_R32G32B32A32_UINT);
|
|
|
|
// Resample card material attributes if requested. Otherwise, clear
|
|
if (bHasAnyCardCopy)
|
|
{
|
|
LumenScene::AllocateCardCaptureAtlas(GraphBuilder, LumenSceneData.GetCardCaptureAtlasSize(), OutCardCaptureAtlas, Scene->GetShaderPlatform());
|
|
|
|
auto* PassParameters = GraphBuilder.AllocParameters<FLumenCardResampleParameters>();
|
|
PassParameters->PS.View = View.ViewUniformBuffer;
|
|
PassParameters->PS.LumenCardScene = FrameTemporaries.LumenCardSceneUniformBuffer;
|
|
PassParameters->PS.SourceAlbedoAtlas = FrameTemporaries.AlbedoAtlas;
|
|
PassParameters->PS.SourceOpacityAtlas = FrameTemporaries.OpacityAtlas;
|
|
PassParameters->PS.SourceNormalAtlas = FrameTemporaries.NormalAtlas;
|
|
PassParameters->PS.SourceEmissiveAtlas = FrameTemporaries.EmissiveAtlas;
|
|
PassParameters->PS.SourceDepthAtlas = FrameTemporaries.DepthAtlas;
|
|
PassParameters->PS.SourceCardData = CardPageResampleDataBufferSRV;
|
|
PassParameters->RenderTargets[0] = FRenderTargetBinding(OutCardCaptureAtlas.Albedo, ERenderTargetLoadAction::ELoad);
|
|
PassParameters->RenderTargets[1] = FRenderTargetBinding(OutCardCaptureAtlas.Normal, ERenderTargetLoadAction::ELoad);
|
|
PassParameters->RenderTargets[2] = FRenderTargetBinding(OutCardCaptureAtlas.Emissive, ERenderTargetLoadAction::ELoad);
|
|
PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(OutCardCaptureAtlas.DepthStencil, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthWrite_StencilWrite);
|
|
|
|
auto PixelShader = View.ShaderMap->GetShader<FLumenCardResamplePS>();
|
|
|
|
FPixelShaderUtils::AddRasterizeToRectsPass<FLumenCardResamplePS>(
|
|
GraphBuilder,
|
|
View.ShaderMap,
|
|
RDG_EVENT_NAME("ResampleLumenCards"),
|
|
PixelShader,
|
|
PassParameters,
|
|
OutCardCaptureAtlas.Size,
|
|
OutCardCaptureRectBufferSRV,
|
|
NumRects,
|
|
TStaticBlendState<>::GetRHI(),
|
|
TStaticRasterizerState<>::GetRHI(),
|
|
TStaticDepthStencilState<true, CF_Always,
|
|
true, CF_Always, SO_Replace, SO_Replace, SO_Replace,
|
|
false, CF_Always, SO_Replace, SO_Replace, SO_Replace,
|
|
0xff, 0xff>::GetRHI());
|
|
}
|
|
|
|
// Try to resample direct lighting and indirect lighting (radiosity) from existing surface cache to new captured cards
|
|
if (GLumenSceneSurfaceCacheResampleLighting != 0)
|
|
{
|
|
AllocateResampledCardCaptureAtlas(GraphBuilder, LumenSceneData.GetCardCaptureAtlasSize(), OutCardCaptureLightingAtlas);
|
|
|
|
FRDGUploadData<uint32> CardTileResampleDataArray(GraphBuilder, NumCaptureTiles);
|
|
|
|
for (int32 RectIndex = 0, TileIndex = 0; RectIndex < CardPagesToRender.Num(); RectIndex++)
|
|
{
|
|
const FCardPageRenderData& CardPageRenderData = CardPagesToRender[RectIndex];
|
|
const FIntPoint RectSizeInTiles = CardPageRenderData.CardCaptureAtlasRect.Size() / Lumen::CardTileSize;
|
|
|
|
for (uint32 TileY = 0; TileY < (uint32)RectSizeInTiles.Y; ++TileY)
|
|
{
|
|
for (uint32 TileX = 0; TileX < (uint32)RectSizeInTiles.X; ++TileX)
|
|
{
|
|
const uint32 PackedTileData = (RectIndex << 8u) | (TileY << 4u) | TileX;
|
|
CardTileResampleDataArray[TileIndex++] = PackedTileData;
|
|
}
|
|
}
|
|
}
|
|
|
|
FRDGBufferRef CardTileResampleDataBuffer = CreateUploadBuffer(GraphBuilder, TEXT("Lumen.CardTileResampleDataBuffer"),
|
|
sizeof(uint32), FMath::RoundUpToPowerOfTwo(NumCaptureTiles),
|
|
CardTileResampleDataArray);
|
|
FRDGBufferSRVRef CardTileResampleDataBufferSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(CardTileResampleDataBuffer, PF_R32_UINT));
|
|
|
|
auto* PassParameters = GraphBuilder.AllocParameters<FResampleLightingHistoryToCardCaptureAtlasCS::FParameters>();
|
|
PassParameters->View = View.ViewUniformBuffer;
|
|
PassParameters->LumenCardScene = FrameTemporaries.LumenCardSceneUniformBuffer;
|
|
PassParameters->DirectLightingAtlas = FrameTemporaries.DirectLightingAtlas;
|
|
PassParameters->IndirectLightingAtlas = FrameTemporaries.IndirectLightingAtlas;
|
|
PassParameters->RadiosityNumFramesAccumulatedAtlas = FrameTemporaries.RadiosityNumFramesAccumulatedAtlas;
|
|
PassParameters->TileShadowDownsampleFactorAtlasForResampling = GraphBuilder.CreateSRV(FrameTemporaries.TileShadowDownsampleFactorAtlas, PF_R32G32B32A32_UINT);
|
|
PassParameters->RWDirectLightingCardCaptureAtlas = GraphBuilder.CreateUAV(OutCardCaptureLightingAtlas.DirectLighting);
|
|
PassParameters->RWRadiosityCardCaptureAtlas = GraphBuilder.CreateUAV(OutCardCaptureLightingAtlas.IndirectLighting);
|
|
PassParameters->RWRadiosityNumFramesAccumulatedCardCaptureAtlas = GraphBuilder.CreateUAV(OutCardCaptureLightingAtlas.NumFramesAccumulated);
|
|
PassParameters->RWTileShadowDownsampleFactorAtlas = GraphBuilder.CreateUAV(OutCardCaptureLightingAtlas.TileShadowDownsampleFactor, PF_R32G32B32A32_UINT);
|
|
PassParameters->NewCardPageResampleData = CardPageResampleDataBufferSRV;
|
|
PassParameters->NewCardTileResampleData = CardTileResampleDataBufferSRV;
|
|
PassParameters->RectCoordBuffer = OutCardCaptureRectBufferSRV;
|
|
PassParameters->CardCaptureAtlasWidthInTiles = LumenSceneData.GetCardCaptureAtlasSize().X / Lumen::CardTileSize;
|
|
|
|
auto ComputeShader = View.ShaderMap->GetShader<FResampleLightingHistoryToCardCaptureAtlasCS>();
|
|
|
|
FComputeShaderUtils::AddPass(
|
|
GraphBuilder,
|
|
RDG_EVENT_NAME("ResampleLightingHistoryToCardCaptureAtlasCS"),
|
|
ComputeShader,
|
|
PassParameters,
|
|
FIntVector(NumCaptureTiles, 1, 1));
|
|
}
|
|
}
|
|
|
|
void FLumenSceneData::FillFrameTemporaries(FRDGBuilder& GraphBuilder, FLumenSceneFrameTemporaries& FrameTemporaries)
|
|
{
|
|
const auto FillBuffer = [&](FRDGBufferSRV*& OutSRV, const TRefCountPtr<FRDGPooledBuffer>& InBuffer)
|
|
{
|
|
if (!OutSRV && InBuffer)
|
|
{
|
|
OutSRV = GraphBuilder.CreateSRV(GraphBuilder.RegisterExternalBuffer(InBuffer));
|
|
}
|
|
};
|
|
|
|
FillBuffer(FrameTemporaries.CardBufferSRV, CardBuffer);
|
|
FillBuffer(FrameTemporaries.MeshCardsBufferSRV, MeshCardsBuffer);
|
|
FillBuffer(FrameTemporaries.HeightfieldBufferSRV, HeightfieldBuffer);
|
|
FillBuffer(FrameTemporaries.PrimitiveGroupBufferSRV, PrimitiveGroupBuffer);
|
|
FillBuffer(FrameTemporaries.SceneInstanceIndexToMeshCardsIndexBufferSRV, SceneInstanceIndexToMeshCardsIndexBuffer);
|
|
FillBuffer(FrameTemporaries.PageTableBufferSRV, PageTableBuffer);
|
|
FillBuffer(FrameTemporaries.CardPageBufferSRV, CardPageBuffer);
|
|
|
|
const auto FillTexture = [&](FRDGTexture*& OutTexture, const TRefCountPtr<IPooledRenderTarget>& InTexture)
|
|
{
|
|
if (!OutTexture && InTexture)
|
|
{
|
|
OutTexture = GraphBuilder.RegisterExternalTexture(InTexture);
|
|
}
|
|
};
|
|
|
|
FillTexture(FrameTemporaries.AlbedoAtlas, AlbedoAtlas);
|
|
FillTexture(FrameTemporaries.OpacityAtlas, OpacityAtlas);
|
|
FillTexture(FrameTemporaries.NormalAtlas, NormalAtlas);
|
|
FillTexture(FrameTemporaries.EmissiveAtlas, EmissiveAtlas);
|
|
FillTexture(FrameTemporaries.DepthAtlas, DepthAtlas);
|
|
FillTexture(FrameTemporaries.DirectLightingAtlas, DirectLightingAtlas);
|
|
FillTexture(FrameTemporaries.IndirectLightingAtlas, IndirectLightingAtlas);
|
|
FillTexture(FrameTemporaries.RadiosityNumFramesAccumulatedAtlas, RadiosityNumFramesAccumulatedAtlas);
|
|
FillTexture(FrameTemporaries.FinalLightingAtlas, FinalLightingAtlas);
|
|
if (!FrameTemporaries.TileShadowDownsampleFactorAtlas && TileShadowDownsampleFactorAtlas)
|
|
{
|
|
FrameTemporaries.TileShadowDownsampleFactorAtlas = GraphBuilder.RegisterExternalBuffer(TileShadowDownsampleFactorAtlas);
|
|
}
|
|
FillTexture(FrameTemporaries.DiffuseLightingAndSecondMomentHistoryAtlas, DiffuseLightingAndSecondMomentHistoryAtlas);
|
|
FillTexture(FrameTemporaries.NumFramesAccumulatedHistoryAtlas, NumFramesAccumulatedHistoryAtlas);
|
|
}
|
|
|
|
void FDeferredShadingSceneRenderer::BeginUpdateLumenSceneTasks(FRDGBuilder& GraphBuilder, FLumenSceneFrameTemporaries& FrameTemporaries)
|
|
{
|
|
LLM_SCOPE_BYTAG(Lumen);
|
|
|
|
bool bAnyLumenActive = false;
|
|
bool bHasOrthographicView = false;
|
|
|
|
for (FViewInfo& View : Views)
|
|
{
|
|
bool bLumenActive = ShouldRenderLumenDiffuseGI(Scene, View);
|
|
bAnyLumenActive = bAnyLumenActive || bLumenActive;
|
|
if (!bHasOrthographicView && !View.IsPerspectiveProjection())
|
|
{
|
|
bHasOrthographicView = true;
|
|
}
|
|
|
|
if (bLumenActive)
|
|
{
|
|
// Cache LumenSceneData pointer per view for efficient lookup of the view specific Lumen scene (also nice for debugging)
|
|
View.ViewLumenSceneData = Scene->FindLumenSceneData(View.ViewState ? View.ViewState->GetShareOriginViewKey() : 0, View.GPUMask.GetFirstIndex());
|
|
|
|
#if WITH_MGPU
|
|
if (View.ViewLumenSceneData->bViewSpecific)
|
|
{
|
|
// Update view specific scene data if the GPU mask changed (copies resources cross GPU so CPU and GPU data are coherent)
|
|
View.ViewLumenSceneData->UpdateGPUMask(GraphBuilder, FrameTemporaries, View.ViewState->Lumen, View.GPUMask);
|
|
}
|
|
else if (View.GPUMask.GetFirstIndex() != 0)
|
|
{
|
|
// Otherwise, if this view is on a different GPU, we need to allocate GPU specific scene data (if not already allocated)
|
|
if (View.ViewLumenSceneData == Scene->DefaultLumenSceneData)
|
|
{
|
|
View.ViewLumenSceneData = new FLumenSceneData(Scene->DefaultLumenSceneData->bTrackAllPrimitives);
|
|
|
|
View.ViewLumenSceneData->CopyInitialData(*Scene->DefaultLumenSceneData);
|
|
|
|
// Key shouldn't already exist in Scene, because "FindLumenSceneData" above should have found it
|
|
FLumenSceneDataKey ByGPUIndex = { 0, View.GPUMask.GetFirstIndex() };
|
|
check(Scene->PerViewOrGPULumenSceneData.Find(ByGPUIndex) == nullptr);
|
|
|
|
Scene->PerViewOrGPULumenSceneData.Emplace(ByGPUIndex, View.ViewLumenSceneData);
|
|
}
|
|
}
|
|
#endif // WITH_MGPU
|
|
}
|
|
}
|
|
|
|
LumenCardRenderer.Reset();
|
|
|
|
// Release Lumen scene resource if it's disabled by scalability
|
|
if (!LumenDiffuseIndirect::IsAllowed())
|
|
{
|
|
FLumenSceneData& LumenSceneData = *Scene->GetLumenSceneData(Views[0]);
|
|
LumenSceneData.ReleaseAtlas();
|
|
}
|
|
|
|
if (!bAnyLumenActive || ViewFamily.EngineShowFlags.HitProxies)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FRHIGPUMask GPUMask = GraphBuilder.RHICmdList.GetGPUMask();
|
|
const bool bAllowCardSharing = LumenScene::AllowSurfaceCacheCardSharing();
|
|
const bool bAllowCardDownsampleFromSelf = CVarLumenSceneSurfaceCacheAllowCardDownsampleFromSelf.GetValueOnRenderThread() != 0;
|
|
|
|
FLumenSceneData& LumenSceneData = *Scene->GetLumenSceneData(Views[0]);
|
|
LumenSceneData.bDebugClearAllCachedState = GLumenSceneRecaptureLumenSceneEveryFrame != 0
|
|
|| LumenSceneData.bAllowCardSharing != bAllowCardSharing
|
|
|| LumenSceneData.bAllowCardDownsampleFromSelf != bAllowCardDownsampleFromSelf;
|
|
LumenSceneData.bAllowCardSharing = bAllowCardSharing;
|
|
LumenSceneData.bAllowCardDownsampleFromSelf = bAllowCardDownsampleFromSelf;
|
|
FrameTemporaries.bReallocateAtlas = LumenSceneData.UpdateAtlasSize();
|
|
|
|
FLumenSceneData::FFeedbackData SurfaceCacheFeedbackData;
|
|
{
|
|
extern int32 GLumenSurfaceCacheFeedback;
|
|
if (GLumenSurfaceCacheFeedback != 0)
|
|
{
|
|
FrameTemporaries.SurfaceCacheFeedbackBuffer = LumenSceneData.SurfaceCacheFeedback.GetLatestReadbackBuffer();
|
|
|
|
if (FrameTemporaries.SurfaceCacheFeedbackBuffer)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(LockSurfaceCacheFeedbackBuffer);
|
|
SurfaceCacheFeedbackData.NumElements = Lumen::GetCompactedFeedbackBufferSize();
|
|
SurfaceCacheFeedbackData.Data = (const uint32*) FrameTemporaries.SurfaceCacheFeedbackBuffer->Lock(SurfaceCacheFeedbackData.NumElements * sizeof(uint32) * Lumen::FeedbackBufferElementStride);
|
|
}
|
|
}
|
|
}
|
|
|
|
const FLumenSceneReadback::FAddOp* SceneAddOpsReadbackData = nullptr;
|
|
const FLumenSceneReadback::FRemoveOp* SceneRemoveOpsReadbackData = nullptr;
|
|
|
|
if (CVarLumenSceneGPUDrivenUpdate.GetValueOnRenderThread() != 0)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(LockSceneReadbackBuffer);
|
|
|
|
FLumenSceneReadback::FBuffersRHI ReadbackBuffers = LumenSceneData.SceneReadback.GetLatestReadbackBuffers();
|
|
|
|
FrameTemporaries.SceneAddOpsReadbackBuffer = ReadbackBuffers.AddOps;
|
|
FrameTemporaries.SceneRemoveOpsReadbackBuffer = ReadbackBuffers.RemoveOps;
|
|
|
|
if (ReadbackBuffers.AddOps)
|
|
{
|
|
SceneAddOpsReadbackData = (const FLumenSceneReadback::FAddOp*) FrameTemporaries.SceneAddOpsReadbackBuffer->Lock(LumenSceneData.SceneReadback.GetAddOpsBufferSizeInBytes());
|
|
}
|
|
|
|
if (ReadbackBuffers.RemoveOps)
|
|
{
|
|
SceneRemoveOpsReadbackData = (const FLumenSceneReadback::FRemoveOp*)FrameTemporaries.SceneRemoveOpsReadbackBuffer->Lock(LumenSceneData.SceneReadback.GetRemoveOpsBufferSizeInBytes());
|
|
}
|
|
}
|
|
|
|
FrameTemporaries.UpdateSceneTask = GraphBuilder.AddSetupTask([this, GPUMask, &LumenSceneData, bReallocateAtlas = FrameTemporaries.bReallocateAtlas, SurfaceCacheFeedbackData, SceneAddOpsReadbackData, SceneRemoveOpsReadbackData, bHasOrthographicView]
|
|
{
|
|
SCOPED_NAMED_EVENT(FDeferredShadingSceneRenderer_BeginUpdateLumenSceneTasks, FColor::Emerald);
|
|
QUICK_SCOPE_CYCLE_COUNTER(BeginUpdateLumenSceneTasks);
|
|
|
|
// Surface cache reset for debugging
|
|
if ((GLumenSceneSurfaceCacheReset != 0)
|
|
|| (GLumenSceneSurfaceCacheResetEveryNthFrame > 0 && (ViewFamily.FrameNumber % (uint32)GLumenSceneSurfaceCacheResetEveryNthFrame == 0)))
|
|
{
|
|
LumenSceneData.bDebugClearAllCachedState = true;
|
|
GLumenSceneSurfaceCacheReset = 0;
|
|
}
|
|
|
|
if (GLumenSceneForceEvictHiResPages != 0)
|
|
{
|
|
LumenSceneData.ForceEvictEntireCache();
|
|
GLumenSceneForceEvictHiResPages = 0;
|
|
}
|
|
|
|
LumenSceneData.NumMeshCardsToAdd = 0;
|
|
LumenSceneData.NumLockedCardsToUpdate = 0;
|
|
LumenSceneData.NumHiResPagesToAdd = 0;
|
|
|
|
UpdateLumenScenePrimitives(GPUMask, Scene);
|
|
|
|
if (LumenSceneData.bDebugClearAllCachedState || bReallocateAtlas)
|
|
{
|
|
LumenSceneData.RemoveAllMeshCards();
|
|
}
|
|
|
|
TArray<FVector, TInlineAllocator<LUMEN_MAX_VIEWS>> LumenSceneCameraOrigins;
|
|
float MaxCardUpdateDistanceFromCamera = 0.0f;
|
|
float LumenSceneDetail = 0.0f;
|
|
bool bAddTranslucentToCache = false;
|
|
|
|
for (const FViewInfo& View : Views)
|
|
{
|
|
LumenSceneCameraOrigins.Add(Lumen::GetLumenSceneViewOrigin(View, Lumen::GetNumGlobalDFClipmaps(View) - 1));
|
|
MaxCardUpdateDistanceFromCamera = FMath::Max(MaxCardUpdateDistanceFromCamera, LumenScene::GetCardMaxDistance(View));
|
|
LumenSceneDetail = FMath::Max(LumenSceneDetail, FMath::Clamp<float>(View.FinalPostProcessSettings.LumenSceneDetail, .125f, 8.0f));
|
|
bAddTranslucentToCache |= LumenReflections::UseTranslucentRayTracing(View) && LumenReflections::UseHitLighting(View, GetViewPipelineState(View).DiffuseIndirectMethod);
|
|
bAddTranslucentToCache |= RayTracedTranslucency::IsEnabled(View);
|
|
}
|
|
|
|
// Add streaming view origins, only if there are futher apart than existing origins
|
|
AddLumenStreamingViewOrigins(ViewFamily, LumenSceneCameraOrigins);
|
|
|
|
const int32 MaxTileCapturesPerFrame = GetMaxTileCapturesPerFrame();
|
|
|
|
if (MaxTileCapturesPerFrame > 0)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(FillCardPagesToRender);
|
|
|
|
TArray<FSurfaceCacheRequest, SceneRenderingAllocator> SurfaceCacheRequests;
|
|
|
|
if (CVarLumenSceneGPUDrivenUpdate.GetValueOnRenderThread() != 0)
|
|
{
|
|
ProcessSceneRemoveOpsReadbackData(LumenSceneData, SceneRemoveOpsReadbackData);
|
|
ProcessSceneAddOpsReadbackData(LumenSceneData, SceneAddOpsReadbackData);
|
|
}
|
|
else
|
|
{
|
|
UpdateSurfaceCachePrimitives(
|
|
LumenSceneData,
|
|
LumenSceneCameraOrigins,
|
|
bHasOrthographicView,
|
|
LumenSceneDetail,
|
|
MaxCardUpdateDistanceFromCamera,
|
|
LumenCardRenderer,
|
|
bAddTranslucentToCache);
|
|
}
|
|
|
|
UpdateSurfaceCacheMeshCards(
|
|
LumenSceneData,
|
|
SurfaceCacheFeedbackData,
|
|
LumenSceneCameraOrigins,
|
|
bHasOrthographicView,
|
|
LumenSceneDetail,
|
|
MaxCardUpdateDistanceFromCamera,
|
|
SurfaceCacheRequests,
|
|
ViewFamily);
|
|
|
|
LumenSceneData.ProcessLumenSurfaceCacheRequests(
|
|
Views[0],
|
|
MaxCardUpdateDistanceFromCamera,
|
|
MaxTileCapturesPerFrame,
|
|
LumenCardRenderer,
|
|
GPUMask,
|
|
SurfaceCacheRequests);
|
|
}
|
|
|
|
TArray<FCardPageRenderData, SceneRenderingAllocator>& CardPagesToRender = LumenCardRenderer.CardPagesToRender;
|
|
|
|
if (CardPagesToRender.Num() > 0)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(MeshPassSetup);
|
|
|
|
#if (UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT) && STATS
|
|
if (GLumenSceneSurfaceCacheLogUpdates != 0)
|
|
{
|
|
UE_LOG(LogRenderer, Log, TEXT("Surface Cache Updates: %d"), CardPagesToRender.Num());
|
|
|
|
if (GLumenSceneSurfaceCacheLogUpdates > 1)
|
|
{
|
|
for (const FCardPageRenderData& CardPageRenderData : CardPagesToRender)
|
|
{
|
|
const FLumenPrimitiveGroup& LumenPrimitiveGroup = LumenSceneData.PrimitiveGroups[CardPageRenderData.PrimitiveGroupIndex];
|
|
|
|
UE_LOG(LogRenderer, Log, TEXT("%s Instance:%d NumPrimsInGroup: %d"),
|
|
*LumenPrimitiveGroup.Primitives[0]->Proxy->GetStatId().GetName().ToString(),
|
|
LumenPrimitiveGroup.PrimitiveInstanceIndex,
|
|
LumenPrimitiveGroup.Primitives.Num());
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
for (FCardPageRenderData& CardPageRenderData : CardPagesToRender)
|
|
{
|
|
CardPageRenderData.StartMeshDrawCommandIndex = LumenCardRenderer.MeshDrawCommands.Num();
|
|
CardPageRenderData.NumMeshDrawCommands = 0;
|
|
int32 NumNanitePrimitives = 0;
|
|
|
|
const FLumenPrimitiveGroup& PrimitiveGroup = LumenSceneData.PrimitiveGroups[CardPageRenderData.PrimitiveGroupIndex];
|
|
const FLumenCard& Card = LumenSceneData.Cards[CardPageRenderData.CardIndex];
|
|
ensure(Card.bVisible);
|
|
|
|
if (PrimitiveGroup.bHeightfield)
|
|
{
|
|
LumenScene::AddCardCaptureDraws(
|
|
Scene,
|
|
CardPageRenderData,
|
|
PrimitiveGroup,
|
|
LumenSceneData.LandscapePrimitives,
|
|
LumenCardRenderer.MeshDrawCommands,
|
|
LumenCardRenderer.MeshDrawPrimitiveIds);
|
|
}
|
|
else
|
|
{
|
|
LumenScene::AddCardCaptureDraws(
|
|
Scene,
|
|
CardPageRenderData,
|
|
PrimitiveGroup,
|
|
PrimitiveGroup.Primitives,
|
|
LumenCardRenderer.MeshDrawCommands,
|
|
LumenCardRenderer.MeshDrawPrimitiveIds);
|
|
}
|
|
|
|
CardPageRenderData.NumMeshDrawCommands = LumenCardRenderer.MeshDrawCommands.Num() - CardPageRenderData.StartMeshDrawCommandIndex;
|
|
}
|
|
}
|
|
|
|
}, MakeArrayView({ Scene->GetCacheMeshDrawCommandsTask(), Scene->GetCacheNaniteMaterialBinsTask() }), UE::Tasks::ETaskPriority::High);
|
|
}
|
|
|
|
IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FLumenCardScene, "LumenCardScene");
|
|
|
|
void UpdateLumenCardSceneUniformBuffer(
|
|
FRDGBuilder& GraphBuilder,
|
|
FScene* Scene,
|
|
const FLumenSceneData& LumenSceneData,
|
|
FLumenSceneFrameTemporaries& FrameTemporaries)
|
|
{
|
|
FLumenCardScene* UniformParameters = GraphBuilder.AllocParameters<FLumenCardScene>();
|
|
UniformParameters->NumCards = LumenSceneData.Cards.Num();
|
|
UniformParameters->NumMeshCards = LumenSceneData.MeshCards.Num();
|
|
UniformParameters->NumCardPages = LumenSceneData.GetNumCardPages();
|
|
UniformParameters->NumHeightfields = LumenSceneData.Heightfields.Num();
|
|
UniformParameters->NumPrimitiveGroups = LumenSceneData.PrimitiveGroups.Num();
|
|
UniformParameters->PhysicalAtlasSize = LumenSceneData.GetPhysicalAtlasSize();
|
|
UniformParameters->InvPhysicalAtlasSize = FVector2f(1.0f) / UniformParameters->PhysicalAtlasSize;
|
|
UniformParameters->IndirectLightingAtlasDownsampleFactor = LumenRadiosity::GetAtlasDownsampleFactor();
|
|
|
|
if (FrameTemporaries.CardBufferSRV)
|
|
{
|
|
UniformParameters->CardData = FrameTemporaries.CardBufferSRV;
|
|
UniformParameters->MeshCardsData = FrameTemporaries.MeshCardsBufferSRV;
|
|
UniformParameters->HeightfieldData = FrameTemporaries.HeightfieldBufferSRV;
|
|
UniformParameters->PrimitiveGroupData = FrameTemporaries.PrimitiveGroupBufferSRV;
|
|
UniformParameters->SceneInstanceIndexToMeshCardsIndexBuffer = FrameTemporaries.SceneInstanceIndexToMeshCardsIndexBufferSRV;
|
|
UniformParameters->PageTableBuffer = FrameTemporaries.PageTableBufferSRV;
|
|
UniformParameters->CardPageData = FrameTemporaries.CardPageBufferSRV;
|
|
}
|
|
else
|
|
{
|
|
FRDGBufferSRVRef DefaultSRV = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, sizeof(FVector4f)));
|
|
|
|
UniformParameters->CardData = DefaultSRV;
|
|
UniformParameters->MeshCardsData = DefaultSRV;
|
|
UniformParameters->HeightfieldData = DefaultSRV;
|
|
UniformParameters->CardPageData = DefaultSRV;
|
|
UniformParameters->PrimitiveGroupData = DefaultSRV;
|
|
UniformParameters->PageTableBuffer = UniformParameters->SceneInstanceIndexToMeshCardsIndexBuffer = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultByteAddressBuffer(GraphBuilder, sizeof(FVector4f)));
|
|
}
|
|
|
|
if (FrameTemporaries.AlbedoAtlas)
|
|
{
|
|
UniformParameters->AlbedoAtlas = FrameTemporaries.AlbedoAtlas;
|
|
UniformParameters->OpacityAtlas = FrameTemporaries.OpacityAtlas;
|
|
UniformParameters->NormalAtlas = FrameTemporaries.NormalAtlas;
|
|
UniformParameters->EmissiveAtlas = FrameTemporaries.EmissiveAtlas;
|
|
UniformParameters->DepthAtlas = FrameTemporaries.DepthAtlas;
|
|
}
|
|
else
|
|
{
|
|
UniformParameters->AlbedoAtlas = UniformParameters->OpacityAtlas = UniformParameters->NormalAtlas = UniformParameters->EmissiveAtlas = UniformParameters->DepthAtlas = GSystemTextures.GetBlackDummy(GraphBuilder);
|
|
}
|
|
|
|
FrameTemporaries.LumenCardSceneUniformBuffer = GraphBuilder.CreateUniformBuffer(UniformParameters);
|
|
}
|
|
|
|
DECLARE_GPU_STAT(UpdateCardSceneBuffer);
|
|
|
|
class FClearLumenCardCapturePS : public FGlobalShader
|
|
{
|
|
DECLARE_GLOBAL_SHADER(FClearLumenCardCapturePS);
|
|
SHADER_USE_PARAMETER_STRUCT(FClearLumenCardCapturePS, FGlobalShader);
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
using FPermutationDomain = TShaderPermutationDomain<>;
|
|
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
|
|
{
|
|
return DoesPlatformSupportLumenGI(Parameters.Platform);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FClearLumenCardCapturePS, "/Engine/Private/Lumen/LumenSceneLighting.usf", "ClearLumenCardCapturePS", SF_Pixel);
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FClearLumenCardCaptureParameters, )
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FPixelShaderUtils::FRasterizeToRectsVS::FParameters, VS)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FClearLumenCardCapturePS::FParameters, PS)
|
|
RENDER_TARGET_BINDING_SLOTS()
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
void ClearLumenCardCapture(
|
|
FRDGBuilder& GraphBuilder,
|
|
const FGlobalShaderMap* GlobalShaderMap,
|
|
const FCardCaptureAtlas& Atlas,
|
|
FRDGBufferSRVRef RectCoordBufferSRV,
|
|
uint32 NumRects)
|
|
{
|
|
FClearLumenCardCaptureParameters* PassParameters = GraphBuilder.AllocParameters<FClearLumenCardCaptureParameters>();
|
|
|
|
PassParameters->RenderTargets[0] = FRenderTargetBinding(Atlas.Albedo, ERenderTargetLoadAction::ELoad);
|
|
PassParameters->RenderTargets[1] = FRenderTargetBinding(Atlas.Normal, ERenderTargetLoadAction::ELoad);
|
|
PassParameters->RenderTargets[2] = FRenderTargetBinding(Atlas.Emissive, ERenderTargetLoadAction::ELoad);
|
|
PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(Atlas.DepthStencil, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthWrite_StencilWrite);
|
|
|
|
auto PixelShader = GlobalShaderMap->GetShader<FClearLumenCardCapturePS>();
|
|
|
|
FPixelShaderUtils::AddRasterizeToRectsPass<FClearLumenCardCapturePS>(
|
|
GraphBuilder,
|
|
GlobalShaderMap,
|
|
RDG_EVENT_NAME("ClearCardCapture"),
|
|
PixelShader,
|
|
PassParameters,
|
|
Atlas.Size,
|
|
RectCoordBufferSRV,
|
|
NumRects,
|
|
TStaticBlendState<>::GetRHI(),
|
|
TStaticRasterizerState<>::GetRHI(),
|
|
TStaticDepthStencilState<true, CF_Always,
|
|
true, CF_Always, SO_Replace, SO_Replace, SO_Replace,
|
|
false, CF_Always, SO_Replace, SO_Replace, SO_Replace,
|
|
0xff, 0xff>::GetRHI());
|
|
}
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FLumenCardPassParameters, )
|
|
// An RDG View uniform buffer is used as an optimization to move creation off the render thread.
|
|
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FViewUniformShaderParameters, View)
|
|
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FLumenCardPassUniformParameters, CardPass)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FInstanceCullingDrawParams, InstanceCullingDrawParams)
|
|
RENDER_TARGET_BINDING_SLOTS()
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
FIntPoint FLumenSceneData::GetCardCaptureAtlasSizeInPages() const
|
|
{
|
|
const float MultPerComponent = 1.0f / FMath::Sqrt(FMath::Clamp(GLumenSceneCardCaptureFactor, 1.0f, 1024.0f));
|
|
|
|
FIntPoint CaptureAtlasSizeInPages;
|
|
CaptureAtlasSizeInPages.X = FMath::DivideAndRoundUp<uint32>(PhysicalAtlasSize.X * MultPerComponent + 0.5f, Lumen::PhysicalPageSize);
|
|
CaptureAtlasSizeInPages.Y = FMath::DivideAndRoundUp<uint32>(PhysicalAtlasSize.Y * MultPerComponent + 0.5f, Lumen::PhysicalPageSize);
|
|
return CaptureAtlasSizeInPages;
|
|
}
|
|
|
|
FIntPoint FLumenSceneData::GetCardCaptureAtlasSize() const
|
|
{
|
|
return GetCardCaptureAtlasSizeInPages() * Lumen::PhysicalPageSize;
|
|
}
|
|
|
|
uint32 FLumenSceneData::GetCardCaptureRefreshNumTexels() const
|
|
{
|
|
const float CardCaptureRefreshFraction = FMath::Clamp(CVarLumenSceneCardCaptureRefreshFraction.GetValueOnRenderThread(), 0.0f, 1.0f);
|
|
if (CardCaptureRefreshFraction > 0.0f)
|
|
{
|
|
// Allow to capture at least 1 full physical page
|
|
FIntPoint CardCaptureAtlasSize = GetCardCaptureAtlasSize();
|
|
return FMath::Max(CardCaptureAtlasSize.X * CardCaptureAtlasSize.Y * CardCaptureRefreshFraction, Lumen::PhysicalPageSize * Lumen::PhysicalPageSize);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32 FLumenSceneData::GetCardCaptureRefreshNumPages() const
|
|
{
|
|
const float CardCaptureRefreshFraction = FMath::Clamp(CVarLumenSceneCardCaptureRefreshFraction.GetValueOnRenderThread(), 0.0f, 1.0f);
|
|
if (CardCaptureRefreshFraction > 0.0f)
|
|
{
|
|
// Allow to capture at least 1 full physical page
|
|
return FMath::Clamp(GetMaxTileCapturesPerFrame() * CardCaptureRefreshFraction, 1, GetMaxTileCapturesPerFrame());
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool UpdateGlobalLightingState(const FScene* Scene, const FViewInfo& View, FLumenSceneData& LumenSceneData)
|
|
{
|
|
FLumenGlobalLightingState& GlobalLightingState = LumenSceneData.GlobalLightingState;
|
|
|
|
bool bPropagateGlobalLightingChange = false;
|
|
const FLightSceneInfo* DirectionalLightSceneInfo = nullptr;
|
|
|
|
for (const FLightSceneInfo* LightSceneInfo : Scene->DirectionalLights)
|
|
{
|
|
if (LightSceneInfo->ShouldRenderLightViewIndependent()
|
|
&& LightSceneInfo->ShouldRenderLight(View, true)
|
|
&& LightSceneInfo->Proxy->GetIndirectLightingScale() > 0.0f)
|
|
{
|
|
DirectionalLightSceneInfo = LightSceneInfo;
|
|
break;
|
|
}
|
|
}
|
|
|
|
{
|
|
const float OldMax = GlobalLightingState.bDirectionalLightValid ? GlobalLightingState.DirectionalLightColor.GetMax() : 0.0f;
|
|
const float NewMax = DirectionalLightSceneInfo ? DirectionalLightSceneInfo->Proxy->GetColor().GetMax() : 0.0f;
|
|
const float Ratio = FMath::Max(OldMax, .00001f) / FMath::Max(NewMax, .00001f);
|
|
|
|
if (Ratio > 4.0f || Ratio < .25f)
|
|
{
|
|
bPropagateGlobalLightingChange = true;
|
|
}
|
|
}
|
|
|
|
if (DirectionalLightSceneInfo)
|
|
{
|
|
GlobalLightingState.DirectionalLightColor = DirectionalLightSceneInfo->Proxy->GetColor();
|
|
GlobalLightingState.bDirectionalLightValid = true;
|
|
}
|
|
else
|
|
{
|
|
GlobalLightingState.DirectionalLightColor = FLinearColor::Black;
|
|
GlobalLightingState.bDirectionalLightValid = false;
|
|
}
|
|
|
|
const FSkyLightSceneProxy* SkyLightProxy = Scene->SkyLight;
|
|
|
|
{
|
|
const float OldMax = GlobalLightingState.bSkyLightValid ? GlobalLightingState.SkyLightColor.GetMax() : 0.0f;
|
|
const float NewMax = SkyLightProxy ? SkyLightProxy->GetEffectiveLightColor().GetMax() : 0.0f;
|
|
const float Ratio = FMath::Max(OldMax, .00001f) / FMath::Max(NewMax, .00001f);
|
|
|
|
if (Ratio > 4.0f || Ratio < .25f)
|
|
{
|
|
bPropagateGlobalLightingChange = true;
|
|
}
|
|
}
|
|
|
|
if (SkyLightProxy)
|
|
{
|
|
GlobalLightingState.SkyLightColor = SkyLightProxy->GetEffectiveLightColor();
|
|
GlobalLightingState.bSkyLightValid = true;
|
|
}
|
|
else
|
|
{
|
|
GlobalLightingState.SkyLightColor = FLinearColor::Black;
|
|
GlobalLightingState.bSkyLightValid = false;
|
|
}
|
|
|
|
if (CVarLumenScenePropagateGlobalLightingChange.GetValueOnRenderThread() == 0)
|
|
{
|
|
bPropagateGlobalLightingChange = false;
|
|
}
|
|
|
|
return bPropagateGlobalLightingChange;
|
|
}
|
|
|
|
void FDeferredShadingSceneRenderer::UpdateLumenScene(FRDGBuilder& GraphBuilder, FLumenSceneFrameTemporaries& FrameTemporaries)
|
|
{
|
|
LLM_SCOPE_BYTAG(Lumen);
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FDeferredShadingSceneRenderer::UpdateLumenScene);
|
|
CSV_SCOPED_SET_WAIT_STAT(UpdateLumenScene);
|
|
|
|
FrameTemporaries.UpdateSceneTask.Wait();
|
|
|
|
if (FrameTemporaries.SceneAddOpsReadbackBuffer)
|
|
{
|
|
FrameTemporaries.SceneAddOpsReadbackBuffer->Unlock();
|
|
}
|
|
|
|
if (FrameTemporaries.SceneRemoveOpsReadbackBuffer)
|
|
{
|
|
FrameTemporaries.SceneRemoveOpsReadbackBuffer->Unlock();
|
|
}
|
|
|
|
if (FrameTemporaries.SurfaceCacheFeedbackBuffer)
|
|
{
|
|
FrameTemporaries.SurfaceCacheFeedbackBuffer->Unlock();
|
|
}
|
|
|
|
bool bAnyLumenActive = false;
|
|
|
|
for (FViewInfo& View : Views)
|
|
{
|
|
const FPerViewPipelineState& ViewPipelineState = GetViewPipelineState(View);
|
|
bool bLumenActive =
|
|
(ViewPipelineState.DiffuseIndirectMethod == EDiffuseIndirectMethod::Lumen
|
|
// Don't update scene lighting for secondary views
|
|
&& !View.bIsPlanarReflection
|
|
&& !View.bIsReflectionCapture
|
|
&& View.ViewState);
|
|
|
|
bAnyLumenActive = bAnyLumenActive || bLumenActive;
|
|
}
|
|
|
|
if (bAnyLumenActive)
|
|
{
|
|
FLumenSceneData& LumenSceneData = *Scene->GetLumenSceneData(Views[0]);
|
|
const TArray<FCardPageRenderData, SceneRenderingAllocator>& CardPagesToRender = LumenCardRenderer.CardPagesToRender;
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(UpdateLumenScene);
|
|
|
|
RHI_BREADCRUMB_EVENT_STAT(GraphBuilder.RHICmdList, UpdateLumenSceneBuffers, "UpdateLumenSceneBuffers");
|
|
SCOPED_GPU_STAT(GraphBuilder.RHICmdList, UpdateLumenSceneBuffers);
|
|
|
|
RDG_EVENT_SCOPE_STAT(GraphBuilder, LumenSceneUpdate, "LumenSceneUpdate: %u card captures %.3fM texels", CardPagesToRender.Num(), LumenCardRenderer.NumCardTexelsToCapture / (1024.0f * 1024.0f));
|
|
RDG_GPU_STAT_SCOPE(GraphBuilder, LumenSceneUpdate);
|
|
|
|
// Atlas reallocation
|
|
if (FrameTemporaries.bReallocateAtlas || !LumenSceneData.AlbedoAtlas)
|
|
{
|
|
LumenSceneData.AllocateCardAtlases(GraphBuilder, FrameTemporaries, Views[0].Family);
|
|
ClearLumenSurfaceCacheAtlas(GraphBuilder, FrameTemporaries, Views[0].ShaderMap);
|
|
}
|
|
|
|
LumenSceneData.FillFrameTemporaries(GraphBuilder, FrameTemporaries);
|
|
|
|
if (LumenSceneData.bDebugClearAllCachedState)
|
|
{
|
|
ClearLumenSurfaceCacheAtlas(GraphBuilder, FrameTemporaries, Views[0].ShaderMap);
|
|
}
|
|
|
|
if (CVarLumenSceneUploadEveryFrame.GetValueOnRenderThread() != 0)
|
|
{
|
|
LumenSceneData.bReuploadSceneRequest = true;
|
|
}
|
|
|
|
UpdateLumenCardSceneUniformBuffer(GraphBuilder, Scene, *Scene->GetLumenSceneData(Views[0]), FrameTemporaries);
|
|
|
|
FCardCaptureAtlas CardCaptureAtlas;
|
|
FRDGBufferSRVRef CardCaptureRectBufferSRV = nullptr;
|
|
bool bNeedsClearCaptureAtlas = false;
|
|
int32 NumPagesNeedDilation = 0;
|
|
|
|
if (CardPagesToRender.Num())
|
|
{
|
|
// Before we update the GPU page table, read from the persistent atlases for the card pages we are reallocating, and write it to the card capture atlas
|
|
// This is a resample operation, as the original data may have been at a different mip level, or didn't exist at all
|
|
ResampleLumenCards(
|
|
GraphBuilder,
|
|
Views[0],
|
|
Scene,
|
|
LumenSceneData,
|
|
FrameTemporaries,
|
|
CardPagesToRender,
|
|
LumenCardRenderer.bHasAnyCardCopy,
|
|
CardCaptureAtlas,
|
|
LumenCardRenderer.ResampledCardCaptureAtlas,
|
|
CardCaptureRectBufferSRV,
|
|
NumPagesNeedDilation);
|
|
}
|
|
|
|
FRDGScatterUploadBuilder& UploadBuilder = *FRDGScatterUploadBuilder::Create(GraphBuilder);
|
|
|
|
LumenSceneData.UploadPageTable(GraphBuilder, UploadBuilder, FrameTemporaries);
|
|
|
|
LumenCardRenderer.bPropagateGlobalLightingChange = UpdateGlobalLightingState(Scene, Views[0], LumenSceneData);
|
|
|
|
Lumen::UpdateCardSceneBuffer(GraphBuilder, UploadBuilder, FrameTemporaries, ViewFamily, Scene);
|
|
|
|
UploadBuilder.Execute(GraphBuilder);
|
|
|
|
if (CVarLumenSceneGPUDrivenUpdate.GetValueOnRenderThread() != 0)
|
|
{
|
|
LumenScene::GPUDrivenUpdate(GraphBuilder, Scene, Views, FrameTemporaries);
|
|
}
|
|
|
|
// Init transient render targets for capturing cards
|
|
if (!CardCaptureAtlas.Albedo)
|
|
{
|
|
LumenScene::AllocateCardCaptureAtlas(GraphBuilder, LumenSceneData.GetCardCaptureAtlasSize(), CardCaptureAtlas, Scene->GetShaderPlatform());
|
|
bNeedsClearCaptureAtlas = true;
|
|
}
|
|
|
|
if (CardPagesToRender.Num() > 0)
|
|
{
|
|
FRHIBuffer* PrimitiveIdVertexBuffer = nullptr;
|
|
FInstanceCullingResult InstanceCullingResult;
|
|
FInstanceCullingContext* InstanceCullingContext = nullptr;
|
|
if (Scene->GPUScene.IsEnabled())
|
|
{
|
|
InstanceCullingContext = GraphBuilder.AllocObject<FInstanceCullingContext>(TEXT("LumenCardCapture"), Views[0].GetShaderPlatform(), nullptr, TArrayView<const int32>(&Views[0].SceneRendererPrimaryViewId, 1), nullptr);
|
|
|
|
int32 MaxInstances = 0;
|
|
int32 VisibleMeshDrawCommandsNum = 0;
|
|
int32 NewPassVisibleMeshDrawCommandsNum = 0;
|
|
|
|
InstanceCullingContext->SetupDrawCommands(LumenCardRenderer.MeshDrawCommands, false, Scene, MaxInstances, VisibleMeshDrawCommandsNum, NewPassVisibleMeshDrawCommandsNum);
|
|
// Not supposed to do any compaction here.
|
|
ensure(VisibleMeshDrawCommandsNum == LumenCardRenderer.MeshDrawCommands.Num());
|
|
|
|
InstanceCullingContext->BuildRenderingCommands(GraphBuilder, Scene->GPUScene, Views[0].DynamicPrimitiveCollector.GetInstanceSceneDataOffset(), Views[0].DynamicPrimitiveCollector.NumInstances(), InstanceCullingResult);
|
|
}
|
|
else
|
|
{
|
|
// Prepare primitive Id VB for rendering mesh draw commands.
|
|
if (LumenCardRenderer.MeshDrawPrimitiveIds.Num() > 0)
|
|
{
|
|
const uint32 PrimitiveIdBufferDataSize = LumenCardRenderer.MeshDrawPrimitiveIds.Num() * sizeof(int32);
|
|
|
|
FPrimitiveIdVertexBufferPoolEntry Entry = GPrimitiveIdVertexBufferPool.Allocate(GraphBuilder.RHICmdList, PrimitiveIdBufferDataSize);
|
|
PrimitiveIdVertexBuffer = Entry.BufferRHI;
|
|
|
|
void* RESTRICT Data = GraphBuilder.RHICmdList.LockBuffer(PrimitiveIdVertexBuffer, 0, PrimitiveIdBufferDataSize, RLM_WriteOnly);
|
|
FMemory::Memcpy(Data, LumenCardRenderer.MeshDrawPrimitiveIds.GetData(), PrimitiveIdBufferDataSize);
|
|
GraphBuilder.RHICmdList.UnlockBuffer(PrimitiveIdVertexBuffer);
|
|
|
|
GPrimitiveIdVertexBufferPool.ReturnToFreeList(Entry);
|
|
}
|
|
}
|
|
|
|
InstanceCullingResult.Parameters.Scene = GetSceneUniforms().GetBuffer(GraphBuilder);
|
|
|
|
if (!CardCaptureRectBufferSRV)
|
|
{
|
|
FRDGBufferRef CardCaptureRectBuffer = nullptr;
|
|
FRDGUploadData<FUintVector4> CardCaptureRectArray(GraphBuilder, CardPagesToRender.Num());
|
|
|
|
for (int32 Index = 0; Index < CardPagesToRender.Num(); Index++)
|
|
{
|
|
const FCardPageRenderData& CardPageRenderData = CardPagesToRender[Index];
|
|
|
|
FUintVector4& Rect = CardCaptureRectArray[Index];
|
|
Rect.X = FMath::Max(CardPageRenderData.CardCaptureAtlasRect.Min.X, 0);
|
|
Rect.Y = FMath::Max(CardPageRenderData.CardCaptureAtlasRect.Min.Y, 0);
|
|
Rect.Z = FMath::Max(CardPageRenderData.CardCaptureAtlasRect.Max.X, 0);
|
|
Rect.W = FMath::Max(CardPageRenderData.CardCaptureAtlasRect.Max.Y, 0);
|
|
|
|
if (CardPageRenderData.NeedsRender() && CardPageRenderData.DilationMode == ELumenCardDilationMode::DilateOneTexel)
|
|
{
|
|
++NumPagesNeedDilation;
|
|
}
|
|
}
|
|
|
|
CardCaptureRectBuffer =
|
|
CreateUploadBuffer(GraphBuilder, TEXT("Lumen.CardCaptureRects"),
|
|
sizeof(FUintVector4), FMath::RoundUpToPowerOfTwo(CardPagesToRender.Num()),
|
|
CardCaptureRectArray);
|
|
CardCaptureRectBufferSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(CardCaptureRectBuffer, PF_R32G32B32A32_UINT));
|
|
}
|
|
|
|
if (bNeedsClearCaptureAtlas)
|
|
{
|
|
ClearLumenCardCapture(GraphBuilder, Views[0].ShaderMap, CardCaptureAtlas, CardCaptureRectBufferSRV, CardPagesToRender.Num());
|
|
}
|
|
|
|
FViewInfo* SharedView = Views[0].CreateSnapshot();
|
|
{
|
|
SharedView->DynamicPrimitiveCollector = FGPUScenePrimitiveCollector(&GetGPUSceneDynamicContext());
|
|
SharedView->StereoPass = EStereoscopicPass::eSSP_FULL;
|
|
SharedView->DrawDynamicFlags = EDrawDynamicFlags::ForceLowestLOD;
|
|
|
|
// Don't do material texture mip biasing in proxy card rendering
|
|
SharedView->MaterialTextureMipBias = 0;
|
|
|
|
SharedView->CachedViewUniformShaderParameters = MakeUnique<FViewUniformShaderParameters>();
|
|
ViewParametersUtils::CopyViewParametersOnly(*SharedView->CachedViewUniformShaderParameters, *Views[0].CachedViewUniformShaderParameters);
|
|
|
|
// Resources needed by Substrate
|
|
SharedView->CachedViewUniformShaderParameters->ShadingEnergyDiffuseTexture = Views[0].CachedViewUniformShaderParameters->ShadingEnergyDiffuseTexture;
|
|
SharedView->CachedViewUniformShaderParameters->ShadingEnergyClothSpecTexture= Views[0].CachedViewUniformShaderParameters->ShadingEnergyClothSpecTexture;
|
|
SharedView->CachedViewUniformShaderParameters->ShadingEnergyGGXGlassTexture = Views[0].CachedViewUniformShaderParameters->ShadingEnergyGGXGlassTexture;
|
|
SharedView->CachedViewUniformShaderParameters->ShadingEnergyGGXSpecTexture = Views[0].CachedViewUniformShaderParameters->ShadingEnergyGGXSpecTexture;
|
|
SharedView->CachedViewUniformShaderParameters->ShadingEnergySampler = Views[0].CachedViewUniformShaderParameters->ShadingEnergySampler;
|
|
SharedView->CachedViewUniformShaderParameters->SimpleVolumeTexture = Views[0].CachedViewUniformShaderParameters->SimpleVolumeTexture;
|
|
SharedView->CachedViewUniformShaderParameters->SimpleVolumeTextureSampler = Views[0].CachedViewUniformShaderParameters->SimpleVolumeTextureSampler;
|
|
SharedView->CachedViewUniformShaderParameters->SimpleVolumeEnvTexture = Views[0].CachedViewUniformShaderParameters->SimpleVolumeEnvTexture;
|
|
SharedView->CachedViewUniformShaderParameters->SimpleVolumeEnvTextureSampler= Views[0].CachedViewUniformShaderParameters->SimpleVolumeEnvTextureSampler;
|
|
|
|
// Copy atmospheric light resources that could be used in emissive materials. Some setups apparently uses that to inject extra light bounce from atmospheric lights, accounting for atsmosphere transmittance.
|
|
if (ShouldRenderSkyAtmosphere(Scene, SharedView->Family->EngineShowFlags))
|
|
{
|
|
if (FSkyAtmosphereRenderSceneInfo* SkyInfo = Scene->GetSkyAtmosphereSceneInfo())
|
|
{
|
|
SharedView->CachedViewUniformShaderParameters->TransmittanceLutTexture = SkyInfo->GetTransmittanceLutTexture()->GetRHI();
|
|
SharedView->CachedViewUniformShaderParameters->TransmittanceLutTextureSampler = Views[0].CachedViewUniformShaderParameters->TransmittanceLutTextureSampler;
|
|
|
|
SharedView->CachedViewUniformShaderParameters->DistantSkyLightLutBufferSRV = SkyInfo->GetDistantSkyLightLutBufferSRV();
|
|
SharedView->CachedViewUniformShaderParameters->MobileDistantSkyLightLutBufferSRV = SkyInfo->GetMobileDistantSkyLightLutBufferSRV();
|
|
|
|
// SkyViewLutTexture and CameraAerialPerspectiveVolume textures are not available at this stage.
|
|
// Those are also view dependent so there is no reason to make them available for Lumen card.
|
|
}
|
|
}
|
|
|
|
VirtualTexture::FFeedbackShaderParams VirtualTextureFeedbackShaderParams;
|
|
VirtualTexture::GetFeedbackShaderParams(VirtualTextureFeedbackShaderParams);
|
|
VirtualTexture::UpdateViewUniformShaderParameters(VirtualTextureFeedbackShaderParams, *SharedView->CachedViewUniformShaderParameters);
|
|
|
|
SharedView->CreateViewUniformBuffers(*SharedView->CachedViewUniformShaderParameters);
|
|
}
|
|
|
|
FLumenCardPassUniformParameters* PassUniformParameters = GraphBuilder.AllocParameters<FLumenCardPassUniformParameters>();
|
|
SetupSceneTextureUniformParameters(GraphBuilder, &GetActiveSceneTextures(), Scene->GetFeatureLevel(), /*SceneTextureSetupMode*/ ESceneTextureSetupMode::None, PassUniformParameters->SceneTextures);
|
|
PassUniformParameters->EyeAdaptationBuffer = GraphBuilder.CreateSRV(GetEyeAdaptationBuffer(GraphBuilder, Views[0]));
|
|
PassUniformParameters->CachedLightingPreExposure = Lumen::GetCachedLightingPreExposure();
|
|
|
|
{
|
|
uint32 NumPages = 0;
|
|
uint32 NumDraws = 0;
|
|
uint32 NumInstances = 0;
|
|
uint32 NumTris = 0;
|
|
|
|
// Compute some stats about non Nanite meshes which are captured
|
|
#if RDG_EVENTS != RDG_EVENTS_NONE
|
|
{
|
|
for (const FCardPageRenderData& CardPageRenderData : CardPagesToRender)
|
|
{
|
|
if (!CardPageRenderData.NeedsRender())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (CardPageRenderData.NumMeshDrawCommands > 0)
|
|
{
|
|
NumPages += 1;
|
|
NumDraws += CardPageRenderData.NumMeshDrawCommands;
|
|
|
|
for (int32 DrawCommandIndex = CardPageRenderData.StartMeshDrawCommandIndex; DrawCommandIndex < CardPageRenderData.StartMeshDrawCommandIndex + CardPageRenderData.NumMeshDrawCommands; ++DrawCommandIndex)
|
|
{
|
|
const FVisibleMeshDrawCommand& VisibleDrawCommand = LumenCardRenderer.MeshDrawCommands[DrawCommandIndex];
|
|
const FMeshDrawCommand* MeshDrawCommand = VisibleDrawCommand.MeshDrawCommand;
|
|
|
|
uint32 NumInstancesPerDraw = 0;
|
|
|
|
// Count number of instances to draw
|
|
if (VisibleDrawCommand.NumRuns)
|
|
{
|
|
for (int32 InstanceRunIndex = 0; InstanceRunIndex < VisibleDrawCommand.NumRuns; ++InstanceRunIndex)
|
|
{
|
|
const int32 FirstInstance = VisibleDrawCommand.RunArray[InstanceRunIndex * 2 + 0];
|
|
const int32 LastInstance = VisibleDrawCommand.RunArray[InstanceRunIndex * 2 + 1];
|
|
NumInstancesPerDraw += LastInstance - FirstInstance + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NumInstancesPerDraw += MeshDrawCommand->NumInstances;
|
|
}
|
|
|
|
NumInstances += NumInstancesPerDraw;
|
|
NumTris += MeshDrawCommand->NumPrimitives * NumInstancesPerDraw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(CardPageRenderPasses);
|
|
|
|
FLumenCardPassParameters* CommonPassParameters = GraphBuilder.AllocParameters<FLumenCardPassParameters>();
|
|
CommonPassParameters->CardPass = GraphBuilder.CreateUniformBuffer(PassUniformParameters);
|
|
CommonPassParameters->RenderTargets[0] = FRenderTargetBinding(CardCaptureAtlas.Albedo, ERenderTargetLoadAction::ELoad);
|
|
CommonPassParameters->RenderTargets[1] = FRenderTargetBinding(CardCaptureAtlas.Normal, ERenderTargetLoadAction::ELoad);
|
|
CommonPassParameters->RenderTargets[2] = FRenderTargetBinding(CardCaptureAtlas.Emissive, ERenderTargetLoadAction::ELoad);
|
|
CommonPassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(CardCaptureAtlas.DepthStencil, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthWrite_StencilNop);
|
|
|
|
InstanceCullingResult.GetDrawParameters(CommonPassParameters->InstanceCullingDrawParams);
|
|
|
|
for (const FCardPageRenderData& CardPageRenderData : CardPagesToRender)
|
|
{
|
|
if (!CardPageRenderData.NeedsRender())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
RDG_EVENT_SCOPE(GraphBuilder, "MeshCardCapture Pages:%u Draws:%u Instances:%u Tris:%u", NumPages, NumDraws, NumInstances, NumTris);
|
|
|
|
if (CardPageRenderData.NumMeshDrawCommands > 0)
|
|
{
|
|
CardPageRenderData.PatchView(Scene, SharedView);
|
|
|
|
FLumenCardPassParameters* PassParameters = GraphBuilder.AllocParameters<FLumenCardPassParameters>(CommonPassParameters);
|
|
PassParameters->View = GraphBuilder.CreateUniformBuffer(GraphBuilder.AllocParameters(SharedView->CachedViewUniformShaderParameters.Get()));
|
|
|
|
GraphBuilder.AddPass(
|
|
RDG_EVENT_NAME("CardPage Commands:%u", CardPageRenderData.NumMeshDrawCommands),
|
|
PassParameters,
|
|
ERDGPassFlags::Raster,
|
|
[this, ShaderPlatform = Scene->GetShaderPlatform(), bGPUSceneEnabled = Scene->GPUScene.IsEnabled(), PrimitiveIdVertexBuffer, &CardPageRenderData, PassParameters, InstanceCullingContext](FRDGAsyncTask, FRHICommandList& RHICmdList)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(MeshPass);
|
|
|
|
const FIntRect ViewRect = CardPageRenderData.CardCaptureAtlasRect;
|
|
RHICmdList.SetViewport(ViewRect.Min.X, ViewRect.Min.Y, 0.0f, ViewRect.Max.X, ViewRect.Max.Y, 1.0f);
|
|
|
|
FGraphicsMinimalPipelineStateSet GraphicsMinimalPipelineStateSet;
|
|
if (bGPUSceneEnabled)
|
|
{
|
|
FInstanceCullingDrawParams& InstanceCullingDrawParams = PassParameters->InstanceCullingDrawParams;
|
|
|
|
InstanceCullingContext->SubmitDrawCommands(
|
|
LumenCardRenderer.MeshDrawCommands,
|
|
GraphicsMinimalPipelineStateSet,
|
|
GetMeshDrawCommandOverrideArgs(PassParameters->InstanceCullingDrawParams),
|
|
CardPageRenderData.StartMeshDrawCommandIndex,
|
|
CardPageRenderData.NumMeshDrawCommands,
|
|
1,
|
|
RHICmdList);
|
|
}
|
|
else
|
|
{
|
|
FMeshDrawCommandSceneArgs SceneArgs;
|
|
SceneArgs.PrimitiveIdsBuffer = PrimitiveIdVertexBuffer;
|
|
|
|
SubmitMeshDrawCommandsRange(
|
|
LumenCardRenderer.MeshDrawCommands,
|
|
GraphicsMinimalPipelineStateSet,
|
|
SceneArgs,
|
|
FInstanceCullingContext::GetInstanceIdBufferStride(ShaderPlatform),
|
|
false,
|
|
CardPageRenderData.StartMeshDrawCommandIndex,
|
|
CardPageRenderData.NumMeshDrawCommands,
|
|
1,
|
|
RHICmdList);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bAnyNaniteMeshes = false;
|
|
|
|
for (const FCardPageRenderData& CardPageRenderData : CardPagesToRender)
|
|
{
|
|
if (CardPageRenderData.HasNanite() && CardPageRenderData.NeedsRender())
|
|
{
|
|
bAnyNaniteMeshes = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (UseNanite(ShaderPlatform) && ViewFamily.EngineShowFlags.NaniteMeshes && bAnyNaniteMeshes)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(NaniteMeshPass);
|
|
|
|
// Should have launched earlier in the frame, but ensure we have built Lumen commands here just in case (launched early will make this a no-op)
|
|
Nanite::BuildShadingCommands(GraphBuilder, *Scene, ENaniteMeshPass::LumenCardCapture, Scene->NaniteShadingCommands[ENaniteMeshPass::LumenCardCapture]);
|
|
|
|
const FIntPoint DepthStencilAtlasSize = CardCaptureAtlas.Size;
|
|
const FIntRect DepthAtlasRect = FIntRect(0, 0, DepthStencilAtlasSize.X, DepthStencilAtlasSize.Y);
|
|
|
|
Nanite::FSharedContext SharedContext{};
|
|
SharedContext.FeatureLevel = Scene->GetFeatureLevel();
|
|
SharedContext.ShaderMap = GetGlobalShaderMap(SharedContext.FeatureLevel);
|
|
SharedContext.Pipeline = Nanite::EPipeline::Lumen;
|
|
|
|
Nanite::FRasterContext RasterContext = Nanite::InitRasterContext(
|
|
GraphBuilder,
|
|
SharedContext,
|
|
ViewFamily,
|
|
DepthStencilAtlasSize,
|
|
DepthAtlasRect,
|
|
Nanite::EOutputBufferMode::VisBuffer,
|
|
true,
|
|
/*bAsyncCompute*/ CVarLumenSceneSurfaceCacheNaniteAsyncRasterization.GetValueOnRenderThread() != 0,
|
|
CardCaptureRectBufferSRV,
|
|
CardPagesToRender.Num());
|
|
|
|
Nanite::FConfiguration CullingConfig = { 0 };
|
|
CullingConfig.bSupportsMultiplePasses = true;
|
|
CullingConfig.SetViewFlags(*SharedView);
|
|
CullingConfig.bIsLumenCapture = true;
|
|
CullingConfig.bDisableProgrammable = true;
|
|
|
|
auto NaniteRenderer = Nanite::IRenderer::Create(
|
|
GraphBuilder,
|
|
*Scene,
|
|
*SharedView,
|
|
GetSceneUniforms(),
|
|
SharedContext,
|
|
RasterContext,
|
|
CullingConfig,
|
|
FIntRect(),
|
|
nullptr
|
|
);
|
|
|
|
Nanite::FRasterResults RasterResults;
|
|
|
|
const uint32 NumCardPagesToRender = CardPagesToRender.Num();
|
|
|
|
uint32 NextCardIndex = 0;
|
|
while(NextCardIndex < NumCardPagesToRender)
|
|
{
|
|
TArray<int32, SceneRenderingAllocator> CardPagesToCreatePackedView;
|
|
TArray<Nanite::FInstanceDraw, SceneRenderingAllocator> NaniteInstanceDraws;
|
|
|
|
while(NextCardIndex < NumCardPagesToRender && CardPagesToCreatePackedView.Num() < NANITE_MAX_VIEWS_PER_CULL_RASTERIZE_PASS)
|
|
{
|
|
const FCardPageRenderData& CardPageRenderData = CardPagesToRender[NextCardIndex];
|
|
|
|
if (CardPageRenderData.NaniteInstanceIds.Num() > 0 && CardPageRenderData.NeedsRender())
|
|
{
|
|
for(uint32 InstanceID : CardPageRenderData.NaniteInstanceIds)
|
|
{
|
|
NaniteInstanceDraws.Add(Nanite::FInstanceDraw { InstanceID, (uint32)CardPagesToCreatePackedView.Num() });
|
|
}
|
|
|
|
CardPagesToCreatePackedView.Add(NextCardIndex);
|
|
}
|
|
|
|
NextCardIndex++;
|
|
}
|
|
|
|
if (NaniteInstanceDraws.Num() > 0)
|
|
{
|
|
RDG_EVENT_SCOPE(GraphBuilder, "Nanite::RasterizeLumenCards");
|
|
|
|
const uint32 NumPrimaryViews = CardPagesToCreatePackedView.Num();
|
|
|
|
Nanite::FPackedViewArray* NaniteViews = Nanite::FPackedViewArray::CreateWithSetupTask(
|
|
GraphBuilder,
|
|
NumPrimaryViews,
|
|
[CardPagesToCreatePackedView = MoveTemp(CardPagesToCreatePackedView), &CardPagesToRender, NumCardPagesToRender, DepthStencilAtlasSize] (Nanite::FPackedViewArray::ArrayType& OutViews)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(CreateLumenPackedViews);
|
|
|
|
for (const int32 CardPageToRenderIndex : CardPagesToCreatePackedView)
|
|
{
|
|
const FCardPageRenderData& CardPageRenderData = CardPagesToRender[CardPageToRenderIndex];
|
|
|
|
Nanite::FPackedViewParams Params;
|
|
Params.ViewMatrices = CardPageRenderData.ViewMatrices;
|
|
Params.PrevViewMatrices = CardPageRenderData.ViewMatrices;
|
|
Params.ViewRect = CardPageRenderData.CardCaptureAtlasRect;
|
|
Params.RasterContextSize = DepthStencilAtlasSize;
|
|
Params.MaxPixelsPerEdgeMultipler = 1.0f / CardPageRenderData.NaniteLODScaleFactor;
|
|
|
|
OutViews.Add(Nanite::CreatePackedView(Params));
|
|
}
|
|
});
|
|
|
|
NaniteRenderer->DrawGeometry(
|
|
Scene->NaniteRasterPipelines[ENaniteMeshPass::LumenCardCapture],
|
|
RasterResults.VisibilityQuery,
|
|
*NaniteViews,
|
|
NaniteInstanceDraws
|
|
);
|
|
}
|
|
}
|
|
|
|
NaniteRenderer->ExtractResults( RasterResults );
|
|
|
|
if (CVarLumenSceneSurfaceCacheNaniteMultiView.GetValueOnRenderThread() != 0)
|
|
{
|
|
DispatchLumenMeshCapturePass(
|
|
GraphBuilder,
|
|
*Scene,
|
|
SharedView,
|
|
TArrayView<const FCardPageRenderData>(CardPagesToRender),
|
|
RasterResults,
|
|
RasterContext,
|
|
PassUniformParameters,
|
|
CardCaptureRectBufferSRV,
|
|
CardPagesToRender.Num(),
|
|
CardCaptureAtlas.Size,
|
|
CardCaptureAtlas.Albedo,
|
|
CardCaptureAtlas.Normal,
|
|
CardCaptureAtlas.Emissive,
|
|
CardCaptureAtlas.DepthStencil
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// Single capture per card. Slow path, only for debugging.
|
|
for (int32 PageIndex = 0; PageIndex < CardPagesToRender.Num(); ++PageIndex)
|
|
{
|
|
if (CardPagesToRender[PageIndex].HasNanite())
|
|
{
|
|
DispatchLumenMeshCapturePass(
|
|
GraphBuilder,
|
|
*Scene,
|
|
SharedView,
|
|
TArrayView<const FCardPageRenderData>(&CardPagesToRender[PageIndex], 1),
|
|
RasterResults,
|
|
RasterContext,
|
|
PassUniformParameters,
|
|
CardCaptureRectBufferSRV,
|
|
CardPagesToRender.Num(),
|
|
CardCaptureAtlas.Size,
|
|
CardCaptureAtlas.Albedo,
|
|
CardCaptureAtlas.Normal,
|
|
CardCaptureAtlas.Emissive,
|
|
CardCaptureAtlas.DepthStencil
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DilateCardPageOneTexel(
|
|
GraphBuilder,
|
|
Views[0],
|
|
NumPagesNeedDilation,
|
|
CardCaptureRectBufferSRV,
|
|
CardPagesToRender,
|
|
CardCaptureAtlas);
|
|
|
|
UpdateLumenSurfaceCacheAtlas(
|
|
GraphBuilder,
|
|
Views[0],
|
|
FrameTemporaries,
|
|
CardPagesToRender,
|
|
CardCaptureRectBufferSRV,
|
|
CardCaptureAtlas,
|
|
LumenCardRenderer.ResampledCardCaptureAtlas);
|
|
}
|
|
}
|
|
|
|
UpdateLumenCardSceneUniformBuffer(GraphBuilder, Scene, *Scene->GetLumenSceneData(Views[0]), FrameTemporaries);
|
|
|
|
// Reset arrays, but keep allocated memory for 1024 elements
|
|
int32 LumenSlack = bAnyLumenActive ? 1024 : 0;
|
|
FLumenSceneData& LumenSceneData = *Scene->GetLumenSceneData(Views[0]);
|
|
|
|
// Refresh LumenScene if some updates were ignored due to Lumen being inactive
|
|
// For scene captures, don't trigger a reupload unless the scene capture has view specific LumenSceneData (not the default data)
|
|
if (!bAnyLumenActive && (!Views[0].bIsSceneCapture || &LumenSceneData != Scene->DefaultLumenSceneData))
|
|
{
|
|
if (LumenSceneData.CardIndicesToUpdateInBuffer.Num() > 0
|
|
|| LumenSceneData.MeshCardsIndicesToUpdateInBuffer.Num() > 0
|
|
|| LumenSceneData.HeightfieldIndicesToUpdateInBuffer.Num() > 0
|
|
|| LumenSceneData.PrimitivesToUpdateMeshCards.Num() > 0
|
|
|| LumenSceneData.PrimitiveGroupIndicesToUpdateInBuffer.Num() > 0
|
|
|| LumenSceneData.PageTableIndicesToUpdateInBuffer.Num() > 0)
|
|
{
|
|
LumenSceneData.bReuploadSceneRequest = true;
|
|
}
|
|
}
|
|
|
|
GraphBuilder.AddPostExecuteCallback([&LumenSceneData, LumenSlack]
|
|
{
|
|
LumenSceneData.CardIndicesToUpdateInBuffer.Empty(LumenSlack);
|
|
LumenSceneData.MeshCardsIndicesToUpdateInBuffer.Empty(LumenSlack);
|
|
LumenSceneData.HeightfieldIndicesToUpdateInBuffer.Empty(LumenSlack);
|
|
LumenSceneData.PrimitivesToUpdateMeshCards.Empty(LumenSlack);
|
|
LumenSceneData.PrimitiveGroupIndicesToUpdateInBuffer.Empty(LumenSlack);
|
|
LumenSceneData.PageTableIndicesToUpdateInBuffer.Empty(LumenSlack);
|
|
});
|
|
}
|
|
|
|
void FLumenViewOrigin::Init(const FViewInfo& View)
|
|
{
|
|
Family = View.Family;
|
|
|
|
LumenSceneViewOrigin = Lumen::GetLumenSceneViewOrigin(View, Lumen::GetNumGlobalDFClipmaps(View) - 1);
|
|
WorldCameraOrigin = FVector4f((FVector3f)View.ViewMatrices.GetViewOrigin(), 0.0f); // LUMEN_LWC_TODO
|
|
ViewToClip = FMatrix44f(View.ViewMatrices.GetViewProjectionMatrix());
|
|
PreViewTranslationDF = FDFVector3{ View.ViewMatrices.GetPreViewTranslation() };
|
|
FrustumTranslatedWorldToClip = FMatrix44f(View.ViewMatrices.GetTranslatedViewProjectionMatrix());
|
|
OrthoMaxDimension = View.ViewMatrices.GetOrthoDimensions().GetMax(); // Returns zero if not orthographic
|
|
LastEyeAdaptationExposure = View.GetLastEyeAdaptationExposure();
|
|
if (LastEyeAdaptationExposure <= 0.f)
|
|
{
|
|
// InverseExposureLerp() returns NaN if fed a non-positive exposure value
|
|
LastEyeAdaptationExposure = 1.f;
|
|
}
|
|
MaxTraceDistance = Lumen::GetMaxTraceDistance(View);
|
|
CardMaxDistance = LumenScene::GetCardMaxDistance(View);
|
|
LumenSceneDetail = FMath::Clamp<float>(View.FinalPostProcessSettings.LumenSceneDetail, .125f, 8.0f);
|
|
|
|
ReferenceView = &View;
|
|
}
|
|
|
|
FLumenSceneFrameTemporaries::FLumenSceneFrameTemporaries(const TArray<FViewInfo>& Views)
|
|
{
|
|
if (Views[0].bIsSceneCaptureCube)
|
|
{
|
|
// Cube captures use a single origin
|
|
ViewOrigins.SetNum(1);
|
|
ViewOrigins[0].Init(Views[0]);
|
|
|
|
// Cube captures are omnidirectional, so we want a matrix that will pass anything as in-frustum. An all zero matrix
|
|
// will produce a clip position of [0,0,0,1] for any input vector, accomplishing that goal.
|
|
FVector3f ZeroVector(ForceInitToZero);
|
|
ViewOrigins[0].FrustumTranslatedWorldToClip = FMatrix44f(ZeroVector, ZeroVector, ZeroVector, ZeroVector);
|
|
}
|
|
else if (IStereoRendering::IsStereoEyeView(Views[0]))
|
|
{
|
|
// Stereo views can share the same origin with Primary one due to their closeness
|
|
ViewOrigins.SetNum(1);
|
|
ViewOrigins[0].Init(*Views[0].GetPrimaryView());
|
|
}
|
|
else
|
|
{
|
|
// Limit the number of view origin to LUMEN_MAX_VIEWS to avoid issue down the pipe.
|
|
const int32 ViewCount = FMath::Min(LUMEN_MAX_VIEWS, Views.Num());
|
|
ViewOrigins.SetNum(ViewCount);
|
|
for (int32 ViewIndex = 0; ViewIndex < ViewCount; ++ViewIndex)
|
|
{
|
|
ViewOrigins[ViewIndex].Init(Views[ViewIndex]);
|
|
}
|
|
}
|
|
|
|
// Actual extent of viewports -- useful for passing to EncloseVisualizeExtent (used by VisualizeTexture debug feature)
|
|
ViewExtent = FIntPoint(0,0);
|
|
for (const FViewInfo& View : Views)
|
|
{
|
|
ViewExtent.X = FMath::Max(ViewExtent.X, View.ViewRect.Max.X);
|
|
ViewExtent.Y = FMath::Max(ViewExtent.Y, View.ViewRect.Max.Y);
|
|
}
|
|
}
|
|
|
|
FRDGTextureRef FLumenSharedRT::CreateSharedRT(
|
|
FRDGBuilder& Builder,
|
|
const FRDGTextureDesc& Desc,
|
|
FIntPoint VisibleExtent,
|
|
const TCHAR* Name,
|
|
ERDGTextureFlags Flags)
|
|
{
|
|
if (RenderTarget)
|
|
{
|
|
check(Desc.Extent == RenderTarget->Desc.Extent);
|
|
return RenderTarget;
|
|
}
|
|
|
|
RenderTarget = Builder.CreateTexture(Desc, Name, Flags);
|
|
RenderTarget->EncloseVisualizeExtent(VisibleExtent);
|
|
|
|
return RenderTarget;
|
|
} |