CARLA
Loading...
Searching...
No Matches
PixelReader.h
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
7#pragma once
8
11
13#include <carla/Logging.h>
14#include <carla/Buffer.h>
15#include <carla/BufferView.h>
18
19#include <util/ue-header-guard-begin.h>
20#include "CoreGlobals.h"
21#include "Engine/TextureRenderTarget2D.h"
22#include "Runtime/ImageWriteQueue/Public/ImagePixelData.h"
23#include "DataDrivenShaderPlatformInfo.h"
24#include "RHIDefinitions.h"
25#include <util/ue-header-guard-end.h>
26
27#ifdef _WIN32
28#ifndef WIN32_LEAN_AND_MEAN
29#define WIN32_LEAN_AND_MEAN
30#endif
31#include <D3d12.h>
32#endif
33
34// =============================================================================
35// -- FPixelReader -------------------------------------------------------------
36// =============================================================================
37
38/// Utils for reading pixels from UTextureRenderTarget2D.
39///
40/// @todo This class only supports PF_R8G8B8A8 format.
42{
43public:
44
45 using Payload = std::function<void(void *, uint32, uint32, uint32)>;
46
47 /// Copy the pixels in @a RenderTarget into @a BitMap.
48 ///
49 /// @pre To be called from game-thread.
50 static bool WritePixelsToArray(
51 UTextureRenderTarget2D &RenderTarget,
52 TArray<FColor> &BitMap);
53
54 /// Dump the pixels in @a RenderTarget.
55 ///
56 /// @pre To be called from game-thread.
57 static TUniquePtr<TImagePixelData<FColor>> DumpPixels(
58 UTextureRenderTarget2D &RenderTarget);
59
60 /// Asynchronously save the pixels in @a RenderTarget to disk.
61 ///
62 /// @pre To be called from game-thread.
63 static TFuture<bool> SavePixelsToDisk(
64 UTextureRenderTarget2D &RenderTarget,
65 const FString &FilePath);
66
67 /// Asynchronously save the pixels in @a PixelData to disk.
68 ///
69 /// @pre To be called from game-thread.
70 static TFuture<bool> SavePixelsToDisk(
71 TUniquePtr<TImagePixelData<FColor>> PixelData,
72 const FString &FilePath);
73
74 /// Convenience function to enqueue a render command that sends the pixels
75 /// down the @a Sensor's data stream. It expects a sensor derived from
76 /// ASceneCaptureSensor or compatible.
77 ///
78 /// Note that the serializer needs to define a "header_offset" that it's
79 /// allocated in front of the buffer.
80 ///
81 /// @pre To be called from game-thread.
82 template <typename TSensor, typename TPixel>
83 static void SendPixelsInRenderThread(TSensor &Sensor, bool use16BitFormat = false, std::function<TArray<TPixel>(void *, uint32)> Conversor = {});
84
85 /// Copy the pixels in @a RenderTarget into @a Buffer.
86 ///
87 /// @pre To be called from render-thread.
88 /// @param Pool Optional per-sensor readback pool; when null falls back to a
89 /// per-call alloc.
90 static void WritePixelsToBuffer(
91 UTextureRenderTarget2D &RenderTarget,
92 uint32 Offset,
93 FRHICommandListImmediate &InRHICmdList,
94 FPixelReader::Payload FuncForSending,
95 FRHIGPUReadbackPoolPtr Pool = nullptr);
96
97};
98
99// =============================================================================
100// -- FPixelReader::SendPixelsInRenderThread -----------------------------------
101// =============================================================================
102
103template <typename TSensor, typename TPixel>
104void FPixelReader::SendPixelsInRenderThread(TSensor &Sensor, bool use16BitFormat, std::function<TArray<TPixel>(void *, uint32)> Conversor)
105{
106 TRACE_CPUPROFILER_EVENT_SCOPE(FPixelReader::SendPixelsInRenderThread);
107 check(Sensor.CaptureRenderTarget != nullptr);
108
109 if (!Sensor.HasActorBegunPlay() || !IsValidChecked(&Sensor))
110 {
111 return;
112 }
113
114 /// Blocks until the render thread has finished all it's tasks.
115 Sensor.EnqueueRenderSceneImmediate();
116
117 // Enqueue a command in the render-thread that will write the image buffer to
118 // the data stream. We need to get frame, timestamp and the sensor transform in the capture
119 // (thus executed in the game-thread), so that they reflect the current point in time.
120 // Otherwise the asynchronous execution could send a future header to the client.
121 ENQUEUE_RENDER_COMMAND(FWritePixels_SendPixelsInRenderThread)
122 (
123 [&Sensor, use16BitFormat, Conversor = std::move(Conversor), Frame = FCarlaEngine::GetFrameCounter(),
124 Timestamp = Sensor.GetEpisode().GetElapsedGameTime(), Transform = Sensor.GetActorTransform()](auto &InRHICmdList) mutable
125 {
126 TRACE_CPUPROFILER_EVENT_SCOPE_STR("FWritePixels_SendPixelsInRenderThread");
127
128 /// @todo Can we make sure the sensor is not going to be destroyed?
129 if (IsValidChecked(&Sensor))
130 {
131 FPixelReader::Payload FuncForSending =
132 [&Sensor, Frame, Timestamp, Transform, Conversor = std::move(Conversor)]
133 (void *LockedData, uint32 Size, uint32 Offset, uint32 ExpectedRowBytes)
134 {
135 if (!IsValidChecked(&Sensor))
136 return;
137
138 TArray<TPixel> Converted;
139
140 // optional conversion of data
141 if (Conversor)
142 {
143 TRACE_CPUPROFILER_EVENT_SCOPE_STR("Data conversion");
144 Converted = Conversor(LockedData, Size);
145 LockedData = reinterpret_cast<void *>(Converted.GetData());
146 Size = Converted.Num() * Converted.GetTypeSize();
147 }
148
149 auto Stream = Sensor.GetDataStream(Sensor);
150 Stream.SetFrameNumber(Frame);
151 Stream.SetTimestamp(Timestamp);
152 Stream.SetTransform(Transform);
153 auto Buffer = Stream.PopBufferFromPool();
154
155 uint32 CurrentRowBytes = ExpectedRowBytes;
156
157#ifdef _WIN32
158 // DirectX uses additional bytes to align each row to 256 boundry,
159 // so we need to remove that extra data when sending to the client
160 if (IsD3DPlatform(GMaxRHIShaderPlatform) && Sensor.AreClientsListening())
161 {
162 CurrentRowBytes = Align(ExpectedRowBytes, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
163 if (ExpectedRowBytes != CurrentRowBytes)
164 {
165 TRACE_CPUPROFILER_EVENT_SCOPE_STR("Buffer Copy (windows, row by row)");
166 Buffer.reset(Offset + Size);
167 auto DstRow = Buffer.begin() + Offset;
168 const uint8 *SrcRow = reinterpret_cast<uint8 *>(LockedData);
169 uint32 i = 0;
170 while (i < Size)
171 {
172 FMemory::Memcpy(DstRow, SrcRow, ExpectedRowBytes);
173 DstRow += ExpectedRowBytes;
174 SrcRow += CurrentRowBytes;
175 i += ExpectedRowBytes;
176 }
177 }
178 }
179#endif // _WIN32
180
181 if (ExpectedRowBytes == CurrentRowBytes)
182 {
183 check(ExpectedRowBytes == CurrentRowBytes);
184 TRACE_CPUPROFILER_EVENT_SCOPE_STR("Buffer Copy");
185 Buffer.copy_from(Offset, boost::asio::buffer(LockedData, Size));
186 }
187
188 {
189 // send
190 TRACE_CPUPROFILER_EVENT_SCOPE_STR("Sending buffer");
191 if(Buffer.data())
192 {
193 // serialize data
194 carla::Buffer BufferReady(std::move(carla::sensor::SensorRegistry::Serialize(Sensor, std::move(Buffer))));
195 carla::SharedBufferView BufView = carla::BufferView::CreateFrom(std::move(BufferReady));
196
197 // ROS2
198 #if defined(WITH_ROS2)
199 auto ROS2 = carla::ros2::ROS2::GetInstance();
200 if (ROS2->IsEnabled())
201 {
202 TRACE_CPUPROFILER_EVENT_SCOPE_STR("ROS2 Send PixelReader");
203 auto StreamId = carla::streaming::detail::token_type(Sensor.GetToken()).get_stream_id();
204 auto Res = std::async(std::launch::async, [&Sensor, ROS2, &Stream, StreamId, BufView]()
205 {
206 // get resolution of camera
207 int W = -1, H = -1;
208 float Fov = -1.0f;
209 auto WidthOpt = Sensor.GetAttribute("image_size_x");
210 if (WidthOpt.has_value())
211 W = FCString::Atoi(*WidthOpt->Value);
212 auto HeightOpt = Sensor.GetAttribute("image_size_y");
213 if (HeightOpt.has_value())
214 H = FCString::Atoi(*HeightOpt->Value);
215 auto FovOpt = Sensor.GetAttribute("fov");
216 if (FovOpt.has_value())
217 Fov = FCString::Atof(*FovOpt->Value);
218 // send data to ROS2
219 AActor* ParentActor = Sensor.GetAttachParentActor();
220 if (ParentActor)
221 {
222 FTransform LocalTransformRelativeToParent = Sensor.GetActorTransform().GetRelativeTransform(ParentActor->GetActorTransform());
223 ROS2->ProcessDataFromCamera(Stream.GetSensorType(), StreamId, LocalTransformRelativeToParent, W, H, Fov, BufView, &Sensor);
224 }
225 else
226 {
227 ROS2->ProcessDataFromCamera(Stream.GetSensorType(), StreamId, Stream.GetSensorTransform(), W, H, Fov, BufView, &Sensor);
228 }
229 });
230 }
231 #endif
232
233 if (Sensor.AreClientsListening())
234 {
235 // network
236 SCOPE_CYCLE_COUNTER(STAT_CarlaSensorStreamSend);
237 TRACE_CPUPROFILER_EVENT_SCOPE_STR("Stream Send");
238 Stream.Send(Sensor, BufView);
239 }
240 }
241 }
242 };
243
245 *Sensor.CaptureRenderTarget,
247 InRHICmdList,
248 std::move(FuncForSending),
249 Sensor.GetReadbackPool());
250
251 }
252 }
253 );
254
255 // Blocks until the render thread has finished all it's tasks
256 Sensor.WaitForRenderThreadToFinish();
257}
TSharedPtr< FRHIGPUReadbackPool, ESPMode::ThreadSafe > FRHIGPUReadbackPoolPtr
static uint64_t GetFrameCounter()
Definition CarlaEngine.h:73
Utils for reading pixels from UTextureRenderTarget2D.
Definition PixelReader.h:42
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.
static void SendPixelsInRenderThread(TSensor &Sensor, bool use16BitFormat=false, std::function< TArray< TPixel >(void *, uint32)> Conversor={})
Convenience function to enqueue a render command that sends the pixels down the Sensor's data stream.
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.
static std::shared_ptr< BufferView > CreateFrom(Buffer &&buffer)
Definition BufferView.h:56
A piece of raw data.
static std::shared_ptr< ROS2 > GetInstance()
Definition ROS2.h:61
static Buffer Serialize(Sensor &sensor, Args &&... args)
Serialize the arguments provided into a Buffer by calling to the serializer registered for the given ...
Serializes a stream endpoint.
std::shared_ptr< BufferView > SharedBufferView
Definition BufferView.h:151
typename detail::CompileTimeTypeMapImpl< sizeof...(Items), Items... >::template get< InKey > get