diff --git a/Source/Engine/Graphics/Graphics.h b/Source/Engine/Graphics/Graphics.h index c2f0c6386..c14a27ef2 100644 --- a/Source/Engine/Graphics/Graphics.h +++ b/Source/Engine/Graphics/Graphics.h @@ -128,6 +128,16 @@ public: API_FIELD() static bool ColorGradingVolumeLUT; }; + // Global Illumination rendering configuration and API. + API_CLASS(Static, Attributes="DebugCommand") class FLAXENGINE_API GI + { + DECLARE_SCRIPTING_TYPE_MINIMAL(GI); + + // 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); + }; + public: /// /// Disposes the device. diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index 7f9a35950..a96cbcc78 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -105,6 +105,7 @@ public: int32 CascadesCount = 0; int32 ProbeRaysCount = 0; int32 ProbesCountTotal = 0; + int32 FramesSinceClear = 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) @@ -148,6 +149,12 @@ public: { Release(); } + + bool IsFresh() const + { + // Allow to use data from the previous frame (eg. particles in Editor using the Editor viewport in Game viewport - Game render task runs first) + return LastFrameUsed + 1 >= Engine::FrameCount; + } }; void CalculateVolumeRandomRotation(Matrix3x3& matrix) @@ -182,6 +189,20 @@ void CalculateVolumeRandomRotation(Matrix3x3& matrix) matrix.M33 = 1.0f - 2.0f * u3; } +float Graphics::GI::GetConvergence(const RenderBuffers* buffers) +{ + float result = 0; + auto* ddgiData = buffers ? buffers->FindCustomBuffer(TEXT("DDGI")) : nullptr; + if (ddgiData && ddgiData->IsFresh()) + { + // This depends on scene complexity and lighting conditions so fake it + // Potentially could use probes variance readback to estimate it but that's probably too much trouble + constexpr int32 framesToCoverage = 30; + result = Math::Saturate((float)ddgiData->FramesSinceClear / framesToCoverage + 0.01f); + } + return result; +} + String DynamicDiffuseGlobalIlluminationPass::ToString() const { return TEXT("DynamicDiffuseGlobalIlluminationPass"); @@ -286,7 +307,7 @@ void DynamicDiffuseGlobalIlluminationPass::Dispose() bool DynamicDiffuseGlobalIlluminationPass::Get(const RenderBuffers* buffers, BindingData& result) { auto* ddgiData = buffers ? buffers->FindCustomBuffer(TEXT("DDGI")) : nullptr; - if (ddgiData && ddgiData->LastFrameUsed + 1 >= Engine::FrameCount) // Allow to use data from the previous frame (eg. particles in Editor using the Editor viewport in Game viewport - Game render task runs first) + if (ddgiData && ddgiData->IsFresh()) { result = ddgiData->Result; return false; @@ -450,8 +471,10 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont #if DDGI_DEBUG_INSTABILITY context->ClearUA(ddgiData.ProbesInstability, Float4::Zero); #endif + ddgiData.FramesSinceClear = 0; } ddgiData.LastFrameUsed = Engine::FrameCount; + ddgiData.FramesSinceClear++; // Calculate which cascades should be updated this frame const uint64 cascadeFrequencies[] = { 2, 3, 5, 7 }; @@ -725,7 +748,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, if (auto* sceneTask = ScriptingObject::Cast(task)) { auto* sceneTaskDDGI = sceneTask->Buffers ? sceneTask->Buffers->FindCustomBuffer(TEXT("DDGI")) : nullptr; - if (sceneTaskDDGI && sceneTaskDDGI->LastFrameUsed + 1 >= Engine::FrameCount) + if (sceneTaskDDGI && sceneTaskDDGI->IsFresh()) { // Reuse DDGI from this task renderBuffers = sceneTask->Buffers;