diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp
index 8483d1f6c..9535f3ecf 100644
--- a/Source/Engine/Graphics/Graphics.cpp
+++ b/Source/Engine/Graphics/Graphics.cpp
@@ -22,6 +22,7 @@ bool Graphics::AllowCSMBlending = false;
Quality Graphics::GlobalSDFQuality = Quality::High;
Quality Graphics::GIQuality = Quality::High;
PostProcessSettings Graphics::PostProcessSettings;
+bool Graphics::SpreadWorkload = true;
#if GRAPHICS_API_NULL
extern GPUDevice* CreateGPUDeviceNull();
diff --git a/Source/Engine/Graphics/Graphics.h b/Source/Engine/Graphics/Graphics.h
index 55ded56e5..b7f1fbcd7 100644
--- a/Source/Engine/Graphics/Graphics.h
+++ b/Source/Engine/Graphics/Graphics.h
@@ -69,9 +69,22 @@ public:
///
API_FIELD() static PostProcessSettings PostProcessSettings;
+public:
+ ///
+ /// Debug utility to toggle graphics workloads amortization over several frames by systems such as shadows mapping, global illumination or surface atlas. Can be used to test performance in the worst-case scenario (eg. camera-cut).
+ ///
+ API_FIELD() static bool SpreadWorkload;
+
public:
///
/// Disposes the device.
///
static void DisposeDevice();
};
+
+// Skip disabling workload spreading in Release builds
+#if BUILD_RELEASE
+#define GPU_SPREAD_WORKLOAD true
+#else
+#define GPU_SPREAD_WORKLOAD Graphics::SpreadWorkload
+#endif
diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp
index 3fbf2dc47..f82123a46 100644
--- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp
+++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp
@@ -402,7 +402,7 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont
bool cascadeSkipUpdate[4];
for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++)
{
- cascadeSkipUpdate[cascadeIndex] = !clear && (ddgiData.LastFrameUsed % cascadeFrequencies[cascadeIndex]) != 0;
+ cascadeSkipUpdate[cascadeIndex] = !clear && (ddgiData.LastFrameUsed % cascadeFrequencies[cascadeIndex]) != 0 && GPU_SPREAD_WORKLOAD;
}
// Compute scrolling (probes are placed around camera but are scrolling to increase stability during movement)
diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp
index a85269fcd..b1ed55eec 100644
--- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp
+++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp
@@ -13,6 +13,7 @@
#include "Engine/Core/Config/GraphicsSettings.h"
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Graphics/Graphics.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderBuffers.h"
#include "Engine/Graphics/RenderTargetPool.h"
@@ -536,7 +537,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
context->SetRenderTarget(depthBuffer, ToSpan(targetBuffers, ARRAY_COUNT(targetBuffers)));
{
PROFILE_GPU_CPU_NAMED("Clear");
- if (noCache || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES)
+ if (noCache || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES || !GPU_SPREAD_WORKLOAD)
{
// Full-atlas hardware clear
context->ClearDepth(depthBuffer);
@@ -1268,7 +1269,7 @@ void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, void* actorObject, con
object->Bounds = OrientedBoundingBox(localBounds);
object->Bounds.Transform(localToWorld);
object->Radius = (float)actorObjectBounds.Radius;
- if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES)
+ if (dirty || GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES || !GPU_SPREAD_WORKLOAD)
{
object->LastFrameUpdated = surfaceAtlasData.CurrentFrame;
object->LightingUpdateFrame = surfaceAtlasData.CurrentFrame;
diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp
index 4b5ee7816..2d35ac09e 100644
--- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp
+++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp
@@ -493,7 +493,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex
// Rasterize world geometry into Global SDF
renderContext.View.Pass = DrawPass::GlobalSDF;
uint32 viewMask = renderContext.View.RenderLayersMask;
- const bool useCache = !updated && !GLOBAL_SDF_DEBUG_FORCE_REDRAW;
+ const bool useCache = !updated && !GLOBAL_SDF_DEBUG_FORCE_REDRAW && GPU_SPREAD_WORKLOAD;
static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_GROUP_SIZE == 0, "Invalid chunk size for Global SDF rasterization group size.");
const int32 rasterizeChunks = Math::CeilToInt((float)resolution / (float)GLOBAL_SDF_RASTERIZE_CHUNK_SIZE);
auto& chunks = ChunksCache;
diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp
index d779e0b83..25c4b993d 100644
--- a/Source/Engine/Renderer/ShadowsPass.cpp
+++ b/Source/Engine/Renderer/ShadowsPass.cpp
@@ -231,6 +231,11 @@ struct ShadowAtlasLight
float CalculateUpdateRateInv(const RenderLightData& light, float distanceFromView, bool& freezeUpdate) const
{
+ if (!GPU_SPREAD_WORKLOAD)
+ {
+ freezeUpdate = false;
+ return 1.0f;
+ }
const float shadowsUpdateRate = light.ShadowsUpdateRate;
const float shadowsUpdateRateAtDistance = shadowsUpdateRate * light.ShadowsUpdateRateAtDistance;
float updateRate = Math::Lerp(shadowsUpdateRate, shadowsUpdateRateAtDistance, Math::Saturate(distanceFromView / Distance));