帧率优化, GPU数据读操作异步
This commit is contained in:
parent
c8aa3652ff
commit
5654428986
@ -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;
|
||||
|
||||
|
||||
@ -39,6 +39,7 @@ public:
|
||||
USceneCaptureComponent2D* GetViewCapture() { return ViewCapture; }
|
||||
UTextureRenderTarget2D* GetViewRT() { return ViewRT; }
|
||||
|
||||
void CaptureNow();
|
||||
|
||||
// ===== 多视图参数 =====
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
};
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
AsyncWriteImageToDisk(AtlasPixels, AtlasW, AtlasH);
|
||||
bReadbackInFlight = PendingAtlasCapture->Items.Num() > 0;
|
||||
}
|
||||
|
||||
void UCameraCaptureSubSystem::AsyncWriteImageToDisk(TArray<FColor>& AtlasPixels, const int32 AtlasW, const int32 AtlasH)
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
const FString FullPath = FPaths::Combine( OutPutDirectoryPath, FString::Printf(TEXT("atlas_%010lld.png"), (int64)GFrameCounter) );
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user