You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Add flag for strip render data on cook to Geometry Collection. This can be used if we have a non-native rendering replacement such as using ISM pools. Added p.GeometryCollectionAssetForceStripRenderDataOnCook as a per-project way to force this. #preflight 63f64517ff5afbbb03b47a1a [CL 24370976 by jeremy moore in ue5-main branch]
748 lines
25 KiB
C++
748 lines
25 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "GeometryCollection/GeometryCollectionRenderData.h"
|
|
|
|
#include "GeometryCollection/GeometryCollection.h"
|
|
#include "GeometryCollection/GeometryCollectionAlgo.h"
|
|
#include "GeometryCollection/GeometryCollectionObject.h"
|
|
#include "GeometryCollection/ManagedArray.h"
|
|
|
|
#if WITH_EDITOR
|
|
#include "NaniteBuilder.h"
|
|
#endif
|
|
|
|
|
|
FBoneMapVertexBuffer::~FBoneMapVertexBuffer()
|
|
{
|
|
CleanUp();
|
|
}
|
|
|
|
void FBoneMapVertexBuffer::CleanUp()
|
|
{
|
|
delete BoneMapData;
|
|
BoneMapData = nullptr;
|
|
}
|
|
|
|
void FBoneMapVertexBuffer::Init(TArray<uint16> const& InBoneMap, bool bInNeedsCPUAccess)
|
|
{
|
|
AllocateData(bInNeedsCPUAccess);
|
|
ResizeBuffer(InBoneMap.Num());
|
|
FMemory::Memcpy(Data, InBoneMap.GetData(), PixelFormatStride * NumVertices);
|
|
}
|
|
|
|
void FBoneMapVertexBuffer::ResizeBuffer(uint32 InNumVertices)
|
|
{
|
|
NumVertices = InNumVertices;
|
|
BoneMapData->ResizeBuffer(NumVertices);
|
|
Data = NumVertices != 0 ? BoneMapData->GetDataPointer() : nullptr;
|
|
}
|
|
|
|
void FBoneMapVertexBuffer::AllocateData(bool bInNeedsCPUAccess)
|
|
{
|
|
CleanUp();
|
|
BoneMapData = new TStaticMeshVertexData<uint16>(bInNeedsCPUAccess);
|
|
}
|
|
|
|
void FBoneMapVertexBuffer::InitRHI()
|
|
{
|
|
const bool bHadData = BoneMapData != nullptr;
|
|
VertexBufferRHI = CreateRHIBuffer<true>(BoneMapData, NumVertices, BUF_Static | BUF_ShaderResource | BUF_SourceCopy, TEXT("FBoneMapVertexBuffer"));
|
|
if (VertexBufferRHI != nullptr)
|
|
{
|
|
VertexBufferSRV = RHICreateShaderResourceView(FShaderResourceViewInitializer(bHadData ? VertexBufferRHI : nullptr, PixelFormat));
|
|
}
|
|
}
|
|
|
|
void FBoneMapVertexBuffer::ReleaseRHI()
|
|
{
|
|
VertexBufferSRV.SafeRelease();
|
|
FVertexBuffer::ReleaseRHI();
|
|
}
|
|
|
|
void FBoneMapVertexBuffer::Serialize(FArchive& Ar, bool bInNeedsCPUAccess)
|
|
{
|
|
bNeedsCPUAccess = bInNeedsCPUAccess;
|
|
|
|
Ar << NumVertices;
|
|
|
|
if (Ar.IsLoading())
|
|
{
|
|
// Allocate the vertex data storage type.
|
|
AllocateData(bInNeedsCPUAccess);
|
|
}
|
|
|
|
if (BoneMapData != nullptr)
|
|
{
|
|
// Serialize the vertex data.
|
|
BoneMapData->Serialize(Ar);
|
|
|
|
// Make a copy of the vertex data pointer.
|
|
Data = NumVertices != 0 ? BoneMapData->GetDataPointer() : nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
void FGeometryCollectionMeshResources::InitResources(UGeometryCollection const& Owner)
|
|
{
|
|
FName OwnerName = Owner.GetFName();
|
|
IndexBuffer.SetOwnerName(OwnerName);
|
|
PositionVertexBuffer.SetOwnerName(OwnerName);
|
|
StaticMeshVertexBuffer.SetOwnerName(OwnerName);
|
|
ColorVertexBuffer.SetOwnerName(OwnerName);
|
|
BoneMapVertexBuffer.SetOwnerName(OwnerName);
|
|
|
|
BeginInitResource(&IndexBuffer);
|
|
BeginInitResource(&PositionVertexBuffer);
|
|
BeginInitResource(&StaticMeshVertexBuffer);
|
|
BeginInitResource(&ColorVertexBuffer);
|
|
BeginInitResource(&BoneMapVertexBuffer);
|
|
}
|
|
|
|
void FGeometryCollectionMeshResources::ReleaseResources()
|
|
{
|
|
BeginReleaseResource(&IndexBuffer);
|
|
BeginReleaseResource(&PositionVertexBuffer);
|
|
BeginReleaseResource(&StaticMeshVertexBuffer);
|
|
BeginReleaseResource(&ColorVertexBuffer);
|
|
BeginReleaseResource(&BoneMapVertexBuffer);
|
|
}
|
|
|
|
void FGeometryCollectionMeshResources::Serialize(FArchive& Ar)
|
|
{
|
|
// If platform doesn't have manual vertex fetch then we will need position and
|
|
// bone map CPU access so that we can skin positions on CPU.
|
|
const bool bCPUAccessForBoneTransform = Ar.IsLoading() && GMaxRHIFeatureLevel <= ERHIFeatureLevel::ES3_1;
|
|
// Otherwise we don't need CPU access.
|
|
const bool bNeedsCPUAccess = false;
|
|
|
|
IndexBuffer.Serialize(Ar, bNeedsCPUAccess);
|
|
PositionVertexBuffer.Serialize(Ar, bCPUAccessForBoneTransform);
|
|
StaticMeshVertexBuffer.Serialize(Ar, bNeedsCPUAccess);
|
|
ColorVertexBuffer.Serialize(Ar, bNeedsCPUAccess);
|
|
BoneMapVertexBuffer.Serialize(Ar, bCPUAccessForBoneTransform);
|
|
}
|
|
|
|
|
|
void FGeometryCollectionMeshDescription::Serialize(FArchive& Ar)
|
|
{
|
|
Ar << NumVertices << NumTriangles << PreSkinnedBounds << Sections << SectionsNoInternal;
|
|
|
|
if (Ar.IsCooking())
|
|
{
|
|
// No need for SubSections in cooked build.
|
|
TArray<FGeometryCollectionMeshElement> Dummy;
|
|
Ar << Dummy;
|
|
}
|
|
else
|
|
{
|
|
Ar << SubSections;
|
|
}
|
|
}
|
|
|
|
|
|
#if WITH_EDITOR
|
|
|
|
/** Data to return from BuildMeshDataFromGeometryCollection(). */
|
|
struct FGeometryCollectionBuiltMeshData
|
|
{
|
|
uint32 NumTexCoords = 0;
|
|
TArray<uint32> Indices;
|
|
TArray<FStaticMeshBuildVertex> Vertices;
|
|
TArray<uint16> BonesPerVertex;
|
|
TArray<int32> MaterialsPerTriangle;
|
|
TArray<bool> InternalPerTriangle;
|
|
TArray<uint32> MeshTriangleCounts;
|
|
};
|
|
|
|
/** Get the data needed to build render data from a Geometry Collection. */
|
|
void BuildMeshDataFromGeometryCollection(FGeometryCollection& InCollection, FGeometryCollectionBuiltMeshData& OutMeshData)
|
|
{
|
|
// Vertices Group
|
|
const TManagedArray<FVector3f>& VertexArray = InCollection.Vertex;
|
|
GeometryCollection::UV::FUVLayers UVsLayers = GeometryCollection::UV::FindActiveUVLayers(InCollection);
|
|
const TManagedArray<FLinearColor>& ColorArray = InCollection.Color;
|
|
const TManagedArray<FVector3f>& TangentUArray = InCollection.TangentU;
|
|
const TManagedArray<FVector3f>& TangentVArray = InCollection.TangentV;
|
|
const TManagedArray<FVector3f>& NormalArray = InCollection.Normal;
|
|
const TManagedArray<int32>& BoneMapArray = InCollection.BoneMap;
|
|
|
|
// Faces Group
|
|
const TManagedArray<FIntVector>& IndicesArray = InCollection.Indices;
|
|
const TManagedArray<bool>& VisibleArray = InCollection.Visible;
|
|
const TManagedArray<int32>& MaterialIDArray = InCollection.MaterialID;
|
|
const TManagedArray<bool>& Internal = InCollection.Internal;
|
|
|
|
// Geometry Group
|
|
const int32 NumGeometry = InCollection.NumElements(FGeometryCollection::GeometryGroup);
|
|
const TManagedArray<int32>& VertexStartArray = InCollection.VertexStart;
|
|
const TManagedArray<int32>& VertexCountArray = InCollection.VertexCount;
|
|
const TManagedArray<int32>& FaceStartArray = InCollection.FaceStart;
|
|
const TManagedArray<int32>& FaceCountArray = InCollection.FaceCount;
|
|
|
|
const uint32 NumTexCoords = InCollection.NumUVLayers();
|
|
const bool bHasColors = ColorArray.Num() > 0;
|
|
|
|
// Iterate geometry groups and collect vertices.
|
|
TArray<FStaticMeshBuildVertex> BuildVertices;
|
|
TArray<uint16> BonesPerVertex;
|
|
TArray<int32> DestVertexStarts;
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::BuildVertices);
|
|
|
|
// Gather destination vertex locations so that we can fill them in parallel.
|
|
int32 NumBuildVertices = 0;
|
|
DestVertexStarts.SetNumUninitialized(NumGeometry);
|
|
for (int32 GeometryGroupIndex = 0; GeometryGroupIndex < NumGeometry; GeometryGroupIndex++)
|
|
{
|
|
DestVertexStarts[GeometryGroupIndex] = GeometryGroupIndex > 0 ? DestVertexStarts[GeometryGroupIndex - 1] + VertexCountArray[GeometryGroupIndex - 1] : 0;
|
|
NumBuildVertices += VertexCountArray[GeometryGroupIndex];
|
|
}
|
|
|
|
BuildVertices.AddUninitialized(NumBuildVertices);
|
|
BonesPerVertex.AddUninitialized(NumBuildVertices);
|
|
|
|
ParallelFor(NumGeometry, [&](int32 GeometryGroupIndex)
|
|
{
|
|
const int32 VertexStart = VertexStartArray[GeometryGroupIndex];
|
|
const int32 VertexCount = VertexCountArray[GeometryGroupIndex];
|
|
const int32 DestVertexStart = DestVertexStarts[GeometryGroupIndex];
|
|
|
|
ParallelFor(TEXT("GC:BuildVertices"), VertexCount, 500, [&](int32 VertexIndex)
|
|
{
|
|
FStaticMeshBuildVertex& Vertex = BuildVertices[DestVertexStart + VertexIndex];
|
|
Vertex.Position = VertexArray[VertexStart + VertexIndex];
|
|
Vertex.Color = bHasColors ? ColorArray[VertexStart + VertexIndex].ToFColor(false /* sRGB */) : FColor::White;
|
|
Vertex.TangentX = FVector3f::ZeroVector;
|
|
Vertex.TangentY = FVector3f::ZeroVector;
|
|
Vertex.TangentZ = NormalArray[VertexStart + VertexIndex];
|
|
for (int32 UVIdx = 0; UVIdx < UVsLayers.Num(); ++UVIdx)
|
|
{
|
|
Vertex.UVs[UVIdx] = UVsLayers[UVIdx][VertexStart + VertexIndex];
|
|
if (Vertex.UVs[UVIdx].ContainsNaN())
|
|
{
|
|
Vertex.UVs[UVIdx] = FVector2f::ZeroVector;
|
|
}
|
|
}
|
|
|
|
const int32 BoneIndex = BoneMapArray[VertexStart + VertexIndex];
|
|
BonesPerVertex[DestVertexStart + VertexIndex] = (uint16)BoneIndex;
|
|
});
|
|
});
|
|
}
|
|
|
|
// Iterate geometry groups and collect indices.
|
|
// Can't go parallel here because we remove hidden faces as we go.
|
|
TArray<uint32> BuildIndices;
|
|
TArray<int32> MaterialsPerFace;
|
|
TArray<bool> InternalPerFace;
|
|
TArray<uint32> MeshTriangleCounts;
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::GatherBuildIndices);
|
|
|
|
BuildIndices.Reserve(IndicesArray.Num() * 3);
|
|
MaterialsPerFace.Reserve(IndicesArray.Num());
|
|
InternalPerFace.Reserve(IndicesArray.Num());
|
|
MeshTriangleCounts.Reserve(NumGeometry);
|
|
|
|
for (int32 GeometryGroupIndex = 0; GeometryGroupIndex < NumGeometry; ++GeometryGroupIndex)
|
|
{
|
|
const int32 FaceStart = FaceStartArray[GeometryGroupIndex];
|
|
const int32 FaceCount = FaceCountArray[GeometryGroupIndex];
|
|
const int32 DestFaceStart = MaterialsPerFace.Num();
|
|
const int32 DestVertexOffset = DestVertexStarts[GeometryGroupIndex] - VertexStartArray[GeometryGroupIndex];
|
|
|
|
for (int32 FaceIndex = 0; FaceIndex < FaceCount; ++FaceIndex)
|
|
{
|
|
// Remove hidden.
|
|
if (!VisibleArray[FaceStart + FaceIndex])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FIntVector FaceIndices = IndicesArray[FaceStart + FaceIndex];
|
|
FaceIndices = FaceIndices + FIntVector(DestVertexOffset);
|
|
|
|
// Remove degenerates.
|
|
if (BuildVertices[FaceIndices[0]].Position == BuildVertices[FaceIndices[1]].Position ||
|
|
BuildVertices[FaceIndices[1]].Position == BuildVertices[FaceIndices[2]].Position ||
|
|
BuildVertices[FaceIndices[2]].Position == BuildVertices[FaceIndices[0]].Position)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
BuildIndices.Add(FaceIndices.X);
|
|
BuildIndices.Add(FaceIndices.Y);
|
|
BuildIndices.Add(FaceIndices.Z);
|
|
|
|
const int32 MaterialIndex = MaterialIDArray[FaceStart + FaceIndex];
|
|
MaterialsPerFace.Add(MaterialIndex);
|
|
const bool bInternal = Internal[FaceStart + FaceIndex];
|
|
InternalPerFace.Add(bInternal);
|
|
}
|
|
|
|
MeshTriangleCounts.Add(MaterialsPerFace.Num() - DestFaceStart);
|
|
}
|
|
}
|
|
|
|
OutMeshData.NumTexCoords = NumTexCoords;
|
|
OutMeshData.Indices = MoveTemp(BuildIndices);
|
|
OutMeshData.Vertices = MoveTemp(BuildVertices);
|
|
OutMeshData.BonesPerVertex = MoveTemp(BonesPerVertex);
|
|
OutMeshData.MaterialsPerTriangle = MoveTemp(MaterialsPerFace);
|
|
OutMeshData.InternalPerTriangle = MoveTemp(InternalPerFace);
|
|
OutMeshData.MeshTriangleCounts = MoveTemp(MeshTriangleCounts);
|
|
}
|
|
|
|
struct FGeometryCollectionBuiltMeshSectionData
|
|
{
|
|
TArray<uint32> SortedIndices;
|
|
TArray<FGeometryCollectionMeshElement> Sections;
|
|
TArray<FGeometryCollectionMeshElement> SectionsNoInternal;
|
|
TArray<FGeometryCollectionMeshElement> SubSections;
|
|
};
|
|
|
|
void BuildSectionDataFromMeshData(FGeometryCollectionBuiltMeshData const& InMeshData, FGeometryCollectionBuiltMeshSectionData& OutSectionData)
|
|
{
|
|
// Find spans within the mesh data.
|
|
TArray<FGeometryCollectionMeshElement> Spans;
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::FindSpans);
|
|
|
|
int32 LastMaterialIndex = -1;
|
|
int32 LastIsInternal = -1;
|
|
int32 LastBoneIndex = -1;
|
|
|
|
for (int32 TriangleIndex = 0; TriangleIndex < InMeshData.MaterialsPerTriangle.Num(); ++TriangleIndex)
|
|
{
|
|
const int32 MaterialIndex = InMeshData.MaterialsPerTriangle[TriangleIndex];
|
|
const int32 IsInternal = InMeshData.InternalPerTriangle[TriangleIndex] ? 1 : 0;
|
|
const uint32 VertexIndex0 = InMeshData.Indices[TriangleIndex * 3];
|
|
const uint32 VertexIndex1 = InMeshData.Indices[TriangleIndex * 3 + 1];
|
|
const uint32 VertexIndex2 = InMeshData.Indices[TriangleIndex * 3 + 2];
|
|
const uint16 BoneIndex = InMeshData.BonesPerVertex[VertexIndex0];
|
|
const uint32 MinVertexIndex = FMath::Min(VertexIndex0, FMath::Min(VertexIndex1, VertexIndex2));
|
|
const uint32 MaxVertexIndex = FMath::Max(VertexIndex0, FMath::Max(VertexIndex1, VertexIndex2));
|
|
|
|
// Add a new span if the properties change.
|
|
if (MaterialIndex != LastMaterialIndex || IsInternal != LastIsInternal || BoneIndex != LastBoneIndex)
|
|
{
|
|
FGeometryCollectionMeshElement& Span = Spans.AddZeroed_GetRef();
|
|
Span.TransformIndex = BoneIndex;
|
|
Span.MaterialIndex = MaterialIndex;
|
|
Span.bIsInternal = IsInternal;
|
|
Span.TriangleStart = TriangleIndex;
|
|
Span.TriangleCount = 0;
|
|
Span.VertexStart = MinVertexIndex;
|
|
Span.VertexEnd = MaxVertexIndex;
|
|
}
|
|
|
|
// Update the current span.
|
|
FGeometryCollectionMeshElement& Span = Spans.Last();
|
|
Span.TriangleCount++;
|
|
Span.VertexStart = FMath::Min(Span.VertexStart, MinVertexIndex);
|
|
Span.VertexEnd = FMath::Max(Span.VertexEnd, MaxVertexIndex);
|
|
|
|
LastMaterialIndex = MaterialIndex;
|
|
LastIsInternal = IsInternal;
|
|
LastBoneIndex = BoneIndex;
|
|
}
|
|
}
|
|
|
|
// Sort spans by material and bone index.
|
|
TArray<int32> SortedSpanIndices;
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::SortSpans);
|
|
|
|
TArray<uint64> SortKeys;
|
|
SortKeys.SetNum(Spans.Num());
|
|
SortedSpanIndices.SetNum(Spans.Num());
|
|
for (int32 SpanIndex = 0; SpanIndex < SortedSpanIndices.Num(); SpanIndex++)
|
|
{
|
|
FGeometryCollectionMeshElement const& Span = Spans[SpanIndex];
|
|
SortKeys[SpanIndex] = ((uint64)Span.MaterialIndex << 56) | ((uint64)Span.bIsInternal << 48) | ((uint64)Span.TransformIndex << 32) | (uint64)Span.TriangleStart;
|
|
SortedSpanIndices[SpanIndex] = SpanIndex;
|
|
}
|
|
SortedSpanIndices.StableSort([&SortKeys](int32 A, int32 B)
|
|
{
|
|
return SortKeys[A] < SortKeys[B];
|
|
});
|
|
}
|
|
|
|
// Now copy spans to final section arrays.
|
|
TArray<FGeometryCollectionMeshElement> Sections;
|
|
TArray<FGeometryCollectionMeshElement> SectionsNoInternal;
|
|
TArray<FGeometryCollectionMeshElement> SubSections;
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::BuildSections);
|
|
|
|
Sections.Reserve(Spans.Num());
|
|
SectionsNoInternal.Reserve(Spans.Num());
|
|
SubSections.Reserve(Spans.Num());
|
|
|
|
int32 LastMaterialIndex = -1;
|
|
uint8 LastIsInternal = 0xff;
|
|
int32 LastBoneIndex = -1;
|
|
int32 TriangleIndex = 0;
|
|
|
|
for (int32 SortedSpanIndex = 0; SortedSpanIndex < SortedSpanIndices.Num(); SortedSpanIndex++)
|
|
{
|
|
const int32 SpanIndex = SortedSpanIndices[SortedSpanIndex];
|
|
FGeometryCollectionMeshElement const& Span = Spans[SpanIndex];
|
|
|
|
// Add new section if required.
|
|
if (Span.MaterialIndex != LastMaterialIndex)
|
|
{
|
|
FGeometryCollectionMeshElement& Section = Sections.AddZeroed_GetRef();
|
|
Section.TransformIndex = -1;
|
|
Section.MaterialIndex = Span.MaterialIndex;
|
|
Section.bIsInternal = 0;
|
|
Section.TriangleStart = TriangleIndex;
|
|
Section.TriangleCount = 0;
|
|
Section.VertexStart = Span.VertexStart;
|
|
Section.VertexEnd = Span.VertexEnd;
|
|
}
|
|
{
|
|
// Accumulate this triangle to the current section.
|
|
FGeometryCollectionMeshElement& Section = Sections.Last();
|
|
Section.TriangleCount += Span.TriangleCount;
|
|
Section.VertexStart = FMath::Min(Section.VertexStart, Span.VertexStart);
|
|
Section.VertexEnd = FMath::Max(Section.VertexEnd, Span.VertexEnd);
|
|
}
|
|
|
|
// Add new no-internal section if required.
|
|
// Note that internal face spans have been sorted to the end of each full section.
|
|
if (Span.MaterialIndex != LastMaterialIndex && !Span.bIsInternal)
|
|
{
|
|
FGeometryCollectionMeshElement& Section = SectionsNoInternal.AddZeroed_GetRef();
|
|
Section.TransformIndex = -1;
|
|
Section.MaterialIndex = Span.MaterialIndex;
|
|
Section.bIsInternal = 0;
|
|
Section.TriangleStart = TriangleIndex;
|
|
Section.TriangleCount = 0;
|
|
Section.VertexStart = Span.VertexStart;
|
|
Section.VertexEnd = Span.VertexEnd;
|
|
}
|
|
// Accumulate this triangle if this is non-internal section.
|
|
if (!Span.bIsInternal)
|
|
{
|
|
FGeometryCollectionMeshElement& Section = SectionsNoInternal.Last();
|
|
Section.TriangleCount += Span.TriangleCount;
|
|
Section.VertexStart = FMath::Min(Section.VertexStart, Span.VertexStart);
|
|
Section.VertexEnd = FMath::Max(Section.VertexEnd, Span.VertexEnd);
|
|
}
|
|
|
|
// Add new subsection if required.
|
|
// Note that subsections don't care about the internal flag, since they are only ever used
|
|
// in editor in modes where we want to render internal faces.
|
|
if (Span.MaterialIndex != LastMaterialIndex || Span.TransformIndex != LastBoneIndex)
|
|
{
|
|
FGeometryCollectionMeshElement& Section = SubSections.AddZeroed_GetRef();
|
|
Section.TransformIndex = Span.TransformIndex;
|
|
Section.MaterialIndex = Span.MaterialIndex;
|
|
Section.bIsInternal = 0;
|
|
Section.TriangleStart = TriangleIndex;
|
|
Section.TriangleCount = 0;
|
|
Section.VertexStart = Span.VertexStart;
|
|
Section.VertexEnd = Span.VertexEnd;
|
|
}
|
|
{
|
|
// Accumulate this triangle to the current subsection.
|
|
FGeometryCollectionMeshElement& Section = SubSections.Last();
|
|
Section.TriangleCount += Span.TriangleCount;
|
|
Section.VertexStart = FMath::Min(Section.VertexStart, Span.VertexStart);
|
|
Section.VertexEnd = FMath::Max(Section.VertexEnd, Span.VertexEnd);
|
|
}
|
|
|
|
TriangleIndex += Span.TriangleCount;
|
|
LastMaterialIndex = Span.MaterialIndex;
|
|
LastIsInternal = Span.bIsInternal;
|
|
LastBoneIndex = Span.TransformIndex;
|
|
}
|
|
|
|
//TODO: Clear SectionsNoInternal if it is just a duplicate of Sections.
|
|
|
|
Sections.Shrink();
|
|
SectionsNoInternal.Shrink();
|
|
SubSections.Shrink();
|
|
}
|
|
|
|
// Copy indices to sorted index buffer.
|
|
TArray<uint32> SortedIndices;
|
|
SortedIndices.SetNumUninitialized(InMeshData.Indices.Num());
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::CopySortedIndices);
|
|
|
|
// Gather destination index locations so that we can fill them in parallel.
|
|
TArray<int32> DestIndexStarts;
|
|
DestIndexStarts.SetNumUninitialized(SortedSpanIndices.Num());
|
|
for (int32 SortedSpanIndex = 0; SortedSpanIndex < SortedSpanIndices.Num(); SortedSpanIndex++)
|
|
{
|
|
DestIndexStarts[SortedSpanIndex] = SortedSpanIndex > 0 ? DestIndexStarts[SortedSpanIndex - 1] + Spans[SortedSpanIndices[SortedSpanIndex - 1]].TriangleCount * 3 : 0;
|
|
}
|
|
|
|
ParallelFor(SortedSpanIndices.Num(), [&](int32 SortedSpanIndex)
|
|
{
|
|
const int32 SpanIndex = SortedSpanIndices[SortedSpanIndex];
|
|
FGeometryCollectionMeshElement const& Span = Spans[SpanIndex];
|
|
|
|
const int32 SourceIndexStart = Span.TriangleStart * 3;
|
|
const int32 DestIndexStart = DestIndexStarts[SortedSpanIndex];
|
|
|
|
ParallelFor(TEXT("GC:CopySortedIndices"), Span.TriangleCount, 500, [&](int32 TriangleIndex)
|
|
{
|
|
const int32 SourceIndex = SourceIndexStart + TriangleIndex * 3;
|
|
const int32 DestIndex = DestIndexStart + TriangleIndex * 3;
|
|
SortedIndices[DestIndex] = InMeshData.Indices[SourceIndex];
|
|
SortedIndices[DestIndex + 1] = InMeshData.Indices[SourceIndex + 1];
|
|
SortedIndices[DestIndex + 2] = InMeshData.Indices[SourceIndex + 2];
|
|
});
|
|
});
|
|
}
|
|
|
|
// Move to output.
|
|
OutSectionData.SortedIndices = MoveTemp(SortedIndices);
|
|
OutSectionData.Sections = MoveTemp(Sections);
|
|
OutSectionData.SectionsNoInternal = MoveTemp(SectionsNoInternal);
|
|
OutSectionData.SubSections = MoveTemp(SubSections);
|
|
}
|
|
|
|
FBoxSphereBounds BuildPreSkinnedBounds(FGeometryCollection& InCollection)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::BuildBounds);
|
|
|
|
const TManagedArray<FBox>& BoundingBoxes = InCollection.BoundingBox;
|
|
const TManagedArray<FTransform>& Transform = InCollection.Transform;
|
|
const TManagedArray<int32>& Parent = InCollection.Parent;
|
|
const TManagedArray<int32>& TransformIndices = InCollection.TransformIndex;
|
|
const int32 NumBoxes = BoundingBoxes.Num();
|
|
|
|
TArray<FMatrix> GlobalMatrices;
|
|
GeometryCollectionAlgo::GlobalMatrices(Transform, Parent, GlobalMatrices);
|
|
|
|
bool bBoundsInit = false;
|
|
FBox PreSkinnedBounds(ForceInit);
|
|
|
|
for (int32 BoxIndex = 0; BoxIndex < NumBoxes; ++BoxIndex)
|
|
{
|
|
const int32 TransformIndex = TransformIndices[BoxIndex];
|
|
if (InCollection.IsGeometry(TransformIndex))
|
|
{
|
|
if (!bBoundsInit)
|
|
{
|
|
PreSkinnedBounds = BoundingBoxes[BoxIndex].TransformBy(GlobalMatrices[TransformIndex]);
|
|
bBoundsInit = true;
|
|
}
|
|
else
|
|
{
|
|
PreSkinnedBounds += BoundingBoxes[BoxIndex].TransformBy(GlobalMatrices[TransformIndex]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return bBoundsInit ? FBoxSphereBounds(PreSkinnedBounds) : FBoxSphereBounds(ForceInit);
|
|
}
|
|
|
|
void CreateMeshData(FGeometryCollection& InCollection, FGeometryCollectionBuiltMeshData const& InMeshData, bool bInUseFullPrecisionUVs, FGeometryCollectionRenderData& OutRenderData)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::CreateMeshData);
|
|
|
|
OutRenderData.bHasMeshData = true;
|
|
|
|
OutRenderData.MeshDescription.NumVertices = InMeshData.Vertices.Num();
|
|
OutRenderData.MeshDescription.NumTriangles = InMeshData.Indices.Num() / 3;
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::BufferInit);
|
|
OutRenderData.MeshResource.PositionVertexBuffer.Init(InMeshData.Vertices);
|
|
OutRenderData.MeshResource.StaticMeshVertexBuffer.SetUseFullPrecisionUVs(bInUseFullPrecisionUVs);
|
|
OutRenderData.MeshResource.StaticMeshVertexBuffer.Init(InMeshData.Vertices, InMeshData.NumTexCoords);
|
|
OutRenderData.MeshResource.ColorVertexBuffer.Init(InMeshData.Vertices);
|
|
OutRenderData.MeshResource.BoneMapVertexBuffer.Init(InMeshData.BonesPerVertex);
|
|
}
|
|
|
|
FGeometryCollectionBuiltMeshSectionData SectionData;
|
|
BuildSectionDataFromMeshData(InMeshData, SectionData);
|
|
|
|
// Store the sorted indices that match the section data.
|
|
OutRenderData.MeshResource.IndexBuffer.SetIndices(SectionData.SortedIndices, EIndexBufferStride::AutoDetect);
|
|
|
|
OutRenderData.MeshDescription.Sections = MoveTemp(SectionData.Sections);
|
|
OutRenderData.MeshDescription.SectionsNoInternal = MoveTemp(SectionData.SectionsNoInternal);
|
|
OutRenderData.MeshDescription.SubSections = MoveTemp(SectionData.SubSections);
|
|
|
|
// Calculate bounds.
|
|
OutRenderData.MeshDescription.PreSkinnedBounds = BuildPreSkinnedBounds(InCollection);
|
|
}
|
|
|
|
void CreateNaniteData(FGeometryCollectionBuiltMeshData& InMeshData, FGeometryCollectionRenderData& OutRenderData)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::CreateNaniteData);
|
|
|
|
OutRenderData.bHasNaniteData = true;
|
|
|
|
FMeshNaniteSettings NaniteSettings = {};
|
|
NaniteSettings.bEnabled = true;
|
|
NaniteSettings.TargetMinimumResidencyInKB = 0; // Default to smallest possible, which is a single page
|
|
NaniteSettings.KeepPercentTriangles = 1.0f;
|
|
NaniteSettings.TrimRelativeError = 0.0f;
|
|
NaniteSettings.FallbackPercentTriangles = 1.0f; // 100% - no reduction
|
|
NaniteSettings.FallbackRelativeError = 0.0f;
|
|
|
|
OutRenderData.NaniteResource = {};
|
|
|
|
Nanite::IBuilderModule& NaniteBuilderModule = Nanite::IBuilderModule::Get();
|
|
|
|
if (!NaniteBuilderModule.Build(
|
|
OutRenderData.NaniteResource,
|
|
InMeshData.Vertices,
|
|
InMeshData.Indices,
|
|
InMeshData.MaterialsPerTriangle,
|
|
InMeshData.MeshTriangleCounts,
|
|
InMeshData.NumTexCoords,
|
|
NaniteSettings))
|
|
{
|
|
UE_LOG(LogStaticMesh, Error, TEXT("Failed to build Nanite for geometry collection. See previous line(s) for details."));
|
|
}
|
|
}
|
|
|
|
TUniquePtr<FGeometryCollectionRenderData> FGeometryCollectionRenderData::Create(FGeometryCollection& InCollection, bool bInEnableNanite, bool bInUseFullPrecisionUVs)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::Create);
|
|
|
|
TUniquePtr<FGeometryCollectionRenderData> RenderData = MakeUnique<FGeometryCollectionRenderData>();
|
|
|
|
FGeometryCollectionBuiltMeshData MeshBuildData;
|
|
BuildMeshDataFromGeometryCollection(InCollection, MeshBuildData);
|
|
|
|
if (bInEnableNanite)
|
|
{
|
|
CreateNaniteData(MeshBuildData, *RenderData.Get());
|
|
}
|
|
else
|
|
{
|
|
// Could always create mesh data if we want to be able to enable/disable nanite at runtime in cooked build.
|
|
CreateMeshData(InCollection, MeshBuildData, bInUseFullPrecisionUVs, *RenderData.Get());
|
|
}
|
|
|
|
return RenderData;
|
|
}
|
|
|
|
#endif // WITH_EDITOR
|
|
|
|
|
|
FGeometryCollectionRenderData::~FGeometryCollectionRenderData()
|
|
{
|
|
ReleaseResources();
|
|
|
|
FRenderCommandFence Fence;
|
|
Fence.BeginFence();
|
|
Fence.Wait();
|
|
}
|
|
|
|
// Temporary cvar driven option to force strip render data on cook if we are also stripping other GC data.
|
|
bool GeometryCollectionAssetForceStripRenderDataOnCook = false;
|
|
FAutoConsoleVariableRef CVarGeometryCollectionStripRenderData(
|
|
TEXT("p.GeometryCollectionAssetForceStripRenderDataOnCook"),
|
|
GeometryCollectionAssetForceStripRenderDataOnCook,
|
|
TEXT("Bypass the cook of render data when other geom collection data is also being stripped."));
|
|
|
|
void FGeometryCollectionRenderData::Serialize(FArchive& Ar, UGeometryCollection& Owner)
|
|
{
|
|
const bool bStripRenderData = Owner.bStripRenderDataOnCook || (GeometryCollectionAssetForceStripRenderDataOnCook && Owner.bStripOnCook);
|
|
if (bStripRenderData && Ar.IsCooking())
|
|
{
|
|
// Don't cook rendering data.
|
|
// This is used if we expect to use a non-native GC render path such as the ISM pool.
|
|
bool bDummy = false;
|
|
Ar << bDummy; // bHasMeshData
|
|
Ar << bDummy; // bHasNaniteData
|
|
return;
|
|
}
|
|
|
|
Ar << bHasMeshData;
|
|
Ar << bHasNaniteData;
|
|
|
|
if (bHasMeshData)
|
|
{
|
|
MeshResource.Serialize(Ar);
|
|
MeshDescription.Serialize(Ar);
|
|
}
|
|
else if (Ar.IsLoading())
|
|
{
|
|
MeshResource = {};
|
|
MeshDescription = {};
|
|
}
|
|
|
|
if (bHasNaniteData)
|
|
{
|
|
if (Ar.IsSaving())
|
|
{
|
|
// Nanite data is 1:1 with each geometry group in the collection.
|
|
const int32 NumGeometryGroups = Owner.NumElements(FGeometryCollection::GeometryGroup);
|
|
if (!Owner.EnableNanite || NumGeometryGroups != NaniteResource.HierarchyRootOffsets.Num())
|
|
{
|
|
Ar.SetError();
|
|
}
|
|
}
|
|
|
|
NaniteResource.Serialize(Ar, &Owner, true);
|
|
}
|
|
else if (Ar.IsLoading())
|
|
{
|
|
NaniteResource = {};
|
|
}
|
|
}
|
|
|
|
void FGeometryCollectionRenderData::InitResources(UGeometryCollection const& Owner)
|
|
{
|
|
if (bIsInitialized)
|
|
{
|
|
ReleaseResources();
|
|
}
|
|
|
|
if (bHasMeshData)
|
|
{
|
|
MeshResource.InitResources(Owner);
|
|
}
|
|
|
|
if (bHasNaniteData)
|
|
{
|
|
NaniteResource.InitResources(&Owner);
|
|
}
|
|
|
|
bIsInitialized = true;
|
|
}
|
|
|
|
void FGeometryCollectionRenderData::ReleaseResources()
|
|
{
|
|
if (!bIsInitialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bHasMeshData)
|
|
{
|
|
MeshResource.ReleaseResources();
|
|
}
|
|
|
|
if (bHasNaniteData)
|
|
{
|
|
if (NaniteResource.ReleaseResources())
|
|
{
|
|
// HACK: Make sure the renderer is done processing the command, and done using NaniteResource, before we continue.
|
|
// This code could really use a refactor.
|
|
FRenderCommandFence Fence;
|
|
Fence.BeginFence();
|
|
Fence.Wait();
|
|
}
|
|
}
|
|
|
|
bIsInitialized = false;
|
|
}
|