MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
TextureContainer.cpp
Go to the documentation of this file.
2
6
10
12
14
15namespace MayaFlux::Kakshya {
16
18using Portal::Graphics::TextureLoom;
19
20namespace {
21
23
24 enum class StorageKind : uint8_t { U8,
25 U16,
26 F32 };
27
28 StorageKind storage_kind_for(ImageFormat format)
29 {
30 switch (format) {
31 case ImageFormat::R8:
32 case ImageFormat::RG8:
33 case ImageFormat::RGB8:
34 case ImageFormat::RGBA8:
35 case ImageFormat::RGBA8_SRGB:
36 case ImageFormat::BGRA8:
37 case ImageFormat::BGRA8_SRGB:
38 case ImageFormat::DEPTH24:
39 case ImageFormat::DEPTH24_STENCIL8:
40 return StorageKind::U8;
41
42 case ImageFormat::R16:
43 case ImageFormat::RG16:
44 case ImageFormat::RGBA16:
45 case ImageFormat::R16F:
46 case ImageFormat::RG16F:
47 case ImageFormat::RGBA16F:
48 case ImageFormat::DEPTH16:
49 return StorageKind::U16;
50
51 case ImageFormat::R32F:
52 case ImageFormat::RG32F:
53 case ImageFormat::RGBA32F:
54 case ImageFormat::DEPTH32F:
55 return StorageKind::F32;
56
57 default:
58 return StorageKind::U8;
59 }
60 }
61
62 bool is_float_format(ImageFormat format)
63 {
64 switch (format) {
65 case ImageFormat::R16F:
66 case ImageFormat::RG16F:
67 case ImageFormat::RGBA16F:
68 case ImageFormat::R32F:
69 case ImageFormat::RG32F:
70 case ImageFormat::RGBA32F:
71 case ImageFormat::DEPTH32F:
72 return true;
73 default:
74 return false;
75 }
76 }
77
78 DataVariant make_empty_storage(ImageFormat format, size_t element_count)
79 {
80 switch (storage_kind_for(format)) {
81 case StorageKind::U8:
82 return std::vector<uint8_t>(element_count, 0U);
83 case StorageKind::U16:
84 return std::vector<uint16_t>(element_count, 0U);
85 case StorageKind::F32:
86 return std::vector<float>(element_count, 0.0F);
87 }
88 return std::vector<uint8_t>(element_count, 0U);
89 }
90
91 // Raw byte pointer + byte size from a DataVariant that is known to be
92 // one of the three image alternatives.
93 std::pair<const uint8_t*, size_t> variant_bytes(const DataVariant& v)
94 {
95 return std::visit(
96 [](const auto& vec) -> std::pair<const uint8_t*, size_t> {
97 using T = typename std::decay_t<decltype(vec)>::value_type;
98 if constexpr (std::is_same_v<T, uint8_t>
99 || std::is_same_v<T, uint16_t>
100 || std::is_same_v<T, float>) {
101 return {
102 reinterpret_cast<const uint8_t*>(vec.data()),
103 vec.size() * sizeof(T)
104 };
105 } else {
106 return { nullptr, 0 };
107 }
108 },
109 v);
110 }
111
112 std::pair<uint8_t*, size_t> variant_bytes_mut(DataVariant& v)
113 {
114 return std::visit(
115 [](auto& vec) -> std::pair<uint8_t*, size_t> {
116 using T = typename std::decay_t<decltype(vec)>::value_type;
117 if constexpr (std::is_same_v<T, uint8_t>
118 || std::is_same_v<T, uint16_t>
119 || std::is_same_v<T, float>) {
120 return {
121 reinterpret_cast<uint8_t*>(vec.data()),
122 vec.size() * sizeof(T)
123 };
124 } else {
125 return { nullptr, 0 };
126 }
127 },
128 v);
129 }
130
131 double read_normalized_at(const DataVariant& v, ImageFormat format, size_t elem_index)
132 {
133 return std::visit(
134 [format, elem_index](const auto& vec) -> double {
135 using T = typename std::decay_t<decltype(vec)>::value_type;
136 if (elem_index >= vec.size())
137 return 0.0;
138 if constexpr (std::is_same_v<T, uint8_t>) {
139 return static_cast<double>(vec[elem_index]) / 255.0;
140 } else if constexpr (std::is_same_v<T, uint16_t>) {
141 return is_float_format(format)
142 ? static_cast<double>(vec[elem_index])
143 : static_cast<double>(vec[elem_index]) / 65535.0;
144 } else if constexpr (std::is_same_v<T, float>) {
145 return static_cast<double>(vec[elem_index]);
146 } else {
147 return 0.0;
148 }
149 },
150 v);
151 }
152
153 void write_normalized_at(DataVariant& v, ImageFormat format, size_t elem_index, double value)
154 {
155 std::visit(
156 [format, elem_index, value](auto& vec) {
157 using T = typename std::decay_t<decltype(vec)>::value_type;
158 if (elem_index >= vec.size())
159 return;
160 if constexpr (std::is_same_v<T, uint8_t>) {
161 vec[elem_index] = static_cast<uint8_t>(
162 std::clamp(value * 255.0, 0.0, 255.0));
163 } else if constexpr (std::is_same_v<T, uint16_t>) {
164 if (is_float_format(format)) {
165 vec[elem_index] = static_cast<uint16_t>(value);
166 } else {
167 vec[elem_index] = static_cast<uint16_t>(
168 std::clamp(value * 65535.0, 0.0, 65535.0));
169 }
170 } else if constexpr (std::is_same_v<T, float>) {
171 vec[elem_index] = static_cast<float>(value);
172 }
173 },
174 v);
175 }
176
177} // namespace
178
179//=============================================================================
180// Construction
181//=============================================================================
182
183TextureContainer::TextureContainer(uint32_t width, uint32_t height, ImageFormat format, uint32_t layers)
184 : m_width(width)
185 , m_height(height)
186 , m_format(format)
187 , m_channels(TextureLoom::get_channel_count(format))
188 , m_bpp(TextureLoom::get_bytes_per_pixel(format))
189{
190 m_chain = std::make_shared<DataProcessingChain>();
191 const size_t element_count = static_cast<size_t>(m_width) * m_height * m_channels;
192
193 for (uint32_t i = 0; i < std::max(layers, 1U); ++i) {
194 m_data.emplace_back(make_empty_storage(m_format, element_count));
195 }
196
197 m_normalised_cache.resize(m_data.size());
198
199 m_normalised_dirty = std::vector<std::atomic<bool>>(m_data.size());
200 for (auto& flag : m_normalised_dirty)
201 flag.store(true, std::memory_order_relaxed);
202
203 m_slot_locks.resize(m_data.size());
204 setup_dimensions();
205
206 m_ready_for_processing.store(true);
207
208 MF_INFO(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
209 "TextureContainer created: {}x{} layers={} fmt={} bpp={}",
210 m_width, m_height, m_data.size(), static_cast<int>(m_format), m_bpp);
211}
212
213TextureContainer::TextureContainer(const std::shared_ptr<Core::VKImage>& image, ImageFormat format)
214 : TextureContainer(image->get_width(), image->get_height(), format)
215{
216 from_image(image, 0);
217}
218
219//=============================================================================
220// Setup
221//=============================================================================
222
223void TextureContainer::setup_dimensions()
224{
225 const uint64_t h = m_height;
226 const uint64_t w = m_width;
227 const uint64_t c = m_channels;
228 const auto n = static_cast<uint64_t>(m_data.size());
229
230 m_structure = ContainerDataStructure::image_interleaved();
231
232 if (n > 1) {
233 m_structure.dimensions = DataDimension::create_dimensions(
234 DataModality::IMAGE_COLOR_ARRAY, { n, h, w, c }, MemoryLayout::ROW_MAJOR);
235 } else {
236 m_structure.dimensions = DataDimension::create_dimensions(
237 DataModality::IMAGE_COLOR, { h, w, c }, MemoryLayout::ROW_MAJOR);
238 }
239}
240
241//=============================================================================
242// GPU bridge
243//=============================================================================
244
245void TextureContainer::from_image(const std::shared_ptr<Core::VKImage>& image, uint32_t layer)
246{
247 if (!image || !image->is_initialized()) {
248 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
249 "TextureContainer::from_image called with uninitialised image");
250 return;
251 }
252
253 if (layer >= m_data.size()) {
254 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
255 "TextureContainer::from_image layer {} out of range ({})", layer, m_data.size());
256 return;
257 }
258
259 const size_t sz = byte_size();
260 const size_t element_count = static_cast<size_t>(m_width) * m_height * m_channels;
261
262 {
263 Memory::SeqlockWriteGuard g(m_slot_locks[layer]);
264 m_data[layer] = make_empty_storage(m_format, element_count);
265 auto [ptr, bytes] = variant_bytes_mut(m_data[layer]);
266 if (!ptr || bytes != sz) {
267 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
268 "TextureContainer::from_image variant size mismatch ({} vs {})", bytes, sz);
269 return;
270 }
271 TextureLoom::instance().download_data(image, ptr, sz, nullptr);
272 }
273
274 m_normalised_dirty[layer].store(true, std::memory_order_release);
275 update_processing_state(ProcessingState::READY);
276}
277
278void TextureContainer::from_image(
279 const std::shared_ptr<Core::VKImage>& image,
280 const std::shared_ptr<Buffers::VKBuffer>& staging,
281 uint32_t layer)
282{
283 if (!image || !image->is_initialized()) {
284 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
285 "TextureContainer::from_image(staging) called with uninitialised image");
286 return;
287 }
288
289 if (layer >= m_data.size()) {
290 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
291 "TextureContainer::from_image(staging) layer {} out of range ({})", layer, m_data.size());
292 return;
293 }
294
295 const size_t sz = byte_size();
296 const size_t element_count = static_cast<size_t>(m_width) * m_height * m_channels;
297
298 {
299 Memory::SeqlockWriteGuard g(m_slot_locks[layer]);
300 m_data[layer] = make_empty_storage(m_format, element_count);
301 auto [ptr, bytes] = variant_bytes_mut(m_data[layer]);
302 if (!ptr || bytes != sz) {
303 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
304 "TextureContainer::from_image(staging) variant size mismatch ({} vs {})", bytes, sz);
305 return;
306 }
307 TextureLoom::instance().download_data(image, ptr, sz, staging);
308 }
309
310 m_normalised_dirty[layer].store(true, std::memory_order_release);
311 update_processing_state(ProcessingState::READY);
312}
313
314void TextureContainer::from_image_array(const std::shared_ptr<Core::VKImage>& image)
315{
316 if (!image || !image->is_initialized()) {
317 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
318 "TextureContainer::from_image_array called with uninitialised image");
319 return;
320 }
321
322 const auto n = static_cast<uint32_t>(m_data.size());
323 if (image->get_array_layers() < n) {
324 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
325 "TextureContainer::from_image_array image has {} layers, container expects {}",
326 image->get_array_layers(), n);
327 return;
328 }
329
330 const size_t layer_bytes = byte_size();
331 std::vector<uint8_t> combined(layer_bytes * n);
332 TextureLoom::instance().download_data(image, combined.data(), combined.size(), nullptr);
333
334 const size_t element_count = static_cast<size_t>(m_width) * m_height * m_channels;
335 for (uint32_t i = 0; i < n; ++i) {
336 Memory::SeqlockWriteGuard g(m_slot_locks[i]);
337 m_data[i] = make_empty_storage(m_format, element_count);
338 auto [ptr, bytes] = variant_bytes_mut(m_data[i]);
339 if (ptr && bytes == layer_bytes)
340 std::memcpy(ptr, combined.data() + i * layer_bytes, layer_bytes);
341
342 m_normalised_dirty[i].store(true, std::memory_order_release);
343 }
344
345 update_processing_state(ProcessingState::READY);
346}
347
348void TextureContainer::from_image_array(
349 const std::shared_ptr<Core::VKImage>& image,
350 const std::shared_ptr<Buffers::VKBuffer>& staging)
351{
352 if (!image || !image->is_initialized()) {
353 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
354 "TextureContainer::from_image_array(staging) called with uninitialised image");
355 return;
356 }
357
358 const auto n = static_cast<uint32_t>(m_data.size());
359 if (image->get_array_layers() < n) {
360 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
361 "TextureContainer::from_image_array(staging) image has {} layers, container expects {}",
362 image->get_array_layers(), n);
363 return;
364 }
365
366 const size_t layer_bytes = byte_size();
367 std::vector<uint8_t> combined(layer_bytes * n);
368 TextureLoom::instance().download_data(image, combined.data(), combined.size(), staging);
369
370 const size_t element_count = static_cast<size_t>(m_width) * m_height * m_channels;
371 for (uint32_t i = 0; i < n; ++i) {
372 Memory::SeqlockWriteGuard g(m_slot_locks[i]);
373 m_data[i] = make_empty_storage(m_format, element_count);
374 auto [ptr, bytes] = variant_bytes_mut(m_data[i]);
375 if (ptr && bytes == layer_bytes)
376 std::memcpy(ptr, combined.data() + i * layer_bytes, layer_bytes);
377
378 m_normalised_dirty[i].store(true, std::memory_order_release);
379 }
380
381 update_processing_state(ProcessingState::READY);
382}
383
384std::shared_ptr<Core::VKImage> TextureContainer::to_image(uint32_t layer) const
385{
386 if (layer >= m_data.size()) {
387 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
388 "TextureContainer::to_image layer {} out of range ({})", layer, m_data.size());
389 return nullptr;
390 }
391
392 std::shared_ptr<Core::VKImage> img;
393 seqlock_read_void(m_slot_locks[layer], 8, [&] {
394 auto [ptr, bytes] = variant_bytes(m_data[layer]);
395 if (!ptr || bytes == 0)
396 return;
397 img = TextureLoom::instance().create_2d(m_width, m_height, m_format, ptr);
398 });
399
400 if (!img) {
401 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
402 "TextureContainer::to_image: TextureLoom failed to create VKImage");
403 }
404 return img;
405}
406
407std::shared_ptr<Core::VKImage> TextureContainer::to_image(
408 uint32_t layer, const std::shared_ptr<Buffers::VKBuffer>& staging) const
409{
410 if (layer >= m_data.size()) {
411 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
412 "TextureContainer::to_image(staging) layer {} out of range ({})", layer, m_data.size());
413 return nullptr;
414 }
415
416 std::shared_ptr<Core::VKImage> img;
417 seqlock_read_void(m_slot_locks[layer], 8, [&] {
418 auto [ptr, bytes] = variant_bytes(m_data[layer]);
419 if (!ptr || bytes == 0) {
420 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
421 "TextureContainer::to_image(staging) called on empty/invalid buffer");
422 return;
423 }
424 auto& loom = TextureLoom::instance();
425 img = loom.create_2d(m_width, m_height, m_format, nullptr);
426 if (!img) {
427 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
428 "TextureContainer::to_image(staging): VKImage allocation failed");
429 return;
430 }
431 loom.upload_data(img, ptr, bytes, staging);
432 });
433 return img;
434}
435
436std::shared_ptr<Core::VKImage> TextureContainer::to_image_array() const
437{
438 const auto n = static_cast<uint32_t>(m_data.size());
439 if (n == 0)
440 return nullptr;
441
442 if (n == 1) {
443 std::shared_ptr<Core::VKImage> img;
444 seqlock_read_void(m_slot_locks[0], 8, [&] {
445 auto [ptr, bytes] = variant_bytes(m_data[0]);
446 if (!ptr || bytes == 0)
447 return;
448 img = TextureLoom::instance().create_2d(m_width, m_height, m_format, ptr);
449 });
450 return img;
451 }
452
453 const size_t layer_bytes = byte_size();
454 std::vector<uint8_t> combined(layer_bytes * n);
455 for (uint32_t i = 0; i < n; ++i) {
456 bool ok = seqlock_read_void(m_slot_locks[i], 8, [&] {
457 auto [ptr, bytes] = variant_bytes(m_data[i]);
458 if (!ptr || bytes != layer_bytes) {
459 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
460 "TextureContainer::to_image_array layer {} has unexpected byte count ({} vs {})",
461 i, bytes, layer_bytes);
462 return;
463 }
464 std::memcpy(combined.data() + i * layer_bytes, ptr, layer_bytes);
465 });
466 if (!ok)
467 return nullptr;
468 }
469
470 return TextureLoom::instance().create_2d_array(m_width, m_height, n, m_format, combined.data());
471}
472
473std::shared_ptr<Core::VKImage> TextureContainer::to_image_array(
474 const std::shared_ptr<Buffers::VKBuffer>& staging) const
475{
476 const auto n = static_cast<uint32_t>(m_data.size());
477 if (n == 0)
478 return nullptr;
479
480 if (n == 1) {
481 std::shared_ptr<Core::VKImage> img;
482 seqlock_read_void(m_slot_locks[0], 8, [&] {
483 auto [ptr, bytes] = variant_bytes(m_data[0]);
484 if (!ptr || bytes == 0)
485 return;
486 auto& loom = TextureLoom::instance();
487 img = loom.create_2d(m_width, m_height, m_format, nullptr);
488 if (img)
489 loom.upload_data(img, ptr, bytes, staging);
490 });
491 return img;
492 }
493
494 const size_t layer_bytes = byte_size();
495 std::vector<uint8_t> combined(layer_bytes * n);
496 for (uint32_t i = 0; i < n; ++i) {
497 bool ok = seqlock_read_void(m_slot_locks[i], 8, [&] {
498 auto [ptr, bytes] = variant_bytes(m_data[i]);
499 if (!ptr || bytes != layer_bytes) {
500 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
501 "TextureContainer::to_image_array(staging) layer {} size mismatch", i);
502 return;
503 }
504 std::memcpy(combined.data() + i * layer_bytes, ptr, layer_bytes);
505 });
506
507 if (!ok)
508 return nullptr;
509 }
510
511 auto& loom = TextureLoom::instance();
512 auto img = loom.create_2d_array(m_width, m_height, n, m_format, nullptr);
513 if (!img)
514 return nullptr;
515 loom.upload_data(img, combined.data(), combined.size(), staging);
516 return img;
517}
518
519//=============================================================================
520// Pixel access
521//=============================================================================
522
523std::span<const uint8_t> TextureContainer::pixel_bytes(uint32_t layer) const
524{
525 if (layer >= m_data.size())
526 return {};
527 auto [ptr, bytes] = variant_bytes(m_data[layer]);
528 return ptr ? std::span<const uint8_t>(ptr, bytes) : std::span<const uint8_t> {};
529}
530
531std::span<uint8_t> TextureContainer::pixel_bytes(uint32_t layer)
532{
533 if (layer >= m_data.size())
534 return {};
535 auto [ptr, bytes] = variant_bytes_mut(m_data[layer]);
536 return ptr ? std::span<uint8_t>(ptr, bytes) : std::span<uint8_t> {};
537}
538
539std::span<const uint8_t> TextureContainer::as_uint8(uint32_t layer) const
540{
541 if (layer >= m_data.size())
542 return {};
543 const auto* v = std::get_if<std::vector<uint8_t>>(&m_data[layer]);
544 return v ? std::span<const uint8_t>(v->data(), v->size()) : std::span<const uint8_t> {};
545}
546
547std::span<const uint16_t> TextureContainer::as_uint16(uint32_t layer) const
548{
549 if (layer >= m_data.size())
550 return {};
551 const auto* v = std::get_if<std::vector<uint16_t>>(&m_data[layer]);
552 return v ? std::span<const uint16_t>(v->data(), v->size()) : std::span<const uint16_t> {};
553}
554
555std::span<const float> TextureContainer::as_float(uint32_t layer) const
556{
557 if (layer >= m_data.size())
558 return {};
559 const auto* v = std::get_if<std::vector<float>>(&m_data[layer]);
560 return v ? std::span<const float>(v->data(), v->size()) : std::span<const float> {};
561}
562
563void TextureContainer::set_pixels(std::span<const uint8_t> data, uint32_t layer)
564{
565 if (layer >= m_data.size()) {
566 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
567 "TextureContainer::set_pixels(u8) layer {} out of range", layer);
568 return;
569 }
570 auto* buf = std::get_if<std::vector<uint8_t>>(&m_data[layer]);
571 if (!buf) {
572 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
573 "TextureContainer::set_pixels(u8) called on non-uint8 format {}",
574 static_cast<int>(m_format));
575 return;
576 }
577 if (data.size() != buf->size()) {
578 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
579 "TextureContainer::set_pixels(u8) size mismatch: got {} expected {}",
580 data.size(), buf->size());
581 return;
582 }
583
584 Memory::SeqlockWriteGuard g(m_slot_locks[layer]);
585 std::ranges::copy(data, buf->begin());
586 m_normalised_dirty[layer].store(true, std::memory_order_release);
587}
588
589void TextureContainer::set_pixels(std::span<const uint16_t> data, uint32_t layer)
590{
591 if (layer >= m_data.size()) {
592 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
593 "TextureContainer::set_pixels(u16) layer {} out of range", layer);
594 return;
595 }
596 auto* buf = std::get_if<std::vector<uint16_t>>(&m_data[layer]);
597 if (!buf) {
598 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
599 "TextureContainer::set_pixels(u16) called on non-uint16 format {}",
600 static_cast<int>(m_format));
601 return;
602 }
603 if (data.size() != buf->size()) {
604 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
605 "TextureContainer::set_pixels(u16) size mismatch: got {} expected {}",
606 data.size(), buf->size());
607 return;
608 }
609
610 Memory::SeqlockWriteGuard g(m_slot_locks[layer]);
611 std::ranges::copy(data, buf->begin());
612 m_normalised_dirty[layer].store(true, std::memory_order_release);
613}
614
615void TextureContainer::set_pixels(std::span<const float> data, uint32_t layer)
616{
617 if (layer >= m_data.size()) {
618 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
619 "TextureContainer::set_pixels(f32) layer {} out of range", layer);
620 return;
621 }
622 auto* buf = std::get_if<std::vector<float>>(&m_data[layer]);
623 if (!buf) {
624 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
625 "TextureContainer::set_pixels(f32) called on non-float format {}",
626 static_cast<int>(m_format));
627 return;
628 }
629 if (data.size() != buf->size()) {
630 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
631 "TextureContainer::set_pixels(f32) size mismatch: got {} expected {}",
632 data.size(), buf->size());
633 return;
634 }
635
636 Memory::SeqlockWriteGuard g(m_slot_locks[layer]);
637 std::ranges::copy(data, buf->begin());
638 m_normalised_dirty[layer].store(true, std::memory_order_release);
639}
640
641std::span<const float> TextureContainer::as_normalised_float(uint32_t layer) const
642{
643 if (layer >= m_data.size())
644 return {};
645
646 if (!m_normalised_dirty[layer].load(std::memory_order_acquire))
647 return { m_normalised_cache[layer] };
648
649 std::span<const float> result;
650 seqlock_read_void(m_slot_locks[layer], 8, [&] {
651 result = Kakshya::as_normalised_float(m_data[layer], m_normalised_cache[layer]);
652 });
653
654 if (!result.empty())
655 m_normalised_dirty[layer].store(false, std::memory_order_release);
656
657 return result;
658}
659
660//=============================================================================
661// NDDimensionalContainer
662//=============================================================================
663
664std::vector<DataDimension> TextureContainer::get_dimensions() const
665{
666 return m_structure.dimensions;
667}
668
669uint64_t TextureContainer::get_total_elements() const
670{
671 return m_structure.get_total_elements();
672}
673
674MemoryLayout TextureContainer::get_memory_layout() const
675{
676 return m_structure.memory_layout;
677}
678
679void TextureContainer::set_memory_layout(MemoryLayout layout)
680{
681 m_structure.memory_layout = layout;
682}
683
684uint64_t TextureContainer::get_frame_size() const
685{
686 return static_cast<uint64_t>(m_width) * m_channels;
687}
688
689uint64_t TextureContainer::get_num_frames() const
690{
691 return m_height;
692}
693
694std::vector<DataVariant> TextureContainer::get_region_data(const Region& region) const
695{
696 if (m_data.empty())
697 return {};
698
699 const size_t layer = (m_data.size() > 1 && !region.start_coordinates.empty())
700 ? static_cast<size_t>(region.start_coordinates[0])
701 : 0;
702
703 if (layer >= m_data.size())
704 return {};
705
706 std::optional<std::vector<DataVariant>> result;
707 seqlock_read_void(m_slot_locks[layer], 8, [&] {
708 result = std::visit(
709 [&](const auto& vec) -> std::vector<DataVariant> {
710 using T = typename std::decay_t<decltype(vec)>::value_type;
711 if constexpr (std::is_same_v<T, uint8_t>
712 || std::is_same_v<T, uint16_t>
713 || std::is_same_v<T, float>) {
714 auto extracted = extract_region_data<T>(
715 std::span<const T>(vec.data(), vec.size()),
716 region,
717 m_structure.dimensions);
718 return { DataVariant(std::move(extracted)) };
719 } else {
720 return {};
721 }
722 },
723 m_data[layer]);
724 });
725 return result.value_or(std::vector<DataVariant> {});
726}
727
728std::vector<DataVariant> TextureContainer::get_segments_data(
729 const std::vector<RegionSegment>& /*segments*/) const
730{
731 std::vector<DataVariant> out;
732 out.reserve(m_data.size());
733 for (size_t i = 0; i < m_data.size(); ++i) {
734 seqlock_read_void(m_slot_locks[i], 8, [&] {
735 out.push_back(m_data[i]);
736 });
737 }
738 return out;
739}
740
741void TextureContainer::set_region_data(
742 const Region& region, const std::vector<DataVariant>& data)
743{
744 if (data.empty())
745 return;
746 if (region.start_coordinates.size() < 2 || region.end_coordinates.size() < 2)
747 return;
748
749 const size_t layer = (m_data.size() > 1 && !region.start_coordinates.empty())
750 ? static_cast<size_t>(region.start_coordinates[0])
751 : 0;
752
753 if (layer >= m_data.size())
754 return;
755
756 const size_t coord_offset = (m_data.size() > 1) ? 1 : 0;
757 const uint64_t y0 = region.start_coordinates[coord_offset];
758 const uint64_t x0 = region.start_coordinates[coord_offset + 1];
759 const uint64_t y1 = std::min(region.end_coordinates[coord_offset], static_cast<uint64_t>(m_height - 1));
760 const uint64_t x1 = std::min(region.end_coordinates[coord_offset + 1], static_cast<uint64_t>(m_width - 1));
761
762 Memory::SeqlockWriteGuard g(m_slot_locks[layer]);
763 std::visit(
764 [&](auto& dst_vec) {
765 using T = typename std::decay_t<decltype(dst_vec)>::value_type;
766 if constexpr (std::is_same_v<T, uint8_t>
767 || std::is_same_v<T, uint16_t>
768 || std::is_same_v<T, float>) {
769 const auto* src = std::get_if<std::vector<T>>(&data[0]);
770 if (!src || src->empty()) {
771 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
772 "TextureContainer::set_region_data source variant does not match "
773 "container element type");
774 return;
775 }
776 size_t src_idx = 0;
777 for (uint64_t y = y0; y <= y1 && src_idx < src->size(); ++y) {
778 for (uint64_t x = x0; x <= x1 && src_idx < src->size(); ++x) {
779 const size_t dst_idx = (y * m_width + x) * m_channels;
780 for (uint32_t c = 0; c < m_channels && src_idx < src->size(); ++c, ++src_idx) {
781 if (dst_idx + c < dst_vec.size())
782 dst_vec[dst_idx + c] = (*src)[src_idx];
783 }
784 }
785 }
786 }
787 },
788 m_data[layer]);
789}
790
791std::type_index TextureContainer::value_element_type() const
792{
793 switch (storage_kind_for(m_format)) {
794 case StorageKind::U8:
795 return typeid(uint8_t);
796 case StorageKind::U16:
797 return typeid(uint16_t);
798 case StorageKind::F32:
799 return typeid(float);
800 }
801 return typeid(uint8_t);
802}
803
804uint64_t TextureContainer::coordinates_to_linear_index(const std::vector<uint64_t>& coords) const
805{
806 return coordinates_to_linear(coords, m_structure.dimensions);
807}
808
809std::vector<uint64_t> TextureContainer::linear_index_to_coordinates(uint64_t index) const
810{
811 return linear_to_coordinates(index, m_structure.dimensions);
812}
813
814void TextureContainer::clear()
815{
816 const size_t element_count = static_cast<size_t>(m_width) * m_height * m_channels;
817
818 for (size_t i = 0; i < m_data.size(); ++i) {
819 Memory::SeqlockWriteGuard g(m_slot_locks[i]);
820 m_data[i] = make_empty_storage(m_format, element_count);
821 }
822
823 update_processing_state(ProcessingState::IDLE);
824}
825
826//=============================================================================
827// SignalSourceContainer
828//=============================================================================
829
830ProcessingState TextureContainer::get_processing_state() const
831{
832 return m_processing_state.load();
833}
834
835void TextureContainer::update_processing_state(ProcessingState state)
836{
837 ProcessingState prev = m_processing_state.exchange(state);
838 if (prev == state)
839 return;
840
841 seqlock_read_void(m_cb_lock, 8, [&] {
842 if (m_state_cb)
843 m_state_cb(shared_from_this(), state);
844 });
845}
846
847void TextureContainer::register_state_change_callback(
848 std::function<void(const std::shared_ptr<SignalSourceContainer>&, ProcessingState)> cb)
849{
850 Memory::SeqlockWriteGuard g(m_cb_lock);
851 m_state_cb = std::move(cb);
852}
853
854void TextureContainer::unregister_state_change_callback()
855{
856 Memory::SeqlockWriteGuard g(m_cb_lock);
857 m_state_cb = nullptr;
858}
859
860bool TextureContainer::is_ready_for_processing() const
861{
862 return m_ready_for_processing.load(std::memory_order_acquire);
863}
864
865void TextureContainer::mark_ready_for_processing(bool ready)
866{
867 m_ready_for_processing.store(ready, std::memory_order_release);
868}
869
870std::vector<DataVariant>& TextureContainer::get_processed_data()
871{
872 return m_processed_data;
873}
874
875const std::vector<DataVariant>& TextureContainer::get_processed_data() const
876{
877 return m_processed_data;
878}
879
880const std::vector<DataVariant>& TextureContainer::get_data()
881{
882 return m_data;
883}
884
885DataAccess TextureContainer::channel_data(size_t channel_index)
886{
887 (void)channel_index;
888
889 if (m_data.empty()) {
890 static DataVariant empty = std::vector<uint8_t> {};
891 static std::vector<DataDimension> empty_dims;
892 return { empty, empty_dims, DataModality::IMAGE_COLOR };
893 }
894
895 return { m_data[0], m_structure.dimensions, DataModality::IMAGE_COLOR };
896}
897
898std::vector<DataAccess> TextureContainer::all_channel_data()
899{
900 std::vector<DataAccess> result;
901 result.reserve(m_channels);
902 for (size_t c = 0; c < m_channels; ++c)
903 result.push_back(channel_data(c));
904 return result;
905}
906
907void TextureContainer::add_region_group(const RegionGroup& group)
908{
909 Memory::SeqlockWriteGuard g(m_region_lock);
910 m_region_groups[group.name] = group;
911}
912
913RegionGroup TextureContainer::get_region_group(const std::string& name) const
914{
915 static const RegionGroup empty;
916 std::optional<RegionGroup> result;
917 seqlock_read_void(m_region_lock, 8, [&] {
918 auto it = m_region_groups.find(name);
919 result = (it != m_region_groups.end()) ? it->second : empty;
920 });
921 return result.value_or(empty);
922}
923
924std::unordered_map<std::string, RegionGroup> TextureContainer::get_all_region_groups() const
925{
926 std::optional<std::unordered_map<std::string, RegionGroup>> result;
927 seqlock_read_void(m_region_lock, 8, [&] {
928 result = m_region_groups;
929 });
930 return result.value_or(std::unordered_map<std::string, RegionGroup> {});
931}
932
933void TextureContainer::remove_region_group(const std::string& name)
934{
935 Memory::SeqlockWriteGuard g(m_region_lock);
936 m_region_groups.erase(name);
937}
938
939const void* TextureContainer::get_raw_data() const
940{
941 if (m_data.empty()) {
942 return nullptr;
943 }
944
945 auto [ptr, bytes] = variant_bytes(m_data[0]);
946 return (ptr && bytes > 0) ? static_cast<const void*>(ptr) : nullptr;
947}
948
949bool TextureContainer::has_data() const
950{
951 if (m_data.empty()) {
952 return false;
953 }
954
955 auto [ptr, bytes] = variant_bytes(m_data[0]);
956 return ptr && bytes > 0;
957}
958
959std::shared_ptr<DataProcessingChain> TextureContainer::get_processing_chain()
960{
961 if (!m_chain) {
962 m_chain = std::make_shared<DataProcessingChain>();
963 }
964 return m_chain;
965}
966
967void TextureContainer::get_frames_impl(
968 void* output,
969 size_t count,
970 uint64_t start_frame,
971 uint64_t num_frames,
972 const std::type_info& type) const
973{
974 get_frames_typed(output, count, start_frame, num_frames, type);
975}
976
977auto TextureContainer::get_frame_typed(uint64_t frame_index) const -> DataSpanVariant
978{
979 if (frame_index >= m_data.size()) {
980 return { std::span<const uint8_t> {} };
981 }
982
983 const size_t layer_elems = static_cast<size_t>(m_width) * m_height * m_channels;
984 DataSpanVariant out { std::span<const uint8_t> {} };
985 seqlock_read_void(m_slot_locks[frame_index], 8, [&] {
986 out = std::visit(
987 [&](const auto& vec) -> DataSpanVariant {
988 using T = typename std::decay_t<decltype(vec)>::value_type;
989 if constexpr (std::is_same_v<T, uint8_t> || std::is_same_v<T, uint16_t> || std::is_same_v<T, float>) {
990 const size_t nn = std::min(layer_elems, vec.size());
991 return DataSpanVariant(std::span<const T>(vec.data(), nn));
992 } else {
993 return { std::span<const uint8_t> {} };
994 }
995 },
996 m_data[frame_index]);
997 });
998 return out;
999}
1000
1001void TextureContainer::get_frames_typed(
1002 void* output,
1003 size_t count,
1004 uint64_t start_frame,
1005 uint64_t num_frames,
1006 const std::type_info& type) const
1007{
1008 if (type == typeid(uint8_t)) {
1009 get_frames_typed_as<uint8_t>(std::span<uint8_t>(static_cast<uint8_t*>(output), count), start_frame, num_frames);
1010 return;
1011 }
1012 if (type == typeid(uint16_t)) {
1013 get_frames_typed_as<uint16_t>(std::span<uint16_t>(static_cast<uint16_t*>(output), count), start_frame, num_frames);
1014 return;
1015 }
1016 if (type == typeid(float)) {
1017 get_frames_typed_as<float>(std::span<float>(static_cast<float*>(output), count), start_frame, num_frames);
1018 return;
1019 }
1020
1021 error<std::runtime_error>(
1022 Journal::Component::Kakshya,
1023 Journal::Context::Runtime,
1024 std::source_location::current(),
1025 "TextureContainer supports only uint8_t, uint16_t, and float for typed frame extraction");
1026}
1027
1028template <typename T>
1029auto TextureContainer::get_frame_typed_as(uint64_t frame_index) const -> std::span<const T>
1030{
1031 if (frame_index >= m_data.size())
1032 return {};
1033
1034 std::span<const T> result;
1035 seqlock_read_void(m_slot_locks[frame_index], 8, [&] {
1036 const auto* vec = std::get_if<std::vector<T>>(&m_data[frame_index]);
1037 if (!vec || vec->empty())
1038 return;
1039 const size_t layer_elems = static_cast<size_t>(m_width) * m_height * m_channels;
1040 const size_t n = std::min(layer_elems, vec->size());
1041 result = std::span<const T>(vec->data(), n);
1042 });
1043
1044 return result;
1045}
1046
1047template <typename T>
1048void TextureContainer::get_frames_typed_as(std::span<T> output, uint64_t start_frame, uint64_t num_frames) const
1049{
1050 const size_t layer_elems = static_cast<size_t>(m_width) * m_height * m_channels;
1051 size_t out_idx = 0;
1052
1053 for (uint64_t layer = start_frame;
1054 layer < start_frame + num_frames && layer < m_data.size() && out_idx < output.size();
1055 ++layer) {
1056 seqlock_read_void(m_slot_locks[layer], 8, [&] {
1057 const auto* vec = std::get_if<std::vector<T>>(&m_data[layer]);
1058 if (!vec || vec->empty())
1059 return;
1060 const size_t copy_n = std::min(layer_elems, std::min(vec->size(), output.size() - out_idx));
1061 std::copy_n(vec->begin(), static_cast<std::ptrdiff_t>(copy_n), output.begin() + static_cast<std::ptrdiff_t>(out_idx));
1062 out_idx += copy_n;
1063 });
1064 }
1065
1066 if (out_idx < output.size()) {
1067 std::fill(output.begin() + static_cast<std::ptrdiff_t>(out_idx), output.end(), T {});
1068 }
1069}
1070
1071void TextureContainer::get_value_impl(
1072 const std::vector<uint64_t>& coords,
1073 void* out,
1074 const std::type_info& type) const
1075{
1076 if (coords.empty() || m_data.empty())
1077 return;
1078
1079 const size_t layer = (m_data.size() > 1) ? static_cast<size_t>(coords[0]) : 0;
1080 if (layer >= m_data.size() || coords.size() < (m_data.size() > 1 ? 4U : 3U))
1081 return;
1082
1083 const size_t co = (m_data.size() > 1) ? 1 : 0;
1084 const size_t idx = (coords[co] * m_width + coords[co + 1]) * m_channels + coords[co + 2];
1085
1086 seqlock_read_void(m_slot_locks[layer], 8, [&] {
1087 std::visit([&](const auto& vec) {
1088 using T = typename std::decay_t<decltype(vec)>::value_type;
1089 if (type != typeid(T) || idx >= vec.size())
1090 return;
1091 *static_cast<T*>(out) = vec[idx];
1092 },
1093 m_data[layer]);
1094 });
1095}
1096
1097void TextureContainer::set_value_impl(
1098 const std::vector<uint64_t>& coords,
1099 const void* in,
1100 const std::type_info& type)
1101{
1102 if (coords.empty() || m_data.empty())
1103 return;
1104
1105 const size_t layer = (m_data.size() > 1) ? static_cast<size_t>(coords[0]) : 0;
1106 if (layer >= m_data.size() || coords.size() < (m_data.size() > 1 ? 4U : 3U))
1107 return;
1108
1109 const size_t co = (m_data.size() > 1) ? 1 : 0;
1110 const size_t idx = (coords[co] * m_width + coords[co + 1]) * m_channels + coords[co + 2];
1111
1112 Memory::SeqlockWriteGuard g(m_slot_locks[layer]);
1113 std::visit([&](auto& vec) {
1114 using T = typename std::decay_t<decltype(vec)>::value_type;
1115 if (type != typeid(T) || idx >= vec.size())
1116 return;
1117 vec[idx] = *static_cast<const T*>(in);
1118 },
1119 m_data[layer]);
1120}
1121
1122} // namespace MayaFlux::Kakshya
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
IO::ImageData image
Definition Decoder.cpp:64
uint32_t width
Definition Decoder.cpp:66
uint32_t h
Definition InkPress.cpp:28
size_t count
const uint8_t * ptr
float value
std::shared_ptr< Core::VKImage > output
uint32_t height
Type-erased accessor for NDData with semantic view construction.
TextureContainer(uint32_t width, uint32_t height, Portal::Graphics::ImageFormat format, uint32_t layers=1)
Construct an empty container with declared dimensions.
RAII guard that brackets a Seqlock write region.
Definition SeqLock.hpp:136
ProcessingState
Represents the current processing lifecycle state of a container.
uint64_t coordinates_to_linear(const std::vector< uint64_t > &coords, const std::vector< DataDimension > &dimensions)
Convert N-dimensional coordinates to a linear index for interleaved data.
Definition CoordUtils.cpp:8
typename detail::span_const_from_vector_variant< DataVariant >::type DataSpanVariant
Definition NDData.hpp:592
std::variant< std::vector< double >, std::vector< float >, std::vector< uint8_t >, std::vector< uint16_t >, std::vector< uint32_t >, std::vector< std::complex< float > >, std::vector< std::complex< double > >, std::vector< glm::vec2 >, std::vector< glm::vec3 >, std::vector< glm::vec4 >, std::vector< glm::mat4 > > DataVariant
Multi-type data storage for different precision needs.
Definition NDData.hpp:102
std::vector< uint64_t > linear_to_coordinates(uint64_t index, const std::vector< DataDimension > &dimensions)
Convert a linear index to N-dimensional coordinates for interleaved data.
MemoryLayout
Memory layout for multi-dimensional data.
Definition NDData.hpp:65
ImageFormat
User-friendly image format enum.
std::shared_ptr< T > store(std::shared_ptr< T > obj)
Transfer ownership of an existing object to the persistent store for process lifetime.
Definition Persist.hpp:28
std::string name
Descriptive name of the group.
Organizes related signal regions into a categorized collection.
std::vector< uint64_t > end_coordinates
Ending frame index (inclusive)
Definition Region.hpp:78
std::vector< uint64_t > start_coordinates
Starting frame index (inclusive)
Definition Region.hpp:75
Represents a point or span in N-dimensional space.
Definition Region.hpp:73