Files
UnrealEngine/Engine/Source/Runtime/MovieSceneCapture/Public/FrameGrabber.h
2025-05-18 13:04:45 +08:00

229 lines
8.0 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "HAL/ThreadSafeCounter.h"
#include "HAL/ThreadSafeBool.h"
#include "Slate/SceneViewport.h"
class SWindow;
/** A single, managed surface used as a render target resolution destination */
struct FViewportSurfaceReader
{
/** Constructor */
MOVIESCENECAPTURE_API FViewportSurfaceReader(EPixelFormat InPixelFormat, FIntPoint InBufferSize);
/** Destructor */
MOVIESCENECAPTURE_API ~FViewportSurfaceReader();
/** Initialize this reader so that it can be waited on. */
MOVIESCENECAPTURE_API void Initialize();
/** Wait for this reader to become available, if it's currently in use */
MOVIESCENECAPTURE_API void BlockUntilAvailable();
/** Safely resets the state of the wait event. When doing latent surface reading sometimes we may want to just bail on reading a given frame.
* Should only be performed after flushing rendering commands.
*/
MOVIESCENECAPTURE_API void Reset();
/**
* Resolve the specified viewport RHI, calling the specified callback with the result.
*
* @param BackBuffer The backbuffer to resolve
* @param Callback Callback to call with the locked texture data. This will be called on an undefined thread.
*/
MOVIESCENECAPTURE_API void ResolveRenderTarget(FViewportSurfaceReader* RenderToReadback, const FTextureRHIRef& BackBuffer, TFunction<void(FColor*, int32, int32)> Callback);
/** Get the current size of the texture */
MOVIESCENECAPTURE_API FIntPoint GetCurrentSize() const;
/** Set the rectangle within which to read pixels */
void SetCaptureRect(FIntRect InCaptureRect) { CaptureRect = InCaptureRect; }
/** Set the window size that we expect from the BackBuffer */
void SetWindowSize(FIntPoint InWindowSize) { WindowSize = InWindowSize; }
bool WasEverQueued() const { return bQueuedForCapture; }
protected:
/** Set up this surface to the specified width/height */
MOVIESCENECAPTURE_API void Resize(uint32 Width, uint32 Height);
/** Whether this surface reader is enabled or not */
FThreadSafeBool bEnabled;
/** Optional event that is triggered when the surface is no longer in use */
FEvent* AvailableEvent;
/** Texture used to store the resolved render target */
FTextureRHIRef ReadbackTexture;
/** The rectangle to read from the surface */
FIntRect CaptureRect;
/** In windows mode, the size of the widget with the border */
FIntPoint WindowSize;
/** The desired pixel format of the resolved textures */
EPixelFormat PixelFormat;
/** Whether this reader is enabled or not. */
bool bIsEnabled;
bool bQueuedForCapture;
};
struct IFramePayload
{
virtual ~IFramePayload() {}
/**
* Called when the buffer is now available in CPU ram
* Return true if you would like to execute the default behavior. (If you return false, GetCapturedFrames will be empty).
*/
virtual bool OnFrameReady_RenderThread(FColor* ColorBuffer, FIntPoint BufferSize, FIntPoint TargetSize) const { return true; }
};
typedef TSharedPtr<IFramePayload, ESPMode::ThreadSafe> FFramePayloadPtr;
/** Structure representing a captured frame */
struct FCapturedFrameData
{
FCapturedFrameData(FIntPoint InBufferSize, FFramePayloadPtr InPayload) : BufferSize(InBufferSize), Payload(MoveTemp(InPayload)) {}
FCapturedFrameData(FCapturedFrameData&& In) : ColorBuffer(MoveTemp(In.ColorBuffer)), BufferSize(In.BufferSize), Payload(MoveTemp(In.Payload)) {}
FCapturedFrameData& operator=(FCapturedFrameData&& In){ ColorBuffer = MoveTemp(In.ColorBuffer); BufferSize = In.BufferSize; Payload = MoveTemp(In.Payload); return *this; }
template<typename T>
T* GetPayload() { return static_cast<T*>(Payload.Get()); }
/** The color buffer of the captured frame */
TArray<FColor> ColorBuffer;
/** The size of the resulting color buffer */
FIntPoint BufferSize;
/** Optional user-specified payload */
FFramePayloadPtr Payload;
private:
FCapturedFrameData(const FCapturedFrameData& In);
FCapturedFrameData& operator=(const FCapturedFrameData& In);
};
/**
* Class responsible for resolving render target data for a specific viewport in an efficient manner
* Internally, the class uses a fixed array of resolution surfaces, and dispatches rendering commands
* to resolve the viewport render target into a specific index into this array. This means we can
* resolve the render target data without having to wait, or flush rendering commands.
*/
class FFrameGrabber
{
public:
/**
* Construct this frame grabber
*
* @param InViewport The viewport we are to grab frames for
* @param DesiredBufferSize The desired size of captured frames
* @param InPixelFormat The desired pixel format to store captured frames as
* @param InNumSurfaces The number of destination surfaces contained in our buffer
*/
MOVIESCENECAPTURE_API FFrameGrabber(TSharedRef<FSceneViewport> Viewport, FIntPoint DesiredBufferSize, EPixelFormat InPixelFormat = PF_B8G8R8A8, uint32 NumSurfaces = 3);
/** Destructor */
MOVIESCENECAPTURE_API ~FFrameGrabber();
public:
/** Instruct the frame grabber to start capturing frames */
MOVIESCENECAPTURE_API void StartCapturingFrames();
/** Check whether we're capturing frames or not */
MOVIESCENECAPTURE_API bool IsCapturingFrames() const;
/** Instruct the frame grabber capture this frame, when it receives an event from slate */
MOVIESCENECAPTURE_API void CaptureThisFrame(FFramePayloadPtr Payload);
/** Stop capturing frames */
MOVIESCENECAPTURE_API void StopCapturingFrames();
/** Shut down this grabber, ensuring that any threaded operations are finished */
MOVIESCENECAPTURE_API void Shutdown();
public:
/** Check whether we have any outstanding frames or not */
MOVIESCENECAPTURE_API bool HasOutstandingFrames() const;
/** Retrieve any frames we may have captured */
MOVIESCENECAPTURE_API TArray<FCapturedFrameData> GetCapturedFrames();
protected:
/** Callback for when a backbuffer is ready for reading (called on render thread) */
MOVIESCENECAPTURE_API void OnBackBufferReadyToPresentCallback(SWindow& SlateWindow, const FTextureRHIRef& BackBuffer);
/** Called when the specified surface index has been locked for reading with the render target data (called on render thread) */
MOVIESCENECAPTURE_API void OnFrameReady(int32 SurfaceIndex, FColor* ColorBuffer, int32 Width, int32 Height);
private:
/** Non-copyable */
FFrameGrabber(const FFrameGrabber&);
FFrameGrabber& operator=(const FFrameGrabber&);
/**
* Pointer to the window we want to capture.
* Only held for comparison inside OnBackBufferReadyToPresentCallback - never to be dereferenced or cast to an SWindow.
* Held as a raw pointer to ensure that no referenc counting occurs from the background thread in OnBackBufferReadyToPresentCallback.
*/
void* TargetWindowPtr;
/** Delegate handle for the OnBackBufferReadyToPresent event */
FDelegateHandle OnBackBufferReadyToPresent;
/** Array of captured frames */
TArray<FCapturedFrameData> CapturedFrames;
/** Lock to protect the above array */
mutable FCriticalSection CapturedFramesMutex;
/** Array of surfaces that we resolve the viewport RHI to. Fixed allocation - should never be resized */
struct FResolveSurface
{
FResolveSurface(EPixelFormat InPixelFormat, FIntPoint BufferSize) : Surface(InPixelFormat, BufferSize) {}
FFramePayloadPtr Payload;
FViewportSurfaceReader Surface;
};
TArray<FResolveSurface> Surfaces;
/** Index into the above array to the next surface that we should use - only accessed on main thread */
int32 CurrentFrameIndex;
/** The index that we should capture the next rendered slate window into */
int32 SlateRenderIndex;
/** The total number of frames we are currently waiting on */
FThreadSafeCounter OutstandingFrameCount;
/** Only to be accessed from the render thread - array of frame payloads to be captured from the rendered slate window sorted first to last. */
TArray<FFramePayloadPtr> RenderThread_PendingFramePayloads;
int32 FrameGrabLatency;
/** The current state of the grabber */
enum class EFrameGrabberState
{
Inactive, Active, PendingShutdown
};
EFrameGrabberState State;
/** The desired target size to resolve frames to */
FIntPoint TargetSize;
};