Files
UnrealEngineUWP/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPageAccessCommon.ush
Ryan Hummer 4af2fd066d Updating Dev-Release-5.5 from Main at CL #36144969
#okforversepublic

[CL 36146571 by Ryan Hummer in Dev-5.5 branch]
2024-09-10 10:26:02 -04:00

352 lines
14 KiB
Plaintext

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
VirtualShadowMapPageAccessCommon.ush:
=============================================================================*/
#pragma once
#include "../Common.ush"
#include "../BitPacking.ush"
#include "/Engine/Shared/VirtualShadowMapDefinitions.h"
// 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)
bool IsSinglePageVirtualShadowMap(int VirtualShadowMapId)
{
return VirtualShadowMapId < int(VSM_MAX_SINGLE_PAGE_SHADOW_MAPS);
}
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;
}
uint CalcLevelOffsets(uint Level)
{
// VSM_LEVEL0_DIM_PAGES_XY is a power of two, so the footprint of each mip level MipSize_i=(VSM_LEVEL0_DIM_PAGES_XY>>i)^2 is also a power of two.
// The binary representation of a mip size is just a single bit: 1 << log2(MipSize_i) = (1 << (2 * (VSM_LOG2_LEVEL0_DIM_PAGES_XY - i))).
// To calculate the offset we need to calculate a sum of consecutive mip sizes, which is equivalent to producing a bit pattern with one bit per level starting out at
// bitposition 2*VSM_LOG2_LEVEL0_DIM_PAGES_XY and going down by 2 for every level.
// E.g. VSM_LEVEL0_DIM_PAGES_XY=3
// Level 0: 0000000
// Level 1: 1000000
// Level 2: 1010000
// Level 3: 1010100
// Level 4: 1010101
// To quickly produce a variable number of bits we just select a range of bits from the alternating bit sequence 0x55=0b01010101.
uint NumBits = Level << 1;
uint StartBit = (2 * VSM_LOG2_LEVEL0_DIM_PAGES_XY + 2) - NumBits;
#if COMPILER_SUPPORTS_BITFIELD_INTRINSICS
uint Mask = BitFieldMaskU32(NumBits, StartBit);
#else
uint Mask = ((1u << NumBits) - 1u) << StartBit;
#endif
return 0x55555555u & Mask;
}
struct FVirtualSMLevelOffset
{
bool bIsSinglePageSM;
uint LevelOffset;
};
FVirtualSMLevelOffset InitVirtualMLevelOffset(uint RawLevelOffset)
{
FVirtualSMLevelOffset Result;
// We can derive the bIsSinglePageSM flag from the level offset since CalcFullPageTableLevelOffset always adds VSM_MAX_SINGLE_PAGE_SHADOW_MAPS.
Result.bIsSinglePageSM = RawLevelOffset < VSM_MAX_SINGLE_PAGE_SHADOW_MAPS;
Result.LevelOffset = RawLevelOffset;
return Result;
}
/**
* Compute the offset for a mip level page table given a shadow map ID and a level.
*/
uint CalcFullPageTableLevelOffset(uint VirtualShadowMapId, uint Level)
{
checkSlow(!IsSinglePageVirtualShadowMap(VirtualShadowMapId));
return VSM_MAX_SINGLE_PAGE_SHADOW_MAPS + (VirtualShadowMapId - VSM_MAX_SINGLE_PAGE_SHADOW_MAPS) * VSM_PAGE_TABLE_SIZE + CalcLevelOffsets(Level);
}
/**
* Compute the offset for a mip level page table given a shadow map ID and a level.
*/
FVirtualSMLevelOffset CalcPageTableLevelOffset(uint VirtualShadowMapId, uint Level)
{
FVirtualSMLevelOffset Result;
Result.bIsSinglePageSM = IsSinglePageVirtualShadowMap(VirtualShadowMapId);
if (Result.bIsSinglePageSM)
{
// Single page SM vtables (single entry) are laid out before all the high-detail SMs vTable
Result.LevelOffset = VirtualShadowMapId;
}
else
{
// Full SMs are laid out after the single page ones.
Result.LevelOffset = CalcFullPageTableLevelOffset(VirtualShadowMapId, Level);
}
return Result;
}
uint CalcPageOffsetInFullLevel(uint Level, uint2 PageAddress)
{
return PageAddress.x + (PageAddress.y << CalcLog2LevelDimsPages(Level));
}
/**
* Compute the offset for page within a level page table given a level and PageAddress.
*/
uint CalcPageOffset(FVirtualSMLevelOffset LevelOffset, uint Level, uint2 PageAddress)
{
checkSlow(LevelOffset.bIsSinglePageSM == (LevelOffset.LevelOffset < VSM_MAX_SINGLE_PAGE_SHADOW_MAPS));
uint Result = LevelOffset.LevelOffset;
if (!LevelOffset.bIsSinglePageSM)
{
Result += CalcPageOffsetInFullLevel(Level, PageAddress);
}
return Result;
}
uint CalcPageOffset(uint VirtualShadowMapId, uint Level, uint2 PageAddress)
{
// Single page SMs are allocated before all the others such that we can easily determine the category from the index
if (IsSinglePageVirtualShadowMap(VirtualShadowMapId))
{
// We do call this with the single-page SMs during sampling and then the parameters are non-zero / last level
//checkSlow(PageAddress.x == 0U && PageAddress.y == 0U);
//checkSlow(Level == (VSM_MAX_MIP_LEVELS - 1U));
// We must skip the level offset since there is only one level for a single-page SM
return VirtualShadowMapId;
}
// Store all full-detail VSMs after the block of low-detail ones (single-page entries) - CalcPageTableLevelOffset does the offset.
return CalcFullPageTableLevelOffset(VirtualShadowMapId, Level) + CalcPageOffsetInFullLevel(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(uint PageOffset)
{
return ShadowDecodePageTable(VirtualShadowMap.PageTable[PageOffset]);
}
// Returns true if the page is valid *for rendering*
bool VirtualToPhysicalTexelForRendering(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.bThisLODValidForRendering);
}
// TODO: Rename and clean up some duplication
bool VirtualToPhysicalTexel_PageTableLevelOffset(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.bThisLODValid);
}
bool VirtualToPhysicalTexel(uint ShadowMapID, uint Level, uint2 VirtualTexelAddress, inout uint2 PhysicalTexelAddress)
{
return VirtualToPhysicalTexel_PageTableLevelOffset(CalcPageTableLevelOffset(ShadowMapID, 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(uint VirtualShadowMapID, float2 ShadowMapUV, uint MinMipLevel)
{
uint2 vPage = uint2(ShadowMapUV * VSM_LEVEL0_DIM_PAGES_XY);
FShadowPhysicalPage PhysicalPageEntry = ShadowGetPhysicalPage(CalcPageOffset(VirtualShadowMapID, MinMipLevel, vPage >> MinMipLevel));
FShadowPageTranslationResult Result;
Result.bValid = PhysicalPageEntry.bAnyLODValid;
Result.LODOffset = IsSinglePageVirtualShadowMap(VirtualShadowMapID) ? (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;
}
bool VirtualShadowMapShouldCacheStaticSeparately()
{
return VirtualShadowMap.StaticCachedArrayIndex > 0;
}
uint GetVirtualShadowMapStaticArrayIndex()
{
return VirtualShadowMap.StaticCachedArrayIndex;
}
uint GetVirtualShadowMapHZBArrayIndex(bool bUseStaticOcclusion)
{
return select(bUseStaticOcclusion, VirtualShadowMap.StaticHZBArrayIndex, 0);
}