CameraCaptureDemo/Plugins/CameraCapture/Source/CameraCaptureSubsystem.cpp

293 lines
8.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2026 (c) Jupiter. All Rights Reserved.
#include "CameraCaptureSubSystem.h"
#include "CameraCaptureActor.h"
#include "Components/SceneCaptureComponent2D.h"
#include "Camera/CameraComponent.h"
#include "EngineUtils.h"
#include "Engine/World.h"
#include "Engine/TextureRenderTarget2D.h"
#include "IImageWrapper.h"
#include "IImageWrapperModule.h"
#include "HAL/PlatformFilemanager.h"
#include "Misc/Paths.h"
#include "Misc/FileHelper.h"
#include "Modules/ModuleManager.h"
#include "Async/Async.h"
void UCameraCaptureSubSystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
OutPutDirectoryPath = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("CameraCapture"));
}
void UCameraCaptureSubSystem::Deinitialize()
{
Super::Deinitialize();
}
void UCameraCaptureSubSystem::OnWorldBeginPlay(UWorld& InWorld)
{
Super::OnWorldBeginPlay(InWorld);
for (TActorIterator<ACameraCaptureActor> It(GetWorld()); It; ++It)
{
ACameraCaptureActor* CameraCaptureActor = Cast<ACameraCaptureActor>(*It);
if (CameraCaptureActor)
{
AllCaptureCameras.Emplace(CameraCaptureActor);
}
}
if (AllCaptureCameras.Num() != CAMERA_WIDTH_NUMS * CAMERA_HEIGHTH_NUMS)
{
return;
}
}
static void FlipVertical(TArray<FColor>& Pixels, int32 W, int32 H)
{
for (int32 Row = 0; Row < H / 2; ++Row)
{
const int32 A = Row * W;
const int32 B = (H - 1 - Row) * W;
for (int32 Col = 0; Col < W; ++Col)
{
Swap(Pixels[A + Col], Pixels[B + Col]);
}
}
}
void UCameraCaptureSubSystem::Tick(float DeltaTime)
{
CaptureViewToImageTick();
}
TStatId UCameraCaptureSubSystem::GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(UCameraCaptureSubSystem, STATGROUP_Tickables);
}
void UCameraCaptureSubSystem::CaptureViewToImageTick()
{
if (AllCaptureCameras.IsEmpty())
{
return;
}
// 已经有一批在等 GPU 回读,就先尝试取回
if (bReadbackInFlight)
{
TryResolveCaptureViewAtlas();
return;
}
// 没有在飞行中的回读,就启动新的一批
BeginCaptureViewToAtlas();
}
void UCameraCaptureSubSystem::BeginCaptureViewToAtlas()
{
const int32 AtlasW = ViewWidth * AtlasGridX;
const int32 AtlasH = ViewHeight * AtlasGridY;
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;
}
ACameraCaptureActor* CaptureActor = AllCaptureCameras[CameraIndex].Get();
if (!CaptureActor)
{
continue;
}
// 先让这台 SceneCapture 更新一次 RT
CaptureActor->CaptureNow(); // 你自己封装一个函数,内部调用 ViewCapture->CaptureScene()
UTextureRenderTarget2D* RenderTarget = CaptureActor->GetViewRT();
if (!RenderTarget)
{
continue;
}
FTextureRenderTargetResource* RTRes = RenderTarget->GameThread_GetRenderTargetResource();
if (!RTRes)
{
continue;
}
FRHITexture* SourceTexture = RTRes->GetRenderTargetTexture();
if (!SourceTexture)
{
continue;
}
FCameraReadbackItem Item;
Item.CameraIndex = CameraIndex;
Item.Width = ViewWidth;
Item.Height = ViewHeight;
Item.Readback = MakeUnique<FRHIGPUTextureReadback>(
FName(*FString::Printf(TEXT("CameraReadback_%d"), CameraIndex))
);
FRHIGPUTextureReadback* ReadbackPtr = Item.Readback.Get();
const FResolveRect CopyRect(0, 0, ViewWidth, ViewHeight);
ENQUEUE_RENDER_COMMAND(QueueCameraTextureReadback)(
[ReadbackPtr, SourceTexture, CopyRect](FRHICommandListImmediate& RHICmdList)
{
if (ReadbackPtr && SourceTexture)
{
ReadbackPtr->EnqueueCopy(RHICmdList, SourceTexture, CopyRect);
}
});
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];
}
}
}
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) );
auto EncodeAndWrite = [AtlasPixels = MoveTemp(AtlasPixels), AtlasW, AtlasH, FullPath]()
{
IImageWrapperModule& ImageWrapperModule =
FModuleManager::LoadModuleChecked<IImageWrapperModule>(TEXT("ImageWrapper"));
TSharedPtr<IImageWrapper> Wrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
if (!Wrapper.IsValid()) return;
if (!Wrapper->SetRaw(AtlasPixels.GetData(), AtlasPixels.Num() * sizeof(FColor),
AtlasW, AtlasH, ERGBFormat::BGRA, 8))
{
return;
}
const TArray64<uint8>& Compressed = Wrapper->GetCompressed();
TArray<uint8> Bytes;
Bytes.Append(Compressed.GetData(), (int32)Compressed.Num());
FFileHelper::SaveArrayToFile(Bytes, *FullPath);
};
// 基础版默认异步写盘,避免卡
Async(EAsyncExecution::ThreadPool, MoveTemp(EncodeAndWrite));
}