You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
* Primitives are only tracked by Lumen scene if they can be traced by the current ray tracing method. E.g. if we use HWRT then only primitives with valid BLAS will generate surface cache. * Instead of recomputing VisibleInLumenScene on every add/remove/update this flag is now cached on proxy * Added GPU side filtering of draws which should be skipped when capturing surface cache #preflight 63dd8637323e7eaa30b71680 #jira UE-176073 #rb Daniel.Wright [CL 24031439 by krzysztof narkowicz in ue5-main branch]
1984 lines
75 KiB
C++
1984 lines
75 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "GeometryCollection/GeometryCollectionSceneProxy.h"
|
|
|
|
#include "Async/ParallelFor.h"
|
|
#include "Engine/Engine.h"
|
|
#include "GeometryCollection/GeometryCollection.h"
|
|
#include "GeometryCollection/GeometryCollectionObject.h"
|
|
#include "MaterialDomain.h"
|
|
#include "MaterialShared.h"
|
|
#include "MaterialShaderType.h"
|
|
#include "Materials/Material.h"
|
|
#include "Materials/MaterialRenderProxy.h"
|
|
#include "CommonRenderResources.h"
|
|
#include "Rendering/NaniteResources.h"
|
|
#include "PrimitiveSceneInfo.h"
|
|
#include "GeometryCollection/GeometryCollectionComponent.h"
|
|
#include "GeometryCollection/GeometryCollectionAlgo.h"
|
|
#include "RHIDefinitions.h"
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
#include "GeometryCollection/GeometryCollectionHitProxy.h"
|
|
#endif
|
|
|
|
#if INTEL_ISPC
|
|
#if USING_CODE_ANALYSIS
|
|
MSVC_PRAGMA( warning( push ) )
|
|
MSVC_PRAGMA( warning( disable : ALL_CODE_ANALYSIS_WARNINGS ) )
|
|
#endif // USING_CODE_ANALYSIS
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wnonportable-include-path"
|
|
#endif
|
|
|
|
#include "GeometryCollectionSceneProxy.ispc.generated.h"
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
|
|
#if USING_CODE_ANALYSIS
|
|
MSVC_PRAGMA( warning( pop ) )
|
|
#endif // USING_CODE_ANALYSIS
|
|
|
|
static_assert(sizeof(ispc::FMatrix44f) == sizeof(FMatrix44f), "sizeof(ispc::FMatrix44f) != sizeof(FMatrix44f)");
|
|
static_assert(sizeof(ispc::FVector3f) == sizeof(FVector3f), "sizeof(ispc::FVector3f) != sizeof(FVector3f)");
|
|
#endif
|
|
|
|
#include "ComponentReregisterContext.h"
|
|
#include "ComponentRecreateRenderStateContext.h"
|
|
|
|
#if RHI_RAYTRACING
|
|
#include "RayTracingInstance.h"
|
|
#endif
|
|
|
|
static int32 GParallelGeometryCollectionBatchSize = 1024;
|
|
static TAutoConsoleVariable<int32> CVarParallelGeometryCollectionBatchSize(
|
|
TEXT("r.ParallelGeometryCollectionBatchSize"),
|
|
GParallelGeometryCollectionBatchSize,
|
|
TEXT("The number of vertices per thread dispatch in a single collection. \n"),
|
|
ECVF_Default
|
|
);
|
|
|
|
static int32 GGeometryCollectionTripleBufferUploads = 1;
|
|
FAutoConsoleVariableRef CVarGeometryCollectionTripleBufferUploads(
|
|
TEXT("r.GeometryCollectionTripleBufferUploads"),
|
|
GGeometryCollectionTripleBufferUploads,
|
|
TEXT("Whether to triple buffer geometry collection uploads, which allows Lock_NoOverwrite uploads which are much faster on the GPU with large amounts of data."),
|
|
ECVF_Default
|
|
);
|
|
|
|
static int32 GGeometryCollectionOptimizedTransforms = 1;
|
|
FAutoConsoleVariableRef CVarGeometryCollectionOptimizedTransforms(
|
|
TEXT("r.GeometryCollectionOptimizedTransforms"),
|
|
GGeometryCollectionOptimizedTransforms,
|
|
TEXT("Whether to optimize transform update by skipping automatic updates in GPUScene."),
|
|
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
|
|
{
|
|
FGlobalComponentRecreateRenderStateContext Context;
|
|
}),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static int32 GRayTracingGeometryCollectionProxyMeshes = 0;
|
|
FAutoConsoleVariableRef CVarRayTracingGeometryCollectionProxyMeshes(
|
|
TEXT("r.RayTracing.Geometry.GeometryCollection"),
|
|
GRayTracingGeometryCollectionProxyMeshes,
|
|
TEXT("Include geometry collection proxy meshes in ray tracing effects (default = 0 (Geometry collection meshes disabled in ray tracing))"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static int32 GeometryCollectionBuildGeometryParallelVertexProcessingMinBatch = 5000;
|
|
FAutoConsoleVariableRef CVarGeometryCollectionBuildGeometryParallelVertexProcessingMinBatch(
|
|
TEXT("r.GeometryCollectionBuildGeometry.ParallelVertexProcessingMinBatch"),
|
|
GeometryCollectionBuildGeometryParallelVertexProcessingMinBatch,
|
|
TEXT("When building geometry collection rendering geometry, minimum number of vertices to batch for parallel processing performance tuning"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static int32 GeometryCollectionBuildGeometryParallelIndexProcessingMinBatch = 5000;
|
|
FAutoConsoleVariableRef CVarGeometryCollectionBuildGeometryParallelIndexProcessingMinBatch(
|
|
TEXT("r.GeometryCollectionBuildGeometry.ParallelIndexProcessingMinBatch"),
|
|
GeometryCollectionBuildGeometryParallelIndexProcessingMinBatch,
|
|
TEXT("When building geometry collection rendering geometry, minimum number of indices to batch for parallel processing performance tuning"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
#if !defined(CHAOS_GEOMETRY_COLLECTION_SET_DYNAMIC_DATA_ISPC_ENABLED_DEFAULT)
|
|
#define CHAOS_GEOMETRY_COLLECTION_SET_DYNAMIC_DATA_ISPC_ENABLED_DEFAULT 1
|
|
#endif
|
|
|
|
// Support run-time toggling on supported platforms in non-shipping configurations
|
|
#if !INTEL_ISPC || UE_BUILD_SHIPPING
|
|
static constexpr bool bGeometryCollection_SetDynamicData_ISPC_Enabled = INTEL_ISPC && CHAOS_GEOMETRY_COLLECTION_SET_DYNAMIC_DATA_ISPC_ENABLED_DEFAULT;
|
|
#else
|
|
static bool bGeometryCollection_SetDynamicData_ISPC_Enabled = CHAOS_GEOMETRY_COLLECTION_SET_DYNAMIC_DATA_ISPC_ENABLED_DEFAULT;
|
|
static FAutoConsoleVariableRef CVarGeometryCollectionSetDynamicDataISPCEnabled(TEXT("r.GeometryCollectionSetDynamicData.ISPC"), bGeometryCollection_SetDynamicData_ISPC_Enabled, TEXT("Whether to use ISPC optimizations to set dynamic data in geometry collections"));
|
|
#endif
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(FGeometryCollectionSceneProxyLogging, Log, All);
|
|
|
|
FGeometryCollectionDynamicDataPool GDynamicDataPool;
|
|
|
|
class FGeometryCollectionMeshCollectorResources : public FOneFrameResource
|
|
{
|
|
public:
|
|
FGeometryCollectionVertexFactory VertexFactory;
|
|
|
|
FGeometryCollectionMeshCollectorResources(ERHIFeatureLevel::Type InFeatureLevel):VertexFactory(InFeatureLevel,true)
|
|
{
|
|
}
|
|
virtual ~FGeometryCollectionMeshCollectorResources()
|
|
{
|
|
VertexFactory.ReleaseResource();
|
|
}
|
|
|
|
virtual FGeometryCollectionVertexFactory& GetVertexFactory() { return VertexFactory; }
|
|
};
|
|
|
|
FGeometryCollectionSceneProxy::FGeometryCollectionSceneProxy(UGeometryCollectionComponent* Component)
|
|
: FPrimitiveSceneProxy(Component)
|
|
, MaterialRelevance(Component->GetMaterialRelevance(GetScene().GetFeatureLevel()))
|
|
, NumVertices(0)
|
|
, NumIndices(0)
|
|
, VertexFactory(GetScene().GetFeatureLevel())
|
|
, bSupportsManualVertexFetch(VertexFactory.SupportsManualVertexFetch(GetScene().GetFeatureLevel()))
|
|
, bSupportsTripleBufferVertexUpload(GRHISupportsMapWriteNoOverwrite)
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
, SubSections()
|
|
, SubSectionHitProxies()
|
|
, SubSectionHitProxyIndexMap()
|
|
, bUsesSubSections(false)
|
|
#endif
|
|
, DynamicData(nullptr)
|
|
, ConstantData(nullptr)
|
|
, bShowBoneColors(Component->GetShowBoneColors())
|
|
, bEnableBoneSelection(Component->GetEnableBoneSelection())
|
|
, bSuppressSelectionMaterial(Component->GetSuppressSelectionMaterial())
|
|
, BoneSelectionMaterialID(Component->GetBoneSelectedMaterialID())
|
|
, bUseFullPrecisionUVs(Component->GetRestCollection()->bUseFullPrecisionUVs)
|
|
, TransformVertexBuffersContainsOriginalMesh(false)
|
|
{
|
|
Materials.Empty();
|
|
const int32 NumMaterials = Component->GetNumMaterials();
|
|
for (int MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex)
|
|
{
|
|
Materials.Push(Component->GetMaterial(MaterialIndex));
|
|
|
|
if (Materials[MaterialIndex] == nullptr || !Materials[MaterialIndex]->CheckMaterialUsage_Concurrent(MATUSAGE_GeometryCollections))
|
|
{
|
|
Materials[MaterialIndex] = UMaterial::GetDefaultMaterial(MD_Surface);
|
|
}
|
|
}
|
|
|
|
// Make sure the vertex color material has the usage flag for rendering geometry collections
|
|
if (GEngine->VertexColorMaterial)
|
|
{
|
|
GEngine->VertexColorMaterial->CheckMaterialUsage_Concurrent(MATUSAGE_GeometryCollections);
|
|
}
|
|
|
|
// #todo(dmp): We create the sections before we set the constant data because we need to make sure these
|
|
// are set before the hit proxies are created via CreateHitProxies. Ideally, all data is passed in
|
|
// here when we create proxies, and they are thrown away if underlying geometry changes.
|
|
TManagedArray<FGeometryCollectionSection>& InputSections = Component->GetRestCollection()->GetGeometryCollection()->Sections;
|
|
|
|
const int32 NumSections = InputSections.Num();
|
|
Sections.Reset(NumSections);
|
|
|
|
for (int SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex)
|
|
{
|
|
FGeometryCollectionSection& Section = InputSections[SectionIndex];
|
|
|
|
if (Section.NumTriangles > 0)
|
|
{
|
|
Sections.Add(Section);
|
|
}
|
|
}
|
|
|
|
EnableGPUSceneSupportFlags();
|
|
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
// Init HitProxy array with the maximum number of subsections
|
|
SubSectionHitProxies.SetNumZeroed(Sections.Num() * Component->GetTransformArray().Num());
|
|
#endif
|
|
|
|
// #todo(dmp): This flag means that when motion blur is turned on, it will always render geometry collections into the
|
|
// velocity buffer. Note that the way around this is to loop through the global matrices and test whether they have
|
|
// changed from the prev to curr frame, but this is expensive. We should revisit this if the draw calls for velocity
|
|
// rendering become a problem. One solution could be to use internal solver sleeping state to drive motion blur.
|
|
bAlwaysHasVelocity = true;
|
|
|
|
// Build pre-skinned bounds from the rest collection, never needs to change as this is the bounds before
|
|
// any movement, or skinning ever happens to the component so it is logically immutable.
|
|
const TSharedPtr<FGeometryCollection, ESPMode::ThreadSafe>
|
|
Collection = Component->RestCollection->GetGeometryCollection();
|
|
const TManagedArray<FBox>& BoundingBoxes = Collection->BoundingBox;
|
|
const TManagedArray<FTransform>& Transform = Collection->Transform;
|
|
const TManagedArray<int32>& Parent = Collection->Parent;
|
|
const TManagedArray<int32>& TransformIndex = Collection->TransformIndex;
|
|
|
|
const int32 NumBoxes = BoundingBoxes.Num();
|
|
PreSkinnedBounds = Component->Bounds;
|
|
|
|
if(NumBoxes > 0)
|
|
{
|
|
TArray<FMatrix> TmpGlobalMatrices;
|
|
GeometryCollectionAlgo::GlobalMatrices(Transform, Parent, TmpGlobalMatrices);
|
|
|
|
FBox PreSkinnedBoundsTemp(ForceInit);
|
|
bool bBoundsInit = false;
|
|
for(int32 BoxIdx = 0; BoxIdx < NumBoxes; ++BoxIdx)
|
|
{
|
|
const int32 TIndex = TransformIndex[BoxIdx];
|
|
if(Collection->IsGeometry(TIndex))
|
|
{
|
|
if(!bBoundsInit)
|
|
{
|
|
PreSkinnedBoundsTemp = BoundingBoxes[BoxIdx].TransformBy(TmpGlobalMatrices[TIndex]);
|
|
bBoundsInit = true;
|
|
}
|
|
else
|
|
{
|
|
PreSkinnedBoundsTemp += BoundingBoxes[BoxIdx].TransformBy(TmpGlobalMatrices[TIndex]);
|
|
}
|
|
}
|
|
}
|
|
|
|
PreSkinnedBounds = FBoxSphereBounds(PreSkinnedBoundsTemp);
|
|
}
|
|
|
|
#if RHI_RAYTRACING
|
|
if (IsRayTracingAllowed())
|
|
{
|
|
|
|
ENQUEUE_RENDER_COMMAND(InitGeometryCollectionRayTracingGeometry)(
|
|
[this](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
FRayTracingGeometryInitializer Initializer;
|
|
Initializer.DebugName = FName(TEXT("GeometryCollection"));
|
|
Initializer.GeometryType = RTGT_Triangles;
|
|
Initializer.bFastBuild = true;
|
|
Initializer.bAllowUpdate = false;
|
|
Initializer.TotalPrimitiveCount = 0;
|
|
|
|
RayTracingGeometry.SetInitializer(Initializer);
|
|
RayTracingGeometry.InitResource();
|
|
});
|
|
}
|
|
#endif
|
|
}
|
|
|
|
FGeometryCollectionSceneProxy::~FGeometryCollectionSceneProxy()
|
|
{
|
|
}
|
|
|
|
void FGeometryCollectionSceneProxy::DestroyRenderThreadResources()
|
|
{
|
|
ReleaseResources();
|
|
|
|
if (DynamicData != nullptr)
|
|
{
|
|
GDynamicDataPool.Release(DynamicData);
|
|
DynamicData = nullptr;
|
|
}
|
|
|
|
if (ConstantData != nullptr)
|
|
{
|
|
delete ConstantData;
|
|
}
|
|
|
|
#if RHI_RAYTRACING
|
|
if (IsRayTracingAllowed())
|
|
{
|
|
RayTracingGeometry.ReleaseResource();
|
|
RayTracingDynamicVertexBuffer.Release();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void UpdateLooseParameter(FGeometryCollectionVertexFactory& VertexFactory,
|
|
FRHIShaderResourceView* BoneTransformSRV,
|
|
FRHIShaderResourceView* BonePrevTransformSRV,
|
|
FRHIShaderResourceView* BoneMapSRV)
|
|
{
|
|
FGCBoneLooseParameters LooseParameters;
|
|
|
|
LooseParameters.VertexFetch_BoneTransformBuffer = BoneTransformSRV;
|
|
LooseParameters.VertexFetch_BonePrevTransformBuffer = BonePrevTransformSRV;
|
|
LooseParameters.VertexFetch_BoneMapBuffer = BoneMapSRV;
|
|
|
|
EUniformBufferUsage UniformBufferUsage = VertexFactory.EnableLooseParameter ? UniformBuffer_SingleFrame : UniformBuffer_MultiFrame;
|
|
|
|
VertexFactory.LooseParameterUniformBuffer = FGCBoneLooseParametersRef::CreateUniformBufferImmediate(LooseParameters, UniformBufferUsage);
|
|
}
|
|
|
|
void FGeometryCollectionSceneProxy::SetupVertexFactory(FGeometryCollectionVertexFactory& GeometryCollectionVertexFactory)const
|
|
{
|
|
FGeometryCollectionVertexFactory::FDataType Data;
|
|
|
|
VertexBuffers.PositionVertexBuffer.BindPositionVertexBuffer(&GeometryCollectionVertexFactory, Data);
|
|
VertexBuffers.StaticMeshVertexBuffer.BindTangentVertexBuffer(&GeometryCollectionVertexFactory, Data);
|
|
VertexBuffers.StaticMeshVertexBuffer.BindPackedTexCoordVertexBuffer(&GeometryCollectionVertexFactory, Data);
|
|
VertexBuffers.StaticMeshVertexBuffer.BindLightMapVertexBuffer(&GeometryCollectionVertexFactory, Data, 0);
|
|
VertexBuffers.ColorVertexBuffer.BindColorVertexBuffer(&GeometryCollectionVertexFactory, Data);
|
|
|
|
Data.BoneMapSRV = BoneMapBuffer.VertexBufferSRV;
|
|
Data.BoneTransformSRV = TransformBuffers[CurrentTransformBufferIndex].VertexBufferSRV;
|
|
Data.BonePrevTransformSRV = PrevTransformBuffers[CurrentTransformBufferIndex].VertexBufferSRV;
|
|
|
|
GeometryCollectionVertexFactory.SetData(Data);
|
|
|
|
if (!GeometryCollectionVertexFactory.IsInitialized())
|
|
{
|
|
GeometryCollectionVertexFactory.InitResource();
|
|
}
|
|
else
|
|
{
|
|
GeometryCollectionVertexFactory.UpdateRHI();
|
|
}
|
|
}
|
|
|
|
void FGeometryCollectionSceneProxy::InitResources()
|
|
{
|
|
check(ConstantData);
|
|
check(IsInRenderingThread());
|
|
|
|
NumVertices = ConstantData->Vertices.Num();
|
|
NumIndices = ConstantData->Indices.Num()*3;
|
|
|
|
// taken from this, and expanded here to accommodate modifications for
|
|
// GeometryCollection vertex factory data (transform and bonemap)
|
|
// VertexBuffers.InitWithDummyData(&VertexFactory, GetRequiredVertexCount());
|
|
|
|
// get vertex factory data
|
|
FGeometryCollectionVertexFactory::FDataType Data;
|
|
|
|
VertexBuffers.StaticMeshVertexBuffer.SetUseFullPrecisionUVs(bUseFullPrecisionUVs);
|
|
|
|
// Init buffers
|
|
VertexBuffers.PositionVertexBuffer.Init(NumVertices);
|
|
VertexBuffers.StaticMeshVertexBuffer.Init(NumVertices, GeometryCollectionUV::MAX_NUM_UV_CHANNELS);
|
|
VertexBuffers.ColorVertexBuffer.Init(NumVertices);
|
|
|
|
// Init resources
|
|
VertexBuffers.PositionVertexBuffer.InitResource();
|
|
VertexBuffers.StaticMeshVertexBuffer.InitResource();
|
|
VertexBuffers.ColorVertexBuffer.InitResource();
|
|
|
|
// Bind buffers
|
|
VertexBuffers.PositionVertexBuffer.BindPositionVertexBuffer(&VertexFactory, Data);
|
|
VertexBuffers.StaticMeshVertexBuffer.BindTangentVertexBuffer(&VertexFactory, Data);
|
|
VertexBuffers.StaticMeshVertexBuffer.BindPackedTexCoordVertexBuffer(&VertexFactory, Data);
|
|
VertexBuffers.StaticMeshVertexBuffer.BindLightMapVertexBuffer(&VertexFactory, Data, 0);
|
|
VertexBuffers.ColorVertexBuffer.BindColorVertexBuffer(&VertexFactory, Data);
|
|
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
// Note: Could skip this when bEnableBoneSelection is false if the hitproxy shader was made to not require per-vertex hit proxy IDs in that case
|
|
{
|
|
HitProxyIdBuffer.Init(NumVertices);
|
|
HitProxyIdBuffer.InitResource();
|
|
}
|
|
#endif
|
|
|
|
IndexBuffer.NumIndices = GetRequiredIndexCount();
|
|
IndexBuffer.InitResource();
|
|
|
|
OriginalMeshIndexBuffer.NumIndices = GetRequiredIndexCount();
|
|
OriginalMeshIndexBuffer.InitResource();
|
|
|
|
// If using manual vertex fetch, then we will setup the GPU point transform implementation
|
|
if (bSupportsManualVertexFetch)
|
|
{
|
|
BoneMapBuffer.NumVertices = NumVertices;
|
|
|
|
TransformBuffers.AddDefaulted(1);
|
|
PrevTransformBuffers.AddDefaulted(1);
|
|
|
|
TransformBuffers[0].NumTransforms = ConstantData->NumTransforms;
|
|
PrevTransformBuffers[0].NumTransforms = ConstantData->NumTransforms;
|
|
TransformBuffers[0].InitResource();
|
|
PrevTransformBuffers[0].InitResource();
|
|
|
|
BoneMapBuffer.InitResource();
|
|
|
|
Data.BoneMapSRV = BoneMapBuffer.VertexBufferSRV;
|
|
Data.BoneTransformSRV = TransformBuffers[0].VertexBufferSRV;
|
|
Data.BonePrevTransformSRV = PrevTransformBuffers[0].VertexBufferSRV;
|
|
}
|
|
else
|
|
{
|
|
// make sure these are not null to pass UB validation
|
|
Data.BoneMapSRV = GNullColorVertexBuffer.VertexBufferSRV;
|
|
Data.BoneTransformSRV = GNullColorVertexBuffer.VertexBufferSRV;
|
|
Data.BonePrevTransformSRV = GNullColorVertexBuffer.VertexBufferSRV;
|
|
}
|
|
|
|
//
|
|
// from InitOrUpdateResource(VertexFactory);
|
|
//
|
|
|
|
// also make sure to do the binding to the vertex factory
|
|
VertexFactory.SetData(Data);
|
|
|
|
if (!VertexFactory.IsInitialized())
|
|
{
|
|
VertexFactory.InitResource();
|
|
}
|
|
else
|
|
{
|
|
VertexFactory.UpdateRHI();
|
|
}
|
|
}
|
|
|
|
void FGeometryCollectionSceneProxy::ReleaseResources()
|
|
{
|
|
VertexBuffers.PositionVertexBuffer.ReleaseResource();
|
|
VertexBuffers.StaticMeshVertexBuffer.ReleaseResource();
|
|
VertexBuffers.ColorVertexBuffer.ReleaseResource();
|
|
IndexBuffer.ReleaseResource();
|
|
|
|
OriginalMeshIndexBuffer.ReleaseResource();
|
|
|
|
if (bSupportsManualVertexFetch)
|
|
{
|
|
BoneMapBuffer.ReleaseResource();
|
|
|
|
for (int32 i = 0; i < TransformBuffers.Num(); i++)
|
|
{
|
|
TransformBuffers[i].ReleaseResource();
|
|
PrevTransformBuffers[i].ReleaseResource();
|
|
}
|
|
TransformBuffers.Reset();
|
|
}
|
|
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
HitProxyIdBuffer.ReleaseResource();
|
|
#endif
|
|
|
|
VertexFactory.ReleaseResource();
|
|
}
|
|
|
|
inline static FColor GetOnlyVertexColors(const FGeometryCollectionConstantData* ConstantDataIn, int32 PointIdx, bool bShowBoneColors)
|
|
{
|
|
return ConstantDataIn->Colors[PointIdx].ToFColor(true);
|
|
};
|
|
|
|
inline static FColor GetBonesColors(const FGeometryCollectionConstantData* ConstantDataIn, int32 PointIdx, bool bShowBoneColors)
|
|
{
|
|
return bShowBoneColors
|
|
? ConstantDataIn->BoneColors[PointIdx].ToFColor(true)
|
|
: (ConstantDataIn->BoneColors[PointIdx] * ConstantDataIn->Colors[PointIdx]).ToFColor(true);
|
|
};
|
|
|
|
void FGeometryCollectionSceneProxy::BuildGeometry( const FGeometryCollectionConstantData* ConstantDataIn, TArray<FDynamicMeshVertex>& OutVertices, TArray<int32>& OutIndices, TArray<int32> &OutOriginalMeshIndices)
|
|
{
|
|
const bool bCapturedShowBoneColors = this->bShowBoneColors;
|
|
const auto SetOutVertex = [&OutVertices, &ConstantDataIn, bCapturedShowBoneColors](int32 PointIdx, FColor (*GetColorFunc)(const FGeometryCollectionConstantData*, int32, bool) )
|
|
{
|
|
OutVertices[PointIdx] =
|
|
FDynamicMeshVertex(
|
|
ConstantDataIn->Vertices[PointIdx],
|
|
ConstantDataIn->UVChannels[0][PointIdx],
|
|
GetColorFunc(ConstantDataIn, PointIdx, bCapturedShowBoneColors)
|
|
);
|
|
OutVertices[PointIdx].SetTangents(ConstantDataIn->TangentU[PointIdx], ConstantDataIn->TangentV[PointIdx], ConstantDataIn->Normals[PointIdx]);
|
|
|
|
const int32 NumUvChannels = ConstantDataIn->UVChannels.Num();
|
|
if (ConstantDataIn->UVChannels.Num() > 1)
|
|
{
|
|
for (int32 UVLayerIdx = 1; UVLayerIdx < NumUvChannels; ++UVLayerIdx)
|
|
{
|
|
const FGeometryCollectionConstantData::FUVChannel& UVChannel = ConstantDataIn->UVChannels[UVLayerIdx];
|
|
OutVertices[PointIdx].TextureCoordinate[UVLayerIdx] = UVChannel[PointIdx];
|
|
}
|
|
}
|
|
};
|
|
|
|
OutVertices.SetNumUninitialized(ConstantDataIn->Vertices.Num());
|
|
const int32 MinVertexBatchSize = GeometryCollectionBuildGeometryParallelVertexProcessingMinBatch;
|
|
if (bShowBoneColors || bEnableBoneSelection)
|
|
{
|
|
ParallelFor(TEXT("GCSceneProxy::BuildGeometry(Vertices)"), ConstantData->Vertices.Num(), MinVertexBatchSize,
|
|
[&SetOutVertex](int32 PointIdx)
|
|
{
|
|
SetOutVertex(PointIdx, GetBonesColors);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
ParallelFor(TEXT("GCSceneProxy::BuildGeometry(Vertices)"), ConstantData->Vertices.Num(), MinVertexBatchSize,
|
|
[&SetOutVertex](int32 PointIdx)
|
|
{
|
|
SetOutVertex(PointIdx, GetOnlyVertexColors);
|
|
});
|
|
}
|
|
check(ConstantDataIn->Indices.Num() * 3 == NumIndices);
|
|
|
|
const int32 MinIndexBatchSize = GeometryCollectionBuildGeometryParallelIndexProcessingMinBatch;
|
|
|
|
OutIndices.SetNumUninitialized(NumIndices);
|
|
ParallelFor(TEXT("GCSceneProxy::BuildGeometry(Indices)"), ConstantDataIn->Indices.Num(), MinIndexBatchSize,
|
|
[&OutIndices, &ConstantDataIn](int32 IndexIdx)
|
|
{
|
|
OutIndices[IndexIdx * 3 ] = ConstantDataIn->Indices[IndexIdx].X;
|
|
OutIndices[IndexIdx * 3 + 1] = ConstantDataIn->Indices[IndexIdx].Y;
|
|
OutIndices[IndexIdx * 3 + 2] = ConstantDataIn->Indices[IndexIdx].Z;
|
|
});
|
|
|
|
OutOriginalMeshIndices.SetNumUninitialized(ConstantDataIn->OriginalMeshIndices.Num() * 3);
|
|
ParallelFor(TEXT("GCSceneProxy::BuildGeometry(OriginalMeshIndices)"), ConstantDataIn->OriginalMeshIndices.Num(), MinIndexBatchSize,
|
|
[&OutOriginalMeshIndices, &ConstantDataIn](int32 IndexIdx)
|
|
{
|
|
OutOriginalMeshIndices[IndexIdx * 3] = ConstantDataIn->OriginalMeshIndices[IndexIdx].X;
|
|
OutOriginalMeshIndices[IndexIdx * 3 + 1] = ConstantDataIn->OriginalMeshIndices[IndexIdx].Y;
|
|
OutOriginalMeshIndices[IndexIdx * 3 + 2] = ConstantDataIn->OriginalMeshIndices[IndexIdx].Z;
|
|
});
|
|
}
|
|
|
|
void FGeometryCollectionSceneProxy::SetConstantData_RenderThread(FGeometryCollectionConstantData* NewConstantData, bool ForceInit)
|
|
{
|
|
check(IsInRenderingThread());
|
|
check(NewConstantData);
|
|
|
|
if (ConstantData)
|
|
{
|
|
delete ConstantData;
|
|
ConstantData = nullptr;
|
|
}
|
|
ConstantData = NewConstantData;
|
|
|
|
if (ConstantData->Vertices.Num() != VertexBuffers.PositionVertexBuffer.GetNumVertices() || ForceInit)
|
|
{
|
|
ReleaseResources();
|
|
InitResources();
|
|
}
|
|
|
|
TArray<int32> Indices;
|
|
TArray<int32> OriginalMeshIndices;
|
|
TArray<FDynamicMeshVertex> Vertices;
|
|
BuildGeometry(ConstantData, Vertices, Indices, OriginalMeshIndices);
|
|
check(Vertices.Num() == GetRequiredVertexCount());
|
|
check(Indices.Num() == GetRequiredIndexCount());
|
|
|
|
if (GetRequiredVertexCount())
|
|
{
|
|
ParallelFor(Vertices.Num(), [&](int32 i)
|
|
{
|
|
const FDynamicMeshVertex& Vertex = Vertices[i];
|
|
|
|
VertexBuffers.PositionVertexBuffer.VertexPosition(i) = Vertex.Position;
|
|
VertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(i, Vertex.TangentX.ToFVector3f(), Vertex.GetTangentY(), Vertex.TangentZ.ToFVector3f());
|
|
for (int UVChannelIndex = 0; UVChannelIndex < GeometryCollectionUV::MAX_NUM_UV_CHANNELS; UVChannelIndex++)
|
|
{
|
|
VertexBuffers.StaticMeshVertexBuffer.SetVertexUV(i, UVChannelIndex, Vertex.TextureCoordinate[UVChannelIndex]);
|
|
}
|
|
VertexBuffers.ColorVertexBuffer.VertexColor(i) = Vertex.Color;
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
if (bEnableBoneSelection && PerBoneHitProxies.Num())
|
|
{
|
|
// One proxy per bone
|
|
const int32 ProxyIndex = ConstantData->BoneMap[i];
|
|
HitProxyIdBuffer.VertexColor(i) = PerBoneHitProxies[ProxyIndex]->Id.GetColor();
|
|
}
|
|
else
|
|
{
|
|
HitProxyIdBuffer.VertexColor(i) = WholeObjectHitProxyColor;
|
|
}
|
|
#endif
|
|
});
|
|
|
|
{
|
|
auto& VertexBuffer = VertexBuffers.PositionVertexBuffer;
|
|
void* VertexBufferData = RHILockBuffer(VertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetNumVertices() * VertexBuffer.GetStride(), RLM_WriteOnly);
|
|
FMemory::Memcpy(VertexBufferData, VertexBuffer.GetVertexData(), VertexBuffer.GetNumVertices() * VertexBuffer.GetStride());
|
|
RHIUnlockBuffer(VertexBuffer.VertexBufferRHI);
|
|
}
|
|
|
|
{
|
|
auto& VertexBuffer = VertexBuffers.ColorVertexBuffer;
|
|
void* VertexBufferData = RHILockBuffer(VertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetNumVertices() * VertexBuffer.GetStride(), RLM_WriteOnly);
|
|
FMemory::Memcpy(VertexBufferData, VertexBuffer.GetVertexData(), VertexBuffer.GetNumVertices() * VertexBuffer.GetStride());
|
|
RHIUnlockBuffer(VertexBuffer.VertexBufferRHI);
|
|
}
|
|
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
// Note: Could skip this when bEnableBoneSelection is false if the hitproxy shader was made to not require per-vertex hit proxy IDs in that case
|
|
{
|
|
auto& VertexBuffer = HitProxyIdBuffer;
|
|
void* VertexBufferData = RHILockBuffer(VertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetNumVertices() * VertexBuffer.GetStride(), RLM_WriteOnly);
|
|
FMemory::Memcpy(VertexBufferData, VertexBuffer.GetVertexData(), VertexBuffer.GetNumVertices() * VertexBuffer.GetStride());
|
|
RHIUnlockBuffer(VertexBuffer.VertexBufferRHI);
|
|
}
|
|
#endif
|
|
|
|
{
|
|
auto& VertexBuffer = VertexBuffers.StaticMeshVertexBuffer;
|
|
void* VertexBufferData = RHILockBuffer(VertexBuffer.TangentsVertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetTangentSize(), RLM_WriteOnly);
|
|
FMemory::Memcpy(VertexBufferData, VertexBuffer.GetTangentData(), VertexBuffer.GetTangentSize());
|
|
RHIUnlockBuffer(VertexBuffer.TangentsVertexBuffer.VertexBufferRHI);
|
|
}
|
|
|
|
{
|
|
auto& VertexBuffer = VertexBuffers.StaticMeshVertexBuffer;
|
|
void* VertexBufferData = RHILockBuffer(VertexBuffer.TexCoordVertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetTexCoordSize(), RLM_WriteOnly);
|
|
FMemory::Memcpy(VertexBufferData, VertexBuffer.GetTexCoordData(), VertexBuffer.GetTexCoordSize());
|
|
RHIUnlockBuffer(VertexBuffer.TexCoordVertexBuffer.VertexBufferRHI);
|
|
}
|
|
|
|
{
|
|
void* IndexBufferData = RHILockBuffer(IndexBuffer.IndexBufferRHI, 0, Indices.Num() * sizeof(int32), RLM_WriteOnly);
|
|
FMemory::Memcpy(IndexBufferData, &Indices[0], Indices.Num() * sizeof(int32));
|
|
RHIUnlockBuffer(IndexBuffer.IndexBufferRHI);
|
|
}
|
|
|
|
{
|
|
void* OriginalMeshIndexBufferData = RHILockBuffer(OriginalMeshIndexBuffer.IndexBufferRHI, 0, OriginalMeshIndices.Num() * sizeof(int32), RLM_WriteOnly);
|
|
FMemory::Memcpy(OriginalMeshIndexBufferData, &OriginalMeshIndices[0], OriginalMeshIndices.Num() * sizeof(int32));
|
|
RHIUnlockBuffer(OriginalMeshIndexBuffer.IndexBufferRHI);
|
|
}
|
|
|
|
// If we are using the GeometryCollection vertex factory, populate the vertex buffer for bone map
|
|
if (bSupportsManualVertexFetch)
|
|
{
|
|
void* BoneMapBufferData = RHILockBuffer(BoneMapBuffer.VertexBufferRHI, 0, Vertices.Num() * sizeof(int32), RLM_WriteOnly);
|
|
FMemory::Memcpy(BoneMapBufferData, &ConstantData->BoneMap[0], ConstantData->BoneMap.Num() * sizeof(int32));
|
|
RHIUnlockBuffer(BoneMapBuffer.VertexBufferRHI);
|
|
|
|
// In order to use loose parameter to support raytracing, we need to initialize the transform/pretransform buffer
|
|
// before it's set up in the dynamic path. Otherwise, the transformation matrix will be all zero instead of identity.
|
|
// Then, nothing will be drawn
|
|
const bool bLocalGeometryCollectionTripleBufferUploads = (GGeometryCollectionTripleBufferUploads != 0) && bSupportsTripleBufferVertexUpload;
|
|
const EResourceLockMode LockMode = bLocalGeometryCollectionTripleBufferUploads ? RLM_WriteOnly_NoOverwrite : RLM_WriteOnly;
|
|
|
|
FGeometryCollectionTransformBuffer& TransformBuffer = GetCurrentTransformBuffer();
|
|
FGeometryCollectionTransformBuffer& PrevTransformBuffer = GetCurrentPrevTransformBuffer();
|
|
|
|
|
|
// if we are rendering the base mesh geometry, then use rest transforms rather than the simulated one for both current and previous transforms
|
|
TransformBuffer.UpdateDynamicData(ConstantData->RestTransforms, LockMode);
|
|
PrevTransformBuffer.UpdateDynamicData(ConstantData->RestTransforms, LockMode);
|
|
|
|
}
|
|
|
|
// Update mesh sections
|
|
Sections.Reset(ConstantData->Sections.Num());
|
|
// #todo(dmp): We should restructure the component/SceneProxy usage to avoid this messy stuff. We need to know the sections
|
|
// when we create the sceneproxy for the hit proxy to work, but then we are updating the sections here with potentially differing
|
|
// vertex counts due to hiding geometry. Ideally, the SceneProxy is treated as const and recreated whenever the geometry
|
|
// changes rather than this. SetConstantData_RenderThread should be done in the constructor for the sceneproxy, most likely
|
|
for (FGeometryCollectionSection Section : ConstantData->Sections)
|
|
{
|
|
if (Section.NumTriangles > 0)
|
|
{
|
|
FGeometryCollectionSection& NewSection = Sections.AddDefaulted_GetRef();
|
|
NewSection.MaterialID = Section.MaterialID;
|
|
NewSection.FirstIndex = Section.FirstIndex;
|
|
NewSection.NumTriangles = Section.NumTriangles;
|
|
NewSection.MinVertexIndex = Section.MinVertexIndex;
|
|
NewSection.MaxVertexIndex = Section.MaxVertexIndex;
|
|
}
|
|
}
|
|
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
// Recreate or release subsections as needed
|
|
if (bUsesSubSections)
|
|
{
|
|
InitializeSubSections_RenderThread();
|
|
}
|
|
else
|
|
{
|
|
ReleaseSubSections_RenderThread();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ReleaseSubSections_RenderThread();
|
|
#endif
|
|
}
|
|
|
|
#if RHI_RAYTRACING
|
|
if (IsRayTracingEnabled())
|
|
{
|
|
bGeometryResourceUpdated = true;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
void FGeometryCollectionSceneProxy::SetDynamicData_RenderThread(FGeometryCollectionDynamicData* NewDynamicData)
|
|
{
|
|
check(IsInRenderingThread());
|
|
if (GetRequiredVertexCount())
|
|
{
|
|
if (DynamicData)
|
|
{
|
|
GDynamicDataPool.Release(DynamicData);
|
|
DynamicData = nullptr;
|
|
}
|
|
DynamicData = NewDynamicData;
|
|
|
|
check(VertexBuffers.PositionVertexBuffer.GetNumVertices() == (uint32)ConstantData->Vertices.Num());
|
|
|
|
if (bSupportsManualVertexFetch)
|
|
{
|
|
const bool bLocalGeometryCollectionTripleBufferUploads = (GGeometryCollectionTripleBufferUploads != 0) && bSupportsTripleBufferVertexUpload;
|
|
|
|
if (bLocalGeometryCollectionTripleBufferUploads && TransformBuffers.Num() == 1)
|
|
{
|
|
TransformBuffers.AddDefaulted(2);
|
|
PrevTransformBuffers.AddDefaulted(2);
|
|
|
|
for (int32 i = 1; i < 3; i++)
|
|
{
|
|
TransformBuffers[i].NumTransforms = ConstantData->NumTransforms;
|
|
PrevTransformBuffers[i].NumTransforms = ConstantData->NumTransforms;
|
|
TransformBuffers[i].InitResource();
|
|
PrevTransformBuffers[i].InitResource();
|
|
}
|
|
}
|
|
|
|
// Copy the transform data over to the vertex buffer
|
|
{
|
|
const EResourceLockMode LockMode = bLocalGeometryCollectionTripleBufferUploads ? RLM_WriteOnly_NoOverwrite : RLM_WriteOnly;
|
|
|
|
CycleTransformBuffers(bLocalGeometryCollectionTripleBufferUploads);
|
|
|
|
FGeometryCollectionTransformBuffer& TransformBuffer = GetCurrentTransformBuffer();
|
|
FGeometryCollectionTransformBuffer& PrevTransformBuffer = GetCurrentPrevTransformBuffer();
|
|
|
|
VertexFactory.SetBoneTransformSRV(TransformBuffer.VertexBufferSRV);
|
|
VertexFactory.SetBonePrevTransformSRV(PrevTransformBuffer.VertexBufferSRV);
|
|
|
|
if (DynamicData->IsDynamic)
|
|
{
|
|
TransformBuffer.UpdateDynamicData(DynamicData->Transforms, LockMode);
|
|
PrevTransformBuffer.UpdateDynamicData(DynamicData->PrevTransforms, LockMode);
|
|
|
|
TransformVertexBuffersContainsOriginalMesh = false;
|
|
}
|
|
else if (!TransformVertexBuffersContainsOriginalMesh)
|
|
{
|
|
// if we are rendering the base mesh geometry, then use rest transforms rather than the simulated one for both current and previous transforms
|
|
TransformBuffer.UpdateDynamicData(ConstantData->RestTransforms, LockMode);
|
|
PrevTransformBuffer.UpdateDynamicData(ConstantData->RestTransforms, LockMode);
|
|
|
|
TransformVertexBuffersContainsOriginalMesh = true;
|
|
}
|
|
|
|
UpdateLooseParameter(VertexFactory, TransformBuffer.VertexBufferSRV, PrevTransformBuffer.VertexBufferSRV, BoneMapBuffer.VertexBufferSRV);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto& VertexBuffer = VertexBuffers.PositionVertexBuffer;
|
|
void* VertexBufferData = RHILockBuffer(VertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetNumVertices() * VertexBuffer.GetStride(), RLM_WriteOnly);
|
|
|
|
bool bParallelGeometryCollection = true;
|
|
int32 TotalVertices = ConstantData->Vertices.Num();
|
|
int32 ParallelGeometryCollectionBatchSize = CVarParallelGeometryCollectionBatchSize.GetValueOnRenderThread();
|
|
|
|
int32 NumBatches = (TotalVertices / ParallelGeometryCollectionBatchSize);
|
|
|
|
if (TotalVertices != ParallelGeometryCollectionBatchSize)
|
|
{
|
|
NumBatches++;
|
|
}
|
|
|
|
// Batch too small, don't bother with parallel
|
|
if (ParallelGeometryCollectionBatchSize > TotalVertices)
|
|
{
|
|
bParallelGeometryCollection = false;
|
|
ParallelGeometryCollectionBatchSize = TotalVertices;
|
|
}
|
|
|
|
auto GeometryCollectionBatch([&](int32 BatchNum)
|
|
{
|
|
int32 IndexOffset = ParallelGeometryCollectionBatchSize * BatchNum;
|
|
int32 ThisBatchSize = ParallelGeometryCollectionBatchSize;
|
|
|
|
// Check for final batch
|
|
if (IndexOffset + ParallelGeometryCollectionBatchSize > NumVertices)
|
|
{
|
|
ThisBatchSize = TotalVertices - IndexOffset;
|
|
}
|
|
|
|
if (ThisBatchSize > 0)
|
|
{
|
|
const FMatrix44f* RESTRICT BoneTransformsPtr = DynamicData->IsDynamic ? DynamicData->Transforms.GetData() : ConstantData->RestTransforms.GetData();
|
|
|
|
if (bGeometryCollection_SetDynamicData_ISPC_Enabled)
|
|
{
|
|
#if INTEL_ISPC
|
|
uint8* VertexBufferOffset = (uint8*)VertexBufferData + (IndexOffset * VertexBuffer.GetStride());
|
|
ispc::SetDynamicData_RenderThread(
|
|
(ispc::FVector3f*)VertexBufferOffset,
|
|
ThisBatchSize,
|
|
VertexBuffer.GetStride(),
|
|
&ConstantData->BoneMap[IndexOffset],
|
|
(ispc::FMatrix44f*)BoneTransformsPtr,
|
|
(ispc::FVector3f*)&ConstantData->Vertices[IndexOffset]);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
for (int32 i = IndexOffset; i < IndexOffset + ThisBatchSize; i++)
|
|
{
|
|
FVector3f Transformed = BoneTransformsPtr[ConstantData->BoneMap[i]].TransformPosition(ConstantData->Vertices[i]);
|
|
FMemory::Memcpy((uint8*)VertexBufferData + (i * VertexBuffer.GetStride()), &Transformed, sizeof(FVector3f));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
ParallelFor(NumBatches, GeometryCollectionBatch, !bParallelGeometryCollection);
|
|
|
|
RHIUnlockBuffer(VertexBuffer.VertexBufferRHI);
|
|
}
|
|
|
|
#if RHI_RAYTRACING
|
|
if (IsRayTracingEnabled())
|
|
{
|
|
bGeometryResourceUpdated = true;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
FMaterialRenderProxy* FGeometryCollectionSceneProxy::GetMaterial(FMeshElementCollector& Collector, int32 MaterialIndex) const
|
|
{
|
|
// material for wireframe
|
|
/*
|
|
never used
|
|
auto WireframeMaterialInstance = new FColoredMaterialRenderProxy(
|
|
GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy(IsSelected()) : nullptr,
|
|
FLinearColor(0, 0.5f, 1.f)
|
|
);
|
|
Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance);
|
|
*/
|
|
|
|
// material for colored bones
|
|
|
|
FMaterialRenderProxy* MaterialProxy = nullptr;
|
|
|
|
if (bShowBoneColors && GEngine->VertexColorMaterial)
|
|
{
|
|
UMaterial* VertexColorVisualizationMaterial = GEngine->VertexColorMaterial;
|
|
auto VertexColorVisualizationMaterialInstance = new FColoredMaterialRenderProxy(
|
|
VertexColorVisualizationMaterial->GetRenderProxy(),
|
|
GetSelectionColor(FLinearColor::White, false, false)
|
|
);
|
|
Collector.RegisterOneFrameMaterialProxy(VertexColorVisualizationMaterialInstance);
|
|
MaterialProxy = VertexColorVisualizationMaterialInstance;
|
|
}
|
|
else if(Materials.IsValidIndex(MaterialIndex))
|
|
{
|
|
MaterialProxy = Materials[MaterialIndex]->GetRenderProxy();
|
|
}
|
|
|
|
if (MaterialProxy == nullptr)
|
|
{
|
|
MaterialProxy = UMaterial::GetDefaultMaterial(MD_Surface)->GetRenderProxy();
|
|
}
|
|
|
|
return MaterialProxy;
|
|
}
|
|
|
|
void FGeometryCollectionSceneProxy::GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_GeometryCollectionSceneProxy_GetDynamicMeshElements);
|
|
if (GetRequiredVertexCount())
|
|
{
|
|
const bool bWireframe = AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe;
|
|
const bool bProxyIsSelected = IsSelected();
|
|
|
|
const FEngineShowFlags& EngineShowFlags = ViewFamily.EngineShowFlags;
|
|
|
|
auto SetDebugMaterial = [this, &Collector, &EngineShowFlags, bProxyIsSelected](FMeshBatch& Mesh) -> void
|
|
{
|
|
#if UE_ENABLE_DEBUG_DRAWING
|
|
|
|
// flag to indicate whether we've set a debug material yet
|
|
// Note: Will be used if we add more debug material options
|
|
// (compare to variable of same name in StaticMeshRender.cpp)
|
|
bool bDebugMaterialRenderProxySet = false;
|
|
|
|
if (!bDebugMaterialRenderProxySet && bProxyIsSelected && EngineShowFlags.VertexColors && AllowDebugViewmodes())
|
|
{
|
|
// Override the mesh's material with our material that draws the vertex colors
|
|
UMaterial* VertexColorVisualizationMaterial = NULL;
|
|
switch (GVertexColorViewMode)
|
|
{
|
|
case EVertexColorViewMode::Color:
|
|
VertexColorVisualizationMaterial = GEngine->VertexColorViewModeMaterial_ColorOnly;
|
|
break;
|
|
|
|
case EVertexColorViewMode::Alpha:
|
|
VertexColorVisualizationMaterial = GEngine->VertexColorViewModeMaterial_AlphaAsColor;
|
|
break;
|
|
|
|
case EVertexColorViewMode::Red:
|
|
VertexColorVisualizationMaterial = GEngine->VertexColorViewModeMaterial_RedOnly;
|
|
break;
|
|
|
|
case EVertexColorViewMode::Green:
|
|
VertexColorVisualizationMaterial = GEngine->VertexColorViewModeMaterial_GreenOnly;
|
|
break;
|
|
|
|
case EVertexColorViewMode::Blue:
|
|
VertexColorVisualizationMaterial = GEngine->VertexColorViewModeMaterial_BlueOnly;
|
|
break;
|
|
}
|
|
check(VertexColorVisualizationMaterial != NULL);
|
|
|
|
// Note: static mesh renderer does something more complicated involving per-section selection,
|
|
// but whole component selection seems ok for now
|
|
bool bSectionIsSelected = bProxyIsSelected;
|
|
|
|
auto VertexColorVisualizationMaterialInstance = new FColoredMaterialRenderProxy(
|
|
VertexColorVisualizationMaterial->GetRenderProxy(),
|
|
GetSelectionColor(FLinearColor::White, bSectionIsSelected, IsHovered())
|
|
);
|
|
|
|
Collector.RegisterOneFrameMaterialProxy(VertexColorVisualizationMaterialInstance);
|
|
Mesh.MaterialRenderProxy = VertexColorVisualizationMaterialInstance;
|
|
|
|
bDebugMaterialRenderProxySet = true;
|
|
}
|
|
#endif
|
|
};
|
|
|
|
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
|
|
{
|
|
if ((VisibilityMap & (1 << ViewIndex)) == 0) { continue; }
|
|
|
|
// Render Batches
|
|
|
|
// render original mesh if it isn't dynamic and there is an unfractured mesh
|
|
// #todo(dmp): refactor this to share more code later
|
|
const bool bIsDynamic = DynamicData && DynamicData->IsDynamic;
|
|
if (!bIsDynamic)
|
|
{
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
const TArray<FGeometryCollectionSection>& SectionArray = bUsesSubSections && SubSections.Num() ? SubSections: ConstantData->OriginalMeshSections;
|
|
UE_LOG(FGeometryCollectionSceneProxyLogging, VeryVerbose, TEXT("GetDynamicMeshElements, bUseSubSections=%d, NumSections=%d for %p."), bUsesSubSections, SectionArray.Num(), this);
|
|
#else
|
|
const TArray<FGeometryCollectionSection>& SectionArray = ConstantData->OriginalMeshSections;
|
|
#endif
|
|
|
|
// Grab the material proxies we'll be using for each section
|
|
TArray<FMaterialRenderProxy*, TInlineAllocator<32>> MaterialProxies;
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < SectionArray.Num(); SectionIndex++)
|
|
{
|
|
const FGeometryCollectionSection& Section = SectionArray[SectionIndex];
|
|
FMaterialRenderProxy* MaterialProxy = GetMaterial(Collector, Section.MaterialID);
|
|
MaterialProxies.Add(MaterialProxy);
|
|
}
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < SectionArray.Num(); SectionIndex++)
|
|
{
|
|
const FGeometryCollectionSection& Section = SectionArray[SectionIndex];
|
|
|
|
// Draw the mesh.
|
|
FMeshBatch& Mesh = Collector.AllocateMesh();
|
|
FMeshBatchElement& BatchElement = Mesh.Elements[0];
|
|
BatchElement.IndexBuffer = &OriginalMeshIndexBuffer;
|
|
Mesh.bWireframe = bWireframe;
|
|
Mesh.VertexFactory = &VertexFactory;
|
|
Mesh.MaterialRenderProxy = MaterialProxies[SectionIndex];
|
|
|
|
/*
|
|
bool bHasPrecomputedVolumetricLightmap;
|
|
FMatrix PreviousLocalToWorld;
|
|
int32 SingleCaptureIndex;
|
|
bool bOutputVelocity;
|
|
GetScene().GetPrimitiveUniformShaderParameters_RenderThread(GetPrimitiveSceneInfo(), bHasPrecomputedVolumetricLightmap, PreviousLocalToWorld, SingleCaptureIndex, bOutputVelocity);
|
|
bOutputVelocity |= AlwaysHasVelocity();
|
|
|
|
FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer = Collector.AllocateOneFrameResource<FDynamicPrimitiveUniformBuffer>();
|
|
DynamicPrimitiveUniformBuffer.Set(GetLocalToWorld(), PreviousLocalToWorld, GetBounds(), GetLocalBounds(), true, bHasPrecomputedVolumetricLightmap, bOutputVelocity);
|
|
BatchElement.PrimitiveUniformBufferResource = &DynamicPrimitiveUniformBuffer.UniformBuffer;
|
|
*/
|
|
|
|
BatchElement.PrimitiveUniformBuffer = GetUniformBuffer();
|
|
BatchElement.FirstIndex = Section.FirstIndex;
|
|
BatchElement.NumPrimitives = Section.NumTriangles;
|
|
BatchElement.MinVertexIndex = Section.MinVertexIndex;
|
|
BatchElement.MaxVertexIndex = Section.MaxVertexIndex;
|
|
Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative();
|
|
Mesh.Type = PT_TriangleList;
|
|
Mesh.DepthPriorityGroup = SDPG_World;
|
|
Mesh.bCanApplyViewModeOverrides = true;
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
Mesh.BatchHitProxyId = Section.HitProxy ? Section.HitProxy->Id : FHitProxyId();
|
|
}
|
|
#endif
|
|
|
|
SetDebugMaterial(Mesh);
|
|
|
|
Collector.AddMesh(ViewIndex, Mesh);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
const TArray<FGeometryCollectionSection>& SectionArray = bUsesSubSections && SubSections.Num() ? SubSections: Sections;
|
|
UE_LOG(FGeometryCollectionSceneProxyLogging, VeryVerbose, TEXT("GetDynamicMeshElements, bUseSubSections=%d, NumSections=%d for %p."), bUsesSubSections, SectionArray.Num(), this);
|
|
#else
|
|
const TArray<FGeometryCollectionSection>& SectionArray = Sections;
|
|
#endif
|
|
|
|
// Grab the material proxies we'll be using for each section
|
|
TArray<FMaterialRenderProxy*, TInlineAllocator<32>> MaterialProxies;
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < SectionArray.Num(); ++SectionIndex)
|
|
{
|
|
const FGeometryCollectionSection& Section = SectionArray[SectionIndex];
|
|
|
|
FMaterialRenderProxy* MaterialProxy = GetMaterial(Collector, Section.MaterialID);
|
|
MaterialProxies.Add(MaterialProxy);
|
|
}
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < SectionArray.Num(); ++SectionIndex)
|
|
{
|
|
const FGeometryCollectionSection& Section = SectionArray[SectionIndex];
|
|
|
|
// Draw the mesh.
|
|
FMeshBatch& Mesh = Collector.AllocateMesh();
|
|
FMeshBatchElement& BatchElement = Mesh.Elements[0];
|
|
BatchElement.IndexBuffer = &IndexBuffer;
|
|
Mesh.bWireframe = bWireframe;
|
|
Mesh.VertexFactory = &VertexFactory;
|
|
Mesh.MaterialRenderProxy = MaterialProxies[SectionIndex];
|
|
BatchElement.PrimitiveUniformBuffer = GetUniformBuffer();
|
|
BatchElement.FirstIndex = Section.FirstIndex;
|
|
BatchElement.NumPrimitives = Section.NumTriangles;
|
|
BatchElement.MinVertexIndex = Section.MinVertexIndex;
|
|
BatchElement.MaxVertexIndex = Section.MaxVertexIndex;
|
|
Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative();
|
|
Mesh.Type = PT_TriangleList;
|
|
Mesh.DepthPriorityGroup = SDPG_World;
|
|
Mesh.bCanApplyViewModeOverrides = true;
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
Mesh.BatchHitProxyId = Section.HitProxy ? Section.HitProxy->Id : FHitProxyId();
|
|
}
|
|
#endif
|
|
|
|
SetDebugMaterial(Mesh);
|
|
|
|
Collector.AddMesh(ViewIndex, Mesh);
|
|
}
|
|
}
|
|
|
|
// Highlight selected bone using specialized material - when rendering bones as colors we don't need to run this code as the
|
|
// bone selection is already contained in the rendered colors
|
|
// #note: This renders the geometry again but with the bone selection material. Ideally we'd have one render pass and one
|
|
// material.
|
|
if ((bShowBoneColors || bEnableBoneSelection) && !bSuppressSelectionMaterial && Materials.IsValidIndex(BoneSelectionMaterialID))
|
|
{
|
|
FMaterialRenderProxy* MaterialRenderProxy = Materials[BoneSelectionMaterialID]->GetRenderProxy();
|
|
|
|
FMeshBatch& Mesh = Collector.AllocateMesh();
|
|
FMeshBatchElement& BatchElement = Mesh.Elements[0];
|
|
BatchElement.IndexBuffer = &IndexBuffer;
|
|
Mesh.bWireframe = bWireframe;
|
|
Mesh.VertexFactory = &VertexFactory;
|
|
Mesh.MaterialRenderProxy = MaterialRenderProxy;
|
|
BatchElement.PrimitiveUniformBuffer = GetUniformBuffer();
|
|
BatchElement.FirstIndex = 0;
|
|
BatchElement.NumPrimitives = GetRequiredIndexCount() / 3;
|
|
BatchElement.MinVertexIndex = 0;
|
|
BatchElement.MaxVertexIndex = GetRequiredVertexCount();
|
|
Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative();
|
|
Mesh.Type = PT_TriangleList;
|
|
Mesh.DepthPriorityGroup = SDPG_World;
|
|
Mesh.bCanApplyViewModeOverrides = false;
|
|
Collector.AddMesh(ViewIndex, Mesh);
|
|
}
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
RenderBounds(Collector.GetPDI(ViewIndex), ViewFamily.EngineShowFlags, GetBounds(), IsSelected());
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
#if RHI_RAYTRACING
|
|
void FGeometryCollectionSceneProxy::GetDynamicRayTracingInstances(FRayTracingMaterialGatheringContext& Context, TArray<struct FRayTracingInstance>& OutRayTracingInstances)
|
|
{
|
|
if (GRayTracingGeometryCollectionProxyMeshes == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_GeometryCollectionSceneProxy_GetDynamicRayTracingInstances);
|
|
|
|
if (GetRequiredVertexCount())
|
|
{
|
|
const uint32 LODIndex = 0;
|
|
const bool bWireframe = false; //AllowDebugViewmodes();//&& ViewFamily.EngineShowFlags.Wireframe;
|
|
|
|
// render original mesh if it isn't dynamic and there is an unfractured mesh
|
|
// #todo(dmp): refactor this to share more code later
|
|
const bool bIsDynamic = DynamicData && DynamicData->IsDynamic;
|
|
|
|
//Loose parameter needs to be updated every frame
|
|
FGeometryCollectionMeshCollectorResources* CollectorResources;
|
|
CollectorResources = &Context.RayTracingMeshResourceCollector.
|
|
AllocateOneFrameResource<FGeometryCollectionMeshCollectorResources>(GetScene().GetFeatureLevel());
|
|
FGeometryCollectionVertexFactory& GeometryCollectionVertexFactory = CollectorResources->GetVertexFactory();
|
|
|
|
// Render dynamic objects
|
|
if (!GeometryCollectionVertexFactory.GetType()->SupportsRayTracingDynamicGeometry())
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetupVertexFactory(GeometryCollectionVertexFactory);
|
|
|
|
FGeometryCollectionIndexBuffer* ActiveIndexBuffer = bIsDynamic ? &IndexBuffer : &OriginalMeshIndexBuffer;
|
|
UpdatingRayTracingGeometry_RenderingThread(ActiveIndexBuffer);
|
|
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
const TArray<FGeometryCollectionSection>& SectionArray = bUsesSubSections && SubSections.Num() ? SubSections : Sections;
|
|
UE_LOG(FGeometryCollectionSceneProxyLogging, VeryVerbose, TEXT("GetDynamicMeshElements, bUseSubSections=%d, NumSections=%d for %p."), bUsesSubSections, SectionArray.Num(), this);
|
|
#else
|
|
const TArray<FGeometryCollectionSection>& SectionArray = Sections;
|
|
#endif
|
|
const int32 InstanceCount = SectionArray.Num();
|
|
|
|
if (InstanceCount > 0 && RayTracingGeometry.RayTracingGeometryRHI.IsValid())
|
|
{
|
|
|
|
FRayTracingInstance RayTracingInstance;
|
|
RayTracingInstance.Geometry = &RayTracingGeometry;
|
|
RayTracingInstance.InstanceTransforms.Emplace(GetLocalToWorld());
|
|
|
|
// Grab the material proxies we'll be using for each section
|
|
TArray<FMaterialRenderProxy*, TInlineAllocator<32>> MaterialProxies;
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < SectionArray.Num(); ++SectionIndex)
|
|
{
|
|
const FGeometryCollectionSection& Section = SectionArray[SectionIndex];
|
|
|
|
//TODO: Add BoneColor support in Path/Ray tracing?
|
|
FMaterialRenderProxy* MaterialProxy= Materials[Section.MaterialID]->GetRenderProxy();
|
|
|
|
if (MaterialProxy == nullptr)
|
|
{
|
|
MaterialProxy = UMaterial::GetDefaultMaterial(MD_Surface)->GetRenderProxy();
|
|
}
|
|
|
|
MaterialProxies.Add(MaterialProxy);
|
|
}
|
|
|
|
int32 MaxVertexIndex = 0;
|
|
for (int32 SectionIndex = 0; SectionIndex < SectionArray.Num(); ++SectionIndex)
|
|
{
|
|
const FGeometryCollectionSection& Section = SectionArray[SectionIndex];
|
|
|
|
// Draw the mesh
|
|
|
|
FMeshBatch& Mesh = RayTracingInstance.Materials.AddDefaulted_GetRef();
|
|
Mesh.bWireframe = bWireframe;//bWireframe needs viewfamily access ?
|
|
Mesh.SegmentIndex = SectionIndex;
|
|
Mesh.VertexFactory = &GeometryCollectionVertexFactory;
|
|
Mesh.MaterialRenderProxy = MaterialProxies[SectionIndex];
|
|
Mesh.LODIndex = LODIndex;
|
|
Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative();
|
|
Mesh.bDisableBackfaceCulling = true;
|
|
Mesh.Type = PT_TriangleList;
|
|
Mesh.DepthPriorityGroup = SDPG_World;
|
|
Mesh.bCanApplyViewModeOverrides = true;
|
|
|
|
FMeshBatchElement& BatchElement = Mesh.Elements[0];
|
|
BatchElement.IndexBuffer = ActiveIndexBuffer;
|
|
BatchElement.PrimitiveUniformBuffer = GetUniformBuffer();
|
|
BatchElement.FirstIndex = Section.FirstIndex;
|
|
BatchElement.NumPrimitives = Section.NumTriangles;
|
|
BatchElement.MinVertexIndex = Section.MinVertexIndex;
|
|
BatchElement.MaxVertexIndex = Section.MaxVertexIndex;
|
|
BatchElement.NumInstances = 1;
|
|
|
|
MaxVertexIndex = std::max(Section.MaxVertexIndex, MaxVertexIndex);
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
Mesh.BatchHitProxyId = Section.HitProxy ? Section.HitProxy->Id : FHitProxyId();
|
|
}
|
|
#endif
|
|
//#TODO: bone color, bone selection and render bound?
|
|
}
|
|
|
|
FRWBuffer* VertexBuffer = RayTracingDynamicVertexBuffer.NumBytes > 0 ? &RayTracingDynamicVertexBuffer : nullptr;
|
|
|
|
const uint32 VertexCount = MaxVertexIndex + 1;
|
|
Context.DynamicRayTracingGeometriesToUpdate.Add(
|
|
FRayTracingDynamicGeometryUpdateParams
|
|
{
|
|
RayTracingInstance.Materials,
|
|
false,
|
|
VertexCount,
|
|
VertexCount * (uint32)sizeof(FVector3f),
|
|
RayTracingGeometry.Initializer.TotalPrimitiveCount,
|
|
&RayTracingGeometry,
|
|
VertexBuffer,
|
|
true
|
|
}
|
|
);
|
|
|
|
OutRayTracingInstances.Emplace(RayTracingInstance);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void FGeometryCollectionSceneProxy::UpdatingRayTracingGeometry_RenderingThread(FGeometryCollectionIndexBuffer* InIndexBuffer)
|
|
{
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
const TArray<FGeometryCollectionSection>& SectionArray = bUsesSubSections && SubSections.Num() ? SubSections : Sections;
|
|
UE_LOG(FGeometryCollectionSceneProxyLogging, VeryVerbose, TEXT("GetDynamicMeshElements, bUseSubSections=%d, NumSections=%d for %p."), bUsesSubSections, SectionArray.Num(), this);
|
|
#else
|
|
const TArray<FGeometryCollectionSection>& SectionArray = Sections;
|
|
#endif
|
|
const int32 InstanceCount = SectionArray.Num();//InstanceSceneData.Num();
|
|
|
|
if (InIndexBuffer && bGeometryResourceUpdated)
|
|
{
|
|
RayTracingGeometry.Initializer.Segments.Empty();
|
|
RayTracingGeometry.Initializer.TotalPrimitiveCount = 0;
|
|
|
|
for (int SectionIndex = 0; SectionIndex < InstanceCount; ++SectionIndex)
|
|
{
|
|
const FGeometryCollectionSection& Section = SectionArray[SectionIndex];
|
|
FRayTracingGeometrySegment Segment;
|
|
Segment.FirstPrimitive = Section.FirstIndex / 3;
|
|
Segment.VertexBuffer = VertexBuffers.PositionVertexBuffer.VertexBufferRHI;
|
|
Segment.NumPrimitives = Section.NumTriangles;
|
|
Segment.MaxVertices = VertexBuffers.PositionVertexBuffer.GetNumVertices();
|
|
RayTracingGeometry.Initializer.Segments.Add(Segment);
|
|
RayTracingGeometry.Initializer.TotalPrimitiveCount += Section.NumTriangles;
|
|
}
|
|
|
|
if (RayTracingGeometry.Initializer.TotalPrimitiveCount > 0)
|
|
{
|
|
RayTracingGeometry.Initializer.IndexBuffer = InIndexBuffer->IndexBufferRHI;
|
|
// Create the ray tracing geometry but delay the acceleration structure build.
|
|
RayTracingGeometry.CreateRayTracingGeometry(ERTAccelerationStructureBuildPriority::Skip);
|
|
}
|
|
|
|
bGeometryResourceUpdated = false;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
FPrimitiveViewRelevance FGeometryCollectionSceneProxy::GetViewRelevance(const FSceneView* View) const
|
|
{
|
|
FPrimitiveViewRelevance Result;
|
|
Result.bDrawRelevance = IsShown(View);
|
|
Result.bShadowRelevance = IsShadowCast(View);
|
|
Result.bDynamicRelevance = true;
|
|
Result.bRenderInMainPass = ShouldRenderInMainPass();
|
|
Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask();
|
|
Result.bRenderCustomDepth = ShouldRenderCustomDepth();
|
|
Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow;
|
|
MaterialRelevance.SetPrimitiveViewRelevance(Result);
|
|
|
|
Result.bVelocityRelevance = DrawsVelocity() && Result.bOpaque && Result.bRenderInMainPass;
|
|
|
|
return Result;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
HHitProxy* FGeometryCollectionSceneProxy::CreateHitProxies(UPrimitiveComponent* Component, TArray<TRefCountPtr<HHitProxy> >& OutHitProxies)
|
|
{
|
|
// In order to be able to click on static meshes when they're batched up, we need to have catch all default
|
|
// hit proxy to return.
|
|
HHitProxy* DefaultHitProxy = FPrimitiveSceneProxy::CreateHitProxies(Component, OutHitProxies);
|
|
WholeObjectHitProxyColor = DefaultHitProxy->Id.GetColor();
|
|
|
|
// @todo FractureTools - Reconcile with subsection hit proxies. Subsection is a draw call per hit proxy but is not suitable per-vertex as written
|
|
if (bEnableBoneSelection)
|
|
{
|
|
UGeometryCollectionComponent* GeometryCollectionComp = CastChecked<UGeometryCollectionComponent>(Component);
|
|
int32 NumTransforms = GeometryCollectionComp->GetTransformArray().Num();
|
|
PerBoneHitProxies.Empty();
|
|
for (int32 TransformIndex = 0; TransformIndex < NumTransforms; ++TransformIndex)
|
|
{
|
|
HGeometryCollectionBone* HitProxy = new HGeometryCollectionBone(GeometryCollectionComp, TransformIndex);
|
|
PerBoneHitProxies.Add(HitProxy);
|
|
}
|
|
|
|
OutHitProxies.Append(PerBoneHitProxies);
|
|
}
|
|
else if (Component->GetOwner())
|
|
{
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
// Note the below-created subsection hitproxies are never drawn, because the per-vertex hitproxy
|
|
// rendering path is always on for geometry collections (i.e., USE_PER_VERTEX_HITPROXY_ID is 1)
|
|
const int32 NumTransforms = (Sections.Num() > 0) ? SubSectionHitProxies.Num() / Sections.Num(): 0;
|
|
for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); ++SectionIndex)
|
|
{
|
|
// Create HitProxy for regular material based sections, and update existing section
|
|
FGeometryCollectionSection& Section = Sections[SectionIndex];
|
|
|
|
const int32 MaterialID = Section.MaterialID;
|
|
HActor* const HitProxy = new HActor(Component->GetOwner(), Component, SectionIndex, MaterialID);
|
|
|
|
OutHitProxies.Add(HitProxy);
|
|
Section.HitProxy = HitProxy;
|
|
|
|
// Create HitProxy per transform index using the same material Id than the current sections
|
|
// All combinations of material id/transform index are populated,
|
|
// since it can't be assumed that any of them won't be needed.
|
|
const int32 SectionOffset = SectionIndex * NumTransforms;
|
|
|
|
for (int32 TransformIndex = 0; TransformIndex < NumTransforms; ++TransformIndex)
|
|
{
|
|
static const int32 SubSectionIndex = INDEX_NONE; // The index will get updated later for existing subsections
|
|
|
|
HGeometryCollection* const SubSectionHitProxy = new HGeometryCollection(Component->GetOwner(), Component, SubSectionIndex, MaterialID, TransformIndex);
|
|
|
|
OutHitProxies.Add(SubSectionHitProxy);
|
|
SubSectionHitProxies[SectionOffset + TransformIndex] = SubSectionHitProxy;
|
|
}
|
|
}
|
|
#else
|
|
for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); ++SectionIndex)
|
|
{
|
|
// Create HitProxy for regular material based sections, and update existing section
|
|
FGeometryCollectionSection& Section = Sections[SectionIndex];
|
|
|
|
const int32 MaterialID = Section.MaterialID;
|
|
HActor* const HitProxy = new HActor(Component->GetOwner(), Component, SectionIndex, MaterialID);
|
|
|
|
OutHitProxies.Add(HitProxy);
|
|
Section.HitProxy = HitProxy;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return DefaultHitProxy;
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
void FGeometryCollectionSceneProxy::UseSubSections(bool bInUsesSubSections, bool bForceInit)
|
|
{
|
|
if (!bForceInit)
|
|
{
|
|
bUsesSubSections = bInUsesSubSections;
|
|
}
|
|
else if (bInUsesSubSections)
|
|
{
|
|
FGeometryCollectionSceneProxy* GeometryCollectionSceneProxy = this;
|
|
ENQUEUE_RENDER_COMMAND(InitializeSubSections)(
|
|
[GeometryCollectionSceneProxy](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
if (GeometryCollectionSceneProxy)
|
|
{
|
|
GeometryCollectionSceneProxy->InitializeSubSections_RenderThread();
|
|
GeometryCollectionSceneProxy->bUsesSubSections = true;
|
|
UE_LOG(FGeometryCollectionSceneProxyLogging, Verbose, TEXT("UseSubSections, %d SubSections initialized for %p."), GeometryCollectionSceneProxy->SubSections.Num(), GeometryCollectionSceneProxy);
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
FGeometryCollectionSceneProxy* GeometryCollectionSceneProxy = this;
|
|
ENQUEUE_RENDER_COMMAND(ReleaseSubSections)(
|
|
[GeometryCollectionSceneProxy](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
if (GeometryCollectionSceneProxy)
|
|
{
|
|
GeometryCollectionSceneProxy->ReleaseSubSections_RenderThread();
|
|
GeometryCollectionSceneProxy->bUsesSubSections = false;
|
|
UE_LOG(FGeometryCollectionSceneProxyLogging, Verbose, TEXT("UseSubSections, SubSections released for %p."), GeometryCollectionSceneProxy);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void FGeometryCollectionSceneProxy::InitializeSubSections_RenderThread()
|
|
{
|
|
// Exit now if there isn't any data
|
|
if (!ConstantData)
|
|
{
|
|
SubSections.Empty();
|
|
SubSectionHitProxyIndexMap.Empty();
|
|
return;
|
|
}
|
|
|
|
// Retrieve the correct arrays depending on the dynamic state
|
|
const bool bIsDynamic = DynamicData && DynamicData->IsDynamic;
|
|
const TArray<FGeometryCollectionSection>& SectionArray = bIsDynamic ? Sections: ConstantData->OriginalMeshSections;
|
|
const TArray<FIntVector>& IndexArray = bIsDynamic ? ConstantData->Indices: ConstantData->OriginalMeshIndices;
|
|
const TArray<int32>& BoneMap = ConstantData->BoneMap;
|
|
|
|
// Reserve sub sections array with a minimum of one transform per section
|
|
SubSections.Empty(SectionArray.Num());
|
|
SubSectionHitProxyIndexMap.Empty(SectionArray.Num());
|
|
|
|
// Lambda that adds a new subsection and update the HitProxy section index
|
|
auto AddSubSection = [this, IndexArray](int32 HitProxyIndex, const FGeometryCollectionSection& Section, int32 FirstFaceIndex, int32 EndFaceIndex)
|
|
{
|
|
// Find the matching HitProxy for this transform/section
|
|
HGeometryCollection* const SubSectionHitProxy = SubSectionHitProxies[HitProxyIndex];
|
|
|
|
// Add the subsection
|
|
FGeometryCollectionSection SubSection;
|
|
SubSection.MaterialID = Section.MaterialID;
|
|
SubSection.FirstIndex = FirstFaceIndex * 3;
|
|
SubSection.NumTriangles = EndFaceIndex - FirstFaceIndex;
|
|
{
|
|
// Find out new min/max vertex indices
|
|
check(SubSection.NumTriangles > 0);
|
|
SubSection.MinVertexIndex = TNumericLimits<int32>::Max();
|
|
SubSection.MaxVertexIndex = TNumericLimits<int32>::Min();
|
|
for (int32 FaceIndex = FirstFaceIndex; FaceIndex < EndFaceIndex; ++FaceIndex)
|
|
{
|
|
SubSection.MinVertexIndex = FMath::Min(SubSection.MinVertexIndex, IndexArray[FaceIndex].GetMin());
|
|
SubSection.MaxVertexIndex = FMath::Max(SubSection.MaxVertexIndex, IndexArray[FaceIndex].GetMax());
|
|
}
|
|
check(SubSection.MinVertexIndex >= Section.MinVertexIndex && SubSection.MinVertexIndex <= Section.MaxVertexIndex)
|
|
check(SubSection.MaxVertexIndex >= Section.MinVertexIndex && SubSection.MaxVertexIndex <= Section.MaxVertexIndex)
|
|
}
|
|
SubSection.HitProxy = SubSectionHitProxy;
|
|
const int32 SubSectionIndex = SubSections.Add(SubSection);
|
|
|
|
// Keep the HitProxy index in a map in case this section's HitProxy pointer ever needs to be updated (e.g. after CreateHitProxies is called)
|
|
SubSectionHitProxyIndexMap.Add(SubSectionIndex, HitProxyIndex);
|
|
|
|
// Update HitProxy with this subsection index
|
|
if (SubSectionHitProxy)
|
|
{
|
|
SubSectionHitProxy->SectionIndex = SubSectionIndex;
|
|
}
|
|
};
|
|
|
|
// Create subsections per transform
|
|
const int32 NumTransforms = (SectionArray.Num() > 0) ? SubSectionHitProxies.Num() / SectionArray.Num(): 0;
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < SectionArray.Num(); ++SectionIndex)
|
|
{
|
|
const int32 SectionOffset = SectionIndex * NumTransforms;
|
|
|
|
const FGeometryCollectionSection& Section = SectionArray[SectionIndex];
|
|
check(Section.NumTriangles > 0); // Sections are not created with zero triangles
|
|
|
|
const int32 FirstFaceIndex = Section.FirstIndex / 3;
|
|
const int32 EndFaceIndex = FirstFaceIndex + Section.NumTriangles;
|
|
|
|
int32 TransformIndex = BoneMap[IndexArray[FirstFaceIndex][0]]; // Assumes one transform per triangle
|
|
int32 FaceIndex = FirstFaceIndex;
|
|
|
|
for (int32 NextFaceIndex = FaceIndex + 1; NextFaceIndex < EndFaceIndex; ++NextFaceIndex)
|
|
{
|
|
const int32 NextTransformIndex = BoneMap[IndexArray[NextFaceIndex][0]]; // Assumes one transform per triangle
|
|
if (TransformIndex != NextTransformIndex)
|
|
{
|
|
// Add the current subsection
|
|
AddSubSection(SectionOffset + TransformIndex, Section, FaceIndex, NextFaceIndex);
|
|
|
|
// Update variables for the next subsection
|
|
TransformIndex = NextTransformIndex;
|
|
FaceIndex = NextFaceIndex;
|
|
}
|
|
}
|
|
|
|
// Add the last remaining subsection
|
|
AddSubSection(SectionOffset + TransformIndex, Section, FaceIndex, EndFaceIndex);
|
|
}
|
|
}
|
|
|
|
void FGeometryCollectionSceneProxy::ReleaseSubSections_RenderThread()
|
|
{
|
|
SubSections.Reset();
|
|
SubSectionHitProxyIndexMap.Reset();
|
|
}
|
|
|
|
#endif // #if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
|
|
void FGeometryCollectionSceneProxy::GetPreSkinnedLocalBounds(FBoxSphereBounds& OutBounds) const
|
|
{
|
|
OutBounds = PreSkinnedBounds;
|
|
}
|
|
|
|
FNaniteGeometryCollectionSceneProxy::FNaniteGeometryCollectionSceneProxy(UGeometryCollectionComponent* Component)
|
|
: Nanite::FSceneProxyBase(Component)
|
|
, GeometryCollection(Component->GetRestCollection())
|
|
, bCurrentlyInMotion(false)
|
|
, bRequiresGPUSceneUpdate(false)
|
|
{
|
|
LLM_SCOPE_BYTAG(Nanite);
|
|
|
|
// Nanite requires GPUScene
|
|
checkSlow(UseGPUScene(GMaxRHIShaderPlatform, GetScene().GetFeatureLevel()));
|
|
checkSlow(DoesPlatformSupportNanite(GMaxRHIShaderPlatform));
|
|
|
|
// Should have valid Nanite data at this point.
|
|
check(GeometryCollection->HasNaniteData());
|
|
NaniteResourceID = GeometryCollection->GetNaniteResourceID();
|
|
NaniteHierarchyOffset = GeometryCollection->GetNaniteHierarchyOffset();
|
|
|
|
MaterialRelevance = Component->GetMaterialRelevance(Component->GetScene()->GetFeatureLevel());
|
|
|
|
// Nanite supports the GPUScene instance data buffer.
|
|
bSupportsInstanceDataBuffer = true;
|
|
|
|
// We always have correct instance transforms, skip GPUScene updates if allowed.
|
|
bShouldUpdateGPUSceneTransforms = (GGeometryCollectionOptimizedTransforms == 0);
|
|
|
|
bSupportsDistanceFieldRepresentation = false;
|
|
|
|
// Dynamic draw path without Nanite isn't supported by Lumen
|
|
bVisibleInLumenScene = false;
|
|
|
|
// Use fast path that does not update static draw lists.
|
|
bStaticElementsAlwaysUseProxyPrimitiveUniformBuffer = true;
|
|
|
|
// Nanite always uses GPUScene, so we can skip expensive primitive uniform buffer updates.
|
|
bVFRequiresPrimitiveUniformBuffer = false;
|
|
|
|
// Indicates if 1 or more materials contain settings not supported by Nanite.
|
|
bHasMaterialErrors = false;
|
|
|
|
bHasPerInstanceHierarchyOffset = true;
|
|
bHasPerInstanceLocalBounds = true;
|
|
bHasPerInstanceDynamicData = true;
|
|
|
|
// Check if the assigned material can be rendered in Nanite. If not, default.
|
|
// TODO: Handle cases like geometry collections adding a "selected geometry" material with translucency.
|
|
const bool IsRenderable = true;// Nanite::FSceneProxy::IsNaniteRenderable(MaterialRelevance);
|
|
|
|
if (!IsRenderable)
|
|
{
|
|
bHasMaterialErrors = true;
|
|
}
|
|
|
|
const TSharedPtr<FGeometryCollection, ESPMode::ThreadSafe> Collection = GeometryCollection->GetGeometryCollection();
|
|
const TManagedArray<FGeometryCollectionSection>& SectionsArray = Component->GetSectionsArray();
|
|
|
|
MaterialSections.SetNumZeroed(SectionsArray.Num());
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < SectionsArray.Num(); ++SectionIndex)
|
|
{
|
|
const FGeometryCollectionSection& MeshSection = SectionsArray[SectionIndex];
|
|
const bool bValidMeshSection = MeshSection.MaterialID != INDEX_NONE;
|
|
|
|
// Keep track of highest observed material index.
|
|
MaterialMaxIndex = FMath::Max(MeshSection.MaterialID, MaterialMaxIndex);
|
|
|
|
UMaterialInterface* MaterialInterface = bValidMeshSection ? Component->GetMaterial(MeshSection.MaterialID) : nullptr;
|
|
|
|
// TODO: PROG_RASTER (Implement programmable raster support)
|
|
const bool bInvalidMaterial = !MaterialInterface || !IsOpaqueBlendMode(*MaterialInterface);
|
|
if (bInvalidMaterial)
|
|
{
|
|
bHasMaterialErrors = true;
|
|
if (MaterialInterface)
|
|
{
|
|
UE_LOG
|
|
(
|
|
LogStaticMesh, Warning,
|
|
TEXT("Invalid material [%s] used on Nanite geometry collection [%s] - forcing default material instead. Only opaque blend mode is currently supported, [%s] blend mode was specified."),
|
|
*MaterialInterface->GetName(),
|
|
*GeometryCollection->GetName(),
|
|
*GetBlendModeString(MaterialInterface->GetBlendMode())
|
|
);
|
|
}
|
|
}
|
|
|
|
const bool bForceDefaultMaterial = /*!!FORCE_NANITE_DEFAULT_MATERIAL ||*/ bHasMaterialErrors;
|
|
if (bForceDefaultMaterial)
|
|
{
|
|
MaterialInterface = UMaterial::GetDefaultMaterial(MD_Surface);
|
|
}
|
|
|
|
// Should never be null here
|
|
check(MaterialInterface != nullptr);
|
|
|
|
// Should always be opaque blend mode here.
|
|
check(IsOpaqueBlendMode(*MaterialInterface));
|
|
|
|
MaterialSections[SectionIndex].ShadingMaterialProxy = MaterialInterface->GetRenderProxy();
|
|
MaterialSections[SectionIndex].RasterMaterialProxy = MaterialInterface->GetRenderProxy(); // TODO: PROG_RASTER (Implement programmable raster support)
|
|
MaterialSections[SectionIndex].MaterialIndex = MeshSection.MaterialID;
|
|
}
|
|
|
|
const bool bHasGeometryBoundingBoxes =
|
|
Collection->HasAttribute("BoundingBox", FGeometryCollection::GeometryGroup) &&
|
|
Collection->NumElements(FGeometryCollection::GeometryGroup);
|
|
|
|
const bool bHasTransformBoundingBoxes =
|
|
Collection->NumElements(FGeometryCollection::TransformGroup) &&
|
|
Collection->HasAttribute("BoundingBox", FGeometryCollection::TransformGroup) &&
|
|
Collection->HasAttribute("TransformToGeometryIndex", FGeometryCollection::TransformGroup);
|
|
|
|
int32 NumGeometry = 0;
|
|
if (bHasGeometryBoundingBoxes)
|
|
{
|
|
NumGeometry = Collection->NumElements(FGeometryCollection::GeometryGroup);
|
|
GeometryNaniteData.SetNumUninitialized(NumGeometry);
|
|
|
|
const TManagedArray<FBox>& BoundingBoxes = Collection->GetAttribute<FBox>("BoundingBox", FGeometryCollection::GeometryGroup);
|
|
for (int32 GeometryIndex = 0; GeometryIndex < NumGeometry; ++GeometryIndex)
|
|
{
|
|
FGeometryNaniteData& Instance = GeometryNaniteData[GeometryIndex];
|
|
Instance.HierarchyOffset = GeometryCollection->GetNaniteHierarchyOffset(GeometryIndex);
|
|
Instance.LocalBounds = BoundingBoxes[GeometryIndex];
|
|
}
|
|
}
|
|
else if (bHasTransformBoundingBoxes)
|
|
{
|
|
Nanite::FResources& Resource = GeometryCollection->NaniteData->NaniteResource;
|
|
NumGeometry = Resource.HierarchyRootOffsets.Num();
|
|
GeometryNaniteData.SetNumUninitialized(NumGeometry);
|
|
|
|
const TManagedArray<FBox>& BoundingBoxes = Collection->GetAttribute<FBox>("BoundingBox", FGeometryCollection::TransformGroup);
|
|
const TManagedArray<int32>& TransformToGeometry = Collection->GetAttribute<int32>("TransformToGeometryIndex", FGeometryCollection::TransformGroup);
|
|
const int32 NumTransforms = TransformToGeometry.Num();
|
|
for (int32 TransformIndex = 0; TransformIndex < NumTransforms; ++TransformIndex)
|
|
{
|
|
const int32 GeometryIndex = TransformToGeometry[TransformIndex];
|
|
if (GeometryIndex > INDEX_NONE)
|
|
{
|
|
FGeometryNaniteData& Instance = GeometryNaniteData[GeometryIndex];
|
|
Instance.HierarchyOffset = GeometryCollection->GetNaniteHierarchyOffset(GeometryIndex);
|
|
Instance.LocalBounds = BoundingBoxes[TransformIndex];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Need to specify initial instance list, even with just identity transforms, so that the
|
|
// GPUScene instance data allocator reserves space for the instances early on. The instance
|
|
// transforms will be corrected during the first frame before any rendering occurs.
|
|
InstanceSceneData.SetNumUninitialized(NumGeometry);
|
|
InstanceDynamicData.SetNumUninitialized(NumGeometry);
|
|
InstanceLocalBounds.SetNumUninitialized(NumGeometry);
|
|
InstanceHierarchyOffset.SetNumZeroed(NumGeometry);
|
|
|
|
for (int32 GeometryIndex = 0; GeometryIndex < NumGeometry; ++GeometryIndex)
|
|
{
|
|
FInstanceSceneData& SceneData = InstanceSceneData[GeometryIndex];
|
|
SceneData.LocalToPrimitive.SetIdentity();
|
|
|
|
FInstanceDynamicData& DynamicData = InstanceDynamicData[GeometryIndex];
|
|
DynamicData.PrevLocalToPrimitive.SetIdentity();
|
|
|
|
InstanceLocalBounds[GeometryIndex] = FRenderBounds();
|
|
}
|
|
}
|
|
|
|
SIZE_T FNaniteGeometryCollectionSceneProxy::GetTypeHash() const
|
|
{
|
|
static size_t UniquePointer;
|
|
return reinterpret_cast<size_t>(&UniquePointer);
|
|
}
|
|
|
|
FPrimitiveViewRelevance FNaniteGeometryCollectionSceneProxy::GetViewRelevance(const FSceneView* View) const
|
|
{
|
|
LLM_SCOPE_BYTAG(Nanite);
|
|
|
|
FPrimitiveViewRelevance Result;
|
|
Result.bDrawRelevance = IsShown(View) && View->Family->EngineShowFlags.NaniteMeshes;
|
|
Result.bShadowRelevance = IsShadowCast(View);
|
|
Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask();
|
|
|
|
// Always render the Nanite mesh data with static relevance.
|
|
Result.bStaticRelevance = true;
|
|
|
|
// Should always be covered by constructor of Nanite scene proxy.
|
|
Result.bRenderInMainPass = true;
|
|
|
|
#if WITH_EDITOR
|
|
// Only check these in the editor
|
|
Result.bEditorVisualizeLevelInstanceRelevance = IsEditingLevelInstanceChild();
|
|
Result.bEditorStaticSelectionRelevance = (IsSelected() || IsHovered());
|
|
#endif
|
|
|
|
bool bSetDynamicRelevance = false;
|
|
|
|
Result.bOpaque = true;
|
|
|
|
MaterialRelevance.SetPrimitiveViewRelevance(Result);
|
|
Result.bVelocityRelevance = Result.bOpaque && Result.bRenderInMainPass && DrawsVelocity();
|
|
|
|
return Result;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
HHitProxy* FNaniteGeometryCollectionSceneProxy::CreateHitProxies(UPrimitiveComponent* Component, TArray<TRefCountPtr<HHitProxy>>& OutHitProxies)
|
|
{
|
|
LLM_SCOPE_BYTAG(Nanite);
|
|
|
|
if (Component->GetOwner())
|
|
{
|
|
// Generate separate hit proxies for each material section, so that we can perform hit tests against each one.
|
|
for (int32 SectionIndex = 0; SectionIndex < MaterialSections.Num(); ++SectionIndex)
|
|
{
|
|
FMaterialSection& Section = MaterialSections[SectionIndex];
|
|
HHitProxy* ActorHitProxy = new HActor(Component->GetOwner(), Component, SectionIndex, SectionIndex);
|
|
check(!Section.HitProxy);
|
|
Section.HitProxy = ActorHitProxy;
|
|
OutHitProxies.Add(ActorHitProxy);
|
|
}
|
|
}
|
|
|
|
return Super::CreateHitProxies(Component, OutHitProxies);
|
|
}
|
|
#endif
|
|
|
|
void FNaniteGeometryCollectionSceneProxy::DrawStaticElements(FStaticPrimitiveDrawInterface* PDI)
|
|
{
|
|
const FLightCacheInterface* LCI = nullptr;
|
|
DrawStaticElementsInternal(PDI, LCI);
|
|
}
|
|
|
|
uint32 FNaniteGeometryCollectionSceneProxy::GetMemoryFootprint() const
|
|
{
|
|
return sizeof(*this) + GetAllocatedSize();
|
|
}
|
|
|
|
void FNaniteGeometryCollectionSceneProxy::OnTransformChanged()
|
|
{
|
|
}
|
|
|
|
void FNaniteGeometryCollectionSceneProxy::GetNaniteResourceInfo(uint32& ResourceID, uint32& HierarchyOffset, uint32& ImposterIndex) const
|
|
{
|
|
ResourceID = NaniteResourceID;
|
|
HierarchyOffset = NaniteHierarchyOffset;
|
|
ImposterIndex = INDEX_NONE; // Imposters are not supported (yet?)
|
|
}
|
|
|
|
void FNaniteGeometryCollectionSceneProxy::GetNaniteMaterialMask(FUint32Vector2& OutMaterialMask) const
|
|
{
|
|
// TODO: Implement support
|
|
OutMaterialMask = FUint32Vector2(~uint32(0), ~uint32(0));
|
|
}
|
|
|
|
Nanite::FResourceMeshInfo FNaniteGeometryCollectionSceneProxy::GetResourceMeshInfo() const
|
|
{
|
|
Nanite::FResources& Resource = GeometryCollection->NaniteData->NaniteResource;
|
|
|
|
Nanite::FResourceMeshInfo OutInfo;
|
|
|
|
OutInfo.NumClusters = Resource.NumClusters;
|
|
OutInfo.NumNodes = Resource.NumHierarchyNodes;
|
|
OutInfo.NumVertices = Resource.NumInputVertices;
|
|
OutInfo.NumTriangles = Resource.NumInputTriangles;
|
|
OutInfo.NumMaterials = MaterialMaxIndex + 1;
|
|
OutInfo.DebugName = GeometryCollection->GetFName();
|
|
|
|
// TODO: SegmentMapping
|
|
OutInfo.NumSegments = 0;
|
|
|
|
return MoveTemp(OutInfo);
|
|
}
|
|
|
|
void FNaniteGeometryCollectionSceneProxy::SetConstantData_RenderThread(FGeometryCollectionConstantData* NewConstantData, bool ForceInit)
|
|
{
|
|
const TSharedPtr<FGeometryCollection, ESPMode::ThreadSafe> Collection = GeometryCollection->GetGeometryCollection();
|
|
const TManagedArray<int32>& TransformToGeometryIndices = Collection->TransformToGeometryIndex;
|
|
|
|
const int32 TransformCount = NewConstantData->RestTransforms.Num();
|
|
check(TransformCount == TransformToGeometryIndices.Num());
|
|
InstanceSceneData.Reset(TransformCount);
|
|
InstanceDynamicData.Reset(TransformCount);
|
|
InstanceLocalBounds.Reset(TransformCount);
|
|
InstanceHierarchyOffset.Reset(TransformCount);
|
|
|
|
for (int32 TransformIndex = 0; TransformIndex < TransformCount; ++TransformIndex)
|
|
{
|
|
const int32 TransformToGeometryIndex = TransformToGeometryIndices[TransformIndex];
|
|
if (!Collection->IsGeometry(TransformIndex))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FGeometryNaniteData& NaniteData = GeometryNaniteData[TransformToGeometryIndex];
|
|
|
|
FInstanceSceneData& Instance = InstanceSceneData.Emplace_GetRef();
|
|
Instance.LocalToPrimitive = NewConstantData->RestTransforms[TransformIndex];
|
|
|
|
FInstanceDynamicData& DynamicData = InstanceDynamicData.Emplace_GetRef();
|
|
DynamicData.PrevLocalToPrimitive = Instance.LocalToPrimitive;
|
|
|
|
InstanceLocalBounds.Emplace(NaniteData.LocalBounds);
|
|
InstanceHierarchyOffset.Emplace(NaniteData.HierarchyOffset);
|
|
}
|
|
|
|
delete NewConstantData;
|
|
}
|
|
|
|
void FNaniteGeometryCollectionSceneProxy::SetDynamicData_RenderThread(FGeometryCollectionDynamicData* NewDynamicData)
|
|
{
|
|
// Are we currently simulating?
|
|
if (NewDynamicData->IsDynamic)
|
|
{
|
|
const TSharedPtr<FGeometryCollection, ESPMode::ThreadSafe> Collection = GeometryCollection->GetGeometryCollection();
|
|
const TManagedArray<int32>& TransformToGeometryIndices = Collection->TransformToGeometryIndex;
|
|
const TManagedArray<TSet<int32>>& TransformChildren = Collection->Children;
|
|
const TManagedArray<int32>& SimulationType = Collection->SimulationType;
|
|
|
|
const int32 TransformCount = NewDynamicData->Transforms.Num();
|
|
check(TransformCount == TransformToGeometryIndices.Num());
|
|
check(TransformCount == TransformChildren.Num());
|
|
check(TransformCount == NewDynamicData->PrevTransforms.Num());
|
|
InstanceSceneData.Reset(TransformCount);
|
|
InstanceDynamicData.Reset(TransformCount);
|
|
InstanceLocalBounds.Reset(TransformCount);
|
|
InstanceHierarchyOffset.Reset(TransformCount);
|
|
|
|
for (int32 TransformIndex = 0; TransformIndex < TransformCount; ++TransformIndex)
|
|
{
|
|
const int32 TransformToGeometryIndex = TransformToGeometryIndices[TransformIndex];
|
|
if (SimulationType[TransformIndex] != FGeometryCollection::ESimulationTypes::FST_Rigid)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FGeometryNaniteData& NaniteData = GeometryNaniteData[TransformToGeometryIndex];
|
|
|
|
FInstanceSceneData& Instance = InstanceSceneData.Emplace_GetRef();
|
|
Instance.LocalToPrimitive = NewDynamicData->Transforms[TransformIndex];
|
|
|
|
FInstanceDynamicData& DynamicData = InstanceDynamicData.Emplace_GetRef();
|
|
|
|
if (bCurrentlyInMotion)
|
|
{
|
|
DynamicData.PrevLocalToPrimitive = NewDynamicData->PrevTransforms[TransformIndex];
|
|
}
|
|
else
|
|
{
|
|
DynamicData.PrevLocalToPrimitive = Instance.LocalToPrimitive;
|
|
}
|
|
|
|
InstanceLocalBounds.Emplace(NaniteData.LocalBounds);
|
|
InstanceHierarchyOffset.Emplace(NaniteData.HierarchyOffset);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Rendering base geometry, use rest transforms rather than simulated transforms.
|
|
// ...
|
|
}
|
|
|
|
GDynamicDataPool.Release(NewDynamicData);
|
|
}
|
|
|
|
void FNaniteGeometryCollectionSceneProxy::ResetPreviousTransforms_RenderThread()
|
|
{
|
|
// Reset previous transforms to avoid locked motion vectors
|
|
check(InstanceSceneData.Num() == InstanceDynamicData.Num()); // Sanity check, should always have matching associated arrays
|
|
for (int32 InstanceIndex = 0; InstanceIndex < InstanceSceneData.Num(); ++InstanceIndex)
|
|
{
|
|
InstanceDynamicData[InstanceIndex].PrevLocalToPrimitive = InstanceSceneData[InstanceIndex].LocalToPrimitive;
|
|
}
|
|
}
|
|
|
|
void FNaniteGeometryCollectionSceneProxy::FlushGPUSceneUpdate_GameThread()
|
|
{
|
|
ENQUEUE_RENDER_COMMAND(NaniteProxyUpdateGPUScene)(
|
|
[this](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
FPrimitiveSceneInfo* NanitePrimitiveInfo = GetPrimitiveSceneInfo();
|
|
if (NanitePrimitiveInfo && GetRequiresGPUSceneUpdate_RenderThread())
|
|
{
|
|
// Attempt to queue up a GPUScene update - maintain dirty flag if the request fails.
|
|
const bool bRequiresUpdate = !NanitePrimitiveInfo->RequestGPUSceneUpdate();
|
|
SetRequiresGPUSceneUpdate_RenderThread(bRequiresUpdate);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
void FNaniteGeometryCollectionSceneProxy::OnMotionBegin()
|
|
{
|
|
bCurrentlyInMotion = true;
|
|
bCanSkipRedundantTransformUpdates = false;
|
|
}
|
|
|
|
void FNaniteGeometryCollectionSceneProxy::OnMotionEnd()
|
|
{
|
|
bCurrentlyInMotion = false;
|
|
bCanSkipRedundantTransformUpdates = true;
|
|
ResetPreviousTransforms_RenderThread();
|
|
}
|
|
|
|
FGeometryCollectionDynamicDataPool::FGeometryCollectionDynamicDataPool()
|
|
{
|
|
FreeList.SetNum(32);
|
|
for (int32 ListIndex = 0; ListIndex < FreeList.Num(); ++ListIndex)
|
|
{
|
|
FreeList[ListIndex] = new FGeometryCollectionDynamicData;
|
|
}
|
|
}
|
|
|
|
FGeometryCollectionDynamicDataPool::~FGeometryCollectionDynamicDataPool()
|
|
{
|
|
FScopeLock ScopeLock(&ListLock);
|
|
|
|
for (FGeometryCollectionDynamicData* Entry : FreeList)
|
|
{
|
|
delete Entry;
|
|
}
|
|
|
|
for (FGeometryCollectionDynamicData* Entry : UsedList)
|
|
{
|
|
delete Entry;
|
|
}
|
|
|
|
FreeList.Empty();
|
|
UsedList.Empty();
|
|
}
|
|
|
|
FGeometryCollectionDynamicData* FGeometryCollectionDynamicDataPool::Allocate()
|
|
{
|
|
FScopeLock ScopeLock(&ListLock);
|
|
|
|
FGeometryCollectionDynamicData* NewEntry = nullptr;
|
|
if (FreeList.Num() > 0)
|
|
{
|
|
NewEntry = FreeList.Pop(false /* no shrinking */);
|
|
}
|
|
|
|
if (NewEntry == nullptr)
|
|
{
|
|
NewEntry = new FGeometryCollectionDynamicData;
|
|
}
|
|
|
|
NewEntry->Reset();
|
|
UsedList.Push(NewEntry);
|
|
|
|
return NewEntry;
|
|
}
|
|
|
|
void FGeometryCollectionDynamicDataPool::Release(FGeometryCollectionDynamicData* DynamicData)
|
|
{
|
|
FScopeLock ScopeLock(&ListLock);
|
|
|
|
int32 UsedIndex = UsedList.Find(DynamicData);
|
|
if (ensure(UsedIndex != INDEX_NONE))
|
|
{
|
|
UsedList.RemoveAt(UsedIndex, 1, false /* no shrinking */);
|
|
FreeList.Push(DynamicData);
|
|
}
|
|
}
|
|
|
|
void FGeometryCollectionTransformBuffer::UpdateDynamicData(const TArray<FMatrix44f>& Transforms, EResourceLockMode LockMode)
|
|
{
|
|
check(NumTransforms == Transforms.Num());
|
|
|
|
void* VertexBufferData = RHILockBuffer(VertexBufferRHI, 0, Transforms.Num() * sizeof(FMatrix44f), LockMode);
|
|
FMemory::Memcpy(VertexBufferData, Transforms.GetData(), Transforms.Num() * sizeof(FMatrix44f));
|
|
RHIUnlockBuffer(VertexBufferRHI);
|
|
}
|