Files
UnrealEngine/Engine/Source/Developer/NaniteBuilder/Private/Encode/NaniteEncodeFixup.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

399 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "NaniteEncodeFixup.h"
#include "Math/UnrealMath.h"
#include "Cluster.h"
#include "ClusterDAG.h"
#include "NaniteDefinitions.h"
#include "NaniteEncodeShared.h"
#include "Nanite/NaniteFixupChunk.h"
namespace Nanite
{
void CalculatePageDependenciesAndFixups(
FResources& Resources,
TArray<FPageFixups>& PageFixups,
const TArray<FPage>& Pages,
const FClusterDAG& ClusterDAG,
const TArray<FClusterGroupPart>& Parts
)
{
const TArray<FCluster>& Clusters = ClusterDAG.Clusters;
const TArray<FClusterGroup>& Groups = ClusterDAG.Groups;
const uint32 NumPages = Pages.Num();
PageFixups.SetNum(NumPages);
struct FPartFixupRef
{
uint32 PageIndex = MAX_uint32;
uint32 PartFixupIndex = MAX_uint32;
};
TArray<TArray<uint32, TInlineAllocator<8>>> GroupToParts;
TArray<TArray<uint32, TInlineAllocator<16>>> GroupToParentParts;
TArray<TArray<uint16, TInlineAllocator<16>>> PageDependencies;
TArray<FPartFixupRef> PartToPartFixup;
GroupToParts.SetNum(Groups.Num());
GroupToParentParts.SetNum(Groups.Num());
PageDependencies.SetNum(Pages.Num());
PartToPartFixup.SetNum(Parts.Num());
// Build GroupToParts and GroupToParentParts
for (uint32 PartIndex = 0; PartIndex < (uint32)Parts.Num(); PartIndex++)
{
const FClusterGroupPart& Part = Parts[PartIndex];
check(Part.MinLODError >= 0.0f);
GroupToParts[Part.GroupIndex].Add(PartIndex);
for (uint32 ClusterIndex : Part.Clusters)
{
const FCluster& Cluster = Clusters[ClusterIndex];
if (Cluster.GeneratingGroupIndex != MAX_uint32)
{
GroupToParentParts[Cluster.GeneratingGroupIndex].AddUnique(PartIndex);
}
}
}
// Note: Because of assembly references, we can't assume we can go from child parts to parents as not all groups have parts.
// But we can always go from parent part to child groups, or child group to parent parts.
for (uint32 GroupIndex = 0; GroupIndex < (uint32)Groups.Num(); GroupIndex++)
{
const FClusterGroup& Group = Groups[GroupIndex];
if (Group.bTrimmed)
continue;
const uint32 HierarchyRootOffset = Resources.HierarchyRootOffsets[Group.MeshIndex];
// Last page owns the fixup for the entire group
uint32 LastPageIndex = 0;
Resources.ForEachPage(Group.PageRangeKey, [&LastPageIndex](uint32 PageIndex)
{
LastPageIndex = FMath::Max(LastPageIndex, PageIndex);
});
Resources.ForEachPage(Group.PageRangeKey, [&](uint32 PageIndex)
{
check(PageIndex <= LastPageIndex);
// Other pages in range reference the last page for reconsideration
if (PageIndex < LastPageIndex && !Resources.IsRootPage(PageIndex))
{
PageFixups[PageIndex].ReconsiderPages.AddUnique(uint16(LastPageIndex));
}
// Make every page of the group depend on all pages from parent part groups
for (uint32 ParentPartIndex : GroupToParentParts[GroupIndex])
{
const FClusterGroup& ParentGroup = Groups[Parts[ParentPartIndex].GroupIndex];
Resources.ForEachPage(ParentGroup.PageRangeKey, [&](uint32 ParentPageIndex)
{
if (ParentPageIndex != PageIndex)
{
PageDependencies[PageIndex].AddUnique(uint16(ParentPageIndex));
}
});
}
});
FPageFixups& LastPageFixups = PageFixups[LastPageIndex];
FGroupFixup& GroupFixup = LastPageFixups.GroupFixups.AddDefaulted_GetRef();
GroupFixup.PageDependencyRangeKey = Group.PageRangeKey;
GroupFixup.Flags = 0u;
GroupFixup.FirstPartFixup = LastPageFixups.PartFixups.Num();
GroupFixup.GroupIndex = GroupIndex;
// Add fixups for all group parts
for (uint32 PartIndex : GroupToParts[GroupIndex])
{
const FClusterGroupPart& Part = Parts[PartIndex];
check(Part.GroupIndex == GroupIndex);
check(PartToPartFixup[PartIndex].PageIndex == MAX_uint32);
PartToPartFixup[PartIndex] = { LastPageIndex, uint32(LastPageFixups.PartFixups.Num()) };
FPartFixup& PartFixup = LastPageFixups.PartFixups.AddDefaulted_GetRef();
PartFixup.PageIndex = Part.PageIndex;
PartFixup.StartClusterIndex = Part.PageClusterOffset;
PartFixup.LeafCounter = 0u;
PartFixup.GroupIndex = GroupIndex;
// It needs to be installed for every assembly instance
for (const FHierarchyNodeRef& NodeRef : Part.HierarchyNodeRefs)
{
const uint32 GlobalHierarchyNodeIndex = HierarchyRootOffset + NodeRef.NodeIndex;
PartFixup.HierarchyLocations.Add(FHierarchyNodeRef{ GlobalHierarchyNodeIndex, NodeRef.ChildIndex });
}
}
GroupFixup.NumPartFixups = LastPageFixups.PartFixups.Num() - GroupFixup.FirstPartFixup;
}
uint32 NumAddedPages = 0;
for (uint32 PageIndex = 0; PageIndex < NumPages; PageIndex++)
{
for (FGroupFixup& GroupFixup : PageFixups[PageIndex].GroupFixups)
{
const uint32 GroupIndex = GroupFixup.GroupIndex;
const FClusterGroup& Group = Groups[GroupIndex];
uint32 LastPageIndex = 0;
Resources.ForEachPage(Group.PageRangeKey, [&LastPageIndex](uint32 PageIndex)
{
LastPageIndex = FMath::Max(LastPageIndex, PageIndex);
});
if (!Resources.IsRootPage(LastPageIndex))
{
// Add fixup information to update parent parts
for (uint32 ParentPartIndex : GroupToParentParts[GroupIndex])
{
const FClusterGroupPart& ParentPart = Parts[ParentPartIndex];
FParentFixup& ParentPartFixup = GroupFixup.ParentFixups.AddDefaulted_GetRef();
ParentPartFixup.PageIndex = ParentPart.PageIndex;
ParentPartFixup.PartFixupPageIndex = PartToPartFixup[ParentPartIndex].PageIndex;
ParentPartFixup.PartFixupIndex = PartToPartFixup[ParentPartIndex].PartFixupIndex;
check(ParentPartFixup.PartFixupPageIndex == PageIndex || PageDependencies[PageIndex].Contains(ParentPartFixup.PartFixupPageIndex));
PageFixups[ParentPartFixup.PartFixupPageIndex].PartFixups[ParentPartFixup.PartFixupIndex].LeafCounter++;
// Add parent cluster indices to change leaf status of
for (uint32 i = 0; i < (uint32)ParentPart.Clusters.Num(); i++)
{
if (Clusters[ParentPart.Clusters[i]].GeneratingGroupIndex == GroupIndex)
{
const uint32 LocalClusterIndex = ParentPart.PageClusterOffset + i;
check(LocalClusterIndex <= MAX_uint8);
ParentPartFixup.ClusterIndices.Add(uint8(LocalClusterIndex));
}
}
check(ParentPartFixup.ClusterIndices.Num());
}
}
}
}
// Negate MinLODError of any part references that is not a leaf in the initial state
for (uint32 PageIndex = 0; PageIndex < NumPages; PageIndex++)
{
for (const FPartFixup& PartFixup : PageFixups[PageIndex].PartFixups)
{
if (PartFixup.LeafCounter > 0)
{
for (const FHierarchyNodeRef& NodeRef : PartFixup.HierarchyLocations)
{
Resources.HierarchyNodes[NodeRef.NodeIndex].Misc0[NodeRef.ChildIndex].MinLODError_MaxParentLODError |= 0x80000000u; // MinLODError <= -0.0f
}
}
}
}
// Generate page dependencies
Resources.PageStreamingStates.SetNum(NumPages);
Resources.NumClusters = 0;
for (uint32 PageIndex = 0; PageIndex < NumPages; PageIndex++)
{
const FPage& Page = Pages[PageIndex];
Resources.NumClusters += Page.NumClusters;
FPageStreamingState& PageStreamingState = Resources.PageStreamingStates[PageIndex];
PageStreamingState.DependenciesStart = Resources.PageDependencies.Num();
Resources.PageDependencies.Append(PageDependencies[PageIndex]);
PageStreamingState.DependenciesNum = uint16(Resources.PageDependencies.Num() - PageStreamingState.DependenciesStart);
}
check(Resources.NumClusters <= (uint32)Clusters.Num()); // There can be unused clusters when trim is used
#if DO_CHECK
// Root pages should only have information about how to install itself in the hierarchy.
for (uint32 PageIndex = 0; PageIndex < Resources.NumRootPages; PageIndex++)
{
const FPageFixups& Fixups = PageFixups[PageIndex];
for (const FGroupFixup& GroupFixup : Fixups.GroupFixups)
{
check(!GroupFixup.PageDependencyRangeKey.HasStreamingPages());
check(GroupFixup.ParentFixups.Num() == 0);
}
}
#endif
}
void PerformPageInternalFixup(
const FResources& Resources,
const TArray<FPage>& Pages,
const FClusterDAG& ClusterDAG,
const TArray<FClusterGroupPart>& Parts,
uint32 PageIndex,
TArray<FPackedCluster>& PackedClusters)
{
const FPage& Page = Pages[PageIndex];
// Perform page-internal fix up directly on PackedClusters
for (uint32 LocalPartIndex = 0; LocalPartIndex < Page.PartsNum; LocalPartIndex++)
{
const FClusterGroupPart& Part = Parts[Page.PartsStartIndex + LocalPartIndex];
const FClusterGroup& Group = ClusterDAG.Groups[Part.GroupIndex];
const bool bRootGroup = !Group.PageRangeKey.HasStreamingPages();
for (uint32 ClusterPositionInPart = 0; ClusterPositionInPart < (uint32)Part.Clusters.Num(); ClusterPositionInPart++)
{
const FCluster& Cluster = ClusterDAG.Clusters[Part.Clusters[ClusterPositionInPart]];
FPackedCluster& PackedCluster = PackedClusters[Part.PageClusterOffset + ClusterPositionInPart];
uint32 ClusterFlags = PackedCluster.GetFlags();
if (bRootGroup)
{
ClusterFlags |= NANITE_CLUSTER_FLAG_ROOT_GROUP;
}
if (Cluster.GeneratingGroupIndex != MAX_uint32)
{
const FClusterGroup& GeneratingGroup = ClusterDAG.Groups[Cluster.GeneratingGroupIndex];
bool bContainsCurrentPage = false;
bool bAllDependenciesResident = Resources.TrueForAllPages(
GeneratingGroup.PageRangeKey,
[&](uint32 PageIndex)
{
if (PageIndex == Cluster.PageIndex)
{
bContainsCurrentPage = true;
return true;
}
return false; // contains a streaming page that isn't the current
},
true // bStreamingPagesOnly
);
if (bAllDependenciesResident)
{
// Dependencies met by current page and/or root pages
ClusterFlags &= ~NANITE_CLUSTER_FLAG_STREAMING_LEAF;
if (!bContainsCurrentPage)
{
// Dependencies met by root pages
ClusterFlags &= ~NANITE_CLUSTER_FLAG_ROOT_LEAF;
}
}
}
else
{
ClusterFlags |= NANITE_CLUSTER_FLAG_FULL_LEAF;
}
PackedCluster.SetFlags(ClusterFlags);
}
}
}
void BuildFixupChunkData(TArray<uint8>& OutData, const FPageFixups& PageFixups, uint32 NumClusters)
{
const uint32 TotalGroupFixups = (uint32)PageFixups.GroupFixups.Num();
uint32 TotalPartFixups = (uint32)PageFixups.PartFixups.Num();
uint32 TotalParentFixups = 0u;
uint32 TotalHierarchyLocations = 0u;
uint32 TotalClusterIndices = 0u;
for (const FGroupFixup& GroupFixup : PageFixups.GroupFixups)
{
TotalParentFixups += GroupFixup.ParentFixups.Num();
for (const FParentFixup& ParentFixup : GroupFixup.ParentFixups)
{
TotalClusterIndices += ParentFixup.ClusterIndices.Num();
}
}
for (const FPartFixup& PartFixup : PageFixups.PartFixups)
{
TotalHierarchyLocations += PartFixup.HierarchyLocations.Num();
}
const uint32 FixupChunkSize = FFixupChunk::GetSize(TotalGroupFixups, TotalPartFixups, TotalParentFixups, TotalHierarchyLocations, PageFixups.ReconsiderPages.Num(), TotalClusterIndices);
OutData.Init(0x00, FixupChunkSize);
FFixupChunk& FixupChunk = *(FFixupChunk*)OutData.GetData();
FixupChunk.Header.Magic = NANITE_FIXUP_MAGIC;
FixupChunk.Header.NumGroupFixups = IntCastChecked<uint16>(TotalGroupFixups);
FixupChunk.Header.NumPartFixups = IntCastChecked<uint16>(TotalPartFixups);
FixupChunk.Header.NumClusters = IntCastChecked<uint16>(NumClusters);
FixupChunk.Header.NumReconsiderPages = IntCastChecked<uint16>(PageFixups.ReconsiderPages.Num());
FixupChunk.Header.NumParentFixups = TotalParentFixups;
FixupChunk.Header.NumHierarchyLocations = TotalHierarchyLocations;
FixupChunk.Header.NumClusterIndices = TotalClusterIndices;
uint32 NextGroupOffset = 0u;
uint32 NextPartOffset = 0u;
uint32 NextParentOffset = 0u;
uint32 NextHierarchyOffset = 0u;
uint32 NextClusterOffset = 0u;
for (const FGroupFixup& SrcGroupFixup : PageFixups.GroupFixups)
{
FFixupChunk::FGroupFixup& GroupFixup = FixupChunk.GetGroupFixup(NextGroupOffset++);
GroupFixup.PageDependencies = SrcGroupFixup.PageDependencyRangeKey;
GroupFixup.Flags = 0u;
GroupFixup.FirstPartFixup = IntCastChecked<uint16>(SrcGroupFixup.FirstPartFixup);
GroupFixup.NumPartFixups = IntCastChecked<uint16>(SrcGroupFixup.NumPartFixups);
GroupFixup.FirstParentFixup = IntCastChecked<uint16>(NextParentOffset);
GroupFixup.NumParentFixups = IntCastChecked<uint16>(SrcGroupFixup.ParentFixups.Num());
for (const FParentFixup& SrcParentFixup : SrcGroupFixup.ParentFixups)
{
FFixupChunk::FParentFixup& ParentFixup = FixupChunk.GetParentFixup(NextParentOffset++);
ParentFixup.PageIndex = IntCastChecked<uint16>(SrcParentFixup.PageIndex);
ParentFixup.PartFixupPageIndex = IntCastChecked<uint16>(SrcParentFixup.PartFixupPageIndex);
ParentFixup.PartFixupIndex = IntCastChecked<uint8>(SrcParentFixup.PartFixupIndex);
ParentFixup.NumClusterIndices = IntCastChecked<uint8>(SrcParentFixup.ClusterIndices.Num());
ParentFixup.FirstClusterIndex = IntCastChecked<uint16>(NextClusterOffset);
for (uint8 ClusterIndex : SrcParentFixup.ClusterIndices)
{
FixupChunk.GetClusterIndex(NextClusterOffset++) = ClusterIndex;
}
}
}
for (const FPartFixup& SrcPartFixup : PageFixups.PartFixups)
{
FFixupChunk::FPartFixup& PartFixup = FixupChunk.GetPartFixup(NextPartOffset++);
PartFixup.PageIndex = IntCastChecked<uint16>(SrcPartFixup.PageIndex);
PartFixup.StartClusterIndex = IntCastChecked<uint8>(SrcPartFixup.StartClusterIndex);
PartFixup.LeafCounter = IntCastChecked<uint8>(SrcPartFixup.LeafCounter);
PartFixup.FirstHierarchyLocation = NextHierarchyOffset;
PartFixup.NumHierarchyLocations = IntCastChecked<uint16>(SrcPartFixup.HierarchyLocations.Num());
for (const FHierarchyNodeRef& NodeRef : SrcPartFixup.HierarchyLocations)
{
FixupChunk.GetHierarchyLocation(NextHierarchyOffset++) = FFixupChunk::FHierarchyLocation(NodeRef.NodeIndex, NodeRef.ChildIndex);
}
}
for (int32 i = 0; i < PageFixups.ReconsiderPages.Num(); i++)
{
FixupChunk.GetReconsiderPageIndex(i) = PageFixups.ReconsiderPages[i];
}
check(NextGroupOffset == TotalGroupFixups);
check(NextPartOffset == TotalPartFixups);
check(NextParentOffset == TotalParentFixups);
check(NextHierarchyOffset == TotalHierarchyLocations);
check(NextClusterOffset == TotalClusterIndices);
}
} // namespace Nanite