diff --git a/Source/Engine/Graphics/Graphics.h b/Source/Engine/Graphics/Graphics.h index 73882b5e1..447625d64 100644 --- a/Source/Engine/Graphics/Graphics.h +++ b/Source/Engine/Graphics/Graphics.h @@ -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 + /// + /// 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. + /// + API_FUNCTION(Attributes="DebugCommand") static void Dump(); +#endif }; public: diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index e3afc6642..c03e78759 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -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; } diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index 8feedd9cd..3a7589212 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -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(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; diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h index cce2e92c8..05da101d5 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h @@ -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; diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 913b176d1..0b8a92c80 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -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(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 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(); diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index 23716c77d..ee2aabd02 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -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.