You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This feature introduces the ability to capture the viewport's SceneColor at the exact frame a transition begins, write it into a UTextureRenderTarget2D, and automatically expose that render target to transition materials via a named texture parameter. This unlocks an entirely new class of transition effects that operate on a frozen still of the previous scene, such as screen shatters, page turns, slide-aways, glitch freezes, persona-style tile-spins, or dissolve-on-captured-frame effects, while maintaining the plugin's single Blueprint node workflow.
2. Motivation
The existing PostProcess architecture inherently processes the live scene every frame, meaning it cannot "freeze" the previous frame's image to manipulate it over time without external capture mechanisms.
Dismissed SceneCapture2D: Requires a separate view re-render which is too costly, and it suffers from diverging TAA and autoexposure history compared to the main viewport.
Dismissed DrawMaterialToRenderTarget: Does not have access to a valid SceneTexture:PostProcessInput0 context outside of the render pipeline, making it impossible to capture the post-processed frame accurately.
3. Design overview
The adopted solution (Plan C) uses a custom FSceneViewExtensionBase subclass that hooks into the render pipeline by subscribing to EPostProcessingPass::Tonemap. In the AfterPass callback, it issues an RDG AddCopyTexturePass to copy the tonemapped SceneColor into a pooled UTextureRenderTarget2D. Tonemap-after is chosen because the frame is TAA-resolved, tonemapped, in LDR, and pre-UI—matching the plugin's existing PP rendering layer semantics.
sequenceDiagram
participant GT as Game Thread (Subsystem)
participant SVE as Frame Capture SVE
participant RT as Render Thread (RDG)
GT->>SVE: StartTransition (Detect bCaptureFrameOnStart)
GT->>SVE: RequestCapture(FrameCaptureRT)
GT-->>GT: Set bPendingStartAfterCapture = true (Defer start)
note over SVE,RT: Render pipeline executes normally
SVE->>RT: SubscribeToPostProcessingPass (After Tonemap)
RT->>SVE: Execute AfterPass callback
SVE->>RT: AddCopyTexturePass (SceneColor -> FrameCaptureRT)
SVE-->>SVE: Set bCaptureDone = true (Atomic)
GT->>GT: Tick() checks IsCaptureFinished()
GT->>GT: Inject captured RT into OverrideParams
GT->>GT: Clear deferred flag, call StartTransition_Internal()
Loading
4. New types and member additions
// SVE DeclarationclassFTransitionFrameCaptureSVE : publicFSceneViewExtensionBase
{
public:FTransitionFrameCaptureSVE(const FAutoRegister& AutoReg);
// Called on Game ThreadvoidRequestCapture(UTextureRenderTarget2D* InTargetRT);
// Called on Game ThreadboolIsCaptureFinished() const;
// FSceneViewExtensionBase InterfacevirtualvoidSubscribeToPostProcessingPass(EPostProcessingPass PassId, FAfterPassCallbackDelegateArray& InOutPassCallbacks, bool bIsPassEnabled) override;
// The callback itself. (Signature requires verification against local engine source for UE 5.5/5.6)
FScreenPassTexture PostProcessPassAfterTonemap_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& View, const FPostProcessMaterialInputs& Inputs);
virtual int32 GetPriority() constoverride { return10; }
private:// Written on GT, read on RT
std::atomic<bool> bCaptureRequested{false};
// Written on RT, read on GT
std::atomic<bool> bCaptureDone{false};
// Only touched on GT
TWeakObjectPtr<UTextureRenderTarget2D> TargetRT;
};
// Subsystem additionsUSTRUCT()
struct FPendingStartArgs
{
GENERATED_BODY()
UPROPERTY()
TObjectPtr<UTransitionPreset> Preset;
ETransitionMode Mode;
float PlaySpeed;
bool bInvert;
bool bHoldAtMax;
FTransitionParameters OverrideParams;
};
// In UTransitionManagerSubsystem:UPROPERTY()
TObjectPtr<UTextureRenderTarget2D> FrameCaptureRT;
TSharedPtr<FTransitionFrameCaptureSVE, ESPMode::ThreadSafe> CaptureSVE;
bool bPendingStartAfterCapture = false;
FPendingStartArgs PendingStartArgs;
// Preset additions (In UTransitionPreset)UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "TransitionFX")
bool bCaptureFrameOnStart = false;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "TransitionFX", meta = (EditCondition = "bCaptureFrameOnStart"))
FName CapturedFrameParamName = TEXT("CapturedFrame");
// 0,0 means viewport-followUPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "TransitionFX", meta = (EditCondition = "bCaptureFrameOnStart"))
FIntPoint CaptureResolution = FIntPoint(0, 0);
5. Implementation plan, file by file
NEW Plugins/TransitionFX/Source/TransitionFX/Public/TransitionFrameCaptureSVE.h
Purpose: Declares the custom scene view extension for frame capture.
MODIFY Plugins/TransitionFX/Source/TransitionFX/Public/TransitionPreset.h and Plugins/TransitionFX/Source/TransitionFX/Private/TransitionPreset.cpp
Purpose: Expose capture configuration to the user.
Added/Modified:bCaptureFrameOnStart, CapturedFrameParamName, CaptureResolution to UTransitionPreset.
MODIFY Plugins/TransitionFX/Source/TransitionFX/Public/TransitionManagerSubsystem.h and Plugins/TransitionFX/Source/TransitionFX/Private/TransitionManagerSubsystem.cpp
Purpose: SVE lifecycle, RT pooling, and deferred transition start logic.
Create and store the SVE in Initialize, release in Deinitialize.
Lazy-allocate FrameCaptureRT and resize when the viewport size changes.
Rework StartTransition(...) into a public shim that detects bCaptureFrameOnStart, caches args, calls CaptureSVE->RequestCapture(FrameCaptureRT), sets bPendingStartAfterCapture = true, and returns. Extract the current body into a private StartTransition_Internal(...).
Extend ForceClear / Deinitialize / world-teardown paths to cancel a pending capture safely.
MODIFY Plugins/TransitionFX/TransitionFX.Build.cs
Purpose: Ensure necessary renderer and RDG modules are available.
Verify and, if missing, add RenderCore, Renderer, and Projects to PublicDependencyModuleNames / PrivateDependencyModuleNames as appropriate. Do not blindly add them; check first.
Reference Material: A reference material (e.g., M_Transition_CapturedShatter) that samples the CapturedFrame texture parameter should be created, though the asset creation itself is out of scope for this issue.
6. StartTransition flow change
Before:
Blueprint -> StartTransition()
-> Stop existing transition
-> Create/reuse PostProcessTransitionEffect
-> Initialize effect and apply override params
-> Set bIsTransitionActive = true
-> Immediately begin ticking progress
After:
Blueprint -> StartTransition()
-> Detect Preset->bCaptureFrameOnStart == true
-> YES:
-> Cache arguments in PendingStartArgs
-> CaptureSVE->RequestCapture(FrameCaptureRT)
-> Set bPendingStartAfterCapture = true
-> Return (Deferred start. Looks like a 1-2 frame wait in Blueprint/latent actions)
-> NO:
-> StartTransition_Internal() (Original behavior)
Tick()
-> If bPendingStartAfterCapture && CaptureSVE->IsCaptureFinished()
-> Inject FrameCaptureRT into PendingStartArgs.OverrideParams
-> bPendingStartAfterCapture = false
-> StartTransition_Internal()
Blueprint callers and the PlayTransitionAndWait latent action are entirely unaffected: the deferred-start simply looks like a 1–2 frame delay inside the existing "waiting for transition" state.
7. Edge cases and risks
Dynamic resolution / upscaler output mismatch: Source scene color and the destination UTextureRenderTarget2D extents may disagree. If so, AddCopyTexturePass will fail. Fallback to AddDrawScreenPass with UV remap when source and destination extents disagree.
Split screen and multi-view: SVE callback will trigger for all views. The callback must filter the SVE capture logic to only the primary player's FSceneView.
RT lifetime: Use a single pooled UTextureRenderTarget2D, resized on viewport changes, and explicitly released in Deinitialize.
Pause / slomo: Tonemapper still runs even when paused, so the capture pass will remain valid and execute correctly.
World teardown while a capture is in flight: Ensure an atomic cancel path exists in ForceClear and Deinitialize to abort the pending start.
UI layer not captured: This suffers from the same limitation as the plugin's existing PostProcess architecture. The UMG UI layer sits outside the main render pipeline. Refer to the plugin README "Limitations & Notes" regarding UMG.
UE minor-version drift: The signatures for FPostProcessMaterialInputs and FScreenPassTexture drift between UE 5.5 and 5.6. Verify against local engine source before finalizing the SVE AfterPass code.
Thread safety: The atomic flags (bCaptureRequested, bCaptureDone) manage the thread-boundary state. TWeakObjectPtr is game-thread-only and must be dereferenced and converted to a raw pointer on the GT, passing the required RT/resource to the render thread safely.
8. Testing plan
Manual Verification: Use an in-editor debug widget or UKismetRenderingLibrary::ExportRenderTarget2D to dump the captured frame and verify visually.
Compatibility Check: Ensure all 22+ existing presets continue to function properly with bCaptureFrameOnStart = false (the default).
Performance Profiling: Gather a before/after stat unit sample confirming there is no steady-state performance cost when the SVE is idle / capture is inactive.
9. Blueprint / BP API impact
None. PlayTransitionAndWait, PlayRandomTransitionAndWait, OpenLevelWithTransitionAndWait, and QuickFadeToBlack/FromBlack all retain their current signatures and behavior.
10. Out of scope
UMG-layer capture (to be handled in a future separate issue).
HDR / pre-tonemap capture path.
Authoring of more than one reference material.
Mobile / console validation.
11. Acceptance criteria
Builds successfully on UE 5.5 and UE 5.6.
Default bCaptureFrameOnStart = false preserves all existing behavior.
Example material correctly receives a valid captured frame at progress ≈ 0.
No new warnings in LogTransitionFX.
stat unit delta is under ~0.05 ms on a 1080p desktop reference when the capture is active for a single frame.
12. References
Runtime/Renderer/Public/PostProcess/PostProcessMaterialInputs.h (Verify path in local engine source)
Runtime/Renderer/Public/SceneViewExtension.h (Verify path in local engine source)
RDG headers for AddCopyTexturePass and AddDrawScreenPass (Verify path in local engine source)
1. Summary
This feature introduces the ability to capture the viewport's SceneColor at the exact frame a transition begins, write it into a
UTextureRenderTarget2D, and automatically expose that render target to transition materials via a named texture parameter. This unlocks an entirely new class of transition effects that operate on a frozen still of the previous scene, such as screen shatters, page turns, slide-aways, glitch freezes, persona-style tile-spins, or dissolve-on-captured-frame effects, while maintaining the plugin's single Blueprint node workflow.2. Motivation
The existing PostProcess architecture inherently processes the live scene every frame, meaning it cannot "freeze" the previous frame's image to manipulate it over time without external capture mechanisms.
SceneCapture2D: Requires a separate view re-render which is too costly, and it suffers from diverging TAA and autoexposure history compared to the main viewport.DrawMaterialToRenderTarget: Does not have access to a validSceneTexture:PostProcessInput0context outside of the render pipeline, making it impossible to capture the post-processed frame accurately.3. Design overview
The adopted solution (Plan C) uses a custom
FSceneViewExtensionBasesubclass that hooks into the render pipeline by subscribing toEPostProcessingPass::Tonemap. In the AfterPass callback, it issues an RDGAddCopyTexturePassto copy the tonemapped SceneColor into a pooledUTextureRenderTarget2D. Tonemap-after is chosen because the frame is TAA-resolved, tonemapped, in LDR, and pre-UI—matching the plugin's existing PP rendering layer semantics.sequenceDiagram participant GT as Game Thread (Subsystem) participant SVE as Frame Capture SVE participant RT as Render Thread (RDG) GT->>SVE: StartTransition (Detect bCaptureFrameOnStart) GT->>SVE: RequestCapture(FrameCaptureRT) GT-->>GT: Set bPendingStartAfterCapture = true (Defer start) note over SVE,RT: Render pipeline executes normally SVE->>RT: SubscribeToPostProcessingPass (After Tonemap) RT->>SVE: Execute AfterPass callback SVE->>RT: AddCopyTexturePass (SceneColor -> FrameCaptureRT) SVE-->>SVE: Set bCaptureDone = true (Atomic) GT->>GT: Tick() checks IsCaptureFinished() GT->>GT: Inject captured RT into OverrideParams GT->>GT: Clear deferred flag, call StartTransition_Internal()4. New types and member additions
5. Implementation plan, file by file
NEW
Plugins/TransitionFX/Source/TransitionFX/Public/TransitionFrameCaptureSVE.hFTransitionFrameCaptureSVEclass.NEW
Plugins/TransitionFX/Source/TransitionFX/Private/TransitionFrameCaptureSVE.cppFSceneViewExtensions::NewExtension<FTransitionFrameCaptureSVE>().FTransitionFrameCaptureSVEimplementation.MODIFY
Plugins/TransitionFX/Source/TransitionFX/Public/TransitionPreset.handPlugins/TransitionFX/Source/TransitionFX/Private/TransitionPreset.cppbCaptureFrameOnStart,CapturedFrameParamName,CaptureResolutiontoUTransitionPreset.MODIFY
Plugins/TransitionFX/Source/TransitionFX/Public/TransitionManagerSubsystem.handPlugins/TransitionFX/Source/TransitionFX/Private/TransitionManagerSubsystem.cppCaptureSVE,FrameCaptureRT,bPendingStartAfterCapture,FPendingStartArgs,StartTransitionshim,StartTransition_Internal.Initialize, release inDeinitialize.FrameCaptureRTand resize when the viewport size changes.StartTransition(...)into a public shim that detectsbCaptureFrameOnStart, caches args, callsCaptureSVE->RequestCapture(FrameCaptureRT), setsbPendingStartAfterCapture = true, and returns. Extract the current body into a privateStartTransition_Internal(...).ForceClear/Deinitialize/ world-teardown paths to cancel a pending capture safely.MODIFY
Plugins/TransitionFX/TransitionFX.Build.csRenderCore,Renderer, andProjectstoPublicDependencyModuleNames/PrivateDependencyModuleNamesas appropriate. Do not blindly add them; check first.Reference Material: A reference material (e.g.,
M_Transition_CapturedShatter) that samples theCapturedFrametexture parameter should be created, though the asset creation itself is out of scope for this issue.6. StartTransition flow change
Before:
After:
Blueprint callers and the
PlayTransitionAndWaitlatent action are entirely unaffected: the deferred-start simply looks like a 1–2 frame delay inside the existing "waiting for transition" state.7. Edge cases and risks
UTextureRenderTarget2Dextents may disagree. If so,AddCopyTexturePasswill fail. Fallback toAddDrawScreenPasswith UV remap when source and destination extents disagree.FSceneView.UTextureRenderTarget2D, resized on viewport changes, and explicitly released inDeinitialize.ForceClearandDeinitializeto abort the pending start.FPostProcessMaterialInputsandFScreenPassTexturedrift between UE 5.5 and 5.6. Verify against local engine source before finalizing the SVE AfterPass code.bCaptureRequested,bCaptureDone) manage the thread-boundary state.TWeakObjectPtris game-thread-only and must be dereferenced and converted to a raw pointer on the GT, passing the required RT/resource to the render thread safely.8. Testing plan
UKismetRenderingLibrary::ExportRenderTarget2Dto dump the captured frame and verify visually.bCaptureFrameOnStart = false(the default).stat unitsample confirming there is no steady-state performance cost when the SVE is idle / capture is inactive.9. Blueprint / BP API impact
None.
PlayTransitionAndWait,PlayRandomTransitionAndWait,OpenLevelWithTransitionAndWait, andQuickFadeToBlack/FromBlackall retain their current signatures and behavior.10. Out of scope
11. Acceptance criteria
bCaptureFrameOnStart = falsepreserves all existing behavior.LogTransitionFX.stat unitdelta is under ~0.05 ms on a 1080p desktop reference when the capture is active for a single frame.12. References
Runtime/Renderer/Public/PostProcess/PostProcessMaterialInputs.h(Verify path in local engine source)Runtime/Renderer/Public/SceneViewExtension.h(Verify path in local engine source)AddCopyTexturePassandAddDrawScreenPass(Verify path in local engine source)