391 lines
15 KiB
HLSL
391 lines
15 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
VirtualShadowMapPageAccessCommon.ush:
|
|
=============================================================================*/
|
|
#pragma once
|
|
|
|
#include "../Common.ush"
|
|
#include "../BitPacking.ush"
|
|
#include "/Engine/Shared/VirtualShadowMapDefinitions.h"
|
|
#include "VirtualShadowMapHandle.ush"
|
|
|
|
// NOTE: These page flags are combined hierarchically using bitwise *OR*, so plan/negate them appropriately
|
|
// Marks pages that are allocated
|
|
#define VSM_FLAG_ALLOCATED (1U << 0)
|
|
// Marks pages whose dynamic pages are uncached
|
|
#define VSM_FLAG_DYNAMIC_UNCACHED (1U << 1)
|
|
// Marks pages whose static pages are uncached
|
|
#define VSM_FLAG_STATIC_UNCACHED (1U << 2)
|
|
// Marks pages that are _not_ coarse (i.e., "normal" pages) that should include all geometry and conversely to mark geometry that is
|
|
// "detail geometry" and which can skip rendering to coarse pages
|
|
#define VSM_FLAG_DETAIL_GEOMETRY (1U << 3)
|
|
|
|
// Stored in the physical meta data flags and must be higher than the VSM_*_FLAG
|
|
// These are *NOT* valid to be used with hierarchical flag lookups!
|
|
|
|
// The next time this page is rendered, clear the dynamic portion and re-render
|
|
#define VSM_EXTENDED_FLAG_INVALIDATE_DYNAMIC (1U << 4)
|
|
// The next time this page is rendered, clear the static portion and re-render
|
|
#define VSM_EXTENDED_FLAG_INVALIDATE_STATIC (1U << 5)
|
|
// Propogated from the NaniteView for convenience; allows skipping separate static/dynamic rendering and associated merge
|
|
#define VSM_EXTENDED_FLAG_VIEW_UNCACHED (1U << 6)
|
|
// Set if anything is rendered into static page (i.e. regenerate HZB and merge), or if anything is rendered into the single page for uncached pages (regenerate HZB)
|
|
#define VSM_EXTENDED_FLAG_DIRTY (1U << 7)
|
|
// Set on physical pages that are unreferenced in the current render pass/frame. We can keep these alive and cached if space permits but otherwise ignore them.
|
|
#define VSM_EXTENDED_FLAG_UNREFERENCED (1U << 8)
|
|
// A bit messy, but this flag forces a page to be considered cached even if an UNCACHED flag is set.
|
|
// This is currently used so that we still consider these pages during rendering/instance cull (generally for WPO disable/enable purposes)
|
|
// but will not actually clear, draw, or merge them this frame.
|
|
#define VSM_EXTENDED_FLAG_FORCE_CACHED (1U << 9)
|
|
|
|
// Convenience
|
|
#define VSM_FLAG_ANY_UNCACHED (VSM_FLAG_DYNAMIC_UNCACHED | VSM_FLAG_STATIC_UNCACHED)
|
|
#define VSM_EXTENDED_FLAG_ANY_INVALIDATED (VSM_EXTENDED_FLAG_INVALIDATE_DYNAMIC | VSM_EXTENDED_FLAG_INVALIDATE_STATIC)
|
|
|
|
// NOTE: Bits for the hierarchical page flags are stored in the same uints as the regular mip tail,
|
|
// offset based on the Hmip level. For instance, at the 1x1 level the first 4 bits store the page
|
|
// flags for the coarsest mip, the next 4 bits store the hierarchical page flags for the second
|
|
// coarsest mip and so on.
|
|
// If the total bit count needs to change be sure it doesn't overlow the page flags for all Hmips
|
|
#define VSM_PAGE_FLAGS_BITS_PER_HMIP (4U)
|
|
#define VSM_PAGE_FLAGS_BITS_MASK ((1U<<VSM_PAGE_FLAGS_BITS_PER_HMIP)-1U)
|
|
|
|
struct FPhysicalPageMetaData
|
|
{
|
|
uint Flags; // VSM_FLAG_* (not all relevant to physical pages) and VSM_EXTENDED_FLAG_*
|
|
uint LastRequestedSceneFrameNumber;
|
|
// Link back to the virtual shadow map & page table slot that references this physical page
|
|
uint VirtualShadowMapId;
|
|
uint MipLevel;
|
|
uint2 PageAddress;
|
|
};
|
|
|
|
uint CalcLog2LevelDimsPages(uint Level)
|
|
{
|
|
return VSM_LOG2_LEVEL0_DIM_PAGES_XY - Level; // log2( VSM_LEVEL0_DIM_PAGES_XY >> Level )
|
|
}
|
|
|
|
uint CalcLevelDimsPages(uint Level)
|
|
{
|
|
return 1u << CalcLog2LevelDimsPages( Level );
|
|
}
|
|
|
|
uint CalcLevelDimsTexels(uint Level)
|
|
{
|
|
return uint(VSM_VIRTUAL_MAX_RESOLUTION_XY) >> Level;
|
|
}
|
|
|
|
uint2 CalcLevelOffsets(uint MipLevel)
|
|
{
|
|
uint2 Result = uint2(0u, 0u);
|
|
if (MipLevel > 0u)
|
|
{
|
|
// Lay out the mip tail below in address space so we can wrap the X coord using POT
|
|
Result.y += VSM_LEVEL0_DIM_PAGES_XY;
|
|
|
|
uint MaxMask = (1u << (VSM_MAX_MIP_LEVELS - 1)) - 1u;
|
|
uint StartBit = VSM_MAX_MIP_LEVELS - MipLevel;
|
|
Result.x += MaxMask & (MaxMask << StartBit);
|
|
}
|
|
return Result;
|
|
};
|
|
|
|
struct FVirtualSMLevelOffset
|
|
{
|
|
bool bIsSinglePageSM;
|
|
uint2 LevelTexelOffset;
|
|
|
|
uint GetPacked()
|
|
{
|
|
return (LevelTexelOffset.x << 16u) | LevelTexelOffset.y;
|
|
}
|
|
|
|
static FVirtualSMLevelOffset Unpack(uint PackedLevelOffset)
|
|
{
|
|
FVirtualSMLevelOffset Result;
|
|
Result.LevelTexelOffset.x = PackedLevelOffset >> 16u;
|
|
Result.LevelTexelOffset.y = PackedLevelOffset & 0xFFFFu;
|
|
// We can derive the bIsSinglePageSM flag from the level offset since it will always be in the 0th page
|
|
Result.bIsSinglePageSM = all(Result.LevelTexelOffset < VSM_PAGE_SIZE);
|
|
return Result;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Compute the offset for a mip level page table given a shadow map ID and a level.
|
|
*/
|
|
FVirtualSMLevelOffset CalcPageTableLevelOffset(FVirtualShadowMapHandle VirtualShadowMapHandle, uint MipLevel)
|
|
{
|
|
FVirtualSMLevelOffset Result;
|
|
Result.bIsSinglePageSM = VirtualShadowMapHandle.IsSinglePage();
|
|
if (Result.bIsSinglePageSM)
|
|
{
|
|
// Map into single page at 0,0
|
|
Result.LevelTexelOffset.y = VirtualShadowMapHandle.Id >> VSM_LOG2_PAGE_SIZE;
|
|
Result.LevelTexelOffset.x = VirtualShadowMapHandle.Id & VSM_PAGE_SIZE_MASK;
|
|
}
|
|
else
|
|
{
|
|
// Skip the first one as we reserve that for the single-page bozos
|
|
uint FullId = uint(VirtualShadowMapHandle.Id - VSM_MAX_SINGLE_PAGE_SHADOW_MAPS) + 1u;
|
|
Result.LevelTexelOffset.y = (FullId >> VirtualShadowMap.PageTableRowShift) * VSM_PAGE_TABLE_TEX2D_SIZE_Y;
|
|
Result.LevelTexelOffset.x = (FullId & VirtualShadowMap.PageTableRowMask) * VSM_PAGE_TABLE_TEX2D_SIZE_X;
|
|
|
|
Result.LevelTexelOffset += CalcLevelOffsets(MipLevel);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Compute the offset for a mip level page table given a shadow map ID and a level.
|
|
*/
|
|
FVirtualSMLevelOffset CalcPageTableLevelOffset(int VirtualShadowMapId, uint MipLevel)
|
|
{
|
|
return CalcPageTableLevelOffset(FVirtualShadowMapHandle::MakeFromId(VirtualShadowMapId), MipLevel);
|
|
}
|
|
|
|
uint2 CalcPageOffsetInFullLevel(uint Level, uint2 PageAddress)
|
|
{
|
|
return PageAddress;
|
|
}
|
|
|
|
/**
|
|
* Offset of an individual page in the global page table data structure, whatever it may be.
|
|
*/
|
|
struct FVSMPageOffset
|
|
{
|
|
uint2 TexelAddress;
|
|
|
|
// Returns the address formated for resource (texture / buffer) access.
|
|
uint2 GetResourceAddress()
|
|
{
|
|
return TexelAddress;
|
|
}
|
|
|
|
uint GetPacked()
|
|
{
|
|
return (TexelAddress.x << 16u) | TexelAddress.y;
|
|
}
|
|
|
|
static FVSMPageOffset Unpack(uint Packed)
|
|
{
|
|
FVSMPageOffset Result;
|
|
Result.TexelAddress.x = Packed >> 16u;
|
|
Result.TexelAddress.y = Packed & 0xFFFFu;
|
|
return Result;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Compute the offset for page within a level page table given a level and PageAddress.
|
|
*/
|
|
FVSMPageOffset CalcPageOffset(FVirtualSMLevelOffset LevelOffset, uint Level, uint2 PageAddress)
|
|
{
|
|
//checkSlow(LevelOffset.bIsSinglePageSM == (LevelOffset.LevelOffset < VSM_MAX_SINGLE_PAGE_SHADOW_MAPS));
|
|
FVSMPageOffset Result;
|
|
Result.TexelAddress = LevelOffset.LevelTexelOffset;
|
|
|
|
if (!LevelOffset.bIsSinglePageSM)
|
|
{
|
|
Result.TexelAddress += CalcPageOffsetInFullLevel(Level, PageAddress);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
FVSMPageOffset CalcPageOffset(FVirtualShadowMapHandle VirtualShadowMapHandle, uint Level, uint2 PageAddress)
|
|
{
|
|
FVirtualSMLevelOffset LevelOffset = CalcPageTableLevelOffset(VirtualShadowMapHandle, Level);
|
|
return CalcPageOffset(LevelOffset, Level, PageAddress);
|
|
}
|
|
|
|
FVSMPageOffset CalcPageOffset(int VirtualShadowMapId, uint Level, uint2 PageAddress)
|
|
{
|
|
return CalcPageOffset(FVirtualShadowMapHandle::MakeFromId(VirtualShadowMapId), Level, PageAddress);
|
|
}
|
|
|
|
bool IsVirtualShadowMapPageAddressValid(int2 PageAddress, uint Level)
|
|
{
|
|
return (all(PageAddress >= 0) && all(PageAddress < CalcLevelDimsPages(Level)));
|
|
}
|
|
|
|
// Linearlize a physical page address to a linear offset
|
|
uint VSMPhysicalPageAddressToIndex(uint2 PhysicalPageAddress)
|
|
{
|
|
return (PhysicalPageAddress.y << VirtualShadowMap.PhysicalPageRowShift) + PhysicalPageAddress.x;
|
|
}
|
|
|
|
uint2 VSMPhysicalIndexToPageAddress(uint PageIndex)
|
|
{
|
|
uint2 PageAddress;
|
|
PageAddress.x = PageIndex & VirtualShadowMap.PhysicalPageRowMask;
|
|
PageAddress.y = PageIndex >> VirtualShadowMap.PhysicalPageRowShift;
|
|
return PageAddress;
|
|
}
|
|
|
|
|
|
// Current page table format:
|
|
// NOTE: Some redundancy in flags and encoding, but we have spare bits for now
|
|
// [0:9] PageAddress.x
|
|
// [10:19] PageAddress.y
|
|
// [20:25] LODOffset
|
|
// [26:29] (currently unused)
|
|
// [30] bThisLODValidForRendering
|
|
// [31] bAnyLODValid
|
|
struct FShadowPhysicalPage
|
|
{
|
|
uint2 PhysicalAddress; // Physical page address X, Y
|
|
uint LODOffset; // 0 if page is mapped at this mip/clipmap level; 1 if mapped at next courser level, etc. [0..64)
|
|
bool bAnyLODValid; // Valid physical page mapped at some LOD level
|
|
bool bThisLODValidForRendering; // Valid physical page mapped for rendering into
|
|
bool bThisLODValid; // Valid page mapped at this specific level (equivalent to bAnyMipValid && LODOffset == 0)
|
|
};
|
|
|
|
#define VSM_PHYSICAL_PAGE_VALID_FOR_RENDERING_FLAG 0x40000000
|
|
#define VSM_PHYSICAL_PAGE_ANY_MIP_VALID_FLAG 0x80000000
|
|
#define VSM_PHYSICAL_PAGE_INVALID 0x00000000
|
|
|
|
uint ShadowEncodePageTable(uint2 PhysicalAddress, bool bValidForRendering)
|
|
{
|
|
return (PhysicalAddress.y << 10) | (PhysicalAddress.x) |
|
|
(bValidForRendering ?
|
|
(VSM_PHYSICAL_PAGE_ANY_MIP_VALID_FLAG | VSM_PHYSICAL_PAGE_VALID_FOR_RENDERING_FLAG) :
|
|
(VSM_PHYSICAL_PAGE_ANY_MIP_VALID_FLAG)) ;
|
|
}
|
|
uint ShadowEncodePageTable(uint2 PhysicalAddress, uint LODOffset)
|
|
{
|
|
// These are hierarchical pointers to coarser pages that are mapped, so never valid for rendering directly
|
|
// See PropagateMappedMips
|
|
return VSM_PHYSICAL_PAGE_ANY_MIP_VALID_FLAG | (LODOffset << 20) | (PhysicalAddress.y << 10) | (PhysicalAddress.x);
|
|
}
|
|
|
|
FShadowPhysicalPage ShadowDecodePageTable(uint Value)
|
|
{
|
|
FShadowPhysicalPage Result;
|
|
Result.PhysicalAddress = uint2(Value & 0x3FF, (Value >> 10) & 0x3FF);
|
|
Result.LODOffset = (Value >> 20) & 0x3F;
|
|
Result.bAnyLODValid = (Value & VSM_PHYSICAL_PAGE_ANY_MIP_VALID_FLAG) != 0;
|
|
Result.bThisLODValidForRendering = (Value & VSM_PHYSICAL_PAGE_VALID_FOR_RENDERING_FLAG) != 0;
|
|
Result.bThisLODValid = Result.bAnyLODValid && Result.LODOffset == 0;
|
|
return Result;
|
|
}
|
|
|
|
FShadowPhysicalPage ShadowGetPhysicalPage(FVSMPageOffset VSMPageOffset)
|
|
{
|
|
return ShadowDecodePageTable(VirtualShadowMap.PageTable[VSMPageOffset.GetResourceAddress()]);
|
|
}
|
|
|
|
uint VirtualShadowMapGetPageFlag(FVSMPageOffset VSMPageOffset)
|
|
{
|
|
return VirtualShadowMap.PageFlags[VSMPageOffset.GetResourceAddress()];
|
|
}
|
|
|
|
// Returns true if the page is valid *for rendering*
|
|
FShadowPhysicalPage VirtualToPhysicalTexelBase(FVirtualSMLevelOffset PageTableLevelOffset, uint Level, uint2 VirtualTexelAddress, inout uint2 PhysicalTexelAddress)
|
|
{
|
|
uint VPageX = VirtualTexelAddress.x >> VSM_LOG2_PAGE_SIZE;
|
|
uint VPageY = VirtualTexelAddress.y >> VSM_LOG2_PAGE_SIZE;
|
|
|
|
FShadowPhysicalPage PhysicalPageEntry = ShadowGetPhysicalPage(CalcPageOffset(PageTableLevelOffset, Level, uint2(VPageX, VPageY)));
|
|
PhysicalTexelAddress = PhysicalPageEntry.PhysicalAddress * VSM_PAGE_SIZE + (VirtualTexelAddress & VSM_PAGE_SIZE_MASK);
|
|
return PhysicalPageEntry;
|
|
}
|
|
|
|
// Returns true if the page is valid *for rendering*
|
|
bool VirtualToPhysicalTexelForRendering(FVirtualSMLevelOffset PageTableLevelOffset, uint Level, uint2 VirtualTexelAddress, inout uint2 PhysicalTexelAddress)
|
|
{
|
|
return VirtualToPhysicalTexelBase(PageTableLevelOffset, Level, VirtualTexelAddress, PhysicalTexelAddress).bThisLODValidForRendering;
|
|
}
|
|
|
|
// Returns true if the page is valid at all
|
|
bool VirtualToPhysicalTexel(FVirtualSMLevelOffset PageTableLevelOffset, uint Level, uint2 VirtualTexelAddress, inout uint2 PhysicalTexelAddress)
|
|
{
|
|
return VirtualToPhysicalTexelBase(PageTableLevelOffset, Level, VirtualTexelAddress, PhysicalTexelAddress).bThisLODValid;
|
|
}
|
|
|
|
bool VirtualToPhysicalTexel(FVirtualShadowMapHandle VirtualShadowMapHandle, uint Level, uint2 VirtualTexelAddress, inout uint2 PhysicalTexelAddress)
|
|
{
|
|
return VirtualToPhysicalTexel(CalcPageTableLevelOffset(VirtualShadowMapHandle, Level), Level, VirtualTexelAddress, PhysicalTexelAddress);
|
|
}
|
|
|
|
struct FShadowPageTranslationResult
|
|
{
|
|
bool bValid;
|
|
uint LODOffset;
|
|
uint2 VirtualTexelAddress;
|
|
float2 VirtualTexelAddressFloat;
|
|
uint2 PhysicalTexelAddress;
|
|
};
|
|
|
|
// Finds the best-resolution mapped page at the given UV
|
|
FShadowPageTranslationResult ShadowVirtualToPhysicalUV(FVirtualShadowMapHandle VirtualShadowMapHandle, float2 ShadowMapUV, uint MinMipLevel)
|
|
{
|
|
uint2 vPage = uint2(ShadowMapUV * VSM_LEVEL0_DIM_PAGES_XY);
|
|
FShadowPhysicalPage PhysicalPageEntry = ShadowGetPhysicalPage(CalcPageOffset(VirtualShadowMapHandle, MinMipLevel, vPage >> MinMipLevel));
|
|
|
|
FShadowPageTranslationResult Result;
|
|
Result.bValid = PhysicalPageEntry.bAnyLODValid;
|
|
Result.LODOffset = VirtualShadowMapHandle.IsSinglePage() ? (VSM_MAX_MIP_LEVELS - 1U) : (PhysicalPageEntry.LODOffset + MinMipLevel);
|
|
// TODO: Can optimize this slightly based on relative offset
|
|
Result.VirtualTexelAddressFloat = ShadowMapUV * float(CalcLevelDimsTexels(Result.LODOffset));
|
|
Result.VirtualTexelAddress = uint2(Result.VirtualTexelAddressFloat);
|
|
Result.PhysicalTexelAddress = PhysicalPageEntry.PhysicalAddress * VSM_PAGE_SIZE + (Result.VirtualTexelAddress & VSM_PAGE_SIZE_MASK);
|
|
|
|
return Result;
|
|
}
|
|
|
|
struct FPageInfo
|
|
{
|
|
uint ViewId;
|
|
bool bStaticPage; // Write to static page vs dynamic page
|
|
};
|
|
|
|
uint PackPageInfo(FPageInfo PageInfo)
|
|
{
|
|
// TODO: Line up the bit encoding here with the max view count from Nanite
|
|
return
|
|
PageInfo.ViewId |
|
|
(PageInfo.bStaticPage ? (1U << 16) : 0U);
|
|
}
|
|
|
|
FPageInfo UnpackPageInfo(uint PackedData)
|
|
{
|
|
FPageInfo PageInfo;
|
|
PageInfo.ViewId = PackedData & 0xFFFF;
|
|
PageInfo.bStaticPage = ((PackedData >> 16) & 0x1) != 0;
|
|
return PageInfo;
|
|
}
|
|
|
|
uint GetVirtualShadowMapStaticArrayIndex()
|
|
{
|
|
return VirtualShadowMap.StaticCachedArrayIndex;
|
|
}
|
|
|
|
uint GetVirtualShadowMapHZBArrayIndex(bool bUseStaticOcclusion)
|
|
{
|
|
return select(bUseStaticOcclusion, VirtualShadowMap.StaticHZBArrayIndex, 0);
|
|
}
|
|
|
|
// Gather a 2x2 footprint from a page table texture.
|
|
uint4 GatherPageTable(Texture2D<uint> PageTableTexture, uint2 TexelCoord, uint HMipLevel, float UVScaleFactor)
|
|
{
|
|
uint2 TexelCoordMip = TexelCoord >> HMipLevel;
|
|
#if COMPILER_SUPPORTS_GATHER_LOD_RED
|
|
// Offset to 2x2 footprint center and scale to UV space
|
|
float MipScale = float(1U << HMipLevel);
|
|
float2 UV = float2(TexelCoordMip + uint2(1U, 1U)) * VirtualShadowMap.PageTableTextureSizeInvSize.zw * MipScale * UVScaleFactor;
|
|
uint4 Result2x2 = PageTableTexture.GatherLODRed(VirtualShadowMap.PageTableSampler, UV, HMipLevel);
|
|
#else
|
|
uint4 TexelRectMip = uint4(TexelCoordMip.xy, TexelCoordMip.xy + uint2(1U, 1U));
|
|
uint4 Result2x2 = uint4(
|
|
PageTableTexture.Load(uint3(TexelRectMip.xw, HMipLevel)), // (-, +)
|
|
PageTableTexture.Load(uint3(TexelRectMip.zw, HMipLevel)), // (+, +)
|
|
PageTableTexture.Load(uint3(TexelRectMip.zy, HMipLevel)), // (+, -)
|
|
PageTableTexture.Load(uint3(TexelRectMip.xy, HMipLevel)) // (-, -)
|
|
);
|
|
#endif
|
|
return Result2x2;
|
|
}
|