帧率优化, GPU数据读操作异步

This commit is contained in:
顾宏 2026-03-06 18:25:27 +08:00
parent c8aa3652ff
commit 5654428986
4 changed files with 216 additions and 60 deletions

View File

@ -11,6 +11,14 @@ ACameraCaptureActor::ACameraCaptureActor()
// PrimaryActorTick.bCanEverTick = false;
}
void ACameraCaptureActor::CaptureNow()
{
if (ViewCapture)
{
ViewCapture->CaptureScene();
}
}
void ACameraCaptureActor::BeginPlay()
{
Super::BeginPlay();
@ -25,8 +33,8 @@ void ACameraCaptureActor::PostEditChangeProperty(FPropertyChangedEvent& Property
// 任何关键参数变化都重建(避免 RT 尺寸/数量不匹配)
BuildViewCaptures();
}
#endif
#endif
void ACameraCaptureActor::BuildViewCaptures()
{
if (ViewCapture)
@ -35,14 +43,10 @@ void ACameraCaptureActor::BuildViewCaptures()
ViewCapture = nullptr;
}
if (ViewRT)
{
ViewRT = nullptr;
}
ViewRT = NewObject<UTextureRenderTarget2D>(this);
ViewRT->RenderTargetFormat = RTF_RGBA8;
ViewRT->InitCustomFormat(ViewWidth, ViewHeight, PF_B8G8R8A8, true);
ViewRT->ClearColor = FLinearColor::Black;
ViewRT->InitAutoFormat(ViewWidth, ViewHeight);
ViewRT->UpdateResourceImmediate(true);
ViewCapture = NewObject<USceneCaptureComponent2D>(this, TEXT("ViewCapture"));
@ -50,7 +54,8 @@ void ACameraCaptureActor::BuildViewCaptures()
ViewCapture->RegisterComponent();
ViewCapture->TextureTarget = ViewRT;
ViewCapture->CaptureSource = ESceneCaptureSource::SCS_FinalColorHDR;
ViewCapture->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR;
ViewCapture->bCaptureEveryFrame = false;
ViewCapture->bCaptureOnMovement = false;

View File

@ -39,6 +39,7 @@ public:
USceneCaptureComponent2D* GetViewCapture() { return ViewCapture; }
UTextureRenderTarget2D* GetViewRT() { return ViewRT; }
void CaptureNow();
// ===== 多视图参数 =====

View File

@ -3,12 +3,31 @@
#pragma once
#include "CoreMinimal.h"
#include "RHIGPUReadback.h"
#include "RHICommandList.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Subsystems/WorldSubsystem.h"
#include "Tickable.h"
#include "CameraCaptureSubSystem.generated.h"
struct FCameraReadbackItem
{
int32 CameraIndex = INDEX_NONE;
int32 Width = 0;
int32 Height = 0;
TUniquePtr<FRHIGPUTextureReadback> Readback;
};
struct FPendingAtlasCapture
{
int64 FrameId = 0;
int32 AtlasW = 0;
int32 AtlasH = 0;
TArray<FCameraReadbackItem> Items;
};
class ACameraCaptureActor;
@ -37,7 +56,13 @@ private:
void CaptureViewToImageTick();
void AsyncWriteImageToDisk(TArray<FColor>& AtlasPixels, const int32 AtlasW, const int32 AtlasH);
// :发起 Capture + Enqueue GPU Readback
void BeginCaptureViewToAtlas();
// 轮询并取回 GPU 数据,再拼图
void TryResolveCaptureViewAtlas();
void AsyncWriteImageToDisk(TArray<FColor> AtlasPixels, const int32 AtlasW, const int32 AtlasH);
TArray<TWeakObjectPtr<ACameraCaptureActor>> AllCaptureCameras;
@ -45,4 +70,7 @@ private:
UPROPERTY()
FString OutPutDirectoryPath;
TUniquePtr<FPendingAtlasCapture> PendingAtlasCapture;
bool bReadbackInFlight = false;
};

View File

@ -81,67 +81,189 @@ void UCameraCaptureSubSystem::CaptureViewToImageTick()
return;
}
TArray<FColor> AtlasPixels;
// 已经有一批在等 GPU 回读,就先尝试取回
if (bReadbackInFlight)
{
TryResolveCaptureViewAtlas();
return;
}
// 没有在飞行中的回读,就启动新的一批
BeginCaptureViewToAtlas();
}
void UCameraCaptureSubSystem::BeginCaptureViewToAtlas()
{
const int32 AtlasW = ViewWidth * AtlasGridX;
const int32 AtlasH = ViewHeight * AtlasGridY;
AtlasPixels.SetNumZeroed(AtlasW * AtlasH);
for (int32 CameraIndex = 0; CameraIndex < AllCaptureCameras.Num(); CameraIndex++)
PendingAtlasCapture = MakeUnique<FPendingAtlasCapture>();
PendingAtlasCapture->FrameId = (int64)GFrameCounter;
PendingAtlasCapture->AtlasW = AtlasW;
PendingAtlasCapture->AtlasH = AtlasH;
const int32 MaxTiles = AtlasGridX * AtlasGridY;
const int32 CameraCount = FMath::Min(AllCaptureCameras.Num(), MaxTiles);
PendingAtlasCapture->Items.Reserve(CameraCount);
for (int32 CameraIndex = 0; CameraIndex < CameraCount; ++CameraIndex)
{
if (!AllCaptureCameras[CameraIndex].IsValid())
{
continue;
}
UTextureRenderTarget2D* RenderTarget = AllCaptureCameras[CameraIndex]->GetViewRT();
ACameraCaptureActor* CaptureActor = AllCaptureCameras[CameraIndex].Get();
if (!CaptureActor)
{
continue;
}
// 先让这台 SceneCapture 更新一次 RT
CaptureActor->CaptureNow(); // 你自己封装一个函数,内部调用 ViewCapture->CaptureScene()
UTextureRenderTarget2D* RenderTarget = CaptureActor->GetViewRT();
if (!RenderTarget)
{
continue;
}
FTextureRenderTargetResource* RenderTargetRes = RenderTarget->GameThread_GetRenderTargetResource();
if (!RenderTargetRes)
FTextureRenderTargetResource* RTRes = RenderTarget->GameThread_GetRenderTargetResource();
if (!RTRes)
{
continue;
}
TArray<FColor> Pixels;
Pixels.SetNumUninitialized(ViewWidth * ViewHeight);
FReadSurfaceDataFlags ReadFlags(RCM_UNorm);
ReadFlags.SetLinearToGamma(true);
if (!RenderTargetRes->ReadPixels(Pixels, ReadFlags)) continue;
FlipVertical(Pixels, ViewWidth, ViewHeight);
const int32 GX = CameraIndex % AtlasGridX;
const int32 GY = CameraIndex / AtlasGridX;
const int32 DstX0 = GX * ViewWidth;
const int32 DstY0 = GY * ViewHeight;
for (int32 y = 0; y < ViewHeight; ++y)
FRHITexture* SourceTexture = RTRes->GetRenderTargetTexture();
if (!SourceTexture)
{
const int32 DstY = DstY0 + y;
if (DstY < 0 || DstY >= AtlasH) continue;
continue;
}
const int32 SrcRow = y * ViewWidth;
const int32 DstRow = DstY * AtlasW;
FCameraReadbackItem Item;
Item.CameraIndex = CameraIndex;
Item.Width = ViewWidth;
Item.Height = ViewHeight;
Item.Readback = MakeUnique<FRHIGPUTextureReadback>(
FName(*FString::Printf(TEXT("CameraReadback_%d"), CameraIndex))
);
for (int32 x = 0; x < ViewWidth; ++x)
FRHIGPUTextureReadback* ReadbackPtr = Item.Readback.Get();
const FResolveRect CopyRect(0, 0, ViewWidth, ViewHeight);
ENQUEUE_RENDER_COMMAND(QueueCameraTextureReadback)(
[ReadbackPtr, SourceTexture, CopyRect](FRHICommandListImmediate& RHICmdList)
{
const int32 DstX = DstX0 + x;
if (DstX < 0 || DstX >= AtlasW) continue;
if (ReadbackPtr && SourceTexture)
{
ReadbackPtr->EnqueueCopy(RHICmdList, SourceTexture, CopyRect);
}
});
AtlasPixels[DstRow + DstX] = Pixels[SrcRow + x];
PendingAtlasCapture->Items.Add(MoveTemp(Item));
}
bReadbackInFlight = PendingAtlasCapture->Items.Num() > 0;
}
void UCameraCaptureSubSystem::TryResolveCaptureViewAtlas()
{
if (!bReadbackInFlight || !PendingAtlasCapture.IsValid())
{
return;
}
// 只要有一个还没准备好,就下一帧再来
for (const FCameraReadbackItem& Item : PendingAtlasCapture->Items)
{
if (!Item.Readback.IsValid() || !Item.Readback->IsReady())
{
return;
}
}
// 全部 ready开始组装 atlas
TArray<FColor> AtlasPixels;
AtlasPixels.SetNumZeroed(PendingAtlasCapture->AtlasW * PendingAtlasCapture->AtlasH);
for (const FCameraReadbackItem& Item : PendingAtlasCapture->Items)
{
if (!Item.Readback.IsValid())
{
continue;
}
int32 RowPitchInPixels = 0;
int32 BufferHeight = 0;
void* RawPtr = Item.Readback->Lock(RowPitchInPixels, &BufferHeight);
if (!RawPtr)
{
Item.Readback->Unlock();
continue;
}
// RawPtr 指向 GPU 读回后的 CPU 可访问数据
const FColor* SrcPixels = static_cast<const FColor*>(RawPtr);
// 先拷到一个连续的小图缓存里
TArray<FColor> ViewPixels;
ViewPixels.SetNumUninitialized(Item.Width * Item.Height);
for (int32 Y = 0; Y < Item.Height; ++Y)
{
const FColor* SrcRow = SrcPixels + Y * RowPitchInPixels;
FColor* DstRow = ViewPixels.GetData() + Y * Item.Width;
FMemory::Memcpy(DstRow, SrcRow, sizeof(FColor) * Item.Width);
}
Item.Readback->Unlock();
// 如果你最后导出的图上下颠倒,这里保留翻转
FlipVertical(ViewPixels, Item.Width, Item.Height);
const int32 GX = Item.CameraIndex % AtlasGridX;
const int32 GY = Item.CameraIndex / AtlasGridX;
const int32 DstX0 = GX * Item.Width;
const int32 DstY0 = GY * Item.Height;
for (int32 Y = 0; Y < Item.Height; ++Y)
{
const int32 AtlasY = DstY0 + Y;
if (AtlasY < 0 || AtlasY >= PendingAtlasCapture->AtlasH)
{
continue;
}
const int32 SrcRowIndex = Y * Item.Width;
const int32 DstRowIndex = AtlasY * PendingAtlasCapture->AtlasW;
for (int32 X = 0; X < Item.Width; ++X)
{
const int32 AtlasX = DstX0 + X;
if (AtlasX < 0 || AtlasX >= PendingAtlasCapture->AtlasW)
{
continue;
}
AtlasPixels[DstRowIndex + AtlasX] = ViewPixels[SrcRowIndex + X];
}
}
}
AsyncWriteImageToDisk(AtlasPixels, AtlasW, AtlasH);
const int32 AtlasW = PendingAtlasCapture->AtlasW;
const int32 AtlasH = PendingAtlasCapture->AtlasH;
PendingAtlasCapture.Reset();
bReadbackInFlight = false;
AsyncWriteImageToDisk(MoveTemp(AtlasPixels), AtlasW, AtlasH);
}
void UCameraCaptureSubSystem::AsyncWriteImageToDisk(TArray<FColor>& AtlasPixels, const int32 AtlasW, const int32 AtlasH)
void UCameraCaptureSubSystem::AsyncWriteImageToDisk(TArray<FColor> AtlasPixels, const int32 AtlasW, const int32 AtlasH)
{
const FString FullPath = FPaths::Combine( OutPutDirectoryPath, FString::Printf(TEXT("atlas_%010lld.png"), (int64)GFrameCounter) );