// Copyright Ngonart OU. All Rights Reserved. // UE5 Modular Game Framework — SS_PlanarCaptureManager (138) // Global budget manager for all planar capture surfaces in the world. // // One instance per World (World Subsystem). Each frame, scores every registered // capture surface by distance, screen coverage, facing angle, and scripted priority. // Assigns quality tiers across all surfaces respecting a global budget // (max simultaneous high-quality captures, max total render target memory). // Forces idle/disabled state on surfaces outside active rooms/sublevels. // // Also manages the render target pool — allocates, reuses, and resizes RTs // to minimize memory churn. #pragma once #include "CoreMinimal.h" #include "Subsystems/WorldSubsystem.h" #include "Capture/PlanarCaptureCommon.h" #include "SS_PlanarCaptureManager.generated.h" class ABP_PlanarCaptureActor; class UBPC_PlanarCapture; // Delegates DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSurfaceRegistered, ABP_PlanarCaptureActor*, Surface, int32, TotalSurfaces); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSurfaceUnregistered, ABP_PlanarCaptureActor*, Surface, int32, TotalSurfaces); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGlobalQualityCapChanged, EPlanarCaptureQualityTier, NewCap); /** * SS_PlanarCaptureManager — Global Planar Capture Budget Manager. * * Manages all ABP_PlanarCaptureActor instances per world. Evaluates priority * and assigns quality tiers to stay within a configurable budget. * Also owns the render target pool to reduce memory allocation overhead. * * Multiplayer: This subsystem exists on both server and clients. * On the server, it tracks surfaces for replication state. * On clients, it drives actual capture rendering. */ UCLASS() class PG_FRAMEWORK_API USS_PlanarCaptureManager : public UWorldSubsystem { GENERATED_BODY() public: USS_PlanarCaptureManager(); // ======================================================================== // Lifecycle // ======================================================================== virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Deinitialize() override; virtual void Tick(float DeltaTime) override; virtual TStatId GetStatId() const override; // ======================================================================== // Surface Registry // ======================================================================== /** * Register a capture surface actor with the global manager. * Called by ABP_PlanarCaptureActor::BeginPlay. */ UFUNCTION(BlueprintCallable, Category = "Capture|Manager") void RegisterSurface(ABP_PlanarCaptureActor* Surface); /** * Unregister a capture surface actor. Called on EndPlay. */ UFUNCTION(BlueprintCallable, Category = "Capture|Manager") void UnregisterSurface(ABP_PlanarCaptureActor* Surface); /** * Get all currently registered surfaces. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|Manager") TArray GetRegisteredSurfaces() const; /** * Get the number of registered surfaces. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|Manager") int32 GetSurfaceCount() const { return RegisteredSurfaces.Num(); } // ======================================================================== // Quality Budget Management // ======================================================================== /** * Global quality ceiling — caps all surfaces at this tier regardless of score. * Use this for lower-end hardware targets. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget") EPlanarCaptureQualityTier GlobalQualityCap = EPlanarCaptureQualityTier::High; /** * Maximum number of simultaneous Hero-tier captures. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget") int32 MaxHeroSurfaces = 1; /** * Maximum number of simultaneous High-tier captures. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget") int32 MaxHighSurfaces = 3; /** * Maximum number of simultaneous Medium-tier captures. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget") int32 MaxMediumSurfaces = 6; /** * Maximum total render target memory in megabytes across all surfaces. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget") float MaxTotalRenderTargetMemoryMB = 128.0f; /** * Distance at which capture quality drops to Off (world units). */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget") float MaxCaptureDistance = 10000.0f; /** * Interval between full re-evaluation of all surfaces (seconds). * Individual surfaces check their own interval every frame via their component. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Capture|Manager|Budget") float FullEvaluationInterval = 0.5f; // ======================================================================== // Render Target Pool // ======================================================================== /** * Request a render target from the pool. Returns nullptr if none available. */ UTextureRenderTarget2D* RequestRenderTarget(int32 Size); /** * Release a render target back to the pool. */ void ReleaseRenderTarget(UTextureRenderTarget2D* RenderTarget); /** * Get the total memory used by the render target pool (in MB). */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|Manager") float GetPoolMemoryUsageMB() const; // ======================================================================== // Query // ======================================================================== /** * Get the nearest capture surface of a given mode to a world location. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Capture|Manager") ABP_PlanarCaptureActor* GetNearestSurfaceOfMode( EPlanarCaptureMode Mode, FVector WorldLocation, float MaxDistance = 0.0f) const; /** * Force all surfaces to a specific quality tier (e.g., for cutscenes). */ UFUNCTION(BlueprintCallable, Category = "Capture|Manager") void ForceAllSurfacesToTier(EPlanarCaptureQualityTier Tier); /** * Release the force-tier override and resume normal scoring. */ UFUNCTION(BlueprintCallable, Category = "Capture|Manager") void ReleaseForceTier(); // ======================================================================== // Event Dispatchers // ======================================================================== UPROPERTY(BlueprintAssignable, Category = "Capture|Manager|Events") FOnSurfaceRegistered OnSurfaceRegistered; UPROPERTY(BlueprintAssignable, Category = "Capture|Manager|Events") FOnSurfaceUnregistered OnSurfaceUnregistered; UPROPERTY(BlueprintAssignable, Category = "Capture|Manager|Events") FOnGlobalQualityCapChanged OnGlobalQualityCapChanged; protected: // ======================================================================== // Internal State // ======================================================================== /** All registered capture surface actors. */ UPROPERTY() TArray> RegisteredSurfaces; /** Render target pool. */ TArray RenderTargetPool; /** Time accumulator for full re-evaluation interval. */ float TimeSinceLastEvaluation = 0.0f; /** Force-tier override — if set, all surfaces use this tier. */ TOptional ForceTierOverride; /** Count of surfaces at each tier (tracked for budget enforcement). */ TMap TierAssignmentCounts; // ======================================================================== // Internal Methods // ======================================================================== /** Evaluate all registered surfaces and assign quality tiers. */ void EvaluateAllSurfaces(); /** * Score a single surface and determine its quality tier within budget constraints. * @return The assigned tier. */ EPlanarCaptureQualityTier ScoreAndAssignTier(UBPC_PlanarCapture* Capture); /** Enforce budget limits — demote lower-priority surfaces if budget exceeded. */ void EnforceBudgetLimits(); /** Create a new render target of the given size. */ UTextureRenderTarget2D* CreateRenderTarget(int32 Size); /** Get or create a render target for a given size (first checks pool). */ UTextureRenderTarget2D* GetOrCreateRenderTarget(int32 Size); };