Out of pure interest I was trying to make it work by actually getting last rendered frame, and found a function GetViewportScreenShot on viewport, it does just that.
However, I then wasn’t able to correctly determine how to use that data. The only thing I managed to achieve is copying that data onto provided TextureRenderTarget2D. That works just fine (resulting quality isn’t great, but good enough). However, the drawing part turned out to cause a notable freeze, so I ended up not using it.
Maybe someone else can figure it out better? I’m pretty sure this entire approach isn’t right.
Please note that I had absolutely ZERO idea what am I doing here.
#include "Kismet/KismetRenderingLibrary.h"
#include "Engine/GameViewportClient.h"
#include "Engine/Canvas.h"
#include "CanvasItem.h"
#include "Slate/SceneViewport.h"
#include "ViewportFrameCapture.generated.h"
UCLASS()
class UViewportFrameCapture : public UObject
{
GENERATED_BODY()
UFUNCTION(BlueprintCallable)
static bool GetLastRenderedFrame(UObject* WorldContext, UTextureRenderTarget2D*& OutTexture)
{
if (!OutTexture)
{
return false;
}
TArray<FColor> BitMap;
FVector2D ViewportSize;
bool bReceivedColorBuffer = false;
if (GEngine && GEngine->GameViewport)
{
if (FSceneViewport* Viewport = GEngine->GameViewport->GetGameViewport())
{
// read last rendered frame
bReceivedColorBuffer = GetViewportScreenShot(Viewport, BitMap, FIntRect(0, 0, 0, 0));
// find viewport size
GEngine->GameViewport->GetViewportSize(ViewportSize);
}
}
if (!bReceivedColorBuffer)
{
return false;
}
// set up pixel format for render target
const EPixelFormat RequestedFormat = FSlateApplication::Get().GetRenderer()->GetSlateRecommendedColorFormat();
OutTexture->InitCustomFormat(ViewportSize.X, ViewportSize.Y, RequestedFormat, true);
OutTexture->UpdateResourceImmediate(true);
// create canvas object to draw on
UCanvas* Canvas;
FVector2D CanvasSize;
FDrawToRenderTargetContext Context;
UKismetRenderingLibrary::BeginDrawCanvasToRenderTarget(WorldContext, OutTexture, Canvas, CanvasSize, Context);
// this partially repeats BeginDrawCanvasToRenderTarget, except we create canvas in DeferDrawing mode,
// otherwise rendering thread will freeze for several seconds in CreateRHIBuffer
UWorld* World = GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::LogAndReturnNull);
FTextureRenderTargetResource* RenderTargetResource = OutTexture->GameThread_GetRenderTargetResource();
FCanvas* ParamsCanvas = new FCanvas(
RenderTargetResource,
nullptr,
World,
World->FeatureLevel,
FCanvas::CDM_DeferDrawing);
Canvas->Init(OutTexture->SizeX, OutTexture->SizeY, nullptr, ParamsCanvas);
// TODO: this is unoptimzed, causes about 0.5s freeze. There must be a better way to copy bitmap onto canvas, but I wasnt able to find it
for (int i = 0; i < BitMap.Num(); i++)
{
const FVector2D Pos(i % (int)ViewportSize.X, i / (int)ViewportSize.X);
FCanvasLineItem Item(Pos, Pos);
Item.LineThickness = 1.0f;
Item.SetColor(BitMap[i]);
Canvas->DrawItem(Item);
}
UKismetRenderingLibrary::EndDrawCanvasToRenderTarget(WorldContext, Context);
return true;
}
};