Cogs.Core
CaptureSystem.cpp
1#include "CaptureSystem.h"
2
3#include "Context.h"
4#include "Services/Time.h"
5#include "Components/Core/CameraComponent.h"
6#include "Systems/Core/CameraSystem.h"
7#include "Services/Time.h"
8#include "Resources/TextureManager.h"
9#include "Renderer/RenderResource.h"
10
11#include "Foundation/Logging/Logger.h"
12#include "Foundation/Platform/IO.h"
13
14#include <chrono>
15
16#ifndef EMSCRIPTEN
17#define STB_IMAGE_WRITE_IMPLEMENTATION
18#include "../../../Libraries/stb/stb_image_write.h"
19#endif
20
21using namespace Cogs::Core;
22
23namespace
24{
25 using namespace Cogs::Core;
26
27 Cogs::Logging::Log logger = Cogs::Logging::getLogger("CaptureSystem");
28
29#ifndef EMSCRIPTEN
30 void writeFunc(void *context, void *data, int size)
31 {
32 // Note: stbi_write_png_to_func creates an intermediate buffer
33 // that is passed to this callback and is finished with the
34 // input data when it is called, so we can overwrite the input.
35 auto *v = reinterpret_cast<Cogs::Memory::MemoryBuffer* > (context);
36 v->resize(size, false);
37 std::memcpy(v->data(), data, size);
38 }
39#endif
40
41 struct PNGEncodingTask
42 {
43 std::atomic<CaptureItem*>& taskItem;
44
45 void operator()()
46 {
47 auto * item = taskItem.load();
48
49 using namespace std::chrono_literals;
50 //LOG_DEBUG(logger, "PNG encoding of frame %d", item->frame);
51
52#ifdef EMSCRIPTEN
53 item->state = CaptureItem::State::Dropped;
54#else
55
56 if (stbi_write_png_to_func(writeFunc, &item->data, item->width, item->height, 4, item->data.data(), 4 * item->width) == 0)
57 {
58 LOG_WARNING(logger, "PNG encoding of frame %d failed", item->frame);
59 item->state = CaptureItem::State::Dropped;
60 }
61 else {
62 item->state = CaptureItem::State::EncodedAsPng;
63 }
64#endif
65 //std::this_thread::sleep_for(10ms);
66
67 taskItem.store(nullptr);
68 }
69 };
70
71 struct StoreImageTask
72 {
73 std::atomic<CaptureItem*>& taskItem;
74
75 void operator()()
76 {
77 auto * item = taskItem.load();
78#ifdef EMSCRIPTEN
79 LOG_WARNING(logger, "Storing of images not implemented.");
80#else
81
82 char sep[2];
83 if (item->recordPath.empty()) {
84 sep[0] = '\0';
85 }
86 else {
87 sep[0] = Cogs::IO::pathSeparator();
88 sep[1] = '\0';
89 }
90
91 auto seconds = int(std::floor(item->time));
92 auto millis = int(std::floor(1000.f*(item->time - seconds)));
93
94 const char* suffix = nullptr;
95 switch (item->format) {
96 case CaptureFormat::PNG:
97 suffix = "png";
98 break;
99 case CaptureFormat::RawARGB:
100 suffix = "rgba";
101 break;
102 case CaptureFormat::RawFloat:
103 suffix = "float";
104 break;
105 default:
106 assert(false);
107 }
108
109 char buffer[1024];
110 snprintf(buffer, sizeof(buffer), "%s%s%s-f%05d-t%05d_%03d-%dx%d.%s", item->recordPath.c_str(), sep, item->name.c_str(), item->frame, seconds, millis, item->width, item->height, suffix);
111
112
113 LOG_DEBUG(logger, "Writing '%s'", buffer);
114 Cogs::IO::writeBinaryFile(buffer, item->data.data(), item->data.size());
115#endif
116 item->state = CaptureItem::State::Ready;
117 taskItem.store(nullptr);
118 }
119
120
121 };
122
123 void readbackCallback(Context * context, const CameraData* camData, uint32_t frame, const char * key, const void * data, int size_)
124 {
125 const auto size = static_cast<unsigned>(size_);
126 if (std::string(key) != "Capture") return;
127
128 auto * capComp = camData->camera.resolve()->getComponent<CaptureComponent>();
129 if (!capComp) {
130 LOG_FATAL(logger, "No capture component attached to camera.");
131 return;
132 }
133 auto & capData = context->captureSystem->getData(capComp);
134
135 for (auto & item : capData.items) {
136 if (item->frame == frame) {
137
138 item->width = int(std::max(0.f, camData->viewportSize[0]));
139 item->height = int(std::max(0.f, camData->viewportSize[1]));
140
141 if (item->format == CaptureFormat::PNG) {
142
143 auto expectedSize = 4 * item->width * item->height;
144 if (size != expectedSize) {
145 LOG_WARNING(logger, "Expected %d bytes of image data, got %d bytes, dropping frame %d", expectedSize, size, frame);
146 return;
147 }
148
149 if (capData.encodePNGItem.load()) {
150 if (capComp->dropFrames) {
151 //LOG_WARNING(logger, "PNG encoding of previous frame not finished, dropping frame %d.", frame);
152 item->state = CaptureItem::State::Dropped;
153 return;
154 }
155 else {
156 context->taskManager->wait(capData.encodePngTask);
157 assert(capData.encodePNGItem.load() == nullptr);
158 capData.encodePngTask = NoTask;
159 }
160 }
161
162 item->state = CaptureItem::State::EncodeToPng;
163 item->data.resize(size, false);
164 std::memcpy(item->data.data(), data, size);
165
166 capData.encodePNGItem.store(item.get());
167 capData.encodePngTask = context->taskManager->enqueue(context->taskManager->GlobalQueue,
168 PNGEncodingTask{ capData.encodePNGItem });
169 //LOG_DEBUG(logger, "Scheduled frame %d for PNG encoding", frame);
170 }
171 else {
172 item->state = CaptureItem::State::ReceivedReadback;
173 item->data.resize(size, false);
174 std::memcpy(item->data.data(), data, size);
175 //LOG_DEBUG(logger, "Received data for frame %d", frame);
176 }
177 if (0 < capComp->framesToCapture) {
178 capComp->framesToCapture--;
179 }
180 return;
181 }
182 }
183 //LOG_WARNING(logger, "Got capture readback data but couldn't find appropriate metadata item for frame %d.", frame)
184 }
185
186
187 void removeDroppedItems(std::vector<std::unique_ptr<CaptureItem>>& itemStore, std::deque<std::unique_ptr<CaptureItem>>& items)
188 {
189 for (auto it = items.begin(); it != items.end(); ) {
190 if ((*it)->state == CaptureItem::State::Dropped) {
191 //LOG_DEBUG(logger, "Discarded dropped item");
192 itemStore.push_back(std::move(*it));
193 it = items.erase(it);
194 }
195 else {
196 ++it;
197 }
198 }
199 }
200
201 void removeScheduledForDiscard(std::vector<std::unique_ptr<CaptureItem>>& itemStore, std::deque<std::unique_ptr<CaptureItem>>& items)
202 {
203 // Check if there is anything besides just issued frames,
204 // in that case we will delete all issued frames up until the first that we have data on.
205 bool anyNotJustIssued = false;
206 for (auto & item : items) {
207 if (item->state != CaptureItem::State::Issued) {
208 anyNotJustIssued = true;
209 break;
210 }
211 }
212
213 for (auto it = items.begin(); it != items.end(); ) {
214 auto & item = *it;
215 bool discard = false;
216
217 if (anyNotJustIssued && item->state == CaptureItem::State::Issued) {
218 discard = true;
219 }
220 else {
221 anyNotJustIssued = false;
222 if (item->state == CaptureItem::State::DiscardNextFrame) {
223 discard = true;
224 }
225 }
226
227 if (discard) {
228 itemStore.push_back(std::move(*it));
229 it = items.erase(it);
230 }
231 else {
232 ++it;
233 }
234 }
235
236
237
238
239 for (auto & item : items) {
240 if (item->state == CaptureItem::State::Ready) {
241 item->state = CaptureItem::State::DiscardNextFrame;
242 }
243 }
244
245 }
246
247
248}
249
251{
253 context->callbacks.readbackCallbackInternal.push_back(readbackCallback);
254}
255
257{
258 for (auto & capComp : pool) {
259 auto & capData = getData(&capComp);
260
261 auto * camComp = capComp.getComponent<CameraComponent>();
262 if (!camComp) continue;
263
264 bool doCapture = capComp.framesToCapture != 0;
265
266 char args[256];
267 snprintf(args, sizeof(args), "Capture=%s&Format=%s", doCapture ? "True" : "False", capComp.format == CaptureFormat::RawFloat ? "Float" : "4xU8");
268
269 switch (capComp.mode) {
270 case CaptureMode::Off:
271 camComp->flags &= ~CameraFlags::EnableRender;
272 camComp->renderPipeline = "";
273 break;
275 camComp->flags |= CameraFlags::EnableRender;
276 switch (capComp.sceneMode) {
278 camComp->renderPipeline = "Pipelines/CaptureForward.pipeline?" + std::string(args);
279 break;
281 camComp->renderPipeline = "Pipelines/CaptureForwardHdr.pipeline?" + std::string(args);
282 break;
284 camComp->renderPipeline = "Pipelines/CaptureDeferredCausticsHdr.pipeline?" + std::string(args);
285 break;
286 default:
287 assert(false);
288 }
289 break;
291 camComp->flags |= CameraFlags::EnableRender;
292 camComp->renderPipeline = "Pipelines/Capture.pipeline?Mode=ObjectId&" + std::string(args);
293 break;
295 camComp->flags |= CameraFlags::EnableRender;
296 camComp->renderPipeline = "Pipelines/Capture.pipeline?Mode=ObjectIdColorize&" + std::string(args);
297 break;
299 camComp->flags |= CameraFlags::EnableRender;
300 camComp->renderPipeline = "Pipelines/Capture.pipeline?Mode=Normals&" + std::string(args);
301 break;
303 camComp->flags |= CameraFlags::EnableRender;
304 camComp->renderPipeline = "Pipelines/Capture.pipeline?Mode=Depth&" + std::string(args);
305 break;
306 default:
307 assert(false);
308 }
309 camComp->setChanged();
310
311 for (auto & item : capData.items) {
312 if (item->state == CaptureItem::State::ReceivedReadback || item->state == CaptureItem::State::EncodedAsPng)
313 {
314 if (item->recordToDisc) {
315 if (capData.storeImageItem.load() && capComp.dropFrames) {
316 LOG_WARNING(logger, "Storing of previous frame not finished, skip storing of frame %d.", item->frame);
317 item->state = CaptureItem::State::Ready;
318 }
319 else {
320 if (capData.storeImageTask.isValid()) {
321 context->taskManager->wait(capData.storeImageTask);
322 capData.storeImageTask = NoTask;
323 }
324 assert(capData.storeImageItem.load() == nullptr);
325 item->state = CaptureItem::State::WriteToDisc;
326 capData.storeImageItem.store(item.get());
327 assert(capData.storeImageItem.load());
328 capData.storeImageTask = context->taskManager->enqueue(context->taskManager->GlobalQueue, StoreImageTask{ capData.storeImageItem });
329 }
330 }
331 else {
332 item->state = CaptureItem::State::Ready;
333 }
334 }
335 }
336
337 removeDroppedItems(capData.itemStore, capData.items);
338 removeScheduledForDiscard(capData.itemStore, capData.items);
339
340 if (doCapture) {
341 if (10 <= capData.items.size()) {
342 if (capData.items.front()->state == CaptureItem::State::Issued) {
343 capData.items.pop_front();
344 }
345 else {
346 LOG_WARNING(logger, "10 or more capture frames in flight for CaptureComponent, skipping.");
347 }
348 }
349 else {
350 auto * entity = capComp.getContainer();
351 std::unique_ptr<CaptureItem> item;
352 if (capData.itemStore.empty()) {
353 item = std::make_unique<CaptureItem>();
354 }
355 else {
356 item = std::move(capData.itemStore.back());
357 capData.itemStore.pop_back();
358 }
359 item->time = context->time->getAnimationTime();
360 item->frame = context->time->getFrame();
361 item->state = CaptureItem::State::Issued;
362 item->mode = capComp.mode;
363 item->sceneMode = capComp.sceneMode;
364 item->format = capComp.format;
365 item->recordToDisc = capComp.recordToDisc;
366 item->recordPath = capComp.recordPath;
367 item->name = entity->getName();
368 if (item->name.empty()) {
369 item->name = "entity_" + std::to_string(entity->getId());
370 }
371 capData.items.push_back(std::move(item));
372 }
373 }
374 }
375}
376
377
379{
380 auto * comp = component.resolveComponent<CaptureComponent>();
381 auto & data = getData(comp);
382 if (data.encodePngTask.isValid()) {
383 context->taskManager->wait(data.encodePngTask);
384 }
385 if (data.storeImageTask.isValid()) {
386 context->taskManager->wait(data.storeImageTask);
387 }
389}
ComponentType * getComponent() const
Definition: Component.h:159
void initialize(Context *context) override
Initialize the system.
void destroyComponent(ComponentHandle component) override
Context * context
Pointer to the Context instance the system lives in.
virtual void initialize(Context *context)
Initialize the system.
virtual void destroyComponent(ComponentHandle)
Destroy the component held by the given handle.
void update()
Updates the system state to that of the current frame.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class TaskManager > taskManager
TaskManager service instance.
Definition: Context.h:186
std::unique_ptr< class Time > time
Time service instance.
Definition: Context.h:198
Log implementation class.
Definition: LogManager.h:139
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
@ RawFloat
Store image as a blob of raw float values (in host byte order).
@ ObjectId
Render object ids.
@ Normals
Render world-space surface normals.
@ ObjectIdColorize
Render object ids, colorized for visualization.
@ Depth
Render world-space distance from eye, scaled by 1/100 (so a value of 1 equals a distance of 100 units...
@ Scene
Render in normal scene mode,.
@ Off
Disable capture and camera, consume no rendering resources.
@ EnableRender
Renderable.
@ Forward
Normal forward rendering.
@ DeferredCausticsHdr
Deferred rendering with support for caustics and HDR post-processing.
@ ForwardHdr
Normal forward rendering with HDR post-proessing (exposure, bloom, tonemapping)
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
Handle to a Component instance.
Definition: Component.h:67
COGSFOUNDATION_API class Component * resolve() const
Resolve the handle, returning a pointer to the held Component instance.
Definition: Component.cpp:65
ComponentType * resolveComponent() const
Definition: Component.h:90
Contains data describing a Camera instance and its derived data structured such as matrix data and vi...
Definition: CameraSystem.h:67