Optimize ProbesRenderer to use time-slicing for cubemap faces rendering and filtering

This commit is contained in:
Wojtek Figat
2025-07-03 11:43:56 +02:00
parent 094a6562b8
commit 33e58c12cb
2 changed files with 66 additions and 40 deletions

View File

@@ -106,6 +106,8 @@ private:
Array<ProbeEntry> _probesToBake; Array<ProbeEntry> _probesToBake;
ProbeEntry _current; ProbeEntry _current;
int32 _workStep;
float _customCullingNear;
AssetReference<Shader> _shader; AssetReference<Shader> _shader;
GPUPipelineState* _psFilterFace = nullptr; GPUPipelineState* _psFilterFace = nullptr;
@@ -134,6 +136,7 @@ ProbesRendererService ProbesRendererServiceInstance;
TimeSpan ProbesRenderer::UpdateDelay(0, 0, 0, 0, 100); TimeSpan ProbesRenderer::UpdateDelay(0, 0, 0, 0, 100);
TimeSpan ProbesRenderer::ReleaseTimeout(0, 0, 0, 30); TimeSpan ProbesRenderer::ReleaseTimeout(0, 0, 0, 30);
int32 ProbesRenderer::MaxWorkPerFrame = 1;
Delegate<Actor*> ProbesRenderer::OnRegisterBake; Delegate<Actor*> ProbesRenderer::OnRegisterBake;
Delegate<Actor*> ProbesRenderer::OnFinishBake; Delegate<Actor*> ProbesRenderer::OnFinishBake;
@@ -293,6 +296,7 @@ void ProbesRendererService::Update()
// Clear flag // Clear flag
_updateFrameNumber = 0; _updateFrameNumber = 0;
_workStep = 0;
_current.Type = ProbeEntry::Types::Invalid; _current.Type = ProbeEntry::Types::Invalid;
} }
else if (_current.Type == ProbeEntry::Types::Invalid && timeSinceUpdate > ProbesRenderer::UpdateDelay) else if (_current.Type == ProbeEntry::Types::Invalid && timeSinceUpdate > ProbesRenderer::UpdateDelay)
@@ -321,6 +325,7 @@ void ProbesRendererService::Update()
_probesToBake.RemoveAtKeepOrder(firstValidEntryIndex); _probesToBake.RemoveAtKeepOrder(firstValidEntryIndex);
_task->Enabled = true; _task->Enabled = true;
_updateFrameNumber = 0; _updateFrameNumber = 0;
_workStep = 0;
_lastProbeUpdate = timeNow; _lastProbeUpdate = timeNow;
} }
// Check if need to release data // Check if need to release data
@@ -408,9 +413,11 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context)
PROFILE_GPU("Render Probe"); PROFILE_GPU("Render Probe");
// Init // Init
float customCullingNear = -1;
const int32 probeResolution = _current.GetResolution(); const int32 probeResolution = _current.GetResolution();
const PixelFormat probeFormat = _current.GetFormat(); const PixelFormat probeFormat = _current.GetFormat();
if (_workStep == 0)
{
_customCullingNear = -1;
if (_current.Type == ProbeEntry::Types::EnvProbe) if (_current.Type == ProbeEntry::Types::EnvProbe)
{ {
auto envProbe = (EnvironmentProbe*)_current.Actor.Get(); auto envProbe = (EnvironmentProbe*)_current.Actor.Get();
@@ -434,13 +441,12 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context)
Vector3 position = skyLight->GetPosition(); Vector3 position = skyLight->GetPosition();
float nearPlane = 10.0f; float nearPlane = 10.0f;
float farPlane = Math::Max(nearPlane + 1000.0f, skyLight->SkyDistanceThreshold * 2.0f); float farPlane = Math::Max(nearPlane + 1000.0f, skyLight->SkyDistanceThreshold * 2.0f);
customCullingNear = skyLight->SkyDistanceThreshold; _customCullingNear = skyLight->SkyDistanceThreshold;
// Setup view // Setup view
LargeWorlds::UpdateOrigin(_task->View.Origin, position); LargeWorlds::UpdateOrigin(_task->View.Origin, position);
_task->View.SetUpCube(nearPlane, farPlane, position - _task->View.Origin); _task->View.SetUpCube(nearPlane, farPlane, position - _task->View.Origin);
} }
_task->CameraCut();
// Resize buffers // Resize buffers
bool resizeFailed = _output->Resize(probeResolution, probeResolution, probeFormat); bool resizeFailed = _output->Resize(probeResolution, probeResolution, probeFormat);
@@ -449,31 +455,34 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context)
resizeFailed |= _task->Resize(probeResolution, probeResolution); resizeFailed |= _task->Resize(probeResolution, probeResolution);
if (resizeFailed) if (resizeFailed)
LOG(Error, "Failed to resize probe"); LOG(Error, "Failed to resize probe");
}
// Disable actor during baking (it cannot influence own results) // Disable actor during baking (it cannot influence own results)
const bool isActorActive = _current.Actor->GetIsActive(); const bool isActorActive = _current.Actor->GetIsActive();
_current.Actor->SetIsActive(false); _current.Actor->SetIsActive(false);
// Lower quality when rendering probes in-game to gain performance // Lower quality when rendering probes in-game to gain performance
_task->View.MaxShadowsQuality = Engine::IsPlayMode() ? Quality::Low : Quality::Ultra; _task->View.MaxShadowsQuality = Engine::IsPlayMode() || probeResolution <= 128 ? Quality::Low : Quality::Ultra;
// Render scene for all faces // Render scene for all faces
for (int32 faceIndex = 0; faceIndex < 6; faceIndex++) int32 workLeft = ProbesRenderer::MaxWorkPerFrame;
const int32 lastFace = Math::Min(_workStep + workLeft, 6);
for (int32 faceIndex = _workStep; faceIndex < lastFace; faceIndex++)
{ {
_task->CameraCut();
_task->View.SetFace(faceIndex); _task->View.SetFace(faceIndex);
// Handle custom frustum for the culling (used to skip objects near the camera) // Handle custom frustum for the culling (used to skip objects near the camera)
if (customCullingNear > 0) if (_customCullingNear > 0)
{ {
Matrix p; Matrix p;
Matrix::PerspectiveFov(PI_OVER_2, 1.0f, customCullingNear, _task->View.Far, p); Matrix::PerspectiveFov(PI_OVER_2, 1.0f, _customCullingNear, _task->View.Far, p);
_task->View.CullingFrustum.SetMatrix(_task->View.View, p); _task->View.CullingFrustum.SetMatrix(_task->View.View, p);
} }
// Render frame // Render frame
Renderer::Render(_task); Renderer::Render(_task);
context->ClearState(); context->ClearState();
_task->CameraCut();
// Copy frame to cube face // Copy frame to cube face
{ {
@@ -483,12 +492,17 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context)
context->Draw(_output->View()); context->Draw(_output->View());
context->ResetRenderTarget(); context->ResetRenderTarget();
} }
// Move to the next face
_workStep++;
workLeft--;
} }
// Enable actor back // Enable actor back
_current.Actor->SetIsActive(isActorActive); _current.Actor->SetIsActive(isActorActive);
// Filter all lower mip levels // Filter all lower mip levels
if (workLeft > 0)
{ {
PROFILE_GPU("Filtering"); PROFILE_GPU("Filtering");
Data data; Data data;
@@ -520,11 +534,18 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context)
context->Draw(_tmpFace->View(0, mipIndex)); context->Draw(_tmpFace->View(0, mipIndex));
} }
} }
// End
workLeft--;
_workStep++;
} }
// Cleanup // Cleanup
context->ClearState(); context->ClearState();
if (_workStep < 7)
return; // Continue rendering next frame
// Mark as rendered // Mark as rendered
_updateFrameNumber = Engine::FrameCount; _updateFrameNumber = Engine::FrameCount;
_task->Enabled = false; _task->Enabled = false;

View File

@@ -23,6 +23,11 @@ public:
/// </summary> /// </summary>
static TimeSpan ReleaseTimeout; static TimeSpan ReleaseTimeout;
/// <summary>
/// Maximum amount of cubemap faces or filtering passes that can be performed per-frame (in total). Set it to 7 to perform whole cubemap capture within a single frame, lower values spread the work across multiple frames.
/// </summary>
static int32 MaxWorkPerFrame;
static Delegate<Actor*> OnRegisterBake; static Delegate<Actor*> OnRegisterBake;
static Delegate<Actor*> OnFinishBake; static Delegate<Actor*> OnFinishBake;