From a65eb6464544c01eeeb345caeed97e76412479b4 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Tue, 2 Aug 2022 21:24:33 +1000 Subject: [PATCH] Make occlusion culling work in UE 4.26. By using an explicit struct to shuttle the necessary data, rather than trying to copy an engine data structure. --- CHANGES.md | 6 ++ .../Private/CesiumViewExtension.cpp | 63 +++++++++---------- .../Private/CesiumViewExtension.h | 56 +++++++++++++++-- 3 files changed, 88 insertions(+), 37 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c0d788415..6dc2b3987 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Change Log +### ? - ? + +##### Fixes :wrench: + +- Fixed a bug that caused a crash in Unreal Engine 4.26 when enabling the experimental tileset occlusion culling feature. + ### v1.16.1 - 2022-08-01 ##### Fixes :wrench: diff --git a/Source/CesiumRuntime/Private/CesiumViewExtension.cpp b/Source/CesiumRuntime/Private/CesiumViewExtension.cpp index 5d8b1b8e0..26711fd9b 100644 --- a/Source/CesiumRuntime/Private/CesiumViewExtension.cpp +++ b/Source/CesiumRuntime/Private/CesiumViewExtension.cpp @@ -23,20 +23,20 @@ TileOcclusionState CesiumViewExtension::getPrimitiveOcclusionState( for (const SceneViewOcclusionResults& viewOcclusionResults : _currentOcclusionResults.occlusionResultsByView) { - const FPrimitiveOcclusionHistory* pHistory = - viewOcclusionResults.PrimitiveOcclusionHistorySet.Find( - FPrimitiveOcclusionHistoryKey(id, 0)); + const PrimitiveOcclusionResult* pOcclusionResult = + viewOcclusionResults.PrimitiveOcclusionResults.Find(id); - if (pHistory && pHistory->LastConsideredTime >= frameTimeCutoff) { - if (!pHistory->OcclusionStateWasDefiniteLastFrame) { + if (pOcclusionResult && + pOcclusionResult->LastConsideredTime >= frameTimeCutoff) { + if (!pOcclusionResult->OcclusionStateWasDefiniteLastFrame) { return TileOcclusionState::OcclusionUnavailable; } if (previouslyOccluded) { - if (pHistory->LastPixelsPercentage > 0.01f) { + if (pOcclusionResult->LastPixelsPercentage > 0.01f) { return TileOcclusionState::NotOccluded; } - } else if (!pHistory->WasOccludedLastFrame) { + } else if (!pOcclusionResult->WasOccludedLastFrame) { return TileOcclusionState::NotOccluded; } @@ -71,9 +71,9 @@ void CesiumViewExtension::BeginRenderViewFamily( // Recycle the current occlusion results. for (SceneViewOcclusionResults& occlusionResults : _currentOcclusionResults.occlusionResultsByView) { - occlusionResults.PrimitiveOcclusionHistorySet.Reset(); - _recycledOcclusionHistorySets.Enqueue( - std::move(occlusionResults.PrimitiveOcclusionHistorySet)); + occlusionResults.PrimitiveOcclusionResults.Reset(); + _recycledOcclusionResultSets.Enqueue( + std::move(occlusionResults.PrimitiveOcclusionResults)); } _currentOcclusionResults = {}; @@ -121,19 +121,21 @@ void CesiumViewExtension::PostRenderViewFamily_RenderThread( // Do we actually need the view? occlusionResults.pView = pView; - if (!_recycledOcclusionHistorySets.IsEmpty()) { + if (!_recycledOcclusionResultSets.IsEmpty()) { // Recycle a previously allocated occlusion history set, if one is // available. - occlusionResults.PrimitiveOcclusionHistorySet = - std::move(*_recycledOcclusionHistorySets.Peek()); - _recycledOcclusionHistorySets.Pop(); - occlusionResults.PrimitiveOcclusionHistorySet.Append( - pViewState->PrimitiveOcclusionHistorySet); + occlusionResults.PrimitiveOcclusionResults = + std::move(*_recycledOcclusionResultSets.Peek()); + _recycledOcclusionResultSets.Pop(); } else { // If no previously-allocated set exists, just allocate a new one. It // will be recycled later. - occlusionResults.PrimitiveOcclusionHistorySet = - pViewState->PrimitiveOcclusionHistorySet; + } + + occlusionResults.PrimitiveOcclusionResults.Reserve( + pViewState->PrimitiveOcclusionHistorySet.Num()); + for (const auto& element : pViewState->PrimitiveOcclusionHistorySet) { + occlusionResults.PrimitiveOcclusionResults.Emplace(element); } // Unreal will not execute occlusion queries that get frustum culled in a @@ -147,7 +149,7 @@ void CesiumViewExtension::PostRenderViewFamily_RenderThread( if (pView->bIsViewInfo && pScene != nullptr) { const FViewInfo* pViewInfo = static_cast(pView); const FSceneBitArray& visibility = pViewInfo->PrimitiveVisibilityMap; - auto& occlusion = occlusionResults.PrimitiveOcclusionHistorySet; + auto& occlusion = occlusionResults.PrimitiveOcclusionResults; const uint32 PrimitiveCount = pScene->Primitives.Num(); for (uint32 i = 0; i < PrimitiveCount; ++i) { @@ -159,21 +161,18 @@ void CesiumViewExtension::PostRenderViewFamily_RenderThread( if (pSceneInfo == nullptr) continue; - const FPrimitiveOcclusionHistory* pHistory = - occlusion.Find(FPrimitiveOcclusionHistoryKey( - pSceneInfo->PrimitiveComponentId, - 0)); - if (!pHistory || - pHistory->LastConsideredTime < pViewState->LastRenderTime) { + const PrimitiveOcclusionResult* pOcclusionResult = + occlusion.Find(pSceneInfo->PrimitiveComponentId); + if (!pOcclusionResult || pOcclusionResult->LastConsideredTime < + pViewState->LastRenderTime) { // No valid occlusion history for this culled primitive, so create // it. - FPrimitiveOcclusionHistory historyEntry{}; - historyEntry.PrimitiveId = pSceneInfo->PrimitiveComponentId; - historyEntry.LastConsideredTime = pViewState->LastRenderTime; - historyEntry.LastPixelsPercentage = 0.0f; - historyEntry.WasOccludedLastFrame = true; - historyEntry.OcclusionStateWasDefiniteLastFrame = true; - occlusion.Add(std::move(historyEntry)); + occlusion.Emplace(PrimitiveOcclusionResult( + pSceneInfo->PrimitiveComponentId, + pViewState->LastRenderTime, + 0.0f, + true, + true)); } } } diff --git a/Source/CesiumRuntime/Private/CesiumViewExtension.h b/Source/CesiumRuntime/Private/CesiumViewExtension.h index ad908edec..16645e97b 100644 --- a/Source/CesiumRuntime/Private/CesiumViewExtension.h +++ b/Source/CesiumRuntime/Private/CesiumViewExtension.h @@ -16,10 +16,56 @@ class ACesium3DTileset; class CesiumViewExtension : public FSceneViewExtensionBase { private: // Occlusion results for a single view. + struct PrimitiveOcclusionResult { + PrimitiveOcclusionResult( + const FPrimitiveComponentId primitiveId, + float lastConsideredTime, + float lastPixelsPercentage, + bool occlusionStateWasDefiniteLastFrame, + bool wasOccludedLastFrame) + : PrimitiveId(primitiveId), + LastConsideredTime(lastConsideredTime), + LastPixelsPercentage(lastPixelsPercentage), + OcclusionStateWasDefiniteLastFrame( + occlusionStateWasDefiniteLastFrame), + WasOccludedLastFrame(wasOccludedLastFrame) {} + + PrimitiveOcclusionResult(const FPrimitiveOcclusionHistory& renderer) + : PrimitiveId(renderer.PrimitiveId), + LastConsideredTime(renderer.LastConsideredTime), + LastPixelsPercentage(renderer.LastPixelsPercentage), + OcclusionStateWasDefiniteLastFrame( + renderer.OcclusionStateWasDefiniteLastFrame), + WasOccludedLastFrame(renderer.WasOccludedLastFrame) {} + + FPrimitiveComponentId PrimitiveId; + float LastConsideredTime; + float LastPixelsPercentage; + bool OcclusionStateWasDefiniteLastFrame; + bool WasOccludedLastFrame; + }; + + // Defines how PrimitiveOcclusionResult is stored in a TSet + struct PrimitiveOcclusionResultKeyFuncs + : BaseKeyFuncs { + typedef FPrimitiveComponentId KeyInitType; + + static KeyInitType GetSetKey(const PrimitiveOcclusionResult& Element) { + return Element.PrimitiveId; + } + + static bool Matches(KeyInitType A, KeyInitType B) { return A == B; } + + static uint32 GetKeyHash(KeyInitType Key) { + return GetTypeHash(Key.PrimIDValue); + } + }; + + // The occlusion results for a single view. struct SceneViewOcclusionResults { const FSceneView* pView = nullptr; - TSet - PrimitiveOcclusionHistorySet{}; + TSet + PrimitiveOcclusionResults{}; }; // A collection of occlusion results by view. @@ -35,13 +81,13 @@ class CesiumViewExtension : public FSceneViewExtensionBase { // thread. TQueue _occlusionResultsQueue; - // A queue to recycle the previously-allocated occlusion history sets. The + // A queue to recycle the previously-allocated occlusion result sets. The // game thread recycles the sets by moving them into the queue and sending // them back to the render thread. TQueue< - TSet, + TSet, EQueueMode::Spsc> - _recycledOcclusionHistorySets; + _recycledOcclusionResultSets; // The last known frame number. This is used to determine when an occlusion // results aggregation is complete.