281 lines
12 KiB
C++
281 lines
12 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "VT/RuntimeVirtualTextureSceneProxy.h"
|
|
|
|
#include "Components/RuntimeVirtualTextureComponent.h"
|
|
#include "RendererOnScreenNotification.h"
|
|
#include "SceneInterface.h"
|
|
#include "SceneUtils.h"
|
|
#include "VirtualTextureSystem.h"
|
|
#include "VT/RuntimeVirtualTextureEnum.h"
|
|
#include "VT/RuntimeVirtualTexture.h"
|
|
#include "VT/RuntimeVirtualTextureProducer.h"
|
|
#include "VT/VirtualTexture.h"
|
|
#include "VT/VirtualTextureBuilder.h"
|
|
#include "VT/VirtualTextureScalability.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "VirtualTexture"
|
|
|
|
FRuntimeVirtualTextureSceneProxy::FRuntimeVirtualTextureSceneProxy(URuntimeVirtualTextureComponent* InComponent)
|
|
{
|
|
// Evaluate the flags used to hide primitives writing to this virtual texture.
|
|
InComponent->GetHidePrimitiveSettings(bHidePrimitivesInEditor, bHidePrimitivesInGame);
|
|
|
|
if (InComponent->GetVirtualTexture() != nullptr)
|
|
{
|
|
if (InComponent->IsEnabledInScene())
|
|
{
|
|
URuntimeVirtualTexture::FInitSettings InitSettings;
|
|
InitSettings.TileCountBias = InComponent->IsScalable() ? VirtualTextureScalability::GetRuntimeVirtualTextureSizeBias(InComponent->GetScalabilityGroup()) : 0;
|
|
|
|
VirtualTexture = InComponent->GetVirtualTexture();
|
|
RuntimeVirtualTextureId = VirtualTexture->GetUniqueID();
|
|
|
|
Transform = InComponent->GetComponentTransform();
|
|
Bounds = InComponent->Bounds.GetBox();
|
|
const FVector4f CustomMaterialData = InComponent->GetCustomMaterialData();
|
|
|
|
// The producer description is calculated using the transform to determine the aspect ratio
|
|
FVTProducerDescription ProducerDesc;
|
|
VirtualTexture->GetProducerDescription(ProducerDesc, InitSettings, Transform);
|
|
|
|
MaterialType = VirtualTexture->GetMaterialType();
|
|
const bool bClearTextures = VirtualTexture->GetClearTextures();
|
|
|
|
// Get streaming texture if it is valid.
|
|
UVirtualTexture2D* StreamingTexture = nullptr;
|
|
|
|
FSceneInterface* SceneInterface = InComponent->GetScene();
|
|
const EShadingPath ShadingPath = SceneInterface ? SceneInterface->GetShadingPath() : EShadingPath::Deferred;
|
|
if (InComponent->IsStreamingLowMips(ShadingPath))
|
|
{
|
|
if (InComponent->IsStreamingTextureInvalid(ShadingPath))
|
|
{
|
|
#if !UE_BUILD_SHIPPING
|
|
// Notify that streaming texture is invalid since this can cause performance regression.
|
|
const FString Name = InComponent->GetPathName();
|
|
OnScreenWarningDelegateHandle = FRendererOnScreenNotification::Get().AddLambda([Name](FCoreDelegates::FSeverityMessageMap& OutMessages)
|
|
{
|
|
OutMessages.Add(
|
|
FCoreDelegates::EOnScreenMessageSeverity::Warning,
|
|
FText::Format(LOCTEXT("SVTInvalid", "Runtime Virtual Texture '{0}' streaming mips needs to be rebuilt."), FText::FromString(Name)));
|
|
});
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
StreamingTexture = InComponent->GetStreamingTexture()->GetVirtualTexture(ShadingPath);
|
|
}
|
|
}
|
|
|
|
// The producer object created here will be passed into the virtual texture system which will take ownership.
|
|
IVirtualTexture* Producer = nullptr;
|
|
|
|
// Create a producer for the streaming low mips.
|
|
// This is bound with the main producer so that one allocated VT can use both runtime or streaming producers dependent on mip level.
|
|
if (StreamingTexture == nullptr)
|
|
{
|
|
// Create the runtime virtual texture producer.
|
|
Producer = new FRuntimeVirtualTextureProducer(ProducerDesc, RuntimeVirtualTextureId, MaterialType, bClearTextures, SceneInterface, Transform, Bounds, CustomMaterialData);
|
|
|
|
// We only need to dirty flush up to the producer description MaxLevel which accounts for the RemoveLowMips
|
|
MaxDirtyLevel = ProducerDesc.MaxLevel;
|
|
}
|
|
else
|
|
{
|
|
// Create the streaming virtual texture producer.
|
|
FVTProducerDescription StreamingProducerDesc;
|
|
IVirtualTexture* StreamingProducer = RuntimeVirtualTexture::CreateStreamingTextureProducer(StreamingTexture, ProducerDesc, StreamingProducerDesc);
|
|
|
|
// Copy the layer fallback colors from the streaming virtual texture.
|
|
for (uint32 LayerIndex = 0u; LayerIndex < ProducerDesc.NumTextureLayers; ++LayerIndex)
|
|
{
|
|
ProducerDesc.LayerFallbackColor[LayerIndex] = StreamingProducerDesc.LayerFallbackColor[LayerIndex];
|
|
}
|
|
|
|
if (InComponent->IsStreamingLowMipsOnly())
|
|
{
|
|
// Clamp the the runtime virtual texture producer dimensions to the streaming virtual texture dimensions.
|
|
// This will force to only using streaming pages.
|
|
ProducerDesc.BlockWidthInTiles = StreamingProducerDesc.BlockWidthInTiles;
|
|
ProducerDesc.BlockHeightInTiles = StreamingProducerDesc.BlockHeightInTiles;
|
|
ProducerDesc.MaxLevel = StreamingProducerDesc.MaxLevel;
|
|
}
|
|
|
|
// Create the runtime virtual texture producer.
|
|
Producer = new FRuntimeVirtualTextureProducer(ProducerDesc, RuntimeVirtualTextureId, MaterialType, bClearTextures, SceneInterface, Transform, Bounds, CustomMaterialData);
|
|
|
|
// Bind the runtime virtual texture producer to the streaming producer.
|
|
const int32 NumLevels = (int32)FMath::CeilLogTwo(FMath::Max(ProducerDesc.BlockWidthInTiles, ProducerDesc.BlockHeightInTiles));
|
|
const int32 NumStreamingLevels = (int32)FMath::CeilLogTwo(FMath::Max(StreamingProducerDesc.BlockWidthInTiles, StreamingProducerDesc.BlockHeightInTiles));
|
|
ensure(NumLevels >= NumStreamingLevels);
|
|
const int32 TransitionLevel = NumLevels - NumStreamingLevels;
|
|
|
|
Producer = RuntimeVirtualTexture::BindStreamingTextureProducer(Producer, StreamingProducer, TransitionLevel);
|
|
|
|
// Any dirty flushes don't need to flush the streaming mips (they only change with a build step).
|
|
MaxDirtyLevel = TransitionLevel - 1;
|
|
}
|
|
|
|
// Store effective virtual texture size used when calculating dirty regions.
|
|
VirtualTextureSize = FIntPoint(ProducerDesc.BlockWidthInTiles * ProducerDesc.TileSize, ProducerDesc.BlockHeightInTiles * ProducerDesc.TileSize);
|
|
|
|
// The Initialize() call will allocate the virtual texture by spawning work on the render thread.
|
|
VirtualTexture->Initialize(Producer, ProducerDesc, Transform, Bounds);
|
|
|
|
bAdaptive = VirtualTexture->GetAdaptivePageTable();
|
|
|
|
for (int32 Index = 0; Index < ERuntimeVirtualTextureShaderUniform_Count; ++Index)
|
|
{
|
|
ShaderUniforms[Index] = VirtualTexture->GetUniformParameter(Index);
|
|
}
|
|
|
|
// Store the ProducerHandle, SpaceID and AllocatedVirtualTexture immediately after virtual texture is initialized.
|
|
ENQUEUE_RENDER_COMMAND(GetProducerHandle)(
|
|
[this, VirtualTexturePtr = VirtualTexture](FRHICommandList& RHICmdList)
|
|
{
|
|
ProducerHandle = VirtualTexturePtr->GetProducerHandle();
|
|
AllocatedVirtualTexture = VirtualTexturePtr->GetAllocatedVirtualTexture();
|
|
SpaceID = AllocatedVirtualTexture->GetSpaceID();
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// When not enabled, ensure that the RVT asset has no allocated VT.
|
|
// In PIE this handles removing the RVT from the editor scene.
|
|
InComponent->GetVirtualTexture()->Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
FRuntimeVirtualTextureSceneProxy::~FRuntimeVirtualTextureSceneProxy()
|
|
{
|
|
#if !UE_BUILD_SHIPPING
|
|
FRendererOnScreenNotification::Get().Remove(OnScreenWarningDelegateHandle);
|
|
#endif
|
|
}
|
|
|
|
void FRuntimeVirtualTextureSceneProxy::Release()
|
|
{
|
|
if (VirtualTexture != nullptr)
|
|
{
|
|
VirtualTexture->Release();
|
|
VirtualTexture = nullptr;
|
|
}
|
|
}
|
|
|
|
void FRuntimeVirtualTextureSceneProxy::MarkUnused()
|
|
{
|
|
VirtualTexture = nullptr;
|
|
ProducerHandle = {};
|
|
AllocatedVirtualTexture = nullptr;
|
|
SpaceID = -1;
|
|
}
|
|
|
|
/** Transform world bounds into Virtual Texture UV space. */
|
|
static FBox2D GetUVRectFromWorldBounds(FTransform const& InTransform, FBoxSphereBounds const& InBounds)
|
|
{
|
|
const FVector O = InTransform.GetTranslation();
|
|
const FVector U = InTransform.GetUnitAxis(EAxis::X) * 1.f / InTransform.GetScale3D().X;
|
|
const FVector V = InTransform.GetUnitAxis(EAxis::Y) * 1.f / InTransform.GetScale3D().Y;
|
|
const FVector P = InBounds.GetSphere().Center - O;
|
|
const FVector2D UVCenter = FVector2D(FVector::DotProduct(P, U), FVector::DotProduct(P, V));
|
|
const float Scale = FMath::Max(1.f / InTransform.GetScale3D().X, 1.f / InTransform.GetScale3D().Y);
|
|
const float UVRadius = InBounds.GetSphere().W * Scale;
|
|
const FVector2D UVExtent(UVRadius, UVRadius);
|
|
|
|
return FBox2D(UVCenter - UVExtent, UVCenter + UVExtent);
|
|
}
|
|
|
|
void FRuntimeVirtualTextureSceneProxy::Dirty(FBoxSphereBounds const& InBounds, EVTInvalidatePriority InInvalidatePriority)
|
|
{
|
|
// If Producer handle is not initialized yet it's safe to do nothing because we won't have rendered anything to the VT that needs flushing.
|
|
if (ProducerHandle.PackedValue != 0)
|
|
{
|
|
const FBox2D UVRect = GetUVRectFromWorldBounds(Transform, InBounds);
|
|
|
|
// Convert to Texel coordinate space
|
|
const FIntRect TextureRect(0, 0, VirtualTextureSize.X, VirtualTextureSize.Y);
|
|
FIntRect TexelRect(
|
|
FMath::FloorToInt(UVRect.Min.X * VirtualTextureSize.X),
|
|
FMath::FloorToInt(UVRect.Min.Y * VirtualTextureSize.Y),
|
|
FMath::CeilToInt(UVRect.Max.X * VirtualTextureSize.X),
|
|
FMath::CeilToInt(UVRect.Max.Y * VirtualTextureSize.Y));
|
|
TexelRect.Clip(TextureRect);
|
|
|
|
// Only add rect if it has some area
|
|
if (TexelRect.Min != TexelRect.Max)
|
|
{
|
|
FDirtyRect DirtyRect{ .Rect = TexelRect, .InvalidatePriority = InInvalidatePriority };
|
|
const bool bFirst = DirtyRects.Add(DirtyRect) == 0;
|
|
if (bFirst)
|
|
{
|
|
CombinedDirtyRect = DirtyRect;
|
|
}
|
|
else
|
|
{
|
|
CombinedDirtyRect.Union(DirtyRect);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FRuntimeVirtualTextureSceneProxy::FlushDirtyPages()
|
|
{
|
|
// Don't do any work if we won't mark anything dirty.
|
|
if (MaxDirtyLevel >= 0 && CombinedDirtyRect.Rect.Width() != 0 && CombinedDirtyRect.Rect.Height() != 0)
|
|
{
|
|
// Keeping visible pages mapped reduces update flicker due to the latency in the unmap/feedback/map sequence.
|
|
// But it potentially creates more page update work since more pages may get updated.
|
|
const uint32 MaxAgeToKeepMapped = VirtualTextureScalability::GetKeepDirtyPageMappedFrameThreshold();
|
|
|
|
//todo[vt]:
|
|
// Profile to work out best heuristic for when we should use the CombinedDirtyRect
|
|
// Also consider using some other structure to represent dirty area such as a course 2D bitfield
|
|
bool bCombinedFlush = (DirtyRects.Num() > 2 || CombinedDirtyRect.Rect == FIntRect(0, 0, VirtualTextureSize.X, VirtualTextureSize.Y));
|
|
// Don't use the combined rect if one of the dirty rects is prioritized, because that would would leave all of the pages being covered by the combined rect to get prioritized,
|
|
// which would give exactly the opposite result of what we're trying to achieve, since we need to keep the number of prioritized pages to remain low :
|
|
bCombinedFlush &= (CombinedDirtyRect.InvalidatePriority == EVTInvalidatePriority::Normal);
|
|
|
|
if (bCombinedFlush)
|
|
{
|
|
FVirtualTextureSystem::Get().FlushCache(ProducerHandle, SpaceID, CombinedDirtyRect.Rect, MaxDirtyLevel, MaxAgeToKeepMapped, EVTInvalidatePriority::Normal);
|
|
}
|
|
else
|
|
{
|
|
for (const FDirtyRect& DirtyRect : DirtyRects)
|
|
{
|
|
FVirtualTextureSystem::Get().FlushCache(ProducerHandle, SpaceID, DirtyRect.Rect, MaxDirtyLevel, MaxAgeToKeepMapped, DirtyRect.InvalidatePriority);
|
|
}
|
|
}
|
|
}
|
|
|
|
DirtyRects.Reset();
|
|
CombinedDirtyRect = FDirtyRect();
|
|
}
|
|
|
|
void FRuntimeVirtualTextureSceneProxy::RequestPreload(FBoxSphereBounds const& InBounds, int32 InLevel)
|
|
{
|
|
// If Producer handle is not initialized yet it's safe to do nothing.
|
|
if (ProducerHandle.PackedValue != 0)
|
|
{
|
|
const FBox2D UVRect = GetUVRectFromWorldBounds(Transform, InBounds);
|
|
FVirtualTextureSystem::Get().RequestTiles(AllocatedVirtualTexture, FVector2D::One(), FVector2D::Zero(), FVector2D::One(), UVRect.Min, UVRect.Max, InLevel);
|
|
}
|
|
}
|
|
|
|
FVector4 FRuntimeVirtualTextureSceneProxy::GetUniformParameter(ERuntimeVirtualTextureShaderUniform UniformName) const
|
|
{
|
|
const int32 UniformIndex = (int32)UniformName;
|
|
|
|
if (UniformIndex < ERuntimeVirtualTextureShaderUniform_Count)
|
|
{
|
|
return ShaderUniforms[UniformIndex];
|
|
}
|
|
|
|
checkNoEntry();
|
|
return FVector4::Zero();
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|