Files
UnrealEngine/Engine/Source/Runtime/Landscape/Private/LandscapeGrassWeightExporter.cpp
2025-05-18 13:04:45 +08:00

876 lines
31 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LandscapeGrassWeightExporter.h"
#include "SceneRendererInterface.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "Materials/Material.h"
#include "LandscapeGrassType.h"
#include "Engine/TextureRenderTarget2D.h"
#include "EngineModule.h"
#include "LandscapeRender.h"
#include "MeshPassProcessor.h"
#include "MeshPassProcessor.inl"
#include "SimpleMeshDrawCommandPass.h"
#include "TextureResource.h"
#include "RenderCaptureInterface.h"
#include "ShaderPlatformCachedIniValue.h"
#include "LandscapePrivate.h"
#include "LandscapeAsyncTextureReadback.h"
#if UE_BUILD_DEBUG
#include "Misc/FileHelper.h"
#endif
class FLandscapeGrassWeightVS;
class FLandscapeGrassWeightPS;
int32 GRenderCaptureNextGrassmapDraws = 0;
static FAutoConsoleVariableRef CVarRenderCaptureNextGrassmapDraws(
TEXT("grass.GrassMap.RenderCaptureNextDraws"),
GRenderCaptureNextGrassmapDraws,
TEXT("Trigger render captures during the next N grassmap draw calls."));
extern int32 GGrassMapAlwaysBuildRuntimeGenerationResources;
extern int32 GGrassMapUseRuntimeGeneration;
BEGIN_SHADER_PARAMETER_STRUCT(FLandscapeGrassPassParameters, )
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSceneUniformParameters, Scene)
SHADER_PARAMETER_STRUCT_INCLUDE(FInstanceCullingDrawParams, InstanceCullingDrawParams)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
class FLandscapeGrassWeightShaderElementData : public FMeshMaterialShaderElementData
{
public:
int32 OutputPass;
FVector2f RenderOffset;
};
extern int32 GGrassEnable;
static bool ShouldCacheLandscapeGrassShaders(const FMeshMaterialShaderPermutationParameters& Parameters)
{
const bool bIsEditorPlatform =
IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5) &&
EnumHasAllFlags(Parameters.Flags, EShaderPermutationFlags::HasEditorOnlyData);
#if WITH_EDITOR
static FShaderPlatformCachedIniValue<int32> GrassMapsUseRuntimeGenerationPerPlatform(TEXT("grass.GrassMap.UseRuntimeGeneration"));
const bool bPlatformUsesRuntimeGen = (GrassMapsUseRuntimeGenerationPerPlatform.Get(Parameters.Platform) != 0);
#else
const bool bPlatformUsesRuntimeGen = (GGrassMapUseRuntimeGeneration != 0);
#endif // WITH_EDITOR
#if WITH_EDITOR
static FShaderPlatformCachedIniValue<int32> GrassEnabledPerPlatform(TEXT("grass.Enable"));
const bool bGrassEnable = (GrassEnabledPerPlatform.Get(Parameters.Platform) != 0);
#else
const bool bGrassEnable = GGrassEnable != 0;
#endif // WITH_EDITOR
const bool bShouldBuildForPlatform = GGrassMapAlwaysBuildRuntimeGenerationResources || (
bGrassEnable && (bIsEditorPlatform || bPlatformUsesRuntimeGen));
const bool bIsFixedGridVertexFactory =
Parameters.VertexFactoryType == FindVertexFactoryType(FName(TEXT("FLandscapeFixedGridVertexFactory"), FNAME_Find));
// We only need grass weight shaders for Landscape fixed grid vertex factories
// And only for platforms that have runtime generation enabled or are editor platforms (or if we are always building resources)
const bool bIsLandscapeRelated = (Parameters.MaterialParameters.bIsUsedWithLandscape || Parameters.MaterialParameters.bIsSpecialEngineMaterial);
const bool bShouldCache =
bIsLandscapeRelated &&
bIsFixedGridVertexFactory &&
bShouldBuildForPlatform;
return bShouldCache;
}
class FLandscapeGrassWeightVS : public FMeshMaterialShader
{
DECLARE_SHADER_TYPE(FLandscapeGrassWeightVS, MeshMaterial);
LAYOUT_FIELD(FShaderParameter, RenderOffsetParameter);
protected:
FLandscapeGrassWeightVS()
{}
FLandscapeGrassWeightVS(const FMeshMaterialShaderType::CompiledShaderInitializerType& Initializer)
: FMeshMaterialShader(Initializer)
{
RenderOffsetParameter.Bind(Initializer.ParameterMap, TEXT("RenderOffset"));
PassUniformBuffer.Bind(Initializer.ParameterMap, FSceneTextureUniformParameters::FTypeInfo::GetStructMetadata()->GetShaderVariableName());
}
public:
static bool ShouldCompilePermutation(const FMeshMaterialShaderPermutationParameters& Parameters)
{
return ShouldCacheLandscapeGrassShaders(Parameters);
}
void GetShaderBindings(
const FScene* Scene,
ERHIFeatureLevel::Type FeatureLevel,
const FPrimitiveSceneProxy* PrimitiveSceneProxy,
const FMaterialRenderProxy& MaterialRenderProxy,
const FMaterial& Material,
const FLandscapeGrassWeightShaderElementData& ShaderElementData,
FMeshDrawSingleShaderBindings& ShaderBindings) const
{
FMeshMaterialShader::GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, Material, ShaderElementData, ShaderBindings);
ShaderBindings.Add(RenderOffsetParameter, ShaderElementData.RenderOffset);
}
};
IMPLEMENT_MATERIAL_SHADER_TYPE(, FLandscapeGrassWeightVS, TEXT("/Engine/Private/LandscapeGrassWeight.usf"), TEXT("VSMain"), SF_Vertex);
class FLandscapeGrassWeightPS : public FMeshMaterialShader
{
DECLARE_SHADER_TYPE(FLandscapeGrassWeightPS, MeshMaterial);
LAYOUT_FIELD(FShaderParameter, OutputPassParameter);
public:
static bool ShouldCompilePermutation(const FMeshMaterialShaderPermutationParameters& Parameters)
{
return ShouldCacheLandscapeGrassShaders(Parameters);
}
FLandscapeGrassWeightPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FMeshMaterialShader(Initializer)
{
OutputPassParameter.Bind(Initializer.ParameterMap, TEXT("OutputPass"));
PassUniformBuffer.Bind(Initializer.ParameterMap, FSceneTextureUniformParameters::FTypeInfo::GetStructMetadata()->GetShaderVariableName());
}
FLandscapeGrassWeightPS()
{}
void GetShaderBindings(
const FScene* Scene,
ERHIFeatureLevel::Type FeatureLevel,
const FPrimitiveSceneProxy* PrimitiveSceneProxy,
const FMaterialRenderProxy& MaterialRenderProxy,
const FMaterial& Material,
const FLandscapeGrassWeightShaderElementData& ShaderElementData,
FMeshDrawSingleShaderBindings& ShaderBindings) const
{
FMeshMaterialShader::GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, Material, ShaderElementData, ShaderBindings);
ShaderBindings.Add(OutputPassParameter, ShaderElementData.OutputPass);
}
};
IMPLEMENT_MATERIAL_SHADER_TYPE(, FLandscapeGrassWeightPS, TEXT("/Engine/Private/LandscapeGrassWeight.usf"), TEXT("PSMain"), SF_Pixel);
void UE::Landscape::Grass::AddGrassWeightShaderTypes(FMaterialShaderTypes& InOutShaderTypes)
{
InOutShaderTypes.AddShaderType<FLandscapeGrassWeightVS>();
InOutShaderTypes.AddShaderType<FLandscapeGrassWeightPS>();
}
class FLandscapeGrassWeightMeshProcessor : public FMeshPassProcessor
{
public:
FLandscapeGrassWeightMeshProcessor(const FScene* Scene, ERHIFeatureLevel::Type InFeatureLevel, const FSceneView* InViewIfDynamicMeshCommand, FMeshPassDrawListContext* InDrawListContext);
void AddMeshBatch(const FMeshBatch& RESTRICT MeshBatch,
uint64 BatchElementMask,
int32 NumPasses,
FVector2D ViewOffset,
float PassOffsetX,
int32 FirstHeightMipsPassIndex,
const TArray<int32>& HeightMips,
const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy);
virtual void AddMeshBatch(const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId = -1) override final
{
checkf(false, TEXT("Default AddMeshBatch can't be used as rendering requires extra parameters per pass."));
}
virtual void CollectPSOInitializers(const FSceneTexturesConfig& SceneTexturesConfig,
const FMaterial& Material,
const FPSOPrecacheVertexFactoryData& VertexFactoryData,
const FPSOPrecacheParams& PreCacheParams,
TArray<FPSOPrecacheData>& PSOInitializers) override final;
private:
bool TryAddMeshBatch(
const FMeshBatch& RESTRICT MeshBatch,
uint64 BatchElementMask,
const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
int32 StaticMeshId,
const FMaterialRenderProxy& MaterialRenderProxy,
const FMaterial& MaterialResource,
int32 NumPasses,
FVector2D ViewOffset,
float PassOffsetX,
int32 FirstHeightMipsPassIndex,
const TArray<int32>& HeightMips);
bool Process(
const FMeshBatch& MeshBatch,
uint64 BatchElementMask,
const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
const FMaterialRenderProxy& RESTRICT MaterialRenderProxy,
const FMaterial& RESTRICT MaterialResource,
int32 NumPasses,
FVector2D ViewOffset,
float PassOffsetX,
int32 FirstHeightMipsPassIndex,
const TArray<int32>& HeightMips);
FMeshPassProcessorRenderState PassDrawRenderState;
};
static const TCHAR* LandscapeGrassWeightMeshPassName = TEXT("LandscapeGrassWeight");
FLandscapeGrassWeightMeshProcessor::FLandscapeGrassWeightMeshProcessor(const FScene* Scene, ERHIFeatureLevel::Type InFeatureLevel, const FSceneView* InViewIfDynamicMeshCommand, FMeshPassDrawListContext* InDrawListContext)
: FMeshPassProcessor(LandscapeGrassWeightMeshPassName, Scene, InFeatureLevel, InViewIfDynamicMeshCommand, InDrawListContext)
{
PassDrawRenderState.SetBlendState(TStaticBlendState<>::GetRHI());
PassDrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_Always>::GetRHI());
}
void FLandscapeGrassWeightMeshProcessor::AddMeshBatch(const FMeshBatch& RESTRICT MeshBatch,
uint64 BatchElementMask,
int32 NumPasses,
FVector2D ViewOffset,
float PassOffsetX,
int32 FirstHeightMipsPassIndex,
const TArray<int32>& HeightMips,
const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy)
{
const FMaterialRenderProxy* MaterialRenderProxy = MeshBatch.MaterialRenderProxy;
while (MaterialRenderProxy)
{
const FMaterial* Material = MaterialRenderProxy->GetMaterialNoFallback(FeatureLevel);
if (Material)
{
if (TryAddMeshBatch(MeshBatch, BatchElementMask, PrimitiveSceneProxy, -1, *MaterialRenderProxy, *Material, NumPasses, ViewOffset, PassOffsetX, FirstHeightMipsPassIndex, HeightMips))
{
break;
}
}
MaterialRenderProxy = MaterialRenderProxy->GetFallback(FeatureLevel);
}
}
bool FLandscapeGrassWeightMeshProcessor::TryAddMeshBatch(
const FMeshBatch& RESTRICT MeshBatch,
uint64 BatchElementMask,
const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
int32 StaticMeshId,
const FMaterialRenderProxy& MaterialRenderProxy,
const FMaterial& MaterialResource,
int32 NumPasses,
FVector2D ViewOffset,
float PassOffsetX,
int32 FirstHeightMipsPassIndex,
const TArray<int32>& HeightMips)
{
check(MeshBatch.VertexFactory != nullptr);
return Process(MeshBatch, BatchElementMask, PrimitiveSceneProxy, MaterialRenderProxy, MaterialResource, NumPasses, ViewOffset, PassOffsetX, FirstHeightMipsPassIndex, HeightMips);
}
bool FLandscapeGrassWeightMeshProcessor::Process(
const FMeshBatch& MeshBatch,
uint64 BatchElementMask,
const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
const FMaterialRenderProxy& RESTRICT MaterialRenderProxy,
const FMaterial& RESTRICT MaterialResource,
int32 NumPasses,
FVector2D ViewOffset,
float PassOffsetX,
int32 FirstHeightMipsPassIndex,
const TArray<int32>& HeightMips)
{
const FVertexFactory* VertexFactory = MeshBatch.VertexFactory;
FMaterialShaderTypes ShaderTypes;
ShaderTypes.AddShaderType<FLandscapeGrassWeightVS>();
ShaderTypes.AddShaderType<FLandscapeGrassWeightPS>();
FMaterialShaders Shaders;
if (!MaterialResource.TryGetShaders(ShaderTypes, VertexFactory->GetType(), Shaders))
{
return false;
}
TMeshProcessorShaders<
FLandscapeGrassWeightVS,
FLandscapeGrassWeightPS> PassShaders;
Shaders.TryGetVertexShader(PassShaders.VertexShader);
Shaders.TryGetPixelShader(PassShaders.PixelShader);
const FMeshDrawingPolicyOverrideSettings OverrideSettings = ComputeMeshOverrideSettings(MeshBatch);
const ERasterizerFillMode MeshFillMode = ComputeMeshFillMode(MaterialResource, OverrideSettings);
const ERasterizerCullMode MeshCullMode = CM_None;
FLandscapeGrassWeightShaderElementData ShaderElementData;
ShaderElementData.InitializeMeshMaterialData(ViewIfDynamicMeshCommand, PrimitiveSceneProxy, MeshBatch, -1, true);
const FMeshDrawCommandSortKey SortKey = CalculateMeshStaticSortKey(PassShaders.VertexShader, PassShaders.PixelShader);
for (int32 PassIndex = 0; PassIndex < NumPasses; ++PassIndex)
{
ShaderElementData.OutputPass = (PassIndex >= FirstHeightMipsPassIndex) ? 0 : PassIndex;
ShaderElementData.RenderOffset = FVector2f(ViewOffset) + FVector2f(PassOffsetX * PassIndex, 0); // LWC_TODO: Precision loss
uint64 Mask = (PassIndex >= FirstHeightMipsPassIndex) ? HeightMips[PassIndex - FirstHeightMipsPassIndex] : BatchElementMask;
BuildMeshDrawCommands(
MeshBatch,
Mask,
PrimitiveSceneProxy,
MaterialRenderProxy,
MaterialResource,
PassDrawRenderState,
PassShaders,
MeshFillMode,
MeshCullMode,
SortKey,
EMeshPassFeatures::Default,
ShaderElementData);
}
return true;
}
void FLandscapeGrassWeightMeshProcessor::CollectPSOInitializers(const FSceneTexturesConfig& SceneTexturesConfig, const FMaterial& Material, const FPSOPrecacheVertexFactoryData& VertexFactoryData, const FPSOPrecacheParams& PreCacheParams, TArray<FPSOPrecacheData>& PSOInitializers)
{
// Only support the Landscape fixed grid vertex factory type.
if (VertexFactoryData.VertexFactoryType != &FLandscapeFixedGridVertexFactory::StaticType)
{
return;
}
FMaterialShaderTypes ShaderTypes;
ShaderTypes.AddShaderType<FLandscapeGrassWeightVS>();
ShaderTypes.AddShaderType<FLandscapeGrassWeightPS>();
FMaterialShaders Shaders;
if (!Material.TryGetShaders(ShaderTypes, VertexFactoryData.VertexFactoryType, Shaders))
{
return;
}
TMeshProcessorShaders<
FLandscapeGrassWeightVS,
FLandscapeGrassWeightPS> PassShaders;
Shaders.TryGetVertexShader(PassShaders.VertexShader);
Shaders.TryGetPixelShader(PassShaders.PixelShader);
const FMeshDrawingPolicyOverrideSettings OverrideSettings = ComputeMeshOverrideSettings(PreCacheParams);
const ERasterizerFillMode MeshFillMode = ComputeMeshFillMode(Material, OverrideSettings);
const ERasterizerCullMode MeshCullMode = CM_None;
FGraphicsPipelineRenderTargetsInfo RenderTargetsInfo;
RenderTargetsInfo.NumSamples = 1;
AddRenderTargetInfo(PF_B8G8R8A8, ETextureCreateFlags::RenderTargetable, RenderTargetsInfo);
AddGraphicsPipelineStateInitializer(
VertexFactoryData,
Material,
PassDrawRenderState,
RenderTargetsInfo,
PassShaders,
MeshFillMode,
MeshCullMode,
PT_PointList,
EMeshPassFeatures::Default,
true /*bRequired*/,
PSOInitializers);
}
IPSOCollector* CreateLandscapeGrassWeightPSOCollector(ERHIFeatureLevel::Type FeatureLevel)
{
return new FLandscapeGrassWeightMeshProcessor(nullptr, FeatureLevel, nullptr, nullptr);
}
FRegisterPSOCollectorCreateFunction RegisterLandscapeGrassWeightPSOCollector(&CreateLandscapeGrassWeightPSOCollector, EShadingPath::Deferred, LandscapeGrassWeightMeshPassName);
FRegisterPSOCollectorCreateFunction RegisterMobileLandscapeGrassWeightPSOCollector(&CreateLandscapeGrassWeightPSOCollector, EShadingPath::Mobile, LandscapeGrassWeightMeshPassName);
FLandscapeGrassWeightExporter_RenderThread::FLandscapeGrassWeightExporter_RenderThread(const TArray<int32>& InHeightMips, bool bInReadbackToCPU)
: HeightMips(InHeightMips)
{
if (bInReadbackToCPU)
{
// even when doing a synchronous readback, we use the async readback structure
AsyncReadbackPtr = new FLandscapeAsyncTextureReadback();
}
}
FLandscapeGrassWeightExporter_RenderThread::~FLandscapeGrassWeightExporter_RenderThread()
{
if (AsyncReadbackPtr != nullptr)
{
AsyncReadbackPtr->QueueDeletionFromGameThread();
AsyncReadbackPtr = nullptr;
}
}
void FLandscapeGrassWeightExporter_RenderThread::RenderLandscapeComponentToTexture_RenderThread(FRHICommandListImmediate& RHICmdList)
{
FRDGBuilder GraphBuilder(RHICmdList);
FRDGTextureDesc TextureDesc = FRDGTextureDesc::Create2D(
TargetSize,
PF_B8G8R8A8,
FClearValueBinding(),
ETextureCreateFlags::RenderTargetable);
FRDGTextureRef OutputTexture = GraphBuilder.CreateTexture(TextureDesc, TEXT("LandscapeGrassMapRenderTarget"), ERDGTextureFlags::None);
RenderLandscapeComponentToTexture_RenderThread(GraphBuilder, OutputTexture);
if (AsyncReadbackPtr != nullptr)
{
AsyncReadbackPtr->StartReadback_RenderThread(GraphBuilder, OutputTexture);
}
GraphBuilder.Execute();
}
void FLandscapeGrassWeightExporter_RenderThread::RenderLandscapeComponentToTexture_RenderThread(FRDGBuilder& GraphBuilder, FRDGTextureRef OutputTexture)
{
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(nullptr, SceneInterface, FEngineShowFlags(ESFIM_Game))
.SetTime(FGameTime::GetTimeSinceAppStart()));
ViewFamily.LandscapeLODOverride = 0; // Force LOD render
// Ensure scene primitive rendering is valid (added primitives comitted, GPU-Scene updated, push/pop dynamic culling context).
FScenePrimitiveRenderingContextScopeHelper ScenePrimitiveRenderingContextScopeHelper(GetRendererModule().BeginScenePrimitiveRendering(GraphBuilder, &ViewFamily));
FSceneViewInitOptions ViewInitOptions;
ViewInitOptions.SetViewRectangle(FIntRect(0, 0, TargetSize.X, TargetSize.Y));
ViewInitOptions.ViewOrigin = ViewOrigin;
ViewInitOptions.ViewRotationMatrix = ViewRotationMatrix;
ViewInitOptions.ProjectionMatrix = ProjectionMatrix;
ViewInitOptions.ViewFamily = &ViewFamily;
GetRendererModule().CreateAndInitSingleView(GraphBuilder.RHICmdList, &ViewFamily, &ViewInitOptions);
const FSceneView* View = ViewFamily.Views[0];
auto* PassParameters = GraphBuilder.AllocParameters<FLandscapeGrassPassParameters>();
PassParameters->View = View->ViewUniformBuffer;
PassParameters->Scene = GetSceneUniformBufferRef(GraphBuilder, *View);
PassParameters->RenderTargets[0] = FRenderTargetBinding(OutputTexture, ERenderTargetLoadAction::EClear);
AddSimpleMeshPass(GraphBuilder, PassParameters, SceneInterface->GetRenderScene(), *View, nullptr, RDG_EVENT_NAME("LandscapeGrass"), View->UnscaledViewRect,
[&View, this](FDynamicPassMeshDrawListContext* DynamicMeshPassContext)
{
FLandscapeGrassWeightMeshProcessor PassMeshProcessor(
nullptr,
View->GetFeatureLevel(),
View,
DynamicMeshPassContext);
const uint64 DefaultBatchElementMask = 1 << 0; // LOD 0 only
for (auto& ComponentInfo : ComponentInfos)
{
if (ensure(ComponentInfo.SceneProxy))
{
const FMeshBatch& Mesh = ComponentInfo.SceneProxy->GetGrassMeshBatch();
Mesh.MaterialRenderProxy->UpdateUniformExpressionCacheIfNeeded(View->GetFeatureLevel());
PassMeshProcessor.AddMeshBatch(Mesh, DefaultBatchElementMask, ComponentInfo.NumPasses, ComponentInfo.ViewOffset, PassOffsetX, ComponentInfo.FirstHeightMipsPassIndex, HeightMips, ComponentInfo.SceneProxy);
}
}
});
}
FLandscapeGrassWeightExporter::FLandscapeGrassWeightExporter(ALandscapeProxy* InLandscapeProxy, TArrayView<ULandscapeComponent* const> InLandscapeComponents, bool bInNeedsGrassmap, bool bInNeedsHeightmap, const TArray<int32>& InHeightMips, bool bInRenderImmediately, bool bInReadbackToCPU)
: FLandscapeGrassWeightExporter_RenderThread(InHeightMips, bInReadbackToCPU)
, LandscapeProxy(InLandscapeProxy)
, ComponentSizeVerts(InLandscapeProxy->ComponentSizeQuads + 1)
, SubsectionSizeQuads(InLandscapeProxy->SubsectionSizeQuads)
, NumSubsections(InLandscapeProxy->NumSubsections)
{
check(InLandscapeComponents.Num() > 0);
SceneInterface = InLandscapeComponents[0]->GetScene();
// todo: use a 2d target?
const int32 SingleTileWidth = ComponentSizeVerts;
TargetSize = FIntPoint(0, ComponentSizeVerts);
// First compute the total render target size and prepare ComponentInfos (each component has its own number of needed passes because some might have different materials, thus different grass types) :
ComponentInfos.Reserve(InLandscapeComponents.Num());
int32 CurrentPixelOffsetX = 0;
for (ULandscapeComponent* Component : InLandscapeComponents)
{
ensure(Component->SceneProxy);
FComponentInfo& ComponentInfo = ComponentInfos.Add_GetRef(FComponentInfo(Component, bInNeedsGrassmap, bInNeedsHeightmap, InHeightMips));
ComponentInfo.PixelOffsetX = CurrentPixelOffsetX;
CurrentPixelOffsetX += ComponentInfo.NumPasses * SingleTileWidth;
}
TargetSize.X = CurrentPixelOffsetX;
FIntPoint TargetSizeMinusOne(TargetSize - FIntPoint(1, 1));
PassOffsetX = 2.0f * (float)SingleTileWidth / (float)TargetSize.X;
// Then compute FComponentInfo's ViewOffset with the knowledge of the total render target size :
for (FComponentInfo& ComponentInfo : ComponentInfos)
{
FIntPoint ComponentOffset = (ComponentInfo.Component->GetSectionBase() - LandscapeProxy->LandscapeSectionOffset);
FVector2D ViewOffset(-ComponentOffset.X, ComponentOffset.Y);
ViewOffset.X += ComponentInfo.PixelOffsetX;
ViewOffset /= (FVector2D(TargetSize) * 0.5f);
ComponentInfo.ViewOffset = ViewOffset;
}
// center of target area in world
FVector TargetCenter = LandscapeProxy->GetTransform().TransformPosition(FVector(TargetSizeMinusOne, 0.f) * 0.5f);
// extent of target in world space
FVector TargetExtent = FVector(TargetSize, 0.0f) * LandscapeProxy->GetActorScale() * 0.5f;
ViewOrigin = TargetCenter;
ViewRotationMatrix = FInverseRotationMatrix(LandscapeProxy->GetActorRotation());
ViewRotationMatrix *= FMatrix(FPlane(1.0f, 0.0f, 0.0f, 0.0f),
FPlane(0.0f, -1.0f, 0.0f, 0.0f),
FPlane(0.0f, 0.0f, -1.0f, 0.0f),
FPlane(0.0f, 0.0f, 0.0f, 1.0f));
const float ZOffset = UE_OLD_WORLD_MAX;
ProjectionMatrix = FReversedZOrthoMatrix(
TargetExtent.X,
TargetExtent.Y,
0.5f / ZOffset,
ZOffset);
if (bInRenderImmediately)
{
UE::RenderCommandPipe::FSyncScope SyncScope;
RenderCaptureInterface::FScopedCapture RenderCapture((GRenderCaptureNextGrassmapDraws != 0), TEXT("LandscapeGrassmapCapture"));
GRenderCaptureNextGrassmapDraws = FMath::Max(0, GRenderCaptureNextGrassmapDraws - 1);
// render
FLandscapeGrassWeightExporter_RenderThread* Exporter = this;
ENQUEUE_RENDER_COMMAND(FDrawSceneCommand)(
[Exporter](FRHICommandListImmediate& RHICmdList)
{
Exporter->RenderLandscapeComponentToTexture_RenderThread(RHICmdList);
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThreadFlushResources);
});
}
}
bool FLandscapeGrassWeightExporter::CheckAndUpdateAsyncReadback(bool& bOutRenderCommandsQueued, const bool bInForceFinish)
{
check(AsyncReadbackPtr != nullptr);
return AsyncReadbackPtr->CheckAndUpdate(bOutRenderCommandsQueued, bInForceFinish);
}
bool FLandscapeGrassWeightExporter::IsAsyncReadbackComplete()
{
check(AsyncReadbackPtr != nullptr);
return AsyncReadbackPtr->IsComplete();
}
struct FByteBuffer2DView : public IBuffer2DView<uint8>
{
uint8* BufferStart = nullptr;
int32 ByteStrideY = 0;
int32 ByteStrideX = 0;
int32 NumX = 0;
int32 NumY = 0;
// copy elements from buffer to Dest, in X then Y order
virtual void CopyTo(uint8* Dest, int32 SizeInBytes) const override
{
for (int Y = 0; SizeInBytes > 0 && Y < NumY; Y++)
{
uint8* Src = BufferStart + Y * ByteStrideY;
int32 CopyCountX = FMath::Min(SizeInBytes, NumX);
// we can't use memcpy because of the ByteStride
while (CopyCountX--)
{
*Dest = *Src;
Dest++;
Src += ByteStrideX;
}
SizeInBytes -= NumX;
}
}
// copy elements from buffer to Dest, in X then Y order, return true if the copied data is all zero
virtual bool CopyToAndCalcIsAllZero(uint8* Dest, int32 SizeInBytes) const override
{
uint8 MaxBits = 0;
for (int Y = 0; SizeInBytes > 0 && Y < NumY; Y++)
{
uint8* Src = BufferStart + Y * ByteStrideY;
int32 CopyCountX = FMath::Min(SizeInBytes, NumX);
// we can't use memcpy because of the ByteStride
while (CopyCountX--)
{
uint8 Value = *Src;
MaxBits = MaxBits | Value;
*Dest = Value;
Dest++;
Src += ByteStrideX;
}
SizeInBytes -= NumX;
}
return (MaxBits == 0);
}
virtual int32 Num() const override { return NumX * NumY; }
};
// FColor memory layout matches our BGRA GPU layout only on little endian CPUs!
#if PLATFORM_LITTLE_ENDIAN
#define BGRA_AS_FCOLOR_BLUE B
#define BGRA_AS_FCOLOR_GREEN G
#define BGRA_AS_FCOLOR_RED R
#define BGRA_AS_FCOLOR_ALPHA A
#else
#define BGRA_AS_FCOLOR_BLUE A
#define BGRA_AS_FCOLOR_GREEN R
#define BGRA_AS_FCOLOR_RED G
#define BGRA_AS_FCOLOR_ALPHA B
#endif // PLATFORM_LITTLE_ENDIAN
struct FHeightBuffer2DView : IBuffer2DView<uint16>
{
FColor* BufferStart = nullptr;
int32 StrideY = 0;
int32 NumX = 0;
int32 NumY = 0;
// copy elements from buffer to Dest, in X then Y order
virtual void CopyTo(uint16* Dest, int32 Count) const override
{
for (int y = 0; Count > 0 && y < NumY; y++)
{
FColor* Src = BufferStart + y * StrideY;
int32 CopyCountX = FMath::Min(Count, NumX);
while (CopyCountX--)
{
*Dest = (((uint16) Src->BGRA_AS_FCOLOR_RED) << 8) + (uint16)(Src->BGRA_AS_FCOLOR_GREEN);
Dest++;
Src++;
}
Count -= NumX;
}
}
// copy elements from buffer to Dest, in X then Y order
virtual bool CopyToAndCalcIsAllZero(uint16* Dest, int32 Count) const override
{
unimplemented()
return true;
}
virtual int32 Num() const override { return NumX * NumY; }
};
void FLandscapeGrassWeightExporter::FreeAsyncReadback()
{
check(AsyncReadbackPtr != nullptr);
AsyncReadbackPtr->QueueDeletionFromGameThread();
AsyncReadbackPtr = nullptr;
}
void FLandscapeGrassWeightExporter::CancelAndSelfDestruct()
{
check(AsyncReadbackPtr != nullptr);
// Cancel the readback, and queue destruction on the render thread
AsyncReadbackPtr->CancelAndSelfDestruct();
AsyncReadbackPtr = nullptr;
// Queue destruction of FLandscapeGrassWeightExporter, also on the render thread
FLandscapeGrassWeightExporter* Exporter = this;
ENQUEUE_RENDER_COMMAND(FCancelAndDestructCommand)(
[Exporter](FRHICommandListImmediate& RHICmdList)
{
check(Exporter->AsyncReadbackPtr == nullptr);
delete Exporter;
});
}
TMap<ULandscapeComponent*, TUniquePtr<FLandscapeComponentGrassData>, TInlineSetAllocator<1>> FLandscapeGrassWeightExporter::FetchResults(bool bFreeAsyncReadback)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FetchResults);
TMap<ULandscapeComponent*, TUniquePtr<FLandscapeComponentGrassData>, TInlineSetAllocator<1>> Results;
TArray<FColor> Samples;
check(AsyncReadbackPtr != nullptr);
{
FIntPoint Size;
Samples = AsyncReadbackPtr->TakeResults(&Size);
if (bFreeAsyncReadback)
{
FreeAsyncReadback();
}
check(Size == TargetSize);
}
Results.Reserve(ComponentInfos.Num());
FHeightBuffer2DView HeightData;
TMap<ULandscapeGrassType*, IBuffer2DView<uint8>*> WeightData;
for (auto& ComponentInfo : ComponentInfos)
{
ULandscapeComponent* Component = ComponentInfo.Component;
TUniquePtr<FLandscapeComponentGrassData> NewGrassData = MakeUnique<FLandscapeComponentGrassData>(Component);
int32 ComponentSizeVerts2 = FMath::Square(ComponentSizeVerts);
HeightData.NumX = ComponentSizeVerts;
HeightData.NumY = ComponentSizeVerts;
HeightData.StrideY = TargetSize.X;
#if WITH_EDITORONLY_DATA
NewGrassData->HeightMipData.Empty(HeightMips.Num());
#endif // WITH_EDITORONLY_DATA
WeightData.Empty();
// this array is in 1:1 correspondence with ComponentInfo.RequestedGrassTypes
TArray<FByteBuffer2DView> GrassWeightArrays;
GrassWeightArrays.SetNum(ComponentInfo.RequestedGrassTypes.Num());
for (int32 Index = 0; Index < GrassWeightArrays.Num(); Index++)
{
FByteBuffer2DView* WeightView = &GrassWeightArrays[Index];
ULandscapeGrassType* GrassType = ComponentInfo.RequestedGrassTypes[Index];
WeightView->NumX = ComponentSizeVerts;
WeightView->NumY = ComponentSizeVerts;
WeightView->ByteStrideX = 4;
WeightView->ByteStrideY = TargetSize.X * 4;
WeightView->BufferStart = nullptr;
// Note: WeightData points directly at the elements of GrassWeightArrays (DO NOT REALLOCATE GRASSWEIGHTARRAYS)
WeightData.Add(GrassType, WeightView);
}
// output debug bitmap
#if UE_BUILD_DEBUG
static bool bOutputGrassBitmap = false;
if (bOutputGrassBitmap)
{
FString TempPath = FPaths::ScreenShotDir();
TempPath += TEXT("/GrassDebug");
IFileManager::Get().MakeDirectory(*TempPath, true);
FFileHelper::CreateBitmap(*(TempPath / "Grass"), TargetSize.X, TargetSize.Y, Samples.GetData(), nullptr, &IFileManager::Get(), nullptr, ComponentInfo.RequestedGrassTypes.Num() >= 2);
}
#endif
int32 GrassTypeCount = ComponentInfo.RequestedGrassTypes.Num();
for (int32 PassIdx = 0; PassIdx < ComponentInfo.NumPasses; PassIdx++)
{
FColor* SampleData = &Samples[ComponentInfo.PixelOffsetX + PassIdx * ComponentSizeVerts];
if (PassIdx < ComponentInfo.FirstHeightMipsPassIndex)
{
if (PassIdx == 0) // height in RG, grass weights in BA
{
HeightData.BufferStart = SampleData;
if (GrassTypeCount > 0)
{
GrassWeightArrays[0].BufferStart = &SampleData->BGRA_AS_FCOLOR_BLUE;
if (GrassTypeCount > 1)
{
GrassWeightArrays[1].BufferStart = &SampleData->BGRA_AS_FCOLOR_ALPHA;
}
}
}
else
{
int32 TypeIdx = PassIdx * 4 - 2;
GrassWeightArrays[TypeIdx+0].BufferStart = &SampleData->BGRA_AS_FCOLOR_RED;
if (GrassTypeCount > TypeIdx+1)
{
GrassWeightArrays[TypeIdx+1].BufferStart = &SampleData->BGRA_AS_FCOLOR_GREEN;
if (GrassTypeCount > TypeIdx + 2)
{
GrassWeightArrays[TypeIdx + 2].BufferStart = &SampleData->BGRA_AS_FCOLOR_BLUE;
if (GrassTypeCount > TypeIdx + 3)
{
GrassWeightArrays[TypeIdx + 3].BufferStart = &SampleData->BGRA_AS_FCOLOR_ALPHA;
}
}
}
}
}
else // PassIdx >= FirstHeightMipsPassIndex
{
#if WITH_EDITORONLY_DATA
const int32 Mip = HeightMips[PassIdx - ComponentInfo.FirstHeightMipsPassIndex];
int32 MipSizeVerts = NumSubsections * (SubsectionSizeQuads >> Mip);
TArray<uint16>& MipHeightData = NewGrassData->HeightMipData.Add(Mip);
MipHeightData.SetNumUninitialized(MipSizeVerts* MipSizeVerts);
uint16* DstMipHeight = MipHeightData.GetData();
for (int32 y = 0; y < MipSizeVerts; y++)
{
FColor* SrcSample = &SampleData[y * TargetSize.X];
for (int32 x = 0; x < MipSizeVerts; x++)
{
*DstMipHeight++ = (((uint16)SrcSample->BGRA_AS_FCOLOR_RED) << 8) + (uint16)(SrcSample->BGRA_AS_FCOLOR_GREEN);
SrcSample++;
}
}
#endif // WITH_EDITORONLY_DATA
}
}
#undef BGRA_AS_FCOLOR_BLUE
#undef BGRA_AS_FCOLOR_GREEN
#undef BGRA_AS_FCOLOR_RED
#undef BGRA_AS_FCOLOR_ALPHA
NewGrassData->InitializeFrom(&HeightData, WeightData, /* bStripEmptyWeights = */ true);
Results.Add(Component, MoveTemp(NewGrassData));
}
return Results;
}
void FLandscapeGrassWeightExporter::ApplyResults()
{
TRACE_CPUPROFILER_EVENT_SCOPE(ApplyResults);
TMap<ULandscapeComponent*, TUniquePtr<FLandscapeComponentGrassData>, TInlineSetAllocator<1>> NewGrassData = FetchResults(/* bFreeAsyncReadback = */ true);
ApplyResults(NewGrassData);
}
void FLandscapeGrassWeightExporter::ApplyResults(TMap<ULandscapeComponent*, TUniquePtr<FLandscapeComponentGrassData>, TInlineSetAllocator<1>>& Results)
{
for (auto&& GrassDataPair : Results)
{
ULandscapeComponent* Component = GrassDataPair.Key;
FLandscapeComponentGrassData* ComponentGrassData = GrassDataPair.Value.Release();
ALandscapeProxy* Proxy = Component->GetLandscapeProxy();
UE_LOG(LogGrass, Verbose, TEXT("Populating component %s with grass data, size: %d"), *Component->GetName(), ComponentGrassData->NumElements);
// Assign the new data (thread-safe)
Component->GrassData = MakeShareable(ComponentGrassData);
#if WITH_EDITOR
if (Proxy->bBakeMaterialPositionOffsetIntoCollision)
{
Component->DestroyCollisionData();
Component->UpdateCollisionData();
}
#endif // WITH_EDITOR
}
}