// 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 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(bInNeedsCPUAccess); } void FBoneMapVertexBuffer::InitRHI(FRHICommandListBase& RHICmdList) { const bool bHadData = BoneMapData != nullptr; VertexBufferRHI = FRenderResource::CreateRHIBuffer(RHICmdList, BoneMapData, NumVertices, BUF_Static | BUF_ShaderResource | BUF_SourceCopy, TEXT("FBoneMapVertexBuffer")); if (VertexBufferRHI != nullptr) { VertexBufferSRV = RHICmdList.CreateShaderResourceView(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; // need subsection data on cooked builds when using ray tracing // to be able to build one BLAS per subsection #if !RHI_RAYTRACING if (Ar.IsCooking()) { // No need for SubSections in cooked build. TArray Dummy; Ar << Dummy; } else #endif { Ar << SubSections; } } #if WITH_EDITOR /** Data to return from BuildMeshDataFromGeometryCollection(). */ struct FGeometryCollectionBuiltMeshData { uint32 NumTexCoords = 0; TArray Indices; FMeshBuildVertexData Vertices; TArray BonesPerVertex; TArray MaterialsPerTriangle; TArray InternalPerTriangle; TArray MeshTriangleCounts; FBounds3f VertexBounds; }; /** Get the data needed to build render data from a Geometry Collection. */ bool BuildMeshDataFromGeometryCollection(FGeometryCollection& InCollection, FGeometryCollectionBuiltMeshData& OutMeshData, bool bConvertVertexColorsToSRGB) { // Vertices Group const TManagedArray& VertexArray = InCollection.Vertex; GeometryCollection::UV::FUVLayers UVsLayers = GeometryCollection::UV::FindActiveUVLayers(InCollection); const TManagedArray& ColorArray = InCollection.Color; const TManagedArray& TangentUArray = InCollection.TangentU; const TManagedArray& TangentVArray = InCollection.TangentV; const TManagedArray& NormalArray = InCollection.Normal; const TManagedArray& BoneMapArray = InCollection.BoneMap; // Faces Group const TManagedArray& IndicesArray = InCollection.Indices; const TManagedArray& VisibleArray = InCollection.Visible; const TManagedArray& MaterialIDArray = InCollection.MaterialID; const TManagedArray& Internal = InCollection.Internal; // Geometry Group const int32 NumGeometry = InCollection.NumElements(FGeometryCollection::GeometryGroup); const TManagedArray& VertexStartArray = InCollection.VertexStart; const TManagedArray& VertexCountArray = InCollection.VertexCount; const TManagedArray& FaceStartArray = InCollection.FaceStart; const TManagedArray& FaceCountArray = InCollection.FaceCount; const int32 NumTexCoords = InCollection.NumUVLayers(); const bool bHasColors = ColorArray.Num() > 0; // Iterate geometry groups and collect vertices. FMeshBuildVertexData BuildVertexData; BuildVertexData.UVs.SetNum(NumTexCoords); TArray BonesPerVertex; TArray 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]; } BuildVertexData.Position.AddUninitialized(NumBuildVertices); BuildVertexData.TangentX.AddUninitialized(NumBuildVertices); BuildVertexData.TangentY.AddUninitialized(NumBuildVertices); BuildVertexData.TangentZ.AddUninitialized(NumBuildVertices); for (int32 TexCoord = 0; TexCoord < NumTexCoords; ++TexCoord) { BuildVertexData.UVs[TexCoord].AddUninitialized(NumBuildVertices); } if (bHasColors) { BuildVertexData.Color.AddUninitialized(NumBuildVertices); } BonesPerVertex.AddUninitialized(NumBuildVertices); TArray Bounds; ParallelForWithTaskContext(Bounds, NumGeometry, [&](FBounds3f& BatchBounds, int32 GeometryGroupIndex) { const int32 VertexStart = VertexStartArray[GeometryGroupIndex]; const int32 VertexCount = VertexCountArray[GeometryGroupIndex]; const int32 DestVertexStart = DestVertexStarts[GeometryGroupIndex]; TArray GeometryBounds; ParallelForWithTaskContext(TEXT("GC:BuildVertices"), GeometryBounds, VertexCount, 500, [&](FBounds3f& BatchGeometryBounds, int32 VertexIndex) { BatchGeometryBounds += VertexArray[VertexStart + VertexIndex]; BuildVertexData.Position[DestVertexStart + VertexIndex] = VertexArray[VertexStart + VertexIndex]; BuildVertexData.TangentX[DestVertexStart + VertexIndex] = TangentUArray[VertexStart + VertexIndex]; BuildVertexData.TangentY[DestVertexStart + VertexIndex] = TangentVArray[VertexStart + VertexIndex]; BuildVertexData.TangentZ[DestVertexStart + VertexIndex] = NormalArray[VertexStart + VertexIndex]; if (bHasColors) { BuildVertexData.Color[DestVertexStart + VertexIndex] = ColorArray[VertexStart + VertexIndex].ToFColor(bConvertVertexColorsToSRGB /* sRGB */); } for (int32 TexCoord = 0; TexCoord < NumTexCoords; ++TexCoord) { FVector2f UV = UVsLayers[TexCoord][VertexStart + VertexIndex]; if (UV.ContainsNaN()) { UV = FVector2f::ZeroVector; } BuildVertexData.UVs[TexCoord][DestVertexStart + VertexIndex] = UV; } const int32 BoneIndex = BoneMapArray[VertexStart + VertexIndex]; BonesPerVertex[DestVertexStart + VertexIndex] = (uint16)BoneIndex; }); for (const FBounds3f& B : GeometryBounds) BatchBounds += B; }); for (const FBounds3f& B : Bounds) OutMeshData.VertexBounds += B; } bool bAllOpaqueWhite = true; for (const FColor& Color : BuildVertexData.Color) { if (Color != FColor::White) { bAllOpaqueWhite = false; break; } } if (bAllOpaqueWhite) { BuildVertexData.Color.Empty(); } // Iterate geometry groups and collect indices. // Can't go parallel here because we remove hidden faces as we go. TArray BuildIndices; TArray MaterialsPerFace; TArray InternalPerFace; TArray 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 (BuildVertexData.Position[FaceIndices[0]] == BuildVertexData.Position[FaceIndices[1]] || BuildVertexData.Position[FaceIndices[1]] == BuildVertexData.Position[FaceIndices[2]] || BuildVertexData.Position[FaceIndices[2]] == BuildVertexData.Position[FaceIndices[0]]) { 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(BuildVertexData); OutMeshData.BonesPerVertex = MoveTemp(BonesPerVertex); OutMeshData.MaterialsPerTriangle = MoveTemp(MaterialsPerFace); OutMeshData.InternalPerTriangle = MoveTemp(InternalPerFace); OutMeshData.MeshTriangleCounts = MoveTemp(MeshTriangleCounts); // Return false if we found no mesh data. return OutMeshData.Indices.Num() > 0 && OutMeshData.Vertices.Position.Num() > 0; } struct FGeometryCollectionBuiltMeshSectionData { TArray SortedIndices; TArray Sections; TArray SectionsNoInternal; TArray SubSections; }; void BuildSectionDataFromMeshData(FGeometryCollectionBuiltMeshData const& InMeshData, FGeometryCollectionBuiltMeshSectionData& OutSectionData) { // Find spans within the mesh data. TArray 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 SortedSpanIndices; { TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::SortSpans); TArray 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 Sections; TArray SectionsNoInternal; TArray 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 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 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& BoundingBoxes = InCollection.BoundingBox; const TManagedArray& Transform = InCollection.Transform; const TManagedArray& Parent = InCollection.Parent; const TManagedArray& TransformIndices = InCollection.TransformIndex; const int32 NumBoxes = BoundingBoxes.Num(); TArray 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.Position.Num(); OutRenderData.MeshDescription.NumTriangles = InMeshData.Indices.Num() / 3; const FConstMeshBuildVertexView VertexView = MakeConstMeshBuildVertexView(InMeshData.Vertices); { TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::BufferInit); OutRenderData.MeshResource.PositionVertexBuffer.Init(VertexView); OutRenderData.MeshResource.StaticMeshVertexBuffer.SetUseFullPrecisionUVs(bInUseFullPrecisionUVs); OutRenderData.MeshResource.StaticMeshVertexBuffer.Init(VertexView); OutRenderData.MeshResource.ColorVertexBuffer.Init(VertexView); 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; ClearNaniteResources(OutRenderData.NaniteResourcesPtr); Nanite::IBuilderModule& NaniteBuilderModule = Nanite::IBuilderModule::Get(); // Sections are left empty because they are not used (not building a coarse representation) Nanite::IBuilderModule::FInputMeshData InputMeshData; InputMeshData.Vertices = MoveTemp(InMeshData.Vertices); InputMeshData.TriangleIndices = MoveTemp(InMeshData.Indices); InputMeshData.MaterialIndices = MoveTemp(InMeshData.MaterialsPerTriangle); InputMeshData.TriangleCounts = MoveTemp(InMeshData.MeshTriangleCounts); InputMeshData.NumTexCoords = InMeshData.NumTexCoords; InputMeshData.VertexBounds = InMeshData.VertexBounds; if (!NaniteBuilderModule.Build( *OutRenderData.NaniteResourcesPtr.Get(), InputMeshData, nullptr, // OutFallbackMeshData nullptr, // OutRayTracingFallbackMeshData nullptr, // RayTracingFallbackBuildSettings NaniteSettings)) { UE_LOG(LogStaticMesh, Error, TEXT("Failed to build Nanite for geometry collection. See previous line(s) for details.")); } } TUniquePtr FGeometryCollectionRenderData::Create(FGeometryCollection& InCollection, bool bInEnableNanite, bool bInEnableNaniteFallback, bool bInUseFullPrecisionUVs, bool bConvertVertexColorsToSRGB) { TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::Create); TUniquePtr RenderData = MakeUnique(); FGeometryCollectionBuiltMeshData MeshBuildData; if (BuildMeshDataFromGeometryCollection(InCollection, MeshBuildData, bConvertVertexColorsToSRGB)) { if (!bInEnableNanite || bInEnableNaniteFallback) { CreateMeshData(InCollection, MeshBuildData, bInUseFullPrecisionUVs, *RenderData.Get()); } if (bInEnableNanite) { CreateNaniteData(MoveTemp(MeshBuildData), *RenderData.Get()); } } return RenderData; } #endif // WITH_EDITOR FGeometryCollectionRenderData::~FGeometryCollectionRenderData() { // We should have released any resources before now. check(!bIsInitialized); } void FGeometryCollectionRenderData::Serialize(FArchive& Ar, UGeometryCollection& Owner) { const bool bIsArchiveValidCandidateForStrip = (Ar.IsCooking() || (Ar.IsCountingMemory() && Ar.IsFilterEditorOnly())); if (Owner.bStripRenderDataOnCook && bIsArchiveValidCandidateForStrip) { // Don't cook rendering data. // This is used if we expect to use a custom GC render path such as the ISM pool. bool bDummy = false; Ar << bDummy; // bHasMeshData Ar << bDummy; // bHasNaniteData return; } Ar << bHasMeshData; #if WITH_EDITOR bool bStripNaniteDataFromCook = Ar.IsCooking() && !DoesTargetPlatformSupportNanite(Ar.CookingTarget()); #else bool bStripNaniteDataFromCook = false; #endif if (bStripNaniteDataFromCook) { bool bDummy = false; Ar << bDummy; } else { Ar << bHasNaniteData; } if (bHasMeshData) { MeshResource.Serialize(Ar); MeshDescription.Serialize(Ar); } else if (Ar.IsLoading()) { MeshResource = {}; MeshDescription = {}; } if (bHasNaniteData && !bStripNaniteDataFromCook) { InitNaniteResources(NaniteResourcesPtr); 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 != NaniteResourcesPtr->HierarchyRootOffsets.Num()) { Ar.SetError(); } } NaniteResourcesPtr->Serialize(Ar, &Owner, true); } else if (Ar.IsLoading()) { ClearNaniteResources(NaniteResourcesPtr); } } void FGeometryCollectionRenderData::InitResources(UGeometryCollection const& Owner) { if (bIsInitialized) { ReleaseResources(); } if (bHasMeshData) { MeshResource.InitResources(Owner); } if (bHasNaniteData) { NaniteResourcesPtr->InitResources(&Owner); } bIsInitialized = true; } void FGeometryCollectionRenderData::ReleaseResources() { if (!bIsInitialized) { return; } if (bHasMeshData) { MeshResource.ReleaseResources(); } if (bHasNaniteData) { NaniteResourcesPtr->ReleaseResources(); } bIsInitialized = false; }