// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "DynamicMeshBuilder.h" #include "EngineGlobals.h" #include "HAL/CriticalSection.h" #include "PrimitiveViewRelevance.h" #include "PrimitiveSceneProxy.h" #include "StaticMeshResources.h" #include "Rendering/SkinWeightVertexBuffer.h" #include "GeometryCollectionRendering.h" #include "GeometryCollection/GeometryCollectionEditorSelection.h" #include "HitProxies.h" #include "EngineUtils.h" #include "NaniteSceneProxy.h" #include "GeometryCollection/GeometryCollectionObject.h" #include "GeometryCollection/GeometryCollectionRenderData.h" #include "GeometryCollection/GeometryCollectionHitProxy.h" #include "InstanceDataSceneProxy.h" class UGeometryCollection; class UGeometryCollectionComponent; struct FGeometryCollectionSection; namespace Nanite { struct FResources; } /** Vertex Buffer for transform data */ class FGeometryCollectionTransformBuffer : public FVertexBuffer { public: virtual void InitRHI(FRHICommandListBase& RHICmdList) override { // #note: This differs from instanced static mesh in that we are storing the entire transform in the buffer rather than // splitting out the translation. This is to simplify transferring data at runtime as a memcopy const FRHIBufferCreateDesc CreateDesc = FRHIBufferCreateDesc::CreateVertex(TEXT("FGeometryCollectionTransformBuffer"), NumTransforms * 4) .AddUsage(EBufferUsageFlags::Dynamic | EBufferUsageFlags::ShaderResource) .DetermineInitialState(); VertexBufferRHI = RHICmdList.CreateBuffer(CreateDesc); VertexBufferSRV = RHICmdList.CreateShaderResourceView( VertexBufferRHI, FRHIViewDesc::CreateBufferSRV() .SetType(FRHIViewDesc::EBufferType::Typed) .SetFormat(PF_A32B32G32R32F)); } void UpdateDynamicData(FRHICommandListBase& RHICmdList, const TArray& Transforms, EResourceLockMode LockMode); int32 NumTransforms; FShaderResourceViewRHIRef VertexBufferSRV; }; inline void CopyTransformsWithConversionWhenNeeded(TArray& DstTransforms, const TArray& SrcTransforms) { // LWC_TODO : we have no choice but to convert each element at this point to avoid changing GeometryCollectionAlgo::GlobalMatrices that is used all over the place DstTransforms.SetNumUninitialized(SrcTransforms.Num()); for (int TransformIndex = 0; TransformIndex < SrcTransforms.Num(); ++TransformIndex) { DstTransforms[TransformIndex] = FMatrix44f(SrcTransforms[TransformIndex]); // LWC_TODO: Perf pessimization } } inline void CopyTransformsWithConversionWhenNeeded(TArray& DstTransforms, const TArray& SrcTransforms) { // LWC_TODO : we have no choice but to convert each element at this point to avoid changing GeometryCollectionAlgo::GlobalMatrices that is used all over the place DstTransforms.SetNumUninitialized(SrcTransforms.Num()); for (int TransformIndex = 0; TransformIndex < SrcTransforms.Num(); ++TransformIndex) { DstTransforms[TransformIndex] = FTransform3f(SrcTransforms[TransformIndex]).ToMatrixWithScale(); // LWC_TODO: Perf pessimization } } inline void CopyTransformsWithConversionWhenNeeded(TArray& DstTransforms, const TArray& SrcTransforms) { DstTransforms.SetNumUninitialized(SrcTransforms.Num()); for (int TransformIndex = 0; TransformIndex < SrcTransforms.Num(); ++TransformIndex) { DstTransforms[TransformIndex] = SrcTransforms[TransformIndex].ToMatrixWithScale(); } } /** Mutable rendering data */ struct FGeometryCollectionDynamicData { TArray Transforms; uint64 FrameIndex = 0; FGeometryCollectionDynamicData() { Reset(); } void Reset() { Transforms.Reset(); FrameIndex = GFrameCounter; } void SetTransforms(const TArray& InTransforms) { // use for LWC as FMatrix and FMatrix44f are different when LWC is on CopyTransformsWithConversionWhenNeeded(Transforms, InTransforms); } void SetTransforms(const TArray& InTransforms) { CopyTransformsWithConversionWhenNeeded(Transforms, InTransforms); } }; class FGeometryCollectionDynamicDataPool { public: FGeometryCollectionDynamicDataPool(); ~FGeometryCollectionDynamicDataPool(); FGeometryCollectionDynamicData* Allocate(); void Release(FGeometryCollectionDynamicData* DynamicData); private: TArray UsedList; TArray FreeList; FCriticalSection ListLock; }; class FGeometryCollectionSceneProxyBase { protected: FGeometryCollectionSceneProxyBase(UGeometryCollectionComponent* Component, bool bInIsNanite); virtual ~FGeometryCollectionSceneProxyBase(); void CreateRenderThreadResources(FRHICommandListBase& RHICmdList); void DestroyRenderThreadResources(); /** Setup a geometry collection vertex factory. */ void SetupVertexFactory(FRHICommandListBase& RHICmdList, FGeometryCollectionVertexFactory& GeometryCollectionVertexFactory, FColorVertexBuffer* ColorOverride = nullptr) const; /** Called on render thread to setup dynamic geometry for rendering */ void SetDynamicData_RenderThread(FRHICommandListBase& RHICmdList, FGeometryCollectionDynamicData* NewDynamicData); FGeometryCollectionTransformBuffer& GetCurrentTransformBuffer() { return TransformBuffers[CurrentTransformBufferIndex]; } const FGeometryCollectionTransformBuffer& GetCurrentTransformBuffer() const { return TransformBuffers[CurrentTransformBufferIndex]; } const FGeometryCollectionTransformBuffer& GetCurrentPrevTransformBuffer() const { const int32 NumBuffers = TransformBuffers.Num(); const int32 PreviousIndex = (CurrentTransformBufferIndex + NumBuffers - 1) % NumBuffers; return TransformBuffers[PreviousIndex]; } void CycleTransformBuffers(bool bCycle) { if (bCycle) { CurrentTransformBufferIndex = (CurrentTransformBufferIndex + 1) % TransformBuffers.Num(); } } #if RHI_RAYTRACING void UpdatingRayTracingGeometry_RenderingThread(FRHICommandListBase& RHICmdList, TConstArrayView InSectionArray, bool bRayTracingGeometryPerSection); bool IsRayTracingRelevant() const { return true; } bool IsRayTracingStaticRelevant() const { return false; } bool HasRayTracingRepresentation() const { return bHasRayTracingRepresentation; } void GetDynamicRayTracingInstances(FRayTracingInstanceCollector& Collector, const FMatrix& LocalToWorld, FRHIUniformBuffer* UniformBuffer, bool bAnyMaterialHasWorldPositionOffset); #endif uint32 GetAllocatedSize() const; protected: const bool bIsNanite; ERHIFeatureLevel::Type FeatureLevel; const FGeometryCollectionMeshResources& MeshResource; FGeometryCollectionMeshDescription MeshDescription; FMaterialRelevance MaterialRelevance; FGeometryCollectionVertexFactory VertexFactory; const bool bUseShaderBoneTransform; const bool bSupportsTripleBufferVertexUpload; TArray Materials; int32 NumTransforms = 0; FGeometryCollectionDynamicData* DynamicData = nullptr; private: /** Update skinned position buffer used by mobile CPU skinning path. */ void UpdateSkinnedPositions(FRHICommandListBase& RHICmdList, TArray const& Transforms); FPositionVertexBuffer SkinnedPositionVertexBuffer; int32 CurrentTransformBufferIndex = 0; TArray> TransformBuffers; const bool bHasRayTracingRepresentation = false; #if RHI_RAYTRACING FRayTracingGeometry RayTracingGeometry; FRWBuffer RayTracingDynamicVertexBuffer; TArray PartRayTracingGeometries; #endif bool bRenderResourcesCreated = false; }; /*** * FGeometryCollectionSceneProxy * * The FGeometryCollectionSceneProxy manages the interaction between the GeometryCollectionComponent * on the game thread and the vertex buffers on the render thread. * * NOTE : This class is still in flux, and has a few pending todos. Your comments and * thoughts are appreciated though. The remaining items to address involve: * - @todo double buffer - The double buffering of the FGeometryCollectionDynamicData. * - @todo GPU skin : Make the skinning use the GpuVertexShader */ class FGeometryCollectionSceneProxy final : public FPrimitiveSceneProxy, private FGeometryCollectionSceneProxyBase { TSharedPtr GeometryCollection; FCollisionResponseContainer CollisionResponse; FBoxSphereBounds PreSkinnedBounds; bool bRenderResourcesCreated = false; #if WITH_EDITOR bool bShowBoneColors = false; bool bSuppressSelectionMaterial = false; TArray BoneColors; FColorVertexBuffer ColorVertexBuffer; FGeometryCollectionVertexFactory VertexFactoryDebugColor; UMaterialInterface* BoneSelectedMaterial = nullptr; #endif #if GEOMETRYCOLLECTION_EDITOR_SELECTION bool bUsesSubSections = false; bool bEnableBoneSelection = false; TArray> HitProxies; FColorVertexBuffer HitProxyIdBuffer; #endif public: FGeometryCollectionSceneProxy(UGeometryCollectionComponent* Component); virtual ~FGeometryCollectionSceneProxy(); /** Called on render thread to setup dynamic geometry for rendering */ void SetDynamicData_RenderThread(FRHICommandListBase& RHICmdList, FGeometryCollectionDynamicData* NewDynamicData); virtual uint32 GetMemoryFootprint() const override { return sizeof(*this) + GetAllocatedSize(); } uint32 GetAllocatedSize() const; SIZE_T GetTypeHash() const override; void CreateRenderThreadResources(FRHICommandListBase& RHICmdList) override; void DestroyRenderThreadResources() override; void GetPreSkinnedLocalBounds(FBoxSphereBounds& OutBounds) const override; FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override; void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override; virtual bool AllowInstanceCullingOcclusionQueries() const override { return true; } #if GEOMETRYCOLLECTION_EDITOR_SELECTION virtual HHitProxy* CreateHitProxies(UPrimitiveComponent* Component, TArray >& OutHitProxies) override; virtual const FColorVertexBuffer* GetCustomHitProxyIdBuffer() const override { return (bEnableBoneSelection || bUsesSubSections) ? &HitProxyIdBuffer : nullptr; } #endif #if RHI_RAYTRACING bool IsRayTracingRelevant() const override { return FGeometryCollectionSceneProxyBase::IsRayTracingRelevant(); } bool IsRayTracingStaticRelevant() const override { return FGeometryCollectionSceneProxyBase::IsRayTracingStaticRelevant(); } bool HasRayTracingRepresentation() const override { return FGeometryCollectionSceneProxyBase::HasRayTracingRepresentation(); } void GetDynamicRayTracingInstances(FRayTracingInstanceCollector& Collector) override { return FGeometryCollectionSceneProxyBase::GetDynamicRayTracingInstances(Collector, GetLocalToWorld(), GetUniformBuffer(), bAnyMaterialHasWorldPositionOffset); } #endif protected: /** Get material proxy from material ID */ FMaterialRenderProxy* GetMaterial(FMeshElementCollector& Collector, int32 MaterialIndex) const; /** Get the standard or debug vertex factory dependent on current state. */ FVertexFactory const* GetVertexFactory() const; private: bool ShowCollisionMeshes(const FEngineShowFlags& EngineShowFlags) const; }; class FNaniteGeometryCollectionSceneProxy : public Nanite::FSceneProxyBase, private FGeometryCollectionSceneProxyBase { public: using Super = Nanite::FSceneProxyBase; FNaniteGeometryCollectionSceneProxy(UGeometryCollectionComponent* Component); virtual ~FNaniteGeometryCollectionSceneProxy(); public: // FPrimitiveSceneProxy interface. virtual void CreateRenderThreadResources(FRHICommandListBase& RHICmdList) override; virtual void DestroyRenderThreadResources() override; virtual SIZE_T GetTypeHash() const override; virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override; virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override; #if GEOMETRYCOLLECTION_EDITOR_SELECTION virtual HHitProxy* CreateHitProxies(UPrimitiveComponent* Component, TArray >& OutHitProxies) override; #endif virtual void DrawStaticElements(FStaticPrimitiveDrawInterface* PDI) override; virtual uint32 GetMemoryFootprint() const override { return sizeof(*this) + GetAllocatedSize(); } uint32 GetAllocatedSize() const; // FSceneProxyBase interface. virtual void GetNaniteResourceInfo(uint32& ResourceID, uint32& HierarchyOffset, uint32& AssemblyTransformOffset, uint32& ImposterIndex) const override; virtual Nanite::FResourceMeshInfo GetResourceMeshInfo() const override; /** Called on render thread to setup dynamic geometry for rendering */ void SetDynamicData_RenderThread(FRHICommandListBase& RHICmdList, FGeometryCollectionDynamicData* NewDynamicData, const FMatrix &PrimitiveLocalToWorld); void ResetPreviousTransforms_RenderThread(); void FlushGPUSceneUpdate_GameThread(); FORCEINLINE void SetRequiresGPUSceneUpdate_RenderThread(bool bRequireUpdate) { bRequiresGPUSceneUpdate = bRequireUpdate; } FORCEINLINE bool GetRequiresGPUSceneUpdate_RenderThread() const { return bRequiresGPUSceneUpdate; } inline virtual void GetLCIs(FLCIArray& LCIs) override { FLightCacheInterface* LCI = &EmptyLightCacheInfo; LCIs.Add(LCI); } #if RHI_RAYTRACING bool IsRayTracingRelevant() const override { return FGeometryCollectionSceneProxyBase::IsRayTracingRelevant(); } bool IsRayTracingStaticRelevant() const override { return FGeometryCollectionSceneProxyBase::IsRayTracingStaticRelevant(); } bool HasRayTracingRepresentation() const override { return FGeometryCollectionSceneProxyBase::HasRayTracingRepresentation(); } void GetDynamicRayTracingInstances(FRayTracingInstanceCollector& Collector) override { return FGeometryCollectionSceneProxyBase::GetDynamicRayTracingInstances(Collector, GetLocalToWorld(), GetUniformBuffer(), bAnyMaterialHasWorldPositionOffset); } #endif protected: // TODO : Copy required data from UObject instead of using unsafe object pointer. const UGeometryCollection* GeometryCollection = nullptr; FCollisionResponseContainer CollisionResponse; struct FGeometryNaniteData { FBoxSphereBounds LocalBounds; uint32 HierarchyOffset; }; TArray GeometryNaniteData; uint32 NaniteResourceID = INDEX_NONE; uint32 NaniteHierarchyOffset = INDEX_NONE; uint32 bCastShadow : 1; uint32 bReverseCulling : 1; uint32 bHasMaterialErrors : 1; uint32 bRequiresGPUSceneUpdate : 1; uint32 bEnableBoneSelection : 1; #if GEOMETRYCOLLECTION_EDITOR_SELECTION TArray> HitProxies; #endif FInstanceSceneDataBuffers InstanceSceneDataBuffersImpl; // Geometry collection doesn't currently support baked light maps, so we use this simple empty light cache info for all nanite geometry collection proxies class FEmptyLightCacheInfo : public FLightCacheInterface { public: // FLightCacheInterface. GEOMETRYCOLLECTIONENGINE_API virtual FLightInteraction GetInteraction(const FLightSceneProxy* LightSceneProxy) const override; }; private: void UpdateInstanceSceneDataBuffers(const FMatrix& PrimitiveLocalToWorld); bool ShowCollisionMeshes(const FEngineShowFlags& EngineShowFlags) const; private: static FEmptyLightCacheInfo EmptyLightCacheInfo; };