435 lines
11 KiB
C++
435 lines
11 KiB
C++
// Copyright Ngonart OU. All Rights Reserved.
|
|
// UE5 Modular Game Framework — SS_PlanarCaptureManager implementation
|
|
|
|
#include "Capture/SS_PlanarCaptureManager.h"
|
|
#include "Capture/BP_PlanarCaptureActor.h"
|
|
#include "Capture/BPC_PlanarCapture.h"
|
|
#include "Engine/TextureRenderTarget2D.h"
|
|
#include "Engine/World.h"
|
|
#include "GameFramework/PlayerController.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
|
|
USS_PlanarCaptureManager::USS_PlanarCaptureManager()
|
|
{
|
|
}
|
|
|
|
void USS_PlanarCaptureManager::Initialize(FSubsystemCollectionBase& Collection)
|
|
{
|
|
Super::Initialize(Collection);
|
|
|
|
UE_LOG(LogTemp, Log, TEXT("SS_PlanarCaptureManager: Initialized for world."));
|
|
}
|
|
|
|
void USS_PlanarCaptureManager::Deinitialize()
|
|
{
|
|
// Release all render targets
|
|
for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
|
|
{
|
|
if (Entry.RenderTarget)
|
|
{
|
|
Entry.RenderTarget->ConditionalBeginDestroy();
|
|
}
|
|
}
|
|
RenderTargetPool.Empty();
|
|
|
|
RegisteredSurfaces.Empty();
|
|
|
|
Super::Deinitialize();
|
|
}
|
|
|
|
void USS_PlanarCaptureManager::Tick(float DeltaTime)
|
|
{
|
|
Super::Tick(DeltaTime);
|
|
|
|
TimeSinceLastEvaluation += DeltaTime;
|
|
|
|
if (TimeSinceLastEvaluation >= FullEvaluationInterval)
|
|
{
|
|
TimeSinceLastEvaluation = 0.0f;
|
|
EvaluateAllSurfaces();
|
|
}
|
|
}
|
|
|
|
TStatId USS_PlanarCaptureManager::GetStatId() const
|
|
{
|
|
RETURN_QUICK_DECLARE_CYCLE_STAT(USS_PlanarCaptureManager, STATGROUP_Tickables);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Surface Registry
|
|
// ========================================================================
|
|
|
|
void USS_PlanarCaptureManager::RegisterSurface(ABP_PlanarCaptureActor* Surface)
|
|
{
|
|
if (!Surface)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check for duplicates
|
|
for (const TWeakObjectPtr<ABP_PlanarCaptureActor>& Existing : RegisteredSurfaces)
|
|
{
|
|
if (Existing.Get() == Surface)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("SS_PlanarCaptureManager: Surface '%s' already registered."),
|
|
*Surface->SurfaceDisplayName);
|
|
return;
|
|
}
|
|
}
|
|
|
|
RegisteredSurfaces.Add(Surface);
|
|
UE_LOG(LogTemp, Log, TEXT("SS_PlanarCaptureManager: Registered surface '%s'. Total: %d"),
|
|
*Surface->SurfaceDisplayName, RegisteredSurfaces.Num());
|
|
|
|
OnSurfaceRegistered.Broadcast(Surface, RegisteredSurfaces.Num());
|
|
|
|
// Evaluate immediately to assign initial tier
|
|
EvaluateAllSurfaces();
|
|
}
|
|
|
|
void USS_PlanarCaptureManager::UnregisterSurface(ABP_PlanarCaptureActor* Surface)
|
|
{
|
|
if (!Surface)
|
|
{
|
|
return;
|
|
}
|
|
|
|
RegisteredSurfaces.RemoveAll([Surface](const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry)
|
|
{
|
|
return Entry.Get() == Surface;
|
|
});
|
|
|
|
UE_LOG(LogTemp, Log, TEXT("SS_PlanarCaptureManager: Unregistered surface '%s'. Total: %d"),
|
|
*Surface->SurfaceDisplayName, RegisteredSurfaces.Num());
|
|
|
|
OnSurfaceUnregistered.Broadcast(Surface, RegisteredSurfaces.Num());
|
|
}
|
|
|
|
TArray<ABP_PlanarCaptureActor*> USS_PlanarCaptureManager::GetRegisteredSurfaces() const
|
|
{
|
|
TArray<ABP_PlanarCaptureActor*> Result;
|
|
for (const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry : RegisteredSurfaces)
|
|
{
|
|
if (Entry.IsValid())
|
|
{
|
|
Result.Add(Entry.Get());
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
// ========================================================================
|
|
// Quality Budget Management
|
|
// ========================================================================
|
|
|
|
void USS_PlanarCaptureManager::ForceAllSurfacesToTier(EPlanarCaptureQualityTier Tier)
|
|
{
|
|
ForceTierOverride = Tier;
|
|
|
|
for (TWeakObjectPtr<ABP_PlanarCaptureActor>& SurfaceWeak : RegisteredSurfaces)
|
|
{
|
|
if (ABP_PlanarCaptureActor* Surface = SurfaceWeak.Get())
|
|
{
|
|
if (UBPC_PlanarCapture* Capture = Surface->CaptureComponent)
|
|
{
|
|
Capture->ApplyQualityTier(Tier);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void USS_PlanarCaptureManager::ReleaseForceTier()
|
|
{
|
|
ForceTierOverride.Reset();
|
|
EvaluateAllSurfaces();
|
|
}
|
|
|
|
// ========================================================================
|
|
// Render Target Pool
|
|
// ========================================================================
|
|
|
|
UTextureRenderTarget2D* USS_PlanarCaptureManager::RequestRenderTarget(int32 Size)
|
|
{
|
|
// Check pool for an available RT of the right size
|
|
for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
|
|
{
|
|
if (!Entry.bInUse && Entry.CurrentSize == Size && Entry.RenderTarget)
|
|
{
|
|
Entry.bInUse = true;
|
|
return Entry.RenderTarget;
|
|
}
|
|
}
|
|
|
|
// Create a new one
|
|
return CreateRenderTarget(Size);
|
|
}
|
|
|
|
void USS_PlanarCaptureManager::ReleaseRenderTarget(UTextureRenderTarget2D* RenderTarget)
|
|
{
|
|
if (!RenderTarget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Find the entry and mark as free
|
|
for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
|
|
{
|
|
if (Entry.RenderTarget == RenderTarget)
|
|
{
|
|
Entry.bInUse = false;
|
|
Entry.OwningSurface.Reset();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Not in pool — add it
|
|
FPlanarCaptureRenderTargetEntry NewEntry;
|
|
NewEntry.RenderTarget = RenderTarget;
|
|
NewEntry.CurrentSize = RenderTarget->SizeX;
|
|
NewEntry.bInUse = false;
|
|
RenderTargetPool.Add(NewEntry);
|
|
}
|
|
|
|
float USS_PlanarCaptureManager::GetPoolMemoryUsageMB() const
|
|
{
|
|
float TotalBytes = 0.0f;
|
|
for (const FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
|
|
{
|
|
if (Entry.RenderTarget)
|
|
{
|
|
// RGBA8 = 4 bytes per pixel
|
|
TotalBytes += static_cast<float>(Entry.CurrentSize * Entry.CurrentSize * 4);
|
|
}
|
|
}
|
|
return TotalBytes / (1024.0f * 1024.0f);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Query
|
|
// ========================================================================
|
|
|
|
ABP_PlanarCaptureActor* USS_PlanarCaptureManager::GetNearestSurfaceOfMode(
|
|
EPlanarCaptureMode Mode, FVector WorldLocation, float MaxDistance) const
|
|
{
|
|
ABP_PlanarCaptureActor* Nearest = nullptr;
|
|
float NearestDistSq = (MaxDistance > 0.0f) ? (MaxDistance * MaxDistance) : FLT_MAX;
|
|
|
|
for (const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry : RegisteredSurfaces)
|
|
{
|
|
if (ABP_PlanarCaptureActor* Surface = Entry.Get())
|
|
{
|
|
if (!Surface->CaptureComponent || Surface->CaptureComponent->CaptureMode != Mode)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const float DistSq = FVector::DistSquared(Surface->GetActorLocation(), WorldLocation);
|
|
if (DistSq < NearestDistSq)
|
|
{
|
|
NearestDistSq = DistSq;
|
|
Nearest = Surface;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Nearest;
|
|
}
|
|
|
|
// ========================================================================
|
|
// Internal Methods
|
|
// ========================================================================
|
|
|
|
void USS_PlanarCaptureManager::EvaluateAllSurfaces()
|
|
{
|
|
// Reset tier counts
|
|
TierAssignmentCounts.Empty();
|
|
|
|
// Ensure registered surfaces are still valid
|
|
RegisteredSurfaces.RemoveAll([](const TWeakObjectPtr<ABP_PlanarCaptureActor>& Entry)
|
|
{
|
|
return !Entry.IsValid();
|
|
});
|
|
|
|
// If force tier override is active, apply it to all
|
|
if (ForceTierOverride.IsSet())
|
|
{
|
|
ForceAllSurfacesToTier(ForceTierOverride.GetValue());
|
|
return;
|
|
}
|
|
|
|
// Score and assign tiers
|
|
TArray<TPair<ABP_PlanarCaptureActor*, float>> ScoredSurfaces;
|
|
|
|
for (TWeakObjectPtr<ABP_PlanarCaptureActor>& SurfaceWeak : RegisteredSurfaces)
|
|
{
|
|
if (ABP_PlanarCaptureActor* Surface = SurfaceWeak.Get())
|
|
{
|
|
if (!Surface->bIsActive || !Surface->CaptureComponent)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UBPC_PlanarCapture* Capture = Surface->CaptureComponent;
|
|
FPlanarCaptureScore Score = Capture->GetCurrentScore();
|
|
ScoredSurfaces.Add(TPair<ABP_PlanarCaptureActor*, float>(Surface, Score.CompositeScore));
|
|
}
|
|
}
|
|
|
|
// Sort by score descending (highest score first)
|
|
ScoredSurfaces.Sort([](const TPair<ABP_PlanarCaptureActor*, float>& A,
|
|
const TPair<ABP_PlanarCaptureActor*, float>& B)
|
|
{
|
|
return A.Value > B.Value;
|
|
});
|
|
|
|
// Assign tiers within budget
|
|
for (const auto& Pair : ScoredSurfaces)
|
|
{
|
|
ABP_PlanarCaptureActor* Surface = Pair.Key;
|
|
float Score = Pair.Value;
|
|
|
|
if (!Surface->CaptureComponent)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
EPlanarCaptureQualityTier AssignedTier = ScoreAndAssignTier(Surface->CaptureComponent);
|
|
|
|
// Apply tier
|
|
Surface->CaptureComponent->ApplyQualityTier(AssignedTier);
|
|
}
|
|
}
|
|
|
|
EPlanarCaptureQualityTier USS_PlanarCaptureManager::ScoreAndAssignTier(UBPC_PlanarCapture* Capture)
|
|
{
|
|
if (!Capture)
|
|
{
|
|
return EPlanarCaptureQualityTier::Off;
|
|
}
|
|
|
|
const FPlanarCaptureScore Score = Capture->GetCurrentScore();
|
|
|
|
// If not visible, off
|
|
if (!Score.bInFrustum && Score.CompositeScore < 0.01f)
|
|
{
|
|
return EPlanarCaptureQualityTier::Off;
|
|
}
|
|
|
|
// If too far, off
|
|
if (Score.DistanceToViewer > MaxCaptureDistance)
|
|
{
|
|
return EPlanarCaptureQualityTier::Off;
|
|
}
|
|
|
|
// Determine natural tier based on composite score
|
|
EPlanarCaptureQualityTier NaturalTier;
|
|
|
|
if (Score.CompositeScore >= 0.8f)
|
|
{
|
|
NaturalTier = EPlanarCaptureQualityTier::Hero;
|
|
}
|
|
else if (Score.CompositeScore >= 0.5f)
|
|
{
|
|
NaturalTier = EPlanarCaptureQualityTier::High;
|
|
}
|
|
else if (Score.CompositeScore >= 0.2f)
|
|
{
|
|
NaturalTier = EPlanarCaptureQualityTier::Medium;
|
|
}
|
|
else
|
|
{
|
|
NaturalTier = EPlanarCaptureQualityTier::Low;
|
|
}
|
|
|
|
// Apply global quality cap
|
|
if (static_cast<int32>(NaturalTier) > static_cast<int32>(GlobalQualityCap))
|
|
{
|
|
NaturalTier = GlobalQualityCap;
|
|
}
|
|
|
|
// Check budget limits
|
|
const int32 CurrentCount = TierAssignmentCounts.FindRef(NaturalTier);
|
|
|
|
switch (NaturalTier)
|
|
{
|
|
case EPlanarCaptureQualityTier::Hero:
|
|
if (CurrentCount >= MaxHeroSurfaces)
|
|
{
|
|
NaturalTier = EPlanarCaptureQualityTier::High;
|
|
}
|
|
break;
|
|
case EPlanarCaptureQualityTier::High:
|
|
if (CurrentCount >= MaxHighSurfaces)
|
|
{
|
|
NaturalTier = EPlanarCaptureQualityTier::Medium;
|
|
}
|
|
break;
|
|
case EPlanarCaptureQualityTier::Medium:
|
|
if (CurrentCount >= MaxMediumSurfaces)
|
|
{
|
|
NaturalTier = EPlanarCaptureQualityTier::Low;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Increment the assigned tier count
|
|
TierAssignmentCounts.FindOrAdd(NaturalTier)++;
|
|
|
|
return NaturalTier;
|
|
}
|
|
|
|
void USS_PlanarCaptureManager::EnforceBudgetLimits()
|
|
{
|
|
// Additional enforcement for total memory budget
|
|
float TotalMemoryMB = GetPoolMemoryUsageMB();
|
|
|
|
if (TotalMemoryMB > MaxTotalRenderTargetMemoryMB)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("SS_PlanarCaptureManager: Render target memory budget exceeded (%.2f MB / %.2f MB)"),
|
|
TotalMemoryMB, MaxTotalRenderTargetMemoryMB);
|
|
|
|
// If over budget, demote lowest-priority surfaces
|
|
// Future: implement more sophisticated memory budget enforcement
|
|
}
|
|
}
|
|
|
|
UTextureRenderTarget2D* USS_PlanarCaptureManager::CreateRenderTarget(int32 Size)
|
|
{
|
|
UTextureRenderTarget2D* RT = NewObject<UTextureRenderTarget2D>(this);
|
|
if (!RT)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
RT->InitCustomFormat(Size, Size, PF_B8G8R8A8, false);
|
|
RT->RenderTargetFormat = ETextureRenderTargetFormat::RTF_RGBA8;
|
|
RT->bGPUSharedFlag = false;
|
|
RT->ClearColor = FLinearColor::Black;
|
|
RT->UpdateResourceImmediate(true);
|
|
|
|
FPlanarCaptureRenderTargetEntry Entry;
|
|
Entry.RenderTarget = RT;
|
|
Entry.CurrentSize = Size;
|
|
Entry.bInUse = true;
|
|
RenderTargetPool.Add(Entry);
|
|
|
|
return RT;
|
|
}
|
|
|
|
UTextureRenderTarget2D* USS_PlanarCaptureManager::GetOrCreateRenderTarget(int32 Size)
|
|
{
|
|
// Check pool first
|
|
for (FPlanarCaptureRenderTargetEntry& Entry : RenderTargetPool)
|
|
{
|
|
if (!Entry.bInUse && Entry.CurrentSize == Size && Entry.RenderTarget)
|
|
{
|
|
Entry.bInUse = true;
|
|
return Entry.RenderTarget;
|
|
}
|
|
}
|
|
|
|
return CreateRenderTarget(Size);
|
|
}
|