帧率优化, GPU数据读操作异步
This commit is contained in:
parent
c8aa3652ff
commit
5654428986
@ -11,6 +11,14 @@ ACameraCaptureActor::ACameraCaptureActor()
|
|||||||
// PrimaryActorTick.bCanEverTick = false;
|
// PrimaryActorTick.bCanEverTick = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ACameraCaptureActor::CaptureNow()
|
||||||
|
{
|
||||||
|
if (ViewCapture)
|
||||||
|
{
|
||||||
|
ViewCapture->CaptureScene();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ACameraCaptureActor::BeginPlay()
|
void ACameraCaptureActor::BeginPlay()
|
||||||
{
|
{
|
||||||
Super::BeginPlay();
|
Super::BeginPlay();
|
||||||
@ -25,8 +33,8 @@ void ACameraCaptureActor::PostEditChangeProperty(FPropertyChangedEvent& Property
|
|||||||
// 任何关键参数变化都重建(避免 RT 尺寸/数量不匹配)
|
// 任何关键参数变化都重建(避免 RT 尺寸/数量不匹配)
|
||||||
BuildViewCaptures();
|
BuildViewCaptures();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
|
#endif
|
||||||
void ACameraCaptureActor::BuildViewCaptures()
|
void ACameraCaptureActor::BuildViewCaptures()
|
||||||
{
|
{
|
||||||
if (ViewCapture)
|
if (ViewCapture)
|
||||||
@ -35,14 +43,10 @@ void ACameraCaptureActor::BuildViewCaptures()
|
|||||||
ViewCapture = nullptr;
|
ViewCapture = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ViewRT)
|
|
||||||
{
|
|
||||||
ViewRT = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewRT = NewObject<UTextureRenderTarget2D>(this);
|
ViewRT = NewObject<UTextureRenderTarget2D>(this);
|
||||||
|
ViewRT->RenderTargetFormat = RTF_RGBA8;
|
||||||
|
ViewRT->InitCustomFormat(ViewWidth, ViewHeight, PF_B8G8R8A8, true);
|
||||||
ViewRT->ClearColor = FLinearColor::Black;
|
ViewRT->ClearColor = FLinearColor::Black;
|
||||||
ViewRT->InitAutoFormat(ViewWidth, ViewHeight);
|
|
||||||
ViewRT->UpdateResourceImmediate(true);
|
ViewRT->UpdateResourceImmediate(true);
|
||||||
|
|
||||||
ViewCapture = NewObject<USceneCaptureComponent2D>(this, TEXT("ViewCapture"));
|
ViewCapture = NewObject<USceneCaptureComponent2D>(this, TEXT("ViewCapture"));
|
||||||
@ -50,7 +54,8 @@ void ACameraCaptureActor::BuildViewCaptures()
|
|||||||
ViewCapture->RegisterComponent();
|
ViewCapture->RegisterComponent();
|
||||||
|
|
||||||
ViewCapture->TextureTarget = ViewRT;
|
ViewCapture->TextureTarget = ViewRT;
|
||||||
ViewCapture->CaptureSource = ESceneCaptureSource::SCS_FinalColorHDR;
|
ViewCapture->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR;
|
||||||
|
|
||||||
ViewCapture->bCaptureEveryFrame = false;
|
ViewCapture->bCaptureEveryFrame = false;
|
||||||
ViewCapture->bCaptureOnMovement = false;
|
ViewCapture->bCaptureOnMovement = false;
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,7 @@ public:
|
|||||||
USceneCaptureComponent2D* GetViewCapture() { return ViewCapture; }
|
USceneCaptureComponent2D* GetViewCapture() { return ViewCapture; }
|
||||||
UTextureRenderTarget2D* GetViewRT() { return ViewRT; }
|
UTextureRenderTarget2D* GetViewRT() { return ViewRT; }
|
||||||
|
|
||||||
|
void CaptureNow();
|
||||||
|
|
||||||
// ===== 多视图参数 =====
|
// ===== 多视图参数 =====
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,31 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
|
#include "RHIGPUReadback.h"
|
||||||
|
#include "RHICommandList.h"
|
||||||
|
#include "Engine/TextureRenderTarget2D.h"
|
||||||
#include "Subsystems/WorldSubsystem.h"
|
#include "Subsystems/WorldSubsystem.h"
|
||||||
#include "Tickable.h"
|
#include "Tickable.h"
|
||||||
#include "CameraCaptureSubSystem.generated.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;
|
class ACameraCaptureActor;
|
||||||
|
|
||||||
@ -37,7 +56,13 @@ private:
|
|||||||
|
|
||||||
void CaptureViewToImageTick();
|
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;
|
TArray<TWeakObjectPtr<ACameraCaptureActor>> AllCaptureCameras;
|
||||||
|
|
||||||
@ -45,4 +70,7 @@ private:
|
|||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString OutPutDirectoryPath;
|
FString OutPutDirectoryPath;
|
||||||
|
|
||||||
|
TUniquePtr<FPendingAtlasCapture> PendingAtlasCapture;
|
||||||
|
bool bReadbackInFlight = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -81,67 +81,189 @@ void UCameraCaptureSubSystem::CaptureViewToImageTick()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TArray<FColor> AtlasPixels;
|
// 已经有一批在等 GPU 回读,就先尝试取回
|
||||||
|
if (bReadbackInFlight)
|
||||||
|
{
|
||||||
|
TryResolveCaptureViewAtlas();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有在飞行中的回读,就启动新的一批
|
||||||
|
BeginCaptureViewToAtlas();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void UCameraCaptureSubSystem::BeginCaptureViewToAtlas()
|
||||||
|
{
|
||||||
const int32 AtlasW = ViewWidth * AtlasGridX;
|
const int32 AtlasW = ViewWidth * AtlasGridX;
|
||||||
const int32 AtlasH = ViewHeight * AtlasGridY;
|
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())
|
if (!AllCaptureCameras[CameraIndex].IsValid())
|
||||||
{
|
{
|
||||||
continue;
|
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)
|
if (!RenderTarget)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
FTextureRenderTargetResource* RenderTargetRes = RenderTarget->GameThread_GetRenderTargetResource();
|
FTextureRenderTargetResource* RTRes = RenderTarget->GameThread_GetRenderTargetResource();
|
||||||
if (!RenderTargetRes)
|
if (!RTRes)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
TArray<FColor> Pixels;
|
FRHITexture* SourceTexture = RTRes->GetRenderTargetTexture();
|
||||||
Pixels.SetNumUninitialized(ViewWidth * ViewHeight);
|
if (!SourceTexture)
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
const int32 DstY = DstY0 + y;
|
continue;
|
||||||
if (DstY < 0 || DstY >= AtlasH) continue;
|
}
|
||||||
|
|
||||||
const int32 SrcRow = y * ViewWidth;
|
FCameraReadbackItem Item;
|
||||||
const int32 DstRow = DstY * AtlasW;
|
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 (ReadbackPtr && SourceTexture)
|
||||||
if (DstX < 0 || DstX >= AtlasW) continue;
|
{
|
||||||
|
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) );
|
const FString FullPath = FPaths::Combine( OutPutDirectoryPath, FString::Printf(TEXT("atlas_%010lld.png"), (int64)GFrameCounter) );
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user