Files
UnrealEngineUWP/Engine/Source/Runtime/Renderer/Private/VT/VirtualTexturePhysicalSpace.cpp
jeremy moore e564bc08ca #jira UE-117953
#jira UE-109349
Add mip map bias for virtual texture sampling.
Respects the existing r.MiipMapLodBias CVar.
Also add residency tracking for each virtual texture physical pool.
If the pool has been created from a config with the bEnableResidencyMipMapBias flag set then a mip map lod bias is applied to bring the pool back within budget.
The residency mip map lod bias is applied globally (combined with the global mip map lod bias).
In future it could be good to apply the mip map lod bias per physical pool, but this will require tracking all physical pool combinations for each page table.
Physical pool residency tracking can be shown on screen with r.vt.residency.show CVar.
#rb ben.ingram


#ROBOMERGE-SOURCE: CL 16724483
#ROBOMERGE-BOT: (v835-16672529)

[CL 16724487 by jeremy moore in ue5-main branch]
2021-06-20 18:47:00 -04:00

324 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "VirtualTexturePhysicalSpace.h"
#include "Engine/Canvas.h"
#include "RenderTargetPool.h"
#include "VisualizeTexture.h"
#include "VT/VirtualTexturePoolConfig.h"
#include "VT/VirtualTextureScalability.h"
#include "VT/VirtualTextureSystem.h"
static TAutoConsoleVariable<float> CVarVTResidencyMaxMipMapBias(
TEXT("r.VT.Residency.MaxMipMapBias"),
8,
TEXT("Maximum mip bias to apply to prevent Virtual Texture pool residency over-subscription.\n")
TEXT("Default 8"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarVTResidencyUpperBound(
TEXT("r.VT.Residency.UpperBound"),
0.9f,
TEXT("Virtual Texture pool residency above which we increase mip bias.\n")
TEXT("Default 0.95"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarVTResidencyLowerBound(
TEXT("r.VT.Residency.LowerBound"),
0.75f,
TEXT("Virtual Texture pool residency below which we decrease mip bias.\n")
TEXT("Default 0.85"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarVTResidencyLockedUpperBound(
TEXT("r.VT.Residency.LockedUpperBound"),
0.65f,
TEXT("Virtual Texture pool locked page residency above which we kill any mip bias.\n")
TEXT("That's because locked pages are never affected by the mip bias setting. So it is unlikely that we can get the pool within budget.\n")
TEXT("Default 0.65"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarVTResidencyAdjustmentRate(
TEXT("r.VT.Residency.AdjustmentRate"),
0.2,
TEXT("Rate at which we adjust mip bias due to Virtual Texture pool residency.\n")
TEXT("Default 0.25"),
ECVF_RenderThreadSafe);
FVirtualTexturePhysicalSpace::FVirtualTexturePhysicalSpace(const FVTPhysicalSpaceDescription& InDesc, uint16 InID)
: Description(InDesc)
, NumRefs(0u)
, ID(InID)
, bGpuTextureLimit(false)
, bEnableResidencyMipMapBias(true)
, ResidencyMipMapBias(0.0f)
, LastFrameOversubscribed(0)
#if !UE_BUILD_SHIPPING
, VisibleHistory(HistorySize)
, LockedHistory(HistorySize)
, MipMapBiasHistory(HistorySize)
, HistoryIndex(0)
#endif
{
// Find matching physical pool
FVirtualTextureSpacePoolConfig Config;
UVirtualTexturePoolConfig const* PoolConfig = GetDefault<UVirtualTexturePoolConfig>();
PoolConfig->FindPoolConfig(InDesc.Format, InDesc.NumLayers, InDesc.TileSize, Config);
const uint32 PoolSizeInBytes = Config.SizeInMegabyte * 1024u * 1024u;
const FPixelFormatInfo& FormatInfo = GPixelFormats[InDesc.Format[0]];
check(InDesc.TileSize % FormatInfo.BlockSizeX == 0);
check(InDesc.TileSize % FormatInfo.BlockSizeY == 0);
SIZE_T TileSizeBytes = 0;
for (int32 Layer = 0; Layer < InDesc.NumLayers; ++Layer)
{
TileSizeBytes += CalculateImageBytes(InDesc.TileSize, InDesc.TileSize, 0, InDesc.Format[Layer]);
}
const uint32 MaxTiles = FMath::Max((uint32)(PoolSizeInBytes / TileSizeBytes), 1u);
TextureSizeInTiles = FMath::FloorToInt(FMath::Sqrt((float)MaxTiles));
if (TextureSizeInTiles * InDesc.TileSize > GetMax2DTextureDimension())
{
// A good option to support extremely large caches would be to allow additional slices in an array here for caches...
// Just try to use the maximum texture size for now
TextureSizeInTiles = GetMax2DTextureDimension() / InDesc.TileSize;
bGpuTextureLimit = true;
}
bEnableResidencyMipMapBias = Config.bEnableResidencyMipMapBias;
Pool.Initialize(GetNumTiles());
// Initialize this resource FeatureLevel, so it gets re-created on FeatureLevel changes
SetFeatureLevel(GMaxRHIFeatureLevel);
// Store string for logging.
for (uint32 Layer = 0u; Layer < Description.NumLayers; ++Layer)
{
FormatString += GPixelFormats[Description.Format[Layer]].Name;
if (Layer + 1u < Description.NumLayers)
{
FormatString += TEXT(", ");
}
}
}
FVirtualTexturePhysicalSpace::~FVirtualTexturePhysicalSpace()
{
}
static EPixelFormat GetUnorderedAccessViewFormat(EPixelFormat InFormat)
{
// Use alias formats for compressed textures on APIs where that is possible
// This allows us to compress runtime data directly to the physical texture
if (IsBlockCompressedFormat(InFormat))
{
return GRHISupportsUAVFormatAliasing ? GetBlockCompressedFormatUAVAliasFormat(InFormat) : PF_Unknown;
}
return InFormat;
}
EPixelFormat RemapVirtualTexturePhysicalSpaceFormat(EPixelFormat InFormat)
{
if (InFormat == PF_B8G8R8A8 && IsOpenGLPlatform(GMaxRHIShaderPlatform) && IsMobilePlatform(GMaxRHIShaderPlatform))
{
// FIXME: Mobile/Android OpenGL can't copy data between swizzled formats
// Always use RGBA format for both VT intermediate render targets and VT physical texture
// This will also make uncompressed streaming VT to have a R and B channel swapped
return PF_R8G8B8A8;
}
return InFormat;
}
void FVirtualTexturePhysicalSpace::InitRHI()
{
FRHICommandListImmediate& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList();
// Not all mobile RHIs support sRGB texture views/aliasing, use only linear targets on mobile
ETextureCreateFlags VT_SRGB = GetFeatureLevel() > ERHIFeatureLevel::ES3_1 ? TexCreate_SRGB : TexCreate_None;
for (int32 Layer = 0; Layer < Description.NumLayers; ++Layer)
{
const EPixelFormat FormatSRV = RemapVirtualTexturePhysicalSpaceFormat(Description.Format[Layer]);
const EPixelFormat FormatUAV = GetUnorderedAccessViewFormat(FormatSRV);
const bool bCreateAliasedUAV = (FormatUAV != PF_Unknown) && (FormatUAV != FormatSRV);
// Allocate physical texture from the render target pool
const uint32 TextureSize = GetTextureSize();
FPooledRenderTargetDesc Desc = FPooledRenderTargetDesc::Create2DDesc(
FIntPoint(TextureSize, TextureSize),
FormatSRV,
FClearValueBinding::None,
VT_SRGB,
// GPULightmass hack: always create UAV for PF_A32B32G32R32F
(bCreateAliasedUAV || FormatSRV == PF_A32B32G32R32F) ? TexCreate_ShaderResource | TexCreate_UAV : TexCreate_ShaderResource,
false);
if (bCreateAliasedUAV)
{
Desc.UAVFormat = FormatUAV;
}
GRenderTargetPool.FindFreeElement(RHICmdList, Desc, PooledRenderTarget[Layer], TEXT("VirtualPhysicalTexture"));
FRHITexture* TextureRHI = PooledRenderTarget[Layer]->GetRenderTargetItem().ShaderResourceTexture;
// Create sRGB and non-sRGB shader resource views into the physical texture
FRHITextureSRVCreateInfo SRVCreateInfo;
SRVCreateInfo.Format = FormatSRV;
SRVCreateInfo.SRGBOverride = SRGBO_ForceDisable;
TextureSRV[Layer] = RHICreateShaderResourceView(TextureRHI, SRVCreateInfo);
SRVCreateInfo.SRGBOverride = SRGBO_Default;
TextureSRV_SRGB[Layer] = RHICreateShaderResourceView(TextureRHI, SRVCreateInfo);
if (bCreateAliasedUAV)
{
TextureUAV[Layer] = PooledRenderTarget[Layer]->GetRenderTargetItem().UAV;
}
}
}
void FVirtualTexturePhysicalSpace::ReleaseRHI()
{
for (int32 Layer = 0; Layer < Description.NumLayers; ++Layer)
{
GRenderTargetPool.FreeUnusedResource(PooledRenderTarget[Layer]);
TextureSRV[Layer].SafeRelease();
TextureSRV_SRGB[Layer].SafeRelease();
TextureUAV[Layer].SafeRelease();
}
}
uint32 FVirtualTexturePhysicalSpace::GetSizeInBytes() const
{
SIZE_T TileSizeBytes = 0;
for (int32 Layer = 0; Layer < Description.NumLayers; ++Layer)
{
TileSizeBytes += CalculateImageBytes(Description.TileSize, Description.TileSize, 0, Description.Format[Layer]);
}
return GetNumTiles() * TileSizeBytes;
}
void FVirtualTexturePhysicalSpace::UpdateResidencyTracking(uint32 Frame)
{
float LockedUpperBound = CVarVTResidencyLockedUpperBound.GetValueOnRenderThread();
float LowerBound = CVarVTResidencyLowerBound.GetValueOnRenderThread();
float UpperBound = CVarVTResidencyUpperBound.GetValueOnRenderThread();
float AdjustmentRate = CVarVTResidencyAdjustmentRate.GetValueOnRenderThread();
float MaxMipMapBias = CVarVTResidencyMaxMipMapBias.GetValueOnRenderThread();
const uint32 NumPages = Pool.GetNumPages();
const uint32 NumLockedPages = Pool.GetNumLockedPages();
const float LockedPageResidency = (float)NumLockedPages / (float)NumPages;
const uint32 PageFreeThreshold = FMath::Max(VirtualTextureScalability::GetPageFreeThreshold(), 0u);
const uint32 FrameMinusThreshold = Frame > PageFreeThreshold ? Frame - PageFreeThreshold : 0;
const uint32 NumVisiblePages = Pool.GetNumVisiblePages(FrameMinusThreshold);
const float VisiblePageResidency = (float)NumVisiblePages / (float)NumPages;
if (ResidencyMipMapBias > 0.f && VisiblePageResidency < LowerBound)
{
ResidencyMipMapBias -= AdjustmentRate * (LowerBound - VisiblePageResidency);
}
else if (VisiblePageResidency > UpperBound)
{
ResidencyMipMapBias += AdjustmentRate * (VisiblePageResidency - UpperBound);
}
ResidencyMipMapBias = FMath::Clamp(ResidencyMipMapBias, 0.f, MaxMipMapBias);
if (ResidencyMipMapBias > 0.f)
{
LastFrameOversubscribed = Frame;
}
if (!bEnableResidencyMipMapBias || LockedPageResidency > LockedUpperBound)
{
ResidencyMipMapBias = 0.f;
}
#if !UE_BUILD_SHIPPING
LockedHistory[HistoryIndex+1] = LockedPageResidency;
VisibleHistory[HistoryIndex+1] = VisiblePageResidency;
MipMapBiasHistory[HistoryIndex+1] = ResidencyMipMapBias / MaxMipMapBias;
HistoryIndex++;
#endif
}
void FVirtualTexturePhysicalSpace::DrawResidencyGraph(FCanvas* Canvas, FBox2D CanvasPosition)
{
// Note that this is called on game thread and reads the history values written on the render thread.
// But it should be safe and any race condition will only lead to a slightly incorrect graph.
#if !UE_BUILD_SHIPPING
const FLinearColor BackgroundColor(0.0f, 0.0f, 0.0f, 0.7f);
const FLinearColor GraphBorderColor(0.1f, 0.1f, 0.1f);
const FLinearColor GraphResidencyColor(0.8f, 0.1f, 0.1f);
const FLinearColor GraphLockedPageColor(0.8f, 0.8f, 0.1f);
const FLinearColor GraphMipMapBiasColor(0.1f, 0.8f, 0.1f);
FCanvasTileItem BackgroundTile(CanvasPosition.Min, CanvasPosition.GetSize(), BackgroundColor);
BackgroundTile.BlendMode = SE_BLEND_AlphaBlend;
Canvas->DrawItem(BackgroundTile);
const int32 BorderSize = 10;
FString Title = FString::Printf(TEXT("%s (%dMB)"), *FormatString, GetSizeInBytes() / (1024 * 1024));
Canvas->DrawShadowedString(CanvasPosition.Min.X + BorderSize, CanvasPosition.Min.Y, *Title, GEngine->GetSmallFont(), FLinearColor::White);
CanvasPosition.Min += FVector2D(BorderSize, BorderSize);
CanvasPosition.Max -= FVector2D(BorderSize, BorderSize);
const uint32 NumSamples = FMath::Min3<uint32>((uint32)CanvasPosition.GetSize().X, HistorySize, HistoryIndex);
FHitProxyId HitProxyId = Canvas->GetHitProxyId();
FBatchedElements* BatchedElements = Canvas->GetBatchedElements(FCanvas::ET_Line);
BatchedElements->AddReserveLines(2 + 3 * NumSamples);
BatchedElements->AddLine(
FVector(CanvasPosition.Min.X - 1.0f, CanvasPosition.Max.Y, 0.0f),
FVector(CanvasPosition.Min.X - 1.0f, CanvasPosition.Min.Y - 1.0f, 0.0f),
GraphBorderColor,
HitProxyId);
BatchedElements->AddLine(
FVector(CanvasPosition.Min.X, CanvasPosition.Max.Y - 1.0f, 0.0f),
FVector(CanvasPosition.Max.X, CanvasPosition.Max.Y - 1.0f, 0.0f),
GraphBorderColor,
HitProxyId);
for (uint32 SampleIndex = 0; SampleIndex < NumSamples; ++SampleIndex)
{
float Visible0 = VisibleHistory[HistoryIndex - NumSamples + SampleIndex];
float Visible1 = VisibleHistory[HistoryIndex - NumSamples + SampleIndex + 1];
BatchedElements->AddLine(
FVector(CanvasPosition.Min.X + SampleIndex, CanvasPosition.Max.Y - Visible0 * CanvasPosition.GetSize().Y, 0.0f),
FVector(CanvasPosition.Min.X + SampleIndex + 1, CanvasPosition.Max.Y - Visible1 * CanvasPosition.GetSize().Y, 0.0f),
GraphResidencyColor,
HitProxyId);
float Locked0 = LockedHistory[HistoryIndex - NumSamples + SampleIndex];
float Locked1 = LockedHistory[HistoryIndex - NumSamples + SampleIndex + 1];
BatchedElements->AddLine(
FVector(CanvasPosition.Min.X + SampleIndex, CanvasPosition.Max.Y - Locked0 * CanvasPosition.GetSize().Y, 0.0f),
FVector(CanvasPosition.Min.X + SampleIndex + 1, CanvasPosition.Max.Y - Locked1 * CanvasPosition.GetSize().Y, 0.0f),
GraphLockedPageColor,
HitProxyId);
float MipMapBias0 = MipMapBiasHistory[HistoryIndex - NumSamples + SampleIndex];
float MipMapBias1 = MipMapBiasHistory[HistoryIndex - NumSamples + SampleIndex + 1];
BatchedElements->AddLine(
FVector(CanvasPosition.Min.X + SampleIndex, CanvasPosition.Max.Y - MipMapBias0 * CanvasPosition.GetSize().Y, 0.0f),
FVector(CanvasPosition.Min.X + SampleIndex + 1, CanvasPosition.Max.Y - MipMapBias1 * CanvasPosition.GetSize().Y, 0.0f),
GraphMipMapBiasColor,
HitProxyId);
}
#endif // UE_BUILD_SHIPPING
}