Add Graphics.GI.Dump command for profiling DDGI/GlobalSDF

This commit is contained in:
2026-06-25 22:49:32 +02:00
parent 1385d5282f
commit a45dec5170
6 changed files with 131 additions and 0 deletions
+7
View File
@@ -153,6 +153,13 @@ public:
// Gets the normalized 0-1 progress of the Global Illumination lighting bounces convergence (0 = no GI, 1 = full GI convergence). Can be used to continue displaying loading screen after scene load to ensure indirect lighting has been evaluated. Non-zero value means GI has started to be calculated.
API_FUNCTION(Attributes="DebugCommand(Hide=true)")
static float GetConvergence(const RenderBuffers* buffers);
#if COMPILE_WITH_PROFILER
/// <summary>
/// Dumps Global Illumination rendering info to the log (the next frame). Can be used to inspect DDGI, Global Surface Atlas and Global SDF memory and usage (for optimization). Prints info about the number of probes, cascades and draw state.
/// </summary>
API_FUNCTION(Attributes="DebugCommand") static void Dump();
#endif
};
public:
@@ -13,6 +13,7 @@
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/Units.h"
#include "Engine/Content/Content.h"
#include "Engine/Core/Utilities.h"
#include "Engine/Debug/DebugDraw.h"
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/GPUDevice.h"
@@ -121,6 +122,7 @@ public:
int32 ProbeRaysCount = 0;
int32 ProbesCountTotal = 0;
int32 FramesSinceClear = 0;
uint32 MemoryUsage = 0;
Int3 ProbeCounts = Int3::Zero;
GPUTexture* ProbesTrace = nullptr; // Probes ray tracing: (RGB: hit radiance, A: hit distance)
GPUTexture* ProbesData = nullptr; // Probes data: (RGB: probe-space offset, A: state/data)
@@ -218,6 +220,29 @@ float Graphics::GI::GetConvergence(const RenderBuffers* buffers)
return result;
}
#if COMPILE_WITH_PROFILER
uint64 DumpGIFrame = MAX_uint64;
void Graphics::GI::Dump()
{
DumpGIFrame = Engine::FrameCount + 1;
}
void UpdateGIDump(const RenderBuffers* buffers, DDGICustomBuffer& ddgiData)
{
if (DumpGIFrame != Engine::FrameCount)
return;
LOG(Info, "DDGI:");
LOG(Info, " > Cascades: {}, Probes: {}, Ray Limit: {}", ddgiData.CascadesCount, ddgiData.ProbesCountTotal, ddgiData.ProbeRaysCount);
LOG(Info, " > Memory Usage: {}", Utilities::BytesToText(ddgiData.MemoryUsage));
GlobalSurfaceAtlasPass::Dump(buffers);
GlobalSignDistanceFieldPass::Instance()->Dump(buffers);
}
#endif
String DynamicDiffuseGlobalIlluminationPass::ToString() const
{
return TEXT("DynamicDiffuseGlobalIlluminationPass");
@@ -469,6 +494,7 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont
INIT_BUFFER(StatsRead, "DDGI.StatsRead");
#endif
#undef INIT_BUFFER
ddgiData.MemoryUsage = memUsage;
LOG(Info, "Dynamic Diffuse Global Illumination probes: {0}, memory usage: {1} MB", probesCountTotal, memUsage / (1024 * 1024));
clear = true;
}
@@ -756,6 +782,10 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont
#endif
}
#if COMPILE_WITH_PROFILER
UpdateGIDump(renderContext.Buffers, ddgiData);
#endif
return false;
}
@@ -173,6 +173,7 @@ public:
float ResolutionInv;
int32 AtlasPixelsTotal = 0;
int32 AtlasPixelsUsed = 0;
uint32 MemoryUsage = 0;
uint64 LastFrameAtlasInsertFail = 0;
uint64 LastFrameAtlasDefragmentation = 0;
GPUTexture* AtlasDepth = nullptr;
@@ -999,6 +1000,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
return true;
memUsage += surfaceAtlasData.ChunksBuffer->GetMemoryUsage();
}
surfaceAtlasData.MemoryUsage = memUsage;
LOG(Info, "Global Surface Atlas resolution: {0}, memory usage: {1} MB", resolution, memUsage / (1024 * 1024));
context->Clear(surfaceAtlasData.AtlasLighting->View(), Color::Transparent);
@@ -1355,8 +1357,10 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
if (surfaceAtlasData.CulledObjectsBuffer->GetSize() < objectsBufferCapacity)
{
const auto desc = GPUBufferDescription::Raw(objectsBufferCapacity, GPUBufferFlags::UnorderedAccess | GPUBufferFlags::ShaderResource);
surfaceAtlasData.MemoryUsage -= surfaceAtlasData.CulledObjectsBuffer->GetSize();
if (surfaceAtlasData.CulledObjectsBuffer->Init(desc))
return true;
surfaceAtlasData.MemoryUsage += desc.Size;
}
objectsBufferCapacity = surfaceAtlasData.CulledObjectsBuffer->GetSize();
ZoneValue(objectsBufferCapacity / 1024); // CulledObjectsBuffer size in kB
@@ -1817,6 +1821,29 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex
#endif
#if COMPILE_WITH_PROFILER
#include "Engine/Core/Utilities.h"
void GlobalSurfaceAtlasPass::Dump(const RenderBuffers* buffers)
{
auto surfaceAtlasDataPtr = buffers->FindCustomBuffer<GlobalSurfaceAtlasCustomBuffer>(TEXT("GlobalSurfaceAtlas"));
if (!surfaceAtlasDataPtr)
return;
auto& surfaceAtlasData = *surfaceAtlasDataPtr;
LOG(Info, "Global Surface Atlas:");
LOG(Info, " > Resolution: {}, Usage: {}%", surfaceAtlasData.Resolution, (int32)((float)surfaceAtlasData.AtlasPixelsUsed / surfaceAtlasData.AtlasPixelsTotal * 100));
uint32 memoryUsage = surfaceAtlasData.MemoryUsage;
if (surfaceAtlasData.ObjectsBuffer.GetBuffer())
memoryUsage += surfaceAtlasData.ObjectsBuffer.GetBuffer()->GetSize();
if (surfaceAtlasData.ObjectsListBuffer.GetBuffer())
memoryUsage += surfaceAtlasData.ObjectsListBuffer.GetBuffer()->GetSize();
LOG(Info, " > Memory Usage: {}", Utilities::BytesToText(memoryUsage));
LOG(Info, " > Objects: {}, Tiles: {}, Lights: {}", surfaceAtlasData.Objects.Count(), surfaceAtlasData.Atlas.Count(), surfaceAtlasData.Lights.Count());
}
#endif
void GlobalSurfaceAtlasPass::GetCullingData(Vector4& cullingPosDistance) const
{
cullingPosDistance = _surfaceAtlasData->CullingPosDistance;
@@ -97,6 +97,11 @@ public:
void RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output);
#endif
#if COMPILE_WITH_PROFILER
// Dumps the info to the log.
static void Dump(const RenderBuffers* buffers);
#endif
// Gets the culling view position (xyz) and view distance (w)
void GetCullingData(Vector4& cullingPosDistance) const;
@@ -367,6 +367,10 @@ public:
auto& cascade = Cascades[cascadeIndex];
cascade.Index = cascadeIndex;
cascade.Dirty = !useCache || RenderTools::ShouldUpdateCascade(FrameIndex, cascadeIndex, cascadesCount, maxCascadeUpdatesPerFrame, updateEveryFrame);
#if COMPILE_WITH_PROFILER
extern uint64 DumpGIFrame;
cascade.Dirty |= DumpGIFrame == Engine::FrameCount; // Force update of all cascades when dumping info (to collect dynamic objects)
#endif
cascade.DirtyDynamicOnly = useCache && !cascade.Dirty && cascade.DynamicDirtyChunks.HasItems() && cascade.VoxelSize > 0.0f && !DebugOverdraw && GLOBAL_SDF_DYNAMIC_UPDATES;
cascade.Draw = cascade.Dirty || cascade.DirtyDynamicOnly;
if (!cascade.Draw)
@@ -1276,6 +1280,59 @@ void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUC
#endif
#if COMPILE_WITH_PROFILER
#include "Engine/Core/Utilities.h"
#include "Engine/Engine/Units.h"
extern uint64 DumpGIFrame;
void GlobalSignDistanceFieldPass::Dump(const RenderBuffers* buffers) const
{
auto sdfDataPtr = buffers->FindCustomBuffer<GlobalSignDistanceFieldCustomBuffer>(TEXT("GlobalSignDistanceField"));
if (!sdfDataPtr)
return;
auto& sdfData = *sdfDataPtr;
LOG(Info, "Global SDF:");
LOG(Info, " > Cascades: {}, Resolution: {}", sdfData.Cascades.Count(), sdfData.Resolution);
uint32 memoryUsage = sdfData.Texture->GetMemoryUsage() + sdfData.TextureMip->GetMemoryUsage();
if (_objectsBuffer && _objectsBuffer->GetBuffer())
memoryUsage += _objectsBuffer->GetBuffer()->GetSize();
LOG(Info, " > Memory Usage: {}", Utilities::BytesToText(memoryUsage));
for (int32 i = 0; i < sdfData.Cascades.Count(); i++)
{
const auto& cascade = sdfData.Cascades[i];
LOG(Info, " > Cascade {}, range: {}m", i, UNITS_TO_METERS(cascade.Extent));
if (cascade.VoxelSize < METERS_TO_UNITS(1))
LOG(Info, " > Voxel size: {} cm", Utilities::RoundTo2DecimalPlaces(UNITS_TO_METERS(cascade.VoxelSize) * 100));
else
LOG(Info, " > Voxel size: {} m", Utilities::RoundTo2DecimalPlaces(UNITS_TO_METERS(cascade.VoxelSize)));
LOG(Info, " > Chunks: {} ({} static, {} dynamic)", cascade.NonEmptyChunks.Count(), cascade.StaticChunks.Count(), cascade.NonEmptyChunks.Count() - cascade.StaticChunks.Count());
LOG(Info, " > Objects: {}", cascade.RasterizeObjects.Count());
}
// Dynamic objects cause frequent chunk updates so list them when profiling content
HashSet<Actor*> dynamicObjects;
for (const auto& cascade : sdfData.Cascades)
{
for (const auto& object : cascade.RasterizeObjects)
{
if (!GLOBAL_SDF_ACTOR_IS_STATIC(object.Actor))
dynamicObjects.Add(object.Actor);
}
}
LOG(Info, " > Dynamic objects: {}", dynamicObjects.Count());
if (dynamicObjects.HasItems())
{
for (auto& e : dynamicObjects)
{
LOG(Info, " > '{}'", e.Item->GetNamePath());
}
}
}
#endif
void GlobalSignDistanceFieldPass::GetCullingData(BoundingBox& bounds) const
{
auto& cascade = *CurrentCascade.Get();
@@ -80,6 +80,11 @@ public:
void RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output);
#endif
#if COMPILE_WITH_PROFILER
// Dumps the info to the log.
void Dump(const RenderBuffers* buffers) const;
#endif
void GetCullingData(BoundingBox& bounds) const;
// Rasterize Model SDF into the Global SDF. Call it from actor Draw() method during DrawPass::GlobalSDF.