Files
UE5-Modular-Game-Framework/Source/PG_Framework/Private/Capture/SS_PlanarCaptureManager.cpp

433 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)
{
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);
}