Add recycling to CullingId for occlusion culling

This commit is contained in:
2026-09-03 11:48:18 +02:00
parent 5cb16f20ec
commit a2e2d54ee8
8 changed files with 103 additions and 22 deletions
+55 -5
View File
@@ -289,6 +289,7 @@ void RenderBuffers::Release()
if (auto* culling = FromInterface(OcclusionCulling))
Delete(culling);
OcclusionCulling = nullptr;
_statelessCulling = true;
RenderTargetPool::Release(VolumetricFog);
VolumetricFog = nullptr;
@@ -335,6 +336,7 @@ void RenderBuffers::OnRendering(const RenderContext& renderContext)
{
Delete(culling);
OcclusionCulling = nullptr;
_statelessCulling = true;
}
}
if (!OcclusionCulling && occlusionCullingTypeName.HasChars() && enableCulling)
@@ -349,6 +351,7 @@ void RenderBuffers::OnRendering(const RenderContext& renderContext)
LOG(Error, "Occlusion Culling system '{}' is unsupported", occlusionCullingTypeName.ToString());
return;
}
_statelessCulling = OcclusionCulling->IsStateless();
if (_usedCulling)
{
@@ -466,16 +469,36 @@ void RenderBuffers::OnSceneRenderingUpdateActor(SceneRendering* scene, int32 key
void RenderBuffers::OnSceneRenderingRemoveActor(SceneRendering* scene, int32 key, Actor* a)
{
// Skip actors that don't have nested sub-objects
if (!_cullingIdsOwnerTypes.Contains(a->GetTypeHandle()))
bool isCullingIdOwner = _cullingIdsOwnerTypes.Contains(a->GetTypeHandle());
if (_statelessCulling && !isCullingIdOwner)
return;
PROFILE_CPU();
if (auto* sceneData = Scenes.TryGet(scene))
{
for (auto it = sceneData->CullingIds.Begin(); it.IsNotEnd(); ++it)
// Check actors that have nested sub-objects
if (isCullingIdOwner)
{
if (it->Key.First == a)
for (auto it = sceneData->CullingIds.Begin(); it.IsNotEnd(); ++it)
{
sceneData->CullingIds.Remove(it);
if (it->Key.First == a)
{
if (!_statelessCulling && it->Value)
OcclusionCulling->FreeObject(it->Value);
sceneData->CullingIds.Remove(it);
}
}
}
// Check that actor
auto& list = sceneData->Geo[a->_drawCategory];
if (list.IsValidIndex(key) && !_statelessCulling)
{
auto& item = list.Get()[key];
if (item.CullingId)
{
OcclusionCulling->FreeObject(item.CullingId);
item.CullingId = 0;
}
}
}
@@ -483,5 +506,32 @@ void RenderBuffers::OnSceneRenderingRemoveActor(SceneRendering* scene, int32 key
void RenderBuffers::OnSceneRenderingClear(SceneRendering* scene)
{
PROFILE_CPU();
if (!_statelessCulling)
{
// Free culling ids
if (auto* sceneData = Scenes.TryGet(scene))
{
for (auto& e : sceneData->CullingIds)
{
if (e.Value)
OcclusionCulling->FreeObject(e.Value);
}
for (auto& list : sceneData->Geo)
{
for (auto& item : list)
{
if (item.CullingId)
{
OcclusionCulling->FreeObject(item.CullingId);
item.CullingId = 0;
}
}
}
}
else
return;
}
Scenes.Remove(scene);
}
+1
View File
@@ -83,6 +83,7 @@ protected:
bool _useAlpha = false;
bool _useNull = false;
bool _usedCulling = false;
bool _statelessCulling = true;
Viewport _viewport;
Array<GPUTexture*, FixedAllocation<32>> _resources;
CriticalSection _cullingLocker;
@@ -38,6 +38,11 @@ bool HZBOcclusionCulling::IsSupported()
return limits.HasCompute;
}
bool HZBOcclusionCulling::IsStateless()
{
return false;
}
void HZBOcclusionCulling::BeginFrame(const RenderContext& renderContext)
{
PROFILE_CPU();
@@ -229,6 +234,11 @@ void HZBOcclusionCulling::Submit(const RenderContext& renderContext)
context->SetViewportAndScissors(renderContext.Buffers->GetViewport());
}
void HZBOcclusionCulling::FreeObject(uint32 cullingId)
{
_items.FreeObject(cullingId);
}
bool HZBOcclusionCulling::IsVisible(const BoundingBox& bounds, uint32& cullingId)
{
return IsVisible(bounds, cullingId, nullptr);
@@ -50,9 +50,11 @@ private:
public:
// [IOcclusionCulling]
bool IsSupported() override;
bool IsStateless() override;
void BeginFrame(const RenderContext& renderContext) override;
void EndFrame(const RenderContext& renderContext) override;
void Submit(const RenderContext& renderContext) override;
void FreeObject(uint32 cullingId) override;
bool IsVisible(const BoundingBox& bounds, uint32& cullingId) override;
bool IsVisible(const BoundingBox& bounds, GeometryDrawState& drawState) override;
@@ -34,6 +34,11 @@ HardwareOcclusionCulling::~HardwareOcclusionCulling()
#endif
}
bool HardwareOcclusionCulling::IsStateless()
{
return false;
}
void HardwareOcclusionCulling::BeginFrame(const RenderContext& renderContext)
{
PROFILE_CPU();
@@ -214,6 +219,11 @@ void HardwareOcclusionCulling::Submit(const RenderContext& renderContext)
#endif
}
void HardwareOcclusionCulling::FreeObject(uint32 cullingId)
{
_items.FreeObject(cullingId);
}
bool HardwareOcclusionCulling::IsVisible(const BoundingBox& bounds, uint32& cullingId)
{
return IsVisible(bounds, cullingId, nullptr);
@@ -49,9 +49,11 @@ private:
public:
// [IOcclusionCulling]
bool IsStateless() override;
void BeginFrame(const RenderContext& renderContext) override;
void EndFrame(const RenderContext& renderContext) override;
void Submit(const RenderContext& renderContext) override;
void FreeObject(uint32 cullingId) override;
bool IsVisible(const BoundingBox& bounds, uint32& cullingId) override;
bool IsVisible(const BoundingBox& bounds, GeometryDrawState& drawState) override;
@@ -23,6 +23,11 @@ API_INTERFACE() class FLAXENGINE_API IOcclusionCulling
/// </summary>
virtual bool IsSupported() { return true; }
/// <summary>
/// Checks if the culling system doesn't contain any state (eg. visibility resolved on GPU) related to culled objects. Otherwise, the culling system may maintain some state between frames (eg. visibility results from previous frames) to perform the culling operations thus will receive FreeObject.
/// </summary>
virtual bool IsStateless() { return true; }
/// <summary>
/// Frame begin event. Called before the drawing to prepare the culling system for the new frame.
/// </summary>
@@ -38,6 +43,12 @@ API_INTERFACE() class FLAXENGINE_API IOcclusionCulling
/// </summary>
virtual void EndFrame(const RenderContext& renderContext) {}
/// <summary>
/// Frees specific object from the culling system. Called when the object is removed from the scene or destroyed. Can be used to free any state related to the object (eg. visibility results from previous frames).
/// </summary>
/// <remarks>Used only when IsStateless returns true (as optimization).</remarks>
virtual void FreeObject(uint32 cullingId) {}
/// <summary>
/// Object bounds visibility check. Returns true if the object is visible (not occluded by other geometry).
/// Works only for CPU-side culling (or with delayed GPU-readback).
@@ -24,6 +24,7 @@ private:
volatile int64 _freeItemsCount = 0;
volatile int64 _newItemsCount = 0;
Array<uint32> _freeItems;
Array<uint32> _freedItems;
public:
void BeginFrame()
@@ -31,23 +32,9 @@ public:
// Remove used free items
_freeItems.Resize(Math::Max((int32)_freeItemsCount, 0));
#if 0 // TODO: find a different way as there might be some invisible object with CullingId assigned and drawing it later will overlap with reused IDs
// Trim history
constexpr int32 frameTTL = 20;
if (_frameCounter % 10 == 0 && _frameCounter > frameTTL)
{
const int32 lastFrame = _frameCounter - frameTTL;
for (int32 i = 0; i < this->Count(); i++)
{
auto& item = this->Get()[i];
if (item.LastUsedFrame && item.LastUsedFrame < lastFrame)
{
Platform::MemoryClear(&item, sizeof(item));
_freeItems.Add(i);
}
}
}
#endif
// Put back freed items to the free list
_freeItems.Add(_freedItems);
_freedItems.Clear();
// Allocate new items (as requested during the previous frame)
if (_newItemsCount > 0)
@@ -88,4 +75,12 @@ public:
return false;
}
void FreeObject(uint32 cullingId)
{
ASSERT(cullingId > 0 && cullingId < (uint32)this->Count());
_freedItems.Add(cullingId);
auto& item = this->Get()[cullingId];
Platform::MemoryClear(&item, sizeof(item));
}
};