// Copyright Epic Games, Inc. All Rights Reserved. #include "Rendering/LidarPointCloudRendering.h" #include "LidarPointCloudRenderBuffers.h" #include "LidarPointCloudComponent.h" #include "LidarPointCloud.h" #include "LidarPointCloudShared.h" #include "LidarPointCloudOctree.h" #include "LidarPointCloudLODManager.h" #include "PrimitiveSceneProxy.h" #include "MeshBatch.h" #include "Engine/Engine.h" #include "SceneInterface.h" #include "SceneManagement.h" #include "LocalVertexFactory.h" #include "Materials/Material.h" #include "Materials/MaterialRenderProxy.h" #include "SceneView.h" #if RHI_RAYTRACING #include "RayTracingInstance.h" #endif DECLARE_DWORD_COUNTER_STAT(TEXT("Draw Calls"), STAT_DrawCallCount, STATGROUP_LidarPointCloud) bool FLidarPointCloudProxyUpdateDataNode::BuildDataCache(bool bUseStaticBuffers, bool bUseRayTracing) { if(DataNode && DataNode->BuildDataCache(bUseStaticBuffers, bUseRayTracing)) { VertexFactory = DataNode->GetVertexFactory(); DataCache = DataNode->GetDataCache(); RayTracingGeometry = DataNode->GetRayTracingGeometry(); DataNode = nullptr; return true; } return false; } FLidarPointCloudProxyUpdateData::FLidarPointCloudProxyUpdateData() : NumElements(0) , VDMultiplier(1) , RootCellSize(1) { } class FLidarPointCloudCollisionRendering { public: FLidarPointCloudCollisionRendering() : NumPrimitives(0) , MaxVertexIndex(0) { } ~FLidarPointCloudCollisionRendering() { Release(); } void Initialize(FLidarPointCloudOctree* Octree) { // Create collision visualization buffers if (Octree->HasCollisionData()) { const FTriMeshCollisionData* CollisionData = Octree->GetCollisionData(); // Initialize the buffers VertexBuffer.Initialize(CollisionData->Vertices.GetData(), CollisionData->Vertices.Num()); VertexFactory.Initialize(&VertexBuffer); IndexBuffer.Initialize((int32*)CollisionData->Indices.GetData(), CollisionData->Indices.Num() * 3); NumPrimitives = CollisionData->Indices.Num(); MaxVertexIndex = CollisionData->Vertices.Num() - 1; } } void Release() { VertexFactory.ReleaseResource(); VertexBuffer.ReleaseResource(); IndexBuffer.ReleaseResource(); } const FVertexFactory* GetVertexFactory() const { return &VertexFactory; } const FIndexBuffer* GetIndexBuffer() const { return &IndexBuffer; } const int32 GetNumPrimitives() const { return NumPrimitives; } const int32 GetMaxVertexIndex() const { return MaxVertexIndex; } bool ShouldRenderCollision() const { return NumPrimitives > 0 && VertexFactory.IsInitialized(); } private: class FLidarPointCloudCollisionVertexFactory : public FLocalVertexFactory { public: FLidarPointCloudCollisionVertexFactory() : FLocalVertexFactory(ERHIFeatureLevel::SM5, "") { } void Initialize(FVertexBuffer* InVertexBuffer) { FRHICommandListBase& RHICmdList = FRHICommandListImmediate::Get(); FDataType NewData; NewData.PositionComponent = FVertexStreamComponent(InVertexBuffer, 0, 12, VET_Float3); NewData.ColorComponent = FVertexStreamComponent(&GNullColorVertexBuffer, 0, 0, VET_Color, EVertexStreamUsage::ManualFetch); NewData.TangentBasisComponents[0] = FVertexStreamComponent(&GNullColorVertexBuffer, 0, 0, VET_PackedNormal, EVertexStreamUsage::ManualFetch); NewData.TangentBasisComponents[1] = FVertexStreamComponent(&GNullColorVertexBuffer, 0, 0, VET_PackedNormal, EVertexStreamUsage::ManualFetch); if (RHISupportsManualVertexFetch(GMaxRHIShaderPlatform)) { NewData.PositionComponentSRV = RHICmdList.CreateShaderResourceView( InVertexBuffer->VertexBufferRHI, FRHIViewDesc::CreateBufferSRV() .SetType(FRHIViewDesc::EBufferType::Typed) .SetFormat(PF_R32_FLOAT)); NewData.ColorComponentsSRV = GNullColorVertexBuffer.VertexBufferSRV; NewData.TangentsSRV = GNullColorVertexBuffer.VertexBufferSRV; NewData.TextureCoordinatesSRV = GNullColorVertexBuffer.VertexBufferSRV; } Data = NewData; InitResource(RHICmdList); } } VertexFactory; class FLidarPointCloudCollisionVertexBuffer : public FVertexBuffer { const FVector3f* Data; int32 DataLength; public: void Initialize(const FVector3f* InData, int32 InDataLength) { Data = InData; DataLength = InDataLength; InitResource(FRHICommandListImmediate::Get()); } virtual void InitRHI(FRHICommandListBase& RHICmdList) override { const uint32 Size = DataLength * sizeof(FVector3f); const FRHIBufferCreateDesc CreateDesc = FRHIBufferCreateDesc::CreateVertex(TEXT("FLidarPointCloudCollisionVertexBuffer"), Size) .AddUsage(EBufferUsageFlags::Static | EBufferUsageFlags::ShaderResource) .SetInitialState(ERHIAccess::VertexOrIndexBuffer | ERHIAccess::SRVMask) .SetInitActionInitializer(); FRHIBufferInitializer InitialData = RHICmdList.CreateBufferInitializer(CreateDesc); InitialData.WriteData(Data, Size); VertexBufferRHI = InitialData.Finalize(); } } VertexBuffer; class FLidarPointCloudCollisionIndexBuffer : public FIndexBuffer { const int32* Data; int32 DataLength; public: void Initialize(const int32* InData, int32 InDataLength) { Data = InData; DataLength = InDataLength; InitResource(FRHICommandListImmediate::Get()); } virtual void InitRHI(FRHICommandListBase& RHICmdList) override { const uint32 Size = DataLength * sizeof(uint32); const FRHIBufferCreateDesc CreateDesc = FRHIBufferCreateDesc::CreateIndex(TEXT("FLidarPointCloudCollisionIndexBuffer"), Size, sizeof(uint32)) .AddUsage(EBufferUsageFlags::Static) .SetInitialState(ERHIAccess::VertexOrIndexBuffer) .SetInitActionInitializer(); FRHIBufferInitializer InitialData = RHICmdList.CreateBufferInitializer(CreateDesc); InitialData.WriteData(Data, Size); IndexBufferRHI = InitialData.Finalize(); } } IndexBuffer; int32 NumPrimitives; int32 MaxVertexIndex; }; class FLidarOneFrameResource : public FOneFrameResource { public: TArray Payload; virtual ~FLidarOneFrameResource() {} }; class FLidarPointCloudSceneProxy : public ILidarPointCloudSceneProxy, public FPrimitiveSceneProxy { public: FLidarPointCloudSceneProxy(ULidarPointCloudComponent* Component) : FPrimitiveSceneProxy(Component) , ProxyWrapper(MakeShared(this)) , bCompatiblePlatform(GetScene().GetFeatureLevel() >= ERHIFeatureLevel::SM5) , Owner(Component->GetOwner()) , CollisionRenderingPtr(&Component->GetPointCloud()->CollisionRendering) { // Skip material verification - async update could occasionally cause it to crash bVerifyUsedMaterials = false; TreeBuffer = new FLidarPointCloudRenderBuffer(); MaterialRelevance = Component->GetMaterialRelevance(GetScene().GetShaderPlatform()); } virtual ~FLidarPointCloudSceneProxy() { // Proxy is accessed only via RT, so there should not be any concurrency issues here ProxyWrapper->Proxy = nullptr; } virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override { QUICK_SCOPE_CYCLE_COUNTER(STAT_PointCloudSceneProxy_GetDynamicMeshElements); if (!CanBeRendered() || !RenderData.RenderParams.Material) { return; } for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { const FSceneView* View = Views[ViewIndex]; if (IsShown(View) && (VisibilityMap & (1 << ViewIndex))) { // Prepare the draw call if (RenderData.NumElements) { TArray& UserData = Collector.AllocateOneFrameResource().Payload; UserData.Reserve(RenderData.SelectedNodes.Num()); for (const FLidarPointCloudProxyUpdateDataNode& Node : RenderData.SelectedNodes) { if ((RenderData.bUseStaticBuffers && Node.VertexFactory.IsValid() && Node.VertexFactory->IsInitialized()) || (!RenderData.bUseStaticBuffers && Node.DataCache.IsValid())) { FMeshBatch& MeshBatch = Collector.AllocateMesh(); SetupMeshBatch(MeshBatch, Node, &UserData[UserData.Add(BuildUserDataElement(View, Node))]); Collector.AddMesh(ViewIndex, MeshBatch); INC_DWORD_STAT(STAT_DrawCallCount); } } } #if !(UE_BUILD_SHIPPING) FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex); // Draw selected nodes' bounds if (RenderData.RenderParams.bDrawNodeBounds) { for (const FBox& Node : RenderData.Bounds) { DrawWireBox(PDI, Node, FColor(72, 72, 255), SDPG_World); } } // Render bounds if (ViewFamily.EngineShowFlags.Bounds) { RenderBounds(PDI, ViewFamily.EngineShowFlags, GetBounds(), !Owner || IsSelected()); } // Render collision wireframe FLidarPointCloudCollisionRendering* CollisionRendering = *CollisionRenderingPtr; if (ViewFamily.EngineShowFlags.Collision && IsCollisionEnabled() && CollisionRendering && CollisionRendering->ShouldRenderCollision()) { // Create colored proxy FColoredMaterialRenderProxy* CollisionMaterialInstance; CollisionMaterialInstance = new FColoredMaterialRenderProxy(GEngine->WireframeMaterial->GetRenderProxy(), FColor(0, 255, 255, 255)); Collector.RegisterOneFrameMaterialProxy(CollisionMaterialInstance); FMeshBatch& MeshBatch = Collector.AllocateMesh(); MeshBatch.Type = PT_TriangleList; MeshBatch.VertexFactory = CollisionRendering->GetVertexFactory(); MeshBatch.bWireframe = true; MeshBatch.MaterialRenderProxy = CollisionMaterialInstance; MeshBatch.ReverseCulling = !IsLocalToWorldDeterminantNegative(); MeshBatch.DepthPriorityGroup = SDPG_World; MeshBatch.CastShadow = false; FMeshBatchElement& BatchElement = MeshBatch.Elements[0]; BatchElement.IndexBuffer = CollisionRendering->GetIndexBuffer(); BatchElement.FirstIndex = 0; BatchElement.NumPrimitives = CollisionRendering->GetNumPrimitives(); BatchElement.MinVertexIndex = 0; BatchElement.MaxVertexIndex = CollisionRendering->GetMaxVertexIndex(); Collector.AddMesh(ViewIndex, MeshBatch); } #endif // !(UE_BUILD_SHIPPING) } } } virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override { FPrimitiveViewRelevance Result; if (CanBeRendered()) { Result.bDrawRelevance = IsShown(View); Result.bShadowRelevance = IsShadowCast(View); Result.bDynamicRelevance = true; Result.bStaticRelevance = false; Result.bRenderInMainPass = ShouldRenderInMainPass(); Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask(); Result.bRenderCustomDepth = ShouldRenderCustomDepth(); MaterialRelevance.SetPrimitiveViewRelevance(Result); Result.bVelocityRelevance = DrawsVelocity() && Result.bOpaque && Result.bRenderInMainPass; } return Result; } /** UserData is used to pass rendering information to the VertexFactory */ FLidarPointCloudBatchElementUserData BuildUserDataElement(const FSceneView* InView, const FLidarPointCloudProxyUpdateDataNode& Node) const { FLidarPointCloudBatchElementUserData UserDataElement; const bool bUsesSprites = RenderData.RenderParams.PointSize > 0; // Update shader parameters UserDataElement.bEditorView = RenderData.RenderParams.bOwnedByEditor; UserDataElement.ReversedVirtualDepthMultiplier = RenderData.VDMultiplier; UserDataElement.VirtualDepth = RenderData.VDMultiplier * Node.VirtualDepth; UserDataElement.SpriteSizeMultiplier = bUsesSprites ? (RenderData.RenderParams.PointSize + RenderData.RenderParams.GapFillingStrength * 0.005f) * RenderData.RenderParams.ComponentScale : 0; UserDataElement.bUseScreenSizeScaling = RenderData.RenderParams.ScalingMethod == ELidarPointCloudScalingMethod::FixedScreenSize;; UserDataElement.bUsePerPointScaling = RenderData.RenderParams.ScalingMethod == ELidarPointCloudScalingMethod::PerPoint; UserDataElement.bUseStaticBuffers = RenderData.bUseStaticBuffers; UserDataElement.RootCellSize = RenderData.RootCellSize; UserDataElement.RootExtent = FVector3f(RenderData.RenderParams.BoundsSize.GetAbsMax() * 0.5f); UserDataElement.LocationOffset = RenderData.RenderParams.LocationOffset; UserDataElement.ViewRightVector = InView ? (FVector3f)InView->GetViewRight() : FVector3f::RightVector; UserDataElement.ViewUpVector = InView ? (FVector3f)InView->GetViewUp() : FVector3f::ForwardVector; UserDataElement.bUseCameraFacing = !RenderData.RenderParams.bShouldRenderFacingNormals; UserDataElement.BoundsSize = RenderData.RenderParams.BoundsSize; UserDataElement.ElevationColorBottom = FVector3f(RenderData.RenderParams.ColorSource == ELidarPointCloudColorationMode::None ? FColor::White : RenderData.RenderParams.ElevationColorBottom); UserDataElement.ElevationColorTop = FVector3f(RenderData.RenderParams.ColorSource == ELidarPointCloudColorationMode::None ? FColor::White : RenderData.RenderParams.ElevationColorTop); UserDataElement.bUseCircle = bUsesSprites && RenderData.RenderParams.PointShape == ELidarPointCloudSpriteShape::Circle; UserDataElement.bUseColorOverride = RenderData.RenderParams.ColorSource != ELidarPointCloudColorationMode::Data && RenderData.RenderParams.ColorSource != ELidarPointCloudColorationMode::DataWithClassificationAlpha; UserDataElement.bUseElevationColor = RenderData.RenderParams.ColorSource == ELidarPointCloudColorationMode::Elevation || RenderData.RenderParams.ColorSource == ELidarPointCloudColorationMode::None; UserDataElement.Offset = RenderData.RenderParams.Offset; UserDataElement.Contrast = RenderData.RenderParams.Contrast; UserDataElement.Saturation = RenderData.RenderParams.Saturation; UserDataElement.Gamma = RenderData.RenderParams.Gamma; UserDataElement.Tint = RenderData.RenderParams.ColorTint; UserDataElement.IntensityInfluence = RenderData.RenderParams.IntensityInfluence; UserDataElement.bUseClassification = RenderData.RenderParams.ColorSource == ELidarPointCloudColorationMode::Classification; UserDataElement.bUseClassificationAlpha = RenderData.RenderParams.ColorSource == ELidarPointCloudColorationMode::DataWithClassificationAlpha; UserDataElement.SetClassificationColors(RenderData.RenderParams.ClassificationColors); UserDataElement.NumClippingVolumes = FMath::Min(RenderData.ClippingVolumes.Num(), 16); for (uint32 i = 0; i < UserDataElement.NumClippingVolumes; ++i) { const FLidarPointCloudClippingVolumeParams& ClippingVolume = RenderData.ClippingVolumes[i]; UserDataElement.ClippingVolume[i] = FMatrix44f(ClippingVolume.PackedShaderData); UserDataElement.bStartClipped |= ClippingVolume.Mode == ELidarClippingVolumeMode::ClipOutside; } UserDataElement.TreeBuffer = TreeBuffer->SRV; return UserDataElement; } virtual bool CanBeOccluded() const override { return !MaterialRelevance.bDisableDepthTest; } virtual uint32 GetMemoryFootprint() const override { return(sizeof(*this) + GetAllocatedSize()); } uint32 GetAllocatedSize() const { return(FPrimitiveSceneProxy::GetAllocatedSize()); } bool CanBeRendered() const { return bCompatiblePlatform; } virtual SIZE_T GetTypeHash() const override { static size_t UniquePointer; return reinterpret_cast(&UniquePointer); } virtual void UpdateRenderData(const FLidarPointCloudProxyUpdateData& InRenderData) override { RenderData = InRenderData; FRHICommandListBase& RHICmdList = FRHICommandListImmediate::Get(); const int32 NumTreeStructure = RenderData.TreeStructure.Num() > 0 ? RenderData.TreeStructure.Num() : 16; TreeBuffer->Resize(NumTreeStructure); uint8* DataPtr = (uint8*)RHICmdList.LockBuffer(TreeBuffer->Buffer, 0, NumTreeStructure * sizeof(uint32), RLM_WriteOnly); FMemory::Memzero(DataPtr, NumTreeStructure * sizeof(uint32)); if (RenderData.TreeStructure.Num() > 0) { FMemory::Memcpy(DataPtr, RenderData.TreeStructure.GetData(), NumTreeStructure * sizeof(uint32)); } RHICmdList.UnlockBuffer(TreeBuffer->Buffer); } void SetupMeshBatch(FMeshBatch& MeshBatch, const FLidarPointCloudProxyUpdateDataNode& Node, const FLidarPointCloudBatchElementUserData* UserData) const { const bool bUsesSprites = RenderData.RenderParams.PointSize > 0; MeshBatch.Type = bUsesSprites ? PT_TriangleList : PT_PointList; MeshBatch.LODIndex = 0; MeshBatch.VertexFactory = RenderData.bUseStaticBuffers ? Node.VertexFactory.Get() : (FVertexFactory*)&GLidarPointCloudSharedVertexFactory; MeshBatch.bWireframe = false; MeshBatch.MaterialRenderProxy = RenderData.RenderParams.Material->GetRenderProxy(); MeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative(); MeshBatch.DepthPriorityGroup = SDPG_World; MeshBatch.bCanApplyViewModeOverrides = true; FMeshBatchElement& BatchElement = MeshBatch.Elements[0]; BatchElement.PrimitiveUniformBuffer = GetUniformBuffer(); BatchElement.IndexBuffer = &GLidarPointCloudIndexBuffer; BatchElement.FirstIndex = bUsesSprites ? 0 : GLidarPointCloudIndexBuffer.PointOffset; BatchElement.MinVertexIndex = 0; BatchElement.NumPrimitives = Node.NumVisiblePoints * (bUsesSprites ? 2 : 1); BatchElement.MaxVertexIndex = Node.NumVisiblePoints * (bUsesSprites ? 4 : 1); BatchElement.UserData = UserData; BatchElement.VertexFactoryUserData = RenderData.bUseStaticBuffers ? Node.VertexFactory->GetUniformBuffer() : Node.DataCache->GetUniformBuffer(); } #if RHI_RAYTRACING virtual bool IsRayTracingRelevant() const override { return true; } virtual bool HasRayTracingRepresentation() const override { return true; } virtual void GetDynamicRayTracingInstances(FRayTracingInstanceCollector& Collector) override { if (RenderData.NumElements) { TConstArrayView Views = Collector.GetViews(); const uint32 VisibilityMap = Collector.GetVisibilityMap(); // RT geometry will be generated based on first active view and then reused for all other views // TODO: Expose a way for developers to control whether to reuse RT geometry or create one per-view const int32 FirstActiveViewIndex = FMath::CountTrailingZeros(VisibilityMap); checkf(Views.IsValidIndex(FirstActiveViewIndex), TEXT("There should be at least one active view when calling GetDynamicRayTracingInstances(...).")); const FSceneView* FirstActiveView = Views[FirstActiveViewIndex]; TArray& UserData = Collector.AllocateOneFrameResource().Payload; UserData.Reserve(RenderData.SelectedNodes.Num()); CachedRayTracingMaterials.Reset(); const FMatrix& ThisLocalToWorld = GetLocalToWorld(); for (const FLidarPointCloudProxyUpdateDataNode& Node : RenderData.SelectedNodes) { if (Node.RayTracingGeometry.IsValid() && Node.RayTracingGeometry->IsInitialized()) { TArray &NodeRayTracingMaterials = CachedRayTracingMaterials.AddDefaulted_GetRef(); FMeshBatch &MeshBatch = NodeRayTracingMaterials.AddDefaulted_GetRef(); SetupMeshBatch(MeshBatch, Node, &UserData[UserData.Add(BuildUserDataElement(nullptr, Node))]); MeshBatch.SegmentIndex = 0; MeshBatch.CastRayTracedShadow = IsShadowCast(FirstActiveView); FLidarPointCloudRayTracingGeometry* Geometry = Node.RayTracingGeometry.Get(); for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { if ((VisibilityMap & (1 << ViewIndex)) == 0) { continue; } FRayTracingInstance RayTracingInstance; RayTracingInstance.Geometry = Geometry; RayTracingInstance.InstanceTransformsView = MakeArrayView(&ThisLocalToWorld, 1); RayTracingInstance.MaterialsView = MakeArrayView(NodeRayTracingMaterials); Collector.AddRayTracingInstance(ViewIndex, RayTracingInstance); } Collector.AddRayTracingGeometryUpdate( FirstActiveViewIndex, FRayTracingDynamicGeometryUpdateParams { NodeRayTracingMaterials, false, Geometry->GetNumVertices(), Geometry->GetBufferSize(), Geometry->GetNumPrimitives(), Geometry, nullptr, true } ); } } } } #endif // RHI_RAYTRACING public: TSharedPtr ProxyWrapper; private: FLidarPointCloudProxyUpdateData RenderData; FLidarPointCloudRenderBuffer* TreeBuffer; FMaterialRelevance MaterialRelevance; TArray> CachedRayTracingMaterials; bool bCompatiblePlatform; AActor* Owner; FLidarPointCloudCollisionRendering** CollisionRenderingPtr; }; FPrimitiveSceneProxy* ULidarPointCloudComponent::CreateSceneProxy() { FLidarPointCloudSceneProxy* Proxy = nullptr; if (PointCloud) { Proxy = new FLidarPointCloudSceneProxy(this); if (Proxy->CanBeRendered()) { FLidarPointCloudLODManager::RegisterProxy(this, Proxy->ProxyWrapper); } } return Proxy; } void ULidarPointCloud::InitializeCollisionRendering() { // Do not process, if the app is incapable of rendering if (!FApp::CanEverRender()) { return; } if (IsInRenderingThread()) { FScopeLock Lock(&Octree.DataLock); if (!CollisionRendering) { CollisionRendering = new FLidarPointCloudCollisionRendering(); } CollisionRendering->Initialize(&Octree); } else { ENQUEUE_RENDER_COMMAND(InitializeCollisionRendering)( [this](FRHICommandListImmediate& RHICmdList) { InitializeCollisionRendering(); }); } } void ULidarPointCloud::ReleaseCollisionRendering(bool bDestroyAfterRelease) { // Do not process, if the app in incapable of rendering if (!FApp::CanEverRender()) { return; } if (IsInRenderingThread()) { if (CollisionRendering) { if (bDestroyAfterRelease) { delete CollisionRendering; CollisionRendering = nullptr; } else { CollisionRendering->Release(); } } } else { ENQUEUE_RENDER_COMMAND(ReleaseCollisionRendering)( [this, bDestroyAfterRelease](FRHICommandListImmediate& RHICmdList) { ReleaseCollisionRendering(bDestroyAfterRelease); }); } }