CARLA
Loading...
Searching...
No Matches
PixelReader.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
8#include "Carla.h"
9
10#include <util/ue-header-guard-begin.h>
11#include "Engine/TextureRenderTarget2D.h"
12#include "Async/Async.h"
13#include "HighResScreenshot.h"
14#include "ImageWriteQueue.h"
15#include "RHIGPUReadback.h"
16#include <util/ue-header-guard-end.h>
17
18#include <chrono>
19#include <thread>
20
21// =============================================================================
22// -- FPixelReader -------------------------------------------------------------
23// =============================================================================
24
25static TAutoConsoleVariable<int32> CVarPixelReaderLegacyVulkanFenceFlush(
26 TEXT("carla.PixelReader.LegacyVulkanFenceFlush"),
27 0,
28 TEXT("UE4-era Vulkan fence-flush workaround inside FPixelReader::WritePixelsToBuffer.\n")
29 TEXT("Creates an RQT_AbsoluteTime render query, flushes the RHI thread, and\n")
30 TEXT("synchronously waits for the result after every EnqueueCopy. UE 5.5 Vulkan\n")
31 TEXT("rewrote fence handling; the workaround is believed unnecessary.\n")
32 TEXT(" 0: Skip the workaround (default).\n")
33 TEXT(" 1: Run the legacy fence-flush block (rollback)."),
34 ECVF_Default);
35
37 UTextureRenderTarget2D &RenderTarget,
38 uint32 Offset,
39 FRHICommandListImmediate &RHICmdList,
40 FPixelReader::Payload FuncForSending,
42{
43 TRACE_CPUPROFILER_EVENT_SCOPE_STR("WritePixelsToBuffer");
44 check(IsInRenderingThread());
45
46 auto RenderResource =
47 static_cast<const FTextureRenderTarget2DResource *>(RenderTarget.GetResource());
48 if (RenderResource == nullptr)
49 {
50 return;
51 }
52 auto Texture = RenderResource->GetRenderTargetTexture();
53 if (!Texture)
54 {
55 return;
56 }
57
58 // Acquire from per-sensor pool when available; fall back to per-call alloc
59 // if the pool is missing or every slot is in use.
60 FRHIGPUTextureReadback *Readback = nullptr;
61 int32 SlotIndex = INDEX_NONE;
62 TUniquePtr<FRHIGPUTextureReadback> FallbackReadback;
63 if (Pool)
64 {
65 Readback = Pool->Acquire(SlotIndex);
66 }
67 if (Readback == nullptr)
68 {
69 FallbackReadback = MakeUnique<FRHIGPUTextureReadback>(TEXT("CameraBufferReadback"));
70 Readback = FallbackReadback.Get();
71 }
72
73 FIntPoint BackBufferSize = Texture->GetSizeXY();
74 EPixelFormat BackBufferPixelFormat = Texture->GetFormat();
75 {
76 TRACE_CPUPROFILER_EVENT_SCOPE_STR("EnqueueCopy");
77 Readback->EnqueueCopy(RHICmdList,
78 Texture,
79 FResolveRect(0, 0, BackBufferSize.X, BackBufferSize.Y));
80 }
81
82 if (CVarPixelReaderLegacyVulkanFenceFlush.GetValueOnRenderThread() != 0)
83 {
84 // workaround to force RHI with Vulkan to refresh the fences state in the middle of frame
85 FRenderQueryRHIRef Query = RHICreateRenderQuery(RQT_AbsoluteTime);
86 TRACE_CPUPROFILER_EVENT_SCOPE_STR("create query");
87 RHICmdList.EndRenderQuery(Query);
88 TRACE_CPUPROFILER_EVENT_SCOPE_STR("Flush");
89 RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
90 TRACE_CPUPROFILER_EVENT_SCOPE_STR("query result");
91 uint64 OldAbsTime = 0;
92 RHIGetRenderQueryResult(Query, OldAbsTime, true);
93 }
94
95 // Must stay off the render-pipeline named threads: the task busy-waits
96 // on FRHIGPUTextureReadback::IsReady(), which the RHI thread itself has
97 // to drive — dispatching there deadlocks. AnyBackgroundHiPriTask keeps
98 // the original high-priority intent.
99 AsyncTask(ENamedThreads::AnyBackgroundHiPriTask,
100 [=, FuncForSending = std::move(FuncForSending), Pool = std::move(Pool),
101 Fallback = std::move(FallbackReadback)]() mutable {
102 {
103 TRACE_CPUPROFILER_EVENT_SCOPE_STR("Wait GPU transfer");
104 // Bounded: if the copy never completes (capture stopped before its
105 // command buffer was submitted) drop the frame instead of leaking a
106 // spinning task and its pool slot.
107 uint64 Spins = 0;
108 while (!Readback->IsReady())
109 {
110 std::this_thread::sleep_for(std::chrono::microseconds(100));
111 if (++Spins > 50000u) // ~5 s
112 {
113 if (Pool && SlotIndex != INDEX_NONE)
114 Pool->Release(SlotIndex);
115 return;
116 }
117 }
118 }
119
120 // FRHIGPUTextureReadback::Lock() maps through the immediate command list
121 // and asserts IsInRenderingThread(); it cannot run on this worker (the
122 // same hop ImageUtil's ReadImageDataEndAsync makes). The readback is
123 // already complete, so the Lock is a cheap CPU-side map and does not
124 // stall the render thread.
125 ENQUEUE_RENDER_COMMAND(PixelReaderDeliver)(
126 [=, FuncForSending = std::move(FuncForSending), Pool = std::move(Pool),
127 Fallback = std::move(Fallback)](FRHICommandListImmediate&) mutable
128 {
129 {
130 TRACE_CPUPROFILER_EVENT_SCOPE_STR("Readback data");
131 FPixelFormatInfo PixelFormat = GPixelFormats[BackBufferPixelFormat];
132 uint32 ExpectedRowBytes = BackBufferSize.X * PixelFormat.BlockBytes;
133 int32 Size = (BackBufferSize.Y * (PixelFormat.BlockBytes * BackBufferSize.X));
134 // FRHIGPUTextureReadback::Lock takes its argument by reference and writes
135 // the row stride (in pixels) back into it. It must not alias `Size`, or
136 // the payload length collapses to a single row's pixel count.
137 int32 RowPitchInPixels = 0;
138 void* LockedData = Readback->Lock(RowPitchInPixels);
139 if (LockedData)
140 {
141 FuncForSending(LockedData, Size, Offset, ExpectedRowBytes);
142 }
143 Readback->Unlock();
144 }
145
146 if (Pool && SlotIndex != INDEX_NONE)
147 {
148 Pool->Release(SlotIndex);
149 }
150 // Fallback (if any) destructs here, freeing its staging buffer.
151 });
152 });
153}
154
156 UTextureRenderTarget2D &RenderTarget,
157 TArray<FColor> &BitMap)
158{
159 check(IsInGameThread());
160 FTextureRenderTargetResource *RTResource =
161 RenderTarget.GameThread_GetRenderTargetResource();
162 if (RTResource == nullptr)
163 {
164 UE_LOG(LogCarla, Error, TEXT("FPixelReader: UTextureRenderTarget2D missing render target"));
165 return false;
166 }
167 FReadSurfaceDataFlags ReadPixelFlags(RCM_UNorm);
168 ReadPixelFlags.SetLinearToGamma(true);
169 return RTResource->ReadPixels(BitMap, ReadPixelFlags);
170}
171
172TUniquePtr<TImagePixelData<FColor>> FPixelReader::DumpPixels(
173 UTextureRenderTarget2D &RenderTarget)
174{
175 const FIntPoint DestSize(RenderTarget.GetSurfaceWidth(), RenderTarget.GetSurfaceHeight());
176 TUniquePtr<TImagePixelData<FColor>> PixelData = MakeUnique<TImagePixelData<FColor>>(DestSize);
177 TArray<FColor> Pixels(PixelData->Pixels.GetData(), PixelData->Pixels.Num());
178 if (!WritePixelsToArray(RenderTarget, Pixels))
179 {
180 return nullptr;
181 }
182 PixelData->Pixels = Pixels;
183 return PixelData;
184}
185
187 UTextureRenderTarget2D &RenderTarget,
188 const FString &FilePath)
189{
190 return SavePixelsToDisk(DumpPixels(RenderTarget), FilePath);
191}
192
194 TUniquePtr<TImagePixelData<FColor>> PixelData,
195 const FString &FilePath)
196{
197 TUniquePtr<FImageWriteTask> ImageTask = MakeUnique<FImageWriteTask>();
198 ImageTask->PixelData = MoveTemp(PixelData);
199 ImageTask->Filename = FilePath;
200 ImageTask->Format = EImageFormat::PNG;
201 ImageTask->CompressionQuality = (int32) EImageCompressionQuality::Default;
202 ImageTask->bOverwriteFile = true;
203 ImageTask->PixelPreProcessors.Add(TAsyncAlphaWrite<FColor>(255));
204
205 FHighResScreenshotConfig &HighResScreenshotConfig = GetHighResScreenshotConfig();
206 return HighResScreenshotConfig.ImageWriteQueue->Enqueue(MoveTemp(ImageTask));
207}
static TAutoConsoleVariable< int32 > CVarPixelReaderLegacyVulkanFenceFlush(TEXT("carla.PixelReader.LegacyVulkanFenceFlush"), 0, TEXT(" 1: Run the legacy fence-flush block (rollback)."), ECVF_Default)
TSharedPtr< FRHIGPUReadbackPool, ESPMode::ThreadSafe > FRHIGPUReadbackPoolPtr
static void WritePixelsToBuffer(UTextureRenderTarget2D &RenderTarget, uint32 Offset, FRHICommandListImmediate &InRHICmdList, FPixelReader::Payload FuncForSending, FRHIGPUReadbackPoolPtr Pool=nullptr)
Copy the pixels in RenderTarget into Buffer.
static bool WritePixelsToArray(UTextureRenderTarget2D &RenderTarget, TArray< FColor > &BitMap)
Copy the pixels in RenderTarget into BitMap.
std::function< void(void *, uint32, uint32, uint32)> Payload
Definition PixelReader.h:45
static TUniquePtr< TImagePixelData< FColor > > DumpPixels(UTextureRenderTarget2D &RenderTarget)
Dump the pixels in RenderTarget.
static TFuture< bool > SavePixelsToDisk(UTextureRenderTarget2D &RenderTarget, const FString &FilePath)
Asynchronously save the pixels in RenderTarget to disk.