Files
UnrealEngineUWP/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPageOverlap.ush
andrew lauritzen 255de27bf3 Remove old VSM invalidations path and related cleanup
#jira UE-198448
#rnx
#tests qagame,citysample
#rb wouter.dek

[CL 35218641 by andrew lauritzen in ue5-main branch]
2024-07-31 13:16:49 -04:00

293 lines
11 KiB
Plaintext

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "../Common.ush"
#include "../Nanite/NaniteHZBCull.ush"
#include "../Nanite/NaniteDataDecode.ush"
#include "../SceneData.ush"
#include "VirtualShadowMapPageAccessCommon.ush"
#include "VirtualShadowMapPageCacheCommon.ush"
StructuredBuffer<uint> HZBPageTable;
StructuredBuffer<uint4> HZBPageRectBounds;
StructuredBuffer<uint> HZBPageFlags;
// NOTE: Only VSM_FLAG_* are allowed to be used here, not VSM_EXTENDED_*, as this does a hierarchical page flag lookup
// NOTE: HMipLevel = 0 means the regular page flags
uint VirtualShadowMapGetPageFlags(uint ShadowMapID, uint MipLevel, uint HMipLevel, uint2 PageAddress)
{
uint MipToSample = MipLevel + HMipLevel;
uint RawFlags = VirtualShadowMap.PageFlags[CalcPageOffset(ShadowMapID, MipToSample, PageAddress)];
// Extract the flags for the given HMip
uint HMipBitShift = VSM_PAGE_FLAGS_BITS_PER_HMIP * HMipLevel;
return (RawFlags >> HMipBitShift) & VSM_PAGE_FLAGS_BITS_MASK;
}
uint4 VirtualShadowMapGetPageRect(FScreenRect Rect)
{
return uint4(Rect.Pixels) >> VSM_LOG2_PAGE_SIZE;
}
uint4 VirtualShadowMapClipPageRect(uint4 RectPages, uint VirtualShadowMapId, uint MipLevel, StructuredBuffer<uint4> PageRectBoundsBuffer)
{
uint4 Bounds = PageRectBoundsBuffer[VirtualShadowMapId * VSM_MAX_MIP_LEVELS + MipLevel];
return uint4(max(RectPages.xy, Bounds.xy), min(RectPages.zw, Bounds.zw));
}
uint4 VirtualShadowMapGetAllocatedPageRect(FScreenRect Rect, uint VirtualShadowMapId, uint MipLevel)
{
return VirtualShadowMapClipPageRect(
VirtualShadowMapGetPageRect(Rect),
VirtualShadowMapId,
MipLevel,
VirtualShadowMap.AllocatedPageRectBounds);
}
uint4 VirtualShadowMapGetUncachedPageRect(FScreenRect Rect, uint VirtualShadowMapId, uint MipLevel)
{
return VirtualShadowMapClipPageRect(
VirtualShadowMapGetPageRect(Rect),
VirtualShadowMapId,
MipLevel,
VirtualShadowMap.UncachedPageRectBounds);
}
// Returns true if ANY flags in FlagMask are set on at least one overlapped page (see GetPageFlagMaskForRendering below)
// NOTE: Only VSM_FLAG_* are allowed to be used here, not VSM_EXTENDED_*, as this does a hierarchical page flag lookup
// If bDetailGeometry is set, additionally tests whether at least one page has VSM_FLAG_DETAIL_GEOMETRY marked on it
bool OverlapsAnyValidPage(uint ShadowMapID, uint MipLevel, uint4 RectPages, uint FlagMask, bool bDetailGeometry)
{
// Skip empty rectangles (inclusive).
if (any(RectPages.zw < RectPages.xy))
{
return false;
}
uint HMipLevel = MipLevelForRect(RectPages, 2);
RectPages >>= HMipLevel;
for (uint y = RectPages.y; y <= RectPages.w; y++)
{
for (uint x = RectPages.x; x <= RectPages.z; x++)
{
uint PageFlags = VirtualShadowMapGetPageFlags(ShadowMapID, MipLevel, HMipLevel, uint2(x, y));
if ((PageFlags & FlagMask) != 0 &&
(!bDetailGeometry || ((PageFlags & VSM_FLAG_DETAIL_GEOMETRY) != 0)))
{
return true;
}
}
}
return false;
}
/**
* Wrapper type to make misuse slightly harder.
*/
struct FPageTestScreenRect
{
FScreenRect ScreenRect;
uint HZBLevelPageSizeShift;
int HZBLevelPageSizeInclusive;
uint4 RectPages;
bool bWasPageRectClipped;
};
/**
* Set up a screen rect and pre-computed data for testing pages against HZB, this assumes a 4x4-HZB FScreenRect
* as input. The resulting rect has been clamped to the mip level where a page is 4x4 texels, as higher mips are meaningless.
*/
FPageTestScreenRect SetupPageHZBRect(FScreenRect ScreenRect, uint ShadowMapID, uint MipLevel)
{
FPageTestScreenRect Result;
Result.ScreenRect = ScreenRect;
// Clamp to level where a page is 4x4 (HZB mip 0 is half-size)
if (Result.ScreenRect.HZBLevel > (VSM_LOG2_PAGE_SIZE - 3))
{
// Adjust HZB texel rect to match new mip level, this will be too large, but is clipped below.
Result.ScreenRect.HZBTexels = int4(Result.ScreenRect.Pixels.xy, max(Result.ScreenRect.Pixels.xy, Result.ScreenRect.Pixels.zw)) >> (VSM_LOG2_PAGE_SIZE - 2U);
Result.ScreenRect.HZBLevel = VSM_LOG2_PAGE_SIZE - 3U;
}
Result.HZBLevelPageSizeShift = VSM_LOG2_PAGE_SIZE - 1U - Result.ScreenRect.HZBLevel;
Result.HZBLevelPageSizeInclusive = (1U << Result.HZBLevelPageSizeShift) - 1;
uint4 UnClippedRectPages = VirtualShadowMapGetPageRect(ScreenRect);
// If the clipped page rect is smaller than the unclipped rect, there are unmapped pages in the footprint and we return
// that it is visible.
Result.RectPages = VirtualShadowMapClipPageRect(UnClippedRectPages, ShadowMapID, MipLevel, HZBPageRectBounds);
Result.bWasPageRectClipped = any(Result.RectPages.xy > UnClippedRectPages.xy) || any(Result.RectPages.zw < UnClippedRectPages.zw);
return Result;
}
bool IsPageVisibleHZB(uint2 vPage, uint PageFlagOffset, bool bTreatUnmappedAsOccluded, FPageTestScreenRect PageTestScreenRect, bool bUseStaticOcclusion)
{
FShadowPhysicalPage pPage = ShadowDecodePageTable(HZBPageTable[PageFlagOffset]);
if (pPage.bThisLODValid)
{
uint2 PhysicalAddress = pPage.PhysicalAddress;
FScreenRect HZBTestRect = PageTestScreenRect.ScreenRect;
// Move to page local (in mip level) space and clamp rect to page size.
HZBTestRect.HZBTexels -= (vPage << PageTestScreenRect.HZBLevelPageSizeShift).xyxy;
HZBTestRect.HZBTexels = clamp(HZBTestRect.HZBTexels, 0, PageTestScreenRect.HZBLevelPageSizeInclusive);
// Translate to physical address space
HZBTestRect.HZBTexels += (PhysicalAddress << PageTestScreenRect.HZBLevelPageSizeShift).xyxy;
return IsVisibleHZBArray(HZBTestRect, true, GetVirtualShadowMapHZBArrayIndex(bUseStaticOcclusion));
}
return !bTreatUnmappedAsOccluded;
}
/**
* Perform HZB-Test for a rectangle of pages, returning true if the Rect is visible in at least one page.
* @param TestPageMask - page flags to inlcude in occlusion test, e.g., VSM_FLAG_ALLOCATED will test any allocated page, but ignore unallocated ones.
* @param VisiblePageMask - page flags to treat as un-occluded, tested after the above mask, e.g., use VSM_UNCACHED_FLAG to treat any uncached page as visible.
*/
bool IsVisibleMaskedHZB(uint PrevShadowMapID, uint MipLevel, FScreenRect Rect, bool bClampToPageLevel, bool bTreatUnmappedAsOccluded, uint VisiblePageMask, uint TestPageMask, bool bUseStaticOcclusion)
{
// Don't have an HZB to test.
if (PrevShadowMapID == ~0u)
{
return true;
}
// Don't go past mip level of 4x4 for a 4x4 test without possibly covering more than 4 pages.
if (!bClampToPageLevel && Rect.HZBLevel > VSM_LOG2_PAGE_SIZE - 3)
{
return true;
}
FPageTestScreenRect HZBTestRect = SetupPageHZBRect(Rect, PrevShadowMapID, MipLevel);
// Allow treating unmapped pages as visible, such that
if (!bTreatUnmappedAsOccluded && HZBTestRect.bWasPageRectClipped)
{
return true;
}
uint4 RectPages = HZBTestRect.RectPages;
FVirtualSMLevelOffset PageTableLevelOffset = CalcPageTableLevelOffset(PrevShadowMapID, MipLevel);
for (uint y = RectPages.y; y <= RectPages.w; y++)
{
for (uint x = RectPages.x; x <= RectPages.z; x++)
{
uint PageFlagOffset = CalcPageOffset(PageTableLevelOffset, MipLevel, uint2(x, y));
uint PageFlag = HZBPageFlags[PageFlagOffset];
// Skip unallocated pages if bTreatUnmappedAsOccluded is true, otherwise test everything
if (!bTreatUnmappedAsOccluded || ((PageFlag & TestPageMask) != 0U))
{
// Treat pages with the VisiblePageMask as visible - can be used to select only cached pages
if ((PageFlag & VisiblePageMask) != 0U || IsPageVisibleHZB(uint2(x, y), PageFlagOffset, bTreatUnmappedAsOccluded, HZBTestRect, bUseStaticOcclusion))
{
return true;
}
}
}
}
return false;
}
uint GetPageFlagMaskForRendering(bool bCacheAsStatic)
{
uint PageFlagMask = (bCacheAsStatic ? VSM_FLAG_STATIC_UNCACHED : VSM_FLAG_DYNAMIC_UNCACHED);
return PageFlagMask;
}
float CalcClipSpaceRadiusEstimate(bool bIsOrtho, FInstanceSceneData InstanceData, float4x4 LocalToTranslatedWorld, float4x4 ViewToClip)
{
float WorldSpaceRadiusLength = length(InstanceData.LocalBoundsExtent * InstanceData.NonUniformScale.xyz);
if (bIsOrtho)
{
// for ortho we just need the estimated radius & the projection scale, which is symmetric for VSM views
return WorldSpaceRadiusLength * ViewToClip[0][0];
}
else
{
const float3 TranslatedWorldCenter = mul(float4(InstanceData.LocalBoundsCenter, 1.0f), LocalToTranslatedWorld).xyz;
float4 RadiusClipH = mul(float4(WorldSpaceRadiusLength, 0.0f, length(TranslatedWorldCenter), 1.0f), ViewToClip);
return abs(RadiusClipH.x / RadiusClipH.w);
}
}
/**
* Figure out the coarse page flag for a given footprint pixel-radius of an instance (estimated from bounds).
* IF the footprint is considered "small" and thus a candidate for not rendering to coarse pages, we return true - which means it will get compared against the same flag in the page flags in the footprint.
* When the comparison is not 1 (i.e., for a page _not_ marked with VSM_FLAG_DETAIL_GEOMETRY) the instance is culled.
* This test should only be performed at the instance level, as it does not make sense to partially cull geometry, (e.g, creating holes based on varying cluster size).
*/
bool IsDetailGeometry(bool bCacheAsStatic, bool bIsNaniteGeometry, float InstancePixelEstRadius)
{
// Preserve the old behaviour, only based on whether it is nanite or not.
if (VirtualShadowMap.bExcludeNonNaniteFromCoarsePages)
{
return !bIsNaniteGeometry;
}
// We use a separate threshold for dynamically cached geometry, this is because the overhead is lower when caching is enabled as cached page culling removes the instances anyway.
// Nanite geometry has a separate smaller footprint as it has better LOD and lower overhead for replicated drawing.
if (bCacheAsStatic)
{
return InstancePixelEstRadius < VirtualShadowMap.CoarsePagePixelThresholdStatic;
}
if (bIsNaniteGeometry)
{
// Use a separate threshold for Nanite, as the overhead for drawing small instances is far lower.
return InstancePixelEstRadius < VirtualShadowMap.CoarsePagePixelThresholdDynamicNanite;
}
return InstancePixelEstRadius < VirtualShadowMap.CoarsePagePixelThresholdDynamic;
}
RWStructuredBuffer<uint> OutDirtyPageFlags;
bool VirtualShadowMapMarkPageDirty(
uint PageFlagOffset,
bool bInvalidatePage,
bool bCacheAsStatic,
bool bIsViewUncached,
bool bWPOAllowed)
{
FShadowPhysicalPage PhysPage = ShadowGetPhysicalPage(PageFlagOffset);
if (PhysPage.bThisLODValid)
{
// Mark the page dirty so we regenerate HZB, etc.
uint PhysPageIndex = VSMPhysicalPageAddressToIndex(PhysPage.PhysicalAddress);
if (bCacheAsStatic || bIsViewUncached)
{
OutDirtyPageFlags[PhysPageIndex] = 1U;
}
if (bInvalidatePage)
{
uint Offset = VirtualShadowMap.MaxPhysicalPages * (bCacheAsStatic ? 2U : 1U);
// Store invalidation flags after the dirty flags.
OutDirtyPageFlags[Offset + PhysPageIndex] = 1U;
}
else if (bWPOAllowed)
{
uint Offset = VirtualShadowMap.MaxPhysicalPages * 3U;
// If WPO is allowed in this page, we store a bit even if we are not currently animating/invalidating
// This allows us to keep considering this page in future renders in case WPO animating starts,
// which will trigger a full invalidation and thus a re-render on the subsequent frame.
OutDirtyPageFlags[Offset + PhysPageIndex] = 1U;
}
return true;
}
return false;
}