// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Containers/Array.h" #include "Containers/ContainerAllocationPolicies.h" #include "Containers/List.h" #include "CoreMinimal.h" #include "CoreTypes.h" #include "HAL/IConsoleManager.h" #include "Misc/AssertionMacros.h" #include "Stats/Stats.h" namespace DynamicRenderScaling { template class TMap; /* Model how the GPU costs of a budget scales with a ResolutionFraction. */ enum class EHeuristicModel { Unknown, // GPU cost scales linearly to the ResolutionFraction Linear, // GPU cost scales quadratically to the ResolutionFraction Quadratic, }; /* Represent an independent budget to dynamically scale by its own. */ struct FHeuristicSettings final { static constexpr float kDefaultMinResolutionFraction = 0.5f; static constexpr float kDefaultMaxResolutionFraction = 1.0f; static constexpr float kDefaultThrottlingMaxResolutionFraction = 0.0f; static constexpr float kBudgetMsDisabled = 0.0f; static constexpr float kDefaultChangeThreshold = 0.02f; static constexpr float kDefaultTargetedHeadRoom = 0.05f; static constexpr float kDefaultIncreaseAmortizationFactor = 0.9f; static constexpr int32 kDefaultFractionQuantization = 0; static constexpr int32 kDefaultUpperBoundQuantization = 0; EHeuristicModel Model = EHeuristicModel::Unknown; bool bModelScalesWithPrimaryScreenPercentage = false; float MinResolutionFraction = kDefaultMinResolutionFraction; float MaxResolutionFraction = kDefaultMaxResolutionFraction; float ThrottlingMaxResolutionFraction = kDefaultThrottlingMaxResolutionFraction; float BudgetMs = kBudgetMsDisabled; float ChangeThreshold = kDefaultChangeThreshold; float TargetedHeadRoom = kDefaultTargetedHeadRoom; float IncreaseAmortizationFactor = kDefaultIncreaseAmortizationFactor; int32 FractionQuantization = kDefaultFractionQuantization; int32 UpperBoundQuantization = kDefaultUpperBoundQuantization; /** Returns whether the heuristic is enabled or not. */ RENDERCORE_API bool IsEnabled() const; /** Returns the desired GPU cost to be targeted to have head room left to not go over budget. */ RENDERCORE_API float GetTargetedMs(float BudgetMs) const; /** Returns how much the GPU cost scales for a given ResolutionFraction. */ RENDERCORE_API float EstimateCostScale(float ResolutionFraction) const; /** Returns how much the ResolutionFraction should scale for a GPU timing to fit to target. */ RENDERCORE_API float EstimateResolutionFactor(float TargetMs, float TimingMs) const; /** Returns how much the GPU time should scale between two different resolution fraction. */ RENDERCORE_API float EstimateTimeFactor(float CurrentResolutionFraction, float NewResolutionFraction) const; /** Corrects new resolution fraction to ensure it's within bounds, honor AmortizationFactor and FractionQuantization. */ RENDERCORE_API float CorrectNewResolutionFraction(float CurrentResolutionFraction, float NewResolutionFraction, float ResolutionFractionScale) const; /** Returns whether the ResolutionFraction is changing meaningfully enough. */ RENDERCORE_API bool DoesResolutionChangeEnough(float CurrentResolutionFraction, float NewResolutionFraction, bool bCanChangeResolution) const; }; /* Represent an independent budget to dynamically scale by its own. */ class FBudget final { public: RENDERCORE_API FBudget(const TCHAR* Name, FHeuristicSettings (*HeuristicSettingsGetter)(void)); RENDERCORE_API ~FBudget(); /* Returns the global list of all dynamic resolution budgets. */ static RENDERCORE_API TLinkedList*& GetGlobalList(); static RENDERCORE_API int32 GetGlobalListSize(); inline const TCHAR* GetName() const { return Name; } inline const char* GetAnsiName() const { return AnsiName.GetData(); } /* Returns the settings of the heuristic for this budget. */ inline const FHeuristicSettings& GetSettings() const { return CachedSettings; } inline int32 GetBudgetId() const { return BudgetId; } inline bool operator == (const FBudget& Other) const { return BudgetId == Other.BudgetId; } #if STATS inline const TStatId& GetStatId_TargetMs() const { return StatId_TargetMs; } inline const TStatId& GetStatId_MeasuredMs() const { return StatId_MeasuredMs; } inline const TStatId& GetStatId_MinScaling() const { return StatId_MinScaling; } inline const TStatId& GetStatId_MaxScaling() const { return StatId_MaxScaling; } inline const TStatId& GetStatId_CurrentScaling() const { return StatId_CurrentScaling; } #endif private: const TCHAR* Name; TArray AnsiName; FHeuristicSettings(*HeuristicSettingsGetter)(void); TLinkedList GlobalListLink; FHeuristicSettings CachedSettings; int32 BudgetId = 0; #if STATS TStatId StatId_TargetMs; TStatId StatId_MeasuredMs; TStatId StatId_MinScaling; TStatId StatId_MaxScaling; TStatId StatId_CurrentScaling; #endif UE_NONCOPYABLE(FBudget); template friend class TMap; friend RENDERCORE_API void UpdateHeuristicsSettings(); }; /* Map of FBudget -> . */ template class TMap final { public: static constexpr int32 kInlineAllocatedBudgets = 16; TMap() { Array.SetNum(FBudget::GetGlobalListSize()); #if !UE_BUILD_SHIPPING for (TLinkedList::TIterator BudgetIt(FBudget::GetGlobalList()); BudgetIt; BudgetIt.Next()) { Array[BudgetIt->BudgetId].Name = BudgetIt->GetName(); } #endif } TMap(const Type& Value) : TMap() { SetAll(Value); } TMap(const TMap& Map) = default; ~TMap() = default; inline void SetAll(const Type& Value) { Array.SetNum(FBudget::GetGlobalListSize()); for (int32 i = 0; i < Array.Num(); i++) { Array[i].Value = Value; } } inline const Type& operator [](const FBudget& Budget) const { check(Array.Num() == FBudget::GetGlobalListSize()); return Array[Budget.BudgetId].Value; } inline Type& operator [](const FBudget& Budget) { check(Array.Num() == FBudget::GetGlobalListSize()); return Array[Budget.BudgetId].Value; } inline const Type& operator [](const int32 BudgetId) const { check(Array.Num() == FBudget::GetGlobalListSize()); return Array[BudgetId].Value; } inline Type& operator [](const int32 BudgetId) { check(Array.Num() == FBudget::GetGlobalListSize()); return Array[BudgetId].Value; } private: struct FItem { #if !UE_BUILD_SHIPPING const TCHAR* Name = nullptr; #endif Type Value; }; TArray> Array; }; constexpr float FractionToPercentage(float Fraction) { return Fraction * 100.0f; } constexpr float PercentageToFraction(float Percentage) { return Percentage / 100.0f; } inline float GetPercentageCVarToFraction(const TAutoConsoleVariable& Percentage) { return PercentageToFraction(Percentage.GetValueOnAnyThread()); } /** Returns whether the DynamicRenderScaling API is supported */ RENDERCORE_API bool IsSupported(); /** Updates all FBudget::GetSettings() with their new FHeuristicSettings. */ RENDERCORE_API void UpdateHeuristicsSettings(); /** Begins recording GPU timings in RDG for all the different FBudgets. */ RENDERCORE_API void BeginFrame(const TMap& bIsBudgetEnabled); /** Ends recording GPU timings in RDG for all the different FBudgets. */ RENDERCORE_API void EndFrame(); /** Returns the latest available timings. */ RENDERCORE_API const TMap& GetLatestTimings(); } // namespace DynamicRenderScaling