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