diff --git a/Source/Engine/Graphics/RenderBuffers.cpp b/Source/Engine/Graphics/RenderBuffers.cpp index 0a6519ef0..8330aa7f8 100644 --- a/Source/Engine/Graphics/RenderBuffers.cpp +++ b/Source/Engine/Graphics/RenderBuffers.cpp @@ -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); } diff --git a/Source/Engine/Graphics/RenderBuffers.h b/Source/Engine/Graphics/RenderBuffers.h index f1c239e18..073b8ba7e 100644 --- a/Source/Engine/Graphics/RenderBuffers.h +++ b/Source/Engine/Graphics/RenderBuffers.h @@ -83,6 +83,7 @@ protected: bool _useAlpha = false; bool _useNull = false; bool _usedCulling = false; + bool _statelessCulling = true; Viewport _viewport; Array> _resources; CriticalSection _cullingLocker; diff --git a/Source/Engine/Renderer/Culling/HZBOcclusionCulling.cpp b/Source/Engine/Renderer/Culling/HZBOcclusionCulling.cpp index f2d1f3e2e..baeb55c5b 100644 --- a/Source/Engine/Renderer/Culling/HZBOcclusionCulling.cpp +++ b/Source/Engine/Renderer/Culling/HZBOcclusionCulling.cpp @@ -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); diff --git a/Source/Engine/Renderer/Culling/HZBOcclusionCulling.h b/Source/Engine/Renderer/Culling/HZBOcclusionCulling.h index 00583dbde..6254859b1 100644 --- a/Source/Engine/Renderer/Culling/HZBOcclusionCulling.h +++ b/Source/Engine/Renderer/Culling/HZBOcclusionCulling.h @@ -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; diff --git a/Source/Engine/Renderer/Culling/HardwareOcclusionCulling.cpp b/Source/Engine/Renderer/Culling/HardwareOcclusionCulling.cpp index 2aa270589..d2570b735 100644 --- a/Source/Engine/Renderer/Culling/HardwareOcclusionCulling.cpp +++ b/Source/Engine/Renderer/Culling/HardwareOcclusionCulling.cpp @@ -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); diff --git a/Source/Engine/Renderer/Culling/HardwareOcclusionCulling.h b/Source/Engine/Renderer/Culling/HardwareOcclusionCulling.h index 451c770e8..a96e80ecf 100644 --- a/Source/Engine/Renderer/Culling/HardwareOcclusionCulling.h +++ b/Source/Engine/Renderer/Culling/HardwareOcclusionCulling.h @@ -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; diff --git a/Source/Engine/Renderer/Culling/IOcclusionCulling.h b/Source/Engine/Renderer/Culling/IOcclusionCulling.h index 5af28679b..d9419c5b6 100644 --- a/Source/Engine/Renderer/Culling/IOcclusionCulling.h +++ b/Source/Engine/Renderer/Culling/IOcclusionCulling.h @@ -23,6 +23,11 @@ API_INTERFACE() class FLAXENGINE_API IOcclusionCulling /// virtual bool IsSupported() { return true; } + /// + /// 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. + /// + virtual bool IsStateless() { return true; } + /// /// Frame begin event. Called before the drawing to prepare the culling system for the new frame. /// @@ -38,6 +43,12 @@ API_INTERFACE() class FLAXENGINE_API IOcclusionCulling /// virtual void EndFrame(const RenderContext& renderContext) {} + /// + /// 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). + /// + /// Used only when IsStateless returns true (as optimization). + virtual void FreeObject(uint32 cullingId) {} + /// /// 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). diff --git a/Source/Engine/Renderer/Culling/OcclusionCullingTools.h b/Source/Engine/Renderer/Culling/OcclusionCullingTools.h index 5384653cf..e3bf9c219 100644 --- a/Source/Engine/Renderer/Culling/OcclusionCullingTools.h +++ b/Source/Engine/Renderer/Culling/OcclusionCullingTools.h @@ -24,6 +24,7 @@ private: volatile int64 _freeItemsCount = 0; volatile int64 _newItemsCount = 0; Array _freeItems; + Array _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)); + } };