// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Components/DynamicMeshComponent.h" #include "Components/BaseDynamicMeshSceneProxy.h" #include "DynamicMesh/MeshTangents.h" #include "Async/ParallelFor.h" #include "Materials/Material.h" #include "MaterialDomain.h" #include "MaterialShared.h" #include "SceneInterface.h" /** * Scene Proxy for UDynamicMeshComponent. * * Based on FProceduralMeshSceneProxy but simplified in various ways. * * Supports wireframe-on-shaded rendering. * */ class FDynamicMeshSceneProxy final : public FBaseDynamicMeshSceneProxy { using FIndex2i = UE::Geometry::FIndex2i; using FIndex3i = UE::Geometry::FIndex3i; private: FMaterialRelevance MaterialRelevance; // note: FBaseDynamicMeshSceneProxy owns and will destroy these TArray RenderBufferSets; // if true, we store entire mesh in single RenderBuffers and we can do some optimizations bool bIsSingleBuffer = false; public: /** Component that created this proxy (is there a way to look this up?) */ UDynamicMeshComponent* ParentComponent; FDynamicMeshSceneProxy(UDynamicMeshComponent* Component) : FBaseDynamicMeshSceneProxy(Component) , MaterialRelevance(Component->GetMaterialRelevance(GetScene().GetFeatureLevel())) { ParentComponent = Component; } virtual void GetActiveRenderBufferSets(TArray& Buffers) const override { Buffers = RenderBufferSets; } void Initialize() { // allocate buffer sets based on materials ensure(RenderBufferSets.Num() == 0); int32 NumMaterials = GetNumMaterials(); if (NumMaterials == 0) { RenderBufferSets.SetNum(1); RenderBufferSets[0] = AllocateNewRenderBufferSet(); RenderBufferSets[0]->Material = UMaterial::GetDefaultMaterial(MD_Surface); } else { RenderBufferSets.SetNum(NumMaterials); for (int32 k = 0; k < NumMaterials; ++k) { RenderBufferSets[k] = AllocateNewRenderBufferSet(); RenderBufferSets[k]->Material = GetMaterial(k); } } FDynamicMesh3* Mesh = ParentComponent->GetRenderMesh(); if (Mesh->HasAttributes() && Mesh->Attributes()->HasMaterialID() && NumMaterials > 1) { bIsSingleBuffer = false; InitializeByMaterial(RenderBufferSets); } else { bIsSingleBuffer = true; InitializeSingleBufferSet(RenderBufferSets[0]); } // set (or clear) simple Lumen cards via the bounding box // TODO: Implement a better method to set up the lumen cards UpdateLumenCardsFromBounds(); } TUniqueFunction MakeTangentsFunc(bool bSkipAutoCompute = false) { EDynamicMeshComponentTangentsMode TangentsType = ParentComponent->GetTangentsType(); if (TangentsType == EDynamicMeshComponentTangentsMode::ExternallyProvided) { UE::Geometry::FDynamicMesh3* RenderMesh = ParentComponent->GetRenderMesh(); // If the RenderMesh has tangents, use them. Otherwise we fall back to the orthogonal basis, below. if (RenderMesh && RenderMesh->HasAttributes() && RenderMesh->Attributes()->HasTangentSpace()) { UE::Geometry::FDynamicMeshTangents Tangents(RenderMesh); return [Tangents](int VertexID, int TriangleID, int TriVtxIdx, const FVector3f& Normal, FVector3f& TangentX, FVector3f& TangentY) -> void { Tangents.GetTangentVectors(TriangleID, TriVtxIdx, Normal, TangentX, TangentY); }; } } else if (TangentsType == EDynamicMeshComponentTangentsMode::AutoCalculated && bSkipAutoCompute == false) { const UE::Geometry::FMeshTangentsf* Tangents = ParentComponent->GetAutoCalculatedTangents(); if (Tangents != nullptr ) { return [Tangents](int VertexID, int TriangleID, int TriVtxIdx, const FVector3f& Normal, FVector3f& TangentX, FVector3f& TangentY) -> void { Tangents->GetTriangleVertexTangentVectors(TriangleID, TriVtxIdx, TangentX, TangentY); }; } } // fallback to orthogonal basis return [](int VertexID, int TriangleID, int TriVtxIdx, const FVector3f& Normal, FVector3f& TangentX, FVector3f& TangentY) -> void { UE::Geometry::VectorUtil::MakePerpVectors(Normal, TangentX, TangentY); }; } /** * Initialize multiple buffers for the mesh based on the given Decomposition */ void InitializeFromDecomposition(TUniquePtr& Decomposition) { ensure(RenderBufferSets.Num() == 0); int32 NumSets = Decomposition->Num(); RenderBufferSets.SetNum(NumSets); for (int32 k = 0; k < NumSets; ++k) { RenderBufferSets[k] = AllocateNewRenderBufferSet(); RenderBufferSets[k]->Material = Decomposition->GetGroup(k).Material; if (RenderBufferSets[k]->Material == nullptr) { RenderBufferSets[k]->Material = UMaterial::GetDefaultMaterial(MD_Surface); } } bIsSingleBuffer = false; const FDynamicMesh3* Mesh = ParentComponent->GetRenderMesh(); const FDynamicMeshAttributeSet* Attributes = Mesh->Attributes(); // find suitable overlays TArray> UVOverlays; UVOverlays.SetNum(Attributes->NumUVLayers()); for (int32 k = 0; k < UVOverlays.Num(); ++k) { UVOverlays[k] = Attributes->GetUVLayer(k); } const FDynamicMeshNormalOverlay* NormalOverlay = Attributes->PrimaryNormals(); const FDynamicMeshColorOverlay* ColorOverlay = Attributes->PrimaryColors(); TUniqueFunction TangentsFunc = MakeTangentsFunc(); // init renderbuffers for each set ParallelFor(NumSets, [&](int32 SetIndex) { const FMeshRenderDecomposition::FGroup& Group = Decomposition->GetGroup(SetIndex); const TArray& Triangles = Group.Triangles; if (Triangles.Num() > 0) { FMeshRenderBufferSet* RenderBuffers = RenderBufferSets[SetIndex]; RenderBuffers->Triangles = Triangles; InitializeBuffersFromOverlays(RenderBuffers, Mesh, Triangles.Num(), Triangles, UVOverlays, NormalOverlay, ColorOverlay, TangentsFunc); ENQUEUE_RENDER_COMMAND(FDynamicMeshSceneProxyInitializeFromDecomposition)( [RenderBuffers](FRHICommandListImmediate& RHICmdList) { RenderBuffers->Upload(); }); } }); } /** * Initialize a single set of mesh buffers for the entire mesh */ void InitializeSingleBufferSet(FMeshRenderBufferSet* RenderBuffers) { const FDynamicMesh3* Mesh = ParentComponent->GetRenderMesh(); // find suitable overlays TArray> UVOverlays; const FDynamicMeshNormalOverlay* NormalOverlay = nullptr; const FDynamicMeshColorOverlay* ColorOverlay = nullptr; if (Mesh->HasAttributes()) { const FDynamicMeshAttributeSet* Attributes = Mesh->Attributes(); NormalOverlay = Attributes->PrimaryNormals(); UVOverlays.SetNum(Attributes->NumUVLayers()); for (int32 k = 0; k < UVOverlays.Num(); ++k) { UVOverlays[k] = Attributes->GetUVLayer(k); } ColorOverlay = Attributes->PrimaryColors(); } TUniqueFunction TangentsFunc = MakeTangentsFunc(); const bool bTrackTriangles = false; const bool bParallel = true; InitializeBuffersFromOverlays(RenderBuffers, Mesh, Mesh->TriangleCount(), Mesh->TriangleIndicesItr(), UVOverlays, NormalOverlay, ColorOverlay, TangentsFunc, bTrackTriangles, bParallel); ENQUEUE_RENDER_COMMAND(FDynamicMeshSceneProxyInitializeSingle)( [RenderBuffers](FRHICommandListImmediate& RHICmdList) { RenderBuffers->Upload(); }); } /** * Initialize the mesh buffers, one per material */ void InitializeByMaterial(TArray& BufferSets) { const FDynamicMesh3* Mesh = ParentComponent->GetRenderMesh(); if (ensure(Mesh->HasAttributes() && Mesh->Attributes()->HasMaterialID()) == false) { return; } const FDynamicMeshAttributeSet* Attributes = Mesh->Attributes(); // find suitable overlays const FDynamicMeshMaterialAttribute* MaterialID = Attributes->GetMaterialID(); const FDynamicMeshNormalOverlay* NormalOverlay = Mesh->Attributes()->PrimaryNormals(); const FDynamicMeshColorOverlay* ColorOverlay = Mesh->Attributes()->PrimaryColors(); TArray> UVOverlays; UVOverlays.SetNum(Attributes->NumUVLayers()); for (int32 k = 0; k < UVOverlays.Num(); ++k) { UVOverlays[k] = Attributes->GetUVLayer(k); } TUniqueFunction TangentsFunc = MakeTangentsFunc(); // count number of triangles for each material (could do in parallel?) int NumMaterials = BufferSets.Num(); TArray Counts; Counts.SetNum(NumMaterials); for (int k = 0; k < NumMaterials; ++k) { Counts[k].Reset(); } ParallelFor(Mesh->MaxTriangleID(), [&](int tid) { if (!Mesh->IsTriangle(tid)) { return; } int MatIdx; MaterialID->GetValue(tid, &MatIdx); if (MatIdx >= 0 && MatIdx < NumMaterials) { Counts[MatIdx].Increment(); } }); // find max count int32 MaxCount = 0; for (FThreadSafeCounter& Count : Counts) { MaxCount = FMath::Max(Count.GetValue(), MaxCount); } // init renderbuffers for each material // could do this in parallel but then we need to allocate separate triangle arrays...is it worth it? TArray Triangles; Triangles.Reserve(MaxCount); for (int k = 0; k < NumMaterials; ++k) { if (Counts[k].GetValue() > 0) { FMeshRenderBufferSet* RenderBuffers = BufferSets[k]; Triangles.Reset(); for (int tid : Mesh->TriangleIndicesItr()) { int MatIdx; MaterialID->GetValue(tid, &MatIdx); if (MatIdx == k) { Triangles.Add(tid); } } const bool bTrackTriangles = false; const bool bParallel = true; InitializeBuffersFromOverlays(RenderBuffers, Mesh, Triangles.Num(), Triangles, UVOverlays, NormalOverlay, ColorOverlay, TangentsFunc, bTrackTriangles, bParallel); RenderBuffers->Triangles = Triangles; ENQUEUE_RENDER_COMMAND(FDynamicMeshSceneProxyInitializeByMaterial)( [RenderBuffers](FRHICommandListImmediate& RHICmdList) { RenderBuffers->Upload(); }); } } } bool RenderMeshLayoutMatchesRenderBuffers() const { const FDynamicMesh3* Mesh = ParentComponent->GetRenderMesh(); auto CheckBufferSet = [](const FDynamicMesh3* Mesh, const FMeshRenderBufferSet* BufferSet, int NumTriangles) -> bool { if (BufferSet->Triangles) { for (int TriangleID : BufferSet->Triangles.GetValue()) { if (!Mesh->IsTriangle(TriangleID)) { return false; } } } int NumVertices = NumTriangles * 3; if (BufferSet->PositionVertexBuffer.GetNumVertices() != NumVertices || BufferSet->StaticMeshVertexBuffer.GetNumVertices() != NumVertices || BufferSet->ColorVertexBuffer.GetNumVertices() != NumVertices) { return false; } return true; }; if (bIsSingleBuffer) { if (ensure(RenderBufferSets.Num() == 1)) { FMeshRenderBufferSet* BufferSet = RenderBufferSets[0]; FScopeLock BuffersLock(&BufferSet->BuffersLock); if (BufferSet->TriangleCount != Mesh->TriangleCount()) { return false; } int NumTriangles = Mesh->TriangleCount(); if (!CheckBufferSet(Mesh, BufferSet, NumTriangles)) { return false; } } } else { for (FMeshRenderBufferSet* BufferSet : RenderBufferSets) { FScopeLock BuffersLock(&BufferSet->BuffersLock); if (ensure(BufferSet->Triangles)) { int NumTriangles = BufferSet->Triangles->Num(); if (!CheckBufferSet(Mesh, BufferSet, NumTriangles)) { return false; } } } } return true; } /** * Update the vertex position/normal/color buffers */ void FastUpdateVertices(bool bPositions, bool bNormals, bool bColors, bool bUVs) { // This needs to be rewritten for split-by-material buffers. // Could store triangle set with each buffer, and then rebuild vtx buffer(s) as needed? FDynamicMesh3* Mesh = ParentComponent->GetRenderMesh(); // find suitable overlays and attributes FDynamicMeshNormalOverlay* NormalOverlay = nullptr; if (bNormals && ensure(Mesh->HasAttributes()) ) { NormalOverlay = Mesh->Attributes()->PrimaryNormals(); } TArray> UVOverlays; if (bUVs && ensure(Mesh->HasAttributes()) ) { const FDynamicMeshAttributeSet* Attributes = Mesh->Attributes(); UVOverlays.SetNum(Attributes->NumUVLayers()); for (int32 i = 0; i < UVOverlays.Num(); ++i) { UVOverlays[i] = Attributes->GetUVLayer(i); } } FDynamicMeshColorOverlay* ColorOverlay = nullptr; if (bColors && ensure(Mesh->HasAttributes()) ) { ColorOverlay = Mesh->Attributes()->PrimaryColors(); } // Currently deferring tangents auto-computation if doing fast vertex update (maybe needs to be exposed?) TUniqueFunction TangentsFunc = MakeTangentsFunc(true); if (bIsSingleBuffer) { if (ensure(RenderBufferSets.Num() == 1)) { FMeshRenderBufferSet* Buffers = RenderBufferSets[0]; FScopeLock BuffersLock(&Buffers->BuffersLock); if (bPositions || bNormals || bColors) { UpdateVertexBuffersFromOverlays(Buffers, Mesh, Mesh->TriangleCount(), Mesh->TriangleIndicesItr(), NormalOverlay, ColorOverlay, TangentsFunc, bPositions, bNormals, bColors); } if (bUVs) { UpdateVertexUVBufferFromOverlays(Buffers, Mesh, Mesh->TriangleCount(), Mesh->TriangleIndicesItr(), UVOverlays); } ENQUEUE_RENDER_COMMAND(FDynamicMeshSceneProxyFastUpdateVertices)( [Buffers, bPositions, bNormals, bColors, bUVs](FRHICommandListImmediate& RHICmdList) { Buffers->UploadVertexUpdate(bPositions, bNormals || bUVs, bColors); }); } } else { ParallelFor(RenderBufferSets.Num(), [&](int i) { FMeshRenderBufferSet* Buffers = RenderBufferSets[i]; FScopeLock BuffersLock(&Buffers->BuffersLock); if (Buffers->TriangleCount == 0) { return; } if (ensure(Buffers->Triangles.IsSet())) { if (bPositions || bNormals || bColors) { UpdateVertexBuffersFromOverlays(Buffers, Mesh, Buffers->Triangles->Num(), Buffers->Triangles.GetValue(), NormalOverlay, ColorOverlay, TangentsFunc, bPositions, bNormals, bColors); } if (bUVs) { UpdateVertexUVBufferFromOverlays(Buffers, Mesh, Buffers->Triangles->Num(), Buffers->Triangles.GetValue(), UVOverlays); } ENQUEUE_RENDER_COMMAND(FDynamicMeshSceneProxyFastUpdateVertices)( [Buffers, bPositions, bNormals, bColors, bUVs](FRHICommandListImmediate& RHICmdList) { Buffers->UploadVertexUpdate(bPositions, bNormals || bUVs, bColors); }); } }); } } /** * Update the vertex position/normal/color buffers */ void FastUpdateVertices(const TArray& WhichBuffers, bool bPositions, bool bNormals, bool bColors, bool bUVs) { TRACE_CPUPROFILER_EVENT_SCOPE(DynamicMeshProxy_FastUpdateVertices); // skip if we have no updates if (bPositions == false && bNormals == false && bColors == false && bUVs == false) { return; } // This needs to be rewritten for split-by-material buffers. // Could store triangle set with each buffer, and then rebuild vtx buffer(s) as needed? const FDynamicMesh3* Mesh = ParentComponent->GetRenderMesh(); // find suitable overlays const FDynamicMeshNormalOverlay* NormalOverlay = nullptr; if (bNormals && ensure(Mesh->HasAttributes())) { NormalOverlay = Mesh->Attributes()->PrimaryNormals(); } TArray UVOverlays; if (bUVs && ensure(Mesh->HasAttributes())) { const FDynamicMeshAttributeSet* Attributes = Mesh->Attributes(); UVOverlays.SetNum(Attributes->NumUVLayers()); for (int32 i = 0; i < UVOverlays.Num(); ++i) { UVOverlays[i] = Attributes->GetUVLayer(i); } } const FDynamicMeshColorOverlay* ColorOverlay = nullptr; if (bColors && ensure(Mesh->HasAttributes())) { ColorOverlay = Mesh->Attributes()->PrimaryColors(); } // Currently deferring tangents auto-computation if doing fast vertex update (maybe needs to be exposed?) TUniqueFunction TangentsFunc = MakeTangentsFunc(true); ParallelFor(WhichBuffers.Num(), [&](int idx) { int32 BufferIndex = WhichBuffers[idx]; if ( RenderBufferSets.IsValidIndex(BufferIndex) == false) { return; } FMeshRenderBufferSet* Buffers = RenderBufferSets[BufferIndex]; FScopeLock BuffersLock(&Buffers->BuffersLock); if (Buffers->TriangleCount == 0) { return; } if (ensure(Buffers->Triangles.IsSet())) { if (bPositions || bNormals || bColors) { UpdateVertexBuffersFromOverlays(Buffers, Mesh, Buffers->Triangles->Num(), Buffers->Triangles.GetValue(), NormalOverlay, ColorOverlay, TangentsFunc, bPositions, bNormals, bColors); } if (bUVs) { UpdateVertexUVBufferFromOverlays(Buffers, Mesh, Buffers->Triangles->Num(), Buffers->Triangles.GetValue(), UVOverlays); } ENQUEUE_RENDER_COMMAND(FDynamicMeshSceneProxyFastUpdateVerticesBufferList)( [Buffers, bPositions, bNormals, bColors, bUVs](FRHICommandListImmediate& RHICmdList) { Buffers->TransferVertexUpdateToGPU(RHICmdList, bPositions, bNormals, bUVs, bColors); }); } }); } /** * Update index buffers inside each RenderBuffer set */ void FastUpdateAllIndexBuffers() { FDynamicMesh3* Mesh = ParentComponent->GetRenderMesh(); // have to wait for all outstanding rendering to finish because the index buffers we are about to edit might be in-use FlushRenderingCommands(); ParallelFor(RenderBufferSets.Num(), [this, &Mesh](int i) { FMeshRenderBufferSet* Buffers = RenderBufferSets[i]; FScopeLock BuffersLock(&Buffers->BuffersLock); if (Buffers->TriangleCount > 0) { RecomputeRenderBufferTriangleIndexSets(Buffers, Mesh); } ENQUEUE_RENDER_COMMAND(FDynamicMeshSceneProxyFastUpdateAllIndexBuffers)( [Buffers](FRHICommandListImmediate& RHICmdList) { Buffers->UploadIndexBufferUpdate(); }); }); } /** * Update index buffers inside each RenderBuffer set */ void FastUpdateIndexBuffers(const TArray& WhichBuffers) { FDynamicMesh3* Mesh = ParentComponent->GetRenderMesh(); // have to wait for all outstanding rendering to finish because the index buffers we are about to edit might be in-use FlushRenderingCommands(); ParallelFor(WhichBuffers.Num(), [this, &WhichBuffers, &Mesh](int i) { int32 BufferIndex = WhichBuffers[i]; if (RenderBufferSets.IsValidIndex(BufferIndex) == false) { return; } FMeshRenderBufferSet* Buffers = RenderBufferSets[BufferIndex]; FScopeLock BuffersLock(&Buffers->BuffersLock); if (Buffers->TriangleCount > 0) { RecomputeRenderBufferTriangleIndexSets(Buffers, Mesh); } ENQUEUE_RENDER_COMMAND(FDynamicMeshSceneProxyFastUpdateSomeIndexBuffers)( [Buffers](FRHICommandListImmediate& RHICmdList) { Buffers->UploadIndexBufferUpdate(); }); }); } public: virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override { FPrimitiveViewRelevance Result; Result.bDrawRelevance = IsShown(View); Result.bShadowRelevance = IsShadowCast(View); bool bUseStaticDrawPath = bPreferStaticDrawPath && AllowStaticDrawPath(View); Result.bDynamicRelevance = !bUseStaticDrawPath; Result.bStaticRelevance = bUseStaticDrawPath; #if WITH_EDITOR //only check these in the editor Result.bEditorVisualizeLevelInstanceRelevance = IsEditingLevelInstanceChild(); Result.bEditorStaticSelectionRelevance = (IsSelected() || IsHovered()); #endif Result.bRenderInMainPass = ShouldRenderInMainPass(); Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask(); Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow; Result.bRenderCustomDepth = ShouldRenderCustomDepth(); // Note that this is actually a getter. One may argue that it is poorly named. MaterialRelevance.SetPrimitiveViewRelevance(Result); Result.bVelocityRelevance = DrawsVelocity() && Result.bOpaque && Result.bRenderInMainPass; return Result; } virtual void UpdatedReferencedMaterials() override { FBaseDynamicMeshSceneProxy::UpdatedReferencedMaterials(); // The material relevance may need updating. MaterialRelevance = ParentComponent->GetMaterialRelevance(GetScene().GetFeatureLevel()); } virtual void GetLightRelevance(const FLightSceneProxy* LightSceneProxy, bool& bDynamic, bool& bRelevant, bool& bLightMapped, bool& bShadowMapped) const override { FPrimitiveSceneProxy::GetLightRelevance(LightSceneProxy, bDynamic, bRelevant, bLightMapped, bShadowMapped); } virtual bool CanBeOccluded() const override { return !MaterialRelevance.bDisableDepthTest; } virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); } uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); } SIZE_T GetTypeHash() const override { static size_t UniquePointer; return reinterpret_cast(&UniquePointer); } };