Improve Global Surface Atlas objects dirtying limit to pick the largers objects first

This commit is contained in:
2026-04-29 17:52:51 +02:00
parent 61431a6400
commit c4bb39aeac
@@ -40,6 +40,7 @@
#define GLOBAL_SURFACE_ATLAS_TILE_SIZE_MAX 192 // The maximum size of the tile
#define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes)
#define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES 0 // Forces to redraw all object tiles every frame
#define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_LIGHTS 0 // Forces to redraw all lights every frame
#define GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS 0 // Debug draws object bounds on redraw (and tile draw projection locations)
#define GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_CHUNKS 0 // Debug draws culled chunks bounds (non-empty)
#define GLOBAL_SURFACE_ATLAS_MAX_NEW_OBJECTS_PER_FRAME 500 // Limits the amount of newly added objects to atlas per-frame to reduce hitches on 1st frame or camera-cut
@@ -157,6 +158,13 @@ struct GlobalSurfaceAtlasLight
uint64 LastFrameUpdated = 0;
};
struct GlobalSurfaceAtlasPendingDirtyObject
{
void* ActorObject;
GlobalSurfaceAtlasObject* Object;
float Priority;
};
class GlobalSurfaceAtlasCustomBuffer : public RenderBuffers::CustomBuffer, public ISceneRenderingListener
{
public:
@@ -190,6 +198,7 @@ public:
// Cached data to be reused during RasterizeActor
Array<void*> DirtyObjectsBuffer;
Array<GlobalSurfaceAtlasPendingDirtyObject> PendingDirtyObjectsBuffer;
Vector4 CullingPosDistance;
uint64 CurrentFrame;
Float3 ViewPosition;
@@ -495,22 +504,39 @@ public:
void WriteObjects()
{
PROFILE_CPU_NAMED("Write Objects");
DirtyObjectsBuffer.Clear();
PendingDirtyObjectsBuffer.Clear();
ObjectsBuffer.Data.EnsureCapacity(Objects.Count() * sizeof(Float4) * (GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE + 2 * GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE));
ObjectsListBuffer.Data.Resize(Objects.Count() * sizeof(uint32));
auto objectsListData = (uint32*)ObjectsListBuffer.Data.Get();
int32 dirtyTiles = 0, objectIndex = 0;
int32 dirtyObjectsLimitLeft = 50; // TODO: expose as scalability parameter
// TODO: sort dirt objects by size to collect biggest ones first
for (auto& e : Objects)
{
auto& object = e.Value;
if (object.Dirty && dirtyObjectsLimitLeft-- > 0)
// Collect dirty objects
if (object.Dirty)
{
// Collect dirty objects
object.LastFrameUpdated = CurrentFrame;
object.LightingUpdateFrame = CurrentFrame;
DirtyObjectsBuffer.Add(e.Key);
float priority = object.Radius;
GlobalSurfaceAtlasPendingDirtyObject pending = { e.Key, &object, priority };
if (dirtyObjectsLimitLeft-- > 0)
{
PendingDirtyObjectsBuffer.Add(pending);
}
else
{
// Not enough space to update all objects that have bigger radius (more visible)
// TODO: maybe use screen-size as better heuristic for which objects to update first
for (int32 i = 0; i < PendingDirtyObjectsBuffer.Count(); i++)
{
if (PendingDirtyObjectsBuffer[i].Priority < priority)
{
// TODO: this is wrong, we should collect all pending objects and sort them to pick top ones
PendingDirtyObjectsBuffer[i] = pending;
break;
}
}
}
}
if (!object.ObjectDataDirty)
@@ -624,6 +650,19 @@ public:
}
ZoneValue(dirtyTiles);
// Move pending dirty objects to be actually in a dirty buffer to redraw
DirtyObjectsBuffer.Clear();
DirtyObjectsBuffer.Resize(PendingDirtyObjectsBuffer.Count());
auto* dirtyObjectsBufferPtr = DirtyObjectsBuffer.Get();
const auto* pendingDirtyObjectsBufferPtr = PendingDirtyObjectsBuffer.Get();
for (int32 i = 0; i < DirtyObjectsBuffer.Count(); i++)
{
auto pending = pendingDirtyObjectsBufferPtr[i];
pending.Object->LastFrameUpdated = CurrentFrame;
pending.Object->LightingUpdateFrame = CurrentFrame;
dirtyObjectsBufferPtr[i] = pending.ActorObject;
}
#if 0
// Debug print objects buffer usage
uint32 freeObjectsBufferSlotsCount = 0;
@@ -1377,7 +1416,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co
data.GlobalSurfaceAtlas = result.Constants;
// Collect objects to update lighting this frame (dirty objects and dirty lights)
bool allLightingDirty = false;
bool allLightingDirty = GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_LIGHTS;
for (auto& light : renderContext.List->DirectionalLights)
{
GlobalSurfaceAtlasLight& lightData = surfaceAtlasData.Lights[light.ID];