// Copyright Epic Games, Inc. All Rights Reserved. #include "MoviePipelineDeferredPasses.h" #include "MoviePipelineOutputBase.h" #include "Engine/TextureRenderTarget2D.h" #include "SceneManagement.h" #include "SceneView.h" #include "MovieRenderPipelineDataTypes.h" #include "GameFramework/PlayerController.h" #include "MoviePipelineRenderPass.h" #include "EngineModule.h" #include "Engine/World.h" #include "Engine/TextureRenderTarget.h" #include "MoviePipeline.h" #include "Misc/FrameRate.h" #include "MoviePipelineShotConfig.h" #include "MovieRenderOverlappedImage.h" #include "MovieRenderPipelineCoreModule.h" #include "ImagePixelData.h" #include "MoviePipelineOutputBuilder.h" #include "BufferVisualizationData.h" #include "Containers/Array.h" #include "FinalPostProcessSettings.h" #include "Materials/Material.h" #include "MoviePipelineCameraSetting.h" #include "MoviePipelineHighResSetting.h" #include "MoviePipelineQueue.h" #include "MoviePipelineAntiAliasingSetting.h" #include "MoviePipelineOutputSetting.h" #include "MoviePipelineBlueprintLibrary.h" #include "MovieRenderPipelineCoreModule.h" #include "Components/PrimitiveComponent.h" #include "EngineUtils.h" #include "Engine/RendererSettings.h" #include "Camera/CameraComponent.h" #include "CineCameraComponent.h" #include "Interfaces/Interface_PostProcessVolume.h" #include "MoviePipelineTelemetry.h" #include "MoviePipelineUtils.h" #include "WorldPartition/DataLayer/DataLayerAsset.h" #include "TextureResource.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MoviePipelineDeferredPasses) FString UMoviePipelineDeferredPassBase::StencilLayerMaterialAsset = TEXT("/MovieRenderPipeline/Materials/MoviePipeline_StencilCutout.MoviePipeline_StencilCutout"); FString UMoviePipelineDeferredPassBase::DefaultDepthAsset = TEXT("/MovieRenderPipeline/Materials/MovieRenderQueue_WorldDepth.MovieRenderQueue_WorldDepth"); FString UMoviePipelineDeferredPassBase::DefaultMotionVectorsAsset = TEXT("/MovieRenderPipeline/Materials/MovieRenderQueue_MotionVectors.MovieRenderQueue_MotionVectors"); UMoviePipelineDeferredPassBase::UMoviePipelineDeferredPassBase() : UMoviePipelineImagePassBase() { PassIdentifier = FMoviePipelinePassIdentifier("FinalImage"); // To help user knowledge we pre-seed the additional post processing materials with an array of potentially common passes. TArray DefaultPostProcessMaterials; DefaultPostProcessMaterials.Add(DefaultDepthAsset); DefaultPostProcessMaterials.Add(DefaultMotionVectorsAsset); for (FString& MaterialPath : DefaultPostProcessMaterials) { FMoviePipelinePostProcessPass& NewPass = AdditionalPostProcessMaterials.AddDefaulted_GetRef(); NewPass.Material = TSoftObjectPtr(FSoftObjectPath(MaterialPath)); NewPass.bEnabled = false; NewPass.bHighPrecisionOutput = MaterialPath.Equals(DefaultDepthAsset); } bRenderMainPass = true; } void UMoviePipelineDeferredPassBase::PostLoad() { Super::PostLoad(); #if WITH_EDITORONLY_DATA PRAGMA_DISABLE_DEPRECATION_WARNINGS if (bUse32BitPostProcessMaterials_DEPRECATED) { for (FMoviePipelinePostProcessPass& Pass : AdditionalPostProcessMaterials) { Pass.bHighPrecisionOutput = true; } } PRAGMA_ENABLE_DEPRECATION_WARNINGS #endif // WITH_EDITORONLY_DATA } FIntPoint UMoviePipelineDeferredPassBase::GetEffectiveOutputResolutionForCamera(const int32 InCameraIndex) const { return GetOverscannedResolutionForCamera(InCameraIndex); } FIntPoint UMoviePipelineDeferredPassBase::GetOverscannedResolutionForCamera(const int32 InCameraIndex) const { UMoviePipelinePrimaryConfig* PrimaryConfig = GetPipeline()->GetPipelinePrimaryConfig(); UMoviePipelineExecutorShot* CurrentShot = GetPipeline()->GetActiveShotList()[GetPipeline()->GetCurrentShotIndex()]; // Get any cached overscan value for this camera. If there is none, query the live overscan value and cache it float CameraOverscan = 0.0f; if (GetPipeline()->HasCachedCameraOverscan(InCameraIndex)) { CameraOverscan = GetPipeline()->GetCachedCameraOverscan(InCameraIndex); } else { // Get the camera view info to retrieve the camera's overscan, which is used when the settings do not override the overscan FMinimalViewInfo CameraViewInfo; if (GetNumCamerasToRender() == 1) { CameraViewInfo = GetPipeline()->GetWorld()->GetFirstPlayerController()->PlayerCameraManager->GetCameraCacheView(); } else { UCameraComponent* CameraComponent; GetPipeline()->GetSidecarCameraData(CurrentShot, InCameraIndex, CameraViewInfo, &CameraComponent); } CameraOverscan = CameraViewInfo.GetOverscan(); GetPipeline()->CacheCameraOverscan(InCameraIndex, CameraOverscan); } return UMoviePipelineBlueprintLibrary::GetOverscannedResolution(PrimaryConfig, CurrentShot, CameraOverscan); } bool UMoviePipelineDeferredPassBase::GetManualExposureEnabledForCamera(const int32 InCameraIndex) const { UMoviePipelineExecutorShot* CurrentShot = GetPipeline()->GetActiveShotList()[GetPipeline()->GetCurrentShotIndex()]; if (GetNumCamerasToRender() == 1) { APlayerCameraManager* PlayerCameraManager = GetPipeline()->GetWorld()->GetFirstPlayerController()->PlayerCameraManager; // By design, we don't use PlayerCameraManager->GetCameraCacheView() or PlayerCameraManager->ViewTarget.POV, as these cached // values haven't been initialized at the beginning of the first tick where the movie passes are initialized. Instead, we // need to pull values from the component on the target actor. if (PlayerCameraManager->ViewTarget.Target) { UCameraComponent* CameraComponent = MovieSceneHelpers::CameraComponentFromActor(PlayerCameraManager->ViewTarget.Target); if (CameraComponent && CameraComponent->PostProcessBlendWeight > 0.0f && CameraComponent->PostProcessSettings.bOverride_AutoExposureMethod) { return CameraComponent->PostProcessSettings.AutoExposureMethod == AEM_Manual; } } } else { FMinimalViewInfo CameraViewInfo; UCameraComponent* CameraComponent; GetPipeline()->GetSidecarCameraData(CurrentShot, InCameraIndex, CameraViewInfo, &CameraComponent); if (CameraViewInfo.PostProcessBlendWeight > 0.0f && CameraViewInfo.PostProcessSettings.bOverride_AutoExposureMethod) { return CameraViewInfo.PostProcessSettings.AutoExposureMethod == AEM_Manual; } } return false; } FMoviePipelineRenderPassMetrics UMoviePipelineDeferredPassBase::GetRenderPassMetricsForCamera(const int32 InCameraIndex, const FMoviePipelineRenderPassMetrics& InSampleState) const { // Add per-camera custom backbuffer size support here. UMoviePipelinePrimaryConfig* PrimaryConfig = GetPipeline()->GetPipelinePrimaryConfig(); UMoviePipelineExecutorShot* CurrentShot = GetPipeline()->GetActiveShotList()[GetPipeline()->GetCurrentShotIndex()]; check(PrimaryConfig); check(CurrentShot); return UE::MoviePipeline::GetRenderPassMetrics(PrimaryConfig, CurrentShot, InSampleState, GetOverscannedResolutionForCamera(InCameraIndex)); } int32 UMoviePipelineDeferredPassBase::GetNumCamerasToRender() const { UMoviePipelineExecutorShot* CurrentShot = GetPipeline()->GetActiveShotList()[GetPipeline()->GetCurrentShotIndex()]; UMoviePipelineCameraSetting* CameraSettings = GetPipeline()->FindOrAddSettingForShot(CurrentShot); return CameraSettings->bRenderAllCameras ? CurrentShot->SidecarCameras.Num() : 1; } int32 UMoviePipelineDeferredPassBase::GetCameraIndexForRenderPass(const int32 InCameraIndex) const { UMoviePipelineExecutorShot* CurrentShot = GetPipeline()->GetActiveShotList()[GetPipeline()->GetCurrentShotIndex()]; UMoviePipelineCameraSetting* CameraSettings = GetPipeline()->FindOrAddSettingForShot(CurrentShot); // If we're not rendering all cameras, we need to pass -1 so we pick up the real camera name. return CameraSettings->bRenderAllCameras ? InCameraIndex : -1; } FString UMoviePipelineDeferredPassBase::GetCameraName(const int32 InCameraIndex) const { UMoviePipelineExecutorShot* CurrentShot = GetPipeline()->GetActiveShotList()[GetPipeline()->GetCurrentShotIndex()]; UMoviePipelineCameraSetting* CameraSettings = GetPipeline()->FindOrAddSettingForShot(CurrentShot); // If we're not rendering all cameras, we need to pass -1 so we pick up the real camera name. const int32 LocalCameraIndex = CameraSettings->bRenderAllCameras ? InCameraIndex : -1; return CurrentShot->GetCameraName(LocalCameraIndex); } FString UMoviePipelineDeferredPassBase::GetCameraNameOverride(const int32 InCameraIndex) const { // Custom camera name used to override ouput file name param return TEXT(""); } void UMoviePipelineDeferredPassBase::MoviePipelineRenderShowFlagOverride(FEngineShowFlags& OutShowFlag) { if (bDisableMultisampleEffects) { OutShowFlag.SetAntiAliasing(false); OutShowFlag.SetDepthOfField(false); OutShowFlag.SetMotionBlur(false); OutShowFlag.SetBloom(false); OutShowFlag.SetSceneColorFringe(false); } } void UMoviePipelineDeferredPassBase::SetupImpl(const MoviePipeline::FMoviePipelineRenderPassInitSettings& InPassInitSettings) { Super::SetupImpl(InPassInitSettings); LLM_SCOPE_BYNAME(TEXT("MoviePipeline/DeferredPassSetup")); if (bAddDefaultLayer && (GetNumStencilLayers() == 0)) { UE_LOG(LogMovieRenderPipeline, Error, TEXT("The 'Add Default Layer' deferred rendering option requires at least one Actor or Data Layer to be specified.")); GetPipeline()->Shutdown(true); return; } { TSoftObjectPtr StencilMatRef = TSoftObjectPtr(FSoftObjectPath(StencilLayerMaterialAsset)); StencilLayerMaterial = StencilMatRef.LoadSynchronous(); if (!StencilLayerMaterial) { UE_LOG(LogMovieRenderPipeline, Error, TEXT("Failed to load Stencil Mask material, stencil layers will be incorrect. Path: %s"), *StencilMatRef.ToString()); } } for (FMoviePipelinePostProcessPass& AdditionalPass : AdditionalPostProcessMaterials) { if (AdditionalPass.bEnabled) { UMaterialInterface* Material = AdditionalPass.Material.LoadSynchronous(); if (Material) { ActivePostProcessMaterials.Add(Material); if (AdditionalPass.bHighPrecisionOutput) { ActiveHighPrecisionPostProcessMaterials.Add(Material); } } } } // Create a view state. Each individual camera, tile, and stencil layer need their own unique state as this includes visual history for anti-aliasing, etc. UMoviePipelineExecutorShot* CurrentShot = GetPipeline()->GetActiveShotList()[GetPipeline()->GetCurrentShotIndex()]; UMoviePipelineHighResSetting* HighResSettings = GetPipeline()->FindOrAddSettingForShot(CurrentShot); const int32 NumCameras = GetNumCamerasToRender(); int32 TotalNumberOfAccumulators = 0; for (int32 CamIndex = 0; CamIndex < NumCameras; CamIndex++) { const FIntPoint CameraResolution = GetOverscannedResolutionForCamera(CamIndex); // Figure out how big each sub-region (tile) is. FIntPoint BackbufferResolution = FIntPoint( FMath::CeilToInt((float)CameraResolution.X / (float)HighResSettings->TileCount), FMath::CeilToInt((float)CameraResolution.Y / (float)HighResSettings->TileCount)); // Then increase each sub-region by the overlap amount. BackbufferResolution = HighResSettings->CalculatePaddedBackbufferSize(BackbufferResolution); // Re-initialize the render target and surface queue for the current camera GetOrCreateViewRenderTarget(BackbufferResolution); CreateSurfaceQueueImpl(BackbufferResolution); FMultiCameraViewStateData& CameraData = CameraViewStateData.AddDefaulted_GetRef(); // We don't always want to allocate a unique history per tile as very large resolutions can OOM the GPU in backbuffer images alone. // But we do need the history for some features (like Lumen) to work, so it's optional. int32 HighResTileCount = HighResSettings->bAllocateHistoryPerTile ? HighResSettings->TileCount : 1; if (HighResTileCount > 1 && HighResSettings->bPageToSystemMemory) { CameraData.SystemMemoryMirror = FSceneViewStateInterface::SystemMemoryMirrorAllocate(); } if (HighResTileCount > 1 && bRenderMainPass && !GetManualExposureEnabledForCamera(CamIndex)) { // Add view state for auto-exposure render pass FMultiCameraViewStateData::FPerTile& PerTile = CameraData.TileData.FindOrAdd(FIntPoint(-1, -1)); PerTile.SceneViewStates.AddDefaulted(); } for (int32 TileIndexX = 0; TileIndexX < HighResTileCount; TileIndexX++) { for (int32 TileIndexY = 0; TileIndexY < HighResTileCount; TileIndexY++) { FMultiCameraViewStateData::FPerTile& PerTile = CameraData.TileData.FindOrAdd(FIntPoint(TileIndexX, TileIndexY)); // If they want to render the main pass (most likely) add a view state for it if (bRenderMainPass) { PerTile.SceneViewStates.AddDefaulted(); } // If they want to render a "default" stencil layer (that has everything not in another layer) add that... if (GetNumStencilLayers() > 0 && bAddDefaultLayer) { PerTile.SceneViewStates.AddDefaulted(); } // Finally all of the other stencil layers for (int32 Index = 0; Index < GetNumStencilLayers(); Index++) { PerTile.SceneViewStates.AddDefaulted(); } } } // We have to add up the number of accumulators needed separately, because we don't make // one accumulator per high-res tile. if (bRenderMainPass) { TotalNumberOfAccumulators++; } if (GetNumStencilLayers() > 0 && bAddDefaultLayer) { TotalNumberOfAccumulators++; } for (int32 Index = 0; Index < GetNumStencilLayers(); Index++) { TotalNumberOfAccumulators++; } // Now that we have an array of view states, allocate each one. for (TPair& Pair : CameraData.TileData) { for (int32 Index = 0; Index < Pair.Value.SceneViewStates.Num(); Index++) { Pair.Value.SceneViewStates[Index].Allocate(InPassInitSettings.FeatureLevel); } } } // We must allocate one accumulator per output, because when we submit a sample we tie up an accumulator, but because of temporal sampling // the accumulators can be tied up for multiple game frames, thus we must have at least one per output and we can only reuse them between // actual output frames (not engine frames). This doesn't allocate memory until they're actually used so it's ok to over-allocate. int32 PoolSize = (TotalNumberOfAccumulators + (ActivePostProcessMaterials.Num()*NumCameras) + 1) * 3; AccumulatorPool = MakeShared, ESPMode::ThreadSafe>(PoolSize); PreviousCustomDepthValue.Reset(); // This scene view extension will be released automatically as soon as Render Sequence is torn down. // One Extension per sequence, since each sequence has its own OCIO settings. OCIOSceneViewExtension = FSceneViewExtensions::NewExtension(); const bool bEnableStencilPass = bAddDefaultLayer || GetNumStencilLayers() > 0; if (bEnableStencilPass) { IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.CustomDepth")); if (CVar) { PreviousCustomDepthValue = CVar->GetInt(); const int32 CustomDepthWithStencil = 3; if (PreviousCustomDepthValue != CustomDepthWithStencil) { UE_LOG(LogMovieRenderPipeline, Log, TEXT("Overriding project custom depth/stencil value to support a stencil pass.")); // We use ECVF_SetByProjectSetting otherwise once this is set once by rendering, the UI silently fails // if you try to change it afterwards. This SetByProjectSetting will fail if they have manipulated the cvar via the console // during their current session but it's less likely than changing the project settings. CVar->Set(CustomDepthWithStencil, EConsoleVariableFlags::ECVF_SetByProjectSetting); } } } // Cache out the stencil layer names (from data layers or actor layers) and de-duplicate. If layers with the same name // are provided, renders may fail, which is why the the names need to be de-duplicated. if (IsUsingDataLayers()) { for (FSoftObjectPath DataLayerAssetPath : DataLayers) { UDataLayerAsset* DataLayerAsset = Cast(DataLayerAssetPath.TryLoad()); if (DataLayerAsset) { UniqueStencilLayerNames.Add(DataLayerAsset->GetName()); } } } else { for (const FActorLayer& Layer : ActorLayers) { UniqueStencilLayerNames.Add(Layer.Name.ToString()); } } UE::MoviePipeline::DeduplicateNameArray(UniqueStencilLayerNames); } void UMoviePipelineDeferredPassBase::TeardownImpl() { ActivePostProcessMaterials.Reset(); ActiveHighPrecisionPostProcessMaterials.Reset(); UniqueStencilLayerNames.Reset(); for (FMultiCameraViewStateData& CameraData : CameraViewStateData) { for (TPair& Pair : CameraData.TileData) { for (int32 Index = 0; Index < Pair.Value.SceneViewStates.Num(); Index++) { FSceneViewStateInterface* Ref = Pair.Value.SceneViewStates[Index].GetReference(); if (Ref) { Ref->ClearMIDPool(); } Pair.Value.SceneViewStates[Index].Destroy(); } } } CameraViewStateData.Reset(); OCIOSceneViewExtension.Reset(); OCIOSceneViewExtension = nullptr; if (PreviousCustomDepthValue.IsSet()) { IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.CustomDepth")); if (CVar) { if (CVar->GetInt() != PreviousCustomDepthValue.GetValue()) { UE_LOG(LogMovieRenderPipeline, Log, TEXT("Restoring custom depth/stencil value to: %d"), PreviousCustomDepthValue.GetValue()); CVar->Set(PreviousCustomDepthValue.GetValue(), EConsoleVariableFlags::ECVF_SetByProjectSetting); } } } // Preserve our view state until the rendering thread has been flushed. Super::TeardownImpl(); } void UMoviePipelineDeferredPassBase::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) { Super::AddReferencedObjects(InThis, Collector); UMoviePipelineDeferredPassBase& This = *CastChecked(InThis); for (FMultiCameraViewStateData& CameraData : This.CameraViewStateData) { for (TPair& Pair : CameraData.TileData) { for (int32 Index = 0; Index < Pair.Value.SceneViewStates.Num(); Index++) { FSceneViewStateInterface* Ref = Pair.Value.SceneViewStates[Index].GetReference(); if (Ref) { Ref->AddReferencedObjects(Collector); } } } } } namespace UE { namespace MoviePipeline { struct FDeferredPassRenderStatePayload : public UMoviePipelineImagePassBase::IViewCalcPayload { int32 CameraIndex; FIntPoint TileIndex; // Will always be 1,1 if no history-per-tile is enabled int32 SceneViewIndex; }; } } FSceneViewStateInterface* UMoviePipelineDeferredPassBase::GetSceneViewStateInterface(IViewCalcPayload* OptPayload) { UE::MoviePipeline::FDeferredPassRenderStatePayload* Payload = (UE::MoviePipeline::FDeferredPassRenderStatePayload*)OptPayload; check(Payload); // When not using multi-camera support, CameraIndex is -1, but means "first entry" in the array. int32 LocalCameraIndex = FMath::Clamp(Payload->CameraIndex, 0, Payload->CameraIndex); FMultiCameraViewStateData& CameraData = CameraViewStateData[LocalCameraIndex]; FIntPoint TileIndex = CameraData.TileData.Num() == 1 ? FIntPoint(0, 0) : Payload->TileIndex; if (FMultiCameraViewStateData::FPerTile* TileData = CameraData.TileData.Find(TileIndex)) { return TileData->SceneViewStates[Payload->SceneViewIndex].GetReference(); } return nullptr; } FSceneViewStateInterface* UMoviePipelineDeferredPassBase::GetExposureSceneViewStateInterface(IViewCalcPayload* OptPayload, int32 CubeFaceIndex) { UE::MoviePipeline::FDeferredPassRenderStatePayload* Payload = (UE::MoviePipeline::FDeferredPassRenderStatePayload*)OptPayload; check(Payload); // When not using multi-camera support, CameraIndex is -1, but means "first entry" in the array. int32 LocalCameraIndex = FMath::Clamp(Payload->CameraIndex, 0, Payload->CameraIndex); // Special tile {-1,-1} holds the auto-exposure view state, if present FMultiCameraViewStateData& CameraData = CameraViewStateData[LocalCameraIndex]; if (FMultiCameraViewStateData::FPerTile* TileData = CameraData.TileData.Find(FIntPoint(-1,-1))) { return TileData->SceneViewStates[0].GetReference(); } else { return nullptr; } } void UMoviePipelineDeferredPassBase::GatherOutputPassesImpl(TArray& ExpectedRenderPasses) { // No super call here because multiple cameras makes this all complicated // Super::GatherOutputPassesImpl(ExpectedRenderPasses); UMoviePipelineExecutorShot* CurrentShot = GetPipeline()->GetActiveShotList()[GetPipeline()->GetCurrentShotIndex()]; const int32 NumCameras = GetNumCamerasToRender(); for (int32 CameraIndex = 0; CameraIndex < NumCameras; CameraIndex++) { FMoviePipelinePassIdentifier PassIdentifierForCurrentCamera; PassIdentifierForCurrentCamera.Name = PassIdentifier.Name; PassIdentifierForCurrentCamera.CameraName = GetCameraName(CameraIndex); // Add the default backbuffer if (bRenderMainPass) { ExpectedRenderPasses.Add(PassIdentifierForCurrentCamera); } // Each camera will render everything in the Post Process Material stack. TArray RenderPasses; for (UMaterialInterface* Material : ActivePostProcessMaterials) { if (Material) { RenderPasses.Add(GetNameForPostProcessMaterial(Material)); } } for (const FString& Pass : RenderPasses) { ExpectedRenderPasses.Add(FMoviePipelinePassIdentifier(PassIdentifierForCurrentCamera.Name + Pass, PassIdentifierForCurrentCamera.CameraName)); } // Stencil Layer Time! if (GetNumStencilLayers() > 0 && bAddDefaultLayer) { ExpectedRenderPasses.Add(FMoviePipelinePassIdentifier(PassIdentifierForCurrentCamera.Name + TEXT("DefaultLayer"), PassIdentifierForCurrentCamera.CameraName)); } for (const FString& StencilLayerName : GetStencilLayerNames()) { ExpectedRenderPasses.Add(FMoviePipelinePassIdentifier(PassIdentifierForCurrentCamera.Name + StencilLayerName, PassIdentifierForCurrentCamera.CameraName)); } } } void UMoviePipelineDeferredPassBase::AddViewExtensions(FSceneViewFamilyContext& InContext, FMoviePipelineRenderPassMetrics& InOutSampleState) { // OCIO Scene View Extension is a special case and won't be registered like other view extensions. if (InOutSampleState.OCIOConfiguration && InOutSampleState.OCIOConfiguration->bIsEnabled) { FOpenColorIODisplayConfiguration* OCIOConfigNew = const_cast(InOutSampleState).OCIOConfiguration; FOpenColorIODisplayConfiguration& OCIOConfigCurrent = OCIOSceneViewExtension->GetDisplayConfiguration(); // We only need to set this once per render sequence. if (OCIOConfigNew->ColorConfiguration.ConfigurationSource && OCIOConfigNew->ColorConfiguration.ConfigurationSource != OCIOConfigCurrent.ColorConfiguration.ConfigurationSource) { OCIOSceneViewExtension->SetDisplayConfiguration(*OCIOConfigNew); } InContext.ViewExtensions.Add(OCIOSceneViewExtension.ToSharedRef()); } } void UMoviePipelineDeferredPassBase::RenderSample_GameThreadImpl(const FMoviePipelineRenderPassMetrics& InSampleState) { // Wait for a surface to be available to write to. This will stall the game thread while the RHI/Render Thread catch up. Super::RenderSample_GameThreadImpl(InSampleState); const int32 NumCameras = GetNumCamerasToRender(); UMoviePipelineExecutorShot* CurrentShot = GetPipeline()->GetActiveShotList()[GetPipeline()->GetCurrentShotIndex()]; for (int32 CameraIndex = 0; CameraIndex < NumCameras; CameraIndex++) { FMoviePipelinePassIdentifier PassIdentifierForCurrentCamera; PassIdentifierForCurrentCamera.Name = PassIdentifier.Name; // If we're not rendering all cameras, we need to pass -1 so we pick up the real camera name. const int32 CameraIndexForRenderPass = GetCameraIndexForRenderPass(CameraIndex); PassIdentifierForCurrentCamera.CameraName = GetCameraName(CameraIndex); // Main Render Pass if (bRenderMainPass) { // We have an auto-exposure pass if we allocated a view state for it bool bHasAutoExposurePass = CameraViewStateData.Num() && CameraViewStateData[0].TileData.Contains(FIntPoint(-1, -1)); // Auto-exposure pass is rendered only for the first tile and sample, actual pass is always rendered bool bAutoExposurePass = bHasAutoExposurePass && InSampleState.TileIndexes.X == 0 && InSampleState.TileIndexes.Y == 0 && InSampleState.TemporalSampleIndex == 0 && InSampleState.SpatialSampleIndex == 0; bool bActualPass = true; while (bAutoExposurePass || bActualPass) { FMoviePipelineRenderPassMetrics InOutSampleState = GetRenderPassMetricsForCamera(CameraIndexForRenderPass, InSampleState); // InOutSampleState.OutputState.CameraCount = NumCameras; InOutSampleState.OutputState.CameraIndex = CameraIndexForRenderPass; InOutSampleState.OutputState.CameraNameOverride = GetCameraNameOverride(CameraIndex); if (bAutoExposurePass) { // Auto exposure reference pass uses tile indices -1,-1 InOutSampleState.TileIndexes = FIntPoint(-1, -1); } UE::MoviePipeline::FDeferredPassRenderStatePayload Payload; Payload.CameraIndex = CameraIndexForRenderPass; Payload.TileIndex = InOutSampleState.TileIndexes; // Main renders use index 0. Payload.SceneViewIndex = 0; TSharedPtr ViewFamily = CalculateViewFamily(InOutSampleState, &Payload); // Add post-processing materials if needed FSceneView* View = const_cast(ViewFamily->Views[0]); View->FinalPostProcessSettings.BufferVisualizationOverviewMaterials.Empty(); View->FinalPostProcessSettings.BufferVisualizationPipes.Empty(); for (UMaterialInterface* Material : ActivePostProcessMaterials) { if (Material) { View->FinalPostProcessSettings.BufferVisualizationOverviewMaterials.Add(Material); } } for (UMaterialInterface* VisMaterial : View->FinalPostProcessSettings.BufferVisualizationOverviewMaterials) { // If this was just to contribute to the history buffer, no need to go any further. if (InOutSampleState.bDiscardResult) { continue; } FMoviePipelinePassIdentifier LayerPassIdentifier = FMoviePipelinePassIdentifier(PassIdentifier.Name + GetNameForPostProcessMaterial(VisMaterial), PassIdentifierForCurrentCamera.CameraName); auto BufferPipe = MakeShared(); BufferPipe->bIsExpecting32BitPixelData = ActiveHighPrecisionPostProcessMaterials.Contains(VisMaterial); BufferPipe->AddEndpoint(MakeForwardingEndpoint(LayerPassIdentifier, InOutSampleState)); View->FinalPostProcessSettings.BufferVisualizationPipes.Add(VisMaterial->GetFName(), BufferPipe); } int32 NumValidMaterials = View->FinalPostProcessSettings.BufferVisualizationPipes.Num(); View->FinalPostProcessSettings.bBufferVisualizationDumpRequired = NumValidMaterials > 0; // Submit to be rendered. Main render pass always uses target 0. TWeakObjectPtr ViewRenderTarget = GetOrCreateViewRenderTarget(InOutSampleState.BackbufferSize, (IViewCalcPayload*)(&Payload)); check(ViewRenderTarget.IsValid()); FRenderTarget* RenderTarget = ViewRenderTarget->GameThread_GetRenderTargetResource(); check(RenderTarget); FCanvas Canvas = FCanvas(RenderTarget, nullptr, GetPipeline()->GetWorld(), View->GetFeatureLevel(), FCanvas::CDM_DeferDrawing, 1.0f); GetRendererModule().BeginRenderingViewFamily(&Canvas, ViewFamily.Get()); ENQUEUE_RENDER_COMMAND(TransitionTextureSRVState)( [RenderTarget](FRHICommandListImmediate& RHICmdList) mutable { // Transition our render target from a render target view to a shader resource view to allow the UMG preview material to read from this Render Target. RHICmdList.Transition(FRHITransitionInfo(RenderTarget->GetRenderTargetTexture(), ERHIAccess::RTV, ERHIAccess::SRVGraphicsPixel)); }); if (bAutoExposurePass) { // Finished rendering auto-exposure pass bAutoExposurePass = false; } else { // Readback + Accumulate. PostRendererSubmission(InOutSampleState, PassIdentifierForCurrentCamera, GetOutputFileSortingOrder(), Canvas); // Finished rendering actual pass bActualPass = false; } } } // Now do the stencil layer submission (which doesn't support additional post processing materials) { FMoviePipelineRenderPassMetrics InOutSampleState = GetRenderPassMetricsForCamera(CameraIndexForRenderPass, InSampleState); InOutSampleState.OutputState.CameraIndex = CameraIndexForRenderPass; InOutSampleState.OutputState.CameraNameOverride = GetCameraNameOverride(CameraIndex); struct FStencilValues { FStencilValues() : bRenderCustomDepth(false) , StencilMask(ERendererStencilMask::ERSM_Default) , CustomStencil(0) { } bool bRenderCustomDepth; ERendererStencilMask StencilMask; int32 CustomStencil; }; // Now for each stencil layer we reconfigure all the actors custom depth/stencil TArray AllStencilLayerNames = GetStencilLayerNames(); if (bAddDefaultLayer) { AllStencilLayerNames.Add(TEXT("DefaultLayer")); } // If we're going to be using stencil layers, we need to cache all of the users // custom stencil/depth settings since we're changing them to do the mask. TMap PreviousValues; if (AllStencilLayerNames.Num() > 0) { for (TActorIterator ActorItr(GetWorld()); ActorItr; ++ActorItr) { AActor* Actor = *ActorItr; if (Actor) { for (UActorComponent* Component : Actor->GetComponents()) { if (Component && Component->IsA()) { UPrimitiveComponent* PrimitiveComponent = CastChecked(Component); FStencilValues& Values = PreviousValues.Add(PrimitiveComponent); Values.StencilMask = PrimitiveComponent->CustomDepthStencilWriteMask; Values.CustomStencil = PrimitiveComponent->CustomDepthStencilValue; Values.bRenderCustomDepth = PrimitiveComponent->bRenderCustomDepth; } } } } } for (int32 StencilLayerIndex = 0; StencilLayerIndex < AllStencilLayerNames.Num(); StencilLayerIndex++) { const FString& LayerName = AllStencilLayerNames[StencilLayerIndex]; FMoviePipelinePassIdentifier LayerPassIdentifier = FMoviePipelinePassIdentifier(PassIdentifierForCurrentCamera.Name + LayerName); LayerPassIdentifier.CameraName = PassIdentifierForCurrentCamera.CameraName; // Modify all of the actors in this world so they have the right stencil settings (so we can use the stencil buffer as a mask later) for (TActorIterator ActorItr(GetWorld()); ActorItr; ++ActorItr) { AActor* Actor = *ActorItr; if (Actor) { // The way stencil masking works is that we draw the actors on the given layer to the stencil buffer. // Then we apply a post-processing material which colors pixels outside those actors black, before // post processing. Then, TAA, Motion Blur, etc. is applied to all pixels. An alpha channel can preserve // which pixels were the geometry and which are dead space which lets you apply that as a mask later. bool bInLayer = true; if (bAddDefaultLayer && LayerName == TEXT("DefaultLayer")) { // If we're trying to render the default layer, the logic is different - we only add objects who // aren't in any of the stencil layers. bInLayer = IsActorInAnyStencilLayer(Actor); } else { // If this a normal layer, we only add the actor if it exists on this layer. bInLayer = IsActorInLayer(Actor, StencilLayerIndex); } for (UActorComponent* Component : Actor->GetComponents()) { if (Component && Component->IsA()) { UPrimitiveComponent* PrimitiveComponent = CastChecked(Component); // We want to render all objects not on the layer to stencil too so that foreground objects mask. PrimitiveComponent->SetCustomDepthStencilValue(bInLayer ? 1 : 0); PrimitiveComponent->SetCustomDepthStencilWriteMask(ERendererStencilMask::ERSM_Default); PrimitiveComponent->SetRenderCustomDepth(true); } } } } // Submit the actual render now if (StencilLayerMaterial) { UE::MoviePipeline::FDeferredPassRenderStatePayload Payload; Payload.CameraIndex = CameraIndexForRenderPass; Payload.TileIndex = InOutSampleState.TileIndexes; Payload.SceneViewIndex = StencilLayerIndex + (bRenderMainPass ? 1 : 0); TSharedPtr ViewFamily = CalculateViewFamily(InOutSampleState, &Payload); FSceneView* View = const_cast(ViewFamily->Views[0]); // Now that we've modified all of the stencil values, we can submit them to be rendered. View->FinalPostProcessSettings.AddBlendable(StencilLayerMaterial, 1.0f); IBlendableInterface* BlendableInterface = Cast(StencilLayerMaterial); BlendableInterface->OverrideBlendableSettings(*View, 1.f); { TWeakObjectPtr ViewRenderTarget = GetOrCreateViewRenderTarget(InOutSampleState.BackbufferSize, (IViewCalcPayload*)(&Payload)); check(ViewRenderTarget.IsValid()); FRenderTarget* RenderTarget = ViewRenderTarget->GameThread_GetRenderTargetResource(); check(RenderTarget); FCanvas Canvas = FCanvas(RenderTarget, nullptr, GetPipeline()->GetWorld(), View->GetFeatureLevel(), FCanvas::CDM_DeferDrawing, 1.0f); GetRendererModule().BeginRenderingViewFamily(&Canvas, ViewFamily.Get()); ENQUEUE_RENDER_COMMAND(TransitionTextureSRVState)( [RenderTarget](FRHICommandListImmediate& RHICmdList) mutable { // Transition our render target from a render target view to a shader resource view to allow the UMG preview material to read from this Render Target. RHICmdList.Transition(FRHITransitionInfo(RenderTarget->GetRenderTargetTexture(), ERHIAccess::RTV, ERHIAccess::SRVGraphicsPixel)); }); // Readback + Accumulate. PostRendererSubmission(InOutSampleState, LayerPassIdentifier, GetOutputFileSortingOrder() + 1, Canvas); } } } // Now that all stencil layers have been rendered, we can restore the custom depth/stencil/etc. values so that the main render pass acts as the user expects next time. for (TPair& KVP : PreviousValues) { KVP.Key->SetCustomDepthStencilValue(KVP.Value.CustomStencil); KVP.Key->SetCustomDepthStencilWriteMask(KVP.Value.StencilMask); KVP.Key->SetRenderCustomDepth(KVP.Value.bRenderCustomDepth); } } } } void UMoviePipelineDeferredPassBase::OnTileStartImpl(FIntPoint TileIndexes) { for (FMultiCameraViewStateData& CameraData : CameraViewStateData) { if (CameraData.SystemMemoryMirror && bRenderMainPass) { FMultiCameraViewStateData::FPerTile* TileData = CameraData.TileData.Find(TileIndexes); if (TileData && TileData->SceneViewStates.Num()) { TileData->SceneViewStates[0].GetReference()->SystemMemoryMirrorRestore(CameraData.SystemMemoryMirror.Get()); } } } } void UMoviePipelineDeferredPassBase::OnTileEndImpl(FIntPoint TileIndexes) { for (FMultiCameraViewStateData& CameraData : CameraViewStateData) { if (CameraData.SystemMemoryMirror && bRenderMainPass) { FMultiCameraViewStateData::FPerTile* TileData = CameraData.TileData.Find(TileIndexes); if (TileData && TileData->SceneViewStates.Num()) { TileData->SceneViewStates[0].GetReference()->SystemMemoryMirrorBackup(CameraData.SystemMemoryMirror.Get()); } } } } TFunction&&)> UMoviePipelineDeferredPassBase::MakeForwardingEndpoint(const FMoviePipelinePassIdentifier InPassIdentifier, const FMoviePipelineRenderPassMetrics& InSampleState) { // We have a pool of accumulators - we multi-thread the accumulation on the task graph, and for each frame, // the task has the previous samples as pre-reqs to keep the accumulation in order. However, each accumulator // can only work on one frame at a time, so we create a pool of them to work concurrently. This needs a limit // as large accumulations (16k) can take a lot of system RAM. TSharedPtr SampleAccumulator = nullptr; { SCOPE_CYCLE_COUNTER(STAT_MoviePipeline_WaitForAvailableAccumulator); SampleAccumulator = AccumulatorPool->BlockAndGetAccumulator_GameThread(InSampleState.OutputState.OutputFrameNumber, InPassIdentifier); } TSharedRef FramePayload = MakeShared(); FramePayload->PassIdentifier = InPassIdentifier; FramePayload->SampleState = InSampleState; FramePayload->SortingOrder = GetOutputFileSortingOrder() + 1; MoviePipeline::FImageSampleAccumulationArgs AccumulationArgs; { AccumulationArgs.OutputMerger = GetPipeline()->OutputBuilder; AccumulationArgs.ImageAccumulator = StaticCastSharedPtr(SampleAccumulator->Accumulator); AccumulationArgs.bAccumulateAlpha = bAccumulatorIncludesAlpha; } auto Callback = [this, FramePayload, AccumulationArgs, SampleAccumulator](TUniquePtr&& InPixelData) { // Transfer the framePayload to the returned data TUniquePtr PixelDataWithPayload = nullptr; switch (InPixelData->GetType()) { case EImagePixelType::Color: { TImagePixelData* SourceData = static_cast*>(InPixelData.Get()); PixelDataWithPayload = MakeUnique>(InPixelData->GetSize(), MoveTemp(SourceData->Pixels), FramePayload); break; } case EImagePixelType::Float16: { TImagePixelData* SourceData = static_cast*>(InPixelData.Get()); PixelDataWithPayload = MakeUnique>(InPixelData->GetSize(), MoveTemp(SourceData->Pixels), FramePayload); break; } case EImagePixelType::Float32: { TImagePixelData* SourceData = static_cast*>(InPixelData.Get()); PixelDataWithPayload = MakeUnique>(InPixelData->GetSize(), MoveTemp(SourceData->Pixels), FramePayload); break; } default: checkNoEntry(); } bool bFinalSample = FramePayload->IsLastTile() && FramePayload->IsLastTemporalSample(); bool bFirstSample = FramePayload->IsFirstTile() && FramePayload->IsFirstTemporalSample(); FMoviePipelineBackgroundAccumulateTask Task; // There may be other accumulations for this accumulator which need to be processed first Task.LastCompletionEvent = SampleAccumulator->TaskPrereq; FGraphEventRef Event = Task.Execute([PixelData = MoveTemp(PixelDataWithPayload), AccumulationArgs, bFinalSample, SampleAccumulator]() mutable { // Enqueue a encode for this frame onto our worker thread. MoviePipeline::AccumulateSample_TaskThread(MoveTemp(PixelData), AccumulationArgs); if (bFinalSample) { SampleAccumulator->TaskPrereq = nullptr; SampleAccumulator->bIsActive = false; } }); SampleAccumulator->TaskPrereq = Event; this->OutstandingTasks.Add(Event); }; return Callback; } UE::MoviePipeline::FImagePassCameraViewData UMoviePipelineDeferredPassBase::GetCameraInfo(FMoviePipelineRenderPassMetrics& InOutSampleState, IViewCalcPayload* OptPayload) const { UMoviePipelineExecutorShot* CurrentShot = GetPipeline()->GetActiveShotList()[GetPipeline()->GetCurrentShotIndex()]; const int32 NumCameras = GetNumCamerasToRender(); if (NumCameras == 1) { // If there's only one camera being used we can use the parent class which assumes the camera comes from the PlayerCameraManager return Super::GetCameraInfo(InOutSampleState, OptPayload); } else { UE::MoviePipeline::FImagePassCameraViewData OutCameraData; // Here's where it gets a lot more complicated. There's a number of properties we need to fetch from a camera manually to fill out the minimal view info. UCameraComponent* OutCamera = nullptr; GetPipeline()->GetSidecarCameraData(CurrentShot, InOutSampleState.OutputState.CameraIndex, OutCameraData.ViewInfo, &OutCamera); if (OutCamera) { // This has to come from the main camera for consistency's sake, and it's not a per-camera setting in the editor. OutCameraData.ViewActor = GetPipeline()->GetWorld()->GetFirstPlayerController()->GetViewTarget(); // Try adding cine-camera specific metadata (not all animated cameras are cine cameras though) UCineCameraComponent* CineCameraComponent = Cast(OutCamera); if (CineCameraComponent) { // ToDo: This is still wrong, PassIdentifier.CameraName needs to come in from the InOutSampleState somewhere. UE::MoviePipeline::GetMetadataFromCineCamera(CineCameraComponent, PassIdentifier.CameraName, PassIdentifier.Name, OutCameraData.FileMetadata); // We only do this in the multi-camera case because the single camera case is covered by the main Rendering loop. FVector PreviousLocation = OutCameraData.ViewInfo.Location; FRotator PreviousRotation = OutCameraData.ViewInfo.Rotation; if (OutCameraData.ViewInfo.PreviousViewTransform.IsSet()) { PreviousLocation = OutCameraData.ViewInfo.PreviousViewTransform->GetLocation(); PreviousRotation = FRotator(OutCameraData.ViewInfo.PreviousViewTransform->GetRotation()); } UE::MoviePipeline::GetMetadataFromCameraLocRot(PassIdentifier.CameraName, PassIdentifier.Name, OutCameraData.ViewInfo.Location, OutCameraData.ViewInfo.Rotation, PreviousLocation, PreviousRotation, OutCameraData.FileMetadata); } } else { UE_LOG(LogMovieRenderPipeline, Error, TEXT("Failed to find Camera Component for Shot: %d CameraIndex: %d"), GetPipeline()->GetCurrentShotIndex(), InOutSampleState.OutputState.CameraIndex); } return OutCameraData; } } void UMoviePipelineDeferredPassBase::BlendPostProcessSettings(FSceneView* InView, FMoviePipelineRenderPassMetrics& InOutSampleState, IViewCalcPayload* OptPayload) { UMoviePipelineExecutorShot* CurrentShot = GetPipeline()->GetActiveShotList()[GetPipeline()->GetCurrentShotIndex()]; const int32 NumCameras = GetNumCamerasToRender(); // The primary camera should still respect the world post processing volumes and should already be the viewtarget. if (NumCameras == 1) { // If there's only one camera being used we can use the parent class which assumes the camera comes from the PlayerCameraManager Super::BlendPostProcessSettings(InView, InOutSampleState, OptPayload); } else { UCameraComponent* OutCamera = nullptr; FMinimalViewInfo OutViewInfo; GetPipeline()->GetSidecarCameraData(CurrentShot, InOutSampleState.OutputState.CameraIndex, OutViewInfo, &OutCamera); if (!OutCamera) { // GetCameraInfo will have already printed a warning return; } // For sidecar cameras we need to do the blending of PP volumes and camera PP manually. FVector ViewLocation = OutCamera->GetComponentLocation(); UE::MoviePipeline::DoPostProcessBlend(ViewLocation, GetWorld(), OutViewInfo, InView); } } void UMoviePipelineDeferredPassBase::PostRendererSubmission(const FMoviePipelineRenderPassMetrics& InSampleState, const FMoviePipelinePassIdentifier InPassIdentifier, const int32 InSortingOrder, FCanvas& InCanvas) { // If this was just to contribute to the history buffer, no need to go any further. if (InSampleState.bDiscardResult) { return; } // Draw letterboxing, except for 1 pixel outline for anti-aliasing purposes. // This extra pixel thickness will be done later after accumulating the last tile. MoviePipeline::FLetterboxData LetterboxData; APlayerCameraManager* PlayerCameraManager = GetPipeline()->GetWorld()->GetFirstPlayerController()->PlayerCameraManager; if(PlayerCameraManager && PlayerCameraManager->GetCameraCacheView().bConstrainAspectRatio) { const FMinimalViewInfo CameraCache = PlayerCameraManager->GetCameraCacheView(); UMoviePipelineOutputSetting* OutputSettings = GetPipeline()->GetPipelinePrimaryConfig()->FindSetting(); check(OutputSettings); // Taking overscan into account. const FIntPoint FullOutputSize = InSampleState.OverscannedResolution; const float OutputSizeAspectRatio = FullOutputSize.X / (float)FullOutputSize.Y; const float CameraAspectRatio = bAllowCameraAspectRatio ? CameraCache.AspectRatio : OutputSizeAspectRatio; const FIntPoint ConstrainedFullSize = CameraAspectRatio > OutputSizeAspectRatio ? FIntPoint(FullOutputSize.X, FMath::CeilToInt((double)FullOutputSize.X / (double)CameraAspectRatio)) : FIntPoint(FMath::CeilToInt(CameraAspectRatio * FullOutputSize.Y), FullOutputSize.Y); const FIntPoint TileViewMin = InSampleState.OverlappedOffset; const FIntPoint TileViewMax = TileViewMin + InSampleState.BackbufferSize; // Calculate camera ratio constrained rect. FIntPoint ConstrainedViewMin = (FullOutputSize - ConstrainedFullSize) / 2; FIntPoint ConstrainedViewMax = ConstrainedViewMin + ConstrainedFullSize; // When there is overlap, adjust the offsets to leave a 1-pixel margin around the active pixel area, so that // anti-aliasing is able to sample the original pixel during accumulation, instead of // the letterbox pixel which would otherwise darken the edges of the active area. LetterboxData.bDrawLetterboxBorder = InSampleState.OverlappedPad != FIntPoint::ZeroValue; LetterboxData.FrameActiveArea = FIntRect(ConstrainedViewMin, ConstrainedViewMax); // Clip it by the tile rect. ConstrainedViewMin = FIntPoint(FMath::Clamp(ConstrainedViewMin.X, TileViewMin.X, TileViewMax.X), FMath::Clamp(ConstrainedViewMin.Y, TileViewMin.Y, TileViewMax.Y)); ConstrainedViewMax = FIntPoint(FMath::Clamp(ConstrainedViewMax.X, TileViewMin.X, TileViewMax.X), FMath::Clamp(ConstrainedViewMax.Y, TileViewMin.Y, TileViewMax.Y)); // Difference between the clipped constrained rect and the tile rect const FIntPoint OffsetMin = ConstrainedViewMin - TileViewMin; const FIntPoint OffsetMax = TileViewMax - ConstrainedViewMax; const int32 AntiAliasMargin = LetterboxData.bDrawLetterboxBorder ? 1 : 0; const int32 LeftThickness = FMath::Max(OffsetMin.X - AntiAliasMargin, 0); const int32 RightThickness = FMath::Max(OffsetMax.X - AntiAliasMargin, 0); const int32 TopThickness = FMath::Max(OffsetMin.Y - AntiAliasMargin, 0); const int32 BottomThickness = FMath::Max(OffsetMax.Y - AntiAliasMargin, 0); // These thicknesses will inform a later stage which pixels have been cleared out and that // should not be sampled. LetterboxData.LeftSamplePixelsClearedBeforeAccumulation = LeftThickness; LetterboxData.RightSamplePixelsClearedBeforeAccumulation = RightThickness; LetterboxData.TopSamplePixelsClearedBeforeAccumulation = TopThickness; LetterboxData.BottomSamplePixelsClearedBeforeAccumulation = BottomThickness; // Clear left if (LeftThickness > 0) { InCanvas.DrawTile(0, 0, LeftThickness, InSampleState.BackbufferSize.Y, 0.0f, 0.0f, 1.0f, 1.0f, FLinearColor::Black, nullptr, false); } // Clear right if (RightThickness > 0) { InCanvas.DrawTile(InSampleState.BackbufferSize.X - RightThickness, 0, InSampleState.BackbufferSize.X, InSampleState.BackbufferSize.Y, 0.0f, 0.0f, 1.0f, 1.0f, FLinearColor::Black, nullptr, false); } // Clear top if (TopThickness > 0) { InCanvas.DrawTile(0, 0, InSampleState.BackbufferSize.X, TopThickness, 0.0f, 0.0f, 1.0f, 1.0f, FLinearColor::Black, nullptr, false); } // Clear bottom if (BottomThickness > 0) { InCanvas.DrawTile(0, InSampleState.BackbufferSize.Y - BottomThickness, InSampleState.BackbufferSize.X, InSampleState.BackbufferSize.Y, 0.0f, 0.0f, 1.0f, 1.0f, FLinearColor::Black, nullptr, false); } InCanvas.Flush_GameThread(true); } // We have a pool of accumulators - we multi-thread the accumulation on the task graph, and for each frame, // the task has the previous samples as pre-reqs to keep the accumulation in order. However, each accumulator // can only work on one frame at a time, so we create a pool of them to work concurrently. This needs a limit // as large accumulations (16k) can take a lot of system RAM. TSharedPtr SampleAccumulator = nullptr; { SCOPE_CYCLE_COUNTER(STAT_MoviePipeline_WaitForAvailableAccumulator); SampleAccumulator = AccumulatorPool->BlockAndGetAccumulator_GameThread(InSampleState.OutputState.OutputFrameNumber, InPassIdentifier); } TSharedRef FramePayload = MakeShared(); FramePayload->PassIdentifier = InPassIdentifier; FramePayload->SampleState = InSampleState; FramePayload->SortingOrder = InSortingOrder; TSharedPtr LocalSurfaceQueue = GetOrCreateSurfaceQueue(InSampleState.BackbufferSize, (IViewCalcPayload*)(&FramePayload.Get())); MoviePipeline::FImageSampleAccumulationArgs AccumulationArgs; { AccumulationArgs.OutputMerger = GetPipeline()->OutputBuilder; AccumulationArgs.ImageAccumulator = StaticCastSharedPtr(SampleAccumulator->Accumulator); AccumulationArgs.bAccumulateAlpha = bAccumulatorIncludesAlpha; AccumulationArgs.LetterboxData = LetterboxData; } auto Callback = [this, FramePayload, AccumulationArgs, SampleAccumulator](TUniquePtr&& InPixelData) { bool bFinalSample = FramePayload->IsLastTile() && FramePayload->IsLastTemporalSample(); bool bFirstSample = FramePayload->IsFirstTile() && FramePayload->IsFirstTemporalSample(); FMoviePipelineBackgroundAccumulateTask Task; // There may be other accumulations for this accumulator which need to be processed first Task.LastCompletionEvent = SampleAccumulator->TaskPrereq; FGraphEventRef Event = Task.Execute([PixelData = MoveTemp(InPixelData), AccumulationArgs, bFinalSample, SampleAccumulator]() mutable { // Enqueue a encode for this frame onto our worker thread. MoviePipeline::AccumulateSample_TaskThread(MoveTemp(PixelData), AccumulationArgs); if (bFinalSample) { // Final sample has now been executed, break the pre-req chain and free the accumulator for reuse. SampleAccumulator->bIsActive = false; SampleAccumulator->TaskPrereq = nullptr; } }); SampleAccumulator->TaskPrereq = Event; this->OutstandingTasks.Add(Event); }; FRenderTarget* RenderTarget = InCanvas.GetRenderTarget(); ENQUEUE_RENDER_COMMAND(CanvasRenderTargetResolveCommand)( [LocalSurfaceQueue, FramePayload, Callback, RenderTarget](FRHICommandListImmediate& RHICmdList) mutable { // Enqueue a encode for this frame onto our worker thread. LocalSurfaceQueue->OnRenderTargetReady_RenderThread(RenderTarget->GetRenderTargetTexture(), FramePayload, MoveTemp(Callback)); }); } bool UMoviePipelineDeferredPassBase::IsAutoExposureAllowed(const FMoviePipelineRenderPassMetrics& InSampleState) const { // For high-res tiling, check if a view state exists for auto-exposure, with key {-1,-1} return (InSampleState.GetTileCount() == 1) || (CameraViewStateData.Num() && CameraViewStateData[0].TileData.Contains(FIntPoint(-1, -1))); } #if WITH_EDITOR FText UMoviePipelineDeferredPass_PathTracer::GetFooterText(UMoviePipelineExecutorJob* InJob) const { return NSLOCTEXT( "MovieRenderPipeline", "DeferredBasePassSetting_FooterText_PathTracer", "Sampling for the Path Tracer is controlled by the Anti-Aliasing settings and the Reference Motion Blur setting.\n" "All other Path Tracer settings are taken from the Post Process settings."); } #endif bool UMoviePipelineDeferredPassBase::CheckIfPathTracerIsSupported() const { bool bSupportsPathTracing = false; if (IsRayTracingEnabled()) { IConsoleVariable* PathTracingCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.PathTracing")); if (PathTracingCVar) { bSupportsPathTracing = PathTracingCVar->GetInt() != 0; } } return bSupportsPathTracing; } void UMoviePipelineDeferredPassBase::PathTracerValidationImpl() { const bool bSupportsPathTracing = CheckIfPathTracerIsSupported(); if (!bSupportsPathTracing) { const FText ValidationWarning = NSLOCTEXT("MovieRenderPipeline", "PathTracerValidation_Unsupported", "Path Tracing is currently not enabled for this project and this render pass will not work."); ValidationResults.Add(ValidationWarning); ValidationState = EMoviePipelineValidationState::Warnings; } } void UMoviePipelineDeferredPass_PathTracer::ValidateStateImpl() { Super::ValidateStateImpl(); PathTracerValidationImpl(); } void UMoviePipelineDeferredPass_PathTracer::SetupImpl(const MoviePipeline::FMoviePipelineRenderPassInitSettings& InPassInitSettings) { if (!CheckIfPathTracerIsSupported()) { UE_LOG(LogMovieRenderPipeline, Error, TEXT("Cannot render a Path Tracer pass, Path Tracer is not enabled by this project.")); GetPipeline()->Shutdown(true); return; } Super::SetupImpl(InPassInitSettings); } TSharedPtr UMoviePipelineDeferredPass_PathTracer::CalculateViewFamily(FMoviePipelineRenderPassMetrics& InOutSampleState, IViewCalcPayload* OptPayload) { // remove sub-pixel shift, since the path tracer does its own anti-aliasing InOutSampleState.SpatialShiftX = 0; InOutSampleState.SpatialShiftY = 0; InOutSampleState.OverlappedSubpixelShift = FVector2d(0.5, 0.5); return Super::CalculateViewFamily(InOutSampleState, OptPayload); } void UMoviePipelineDeferredPass_PathTracer::UpdateTelemetry(FMoviePipelineShotRenderTelemetry* InTelemetry) const { InTelemetry->bUsesPathTracer = true; InTelemetry->bUsesPPMs |= Algo::AnyOf(AdditionalPostProcessMaterials, [](const FMoviePipelinePostProcessPass& Pass) { return Pass.bEnabled; }); } bool UMoviePipelineDeferredPassBase::IsUsingDataLayers() const { int32 NumDataLayers = 0; for (FSoftObjectPath DataLayerAssetPath : DataLayers) { UDataLayerAsset* DataLayerAsset = Cast(DataLayerAssetPath.TryLoad()); if (DataLayerAsset) { NumDataLayers++; } } return NumDataLayers > 0; } int32 UMoviePipelineDeferredPassBase::GetNumStencilLayers() const { if (IsUsingDataLayers()) { // Because DataLayers are an asset, they can actually be null despite being in this list. int32 NumDataLayers = 0; for (FSoftObjectPath DataLayerAssetPath : DataLayers) { UDataLayerAsset* DataLayerAsset = Cast(DataLayerAssetPath.TryLoad()); if (DataLayerAsset) { NumDataLayers++; } } return NumDataLayers; } return ActorLayers.Num(); } TArray UMoviePipelineDeferredPassBase::GetStencilLayerNames() const { return UniqueStencilLayerNames; } FSoftObjectPath UMoviePipelineDeferredPassBase::GetValidDataLayerByIndex(const int32 InIndex) const { int32 NumValidDataLayers = 0; for (FSoftObjectPath DataLayerAssetPath : DataLayers) { UDataLayerAsset* DataLayerAsset = Cast(DataLayerAssetPath.TryLoad()); if (DataLayerAsset) { if (InIndex == NumValidDataLayers) { return DataLayerAssetPath; } NumValidDataLayers++; } } return FSoftObjectPath(); } bool UMoviePipelineDeferredPassBase::IsActorInLayer(AActor* InActor, int32 InLayerIndex) const { if (IsUsingDataLayers()) { FSoftObjectPath DataLayerAssetPath = GetValidDataLayerByIndex(InLayerIndex); UDataLayerAsset* DataLayerAsset = Cast(DataLayerAssetPath.TryLoad()); if (DataLayerAsset) { return InActor->ContainsDataLayer(DataLayerAsset); } } else { const FName& LayerName = ActorLayers[InLayerIndex].Name; return InActor->Layers.Contains(LayerName); } return false; } bool UMoviePipelineDeferredPassBase::IsActorInAnyStencilLayer(AActor* InActor) const { bool bInLayer = false; if (IsUsingDataLayers()) { for (FSoftObjectPath DataLayerAssetPath : DataLayers) { UDataLayerAsset* DataLayerAsset = Cast(DataLayerAssetPath.TryLoad()); if (DataLayerAsset) { bInLayer = !InActor->ContainsDataLayer(DataLayerAsset); if (!bInLayer) { break; } } } } else { for (const FActorLayer& AllLayer : ActorLayers) { bInLayer = !InActor->Layers.Contains(AllLayer.Name); if (!bInLayer) { break; } } } return bInLayer; } void UMoviePipelineDeferredPassBase::UpdateTelemetry(FMoviePipelineShotRenderTelemetry* InTelemetry) const { InTelemetry->bUsesDeferred = true; InTelemetry->bUsesPPMs |= Algo::AnyOf(AdditionalPostProcessMaterials, [](const FMoviePipelinePostProcessPass& Pass) { return Pass.bEnabled; }); } FString UMoviePipelineDeferredPassBase::GetNameForPostProcessMaterial(const UMaterialInterface* InMaterial) { FString MaterialName = InMaterial->GetName(); // Use the name specified in the post process pass if it's not empty. Otherwise fall back to the material's name. const FMoviePipelinePostProcessPass* MatchingPass = Algo::FindByPredicate(AdditionalPostProcessMaterials, [InMaterial](const FMoviePipelinePostProcessPass& InPass) { return InPass.Material == InMaterial; }); if (MatchingPass && !MatchingPass->Name.IsEmpty()) { MaterialName = MatchingPass->Name; } return MaterialName; }