Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 39 additions & 81 deletions Source/SoftwareOC/Private/OcclusionSceneView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ FOcclusionSceneViewExtension::~FOcclusionSceneViewExtension()
SceneSoftwareOcclusion->OcSubsystem = nullptr;
delete SceneSoftwareOcclusion;
}

OcSubsystem->CachedVisibilityMap.Empty();
OcSubsystem->CachedHiddenMap.Empty();

OcSubsystem->IDToMeshComp.Empty();
}

Expand Down Expand Up @@ -75,12 +73,7 @@ void FOcclusionSceneViewExtension::PostRenderBasePassDeferred_RenderThread(FRDGB

FScene* Scene = InView.ViewActor->GetWorld()->Scene->GetRenderScene();

if (!Scene)
{
return;
}

if(!Scene->World || !IsValid(Scene->World) || Scene->World->IsBeingCleanedUp() || Scene->World->HasAnyFlags(RF_MirroredGarbage) || Scene->World->HasAnyFlags(RF_BeginDestroyed))
if(!IsSceneWorldValid(Scene))
{
return;
}
Expand All @@ -94,30 +87,17 @@ void FOcclusionSceneViewExtension::PostRenderBasePassDeferred_RenderThread(FRDGB
return;
}

for (auto& Tuple : FrameResults->VisibilityMap)
{
OcSubsystem->CachedVisibilityMap.Add(Tuple.Key, Tuple.Value);
}

int NumOccluded = 0;

for (TObjectIterator<UMeshComponent> MeshIterator; MeshIterator; ++MeshIterator)
{
UMeshComponent* Component = *MeshIterator;
if (!IsValid(Component) || !Component->IsRegistered() || !Component->GetWorld() ||
Component->GetWorld()->WorldType == EWorldType::Editor || Component->GetWorld()->WorldType == EWorldType::Inactive ||
Component->GetWorld()->WorldType == EWorldType::Inactive || Component->GetWorld() != OcSubsystem->GetWorld())
if (!USoftwareOCSubsystem::CheckComponentValidWorld(Component, OcSubsystem) ||
!USoftwareOCSubsystem::CheckComponentNotBeingDestroyed(Component))
{
continue;
}

// Paranoid sanity checks.
if(Component->GetWorld()->IsBeingCleanedUp() || Component->GetWorld()->HasAnyFlags(RF_MirroredGarbage) ||
Component->GetWorld()->HasAnyFlags(RF_BeginDestroyed))
{
return;
}

// Now make sure that these components aren't marked to be ignored.
// If they are, don't bother with them (saves us time).
if(Component->bTreatAsBackgroundForOcclusion == 1 || (Component->SceneProxy && !Component->SceneProxy->CanBeOccluded()))
Expand All @@ -127,83 +107,55 @@ void FOcclusionSceneViewExtension::PostRenderBasePassDeferred_RenderThread(FRDGB

FPrimitiveComponentId ComponentId = Component->GetPrimitiveSceneId();

if(Component->HasAnyFlags(RF_ClassDefaultObject) || Component->HasAnyFlags(RF_MirroredGarbage) || Component->HasAnyFlags(RF_BeginDestroyed))
{
continue;
}

bool ObjectHidden = !FrameResults->IsVisible(ComponentId);

// Ensure that any values that aren't in for this frame, are checked against a cache.
if(OcSubsystem->CachedVisibilityMap.Contains(Component->GetPrimitiveSceneId()))

// This prevents us from making meshes visible that weren't in the visibility map
// as IsVisible will just return true if a ComponentID wasn't found in the visibility map.
if(!FrameResults->VisibilityMap.Contains(ComponentId))
{
ObjectHidden = !(*OcSubsystem->CachedVisibilityMap.Find(ComponentId));
ObjectHidden = Component->bHiddenInGame;
}

if(!ObjectHidden)
{
if(Component->GetWorld())
{
Component->GetSceneData().SetLastRenderTime(Component->GetWorld()->GetTimeSeconds(), true);
}
// This is to fix animations not playing in UE5.5 when occlusion culling is off.
Component->GetSceneData().SetLastRenderTime(Component->GetWorld()->GetTimeSeconds(), true);
}

AsyncTask(ENamedThreads::GameThread, [this, Scene, Component, ObjectHidden]()
// We shouldn't tell the GameThead to launch a task to set visibility if we haven't changed.
// This can result in a major optimisation if there are thousands of objects in a scene.
if(Component->bHiddenInGame != ObjectHidden)
{
// Objects can start to die in this frame (since it runs 1 frame later than the code outside of this task).
// We detect them here and stop processing them if so.
if(!Component || !Component->IsRegistered() || Component->HasAnyFlags(RF_MirroredGarbage) ||
Component->HasAnyFlags(RF_BeginDestroyed) || Component->IsBeingDestroyed())
{
return;
}

// Paranoid Sanity Check.
if(!Scene->World || !IsValid(Scene->World) || Scene->World->IsBeingCleanedUp() || Scene->World->HasAnyFlags(RF_MirroredGarbage) || Scene->World->HasAnyFlags(RF_BeginDestroyed))
{
return;
}

if(!IsValid(Component) || !Component->GetWorld())
{
return;
}

if(!OcSubsystem->CachedVisibilityMap.Contains(Component->GetPrimitiveSceneId()))
AsyncTask(ENamedThreads::GameThread, [this, Scene, Component, ObjectHidden]()
{
return;
}

if(Component->bHiddenInGame == *OcSubsystem->CachedVisibilityMap.Find(Component->GetPrimitiveSceneId()))
{
return;
}
// Objects can start to die in this frame (since it runs 1 frame later than the code outside of this task).
// We detect them here and stop processing them if so.
if (!USoftwareOCSubsystem::CheckComponentValidWorld(Component, OcSubsystem) ||
!USoftwareOCSubsystem::CheckComponentNotBeingDestroyed(Component))
{
return;
}

if(Component->bHiddenInGame == ObjectHidden)
{
return;
}
// Paranoid Sanity Check.
if(!IsSceneWorldValid(Scene))
{
return;
}

Component->SetHiddenInGame(ObjectHidden);
});
Component->SetHiddenInGame(ObjectHidden);
});
}

if(GSOVisualizeOccluded)
{
FColor OccludedColour = ObjectHidden ? FColor::Green : FColor::Red;

if(!FrameResults->VisibilityMap.Contains(ComponentId))
{
if(!OcSubsystem->CachedVisibilityMap.Contains(ComponentId))
{
OccludedColour = FColor::Yellow;
}
else
{
OccludedColour = ObjectHidden ? FColor::Magenta : FColor::Red;
}
OccludedColour = FColor::Yellow;
}

// DrawDebugBox throws an error if we try draw immediately. We should until the world has been alive
// DrawDebugBox throws an error if we try draw immediately. We should wait until the world has been alive
// for a bit to actually draw debug stuff.
if(Component->GetWorld() && Component->GetWorld()->GetRealTimeSeconds() >= 1)
{
Expand Down Expand Up @@ -238,3 +190,9 @@ void FOcclusionSceneViewExtension::PostRenderBasePassDeferred_RenderThread(FRDGB

SceneSoftwareOcclusion->DebugDraw(GraphBuilder, *View, Output, 20, 20);
}

bool FOcclusionSceneViewExtension::IsSceneWorldValid(const FScene* InScene)
{
return InScene && InScene->World && IsValid(InScene->World) && !InScene->World->IsBeingCleanedUp() && !InScene->World->HasAnyFlags(RF_BeginDestroyed) &&
InScene->World->HasAnyFlags(RF_WasLoaded);
}
60 changes: 37 additions & 23 deletions Source/SoftwareOC/Private/SoftwareOCSubsystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ void USoftwareOCSubsystem::Deinitialize()
{
Super::Deinitialize();

Reset();
}

void USoftwareOCSubsystem::Reset()
{
IDToMeshComp = {};

if(OcclusionSceneViewExtension)
Expand Down Expand Up @@ -59,44 +64,53 @@ void USoftwareOCSubsystem::Tick(float DeltaTime)
ForceUpdateMap();
}

bool USoftwareOCSubsystem::CheckComponentValidWorld(UMeshComponent* Component, UObject* Context)
{
return Component->IsValidLowLevel() && Context->IsValidLowLevel() &&
IsValid(Component) && IsValid(Context) &&
Component->IsRegistered() &&
Component->GetWorld() && Context->GetWorld() &&
Component->GetWorld()->WorldType != EWorldType::Editor &&
Component->GetWorld()->WorldType != EWorldType::Inactive &&
Component->GetWorld() == Context->GetWorld();
}

bool USoftwareOCSubsystem::CheckComponentNotBeingDestroyed(UMeshComponent* Component)
{
return IsValid(Component) && !Component->GetWorld()->IsBeingCleanedUp() &&
!Component->GetWorld()->HasAnyFlags(RF_BeginDestroyed) && Component->GetWorld()->HasAnyFlags(RF_WasLoaded);
}

void USoftwareOCSubsystem::ForceUpdateMap()
{
// Empty list just incase it's got dangling pointers (Shouldn't but never worth the risk).
IDToMeshComp.Empty();
if(!GetWorld() || !IsValid(GetWorld()))
{
return;
}

for(TObjectIterator<UMeshComponent> MeshItr; MeshItr; ++MeshItr)
{
UMeshComponent* Component = *MeshItr;
if (!IsValid(Component) || !Component->IsRegistered() || !Component->GetWorld() ||
Component->GetWorld()->WorldType == EWorldType::Editor || Component->GetWorld()->WorldType == EWorldType::Inactive ||
Component->GetWorld()->WorldType == EWorldType::Inactive || Component->GetWorld() != GetWorld())
if (!CheckComponentValidWorld(Component, this))
{
continue;
}

if (Component->HasAnyFlags(RF_ClassDefaultObject))
// Paranoid sanity checks.
if(!CheckComponentNotBeingDestroyed(Component))
{
continue;
}

IDToMeshComp.Add(Component->GetPrimitiveSceneId().PrimIDValue, Component);
}

// Clean up Cache maps, as they may not be valid and won't auto update (not marked as UPROPERTY and can't be).

TArray<FPrimitiveComponentId> IDsToRemove;

for(auto& Tuple : CachedVisibilityMap)
{
if(!IDToMeshComp.Contains(Tuple.Key.PrimIDValue))
// Now make sure that these components aren't marked to be ignored.
// If they are, don't bother with them (saves us time).
if(Component->bTreatAsBackgroundForOcclusion == 1 ||
(Component->SceneProxy && ( !Component->SceneProxy->CanBeOccluded() ||
!Component->SceneProxy->ShouldUseAsOccluder())))
{
IDsToRemove.Add(Tuple.Key);
continue;
}
}

for(FPrimitiveComponentId ID : IDsToRemove)
{
CachedVisibilityMap.Remove(ID);
CachedHiddenMap.Remove(ID);

IDToMeshComp.Add(Component->GetPrimitiveSceneId().PrimIDValue, Component);
}
}
Loading