// 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 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 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(); InOutShaderTypes.AddShaderType(); } 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& 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& 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& 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& 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::GetRHI()); } void FLandscapeGrassWeightMeshProcessor::AddMeshBatch(const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, int32 NumPasses, FVector2D ViewOffset, float PassOffsetX, int32 FirstHeightMipsPassIndex, const TArray& 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& 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& HeightMips) { const FVertexFactory* VertexFactory = MeshBatch.VertexFactory; FMaterialShaderTypes ShaderTypes; ShaderTypes.AddShaderType(); ShaderTypes.AddShaderType(); 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& PSOInitializers) { // Only support the Landscape fixed grid vertex factory type. if (VertexFactoryData.VertexFactoryType != &FLandscapeFixedGridVertexFactory::StaticType) { return; } FMaterialShaderTypes ShaderTypes; ShaderTypes.AddShaderType(); ShaderTypes.AddShaderType(); 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& 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(); 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 InLandscapeComponents, bool bInNeedsGrassmap, bool bInNeedsHeightmap, const TArray& 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* 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 { 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, TInlineSetAllocator<1>> FLandscapeGrassWeightExporter::FetchResults(bool bFreeAsyncReadback) { TRACE_CPUPROFILER_EVENT_SCOPE(FetchResults); TMap, TInlineSetAllocator<1>> Results; TArray Samples; check(AsyncReadbackPtr != nullptr); { FIntPoint Size; Samples = AsyncReadbackPtr->TakeResults(&Size); if (bFreeAsyncReadback) { FreeAsyncReadback(); } check(Size == TargetSize); } Results.Reserve(ComponentInfos.Num()); FHeightBuffer2DView HeightData; TMap*> WeightData; for (auto& ComponentInfo : ComponentInfos) { ULandscapeComponent* Component = ComponentInfo.Component; TUniquePtr NewGrassData = MakeUnique(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 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& 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, TInlineSetAllocator<1>> NewGrassData = FetchResults(/* bFreeAsyncReadback = */ true); ApplyResults(NewGrassData); } void FLandscapeGrassWeightExporter::ApplyResults(TMap, 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 } }