1967 lines
73 KiB
C++
1967 lines
73 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "RectLightTextureManager.h"
|
|
#include "Engine/Texture.h"
|
|
#include "Shader.h"
|
|
#include "GlobalShader.h"
|
|
#include "ShaderParameters.h"
|
|
#include "ShaderParameterStruct.h"
|
|
#include "RenderGraphUtils.h"
|
|
#include "ShaderPrintParameters.h"
|
|
#include "ShaderPrint.h"
|
|
#include "RenderingThread.h"
|
|
#include "Containers/Array.h"
|
|
#include "Containers/Queue.h"
|
|
#include "SceneView.h"
|
|
#include "RHIDefinitions.h"
|
|
#include "SceneRendering.h"
|
|
#include "SystemTextures.h"
|
|
#include "TextureLayout.h"
|
|
#include "CommonRenderResources.h"
|
|
#include "ScreenPass.h"
|
|
#include "RectLightTexture.h"
|
|
#include "DataDrivenShaderPlatformInfo.h"
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Possible improvements:
|
|
// * When copying & filtering the texture, add RGBE/BCH6 encoding to reduce footprint and improve
|
|
// fetch perf
|
|
// * Support non-square atlas
|
|
|
|
static TAutoConsoleVariable<int32> CVarRectLightTextureResolution(
|
|
TEXT("r.RectLightAtlas.MaxResolution"),
|
|
4096,
|
|
TEXT("The maximum resolution for storing rect. light textures.\n"),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarRectLighTextureDebug(
|
|
TEXT("r.RectLightAtlas.Debug"),
|
|
0,
|
|
TEXT("Enable rect. light atlas debug information."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarRectLighTextureDebugMipLevel(
|
|
TEXT("r.RectLightAtlas.Debug.MipLevel"),
|
|
0,
|
|
TEXT("Set MIP level for visualizing atlas texture in debug mode."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarRectLighForceUpdate(
|
|
TEXT("r.RectLightAtlas.ForceUpdate"),
|
|
0,
|
|
TEXT("Force rect. light atlas update very frame."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarRectLighResetOnChange(
|
|
TEXT("r.RectLightAtlas.ResetOnChange"),
|
|
0,
|
|
TEXT("Recreate the atlas layout from scratch each time an update is made to the atlas."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarRectLighClearAtlas(
|
|
TEXT("r.RectLightAtlas.ClearAtlas"),
|
|
0,
|
|
TEXT("Clear rect light texture atlas each time the atlas is recreated/updated."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarRectLighFilterQuality(
|
|
TEXT("r.RectLightAtlas.FilterQuality"),
|
|
1,
|
|
TEXT("Define the filtering quality used for filtering texture (0:Box filter, 1:Gaussian filter)."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarRectLighMaxTextureRatio(
|
|
TEXT("r.RectLightAtlas.MaxTextureRatio"),
|
|
2,
|
|
TEXT("Define the max Width/Height or Height/Width ratio that a texture can have."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarRectLighAtlasFormat(
|
|
TEXT("r.RectLightAtlas.Format"),
|
|
0,
|
|
TEXT("Define the atlas format used.\n - 0: R11G11B10 (faster but can caused hue shift). \n - 1: RGA16F (more accurate but slower)"),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
namespace RectLightAtlas
|
|
{
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Configuration
|
|
|
|
// Packing algo.
|
|
// Reference: "A Thousand Ways to Pack the Bin. A Practical Approach to Two-Dimensional Rectangle Bin Packing"
|
|
//
|
|
// 0 : use a Shelf/Row packing algo -> faster, but produces low-quality packing.
|
|
// 1 : use a Skyline/Horizon packing algo -> more expensive, but provides better packing.
|
|
// 2 : use a FTextureLayout
|
|
#define USE_PACKING_MODE 1
|
|
|
|
// If enabled, track the deallocated free rects (i.e. 'holes' in the atlas), and try to allocate
|
|
// slots from them when possible rather than expanding the atlas
|
|
#define USE_WASTE_RECT 1
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Structs & constants
|
|
|
|
static const uint32 InvalidSlotIndex = ~0u;
|
|
static const FIntPoint InvalidOrigin = FIntPoint(-1, -1);
|
|
|
|
static FIntPoint GetSourceResolution(const FRHITexture* Tex, const FVector4f& ScaleOffset)
|
|
{
|
|
FIntPoint Out(1, 1);
|
|
if (Tex)
|
|
{
|
|
const FIntVector SourceOriginalResolution = Tex->GetSizeXYZ();
|
|
|
|
float ScaledResolutionX = ScaleOffset.X * SourceOriginalResolution.X;
|
|
float ScaledResolutionY = ScaleOffset.Y * SourceOriginalResolution.Y;
|
|
|
|
const float RatioYX = ScaledResolutionY / ScaledResolutionX;
|
|
const float RatioXY = ScaledResolutionX / ScaledResolutionY;
|
|
|
|
Out = FIntPoint(ScaledResolutionX, ScaledResolutionY);
|
|
// Max Ratio of 2
|
|
const float RatioThreshold = FMath::Clamp(CVarRectLighMaxTextureRatio.GetValueOnAnyThread(), 1, 16);
|
|
if (RatioYX > RatioThreshold)
|
|
{
|
|
Out.X *= RatioYX / RatioThreshold;
|
|
}
|
|
else if (RatioXY > RatioThreshold)
|
|
{
|
|
Out.Y *= RatioXY / RatioThreshold;
|
|
}
|
|
}
|
|
return Out;
|
|
}
|
|
|
|
struct FAtlasRect
|
|
{
|
|
FIntPoint Origin = InvalidOrigin;
|
|
FIntPoint Resolution = FIntPoint::ZeroValue;
|
|
};
|
|
|
|
struct FAtlasHorizon
|
|
{
|
|
FAtlasRect Line;
|
|
FAtlasRect ExtendedLine;
|
|
};
|
|
|
|
// Store the current atlas layout, i.e. data for knowing where next insertion can be done
|
|
struct FAtlasLayout
|
|
{
|
|
FAtlasLayout(const FIntPoint& MaxResolution = FIntPoint(256, 256))
|
|
: AtlasResolution(MaxResolution)
|
|
#if USE_PACKING_MODE == 2
|
|
, Packer(256, 256, MaxResolution.X, MaxResolution.Y, true, ETextureLayoutAspectRatio::ForceSquare, true)
|
|
#endif
|
|
{
|
|
|
|
}
|
|
|
|
FIntPoint AtlasResolution = FIntPoint::ZeroValue;
|
|
int32 SourceTextureMIPBias = 0;
|
|
#if USE_PACKING_MODE == 0
|
|
int32 SplitX = 0;
|
|
int32 SplitY = 0;
|
|
int32 MaxY = 0;
|
|
#elif USE_PACKING_MODE == 1
|
|
TArray<FAtlasHorizon> Horizons;
|
|
#elif USE_PACKING_MODE == 2
|
|
FTextureLayout Packer;
|
|
#endif
|
|
|
|
#if USE_WASTE_RECT
|
|
TArray<FAtlasRect> FreeRects;
|
|
#endif
|
|
};
|
|
|
|
// Store info of a current slot within the atlas
|
|
struct FAtlasSlot
|
|
{
|
|
uint32 Id = InvalidSlotIndex;
|
|
FAtlasRect Rect;
|
|
FVector4f SourceScaleOffset;
|
|
FTextureReferenceRHIRef SourceTexture = nullptr;
|
|
FIntPoint CachedSourceTextureResolution = FIntPoint(0, 0);
|
|
uint32 RefCount = 0;
|
|
bool bForceRefresh = false;
|
|
bool IsValid() const { return SourceTexture != nullptr; }
|
|
FRHITexture* GetTextureRHI() const { return SourceTexture->GetReferencedTexture(); }
|
|
|
|
FIntPoint GetSourceResolution() const
|
|
{
|
|
return RectLightAtlas::GetSourceResolution(SourceTexture ? SourceTexture->GetReferencedTexture() : nullptr, SourceScaleOffset);
|
|
}
|
|
};
|
|
|
|
// Store info for copying one atlas slot to another one, when a new layout is created
|
|
struct FAtlasCopySlot
|
|
{
|
|
uint32 Id = InvalidSlotIndex;
|
|
FIntPoint SrcOrigin = InvalidOrigin;
|
|
FIntPoint DstOrigin = InvalidOrigin;
|
|
FIntPoint Resolution = FIntPoint::ZeroValue;
|
|
FTextureReferenceRHIRef SourceTexture = nullptr;
|
|
};
|
|
|
|
// Texture manager, holding all atlas data & description
|
|
struct FRectLightTextureManager : public FRenderResource
|
|
{
|
|
TRefCountPtr<IPooledRenderTarget> AtlasTexture = nullptr;
|
|
TArray<FAtlasSlot> AtlasSlots;
|
|
TArray<FAtlasRect> DeletedSlots;
|
|
TQueue<uint32> FreeSlots;
|
|
FAtlasLayout AtlasLayout;
|
|
|
|
bool bLock = false;
|
|
bool bHasPendingAdds = false;
|
|
bool bHasPendingDeletes = false;
|
|
|
|
virtual void ReleaseRHI()
|
|
{
|
|
AtlasTexture.SafeRelease();
|
|
}
|
|
};
|
|
|
|
// lives on the render thread
|
|
TGlobalResource<FRectLightTextureManager> GRectLightTextureManager;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Utils functions
|
|
|
|
bool CanContain(const FAtlasRect& Outside, const FAtlasRect& Inside)
|
|
{
|
|
return Inside.Resolution.X <= Outside.Resolution.X && Inside.Resolution.Y <= Outside.Resolution.Y;
|
|
}
|
|
|
|
static FIntPoint ToMIP(const FIntPoint& InMip, uint32 MipIndex)
|
|
{
|
|
const int32 Div = 1 << MipIndex;
|
|
|
|
FIntPoint Out;
|
|
Out.X = FMath::DivideAndRoundUp(InMip.X,Div);
|
|
Out.Y = FMath::DivideAndRoundUp(InMip.Y,Div);
|
|
return Out;
|
|
}
|
|
|
|
static int32 GetSlotMaxMIPLevel(const FAtlasSlot& In)
|
|
{
|
|
const uint32 MaxMIP = FMath::FloorLog2(FMath::Min(In.Rect.Resolution.X, In.Rect.Resolution.Y));
|
|
return MaxMIP > 0u ? MaxMIP-1u : 0u;
|
|
}
|
|
|
|
// Align the slot resolution to prevent bilinear filtering to leak on neighbors' slot
|
|
static void AlignSlotResolution(FAtlasSlot& In)
|
|
{
|
|
const uint32 MaxMIP = GetSlotMaxMIPLevel(In);
|
|
const FIntPoint ResolutionMIP = ToMIP(In.Rect.Resolution, MaxMIP);
|
|
In.Rect.Resolution = FIntPoint(ResolutionMIP.X << MaxMIP, ResolutionMIP.Y << MaxMIP);
|
|
}
|
|
|
|
static EPixelFormat GetRectLightAtlasFormat()
|
|
{
|
|
return CVarRectLighAtlasFormat.GetValueOnRenderThread() > 0 ? PF_FloatRGBA : PF_FloatR11G11B10;
|
|
}
|
|
|
|
static bool Traits_IsValid(const FAtlasRect& In) { return true; }
|
|
static bool Traits_IsValid(const FAtlasHorizon& In) { return true; }
|
|
static bool Traits_IsValid(const FAtlasSlot& In) { return In.IsValid(); }
|
|
static const FAtlasRect& Traits_GetRect(const FAtlasRect& In) { return In; }
|
|
static const FAtlasRect& Traits_GetRect(const FAtlasHorizon& In) { return In.ExtendedLine; }
|
|
static const FAtlasRect& Traits_GetRect(const FAtlasSlot& In) { return In.Rect; }
|
|
static const FVector4f& Traits_GetScaleOffset(const FAtlasSlot& In) { return In.SourceScaleOffset; }
|
|
|
|
template<typename T>
|
|
static FRDGBufferRef CreateSlotBuffer(FRDGBuilder& GraphBuilder, const TArray<T>& In, const TCHAR* InBufferName)
|
|
{
|
|
struct FAtlasGPUSlot
|
|
{
|
|
uint16 OffsetX;
|
|
uint16 OffsetY;
|
|
uint16 ResolutionX;
|
|
uint16 ResolutionY;
|
|
};
|
|
|
|
if (In.IsEmpty())
|
|
{
|
|
return GSystemTextures.GetDefaultBuffer(GraphBuilder, sizeof(FAtlasGPUSlot), 0u);
|
|
}
|
|
|
|
TArray<FAtlasGPUSlot> SlotData;
|
|
SlotData.Reserve(In.Num());
|
|
for (const T& I : In)
|
|
{
|
|
if (Traits_IsValid(I))
|
|
{
|
|
const FAtlasRect& Rect = Traits_GetRect(I);
|
|
FAtlasGPUSlot& GPUSlot = SlotData.AddDefaulted_GetRef();
|
|
GPUSlot.OffsetX = Rect.Origin.X;
|
|
GPUSlot.OffsetY = Rect.Origin.Y;
|
|
GPUSlot.ResolutionX = Rect.Resolution.X;
|
|
GPUSlot.ResolutionY = Rect.Resolution.Y;
|
|
}
|
|
}
|
|
FRDGBufferRef Buffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(FAtlasGPUSlot), SlotData.Num()), InBufferName);
|
|
GraphBuilder.QueueBufferUpload(Buffer, SlotData.GetData(), SlotData.Num() * sizeof(FAtlasGPUSlot));
|
|
return Buffer;
|
|
}
|
|
|
|
template<typename T>
|
|
static FRDGBufferRef CreateScaleOffsetBuffer(FRDGBuilder& GraphBuilder, const TArray<T>& In, const TCHAR* InBufferName)
|
|
{
|
|
struct FAtlasGPUScaleOffset
|
|
{
|
|
float SourceScaleX;
|
|
float SourceScaleY;
|
|
float SourceOffsetX;
|
|
float SourceOffsetY;
|
|
};
|
|
|
|
if (In.IsEmpty())
|
|
{
|
|
return GSystemTextures.GetDefaultBuffer(GraphBuilder, sizeof(FAtlasGPUScaleOffset), 0u);
|
|
}
|
|
|
|
TArray<FAtlasGPUScaleOffset> ScaleOffsetData;
|
|
ScaleOffsetData.Reserve(In.Num());
|
|
for (const T& I : In)
|
|
{
|
|
if (Traits_IsValid(I))
|
|
{
|
|
//Traits_GetRect(I);
|
|
const FVector4f& ScaleOffset = Traits_GetScaleOffset(I);
|
|
FAtlasGPUScaleOffset& GPUSlot = ScaleOffsetData.AddDefaulted_GetRef();
|
|
GPUSlot.SourceScaleX = ScaleOffset.X;
|
|
GPUSlot.SourceScaleY = ScaleOffset.Y;
|
|
GPUSlot.SourceOffsetX = ScaleOffset.Z;
|
|
GPUSlot.SourceOffsetY = ScaleOffset.W;
|
|
}
|
|
}
|
|
FRDGBufferRef Buffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(FAtlasGPUScaleOffset), ScaleOffsetData.Num()), InBufferName);
|
|
GraphBuilder.QueueBufferUpload(Buffer, ScaleOffsetData.GetData(), ScaleOffsetData.Num() * sizeof(FAtlasGPUScaleOffset));
|
|
return Buffer;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Clear
|
|
#define RECT_ATLAS_DEBUG_CLEAR 0
|
|
|
|
#if RECT_ATLAS_DEBUG_CLEAR
|
|
class FRectLightAtlasClearCS : public FGlobalShader
|
|
{
|
|
DECLARE_GLOBAL_SHADER(FRectLightAtlasClearCS);
|
|
SHADER_USE_PARAMETER_STRUCT(FRectLightAtlasClearCS, FGlobalShader);
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER(FIntPoint, Resolution)
|
|
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutputTexture)
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
public:
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return true; }
|
|
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
|
|
OutEnvironment.SetDefine(TEXT("SHADER_CLEAR_TEXTURE"), 1);
|
|
}
|
|
static EShaderPermutationPrecacheRequest ShouldPrecachePermutation(const FGlobalShaderPermutationParameters& Parameters)
|
|
{
|
|
return EShaderPermutationPrecacheRequest::NotPrecached;
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FRectLightAtlasClearCS, "/Engine/Private/RectLightAtlas.usf", "MainCS", SF_Compute);
|
|
|
|
static FRDGTextureRef AddRectLightClearPass(
|
|
FRDGBuilder& GraphBuilder,
|
|
FGlobalShaderMap* ShaderMap,
|
|
FRDGTextureRef OutputTexture)
|
|
{
|
|
const FIntPoint OutputResolution(OutputTexture->Desc.Extent);
|
|
const uint32 MipCount = FMath::FloorLog2(OutputResolution.X);
|
|
for (uint32 MipIt = 0; MipIt < MipCount; ++MipIt)
|
|
{
|
|
FRectLightAtlasClearCS::FParameters* Parameters = GraphBuilder.AllocParameters<FRectLightAtlasClearCS::FParameters>();
|
|
Parameters->Resolution.X = OutputResolution.X>>MipIt;
|
|
Parameters->Resolution.Y = OutputResolution.Y>>MipIt;
|
|
Parameters->OutputTexture = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(OutputTexture, MipIt));
|
|
|
|
TShaderMapRef<FRectLightAtlasClearCS> ComputeShader(ShaderMap);
|
|
const FIntVector DispatchCount = DispatchCount.DivideAndRoundUp(FIntVector(OutputResolution.X, OutputResolution.Y, 1), FIntVector(8, 8, 1));
|
|
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("RectLightAtlas::Clear"), ComputeShader, Parameters, DispatchCount);
|
|
}
|
|
|
|
return OutputTexture;
|
|
}
|
|
#endif
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Debug
|
|
class FRectLightAtlasDebugInfoCS : public FGlobalShader
|
|
{
|
|
DECLARE_GLOBAL_SHADER(FRectLightAtlasDebugInfoCS);
|
|
SHADER_USE_PARAMETER_STRUCT(FRectLightAtlasDebugInfoCS, FGlobalShader);
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER(FIntPoint, AtlasResolution)
|
|
SHADER_PARAMETER(float, AtlasMaxMipLevel)
|
|
SHADER_PARAMETER(float, Occupancy)
|
|
SHADER_PARAMETER(uint32, SlotCount)
|
|
SHADER_PARAMETER(uint32, HorizonCount)
|
|
SHADER_PARAMETER(uint32, FreeCount)
|
|
SHADER_PARAMETER(FIntPoint, OutputResolution)
|
|
SHADER_PARAMETER(uint32, AtlasMIPIndex)
|
|
SHADER_PARAMETER(uint32, AtlasSourceTextureMIPBias)
|
|
SHADER_PARAMETER(uint32, AtlasFormat)
|
|
SHADER_PARAMETER(uint32, ForceUpdate)
|
|
SHADER_PARAMETER(uint32, ResetOnChange)
|
|
SHADER_PARAMETER(uint32, ClearAtlas)
|
|
SHADER_PARAMETER_SAMPLER(SamplerState, AtlasSampler)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, AtlasTexture)
|
|
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutputTexture)
|
|
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, SlotBuffer)
|
|
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, HorizonBuffer)
|
|
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, FreeBuffer)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(ShaderPrint::FShaderParameters, ShaderPrintParameters)
|
|
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, ViewUniformBuffer)
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
public:
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return ShaderPrint::IsSupported(Parameters.Platform); }
|
|
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
|
|
ShaderPrint::ModifyCompilationEnvironment(Parameters, OutEnvironment);
|
|
|
|
OutEnvironment.SetDefine(TEXT("SHADER_DEBUG"), 1);
|
|
}
|
|
static EShaderPermutationPrecacheRequest ShouldPrecachePermutation(const FGlobalShaderPermutationParameters& Parameters)
|
|
{
|
|
return EShaderPermutationPrecacheRequest::NotPrecached;
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FRectLightAtlasDebugInfoCS, "/Engine/Private/RectLightAtlas.usf", "MainCS", SF_Compute);
|
|
|
|
static FRDGTextureRef AddRectLightDebugInfoPass(
|
|
FRDGBuilder& GraphBuilder,
|
|
const FViewInfo& View,
|
|
const FRDGTextureDesc& OutputDesc)
|
|
{
|
|
// Force ShaderPrint on.
|
|
ShaderPrint::SetEnabled(true);
|
|
|
|
FRDGTextureRef OutputTexture = GraphBuilder.CreateTexture(FRDGTextureDesc::Create2D(OutputDesc.Extent, OutputDesc.Format, FClearValueBinding::Black, TexCreate_UAV | TexCreate_ShaderResource), TEXT("RectLight.DebugTexture"));
|
|
|
|
FGlobalShaderMap* ShaderMap = View.ShaderMap;
|
|
|
|
FRDGTextureRef AtlasTexture = GRectLightTextureManager.AtlasTexture ? GraphBuilder.RegisterExternalTexture(GRectLightTextureManager.AtlasTexture) : GSystemTextures.GetBlackDummy(GraphBuilder);
|
|
|
|
uint32 ValidSlotCount = 0;
|
|
uint32 OccupiedPixels = 0;
|
|
TArray<FAtlasSlot> ValidSlots;
|
|
ValidSlots.Reserve(GRectLightTextureManager.AtlasSlots.Num());
|
|
for (const FAtlasSlot& Slot : GRectLightTextureManager.AtlasSlots)
|
|
{
|
|
if (Slot.IsValid())
|
|
{
|
|
ValidSlots.Add(Slot);
|
|
OccupiedPixels += Slot.Rect.Resolution.X * Slot.Rect.Resolution.Y;
|
|
}
|
|
}
|
|
|
|
#if USE_PACKING_MODE == 1
|
|
const TArray<FAtlasHorizon>& Horizons = GRectLightTextureManager.AtlasLayout.Horizons;
|
|
const TArray<FAtlasRect>& FreeRects = GRectLightTextureManager.AtlasLayout.FreeRects;
|
|
#else
|
|
const TArray<FAtlasHorizon> Horizons;
|
|
const TArray<FAtlasRect> FreeRects;
|
|
#endif
|
|
|
|
// Create a buffer with all the valid slot to highlight them on the debug view
|
|
FRDGBufferRef SlotBuffer = CreateSlotBuffer(GraphBuilder, ValidSlots, TEXT("RectLight.AtlasSlotBuffer"));
|
|
FRDGBufferRef HorizonBuffer = CreateSlotBuffer(GraphBuilder, Horizons, TEXT("RectLight.HorizonBuffer"));
|
|
FRDGBufferRef FreeBuffer = CreateSlotBuffer(GraphBuilder, FreeRects, TEXT("RectLight.FreeBuffer"));
|
|
|
|
uint32 AtlasFormat = 2;
|
|
switch (AtlasTexture->Desc.Format)
|
|
{
|
|
case PF_FloatR11G11B10 : AtlasFormat = 0; break;
|
|
case PF_FloatRGBA : AtlasFormat = 1; break;
|
|
default: AtlasFormat = 2; break;
|
|
}
|
|
|
|
const FIntPoint OutputResolution(OutputTexture->Desc.Extent);
|
|
FRectLightAtlasDebugInfoCS::FParameters* Parameters = GraphBuilder.AllocParameters<FRectLightAtlasDebugInfoCS::FParameters>();
|
|
Parameters->AtlasResolution = AtlasTexture->Desc.Extent;
|
|
Parameters->AtlasMaxMipLevel = AtlasTexture->Desc.NumMips;
|
|
Parameters->AtlasSourceTextureMIPBias = GRectLightTextureManager.AtlasLayout.SourceTextureMIPBias;
|
|
Parameters->AtlasFormat = AtlasFormat;
|
|
Parameters->Occupancy = OccupiedPixels / float(AtlasTexture->Desc.Extent.X * AtlasTexture->Desc.Extent.Y);
|
|
Parameters->SlotCount = ValidSlots.Num();
|
|
Parameters->HorizonCount = Horizons.Num();
|
|
Parameters->FreeCount = FreeRects.Num();
|
|
Parameters->OutputResolution = OutputResolution;
|
|
Parameters->ForceUpdate = CVarRectLighForceUpdate.GetValueOnRenderThread() > 0;
|
|
Parameters->ResetOnChange = CVarRectLighResetOnChange.GetValueOnRenderThread() > 0;
|
|
Parameters->ClearAtlas = CVarRectLighClearAtlas.GetValueOnRenderThread() > 0;
|
|
Parameters->AtlasSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
|
|
Parameters->AtlasTexture = AtlasTexture;
|
|
Parameters->SlotBuffer = GraphBuilder.CreateSRV(SlotBuffer, PF_R16G16B16A16_UINT);
|
|
Parameters->HorizonBuffer = GraphBuilder.CreateSRV(HorizonBuffer, PF_R16G16B16A16_UINT);
|
|
Parameters->FreeBuffer = GraphBuilder.CreateSRV(FreeBuffer, PF_R16G16B16A16_UINT);
|
|
Parameters->ViewUniformBuffer = View.ViewUniformBuffer;
|
|
Parameters->AtlasMIPIndex = FMath::Clamp(CVarRectLighTextureDebugMipLevel.GetValueOnRenderThread(), 0u, Parameters->AtlasMaxMipLevel-1);
|
|
ShaderPrint::SetParameters(GraphBuilder, View.ShaderPrintData, Parameters->ShaderPrintParameters);
|
|
Parameters->OutputTexture = GraphBuilder.CreateUAV(OutputTexture);
|
|
|
|
TShaderMapRef<FRectLightAtlasDebugInfoCS> ComputeShader(ShaderMap);
|
|
const FIntVector DispatchCount = DispatchCount.DivideAndRoundUp(FIntVector(OutputResolution.X, OutputResolution.Y, 1), FIntVector(8, 8, 1));
|
|
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("RectLightAtlas::DebugInfo"), ComputeShader, Parameters, DispatchCount);
|
|
|
|
return OutputTexture;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Rect VS
|
|
|
|
class FRectLightAtlasVS : public FGlobalShader
|
|
{
|
|
DECLARE_GLOBAL_SHADER(FRectLightAtlasVS);
|
|
SHADER_USE_PARAMETER_STRUCT(FRectLightAtlasVS, FGlobalShader);
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, SlotBuffer)
|
|
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, ScaleOffsetBuffer)
|
|
SHADER_PARAMETER(uint32, SlotBufferOffset)
|
|
SHADER_PARAMETER(FIntPoint, AtlasResolution)
|
|
SHADER_PARAMETER(int32, bHasScaleOffset)
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
public:
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return true; }
|
|
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
|
|
OutEnvironment.SetDefine(TEXT("SHADER_RECT_VS"), 1);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FRectLightAtlasVS, "/Engine/Private/RectLightAtlas.usf", "MainVS", SF_Vertex);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Add pass
|
|
|
|
class FRectAtlasAddTexturePS : public FGlobalShader
|
|
{
|
|
DECLARE_GLOBAL_SHADER(FRectAtlasAddTexturePS);
|
|
SHADER_USE_PARAMETER_STRUCT(FRectAtlasAddTexturePS, FGlobalShader);
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER(int32, InTextureMIPBias)
|
|
SHADER_PARAMETER_TEXTURE(Texture2D, InTexture0)
|
|
SHADER_PARAMETER_TEXTURE(Texture2D, InTexture1)
|
|
SHADER_PARAMETER_TEXTURE(Texture2D, InTexture2)
|
|
SHADER_PARAMETER_TEXTURE(Texture2D, InTexture3)
|
|
SHADER_PARAMETER_TEXTURE(Texture2D, InTexture4)
|
|
SHADER_PARAMETER_TEXTURE(Texture2D, InTexture5)
|
|
SHADER_PARAMETER_TEXTURE(Texture2D, InTexture6)
|
|
SHADER_PARAMETER_TEXTURE(Texture2D, InTexture7)
|
|
SHADER_PARAMETER_SAMPLER(SamplerState, InSampler0)
|
|
SHADER_PARAMETER_SAMPLER(SamplerState, InSampler1)
|
|
SHADER_PARAMETER_SAMPLER(SamplerState, InSampler2)
|
|
SHADER_PARAMETER_SAMPLER(SamplerState, InSampler3)
|
|
SHADER_PARAMETER_SAMPLER(SamplerState, InSampler4)
|
|
SHADER_PARAMETER_SAMPLER(SamplerState, InSampler5)
|
|
SHADER_PARAMETER_SAMPLER(SamplerState, InSampler6)
|
|
SHADER_PARAMETER_SAMPLER(SamplerState, InSampler7)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FRectLightAtlasVS::FParameters, VS)
|
|
RENDER_TARGET_BINDING_SLOTS()
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
public:
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return true; }
|
|
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
|
|
OutEnvironment.SetDefine(TEXT("SHADER_ADD_TEXTURE"), 1);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FRectAtlasAddTexturePS, "/Engine/Private/RectLightAtlas.usf", "MainPS", SF_Pixel);
|
|
|
|
// Insert a texture into the atlas
|
|
static void AddSlotsPass(
|
|
FRDGBuilder& GraphBuilder,
|
|
FGlobalShaderMap* ShaderMap,
|
|
const int32 TextureMIPBias,
|
|
const TArray<FAtlasSlot>& Slots,
|
|
FRDGTextureRef& OutAtlas)
|
|
{
|
|
const FIntRect Viewport(FIntPoint::ZeroValue, OutAtlas->Desc.Extent);
|
|
const FIntPoint Resolution(Viewport.Width(), Viewport.Height());
|
|
|
|
FRDGBufferRef SlotBuffer = CreateSlotBuffer(GraphBuilder, Slots, TEXT("RectLight.AtlasSlotBuffer"));
|
|
FRDGBufferRef ScaleOffsetBuffer = CreateScaleOffsetBuffer(GraphBuilder, Slots, TEXT("RectLight.AtlasScaleOffsetBuffer"));
|
|
|
|
// Batch new slots into several passes
|
|
const uint32 SlotCountPerPass = 8u;
|
|
const uint32 PassCount = FMath::DivideAndRoundUp(uint32(Slots.Num()), SlotCountPerPass);
|
|
for (uint32 PassIt = 0; PassIt < PassCount; ++PassIt)
|
|
{
|
|
const uint32 SlotOffset = PassIt * SlotCountPerPass;
|
|
const uint32 SlotCount = SlotCountPerPass * (PassIt+1) <= uint32(Slots.Num()) ? SlotCountPerPass : uint32(Slots.Num()) - (SlotCountPerPass * PassIt);
|
|
|
|
FRectAtlasAddTexturePS::FParameters* Parameters = GraphBuilder.AllocParameters<FRectAtlasAddTexturePS::FParameters>();
|
|
// Init. source texure
|
|
{
|
|
Parameters->InTexture0 = GSystemTextures.BlackDummy->GetRHI();
|
|
Parameters->InTexture1 = GSystemTextures.BlackDummy->GetRHI();
|
|
Parameters->InTexture2 = GSystemTextures.BlackDummy->GetRHI();
|
|
Parameters->InTexture3 = GSystemTextures.BlackDummy->GetRHI();
|
|
Parameters->InTexture4 = GSystemTextures.BlackDummy->GetRHI();
|
|
Parameters->InTexture5 = GSystemTextures.BlackDummy->GetRHI();
|
|
Parameters->InTexture6 = GSystemTextures.BlackDummy->GetRHI();
|
|
Parameters->InTexture7 = GSystemTextures.BlackDummy->GetRHI();
|
|
}
|
|
for (uint32 SlotIt = 0; SlotIt<SlotCount;++SlotIt)
|
|
{
|
|
const FAtlasSlot& Slot = Slots[SlotOffset + SlotIt];
|
|
check(Slot.SourceTexture);
|
|
|
|
switch (SlotIt)
|
|
{
|
|
case 0: Parameters->InTexture0 = Slot.GetTextureRHI(); break;
|
|
case 1: Parameters->InTexture1 = Slot.GetTextureRHI(); break;
|
|
case 2: Parameters->InTexture2 = Slot.GetTextureRHI(); break;
|
|
case 3: Parameters->InTexture3 = Slot.GetTextureRHI(); break;
|
|
case 4: Parameters->InTexture4 = Slot.GetTextureRHI(); break;
|
|
case 5: Parameters->InTexture5 = Slot.GetTextureRHI(); break;
|
|
case 6: Parameters->InTexture6 = Slot.GetTextureRHI(); break;
|
|
case 7: Parameters->InTexture7 = Slot.GetTextureRHI(); break;
|
|
}
|
|
}
|
|
|
|
FRHISamplerState* SamplerState = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
|
|
Parameters->InSampler0 = SamplerState;
|
|
Parameters->InSampler1 = SamplerState;
|
|
Parameters->InSampler2 = SamplerState;
|
|
Parameters->InSampler3 = SamplerState;
|
|
Parameters->InSampler4 = SamplerState;
|
|
Parameters->InSampler5 = SamplerState;
|
|
Parameters->InSampler6 = SamplerState;
|
|
Parameters->InSampler7 = SamplerState;
|
|
|
|
Parameters->InTextureMIPBias = TextureMIPBias;
|
|
Parameters->VS.AtlasResolution = Resolution;
|
|
Parameters->VS.SlotBufferOffset = SlotOffset;
|
|
Parameters->VS.SlotBuffer = GraphBuilder.CreateSRV(SlotBuffer, PF_R16G16B16A16_UINT);
|
|
Parameters->VS.ScaleOffsetBuffer = GraphBuilder.CreateSRV(ScaleOffsetBuffer, PF_A32B32G32R32F);
|
|
Parameters->VS.bHasScaleOffset = true;
|
|
Parameters->RenderTargets[0] = FRenderTargetBinding(OutAtlas, ERenderTargetLoadAction::ELoad, 0);
|
|
|
|
TShaderMapRef<FRectLightAtlasVS> VertexShader(ShaderMap);
|
|
TShaderMapRef<FRectAtlasAddTexturePS> PixelShader(ShaderMap);
|
|
|
|
GraphBuilder.AddPass(
|
|
RDG_EVENT_NAME("RectLightAtlas::AddTexturePass(Slot:%d)", SlotCount),
|
|
Parameters,
|
|
ERDGPassFlags::Raster,
|
|
[Parameters, VertexShader, PixelShader, Viewport, Resolution, SlotCount](FRDGAsyncTask, FRHICommandList& RHICmdList)
|
|
{
|
|
FGraphicsPipelineStateInitializer GraphicsPSOInit;
|
|
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
|
|
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_Zero, BO_Add, BF_One, BF_Zero>::GetRHI();
|
|
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
|
|
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
|
|
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GEmptyVertexDeclaration.VertexDeclarationRHI;
|
|
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
|
|
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
|
|
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
|
|
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
|
|
|
|
RHICmdList.SetViewport(Viewport.Min.X, Viewport.Min.Y, 0.0f, Viewport.Max.X, Viewport.Max.Y, 1.0f);
|
|
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *Parameters);
|
|
SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), Parameters->VS);
|
|
|
|
RHICmdList.SetStreamSource(0, nullptr, 0);
|
|
RHICmdList.DrawPrimitive(0, 2, SlotCount);
|
|
});
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Copy pass
|
|
|
|
class FRectAtlasCopyTexturePS : public FGlobalShader
|
|
{
|
|
DECLARE_GLOBAL_SHADER(FRectAtlasCopyTexturePS);
|
|
SHADER_USE_PARAMETER_STRUCT(FRectAtlasCopyTexturePS, FGlobalShader);
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER(uint32, MipLevel)
|
|
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, SrcSlotBuffer)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D<float4>, SourceAtlasTexture)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FRectLightAtlasVS::FParameters, VS)
|
|
RENDER_TARGET_BINDING_SLOTS()
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
public:
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return true; }
|
|
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
|
|
OutEnvironment.SetDefine(TEXT("SHADER_COPY_TEXTURE"), 1);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FRectAtlasCopyTexturePS, "/Engine/Private/RectLightAtlas.usf", "MainPS", SF_Pixel);
|
|
|
|
// Copy all slots (and their mip) from InAtlas to OutAtlas
|
|
// This is done when atlas need a full repacking, changing the slot layout
|
|
static void CopySlotsPass(
|
|
FRDGBuilder& GraphBuilder,
|
|
FGlobalShaderMap* ShaderMap,
|
|
const TArray<FAtlasCopySlot>& InSlots,
|
|
const FRDGTextureRef& InAtlas,
|
|
FRDGTextureRef& OutAtlas)
|
|
{
|
|
FRDGBufferSRVRef DummyScaleOffsetBuffer = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultBuffer(GraphBuilder, 16u, 1u), PF_A32B32G32R32F);
|
|
|
|
// For each MIP, copy the slot from the old atlas (InAtlas) to the new atlas (OutAtlas)
|
|
const uint32 MipCount = InAtlas->Desc.NumMips; // FMath::Log2(FMath::Min(InAtlas->Desc.Extent.X, InAtlas->Desc.Extent.Y));
|
|
for (uint32 MipIt = 0; MipIt < MipCount; ++MipIt)
|
|
{
|
|
TArray<FAtlasSlot> SrcSlots;
|
|
TArray<FAtlasSlot> DstSlots;
|
|
SrcSlots.Reserve(InSlots.Num());
|
|
DstSlots.Reserve(InSlots.Num());
|
|
for (const FAtlasCopySlot& Slot : InSlots)
|
|
{
|
|
const bool bIsValidMIP = (Slot.Resolution.X >> MipIt) > 1 && (Slot.Resolution.Y >> MipIt) > 1;
|
|
if (bIsValidMIP)
|
|
{
|
|
{
|
|
FAtlasSlot& DstSlot = DstSlots.AddDefaulted_GetRef();
|
|
DstSlot.Rect.Origin = ToMIP(Slot.DstOrigin, MipIt);
|
|
DstSlot.Rect.Resolution = ToMIP(Slot.Resolution, MipIt);
|
|
DstSlot.SourceTexture = Slot.SourceTexture;
|
|
}
|
|
|
|
{
|
|
FAtlasSlot& SrcSlot = SrcSlots.AddDefaulted_GetRef();
|
|
SrcSlot.Rect.Origin = ToMIP(Slot.SrcOrigin, MipIt);
|
|
SrcSlot.Rect.Resolution = ToMIP(Slot.Resolution, MipIt);
|
|
SrcSlot.SourceTexture = Slot.SourceTexture;
|
|
}
|
|
}
|
|
}
|
|
|
|
const uint32 SlotCount = DstSlots.Num();
|
|
if (SlotCount > 0)
|
|
{
|
|
FRDGBufferRef SrcSlotBuffer = CreateSlotBuffer(GraphBuilder, SrcSlots, TEXT("RectLight.SrcSlotBuffer"));
|
|
FRDGBufferRef DstSlotBuffer = CreateSlotBuffer(GraphBuilder, DstSlots, TEXT("RectLight.DstSlotBuffer"));
|
|
|
|
const FIntPoint Resolution = ToMIP(OutAtlas->Desc.Extent.X, MipIt);
|
|
const FIntRect Viewport(FIntPoint::ZeroValue, Resolution);
|
|
|
|
FRectAtlasCopyTexturePS::FParameters* Parameters = GraphBuilder.AllocParameters<FRectAtlasCopyTexturePS::FParameters>();
|
|
Parameters->SourceAtlasTexture = InAtlas;
|
|
Parameters->MipLevel = MipIt;
|
|
Parameters->SrcSlotBuffer = GraphBuilder.CreateSRV(SrcSlotBuffer, PF_R16G16B16A16_UINT);
|
|
Parameters->VS.AtlasResolution = Resolution;
|
|
Parameters->VS.SlotBufferOffset = 0;
|
|
Parameters->VS.SlotBuffer = GraphBuilder.CreateSRV(DstSlotBuffer, PF_R16G16B16A16_UINT);
|
|
Parameters->VS.ScaleOffsetBuffer = DummyScaleOffsetBuffer;
|
|
Parameters->VS.bHasScaleOffset = false;
|
|
|
|
Parameters->RenderTargets[0] = FRenderTargetBinding(OutAtlas, ERenderTargetLoadAction::ELoad, MipIt);
|
|
|
|
TShaderMapRef<FRectLightAtlasVS> VertexShader(ShaderMap);
|
|
TShaderMapRef<FRectAtlasCopyTexturePS> PixelShader(ShaderMap);
|
|
|
|
GraphBuilder.AddPass(
|
|
RDG_EVENT_NAME("RectLightAtlas::CopyTexturePass(MIP:%d,Slots:%d)", MipIt, SlotCount),
|
|
Parameters,
|
|
ERDGPassFlags::Raster,
|
|
[Parameters, VertexShader, PixelShader, Viewport, Resolution, SlotCount](FRDGAsyncTask, FRHICommandList& RHICmdList)
|
|
{
|
|
FGraphicsPipelineStateInitializer GraphicsPSOInit;
|
|
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
|
|
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_Zero, BO_Add, BF_One, BF_Zero>::GetRHI();
|
|
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
|
|
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
|
|
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GEmptyVertexDeclaration.VertexDeclarationRHI;
|
|
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
|
|
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
|
|
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
|
|
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
|
|
|
|
RHICmdList.SetViewport(Viewport.Min.X, Viewport.Min.Y, 0.0f, Viewport.Max.X, Viewport.Max.Y, 1.0f);
|
|
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *Parameters);
|
|
SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), Parameters->VS);
|
|
|
|
RHICmdList.SetStreamSource(0, nullptr, 0);
|
|
RHICmdList.DrawPrimitive(0, 2, SlotCount);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Filtering pass
|
|
|
|
class FRectAtlasFilterTexturePS : public FGlobalShader
|
|
{
|
|
DECLARE_GLOBAL_SHADER(FRectAtlasFilterTexturePS);
|
|
SHADER_USE_PARAMETER_STRUCT(FRectAtlasFilterTexturePS, FGlobalShader);
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER(FIntPoint, SrcAtlasResolution)
|
|
SHADER_PARAMETER(uint32, FilterQuality)
|
|
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, DstSlotBuffer)
|
|
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, SrcSlotBuffer)
|
|
SHADER_PARAMETER_SAMPLER(SamplerState, SourceAtlasSampler)
|
|
SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D<float4>, SourceAtlasTexture)
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FRectLightAtlasVS::FParameters, VS)
|
|
RENDER_TARGET_BINDING_SLOTS()
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
public:
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return true; }
|
|
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
|
|
OutEnvironment.SetDefine(TEXT("SHADER_FILTER_TEXTURE"), 1);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FRectAtlasFilterTexturePS, "/Engine/Private/RectLightAtlas.usf", "MainPS", SF_Pixel);
|
|
|
|
// Filter all provided slots
|
|
static void FilterSlotsPass(
|
|
FRDGBuilder& GraphBuilder,
|
|
FGlobalShaderMap* ShaderMap,
|
|
const TArray<FAtlasSlot>& InSlots,
|
|
FRDGTextureRef& InAtlas)
|
|
{
|
|
FRDGBufferSRVRef DummyScaleOffsetBuffer = GraphBuilder.CreateSRV(GSystemTextures.GetDefaultBuffer(GraphBuilder, 16u, 1u), PF_A32B32G32R32F);
|
|
|
|
const uint32 MipCount = InAtlas->Desc.NumMips; // FMath::Log2(FMath::Min(InAtlas->Desc.Extent.X, InAtlas->Desc.Extent.Y));
|
|
for (uint32 DstMip = 1; DstMip < MipCount; ++DstMip)
|
|
{
|
|
const uint32 SrcMip = DstMip-1;
|
|
|
|
TArray<FAtlasSlot> SrcMIPSlots;
|
|
TArray<FAtlasSlot> DstMIPSlots;
|
|
SrcMIPSlots.Reserve(InSlots.Num());
|
|
DstMIPSlots.Reserve(InSlots.Num());
|
|
for (const FAtlasSlot& Slot : InSlots)
|
|
{
|
|
const bool bIsValidMIP = (Slot.Rect.Resolution.X >> DstMip) > 1 && (Slot.Rect.Resolution.Y >> DstMip) > 1;
|
|
if (bIsValidMIP)
|
|
{
|
|
{
|
|
FAtlasSlot& DstMIPSlot = DstMIPSlots.AddDefaulted_GetRef();
|
|
DstMIPSlot.Rect.Origin = ToMIP(Slot.Rect.Origin, DstMip);
|
|
DstMIPSlot.Rect.Resolution = ToMIP(Slot.Rect.Resolution, DstMip);
|
|
DstMIPSlot.SourceTexture = Slot.SourceTexture;
|
|
}
|
|
|
|
{
|
|
FAtlasSlot& SrcMIPSlot = SrcMIPSlots.AddDefaulted_GetRef();
|
|
SrcMIPSlot.Rect.Origin = ToMIP(Slot.Rect.Origin, SrcMip);
|
|
SrcMIPSlot.Rect.Resolution = ToMIP(Slot.Rect.Resolution, SrcMip);
|
|
SrcMIPSlot.SourceTexture = Slot.SourceTexture;
|
|
}
|
|
}
|
|
}
|
|
|
|
const uint32 SlotCount = DstMIPSlots.Num();
|
|
if (SlotCount > 0)
|
|
{
|
|
FRDGBufferRef SrcSlotBuffer = CreateSlotBuffer(GraphBuilder, SrcMIPSlots, TEXT("RectLight.SrcMIPSlotBuffer"));
|
|
FRDGBufferRef DstSlotBuffer = CreateSlotBuffer(GraphBuilder, DstMIPSlots, TEXT("RectLight.DstMIPSlotBuffer"));
|
|
|
|
FRectAtlasFilterTexturePS::FParameters* Parameters = GraphBuilder.AllocParameters<FRectAtlasFilterTexturePS::FParameters>();
|
|
Parameters->FilterQuality = FMath::Clamp(CVarRectLighFilterQuality.GetValueOnRenderThread(), 0, 1);
|
|
Parameters->SrcAtlasResolution = ToMIP(InAtlas->Desc.Extent, SrcMip);
|
|
Parameters->DstSlotBuffer = GraphBuilder.CreateSRV(DstSlotBuffer, PF_R16G16B16A16_UINT);
|
|
Parameters->SrcSlotBuffer = GraphBuilder.CreateSRV(SrcSlotBuffer, PF_R16G16B16A16_UINT);
|
|
Parameters->SourceAtlasTexture = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::CreateForMipLevel(InAtlas, SrcMip));
|
|
Parameters->SourceAtlasSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
|
|
Parameters->VS.AtlasResolution = ToMIP(InAtlas->Desc.Extent, DstMip);
|
|
Parameters->VS.SlotBufferOffset = 0;
|
|
Parameters->VS.SlotBuffer = GraphBuilder.CreateSRV(DstSlotBuffer, PF_R16G16B16A16_UINT);
|
|
Parameters->VS.ScaleOffsetBuffer = DummyScaleOffsetBuffer;
|
|
Parameters->VS.bHasScaleOffset = false;
|
|
Parameters->RenderTargets[0] = FRenderTargetBinding(InAtlas, ERenderTargetLoadAction::ELoad, DstMip);
|
|
|
|
TShaderMapRef<FRectLightAtlasVS> VertexShader(ShaderMap);
|
|
TShaderMapRef<FRectAtlasFilterTexturePS> PixelShader(ShaderMap);
|
|
|
|
GraphBuilder.AddPass(
|
|
RDG_EVENT_NAME("RectLightAtlas::FilterTexturePass(Mip:%d,Slots:%d)", DstMip, SlotCount),
|
|
Parameters,
|
|
ERDGPassFlags::Raster,
|
|
[Parameters, VertexShader, PixelShader, SlotCount](FRDGAsyncTask, FRHICommandList& RHICmdList)
|
|
{
|
|
const FIntPoint Resolution = Parameters->VS.AtlasResolution;
|
|
const FIntRect Viewport(FIntPoint::ZeroValue, Resolution);
|
|
|
|
FGraphicsPipelineStateInitializer GraphicsPSOInit;
|
|
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
|
|
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_Zero, BO_Add, BF_One, BF_Zero>::GetRHI();
|
|
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
|
|
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
|
|
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GEmptyVertexDeclaration.VertexDeclarationRHI;
|
|
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
|
|
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
|
|
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
|
|
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
|
|
|
|
RHICmdList.SetViewport(Viewport.Min.X, Viewport.Min.Y, 0.0f, Viewport.Max.X, Viewport.Max.Y, 1.0f);
|
|
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *Parameters);
|
|
SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), Parameters->VS);
|
|
|
|
RHICmdList.SetStreamSource(0, nullptr, 0);
|
|
RHICmdList.DrawPrimitive(0, 2, SlotCount);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Internal update
|
|
|
|
// Compute an aligned version of a rect.
|
|
// The output rect will be smaller, but its origin will respect the alignement requirement
|
|
static FAtlasRect ComputeAlignedRect(const FAtlasRect& In, int32 Alignement)
|
|
{
|
|
FAtlasRect Out;
|
|
Out.Origin = FIntPoint(
|
|
FMath::DivideAndRoundUp(In.Origin.X, Alignement) * Alignement,
|
|
FMath::DivideAndRoundUp(In.Origin.Y, Alignement) * Alignement);
|
|
Out.Resolution = (In.Resolution + In.Origin) - Out.Origin;
|
|
return Out;
|
|
}
|
|
|
|
// Find a valid slot among the free rects
|
|
static bool FindFreeRect(FAtlasLayout& Layout, FAtlasSlot& Slot, int32 Alignement)
|
|
{
|
|
#if USE_WASTE_RECT
|
|
// 1. Find the smallest free rect than can contains the slot request
|
|
int32 BestFreeRectIt = -1;
|
|
int32 BestFreeRectResX = Layout.AtlasResolution.X * 2;
|
|
FAtlasRect BestAlignedFreeRect;
|
|
for (int32 FreeIt = 0, FreeCount = Layout.FreeRects.Num(); FreeIt < FreeCount; ++FreeIt)
|
|
{
|
|
// Compute the aligned free rect, to respect the slot alignement requirement
|
|
const FAtlasRect& FreeRect = Layout.FreeRects[FreeIt];
|
|
const FAtlasRect AlignedFreeRect = ComputeAlignedRect(FreeRect, Alignement);
|
|
if (CanContain(AlignedFreeRect, Slot.Rect) && AlignedFreeRect.Resolution.X < BestFreeRectResX)
|
|
{
|
|
BestFreeRectIt = FreeIt;
|
|
BestFreeRectResX = FreeRect.Resolution.X;
|
|
BestAlignedFreeRect = AlignedFreeRect;
|
|
}
|
|
}
|
|
|
|
// 2. If found, split the rest of the valid free rect into two parts
|
|
// --------- -----
|
|
// |Slot| | | |
|
|
// | | | -> |Free0|
|
|
// |---- | ----- | |
|
|
// | | |Free1| | |
|
|
// --------- ----- -----
|
|
// Fitted new New free
|
|
// slot rects
|
|
const bool bFit = BestFreeRectIt != -1;
|
|
if (bFit)
|
|
{
|
|
// Assign the slot origin
|
|
Slot.Rect.Origin = BestAlignedFreeRect.Origin;
|
|
|
|
// Find the split for the two new rects
|
|
const FAtlasRect BestFreeRect = Layout.FreeRects[BestFreeRectIt];
|
|
check(BestFreeRect.Origin.X >= 0 && BestFreeRect.Origin.Y >= 0);
|
|
Layout.FreeRects.RemoveAtSwap(BestFreeRectIt);
|
|
|
|
// If the new Slot doesn't fit the rect, split the remaining empty space into new free rects
|
|
#if 1
|
|
const bool bFitX = Slot.Rect.Origin.X + Slot.Rect.Resolution.X == BestFreeRect.Origin.X + BestFreeRect.Resolution.X;
|
|
const bool bFitY = Slot.Rect.Origin.Y + Slot.Rect.Resolution.Y == BestFreeRect.Origin.Y + BestFreeRect.Resolution.Y;
|
|
// 1. Perfect fit, or right-fit
|
|
// ---------
|
|
// ||'''''''||
|
|
// || ||
|
|
// || ||
|
|
// ||.......||
|
|
// ---------
|
|
if (bFitX && bFitY)
|
|
{
|
|
// The slot takes all the free. rect space. Nothing to do.
|
|
}
|
|
// 2. Width fit
|
|
// ---------
|
|
// ||'''''''||
|
|
// ||.......||
|
|
// | |
|
|
// | |
|
|
// ---------
|
|
else if (bFitX)
|
|
{
|
|
FAtlasRect& FreeRect = Layout.FreeRects.AddDefaulted_GetRef();
|
|
FreeRect.Origin.X = Slot.Rect.Origin.X + Slot.Rect.Resolution.X;
|
|
FreeRect.Origin.Y = BestFreeRect.Origin.Y;
|
|
FreeRect.Resolution.X =(BestFreeRect.Origin.X+ BestFreeRect.Resolution.X) - FreeRect.Origin.X;
|
|
FreeRect.Resolution.Y = BestFreeRect.Resolution.Y;
|
|
}
|
|
// 3. Height fit
|
|
// ---------
|
|
// ||''''| |
|
|
// || | |
|
|
// || | |
|
|
// ||....| |
|
|
// ---------
|
|
else if (bFitY)
|
|
{
|
|
FAtlasRect& FreeRect = Layout.FreeRects.AddDefaulted_GetRef();
|
|
FreeRect.Origin.X = BestFreeRect.Origin.X;
|
|
FreeRect.Origin.Y = Slot.Rect.Origin.Y + Slot.Rect.Resolution.Y;
|
|
FreeRect.Resolution.X = BestFreeRect.Resolution.X;
|
|
FreeRect.Resolution.Y = (BestFreeRect.Origin.Y + BestFreeRect.Resolution.Y) - FreeRect.Origin.Y;
|
|
}
|
|
// 4. Two splits
|
|
// ---------
|
|
// ||''''| |
|
|
// || | |
|
|
// ||....| |
|
|
// | |
|
|
// ---------
|
|
else
|
|
{
|
|
{
|
|
FAtlasRect& FreeRect = Layout.FreeRects.AddDefaulted_GetRef();
|
|
FreeRect.Origin.X = BestFreeRect.Origin.X;
|
|
FreeRect.Origin.Y = Slot.Rect.Origin.Y + Slot.Rect.Resolution.Y;
|
|
FreeRect.Resolution.X =(Slot.Rect.Origin.X + Slot.Rect.Resolution.X) - FreeRect.Origin.X;
|
|
FreeRect.Resolution.Y =(BestFreeRect.Origin.Y + BestFreeRect.Resolution.Y) - FreeRect.Origin.Y;
|
|
}
|
|
{
|
|
FAtlasRect& FreeRect = Layout.FreeRects.AddDefaulted_GetRef();
|
|
FreeRect.Origin.X = Slot.Rect.Origin.X + Slot.Rect.Resolution.X;
|
|
FreeRect.Origin.Y = BestFreeRect.Origin.Y;
|
|
FreeRect.Resolution.X =(BestFreeRect.Origin.X + BestFreeRect.Resolution.X) - FreeRect.Origin.X;
|
|
FreeRect.Resolution.Y = BestFreeRect.Resolution.Y;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return bFit;
|
|
#endif // USE_WASTE_RECT
|
|
}
|
|
|
|
|
|
// Fit a new slot into the curent layout. Returns true if the slot can be fitted, false otherwise
|
|
static bool FitAlignedSlot(FAtlasLayout& Layout, FAtlasSlot& Slot)
|
|
#if USE_PACKING_MODE == 0
|
|
// Use Shelf packing algo.
|
|
{
|
|
// Due to MIPs we need to ensure that the rect is aligned onto its pow2 resolution
|
|
const uint32 MaxMIP = GetSlotMaxMIPLevel(Slot);
|
|
const int32 Alignement = 1u << MaxMIP;
|
|
|
|
// 1. Find a valid slot among the free rects
|
|
bool bFit = FindFreeRect(Layout, Slot, Alignement);
|
|
|
|
// 2. Try to fit slot
|
|
if (!bFit)
|
|
{
|
|
|
|
const FIntPoint AlignedSplit(
|
|
FMath::DivideAndRoundUp(Layout.SplitX, Alignement) * Alignement,
|
|
FMath::DivideAndRoundUp(Layout.SplitY, Alignement) * Alignement);
|
|
const int32 AlignedMaxY = FMath::DivideAndRoundUp(Layout.MaxY, Alignement) * Alignement;
|
|
|
|
// 2.1. Try to fit into the current row
|
|
if (
|
|
AlignedSplit.X + Slot.Rect.Resolution.X <= Layout.AtlasResolution.X &&
|
|
AlignedSplit.Y + Slot.Rect.Resolution.Y <= Layout.AtlasResolution.Y)
|
|
{
|
|
//Slot.Origin = FIntPoint(Layout.SplitX, Layout.SplitY);
|
|
Slot.Rect.Origin = FIntPoint(AlignedSplit.X, AlignedSplit.Y);
|
|
Layout.SplitX += Slot.Rect.Resolution.X;
|
|
Layout.MaxY = FMath::Max(Layout.MaxY, AlignedSplit.Y + Slot.Rect.Resolution.Y);
|
|
}
|
|
// 2.2. Try to fit in a the next row
|
|
else if (
|
|
Slot.Rect.Resolution.X <= Layout.AtlasResolution.X &&
|
|
AlignedMaxY + Slot.Rect.Resolution.Y <= Layout.AtlasResolution.Y)
|
|
{
|
|
Layout.SplitX = 0;
|
|
Layout.SplitY = Layout.MaxY;
|
|
Slot.Rect.Origin = FIntPoint(Layout.SplitX, AlignedMaxY);
|
|
|
|
Layout.SplitX = Slot.Rect.Resolution.X;
|
|
Layout.MaxY = AlignedMaxY + Slot.Rect.Resolution.Y;
|
|
}
|
|
// 2.3. It doesn't fit with the current atlas resolution
|
|
else
|
|
{
|
|
bFit = false;
|
|
}
|
|
}
|
|
|
|
return bFit;
|
|
}
|
|
#elif USE_PACKING_MODE == 1
|
|
// Use skyline packing algo.
|
|
{
|
|
// Due to MIPs we need to ensure that the rect is aligned onto its pow2 resolution
|
|
const uint32 MaxMIP = GetSlotMaxMIPLevel(Slot);
|
|
const int32 Alignement = 1u << MaxMIP;
|
|
|
|
// 0. If the layout is verbatim, initialize the horizon data
|
|
if (Layout.Horizons.IsEmpty())
|
|
{
|
|
FAtlasHorizon& H = Layout.Horizons.AddDefaulted_GetRef();
|
|
H.Line.Origin = FIntPoint(0, 0);
|
|
H.Line.Resolution = Layout.AtlasResolution;
|
|
H.ExtendedLine = H.Line;
|
|
}
|
|
|
|
// 1. Find a valid slot among the free rects
|
|
bool bFit = FindFreeRect(Layout, Slot, Alignement);
|
|
|
|
// 2. Fit rectangle within the existing horizon
|
|
if (!bFit)
|
|
{
|
|
// 2.2 Find the horizon line which result in the lowest horizon after update
|
|
int32 MinHeight = Layout.AtlasResolution.Y * 2; // *2 to ensure MinHeight is initialized with a larger height than the default horizon (i.e., full atlas)
|
|
int32 BestHorizonIt = -1;
|
|
FAtlasRect BestAlignedHorizon;
|
|
for (int32 HorizonIt = 0, HorizonCount = Layout.Horizons.Num(); HorizonIt < HorizonCount; ++HorizonIt)
|
|
{
|
|
// Compute the aligned horizon for taking into account filtering/MIP-mapping requirement
|
|
const FAtlasRect AlignedHorizon = ComputeAlignedRect(Layout.Horizons[HorizonIt].ExtendedLine, Alignement);
|
|
if (CanContain(AlignedHorizon, Slot.Rect))
|
|
{
|
|
if (AlignedHorizon.Origin.Y + Slot.Rect.Resolution.Y < MinHeight)
|
|
{
|
|
MinHeight = AlignedHorizon.Origin.Y + Slot.Rect.Resolution.Y;
|
|
BestAlignedHorizon = AlignedHorizon;
|
|
BestHorizonIt = HorizonIt;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2.3. Insert & update horizon
|
|
bFit = BestHorizonIt != -1;
|
|
if (bFit)
|
|
{
|
|
// The slot is inserted with Left-most insertion.
|
|
// TODO compute waste space heuristic left/right to pick the best
|
|
Slot.Rect.Origin = BestAlignedHorizon.Origin;
|
|
|
|
// 2.3.1 Update Horizon segments
|
|
const uint32 SlotY = Slot.Rect.Origin.Y + Slot.Rect.Resolution.Y;
|
|
const uint32 StartX = Slot.Rect.Origin.X;
|
|
const uint32 EndX = Slot.Rect.Origin.X + Slot.Rect.Resolution.X;
|
|
{
|
|
TArray<FAtlasHorizon> NewHorizonRects;
|
|
NewHorizonRects.Reserve(Layout.Horizons.Num());
|
|
for (int32 HorizonIt = 0, HorizonCount = Layout.Horizons.Num(); HorizonIt < HorizonCount; ++HorizonIt)
|
|
{
|
|
const FAtlasHorizon& Horizon = Layout.Horizons[HorizonIt];
|
|
const uint32 HorizonStartX = Horizon.Line.Origin.X;
|
|
const uint32 HorizonEndX = Horizon.Line.Origin.X + Horizon.Line.Resolution.X;
|
|
const uint32 HorizonY = Horizon.Line.Origin.Y;
|
|
|
|
const bool bIntersection = !(EndX < HorizonStartX || StartX > HorizonEndX);
|
|
if (bIntersection)
|
|
{
|
|
// Sanity check
|
|
//check(HorizonY <= SlotY);
|
|
|
|
// 1. Complete cover
|
|
// Slot |----------| -> Output |----------|
|
|
// Horizon |---| ->
|
|
if (StartX <= HorizonStartX && EndX >= HorizonEndX)
|
|
{
|
|
if ((StartX == HorizonStartX && EndX == HorizonEndX) || StartX == HorizonStartX)
|
|
{
|
|
FAtlasHorizon& H = NewHorizonRects.AddDefaulted_GetRef();
|
|
H.Line.Origin.X = StartX;
|
|
H.Line.Origin.Y = SlotY;
|
|
H.Line.Resolution.X = EndX - StartX;
|
|
H.Line.Resolution.Y = Layout.AtlasResolution.Y - SlotY;
|
|
H.ExtendedLine = H.Line;
|
|
}
|
|
|
|
// Skip horizon line, as it is totally occluded
|
|
}
|
|
// 2. Inside cover
|
|
// Slot |---| -> Output |---|
|
|
// Horizon |----------| -> |--| |---|
|
|
else if (StartX > HorizonStartX && EndX < HorizonEndX)
|
|
{
|
|
{
|
|
FAtlasHorizon& H = NewHorizonRects.AddDefaulted_GetRef();
|
|
H.Line.Origin.X = HorizonStartX;
|
|
H.Line.Origin.Y = HorizonY;
|
|
H.Line.Resolution.X = StartX - HorizonStartX;
|
|
H.Line.Resolution.Y = Layout.AtlasResolution.Y - HorizonY;
|
|
H.ExtendedLine = H.Line;
|
|
}
|
|
|
|
{
|
|
FAtlasHorizon& H = NewHorizonRects.AddDefaulted_GetRef();
|
|
H.Line.Origin.X = StartX;
|
|
H.Line.Origin.Y = SlotY;
|
|
H.Line.Resolution.X = EndX - StartX;
|
|
H.Line.Resolution.Y = Layout.AtlasResolution.Y - SlotY;
|
|
H.ExtendedLine = H.Line;
|
|
}
|
|
|
|
{
|
|
FAtlasHorizon& H = NewHorizonRects.AddDefaulted_GetRef();
|
|
H.Line.Origin.X = EndX;
|
|
H.Line.Origin.Y = HorizonY;
|
|
H.Line.Resolution.X = HorizonEndX - EndX;
|
|
H.Line.Resolution.Y = Layout.AtlasResolution.Y - HorizonY;
|
|
H.ExtendedLine = H.Line;
|
|
}
|
|
}
|
|
// 3. Left cover
|
|
// Slot |---| -> Output |--|
|
|
// Horizon |----------| -> |-------|
|
|
else if (StartX <= HorizonStartX && EndX < HorizonEndX)
|
|
{
|
|
//if (StartX == HorizonStartX && SlotY < uint32(Layout.AtlasResolution.Y))
|
|
{
|
|
FAtlasHorizon& H = NewHorizonRects.AddDefaulted_GetRef();
|
|
H.Line.Origin.X = StartX;
|
|
H.Line.Origin.Y = SlotY;
|
|
H.Line.Resolution.X = EndX - StartX;
|
|
H.Line.Resolution.Y = Layout.AtlasResolution.Y - SlotY;
|
|
H.ExtendedLine = H.Line;
|
|
}
|
|
|
|
{
|
|
FAtlasHorizon& H = NewHorizonRects.AddDefaulted_GetRef();
|
|
H.Line.Origin.X = EndX;
|
|
H.Line.Origin.Y = HorizonY;
|
|
H.Line.Resolution.X = HorizonEndX - EndX;
|
|
H.Line.Resolution.Y = Layout.AtlasResolution.Y - HorizonY;
|
|
H.ExtendedLine = H.Line;
|
|
}
|
|
}
|
|
// 4. Right cover
|
|
// Slot |---| -> Output |--|
|
|
// Horizon |----------| -> |-------|
|
|
else if (StartX > HorizonStartX && EndX <= HorizonEndX)
|
|
{
|
|
{
|
|
FAtlasHorizon& H = NewHorizonRects.AddDefaulted_GetRef();
|
|
H.Line.Origin.X = HorizonStartX;
|
|
H.Line.Origin.Y = HorizonY;
|
|
H.Line.Resolution.X = StartX - HorizonStartX;
|
|
H.Line.Resolution.Y = Layout.AtlasResolution.Y - HorizonY;
|
|
H.ExtendedLine = H.Line;
|
|
}
|
|
|
|
{
|
|
FAtlasHorizon& H = NewHorizonRects.AddDefaulted_GetRef();
|
|
H.Line.Origin.X = StartX;
|
|
H.Line.Origin.Y = SlotY;
|
|
H.Line.Resolution.X = EndX - StartX;
|
|
H.Line.Resolution.Y = Layout.AtlasResolution.Y - SlotY;
|
|
H.ExtendedLine = H.Line;
|
|
}
|
|
}
|
|
// 5. Right cover with overflow (merge this case with 4.)
|
|
// Slot |------| -> Output |------|
|
|
// Horizon |----------| -> |------|
|
|
else if (StartX > HorizonStartX && StartX < HorizonEndX)
|
|
{
|
|
{
|
|
FAtlasHorizon& H = NewHorizonRects.AddDefaulted_GetRef();
|
|
H.Line.Origin.X = HorizonStartX;
|
|
H.Line.Origin.Y = HorizonY;
|
|
H.Line.Resolution.X = StartX - HorizonStartX;
|
|
H.Line.Resolution.Y = Layout.AtlasResolution.Y - HorizonY;
|
|
H.ExtendedLine = H.Line;
|
|
}
|
|
|
|
{
|
|
FAtlasHorizon& H = NewHorizonRects.AddDefaulted_GetRef();
|
|
H.Line.Origin.X = StartX;
|
|
H.Line.Origin.Y = SlotY;
|
|
H.Line.Resolution.X = EndX - StartX;
|
|
H.Line.Resolution.Y = Layout.AtlasResolution.Y - SlotY;
|
|
H.ExtendedLine = H.Line;
|
|
}
|
|
}
|
|
// 6. Left cover with overflow (merge this case with 3.)
|
|
// Slot |-----| -> Output |-----|
|
|
// Horizon |----------| -> |----|
|
|
else if (EndX > StartX && EndX < HorizonEndX)
|
|
{
|
|
{
|
|
FAtlasHorizon& H = NewHorizonRects.AddDefaulted_GetRef();
|
|
H.Line.Origin.X = EndX;
|
|
H.Line.Origin.Y = HorizonY;
|
|
H.Line.Resolution.X = HorizonEndX - EndX;
|
|
H.Line.Resolution.Y = Layout.AtlasResolution.Y - HorizonY;
|
|
H.ExtendedLine = H.Line;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Sanity check
|
|
check(StartX == HorizonEndX);
|
|
NewHorizonRects.Add(Horizon);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FAtlasHorizon& NewHorizon = NewHorizonRects.Add_GetRef(Horizon);
|
|
NewHorizon.ExtendedLine = NewHorizon.Line;
|
|
}
|
|
}
|
|
Layout.Horizons = NewHorizonRects;
|
|
|
|
// 2.3.2 Rebuild extended horizon data
|
|
// The first step (Left->Right) could be done during the horizon building)
|
|
if (Layout.Horizons.Num())
|
|
{
|
|
// Extend left-horizon by sweeping: Left -> Right
|
|
{
|
|
TArray<FIntPoint> VisibilityStack;
|
|
VisibilityStack.Add(FIntPoint(0, Layout.AtlasResolution.Y+1));
|
|
for (int32 HorizonIt = 0, HorizonCount = Layout.Horizons.Num(); HorizonIt < HorizonCount; ++HorizonIt)
|
|
{
|
|
FAtlasHorizon& H = Layout.Horizons[HorizonIt];
|
|
|
|
if (H.Line.Origin.Y >= VisibilityStack.Last().Y)
|
|
{
|
|
while (H.Line.Origin.Y >= VisibilityStack.Last().Y)
|
|
{
|
|
VisibilityStack.Pop(EAllowShrinking::No);
|
|
}
|
|
|
|
// Sanity
|
|
check(!VisibilityStack.IsEmpty());
|
|
}
|
|
|
|
H.ExtendedLine.Origin.X = VisibilityStack.Last().X;
|
|
H.ExtendedLine.Resolution.X += (H.Line.Origin.X - H.ExtendedLine.Origin.X);
|
|
VisibilityStack.Add(FIntPoint(H.Line.Origin.X + H.Line.Resolution.X, H.Line.Origin.Y));
|
|
}
|
|
}
|
|
|
|
// Extend right-horizon by sweeping: Right -> Left
|
|
{
|
|
TArray<FIntPoint> VisibilityStack;
|
|
VisibilityStack.Add(FIntPoint(Layout.AtlasResolution.X, Layout.AtlasResolution.Y+1));
|
|
for (int32 HorizonIt = Layout.Horizons.Num() - 1; HorizonIt >= 0; --HorizonIt)
|
|
{
|
|
FAtlasHorizon& H = Layout.Horizons[HorizonIt];
|
|
|
|
if (H.Line.Origin.Y >= VisibilityStack.Last().Y)
|
|
{
|
|
while (H.Line.Origin.Y >= VisibilityStack.Last().Y)
|
|
{
|
|
VisibilityStack.Pop(EAllowShrinking::No);
|
|
}
|
|
|
|
// Sanity
|
|
check(!VisibilityStack.IsEmpty());
|
|
}
|
|
|
|
H.ExtendedLine.Resolution.X = VisibilityStack.Last().X - H.ExtendedLine.Origin.X;
|
|
VisibilityStack.Add(H.Line.Origin);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bFit;
|
|
}
|
|
#elif USE_PACKING_MODE == 2
|
|
// Use FTextureLayout packer
|
|
{
|
|
uint32 OriginX = 0;
|
|
uint32 OriginY = 0;
|
|
const bool bFit = Layout.Packer.AddElement(OriginX, OriginY, Slot.Rect.Resolution.X, Slot.Rect.Resolution.Y);
|
|
Slot.Rect.Origin.X = OriginX;
|
|
Slot.Rect.Origin.Y = OriginY;
|
|
return bFit;
|
|
}
|
|
#endif
|
|
|
|
// Update the atlas layout (book-keeping) with the remove slots
|
|
static void CleanAtlas(
|
|
FAtlasLayout& InLayout,
|
|
const TArray<FAtlasRect>& InDeleteSlots)
|
|
{
|
|
for (const FAtlasRect& Rect : InDeleteSlots)
|
|
{
|
|
#if USE_WASTE_RECT && (USE_PACKING_MODE == 1 || USE_PACKING_MODE == 2)
|
|
if (Rect.Origin.X >= 0 && Rect.Origin.Y >= 0)
|
|
{
|
|
InLayout.FreeRects.Add(Rect);
|
|
}
|
|
#elif USE_PACKING_MODE ==2
|
|
InLayout.Packer.RemoveElement(Rect.Origin.X, Rect.Origin.Y, Rect.Resolution.X, Rect.Resolution.Y);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Fit a set of slots within an existing altas layout, or create a new layout.
|
|
// * Try to fit the new slot within the existing layout.
|
|
// * If it fails, produce a new atlas layout.
|
|
static void PackAtlas(
|
|
FAtlasLayout& Layout,
|
|
const TArray<FAtlasSlot>& InSlots,
|
|
TArray<FAtlasCopySlot>& CopySlots,
|
|
TArray<FAtlasSlot>& NewSlots)
|
|
{
|
|
const int32 MaxAtlasResolution = CVarRectLightTextureResolution.GetValueOnRenderThread();
|
|
|
|
// Extract slots which are already parts of the current layout from the new/requested slots.
|
|
// Estimate of the target resolution;
|
|
uint32 TargetPixelCount = 0;
|
|
TArray<FAtlasSlot> ValidSlots;
|
|
TArray<FAtlasSlot> ValidNewSlots;
|
|
ValidSlots.Reserve(InSlots.Num());
|
|
ValidNewSlots.Reserve(InSlots.Num());
|
|
for (const FAtlasSlot& Slot : InSlots)
|
|
{
|
|
if (Slot.IsValid())
|
|
{
|
|
FAtlasSlot& ValidSlot = ValidSlots.Add_GetRef(Slot);
|
|
|
|
// Check if the texture resolution has changed (due to streaming)
|
|
// If the texture resolution has change, reset the slot to be handled as a new slot
|
|
const FIntPoint TextureResolution = ValidSlot.GetSourceResolution();
|
|
const bool bHasTextureResolutionChanged = ValidSlot.CachedSourceTextureResolution != TextureResolution;
|
|
if (bHasTextureResolutionChanged || ValidSlot.bForceRefresh)
|
|
{
|
|
ValidSlot.Rect.Origin = InvalidOrigin;
|
|
ValidSlot.Rect.Resolution = TextureResolution;
|
|
AlignSlotResolution(ValidSlot);
|
|
ValidSlot.CachedSourceTextureResolution = TextureResolution;
|
|
}
|
|
|
|
TargetPixelCount += ValidSlot.Rect.Resolution.X * ValidSlot.Rect.Resolution.Y;
|
|
|
|
if (ValidSlot.Rect.Origin == InvalidOrigin)
|
|
{
|
|
ValidNewSlots.Add(ValidSlot);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize atlas resolution the first time
|
|
if (Layout.AtlasResolution == FIntPoint::ZeroValue)
|
|
{
|
|
const float HeuristicFactor = 1.0f;
|
|
Layout.AtlasResolution = FMath::RoundUpToPowerOfTwo(FMath::Sqrt(float(TargetPixelCount)) * HeuristicFactor);
|
|
Layout.AtlasResolution.X = FMath::Min(MaxAtlasResolution, Layout.AtlasResolution.X);
|
|
Layout.AtlasResolution.Y = FMath::Min(MaxAtlasResolution, Layout.AtlasResolution.Y);
|
|
}
|
|
|
|
// Sort rect by perimeter
|
|
ValidSlots.Sort ([](const FAtlasSlot& A, const FAtlasSlot& B) { return (A.Rect.Resolution.X + A.Rect.Resolution.Y) > (B.Rect.Resolution.X + B.Rect.Resolution.Y); });
|
|
ValidNewSlots.Sort ([](const FAtlasSlot& A, const FAtlasSlot& B) { return (A.Rect.Resolution.X + A.Rect.Resolution.Y) > (B.Rect.Resolution.X + B.Rect.Resolution.Y); });
|
|
|
|
// 1. Try to fit the new slots into the current layout
|
|
bool bNeedRefit = false;
|
|
{
|
|
for (const FAtlasSlot& Slot : ValidNewSlots)
|
|
{
|
|
FAtlasSlot NewSlot = Slot;
|
|
if (!FitAlignedSlot(Layout, NewSlot))
|
|
{
|
|
bNeedRefit = true;
|
|
break;
|
|
}
|
|
NewSlots.Add(NewSlot);
|
|
}
|
|
}
|
|
|
|
// 2. Couldn't fit the new textures into the current layout. Refit the atlas layout
|
|
// * Use Simple row packing algo.
|
|
// * First iteration try to repack the texure within the existing resolution
|
|
// * Subsequent iteration increaes atlas resolution to fit the textures
|
|
if (bNeedRefit)
|
|
{
|
|
CopySlots.Reserve(ValidSlots.Num());
|
|
CopySlots.SetNum(0, EAllowShrinking::No);
|
|
NewSlots.SetNum(0, EAllowShrinking::No);
|
|
|
|
FIntPoint CurrentAtlasResolution = Layout.AtlasResolution;
|
|
int32 CurrentSourceTextureMIPBias = 0; // When a refit is done, restart with a MIP bias of 0, to ensure we use the full potential space of the atlas
|
|
bool bIsPackingValid = false;
|
|
while (!bIsPackingValid)
|
|
{
|
|
// 2.0 Reset atlas layout
|
|
Layout = FAtlasLayout(CurrentAtlasResolution);
|
|
Layout.AtlasResolution = CurrentAtlasResolution;
|
|
Layout.SourceTextureMIPBias = CurrentSourceTextureMIPBias;
|
|
|
|
// 2.1 Try to fit all slots
|
|
bool bFit = true;
|
|
for (const FAtlasSlot& SrcSlot : ValidSlots)
|
|
{
|
|
FAtlasSlot DstSlot = SrcSlot;
|
|
if (!FitAlignedSlot(Layout, DstSlot))
|
|
{
|
|
bFit = false;
|
|
break;
|
|
}
|
|
|
|
const bool bIsNewSlot = SrcSlot.Rect.Origin == InvalidOrigin;
|
|
if (bIsNewSlot)
|
|
{
|
|
NewSlots.Add(DstSlot);
|
|
}
|
|
else
|
|
{
|
|
FAtlasCopySlot& CopySlot = CopySlots.AddDefaulted_GetRef();
|
|
CopySlot.Id = SrcSlot.Id;
|
|
CopySlot.Resolution = SrcSlot.Rect.Resolution;
|
|
CopySlot.SrcOrigin = SrcSlot.Rect.Origin;
|
|
CopySlot.DstOrigin = DstSlot.Rect.Origin;
|
|
CopySlot.SourceTexture = SrcSlot.SourceTexture;
|
|
}
|
|
}
|
|
|
|
// 2.2 If the current slots don't fit, increase atlas resolution
|
|
if (!bFit)
|
|
{
|
|
// Ensure the atlas resolution fit under the user requirement.
|
|
// Otherwise starts to drop SourceTexture resolution
|
|
if (Layout.AtlasResolution.X * 2 <= MaxAtlasResolution && Layout.AtlasResolution.Y * 2 <= MaxAtlasResolution)
|
|
{
|
|
CurrentAtlasResolution = Layout.AtlasResolution * 2;
|
|
}
|
|
else
|
|
{
|
|
// Mark all textures as invalid to force copy them into the atlas with a lower mip index
|
|
CurrentSourceTextureMIPBias++;
|
|
for (FAtlasSlot& Slot : ValidSlots)
|
|
{
|
|
const FIntPoint SourceResolution = Slot.GetSourceResolution();
|
|
Slot.Rect.Origin = InvalidOrigin;
|
|
Slot.Rect.Resolution = ToMIP(SourceResolution, CurrentSourceTextureMIPBias);
|
|
Slot.CachedSourceTextureResolution = Slot.Rect.Resolution;
|
|
AlignSlotResolution(Slot);
|
|
}
|
|
}
|
|
|
|
CopySlots.SetNum(0, EAllowShrinking::No);
|
|
NewSlots.SetNum(0, EAllowShrinking::No);
|
|
}
|
|
else
|
|
{
|
|
bIsPackingValid = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// API
|
|
|
|
uint32 AddTexture(UTexture* In, const FVector4f& ScaleOffset)
|
|
{
|
|
check(IsInRenderingThread());
|
|
|
|
uint32 SlotIndex = InvalidSlotIndex;
|
|
if (In)
|
|
{
|
|
// 1. Find if the texture already exist (simple linear search assuming a low number of textures)
|
|
bool bFound = false;
|
|
for (FAtlasSlot& Slot : GRectLightTextureManager.AtlasSlots)
|
|
{
|
|
if ((Slot.SourceTexture == In->TextureReference.TextureReferenceRHI) &&
|
|
(Slot.SourceScaleOffset == ScaleOffset))
|
|
{
|
|
Slot.RefCount++;
|
|
SlotIndex = Slot.Id;
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 2. If not found, then add an atlas slot for this new texture
|
|
if (!bFound)
|
|
{
|
|
FAtlasSlot* Slot = nullptr;
|
|
if (GRectLightTextureManager.FreeSlots.Dequeue(SlotIndex))
|
|
{
|
|
Slot = &GRectLightTextureManager.AtlasSlots[SlotIndex];
|
|
}
|
|
else
|
|
{
|
|
SlotIndex = GRectLightTextureManager.AtlasSlots.Num();
|
|
Slot = &GRectLightTextureManager.AtlasSlots.AddDefaulted_GetRef();
|
|
}
|
|
|
|
const FRHITexture* Tex = In->TextureReference.TextureReferenceRHI;
|
|
|
|
*Slot = FAtlasSlot();
|
|
Slot->SourceTexture = In->TextureReference.TextureReferenceRHI;
|
|
Slot->SourceScaleOffset = ScaleOffset;
|
|
Slot->Id = SlotIndex;
|
|
Slot->Rect.Origin = InvalidOrigin;
|
|
Slot->Rect.Resolution = GetSourceResolution(Tex, ScaleOffset);
|
|
Slot->CachedSourceTextureResolution = Slot->Rect.Resolution;
|
|
Slot->RefCount = 1;
|
|
|
|
// Force alignement based on the max. MIP-level
|
|
AlignSlotResolution(*Slot);
|
|
|
|
GRectLightTextureManager.bHasPendingAdds = true;
|
|
}
|
|
}
|
|
return SlotIndex;
|
|
}
|
|
|
|
void RemoveTexture(uint32 InSlotIndex)
|
|
{
|
|
check(IsInRenderingThread());
|
|
|
|
if (InSlotIndex != InvalidSlotIndex && InSlotIndex < uint32(GRectLightTextureManager.AtlasSlots.Num()))
|
|
{
|
|
// If it is the last light referencing this texture, we retires the atlas slot
|
|
if (--GRectLightTextureManager.AtlasSlots[InSlotIndex].RefCount == 0)
|
|
{
|
|
// Add pending slots to clean-up the layout during the next update call
|
|
GRectLightTextureManager.DeletedSlots.Add(GRectLightTextureManager.AtlasSlots[InSlotIndex].Rect);
|
|
|
|
const int32 SlotCount = GRectLightTextureManager.AtlasSlots.Num();
|
|
if (InSlotIndex == SlotCount-1)
|
|
{
|
|
GRectLightTextureManager.AtlasSlots.SetNum(SlotCount-1);
|
|
}
|
|
else
|
|
{
|
|
GRectLightTextureManager.FreeSlots.Enqueue(InSlotIndex);
|
|
GRectLightTextureManager.AtlasSlots[InSlotIndex] = FAtlasSlot();
|
|
}
|
|
|
|
GRectLightTextureManager.bHasPendingDeletes = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
FAtlasSlotDesc GetAtlasSlot(uint32 InSlotIndex)
|
|
{
|
|
FAtlasSlotDesc Out;
|
|
Out.UVOffset = FVector2f(0,0);
|
|
Out.UVScale = FVector2f(0, 0);
|
|
Out.MaxMipLevel = FLightRenderParameters::GetRectLightAtlasInvalidMIPLevel();
|
|
|
|
if (InSlotIndex < uint32(GRectLightTextureManager.AtlasSlots.Num()) && GRectLightTextureManager.AtlasTexture)
|
|
{
|
|
const FAtlasSlot& Slot = GRectLightTextureManager.AtlasSlots[InSlotIndex];
|
|
const FIntPoint Resolution = GRectLightTextureManager.AtlasTexture->GetDesc().Extent;
|
|
const FVector2f InvResolution(1.f / Resolution.X, 1.f / Resolution.Y);
|
|
|
|
// UV rect shrinking, to avoid leaking during tri/bi-linear filtering, is done within the shader, as it depends on the selected MIP value.
|
|
// UVOffset/UVScale gives the rect value without any border/clamping
|
|
Out.UVOffset = FVector2f(Slot.Rect.Origin.X * InvResolution.X, Slot.Rect.Origin.Y * InvResolution.Y);
|
|
Out.UVScale = FVector2f(Slot.Rect.Resolution.X * InvResolution.X, Slot.Rect.Resolution.Y * InvResolution.Y);
|
|
Out.MaxMipLevel = GetSlotMaxMIPLevel(Slot);
|
|
}
|
|
|
|
return Out;
|
|
}
|
|
|
|
static FRDGTextureRef CreateRectLightAtlasTexture(FRDGBuilder& GraphBuilder, const FIntPoint& Resolution, EPixelFormat AtlasFormat)
|
|
{
|
|
const uint32 MipCount = FMath::Log2(float(FMath::Min(Resolution.X, Resolution.Y)));
|
|
return GraphBuilder.CreateTexture(FRDGTextureDesc::Create2D(
|
|
Resolution,
|
|
AtlasFormat,
|
|
FClearValueBinding::Transparent,
|
|
ETextureCreateFlags::UAV | ETextureCreateFlags::ShaderResource | ETextureCreateFlags::RenderTargetable,
|
|
MipCount),
|
|
TEXT("RectLight.AtlasTexture"),
|
|
ERDGTextureFlags::MultiFrame);
|
|
}
|
|
|
|
void UpdateAtlasTexture(FRDGBuilder& GraphBuilder, const ERHIFeatureLevel::Type FeatureLevel)
|
|
{
|
|
if (GRectLightTextureManager.bLock)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Force update by resetting the atlas layout
|
|
static int32 CachedMaxAtlasResolution = CVarRectLightTextureResolution.GetValueOnRenderThread();
|
|
static EPixelFormat CachedAtlasFormat = GetRectLightAtlasFormat();
|
|
const EPixelFormat AtlasFormat = GetRectLightAtlasFormat();
|
|
const bool bForceUpdate = CVarRectLighForceUpdate.GetValueOnRenderThread() > 0 || CachedMaxAtlasResolution != CVarRectLightTextureResolution.GetValueOnRenderThread() || CachedAtlasFormat != AtlasFormat;
|
|
const bool bResetOnChange = CVarRectLighResetOnChange.GetValueOnRenderThread() > 0;
|
|
const bool bClearAtlas = CVarRectLighClearAtlas.GetValueOnRenderThread() > 0;
|
|
auto ResetAtlas = [&]()
|
|
{
|
|
CachedMaxAtlasResolution = CVarRectLightTextureResolution.GetValueOnRenderThread();
|
|
CachedAtlasFormat = AtlasFormat;
|
|
GRectLightTextureManager.bHasPendingAdds = true;
|
|
GRectLightTextureManager.AtlasLayout = FAtlasLayout(FIntPoint(CachedMaxAtlasResolution, CachedMaxAtlasResolution));
|
|
GRectLightTextureManager.DeletedSlots.Empty();
|
|
GRectLightTextureManager.FreeSlots.Empty();
|
|
for (FAtlasSlot& Slot : GRectLightTextureManager.AtlasSlots)
|
|
{
|
|
Slot.Rect.Origin = InvalidOrigin;
|
|
}
|
|
};
|
|
|
|
if (bForceUpdate)
|
|
{
|
|
ResetAtlas();
|
|
}
|
|
|
|
// Force update if among the existing valid slot a streamed a higher resolution than the existing one
|
|
uint32 RefreshRequestCount = 0;
|
|
{
|
|
for (FAtlasSlot& Slot : GRectLightTextureManager.AtlasSlots)
|
|
{
|
|
if (Slot.IsValid() && Slot.GetTextureRHI())
|
|
{
|
|
const FIntPoint SourceResolutionMIPed = ToMIP(Slot.GetSourceResolution(), GRectLightTextureManager.AtlasLayout.SourceTextureMIPBias);
|
|
if (SourceResolutionMIPed.X > Slot.Rect.Resolution.X || SourceResolutionMIPed.Y > Slot.Rect.Resolution.Y)
|
|
{
|
|
// Invalid the slot
|
|
Slot.Rect.Origin = InvalidOrigin;
|
|
GRectLightTextureManager.bHasPendingAdds = true;
|
|
}
|
|
if (Slot.bForceRefresh)
|
|
{
|
|
++RefreshRequestCount;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Force the atlas to be reset when some change are detected (optional)
|
|
if (GRectLightTextureManager.bHasPendingAdds && !bForceUpdate && bResetOnChange)
|
|
{
|
|
ResetAtlas();
|
|
}
|
|
|
|
// Update the atlas layout with the delete slots
|
|
if (GRectLightTextureManager.DeletedSlots.Num() > 0)
|
|
{
|
|
CleanAtlas(GRectLightTextureManager.AtlasLayout, GRectLightTextureManager.DeletedSlots);
|
|
GRectLightTextureManager.DeletedSlots.SetNum(0);
|
|
GRectLightTextureManager.bHasPendingDeletes = false;
|
|
}
|
|
|
|
// Process new atlas entries
|
|
if (GRectLightTextureManager.bHasPendingAdds)
|
|
{
|
|
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(FeatureLevel);
|
|
|
|
// 1. Compute new layout
|
|
TArray<FAtlasSlot> NewSlots;
|
|
TArray<FAtlasCopySlot> CopySlots;
|
|
PackAtlas(
|
|
GRectLightTextureManager.AtlasLayout,
|
|
GRectLightTextureManager.AtlasSlots,
|
|
CopySlots,
|
|
NewSlots);
|
|
|
|
// 2. Apply layout modification
|
|
const bool bHasChanged = !NewSlots.IsEmpty() || !CopySlots.IsEmpty();
|
|
if (bHasChanged)
|
|
{
|
|
// 2.0 Register or create the texture atlas
|
|
FRDGTextureRef AtlasTexture = nullptr;
|
|
bool bNeedExtraction = false;
|
|
const bool bRecreateAtlasTexture = GRectLightTextureManager.AtlasTexture == nullptr || (CopySlots.Num() == 0 && GRectLightTextureManager.AtlasTexture->GetDesc().Extent != GRectLightTextureManager.AtlasLayout.AtlasResolution);
|
|
if (bRecreateAtlasTexture)
|
|
{
|
|
AtlasTexture = CreateRectLightAtlasTexture(GraphBuilder, GRectLightTextureManager.AtlasLayout.AtlasResolution, AtlasFormat);
|
|
|
|
#if RECT_ATLAS_DEBUG_CLEAR
|
|
AddRectLightClearPass(GraphBuilder, ShaderMap, AtlasTexture);
|
|
#endif
|
|
|
|
bNeedExtraction = true;
|
|
// Sanity check
|
|
check(CopySlots.Num() == 0);
|
|
}
|
|
else
|
|
{
|
|
AtlasTexture = GraphBuilder.RegisterExternalTexture(GRectLightTextureManager.AtlasTexture);
|
|
|
|
// Clear the atlas (optional)
|
|
if (bClearAtlas && bResetOnChange && CopySlots.Num() == 0)
|
|
{
|
|
#if RECT_ATLAS_DEBUG_CLEAR
|
|
AddRectLightClearPass(GraphBuilder, ShaderMap, AtlasTexture);
|
|
#else
|
|
AddClearRenderTargetPass(GraphBuilder, AtlasTexture, FLinearColor::Transparent);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// 2.1 Copy slots from previous to new atlas texture
|
|
if (CopySlots.Num() > 0)
|
|
{
|
|
FRDGTextureRef NewAtlasTexture = CreateRectLightAtlasTexture(GraphBuilder, GRectLightTextureManager.AtlasLayout.AtlasResolution, AtlasFormat);
|
|
bNeedExtraction = true;
|
|
|
|
#if RECT_ATLAS_DEBUG_CLEAR
|
|
AddRectLightClearPass(GraphBuilder, ShaderMap, NewAtlasTexture);
|
|
#endif
|
|
|
|
CopySlotsPass(GraphBuilder, ShaderMap, CopySlots, AtlasTexture, NewAtlasTexture);
|
|
|
|
for (FAtlasCopySlot& Slot : CopySlots)
|
|
{
|
|
// Update:
|
|
// * the slot Origin based on the new layout & Resolution with new layout data
|
|
// * the slot Resolution in case the texture has streamed a higher-res version
|
|
GRectLightTextureManager.AtlasSlots[Slot.Id].Rect.Origin = Slot.DstOrigin;
|
|
GRectLightTextureManager.AtlasSlots[Slot.Id].Rect.Resolution = Slot.Resolution;
|
|
GRectLightTextureManager.AtlasSlots[Slot.Id].bForceRefresh = false;
|
|
}
|
|
AtlasTexture = NewAtlasTexture;
|
|
}
|
|
|
|
// 2.2 Insert & filter new slots into the atlas texture
|
|
if (NewSlots.Num() > 0)
|
|
{
|
|
AddSlotsPass(GraphBuilder, ShaderMap, GRectLightTextureManager.AtlasLayout.SourceTextureMIPBias, NewSlots, AtlasTexture);
|
|
FilterSlotsPass(GraphBuilder, ShaderMap, NewSlots, AtlasTexture);
|
|
|
|
for (FAtlasSlot& Slot : NewSlots)
|
|
{
|
|
// Update:
|
|
// * the slot Origin based on the new layout & Resolution with new layout data
|
|
// * the slot Resolution in case the texture has streamed a higher-res version
|
|
GRectLightTextureManager.AtlasSlots[Slot.Id].Rect.Origin = Slot.Rect.Origin;
|
|
GRectLightTextureManager.AtlasSlots[Slot.Id].Rect.Resolution = Slot.Rect.Resolution;
|
|
GRectLightTextureManager.AtlasSlots[Slot.Id].bForceRefresh = false;
|
|
}
|
|
}
|
|
|
|
if (bNeedExtraction)
|
|
{
|
|
GRectLightTextureManager.AtlasTexture = GraphBuilder.ConvertToExternalTexture(AtlasTexture);
|
|
}
|
|
}
|
|
|
|
GRectLightTextureManager.bHasPendingAdds = false;
|
|
}
|
|
// Process forced refresh slots
|
|
else if (RefreshRequestCount > 0)
|
|
{
|
|
TArray<FAtlasSlot> RefreshSlots;
|
|
RefreshSlots.Reserve(FMath::Max(1u, RefreshRequestCount));
|
|
for (FAtlasSlot& Slot : GRectLightTextureManager.AtlasSlots)
|
|
{
|
|
if (Slot.bForceRefresh)
|
|
{
|
|
Slot.bForceRefresh = false;
|
|
|
|
RefreshSlots.Add(Slot);
|
|
}
|
|
}
|
|
|
|
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(FeatureLevel);
|
|
FRDGTextureRef AtlasTexture = GraphBuilder.RegisterExternalTexture(GRectLightTextureManager.AtlasTexture);
|
|
AddSlotsPass(GraphBuilder, ShaderMap, GRectLightTextureManager.AtlasLayout.SourceTextureMIPBias, RefreshSlots, AtlasTexture);
|
|
FilterSlotsPass(GraphBuilder, ShaderMap, RefreshSlots, AtlasTexture);
|
|
}
|
|
}
|
|
|
|
void AddDebugPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, FRDGTextureRef OutputTexture)
|
|
{
|
|
if (CVarRectLighTextureDebug.GetValueOnRenderThread() > 0 && ShaderPrint::IsSupported(View.Family->GetShaderPlatform()))
|
|
{
|
|
if (FRDGTextureRef DebugOutput = AddRectLightDebugInfoPass(GraphBuilder, View, OutputTexture->Desc))
|
|
{
|
|
// Debug output is blend on top of the SceneColor/OutputTexture, as debug pass is a CS pass, and SceneColor/OutputTexture might not have a UAV flag
|
|
FCopyRectPS::FParameters* Parameters = GraphBuilder.AllocParameters<FCopyRectPS::FParameters>();
|
|
Parameters->InputTexture = DebugOutput;
|
|
Parameters->InputSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
|
|
Parameters->RenderTargets[0] = FRenderTargetBinding(OutputTexture, ERenderTargetLoadAction::ELoad);
|
|
|
|
const FScreenPassTextureViewport InputViewport(DebugOutput->Desc.Extent);
|
|
const FScreenPassTextureViewport OutputViewport(OutputTexture);
|
|
TShaderMapRef<FCopyRectPS> PixelShader(View.ShaderMap);
|
|
|
|
TShaderMapRef<FScreenPassVS> VertexShader(View.ShaderMap);
|
|
FRHIBlendState* BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_InverseSourceAlpha, BO_Add, BF_Zero, BF_One>::GetRHI();
|
|
FRHIDepthStencilState* DepthStencilState = FScreenPassPipelineState::FDefaultDepthStencilState::GetRHI();
|
|
|
|
AddDrawScreenPass(GraphBuilder, RDG_EVENT_NAME("RectLightAtlas::BlitDebug"), View, OutputViewport, InputViewport, VertexShader, PixelShader, BlendState, DepthStencilState, Parameters, EScreenPassDrawFlags::None);
|
|
}
|
|
}
|
|
}
|
|
|
|
FRHITexture* GetAtlasTexture()
|
|
{
|
|
return GRectLightTextureManager.AtlasTexture ? GRectLightTextureManager.AtlasTexture->GetRHI() : nullptr;
|
|
}
|
|
|
|
// Scope object allowing to force refresh of a slot texture, and locking/preventing
|
|
// the atlas update during the 'update'/'capture'
|
|
FAtlasTextureInvalidationScope::FAtlasTextureInvalidationScope(const UTexture* In)
|
|
{
|
|
if (In)
|
|
{
|
|
// Several rect lights can reference the same animated texture, but with different offset/scale.
|
|
// In this case we need to refresh all rects referencing this texture.
|
|
bool bHasBeenLockedLocally = false;
|
|
for (FAtlasSlot& Slot : GRectLightTextureManager.AtlasSlots)
|
|
{
|
|
// If the input texture is actually part of the atlas,
|
|
// then we lock the atlas to not update the atlas during
|
|
// the capture, and force the target texture to refresh
|
|
// its data during the next update
|
|
if (Slot.SourceTexture == In->TextureReference.TextureReferenceRHI)
|
|
{
|
|
if (!bHasBeenLockedLocally)
|
|
{
|
|
// Sanity check, allow a single lock/capture refresh at a time.
|
|
check(GRectLightTextureManager.bLock == false);
|
|
GRectLightTextureManager.bLock = true;
|
|
bHasBeenLockedLocally = true;
|
|
}
|
|
|
|
bLocked = true;
|
|
Slot.bForceRefresh = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FAtlasTextureInvalidationScope::~FAtlasTextureInvalidationScope()
|
|
{
|
|
if (bLocked)
|
|
{
|
|
// Sanity check, allow a single lock/capture refresh at a time.
|
|
check(GRectLightTextureManager.bLock == true);
|
|
|
|
GRectLightTextureManager.bLock = false;
|
|
bLocked = false;
|
|
}
|
|
}
|
|
|
|
} // namespace RectLightAtlas
|