710 lines
32 KiB
C++
710 lines
32 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "LandscapeNaniteComponent.h"
|
|
#include "DerivedDataCacheInterface.h"
|
|
#include "LandscapeEdit.h"
|
|
#include "LandscapeRender.h"
|
|
#include "MaterialDomain.h"
|
|
#include "Materials/Material.h"
|
|
#include "NaniteSceneProxy.h"
|
|
#include "Materials/MaterialInstanceConstant.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "Engine/StaticMeshSourceData.h"
|
|
#include "NaniteDefinitions.h"
|
|
#include "UObject/Package.h"
|
|
#include "RenderUtils.h"
|
|
#include "Serialization/MemoryReader.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(LandscapeNaniteComponent)
|
|
|
|
#if WITH_EDITOR
|
|
#include "AssetCompilingManager.h"
|
|
#include "StaticMeshAttributes.h"
|
|
#include "StaticMeshDescription.h"
|
|
#include "StaticMeshOperations.h"
|
|
#include "MeshUtilitiesCommon.h"
|
|
#include "OverlappingCorners.h"
|
|
#include "MeshBuild.h"
|
|
#include "StaticMeshBuilder.h"
|
|
#include "NaniteBuilder.h"
|
|
#include "Rendering/NaniteResources.h"
|
|
#include "PhysicsEngine/BodySetup.h"
|
|
#include "StaticMeshCompiler.h"
|
|
#include "LandscapePrivate.h"
|
|
#include "LandscapeDataAccess.h"
|
|
#include "LandscapeSubsystem.h"
|
|
#include "MeshDescriptionHelper.h"
|
|
#include "Async/Async.h"
|
|
#include "Async/TaskGraphInterfaces.h"
|
|
#include "EditorFramework/AssetImportData.h"
|
|
#include "Interfaces/ITargetPlatform.h"
|
|
#endif
|
|
|
|
extern float LandscapeNaniteAsyncDebugWait;
|
|
extern float LandscapeNaniteStallDetectionTimeout;
|
|
|
|
namespace UE::Landscape
|
|
{
|
|
extern int32 NaniteExportCacheMaxQuadCount;
|
|
|
|
#if WITH_EDITOR
|
|
bool Nanite::FAsyncBuildData::CheckForStallAndWarn()
|
|
{
|
|
if (bIsComplete.load())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check if it's taking a long time
|
|
// TODO [chris.tchou] Checking start/finish timestamps is not a great way to detect stalls, as it is prone to false positives.
|
|
// Especially because of the way we queue up tasks for the entire landscape all at once, it can take a while to chew through the backlog.
|
|
// (this is worse on larger landscapes and slower machines).
|
|
// Better would be to have a manager that only kicked off tasks based on available resources,
|
|
// and track timestamps on individual task/step completion.
|
|
double Now = FPlatformTime::Seconds();
|
|
bool bStalled =
|
|
((TimeStamp_Requested > 0.0) && (Now - TimeStamp_Requested > LandscapeNaniteStallDetectionTimeout)) ||
|
|
((TimeStamp_StaticMeshBatchBuildStart > 0.0) && (TimeStamp_StaticMeshBatchBuildPostMeshBuildCall < 0.0) && (Now - TimeStamp_StaticMeshBatchBuildStart > LandscapeNaniteStallDetectionTimeout * 0.1));
|
|
|
|
if (bStalled)
|
|
{
|
|
if (FPlatformMisc::IsDebuggerPresent())
|
|
{
|
|
// assume when a debugger is attached, any stalls are caused by breakpoints
|
|
return false;
|
|
}
|
|
|
|
if (!bWarnedStall)
|
|
{
|
|
FString LandscapeName = LandscapeWeakRef.IsValid() ? LandscapeWeakRef->GetName() : FString("INVALID");
|
|
|
|
UE_LOG(LogLandscape, Warning, TEXT("Nanite Build Task for '%s' is taking a long time: Req:%f Exp:%f-%f MB:%f-%f BB:%f PMB:%f LU:%f-%f Complete:%f Cancelled:%f Now:%f bResult:%d bCancel:%d bNeedsPMB:%d Changing landscape.Nanite.StallDetectionTimeout controls how long until this message appears."),
|
|
*LandscapeName,
|
|
TimeStamp_Requested,
|
|
TimeStamp_ExportMeshStart,
|
|
TimeStamp_ExportMeshEnd,
|
|
TimeStamp_StaticMeshBuildStart,
|
|
TimeStamp_StaticMeshBuildEnd,
|
|
TimeStamp_StaticMeshBatchBuildStart,
|
|
TimeStamp_StaticMeshBatchBuildPostMeshBuildCall,
|
|
TimeStamp_LandscapeUpdateStart,
|
|
TimeStamp_LandscapeUpdateEnd,
|
|
TimeStamp_Complete,
|
|
TimeStamp_Cancelled,
|
|
Now,
|
|
bExportResult.load(),
|
|
bCancelled.load(),
|
|
bStaticMeshNeedsToCallPostMeshBuild.load()
|
|
);
|
|
bWarnedStall = true;
|
|
}
|
|
}
|
|
return bStalled;
|
|
}
|
|
#endif // WITH_EDITOR
|
|
}
|
|
|
|
ULandscapeNaniteComponent::ULandscapeNaniteComponent(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
, bEnabled(true)
|
|
{
|
|
// We don't want Nanite representation in ray tracing
|
|
bVisibleInRayTracing = false;
|
|
|
|
// We don't want WPO evaluation enabled on landscape meshes
|
|
bEvaluateWorldPositionOffset = false;
|
|
}
|
|
|
|
void ULandscapeNaniteComponent::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
|
|
#if WITH_EDITOR
|
|
if (UStaticMesh* NaniteStaticMesh = GetStaticMesh())
|
|
{
|
|
UPackage* CurrentPackage = GetPackage();
|
|
check(CurrentPackage);
|
|
// At one point, the Nanite mesh was outered to the component, which leads the mesh to be duplicated when entering PIE. If we outer the mesh to the package instead,
|
|
// PIE duplication will simply reference that mesh, preventing the expensive copy to occur when entering PIE:
|
|
if (!(CurrentPackage->GetPackageFlags() & PKG_PlayInEditor) // No need to do it on PIE, since the outer should already have been changed in the original object
|
|
&& (NaniteStaticMesh->GetOuter() != CurrentPackage))
|
|
{
|
|
// Change the outer :
|
|
NaniteStaticMesh->Rename(nullptr, CurrentPackage);
|
|
}
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
ALandscapeProxy* LandscapeProxy = GetLandscapeProxy();
|
|
if (ensure(LandscapeProxy))
|
|
{
|
|
// Ensure that the component lighting and shadow settings matches the actor
|
|
UpdatedSharedPropertiesFromActor();
|
|
}
|
|
|
|
// Override settings that may have been serialized previously with the wrong values
|
|
{
|
|
// We don't want Nanite representation in ray tracing
|
|
bVisibleInRayTracing = false;
|
|
|
|
// We don't want WPO evaluation enabled on landscape meshes
|
|
bEvaluateWorldPositionOffset = false;
|
|
}
|
|
}
|
|
|
|
void ULandscapeNaniteComponent::CollectPSOPrecacheData(const FPSOPrecacheParams& BasePrecachePSOParams, FMaterialInterfacePSOPrecacheParamsList& OutParams)
|
|
{
|
|
Super::CollectPSOPrecacheData(BasePrecachePSOParams, OutParams);
|
|
|
|
// Mark high priority
|
|
for (FMaterialInterfacePSOPrecacheParams& Params : OutParams)
|
|
{
|
|
Params.Priority = EPSOPrecachePriority::High;
|
|
}
|
|
}
|
|
|
|
ALandscapeProxy* ULandscapeNaniteComponent::GetLandscapeProxy() const
|
|
{
|
|
return CastChecked<ALandscapeProxy>(GetOuter());
|
|
}
|
|
|
|
ALandscape* ULandscapeNaniteComponent::GetLandscapeActor() const
|
|
{
|
|
ALandscapeProxy* Landscape = GetLandscapeProxy();
|
|
if (Landscape)
|
|
{
|
|
return Landscape->GetLandscapeActor();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void ULandscapeNaniteComponent::UpdatedSharedPropertiesFromActor()
|
|
{
|
|
ALandscapeProxy* LandscapeProxy = GetLandscapeProxy();
|
|
|
|
CastShadow = LandscapeProxy->CastShadow;
|
|
bCastDynamicShadow = LandscapeProxy->bCastDynamicShadow;
|
|
bCastStaticShadow = LandscapeProxy->bCastStaticShadow;
|
|
bCastContactShadow = LandscapeProxy->bCastContactShadow;
|
|
bCastFarShadow = LandscapeProxy->bCastFarShadow;
|
|
bCastHiddenShadow = LandscapeProxy->bCastHiddenShadow;
|
|
bCastShadowAsTwoSided = LandscapeProxy->bCastShadowAsTwoSided;
|
|
bAffectDistanceFieldLighting = LandscapeProxy->bAffectDistanceFieldLighting;
|
|
bAffectDynamicIndirectLighting = LandscapeProxy->bAffectDynamicIndirectLighting;
|
|
bAffectIndirectLightingWhileHidden = LandscapeProxy->bAffectIndirectLightingWhileHidden;
|
|
bRenderCustomDepth = LandscapeProxy->bRenderCustomDepth;
|
|
CustomDepthStencilWriteMask = LandscapeProxy->CustomDepthStencilWriteMask;
|
|
CustomDepthStencilValue = LandscapeProxy->CustomDepthStencilValue;
|
|
SetCullDistance(LandscapeProxy->LDMaxDrawDistance);
|
|
LightingChannels = LandscapeProxy->LightingChannels;
|
|
bHoldout = LandscapeProxy->bHoldout;
|
|
ShadowCacheInvalidationBehavior = LandscapeProxy->ShadowCacheInvalidationBehavior;
|
|
}
|
|
|
|
void ULandscapeNaniteComponent::SetEnabled(bool bValue)
|
|
{
|
|
if (bValue != bEnabled)
|
|
{
|
|
bEnabled = bValue;
|
|
MarkRenderStateDirty();
|
|
}
|
|
}
|
|
|
|
bool ULandscapeNaniteComponent::NeedsLoadForTargetPlatform(const class ITargetPlatform* TargetPlatform) const
|
|
{
|
|
// The ULandscapeNaniteComponent will never contain collision data, so if the platform cannot support rendering nanite, it does not need to be exported
|
|
return DoesTargetPlatformSupportNanite(TargetPlatform);
|
|
}
|
|
|
|
bool ULandscapeNaniteComponent::IsHLODRelevant() const
|
|
{
|
|
// This component doesn't need to be included in HLOD, as we're already including the non-nanite LS components
|
|
return false;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
FGraphEventRef ULandscapeNaniteComponent::InitializeForLandscapeAsync(ALandscapeProxy* Landscape, const FGuid& NewProxyContentId, const TArray<ULandscapeComponent*>& InComponentsToExport, int32 InNaniteComponentIndex)
|
|
{
|
|
UE_LOG(LogLandscape, VeryVerbose, TEXT("InitializeForLandscapeAsync actor: '%s' package:'%s'"), *Landscape->GetActorNameOrLabel(), *Landscape->GetPackage()->GetName());
|
|
|
|
check(bVisibleInRayTracing == false);
|
|
|
|
UWorld* World = Landscape->GetWorld();
|
|
|
|
ULandscapeSubsystem* LandscapeSubSystem = World->GetSubsystem<ULandscapeSubsystem>();
|
|
check(LandscapeSubSystem);
|
|
LandscapeSubSystem->IncNaniteBuild();
|
|
|
|
TSharedRef<UE::Landscape::Nanite::FAsyncBuildData> AsyncBuildData =
|
|
LandscapeSubSystem->CreateTrackedNaniteBuildState(Landscape, GetLandscapeActor()->GetNaniteLODIndex(), InComponentsToExport);
|
|
check(AsyncBuildData->NaniteStaticMesh);
|
|
|
|
FGraphEventRef StaticMeshBuildCompleteEvent = AsyncBuildData->BuildCompleteEvent;
|
|
|
|
FGraphEventRef ExportMeshEvent = FFunctionGraphTask::CreateAndDispatchWhenReady(
|
|
[AsyncBuildData, ProxyContentId = NewProxyContentId, Name = Landscape->GetActorNameOrLabel()]()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeNaniteComponent::ExportLandscapeAsync-ExportMeshTask);
|
|
|
|
UE_LOG(LogLandscape, VeryVerbose, TEXT("Exporting actor '%s' package:'%s'"), *Name, *AsyncBuildData->LandscapeWeakRef->GetPackage()->GetName());
|
|
AsyncBuildData->TimeStamp_ExportMeshStart = FPlatformTime::Seconds();
|
|
|
|
if (!AsyncBuildData->LandscapeWeakRef.IsValid() || AsyncBuildData->bCancelled)
|
|
{
|
|
if (AsyncBuildData->TimeStamp_Cancelled < 0.0)
|
|
{
|
|
AsyncBuildData->TimeStamp_Cancelled = FPlatformTime::Seconds();
|
|
}
|
|
AsyncBuildData->bCancelled = true;
|
|
return;
|
|
}
|
|
|
|
UWorld* World = AsyncBuildData->LandscapeWeakRef->GetWorld();
|
|
ULandscapeSubsystem* LandscapeSubSystem = World->GetSubsystem<ULandscapeSubsystem>();
|
|
check(LandscapeSubSystem);
|
|
|
|
// LandscapeSubSystem->WaitLaunchNaniteBuild(); // TODO [chris.tchou]: this can deadlock, any waits should be done outside of async tasks
|
|
|
|
AsyncBuildData->SourceModel = &AsyncBuildData->NaniteStaticMesh->AddSourceModel();
|
|
AsyncBuildData->NaniteMeshDescription = AsyncBuildData->NaniteStaticMesh->CreateMeshDescription(0);
|
|
|
|
// ExportToRawMeshDataCopy places Lightmap UVs in coord 2
|
|
const int32 LightmapUVCoordIndex = 2;
|
|
AsyncBuildData->NaniteStaticMesh->SetLightMapCoordinateIndex(LightmapUVCoordIndex);
|
|
|
|
// create a hash key for the DDC cache of the landscape static mesh export
|
|
FString ExportDDCKey;
|
|
{
|
|
// Mesh Export Version, expressed as a GUID string. Change this if any of the mesh building code here changes.
|
|
// NOTE: this does not invalidate the outer cache where we check if nanite meshes need to be rebuilt on load/cook.
|
|
// it only invalidates the MeshExport DDC cache here.
|
|
static const char* MeshExportVersion = "070c6830-8d06-42a3-f43e-0709bc41a5a9";
|
|
|
|
FSHA1 Hasher;
|
|
check(PLATFORM_LITTLE_ENDIAN); // not sure if NewProxyContentId byte order is platform agnostic or not
|
|
Hasher.Update(reinterpret_cast<const uint8*>(&ProxyContentId), sizeof(FGuid));
|
|
Hasher.Update(reinterpret_cast<const uint8*>(MeshExportVersion), strlen(MeshExportVersion));
|
|
|
|
// since we can break proxies into multiple nanite meshes, the hash needs to include which piece(s) we are building here
|
|
for (ULandscapeComponent* Component : AsyncBuildData->InputComponents)
|
|
{
|
|
FIntPoint ComponentBase = Component->GetSectionBase();
|
|
Hasher.Update(reinterpret_cast<const uint8*>(&ComponentBase), sizeof(FIntPoint));
|
|
}
|
|
|
|
ExportDDCKey = Hasher.Finalize().ToString();
|
|
}
|
|
|
|
// Don't allow the engine to recalculate normals
|
|
AsyncBuildData->SourceModel->BuildSettings.bRecomputeNormals = false;
|
|
AsyncBuildData->SourceModel->BuildSettings.bRecomputeTangents = false;
|
|
AsyncBuildData->SourceModel->BuildSettings.bRemoveDegenerates = false;
|
|
AsyncBuildData->SourceModel->BuildSettings.bUseHighPrecisionTangentBasis = false;
|
|
AsyncBuildData->SourceModel->BuildSettings.bUseFullPrecisionUVs = false;
|
|
AsyncBuildData->SourceModel->BuildSettings.bGenerateLightmapUVs = false; // we generate our own Lightmap UVs; don't stomp on them!
|
|
|
|
FMeshNaniteSettings& NaniteSettings = AsyncBuildData->NaniteStaticMesh->NaniteSettings;
|
|
NaniteSettings.bEnabled = true;
|
|
NaniteSettings.FallbackPercentTriangles = 0.01f; // Keep effectively no fallback mesh triangles
|
|
NaniteSettings.FallbackRelativeError = 1.0f;
|
|
|
|
const FVector3d Scale = AsyncBuildData->LandscapeWeakRef->GetTransform().GetScale3D();
|
|
NaniteSettings.PositionPrecision = FMath::Log2(Scale.GetAbsMax() ) + AsyncBuildData->LandscapeWeakRef->GetNanitePositionPrecision();
|
|
NaniteSettings.MaxEdgeLengthFactor = AsyncBuildData->LandscapeWeakRef->GetNaniteMaxEdgeLengthFactor();
|
|
|
|
int32 LOD = AsyncBuildData->LOD;
|
|
|
|
ALandscapeProxy::FRawMeshExportParams ExportParams;
|
|
ExportParams.ComponentsToExport = MakeArrayView(AsyncBuildData->InputComponents.GetData(), AsyncBuildData->InputComponents.Num());
|
|
ExportParams.ComponentsMaterialSlotName = MakeArrayView(AsyncBuildData->InputMaterialSlotNames.GetData(), AsyncBuildData->InputMaterialSlotNames.Num());
|
|
if (AsyncBuildData->LandscapeWeakRef->IsNaniteSkirtEnabled())
|
|
{
|
|
ExportParams.SkirtDepth = AsyncBuildData->LandscapeWeakRef->GetNaniteSkirtDepth();
|
|
}
|
|
|
|
ExportParams.ExportLOD = LOD;
|
|
ExportParams.ExportCoordinatesType = ALandscapeProxy::FRawMeshExportParams::EExportCoordinatesType::RelativeToProxy;
|
|
ExportParams.UVConfiguration.ExportUVMappingTypes.SetNumZeroed(4);
|
|
ExportParams.UVConfiguration.ExportUVMappingTypes[0] = ALandscapeProxy::FRawMeshExportParams::EUVMappingType::TerrainCoordMapping_XY; // In LandscapeVertexFactory, Texcoords0 = ETerrainCoordMappingType::TCMT_XY (or ELandscapeCustomizedCoordType::LCCT_CustomUV0)
|
|
ExportParams.UVConfiguration.ExportUVMappingTypes[1] = ALandscapeProxy::FRawMeshExportParams::EUVMappingType::TerrainCoordMapping_XZ; // In LandscapeVertexFactory, Texcoords1 = ETerrainCoordMappingType::TCMT_XZ (or ELandscapeCustomizedCoordType::LCCT_CustomUV1)
|
|
ExportParams.UVConfiguration.ExportUVMappingTypes[2] = ALandscapeProxy::FRawMeshExportParams::EUVMappingType::LightmapUV; // Note that this does not match LandscapeVertexFactory's usage, but we work around it in the material graph node to remap TCMT_YZ
|
|
ExportParams.UVConfiguration.ExportUVMappingTypes[3] = ALandscapeProxy::FRawMeshExportParams::EUVMappingType::WeightmapUV; // In LandscapeVertexFactory, Texcoords3 = ELandscapeCustomizedCoordType::LCCT_WeightMapUV
|
|
|
|
// in case we do generate lightmap UVs, use the "XY" mapping as the source chart UV, and store them to UV channel 2
|
|
AsyncBuildData->SourceModel->BuildSettings.SrcLightmapIndex = 0;
|
|
AsyncBuildData->SourceModel->BuildSettings.DstLightmapIndex = LightmapUVCoordIndex;
|
|
|
|
// COMMENT [jonathan.bard] ATM Nanite meshes only support up to 4 UV sets so we cannot support those 2 :
|
|
//ExportParams.UVConfiguration.ExportUVMappingTypes[4] = ALandscapeProxy::FRawMeshExportParams::EUVMappingType::LightmapUV; // In LandscapeVertexFactory, Texcoords4 = lightmap UV
|
|
//ExportParams.UVConfiguration.ExportUVMappingTypes[5] = ALandscapeProxy::FRawMeshExportParams::EUVMappingType::HeightmapUV; // // In LandscapeVertexFactory, Texcoords5 = heightmap UV
|
|
|
|
// calculate the lightmap resolution for the proxy, and the number of quads
|
|
int32 ProxyLightmapRes = 64;
|
|
int32 ProxyQuadCount = 0;
|
|
{
|
|
const int32 ComponentSizeQuads = AsyncBuildData->LandscapeWeakRef->ComponentSizeQuads;
|
|
const float LightMapRes = AsyncBuildData->LandscapeWeakRef->StaticLightingResolution;
|
|
|
|
// min/max section bases of all exported components
|
|
FIntPoint MinSectionBase(INT_MAX, INT_MAX);
|
|
FIntPoint MaxSectionBase(-INT_MAX, -INT_MAX);
|
|
for (ULandscapeComponent* Component : AsyncBuildData->InputComponents)
|
|
{
|
|
FIntPoint SectionBase{ Component->SectionBaseX, Component->SectionBaseY };
|
|
MinSectionBase = MinSectionBase.ComponentMin(SectionBase);
|
|
MaxSectionBase = MaxSectionBase.ComponentMax(SectionBase);
|
|
ProxyQuadCount += ComponentSizeQuads;
|
|
}
|
|
int ProxyQuadsX = (MaxSectionBase.X + ComponentSizeQuads + 1 - MinSectionBase.X);
|
|
int ProxyQuadsY = (MaxSectionBase.Y + ComponentSizeQuads + 1 - MinSectionBase.Y);
|
|
|
|
// as the lightmap is just mapped as a square, it uses the square bounds to determine the resolution
|
|
ProxyLightmapRes = (ProxyQuadsX > ProxyQuadsY ? ProxyQuadsX : ProxyQuadsY) * LightMapRes;
|
|
}
|
|
|
|
AsyncBuildData->NaniteStaticMesh->SetLightMapResolution(ProxyLightmapRes);
|
|
|
|
const bool bUseNaniteExportCache = (UE::Landscape::NaniteExportCacheMaxQuadCount < 0) || (ProxyQuadCount <= UE::Landscape::NaniteExportCacheMaxQuadCount);
|
|
|
|
bool bSuccess = false;
|
|
int64 DDCReadBytes = 0;
|
|
int64 DDCWriteBytes = 0;
|
|
|
|
if (TArray64<uint8> MeshDescriptionData;
|
|
bUseNaniteExportCache && GetDerivedDataCacheRef().GetSynchronous(*ExportDDCKey, MeshDescriptionData, *AsyncBuildData->LandscapeWeakRef->GetFullName()))
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeNaniteComponent::ExportLandscapeAsync - ReadExportedMeshFromDDC);
|
|
|
|
FMemoryReaderView Reader(MakeMemoryView(MeshDescriptionData));
|
|
AsyncBuildData->NaniteMeshDescription->Serialize(Reader);
|
|
|
|
bSuccess = true;
|
|
DDCReadBytes += MeshDescriptionData.Num();
|
|
}
|
|
else
|
|
{
|
|
// build the nanite mesh description
|
|
bSuccess = AsyncBuildData->LandscapeWeakRef->ExportToRawMeshDataCopy(ExportParams, *AsyncBuildData->NaniteMeshDescription, AsyncBuildData.Get());
|
|
|
|
// Apply the mesh description cleanup/optimization here instead of during DDC build (avoids expensive large mesh copies)
|
|
FMeshDescriptionHelper MeshDescriptionHelper(&AsyncBuildData->SourceModel->BuildSettings);
|
|
MeshDescriptionHelper.SetupRenderMeshDescription(AsyncBuildData->NaniteStaticMesh.Get(), *AsyncBuildData->NaniteMeshDescription, true /* Is Nanite */, false /* bNeedTangents */);
|
|
|
|
// cache mesh description, only if we succeeded (failure may be non-deterministic)
|
|
if (bUseNaniteExportCache && bSuccess)
|
|
{
|
|
// serialize the nanite mesh description and submit it to DDC
|
|
TArray64<uint8> MeshDescriptionData64;
|
|
FMemoryWriter64 Writer(MeshDescriptionData64);
|
|
AsyncBuildData->NaniteMeshDescription->Serialize(Writer);
|
|
|
|
GetDerivedDataCacheRef().Put(*ExportDDCKey, MeshDescriptionData64, *AsyncBuildData->LandscapeWeakRef->GetFullName());
|
|
DDCWriteBytes += MeshDescriptionData64.Num();
|
|
}
|
|
}
|
|
|
|
const double ExportSeconds = FPlatformTime::Seconds() - AsyncBuildData->TimeStamp_ExportMeshStart;
|
|
if (!bSuccess)
|
|
{
|
|
UE_LOG(LogLandscape, Log, TEXT("Failed export of raw static mesh for Nanite landscape (%i components) for actor %s : (DDC: %d, DDC read: %lld bytes, DDC write: %lld bytes, key: %s, export: %f seconds)"), AsyncBuildData->InputComponents.Num(), *Name, bUseNaniteExportCache, DDCReadBytes, DDCWriteBytes, *ExportDDCKey, ExportSeconds);
|
|
if (AsyncBuildData->TimeStamp_Cancelled < 0.0)
|
|
{
|
|
AsyncBuildData->TimeStamp_Cancelled = FPlatformTime::Seconds();
|
|
}
|
|
AsyncBuildData->bCancelled = true;
|
|
return;
|
|
}
|
|
|
|
// check we have one polygon group per component
|
|
const FPolygonGroupArray& PolygonGroups = AsyncBuildData->NaniteMeshDescription->PolygonGroups();
|
|
checkf(bSuccess && (PolygonGroups.Num() == AsyncBuildData->InputComponents.Num()), TEXT("Invalid landscape static mesh raw mesh export for actor %s (%i components)"), *Name, AsyncBuildData->InputComponents.Num());
|
|
check(AsyncBuildData->InputMaterials.Num() == AsyncBuildData->InputComponents.Num());
|
|
AsyncBuildData->MeshAttributes = MakeShared<FStaticMeshAttributes>(*AsyncBuildData->NaniteMeshDescription);
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeNaniteComponent::ExportLandscapeAsync - CommitMeshDescription);
|
|
|
|
// commit the mesh description to build the static mesh for realz
|
|
UStaticMesh::FCommitMeshDescriptionParams CommitParams;
|
|
CommitParams.bMarkPackageDirty = false;
|
|
CommitParams.bUseHashAsGuid = true;
|
|
|
|
AsyncBuildData->NaniteStaticMesh->CommitMeshDescription(0u, CommitParams);
|
|
AsyncBuildData->bExportResult = true;
|
|
|
|
AsyncBuildData->TimeStamp_ExportMeshEnd = FPlatformTime::Seconds();
|
|
const double DurationSeconds = AsyncBuildData->TimeStamp_ExportMeshEnd - AsyncBuildData->TimeStamp_ExportMeshStart;
|
|
UE_LOG(LogLandscape, Log, TEXT("Successful export of raw static mesh for Nanite landscape (%i components) for actor %s : (DDC: %d, DDC read: %lld bytes, DDC write: %lld bytes, key: %s, export: %f seconds, commit: %f seconds)"), AsyncBuildData->InputComponents.Num(), *Name, bUseNaniteExportCache, DDCReadBytes, DDCWriteBytes, *ExportDDCKey, ExportSeconds, DurationSeconds - ExportSeconds);
|
|
|
|
if (const double ExtraWait = FMath::Max(LandscapeNaniteAsyncDebugWait - DurationSeconds, 0.0); ExtraWait > 0.0)
|
|
{
|
|
FPlatformProcess::Sleep(ExtraWait);
|
|
}
|
|
|
|
}, TStatId(), nullptr,
|
|
ENamedThreads::AnyBackgroundHiPriTask);
|
|
|
|
FGraphEventArray CommitDependencies{ ExportMeshEvent };
|
|
|
|
FGraphEventRef BatchBuildEvent = FFunctionGraphTask::CreateAndDispatchWhenReady([AsyncBuildData, Component = this, NewProxyContentId, Name = Landscape->GetActorNameOrLabel(), StaticMeshBuildCompleteEvent, InNaniteComponentIndex]()
|
|
{
|
|
auto OnFinishTask = [StaticMeshBuildCompleteEvent, AsyncBuildData]()
|
|
{
|
|
if (AsyncBuildData->LandscapeSubSystemWeakRef.IsValid())
|
|
{
|
|
AsyncBuildData->LandscapeSubSystemWeakRef->DecNaniteBuild();
|
|
}
|
|
StaticMeshBuildCompleteEvent->DispatchSubsequents();
|
|
};
|
|
|
|
AsyncBuildData->TimeStamp_StaticMeshBuildStart = FPlatformTime::Seconds();
|
|
|
|
if (AsyncBuildData->bCancelled || !AsyncBuildData->LandscapeWeakRef.IsValid())
|
|
{
|
|
AsyncBuildData->TimeStamp_StaticMeshBuildEnd = AsyncBuildData->TimeStamp_StaticMeshBuildStart;
|
|
UE_LOG(LogLandscape, Verbose, TEXT("CANCELLED Build Static Mesh '%s'"), *Name);
|
|
OnFinishTask();
|
|
if (AsyncBuildData->TimeStamp_Cancelled < 0.0)
|
|
{
|
|
AsyncBuildData->TimeStamp_Cancelled = FPlatformTime::Seconds();
|
|
}
|
|
return;
|
|
}
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeNaniteComponent::ExportLandscapeAsync-BatchBuildTask);
|
|
AsyncBuildData->NaniteStaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion;
|
|
|
|
UE_LOG(LogLandscape, VeryVerbose, TEXT("Build Static Mesh '%s' package:'%s'"), *Name, *AsyncBuildData->LandscapeWeakRef->GetPackage()->GetName());
|
|
auto CompleteStaticMesh = [AsyncBuildData, Component, NewProxyContentId, Name, InNaniteComponentIndex, OnFinishTask](UStaticMesh* InStaticMesh)
|
|
{
|
|
check(IsInGameThread());
|
|
check(AsyncBuildData->NaniteStaticMesh.Get() == InStaticMesh);
|
|
|
|
// ensure we always remove our PostMeshBuild delegate before returning
|
|
ON_SCOPE_EXIT
|
|
{
|
|
// we need to do this at the very end (otherwise we end up deleting the lambda we are in and making captured data inaccessible...)
|
|
// even at the end, this is a little bit suspect...
|
|
if (AsyncBuildData->PostMeshBuildDelegateHandle.IsValid())
|
|
{
|
|
FDelegateHandle DelegateHandleToRemove = AsyncBuildData->PostMeshBuildDelegateHandle;
|
|
AsyncBuildData->PostMeshBuildDelegateHandle.Reset(); // have to reset before calling Remove, as Remove will bork AsyncBuildData..
|
|
InStaticMesh->OnPostMeshBuild().Remove(DelegateHandleToRemove);
|
|
}
|
|
};
|
|
|
|
if (AsyncBuildData->bStaticMeshNeedsToCallPostMeshBuild)
|
|
{
|
|
AsyncBuildData->TimeStamp_StaticMeshBatchBuildPostMeshBuildCall = FPlatformTime::Seconds();
|
|
UE_LOG(LogLandscape, Verbose, TEXT("Called CompleteStaticMesh from PostMeshBuild for %s"), *Name);
|
|
AsyncBuildData->bStaticMeshNeedsToCallPostMeshBuild = false;
|
|
check(AsyncBuildData->PostMeshBuildDelegateHandle.IsValid()); // will be removed on scope exit
|
|
}
|
|
|
|
AsyncBuildData->TimeStamp_LandscapeUpdateStart = FPlatformTime::Seconds();
|
|
|
|
// this is as horror as we have to mark all the objects created in the background thread as not async
|
|
AsyncBuildData->NaniteStaticMesh->ClearInternalFlags(EInternalObjectFlags::Async);
|
|
AsyncBuildData->NaniteStaticMesh->AssetImportData->ClearInternalFlags(EInternalObjectFlags::Async);
|
|
|
|
AsyncBuildData->NaniteStaticMesh->GetHiResSourceModel().StaticMeshDescriptionBulkData->ClearInternalFlags(EInternalObjectFlags::Async);
|
|
AsyncBuildData->NaniteStaticMesh->GetHiResSourceModel().StaticMeshDescriptionBulkData->CreateMeshDescription()->ClearInternalFlags(EInternalObjectFlags::Async);
|
|
|
|
AsyncBuildData->NaniteStaticMesh->GetSourceModel(0).StaticMeshDescriptionBulkData->ClearInternalFlags(EInternalObjectFlags::Async);
|
|
AsyncBuildData->NaniteStaticMesh->GetSourceModel(0).StaticMeshDescriptionBulkData->GetMeshDescription()->ClearInternalFlags(EInternalObjectFlags::Async);
|
|
|
|
if (AsyncBuildData->bCancelled || !AsyncBuildData->LandscapeWeakRef.IsValid())
|
|
{
|
|
if (AsyncBuildData->TimeStamp_Cancelled < 0.0)
|
|
{
|
|
AsyncBuildData->TimeStamp_Cancelled = FPlatformTime::Seconds();
|
|
}
|
|
OnFinishTask();
|
|
AsyncBuildData->TimeStamp_LandscapeUpdateEnd = FPlatformTime::Seconds();
|
|
return;
|
|
}
|
|
|
|
check(AsyncBuildData->NaniteStaticMesh.Get() == InStaticMesh);
|
|
|
|
// Proxy has been updated since and this nanite calculation is out of date.
|
|
if (AsyncBuildData->LandscapeWeakRef->GetNaniteContentId() != NewProxyContentId)
|
|
{
|
|
AsyncBuildData->bIsComplete = true;
|
|
AsyncBuildData->TimeStamp_Complete = FPlatformTime::Seconds();
|
|
OnFinishTask();
|
|
AsyncBuildData->TimeStamp_LandscapeUpdateEnd = FPlatformTime::Seconds();
|
|
return;
|
|
}
|
|
|
|
AsyncBuildData->NaniteStaticMesh->MarkPackageDirty();
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeNaniteComponent::ExportLandscapeAsync - FinalizeOnComponent);
|
|
|
|
InStaticMesh->CreateBodySetup();
|
|
if (UBodySetup* BodySetup = InStaticMesh->GetBodySetup())
|
|
{
|
|
BodySetup->DefaultInstance.SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
|
|
BodySetup->CollisionTraceFlag = CTF_UseSimpleAsComplex;
|
|
// We won't ever enable collisions (since collisions are handled by ULandscapeHeightfieldCollisionComponent), ensure we don't even cook or load any collision data on this mesh:
|
|
BodySetup->bNeverNeedsCookedCollisionData = true;
|
|
}
|
|
|
|
Component->SetStaticMesh(InStaticMesh);
|
|
AsyncBuildData->NaniteStaticMesh.Reset(); // Release the strong pointer. The component owns it now.
|
|
Component->SetProxyContentId(NewProxyContentId);
|
|
Component->SetEnabled(!Component->IsEnabled());
|
|
|
|
// Nanite Component should remember which ULandscapeComponents it was generated from if we need to update materials.
|
|
Component->SourceLandscapeComponents = AsyncBuildData->InputComponents;
|
|
|
|
AsyncBuildData->LandscapeWeakRef->UpdateRenderingMethod();
|
|
AsyncBuildData->LandscapeWeakRef->NaniteComponents[InNaniteComponentIndex]->MarkRenderStateDirty();
|
|
AsyncBuildData->LandscapeWeakRef->NaniteComponents[InNaniteComponentIndex] = Component;
|
|
AsyncBuildData->bIsComplete = true;
|
|
AsyncBuildData->TimeStamp_Complete = FPlatformTime::Seconds();
|
|
|
|
UE_LOG(LogLandscape, VeryVerbose, TEXT("Complete Static Mesh '%s' package:'%s'"), *Name, *AsyncBuildData->LandscapeWeakRef->GetPackage()->GetName());
|
|
AsyncBuildData->TimeStamp_LandscapeUpdateEnd = FPlatformTime::Seconds();
|
|
|
|
OnFinishTask();
|
|
};
|
|
|
|
// when static mesh build is complete, call CompleteStaticMesh
|
|
AsyncBuildData->bStaticMeshNeedsToCallPostMeshBuild = true;
|
|
UE_LOG(LogLandscape, VeryVerbose, TEXT("Attaching to PostMeshBuild for %s"), *Name);
|
|
|
|
AsyncBuildData->PostMeshBuildDelegateHandle =
|
|
AsyncBuildData->NaniteStaticMesh->OnPostMeshBuild().AddLambda(CompleteStaticMesh);
|
|
|
|
TPolygonGroupAttributesRef<FName> PolygonGroupMaterialSlotNames = AsyncBuildData->MeshAttributes->GetPolygonGroupMaterialSlotNames();
|
|
int32 ComponentIndex = 0;
|
|
for (UMaterialInterface* Material : AsyncBuildData->InputMaterials)
|
|
{
|
|
check(Material != nullptr);
|
|
const FName MaterialSlotName = AsyncBuildData->InputMaterialSlotNames[ComponentIndex];
|
|
check(PolygonGroupMaterialSlotNames.GetRawArray().Contains(MaterialSlotName));
|
|
AsyncBuildData->NaniteStaticMesh->GetStaticMaterials().Add(FStaticMaterial(Material, MaterialSlotName));
|
|
++ComponentIndex;
|
|
}
|
|
|
|
AsyncBuildData->NaniteStaticMesh->MarkAsNotHavingNavigationData();
|
|
|
|
AsyncBuildData->TimeStamp_StaticMeshBatchBuildStart = FPlatformTime::Seconds();
|
|
UStaticMesh::FBuildParameters BuildParameters;
|
|
BuildParameters.bInSilent = true;
|
|
UStaticMesh::BatchBuild({ AsyncBuildData->NaniteStaticMesh.Get() }, BuildParameters);
|
|
|
|
AsyncBuildData->TimeStamp_StaticMeshBuildEnd = FPlatformTime::Seconds();
|
|
},
|
|
TStatId(),
|
|
& CommitDependencies,
|
|
ENamedThreads::GameThread);
|
|
|
|
return StaticMeshBuildCompleteEvent;
|
|
}
|
|
|
|
void ULandscapeNaniteComponent::UpdateMaterials()
|
|
{
|
|
if ( !GetLandscapeActor() || !GetLandscapeActor()->IsNaniteEnabled() || !GetStaticMesh())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (GetStaticMesh()->GetStaticMaterials().Num() < SourceLandscapeComponents.Num())
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool bApplyResults = false;
|
|
|
|
// Re-use existing static materials
|
|
TArray<FStaticMaterial> StaticMaterials = GetStaticMesh()->GetStaticMaterials();
|
|
bool bMaterialsRequiredUpdate = false;
|
|
TArray<TObjectPtr<ULandscapeComponent>>& LandscapeComponents = GetLandscapeProxy()->LandscapeComponents;
|
|
for (int32 SourceComponentIndex = 0; SourceComponentIndex < SourceLandscapeComponents.Num(); ++SourceComponentIndex)
|
|
{
|
|
TObjectPtr<ULandscapeComponent>* SourceLandscapeComponent = Algo::Find(LandscapeComponents, SourceLandscapeComponents[SourceComponentIndex]);
|
|
if (SourceLandscapeComponent && (*SourceLandscapeComponent)->GetMaterialInstanceCount() > 0)
|
|
{
|
|
StaticMaterials[SourceComponentIndex].MaterialInterface = (*SourceLandscapeComponent)->GetMaterialInstance(0);
|
|
bApplyResults = true;
|
|
}
|
|
}
|
|
|
|
if (bApplyResults)
|
|
{
|
|
GetStaticMesh()->SetStaticMaterials(StaticMaterials);
|
|
|
|
#if WITH_EDITOR
|
|
bool bRequiresPostEdit = !GetStaticMesh()->HasAnyFlags(RF_NeedPostLoad) && !HasAnyFlags(RF_NeedPostLoad) && !HasAnyFlags(RF_NeedPostLoadSubobjects);
|
|
if (bRequiresPostEdit)
|
|
{
|
|
if (GetStaticMesh()->IsCompiling())
|
|
{
|
|
FStaticMeshCompilingManager::Get().FinishCompilation({ GetStaticMesh() });
|
|
}
|
|
GetStaticMesh()->PostEditChange();
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void ULandscapeNaniteComponent::SetSourceLandscapeComponents(const TArray<ULandscapeComponent*>& InSourceLandscapeComponents)
|
|
{
|
|
SourceLandscapeComponents.Reset();
|
|
Algo::Transform(InSourceLandscapeComponents, SourceLandscapeComponents, [](ULandscapeComponent* Component){return TObjectPtr<ULandscapeComponent>(Component);});
|
|
UpdateMaterials();
|
|
}
|
|
|
|
bool ULandscapeNaniteComponent::InitializeForLandscape(ALandscapeProxy* Landscape, const FGuid& NewProxyContentId, const TArray<ULandscapeComponent*>& InComponentsToExport, int32 InNaniteComponentIndex)
|
|
{
|
|
FGraphEventRef GraphEvent = InitializeForLandscapeAsync(Landscape, NewProxyContentId, InComponentsToExport, InNaniteComponentIndex);
|
|
|
|
UWorld* World = Landscape->GetWorld();
|
|
ULandscapeSubsystem* LandscapeSubsystem = World->GetSubsystem<ULandscapeSubsystem>();
|
|
check(LandscapeSubsystem);
|
|
const bool bAllNaniteBuildsDone = LandscapeSubsystem->FinishAllNaniteBuildsInFlightNow(ULandscapeSubsystem::EFinishAllNaniteBuildsInFlightFlags::Default);
|
|
// Not passing ULandscapeSubsystem::EFinishAllNaniteBuildsInFlightFlags::AllowCancel, so there should be no way that FinishAllNaniteBuildsInFlightNow returns false :
|
|
check(bAllNaniteBuildsDone && GraphEvent->IsComplete());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ULandscapeNaniteComponent::InitializePlatformForLandscape(ALandscapeProxy* Landscape, const ITargetPlatform* TargetPlatform)
|
|
{
|
|
|
|
UE_LOG(LogLandscape, Verbose, TEXT("InitializePlatformForLandscape '%s' package:'%s'"), *Landscape->GetActorNameOrLabel(), *Landscape->GetPackage()->GetName());
|
|
|
|
// This is a workaround. IsCachedCookedPlatformDataLoaded needs to return true to ensure that StreamablePages are loaded from DDC
|
|
if (TargetPlatform)
|
|
{
|
|
UE_LOG(LogLandscape, Verbose, TEXT("InitializePlatformForLandscape '%s' platform:'%s'"), *Landscape->GetActorNameOrLabel(), *TargetPlatform->DisplayName().ToString());
|
|
if (UStaticMesh* NaniteStaticMesh = GetStaticMesh())
|
|
{
|
|
UE_LOG(LogLandscape, Verbose, TEXT("InitializePlatformForLandscape '%s' mesh:'%p'"), *Landscape->GetActorNameOrLabel(), NaniteStaticMesh);
|
|
NaniteStaticMesh->BeginCacheForCookedPlatformData(TargetPlatform);
|
|
FStaticMeshCompilingManager::Get().FinishCompilation({ NaniteStaticMesh });
|
|
|
|
const double StartTime = FPlatformTime::Seconds();
|
|
|
|
while (!NaniteStaticMesh->IsCachedCookedPlatformDataLoaded(TargetPlatform))
|
|
{
|
|
FAssetCompilingManager::Get().ProcessAsyncTasks(true);
|
|
FPlatformProcess::Sleep(0.01);
|
|
|
|
constexpr double MaxWaitSeconds = 240.0;
|
|
if (FPlatformTime::Seconds() - StartTime > MaxWaitSeconds)
|
|
{
|
|
UE_LOG(LogLandscape, Error, TEXT("ULandscapeNaniteComponent::InitializePlatformForLandscape waited more than %f seconds for IsCachedCookedPlatformDataLoaded to return true"), MaxWaitSeconds);
|
|
return false;
|
|
}
|
|
}
|
|
UE_LOG(LogLandscape, Verbose, TEXT("InitializePlatformForLandscape '%s' Finished in %f"), *Landscape->GetActorNameOrLabel(), FPlatformTime::Seconds() - StartTime);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|