MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
TextureContainer.cpp
Go to the documentation of this file.
2
6
11
13
14namespace MayaFlux::Kakshya {
15
16using Portal::Graphics::TextureLoom;
17
18namespace {
19
20 std::vector<std::atomic<bool>> extended(const std::vector<std::atomic<bool>>& flags, bool value)
21 {
22 std::vector<std::atomic<bool>> out(flags.size() + 1);
23 for (size_t i = 0; i < flags.size(); ++i)
24 out[i].store(flags[i].load(std::memory_order_acquire), std::memory_order_relaxed);
25 out.back().store(value, std::memory_order_relaxed);
26 return out;
27 }
28
29} // namespace
30
31//=============================================================================
32// Construction
33//=============================================================================
34
36 : TextureContainer(width, height, format, LayerCount { std::max(layers, 1U) })
37{
38}
39
41 : m_width(width)
42 , m_height(height)
43 , m_format(format)
44 , m_channels(TextureLoom::get_channel_count(format))
45 , m_bpp(TextureLoom::get_bytes_per_pixel(format))
46{
47 m_chain = std::make_shared<DataProcessingChain>();
48 const size_t element_count = static_cast<size_t>(m_width) * m_height * m_channels;
49
50 for (uint32_t i = 0; i < layers.value; ++i) {
51 m_data.emplace_back(make_empty_storage(m_format, element_count));
52 }
53
54 m_layer_image_cache.resize(m_data.size());
55
56 m_normalised_cache.resize(m_data.size());
57
58 m_normalised_dirty = std::vector<std::atomic<bool>>(m_data.size());
59 for (auto& flag : m_normalised_dirty)
60 flag.store(true, std::memory_order_relaxed);
61
62 m_cpu_stale = std::vector<std::atomic<bool>>(m_data.size());
63 for (auto& flag : m_cpu_stale)
64 flag.store(false, std::memory_order_relaxed);
65
68
69 m_ready_for_processing.store(true);
70
72 "TextureContainer created: {}x{} layers={} fmt={} bpp={}",
73 m_width, m_height, m_data.size(), static_cast<int>(m_format), m_bpp);
74}
75
76TextureContainer::TextureContainer(const std::shared_ptr<Core::VKImage>& image, ImageFormat format)
77 : TextureContainer(image->get_width(), image->get_height(), format)
78{
79 from_image(image, 0);
80}
81
82//=============================================================================
83// Setup
84//=============================================================================
85
87{
88 const uint64_t h = m_height;
89 const uint64_t w = m_width;
90 const uint64_t c = m_channels;
91 const auto n = static_cast<uint64_t>(m_data.size());
92
94
95 if (n != 1) {
98 } else {
101 }
102}
103
104//=============================================================================
105// GPU-held layers
106//=============================================================================
107
108uint32_t TextureContainer::append_image_layer(std::shared_ptr<Core::VKImage> image)
109{
110 const auto layer = static_cast<uint32_t>(m_data.size());
111
112 m_data.emplace_back(make_empty_storage(m_format, 0));
114 m_normalised_cache.emplace_back();
115 m_normalised_dirty = extended(m_normalised_dirty, true);
116 m_cpu_stale = extended(m_cpu_stale, true);
117
118 {
120 m_layer_image_cache.push_back({
121 .key = { .width = m_width, .height = m_height, .layers = 1, .format = m_format },
122 .image = std::move(image),
123 });
124 }
125
128 return layer;
129}
130
131std::shared_ptr<Core::VKImage> TextureContainer::layer_image(uint32_t layer) const
132{
133 if (layer >= m_layer_image_cache.size())
134 return nullptr;
135
137 return m_layer_image_cache[layer].image;
138}
139
141{
142 if (layer >= m_data.size())
143 return;
144
145 {
148 m_cpu_stale[layer].store(true, std::memory_order_release);
149 m_normalised_dirty[layer].store(true, std::memory_order_release);
150 }
151
153}
154
155void TextureContainer::ensure_cpu(uint32_t layer) const
156{
157 if (layer >= m_cpu_stale.size() || !m_cpu_stale[layer].load(std::memory_order_acquire))
158 return;
159
160 std::shared_ptr<Core::VKImage> image;
161 {
163 image = m_layer_image_cache[layer].image;
164 }
165
166 const size_t element_count = static_cast<size_t>(m_width) * m_height * m_channels;
167
169 if (!m_cpu_stale[layer].load(std::memory_order_acquire))
170 return;
171
172 m_data[layer] = make_empty_storage(m_format, element_count);
173 auto [ptr, bytes] = variant_bytes_mutable(m_data[layer]);
174 if (image && image->is_initialized() && ptr && bytes == byte_size()) {
175 TextureLoom::instance().download_data(image, ptr, bytes, nullptr);
176 } else {
178 "TextureContainer: GPU-held layer {} could not be downloaded", layer);
179 }
180
181 m_cpu_stale[layer].store(false, std::memory_order_release);
182 m_normalised_dirty[layer].store(true, std::memory_order_release);
183}
184
186{
187 for (uint32_t i = 0; i < static_cast<uint32_t>(m_data.size()); ++i)
188 ensure_cpu(i);
189}
190
191void TextureContainer::claim_cpu(uint32_t layer)
192{
193 if (layer >= m_cpu_stale.size() || !m_cpu_stale[layer].load(std::memory_order_acquire))
194 return;
195
196 const size_t element_count = static_cast<size_t>(m_width) * m_height * m_channels;
197
199 m_data[layer] = make_empty_storage(m_format, element_count);
200 m_cpu_stale[layer].store(false, std::memory_order_release);
201}
202
203//=============================================================================
204// GPU bridge
205//=============================================================================
206
207void TextureContainer::from_image(const std::shared_ptr<Core::VKImage>& image, uint32_t layer)
208{
209 if (!image || !image->is_initialized()) {
211 "TextureContainer::from_image called with uninitialised image");
212 return;
213 }
214
215 if (layer >= m_data.size()) {
217 "TextureContainer::from_image layer {} out of range ({})", layer, m_data.size());
218 return;
219 }
220
221 const size_t sz = byte_size();
222 const size_t element_count = static_cast<size_t>(m_width) * m_height * m_channels;
223
224 {
226 m_data[layer] = make_empty_storage(m_format, element_count);
227 auto [ptr, bytes] = variant_bytes_mutable(m_data[layer]);
228 if (!ptr || bytes != sz) {
230 "TextureContainer::from_image variant size mismatch ({} vs {})", bytes, sz);
231 return;
232 }
234 m_cpu_stale[layer].store(false, std::memory_order_release);
235 }
236
237 m_normalised_dirty[layer].store(true, std::memory_order_release);
239}
240
242 const std::shared_ptr<Core::VKImage>& image,
243 const std::shared_ptr<Buffers::VKBuffer>& staging,
244 uint32_t layer)
245{
246 if (!image || !image->is_initialized()) {
248 "TextureContainer::from_image(staging) called with uninitialised image");
249 return;
250 }
251
252 if (layer >= m_data.size()) {
254 "TextureContainer::from_image(staging) layer {} out of range ({})", layer, m_data.size());
255 return;
256 }
257
258 const size_t sz = byte_size();
259 const size_t element_count = static_cast<size_t>(m_width) * m_height * m_channels;
260
261 {
263 m_data[layer] = make_empty_storage(m_format, element_count);
264 auto [ptr, bytes] = variant_bytes_mutable(m_data[layer]);
265 if (!ptr || bytes != sz) {
267 "TextureContainer::from_image(staging) variant size mismatch ({} vs {})", bytes, sz);
268 return;
269 }
271 m_cpu_stale[layer].store(false, std::memory_order_release);
272 }
273
274 m_normalised_dirty[layer].store(true, std::memory_order_release);
276}
277
278void TextureContainer::from_image_array(const std::shared_ptr<Core::VKImage>& image)
279{
280 if (!image || !image->is_initialized()) {
282 "TextureContainer::from_image_array called with uninitialised image");
283 return;
284 }
285
286 const auto n = static_cast<uint32_t>(m_data.size());
287 if (image->get_array_layers() < n) {
289 "TextureContainer::from_image_array image has {} layers, container expects {}",
290 image->get_array_layers(), n);
291 return;
292 }
293
294 const size_t layer_bytes = byte_size();
295 std::vector<uint8_t> combined(layer_bytes * n);
296 TextureLoom::instance().download_data(image, combined.data(), combined.size(), nullptr);
297
298 const size_t element_count = static_cast<size_t>(m_width) * m_height * m_channels;
299 for (uint32_t i = 0; i < n; ++i) {
301 m_data[i] = make_empty_storage(m_format, element_count);
302 auto [ptr, bytes] = variant_bytes_mutable(m_data[i]);
303 if (ptr && bytes == layer_bytes)
304 std::memcpy(ptr, combined.data() + i * layer_bytes, layer_bytes);
305 m_cpu_stale[i].store(false, std::memory_order_release);
306
307 m_normalised_dirty[i].store(true, std::memory_order_release);
308 }
309
311}
312
314 const std::shared_ptr<Core::VKImage>& image,
315 const std::shared_ptr<Buffers::VKBuffer>& staging)
316{
317 if (!image || !image->is_initialized()) {
319 "TextureContainer::from_image_array(staging) called with uninitialised image");
320 return;
321 }
322
323 const auto n = static_cast<uint32_t>(m_data.size());
324 if (image->get_array_layers() < n) {
326 "TextureContainer::from_image_array(staging) image has {} layers, container expects {}",
327 image->get_array_layers(), n);
328 return;
329 }
330
331 const size_t layer_bytes = byte_size();
332 std::vector<uint8_t> combined(layer_bytes * n);
333 TextureLoom::instance().download_data(image, combined.data(), combined.size(), staging);
334
335 const size_t element_count = static_cast<size_t>(m_width) * m_height * m_channels;
336 for (uint32_t i = 0; i < n; ++i) {
338 m_data[i] = make_empty_storage(m_format, element_count);
339 auto [ptr, bytes] = variant_bytes_mutable(m_data[i]);
340 if (ptr && bytes == layer_bytes)
341 std::memcpy(ptr, combined.data() + i * layer_bytes, layer_bytes);
342 m_cpu_stale[i].store(false, std::memory_order_release);
343
344 m_normalised_dirty[i].store(true, std::memory_order_release);
345 }
346
348}
349
350std::shared_ptr<Core::VKImage> TextureContainer::to_image(uint32_t layer) const
351{
352 return cached_layer_image(layer, nullptr);
353}
354
355std::shared_ptr<Core::VKImage> TextureContainer::to_image(
356 uint32_t layer, const std::shared_ptr<Buffers::VKBuffer>& staging) const
357{
358 return cached_layer_image(layer, staging);
359}
360
361std::shared_ptr<Core::VKImage> TextureContainer::cached_layer_image(
362 uint32_t layer, const std::shared_ptr<Buffers::VKBuffer>& staging) const
363{
364 if (layer >= m_data.size()) {
366 "TextureContainer::to_image layer {} out of range ({})", layer, m_data.size());
367 return nullptr;
368 }
369
370 if (m_cpu_stale[layer].load(std::memory_order_acquire)) {
372 return m_layer_image_cache[layer].image;
373 }
374
375 const size_t expected = byte_size();
376 auto pixels = seqlock_read(m_slot_locks[layer], 8, [&]() -> std::vector<uint8_t> {
377 auto [ptr, bytes] = variant_bytes(m_data[layer]);
378 if (!ptr || bytes != expected)
379 return {};
380 return { ptr, ptr + bytes };
381 });
382
383 if (!pixels || pixels->empty()) {
385 "TextureContainer::to_image called on empty or invalid layer {}", layer);
386 return nullptr;
387 }
388
390 .width = m_width, .height = m_height, .layers = 1, .format = m_format
391 };
394 m_layer_image_cache[layer], key, *pixels, staging);
395}
396
397std::shared_ptr<Core::VKImage> TextureContainer::to_image_array() const
398{
399 return cached_array_image(nullptr);
400}
401
402std::shared_ptr<Core::VKImage> TextureContainer::to_image_array(
403 const std::shared_ptr<Buffers::VKBuffer>& staging) const
404{
405 return cached_array_image(staging);
406}
407
408std::shared_ptr<Core::VKImage> TextureContainer::cached_array_image(
409 const std::shared_ptr<Buffers::VKBuffer>& staging) const
410{
411 const auto n = static_cast<uint32_t>(m_data.size());
412 if (n == 0)
413 return nullptr;
414
415 if (n == 1)
416 return cached_layer_image(0, staging);
417
419
420 const size_t layer_bytes = byte_size();
421 std::vector<uint8_t> combined(layer_bytes * n);
422 for (uint32_t i = 0; i < n; ++i) {
423 bool valid = false;
424 const bool copied = seqlock_read_void(m_slot_locks[i], 8, [&] {
425 auto [ptr, bytes] = variant_bytes(m_data[i]);
426 valid = ptr && bytes == layer_bytes;
427 if (valid)
428 std::memcpy(combined.data() + i * layer_bytes, ptr, layer_bytes);
429 });
430 if (!copied || !valid) {
432 "TextureContainer::to_image_array layer {} has invalid pixel data", i);
433 return nullptr;
434 }
435 }
436
438 .width = m_width, .height = m_height, .layers = n, .format = m_format, .kind = Portal::Graphics::ImageKey::Kind::SAMPLED_ARRAY
439 };
442 m_array_image_cache, key, combined, staging);
443}
444
446 const std::shared_ptr<Core::VKImage>& image,
447 uint32_t layer,
448 const std::shared_ptr<Buffers::VKBuffer>& staging) const
449{
450 if (!image || layer >= m_data.size()) {
452 "TextureContainer::upload_image null image or layer {} out of range ({})", layer, m_data.size());
453 return false;
454 }
455
456 ensure_cpu(layer);
457
458 bool uploaded = false;
459 seqlock_read_void(m_slot_locks[layer], 8, [&] {
460 auto [ptr, bytes] = variant_bytes(m_data[layer]);
461 if (!ptr || bytes == 0) {
463 "TextureContainer::upload_image called on empty/invalid buffer");
464 return;
465 }
466 TextureLoom::instance().upload_data(image, ptr, bytes, staging);
467 uploaded = true;
468 });
469 return uploaded;
470}
471
473 const std::shared_ptr<Core::VKImage>& image,
474 const std::shared_ptr<Buffers::VKBuffer>& staging) const
475{
476 const auto n = static_cast<uint32_t>(m_data.size());
477 if (!image || n == 0)
478 return false;
479
480 if (n == 1)
481 return upload_image(image, 0, staging);
482
484
485 const size_t layer_bytes = byte_size();
486 std::vector<uint8_t> combined(layer_bytes * n);
487 for (uint32_t i = 0; i < n; ++i) {
488 const bool ok = seqlock_read_void(m_slot_locks[i], 8, [&] {
489 auto [ptr, bytes] = variant_bytes(m_data[i]);
490 if (!ptr || bytes != layer_bytes) {
492 "TextureContainer::upload_image_array layer {} size mismatch", i);
493 return;
494 }
495 std::memcpy(combined.data() + i * layer_bytes, ptr, layer_bytes);
496 });
497 if (!ok)
498 return false;
499 }
500
501 TextureLoom::instance().upload_data(image, combined.data(), combined.size(), staging);
502 return true;
503}
504
505//=============================================================================
506// Pixel access
507//=============================================================================
508
509std::span<const uint8_t> TextureContainer::pixel_bytes(uint32_t layer) const
510{
511 if (layer >= m_data.size())
512 return {};
513 ensure_cpu(layer);
514 auto [ptr, bytes] = variant_bytes(m_data[layer]);
515 return ptr ? std::span<const uint8_t>(ptr, bytes) : std::span<const uint8_t> {};
516}
517
518std::span<uint8_t> TextureContainer::pixel_bytes(uint32_t layer)
519{
520 if (layer >= m_data.size())
521 return {};
522 ensure_cpu(layer);
523 auto [ptr, bytes] = variant_bytes_mutable(m_data[layer]);
524 return ptr ? std::span<uint8_t>(ptr, bytes) : std::span<uint8_t> {};
525}
526
527std::span<const uint8_t> TextureContainer::as_uint8(uint32_t layer) const
528{
529 if (layer >= m_data.size())
530 return {};
531 ensure_cpu(layer);
532 const auto* v = std::get_if<std::vector<uint8_t>>(&m_data[layer]);
533 return v ? std::span<const uint8_t>(v->data(), v->size()) : std::span<const uint8_t> {};
534}
535
536std::span<const uint16_t> TextureContainer::as_uint16(uint32_t layer) const
537{
538 if (layer >= m_data.size())
539 return {};
540 ensure_cpu(layer);
541 const auto* v = std::get_if<std::vector<uint16_t>>(&m_data[layer]);
542 return v ? std::span<const uint16_t>(v->data(), v->size()) : std::span<const uint16_t> {};
543}
544
545std::span<const float> TextureContainer::as_float(uint32_t layer) const
546{
547 if (layer >= m_data.size())
548 return {};
549 ensure_cpu(layer);
550 const auto* v = std::get_if<std::vector<float>>(&m_data[layer]);
551 return v ? std::span<const float>(v->data(), v->size()) : std::span<const float> {};
552}
553
554void TextureContainer::set_pixels(std::span<const uint8_t> data, uint32_t layer)
555{
556 if (layer >= m_data.size()) {
558 "TextureContainer::set_pixels(u8) layer {} out of range", layer);
559 return;
560 }
561 claim_cpu(layer);
562 auto* buf = std::get_if<std::vector<uint8_t>>(&m_data[layer]);
563 if (!buf) {
565 "TextureContainer::set_pixels(u8) called on non-uint8 format {}",
566 static_cast<int>(m_format));
567 return;
568 }
569 if (data.size() != buf->size()) {
571 "TextureContainer::set_pixels(u8) size mismatch: got {} expected {}",
572 data.size(), buf->size());
573 return;
574 }
575
577 std::ranges::copy(data, buf->begin());
578 m_normalised_dirty[layer].store(true, std::memory_order_release);
579}
580
581void TextureContainer::set_pixels(std::span<const uint16_t> data, uint32_t layer)
582{
583 if (layer >= m_data.size()) {
585 "TextureContainer::set_pixels(u16) layer {} out of range", layer);
586 return;
587 }
588 claim_cpu(layer);
589 auto* buf = std::get_if<std::vector<uint16_t>>(&m_data[layer]);
590 if (!buf) {
592 "TextureContainer::set_pixels(u16) called on non-uint16 format {}",
593 static_cast<int>(m_format));
594 return;
595 }
596 if (data.size() != buf->size()) {
598 "TextureContainer::set_pixels(u16) size mismatch: got {} expected {}",
599 data.size(), buf->size());
600 return;
601 }
602
604 std::ranges::copy(data, buf->begin());
605 m_normalised_dirty[layer].store(true, std::memory_order_release);
606}
607
608void TextureContainer::set_pixels(std::span<const float> data, uint32_t layer)
609{
610 if (layer >= m_data.size()) {
612 "TextureContainer::set_pixels(f32) layer {} out of range", layer);
613 return;
614 }
615 claim_cpu(layer);
616 auto* buf = std::get_if<std::vector<float>>(&m_data[layer]);
617 if (!buf) {
619 "TextureContainer::set_pixels(f32) called on non-float format {}",
620 static_cast<int>(m_format));
621 return;
622 }
623 if (data.size() != buf->size()) {
625 "TextureContainer::set_pixels(f32) size mismatch: got {} expected {}",
626 data.size(), buf->size());
627 return;
628 }
629
631 std::ranges::copy(data, buf->begin());
632 m_normalised_dirty[layer].store(true, std::memory_order_release);
633}
634
635std::span<const float> TextureContainer::as_normalised_float(uint32_t layer) const
636{
637 if (layer >= m_data.size())
638 return {};
639
640 ensure_cpu(layer);
641
642 if (!m_normalised_dirty[layer].load(std::memory_order_acquire) && !m_normalised_cache[layer].empty())
643 return { m_normalised_cache[layer] };
644
645 std::span<const float> result;
646 seqlock_read_void(m_slot_locks[layer], 8, [&] {
648 });
649
650 if (!result.empty())
651 m_normalised_dirty[layer].store(false, std::memory_order_release);
652
653 return result;
654}
655
656//=============================================================================
657// NDDimensionalContainer
658//=============================================================================
659
660std::vector<DataDimension> TextureContainer::get_dimensions() const
661{
662 return m_structure.dimensions;
663}
664
669
674
679
681{
682 return static_cast<uint64_t>(m_width) * m_channels;
683}
684
686{
687 return m_height;
688}
689
690std::vector<DataVariant> TextureContainer::get_region_data(const Region& region) const
691{
692 if (m_data.empty())
693 return {};
694
695 const size_t layer = (m_data.size() > 1 && !region.start_coordinates.empty())
696 ? static_cast<size_t>(region.start_coordinates[0])
697 : 0;
698
699 if (layer >= m_data.size())
700 return {};
701
702 ensure_cpu(static_cast<uint32_t>(layer));
703
704 std::optional<std::vector<DataVariant>> result;
705 seqlock_read_void(m_slot_locks[layer], 8, [&] {
706 result = std::visit(
707 [&](const auto& vec) -> std::vector<DataVariant> {
708 using T = typename std::decay_t<decltype(vec)>::value_type;
709 if constexpr (std::is_same_v<T, uint8_t>
710 || std::is_same_v<T, uint16_t>
711 || std::is_same_v<T, float>) {
712 auto extracted = extract_region_data<T>(
713 std::span<const T>(vec.data(), vec.size()),
714 region,
716 return { DataVariant(std::move(extracted)) };
717 } else {
718 return {};
719 }
720 },
721 m_data[layer]);
722 });
723 return result.value_or(std::vector<DataVariant> {});
724}
725
726std::vector<DataVariant> TextureContainer::get_segments_data(
727 const std::vector<RegionSegment>& /*segments*/) const
728{
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
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 ensure_cpu(static_cast<uint32_t>(layer));
757
758 const size_t coord_offset = (m_data.size() > 1) ? 1 : 0;
759 const uint64_t y0 = region.start_coordinates[coord_offset];
760 const uint64_t x0 = region.start_coordinates[coord_offset + 1];
761 const uint64_t y1 = std::min(region.end_coordinates[coord_offset], static_cast<uint64_t>(m_height - 1));
762 const uint64_t x1 = std::min(region.end_coordinates[coord_offset + 1], static_cast<uint64_t>(m_width - 1));
763
765 std::visit(
766 [&](auto& dst_vec) {
767 using T = typename std::decay_t<decltype(dst_vec)>::value_type;
768 if constexpr (std::is_same_v<T, uint8_t>
769 || std::is_same_v<T, uint16_t>
770 || std::is_same_v<T, float>) {
771 const auto* src = std::get_if<std::vector<T>>(&data[0]);
772 if (!src || src->empty()) {
774 "TextureContainer::set_region_data source variant does not match "
775 "container element type");
776 return;
777 }
778 size_t src_idx = 0;
779 for (uint64_t y = y0; y <= y1 && src_idx < src->size(); ++y) {
780 for (uint64_t x = x0; x <= x1 && src_idx < src->size(); ++x) {
781 const size_t dst_idx = (y * m_width + x) * m_channels;
782 for (uint32_t c = 0; c < m_channels && src_idx < src->size(); ++c, ++src_idx) {
783 if (dst_idx + c < dst_vec.size())
784 dst_vec[dst_idx + c] = (*src)[src_idx];
785 }
786 }
787 }
788 }
789 },
790 m_data[layer]);
791}
792
794{
795 size_t element_size = storage_element_size(m_format);
796 if (element_size == 1) {
797 return typeid(uint8_t);
798 }
799 if (element_size == 2) {
800 return typeid(uint16_t);
801 }
802 if (element_size == 4) {
803 return typeid(float);
804 }
805
806 return typeid(uint8_t);
807}
808
809uint64_t TextureContainer::coordinates_to_linear_index(const std::vector<uint64_t>& coords) const
810{
812}
813
814std::vector<uint64_t> TextureContainer::linear_index_to_coordinates(uint64_t index) const
815{
817}
818
820{
821 const size_t element_count = static_cast<size_t>(m_width) * m_height * m_channels;
822
823 for (size_t i = 0; i < m_data.size(); ++i) {
825 m_data[i] = make_empty_storage(m_format, element_count);
826 m_cpu_stale[i].store(false, std::memory_order_release);
827 m_normalised_dirty[i].store(true, std::memory_order_release);
828 }
829
831}
832
833//=============================================================================
834// SignalSourceContainer
835//=============================================================================
836
841
843{
844 ProcessingState prev = m_processing_state.exchange(state);
845 if (prev == state)
846 return;
847
848 seqlock_read_void(m_cb_lock, 8, [&] {
849 if (m_state_cb)
850 m_state_cb(shared_from_this(), state);
851 });
852}
853
855 std::function<void(const std::shared_ptr<SignalSourceContainer>&, ProcessingState)> cb)
856{
858 m_state_cb = std::move(cb);
859}
860
866
868{
869 return m_ready_for_processing.load(std::memory_order_acquire);
870}
871
873{
874 m_ready_for_processing.store(ready, std::memory_order_release);
875}
876
877std::vector<DataVariant>& TextureContainer::get_processed_data()
878{
879 return m_processed_data;
880}
881
882const std::vector<DataVariant>& TextureContainer::get_processed_data() const
883{
884 return m_processed_data;
885}
886
887const std::vector<DataVariant>& TextureContainer::get_data()
888{
890 return m_data;
891}
892
894{
895 (void)channel_index;
896
897 if (m_data.empty()) {
898 static DataVariant empty = std::vector<uint8_t> {};
899 static std::vector<DataDimension> empty_dims;
900 return { empty, empty_dims, DataModality::IMAGE_COLOR };
901 }
902
903 ensure_cpu(0);
905}
906
907std::vector<DataAccess> TextureContainer::all_channel_data()
908{
909 std::vector<DataAccess> result;
910 result.reserve(m_channels);
911 for (size_t c = 0; c < m_channels; ++c)
912 result.push_back(channel_data(c));
913 return result;
914}
915
921
923{
924 static const RegionGroup empty;
925 std::optional<RegionGroup> result;
926 seqlock_read_void(m_region_lock, 8, [&] {
927 auto it = m_region_groups.find(name);
928 result = (it != m_region_groups.end()) ? it->second : empty;
929 });
930 return result.value_or(empty);
931}
932
933std::unordered_map<std::string, RegionGroup> TextureContainer::get_all_region_groups() const
934{
935 std::optional<std::unordered_map<std::string, RegionGroup>> result;
936 seqlock_read_void(m_region_lock, 8, [&] {
937 result = m_region_groups;
938 });
939 return result.value_or(std::unordered_map<std::string, RegionGroup> {});
940}
941
947
949{
950 if (m_data.empty()) {
951 return nullptr;
952 }
953
954 ensure_cpu(0);
955 auto [ptr, bytes] = variant_bytes(m_data[0]);
956 return (ptr && bytes > 0) ? static_cast<const void*>(ptr) : nullptr;
957}
958
960{
961 if (m_data.empty()) {
962 return false;
963 }
964
965 if (m_cpu_stale[0].load(std::memory_order_acquire))
966 return true;
967
968 auto [ptr, bytes] = variant_bytes(m_data[0]);
969 return ptr && bytes > 0;
970}
971
972std::shared_ptr<DataProcessingChain> TextureContainer::get_processing_chain()
973{
974 if (!m_chain) {
975 m_chain = std::make_shared<DataProcessingChain>();
976 }
977 return m_chain;
978}
979
981 void* output,
982 size_t count,
983 uint64_t start_frame,
984 uint64_t num_frames,
985 const std::type_info& type) const
986{
987 get_frames_typed(output, count, start_frame, num_frames, type);
988}
989
990auto TextureContainer::get_frame_typed(uint64_t frame_index) const -> DataSpanVariant
991{
992 if (frame_index >= m_data.size()) {
993 return { std::span<const uint8_t> {} };
994 }
995
996 ensure_cpu(static_cast<uint32_t>(frame_index));
997
998 const size_t layer_elems = static_cast<size_t>(m_width) * m_height * m_channels;
999 DataSpanVariant out { std::span<const uint8_t> {} };
1000 seqlock_read_void(m_slot_locks[frame_index], 8, [&] {
1001 out = std::visit(
1002 [&](const auto& vec) -> DataSpanVariant {
1003 using T = typename std::decay_t<decltype(vec)>::value_type;
1004 if constexpr (std::is_same_v<T, uint8_t> || std::is_same_v<T, uint16_t> || std::is_same_v<T, float>) {
1005 const size_t nn = std::min(layer_elems, vec.size());
1006 return DataSpanVariant(std::span<const T>(vec.data(), nn));
1007 } else {
1008 return { std::span<const uint8_t> {} };
1009 }
1010 },
1011 m_data[frame_index]);
1012 });
1013 return out;
1014}
1015
1017 void* output,
1018 size_t count,
1019 uint64_t start_frame,
1020 uint64_t num_frames,
1021 const std::type_info& type) const
1022{
1023 if (type == typeid(uint8_t)) {
1024 get_frames_typed_as<uint8_t>(std::span<uint8_t>(static_cast<uint8_t*>(output), count), start_frame, num_frames);
1025 return;
1026 }
1027 if (type == typeid(uint16_t)) {
1028 get_frames_typed_as<uint16_t>(std::span<uint16_t>(static_cast<uint16_t*>(output), count), start_frame, num_frames);
1029 return;
1030 }
1031 if (type == typeid(float)) {
1032 get_frames_typed_as<float>(std::span<float>(static_cast<float*>(output), count), start_frame, num_frames);
1033 return;
1034 }
1035
1036 error<std::runtime_error>(
1039 std::source_location::current(),
1040 "TextureContainer supports only uint8_t, uint16_t, and float for typed frame extraction");
1041}
1042
1043template <typename T>
1044auto TextureContainer::get_frame_typed_as(uint64_t frame_index) const -> std::span<const T>
1045{
1046 if (frame_index >= m_data.size())
1047 return {};
1048
1049 ensure_cpu(static_cast<uint32_t>(frame_index));
1050
1051 std::span<const T> result;
1052 seqlock_read_void(m_slot_locks[frame_index], 8, [&] {
1053 const auto* vec = std::get_if<std::vector<T>>(&m_data[frame_index]);
1054 if (!vec || vec->empty())
1055 return;
1056 const size_t layer_elems = static_cast<size_t>(m_width) * m_height * m_channels;
1057 const size_t n = std::min(layer_elems, vec->size());
1058 result = std::span<const T>(vec->data(), n);
1059 });
1060
1061 return result;
1062}
1063
1064template <typename T>
1065void TextureContainer::get_frames_typed_as(std::span<T> output, uint64_t start_frame, uint64_t num_frames) const
1066{
1067 const size_t layer_elems = static_cast<size_t>(m_width) * m_height * m_channels;
1068 size_t out_idx = 0;
1069
1070 for (uint64_t layer = start_frame;
1071 layer < start_frame + num_frames && layer < m_data.size() && out_idx < output.size();
1072 ++layer) {
1073 ensure_cpu(static_cast<uint32_t>(layer));
1074 seqlock_read_void(m_slot_locks[layer], 8, [&] {
1075 const auto* vec = std::get_if<std::vector<T>>(&m_data[layer]);
1076 if (!vec || vec->empty())
1077 return;
1078 const size_t copy_n = std::min(layer_elems, std::min(vec->size(), output.size() - out_idx));
1079 std::copy_n(vec->begin(), static_cast<std::ptrdiff_t>(copy_n), output.begin() + static_cast<std::ptrdiff_t>(out_idx));
1080 out_idx += copy_n;
1081 });
1082 }
1083
1084 if (out_idx < output.size()) {
1085 std::fill(output.begin() + static_cast<std::ptrdiff_t>(out_idx), output.end(), T {});
1086 }
1087}
1088
1090 const std::vector<uint64_t>& coords,
1091 void* out,
1092 const std::type_info& type) const
1093{
1094 if (coords.empty() || m_data.empty())
1095 return;
1096
1097 const size_t layer = (m_data.size() > 1) ? static_cast<size_t>(coords[0]) : 0;
1098 if (layer >= m_data.size() || coords.size() < (m_data.size() > 1 ? 4U : 3U))
1099 return;
1100
1101 ensure_cpu(static_cast<uint32_t>(layer));
1102
1103 const size_t co = (m_data.size() > 1) ? 1 : 0;
1104 const size_t idx = (coords[co] * m_width + coords[co + 1]) * m_channels + coords[co + 2];
1105
1106 seqlock_read_void(m_slot_locks[layer], 8, [&] {
1107 std::visit([&](const auto& vec) {
1108 using T = typename std::decay_t<decltype(vec)>::value_type;
1109 if (type != typeid(T) || idx >= vec.size())
1110 return;
1111 *static_cast<T*>(out) = vec[idx];
1112 },
1113 m_data[layer]);
1114 });
1115}
1116
1118 const std::vector<uint64_t>& coords,
1119 const void* in,
1120 const std::type_info& type)
1121{
1122 if (coords.empty() || m_data.empty())
1123 return;
1124
1125 const size_t layer = (m_data.size() > 1) ? static_cast<size_t>(coords[0]) : 0;
1126 if (layer >= m_data.size() || coords.size() < (m_data.size() > 1 ? 4U : 3U))
1127 return;
1128
1129 ensure_cpu(static_cast<uint32_t>(layer));
1130
1131 const size_t co = (m_data.size() > 1) ? 1 : 0;
1132 const size_t idx = (coords[co] * m_width + coords[co + 1]) * m_channels + coords[co + 2];
1133
1135 std::visit([&](auto& vec) {
1136 using T = typename std::decay_t<decltype(vec)>::value_type;
1137 if (type != typeid(T) || idx >= vec.size())
1138 return;
1139 vec[idx] = *static_cast<const T*>(in);
1140 },
1141 m_data[layer]);
1142}
1143
1144} // namespace MayaFlux::Kakshya
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
IO::ImageData image
Definition Decoder.cpp:47
const std::vector< float > * pixels
Definition Decoder.cpp:48
bool format
uint32_t h
Definition InkPress.cpp:29
const uint32_t * src
glm::ivec3 max
std::vector< float > * out
glm::vec3 value
std::vector< glm::ivec3 > coords
Definition VDBWriter.cpp:25
vk::PhysicalDeviceType type
Definition VKDevice.cpp:126
std::string name
Definition VKDevice.cpp:123
uint32_t index
Definition VKDevice.cpp:122
size_t count
const uint8_t * ptr
uint32_t width
uint32_t height
Type-erased accessor for NDData with semantic view construction.
auto get_frame_typed_as(uint64_t frame_index) const -> std::span< const T >
std::span< const uint8_t > as_uint8(uint32_t layer=0) const
Typed view over the uint8 variant.
auto get_frame_typed(uint64_t frame_index) const -> DataSpanVariant
std::atomic< ProcessingState > m_processing_state
void register_state_change_callback(std::function< void(const std::shared_ptr< SignalSourceContainer > &, ProcessingState)> cb) override
Register a callback to be invoked on processing state changes.
TextureContainer(uint32_t width, uint32_t height, ImageFormat format, uint32_t layers=1)
Construct an empty container with declared dimensions.
std::vector< std::atomic< bool > > m_normalised_dirty
std::shared_ptr< Core::VKImage > to_image(uint32_t layer=0) const
Refresh the container-owned VKImage for one layer.
std::unordered_map< std::string, RegionGroup > m_region_groups
void set_pixels(std::span< const uint8_t > data, uint32_t layer=0)
Replace the layer's pixel buffer with a byte source.
void hold_layer(uint32_t layer)
Mark layer as held on the GPU after its image was rewritten.
void mark_ready_for_processing(bool ready) override
Mark the container as ready or not ready for processing.
std::vector< DataAccess > all_channel_data() override
Get all channel data as accessors.
std::shared_ptr< Core::VKImage > cached_array_image(const std::shared_ptr< Buffers::VKBuffer > &staging) const
std::vector< DataVariant > get_region_data(const Region &region) const override
Get data for a specific region.
void unregister_state_change_callback() override
Unregister the state change callback, if any.
ProcessingState get_processing_state() const override
Get the current processing state of the container.
void get_frames_typed_as(std::span< T > output, uint64_t start_frame, uint64_t num_frames) const
DataAccess channel_data(size_t channel_index) override
Get channel data with semantic interpretation.
std::shared_ptr< DataProcessingChain > get_processing_chain() override
Get the current processing chain for this container.
std::span< const float > as_normalised_float(uint32_t layer=0) const
Layer pixel data as a normalised float span.
void claim_cpu(uint32_t layer)
Give a GPU-held layer fresh CPU storage before a full overwrite.
uint64_t get_frame_size() const override
Get the number of elements that constitute one "frame".
std::vector< std::atomic< bool > > m_cpu_stale
Per layer: the GPU image in m_layer_image_cache holds the content, m_data does not.
uint32_t append_image_layer(std::shared_ptr< Core::VKImage > image)
Append one layer held on the GPU by image.
void update_processing_state(ProcessingState state) override
Update the processing state of the container.
void get_frames_impl(void *output, size_t count, uint64_t start_frame, uint64_t num_frames, const std::type_info &type) const override
Implementation-specific method to retrieve multiple frames.
uint64_t coordinates_to_linear_index(const std::vector< uint64_t > &coords) const override
Convert coordinates to linear index based on current memory layout.
void from_image_array(const std::shared_ptr< Core::VKImage > &image)
Download each array layer of a Vulkan 2D array image into the corresponding layer slot.
std::function< void(const std::shared_ptr< SignalSourceContainer > &, ProcessingState)> m_state_cb
std::shared_ptr< Core::VKImage > to_image_array() const
Upload all layers as a Vulkan 2D array texture.
std::vector< DataVariant > m_processed_data
std::vector< DataVariant > get_segments_data(const std::vector< RegionSegment > &segments) const override
Get data for multiple region segments efficiently.
uint64_t get_num_frames() const override
Get the number of frames in the primary (temporal) dimension.
std::unordered_map< std::string, RegionGroup > get_all_region_groups() const override
Get all region groups in the container.
std::shared_ptr< Core::VKImage > layer_image(uint32_t layer) const
The GPU image backing layer, without uploading CPU pixels.
void set_memory_layout(MemoryLayout layout) override
Set the memory layout for this container.
std::span< const uint8_t > pixel_bytes(uint32_t layer=0) const
Read-only byte-level view over the pixel buffer.
std::shared_ptr< DataProcessingChain > m_chain
std::vector< DataDimension > get_dimensions() const override
Get the dimensions describing the structure of the data.
uint64_t get_total_elements() const override
Get the total number of elements in the container.
bool upload_image(const std::shared_ptr< Core::VKImage > &image, uint32_t layer, const std::shared_ptr< Buffers::VKBuffer > &staging) const
Upload one layer into an existing VKImage instead of creating one.
void set_value_impl(const std::vector< uint64_t > &coords, const void *in, const std::type_info &type) override
Type-erased single-element write.
void get_frames_typed(void *output, size_t count, uint64_t start_frame, uint64_t num_frames, const std::type_info &type) const
const void * get_raw_data() const override
Get a raw pointer to the underlying data storage.
RegionGroup get_region_group(const std::string &name) const override
Get a region group by name.
std::shared_ptr< Core::VKImage > cached_layer_image(uint32_t layer, const std::shared_ptr< Buffers::VKBuffer > &staging) const
void get_value_impl(const std::vector< uint64_t > &coords, void *out, const std::type_info &type) const override
Type-erased single-element read.
std::vector< Portal::Graphics::ImageCacheEntry > m_layer_image_cache
bool is_ready_for_processing() const override
Check if the container is ready for processing.
std::span< const float > as_float(uint32_t layer=0) const
Typed view over the float variant.
void clear() override
Clear all data in the container.
std::span< const uint16_t > as_uint16(uint32_t layer=0) const
Typed view over the uint16 variant.
void set_region_data(const Region &region, const std::vector< DataVariant > &data) override
Write DataVariant bytes into the pixel buffer at the region bounds.
void add_region_group(const RegionGroup &group) override
Add a named group of regions to the container.
std::vector< uint64_t > linear_index_to_coordinates(uint64_t index) const override
Convert linear index to coordinates based on current memory layout.
const std::vector< DataVariant > & get_data() override
Get a reference to the raw data stored in the container.
std::vector< std::vector< float > > m_normalised_cache
void ensure_cpu(uint32_t layer) const
Download a GPU-held layer into m_data before a CPU read.
bool upload_image_array(const std::shared_ptr< Core::VKImage > &image, const std::shared_ptr< Buffers::VKBuffer > &staging) const
Upload all layers into an existing VKImage instead of creating one.
std::type_index value_element_type() const override
Runtime query for the native scalar element type of this container.
std::vector< DataVariant > & get_processed_data() override
Get a mutable reference to the processed data buffer.
size_t byte_size() const
Total byte count of the pixel buffer.
MemoryLayout get_memory_layout() const override
Get the memory layout used by this container.
void from_image(const std::shared_ptr< Core::VKImage > &image, uint32_t layer=0)
Download pixel data from a VKImage into this container.
Portal::Graphics::ImageCacheEntry m_array_image_cache
void remove_region_group(const std::string &name) override
Remove a region group by name.
bool has_data() const override
Check if the container currently holds any data.
SignalSourceContainer wrapping GPU texture data as addressable pixel bytes.
void resize(size_t n)
Replace the slot array with n default-constructed Seqlock instances.
Definition SeqLock.hpp:339
void extend(size_t n)
Add n default-constructed Seqlock instances after the existing slots.
Definition SeqLock.hpp:356
RAII guard that brackets a Seqlock write region.
Definition SeqLock.hpp:155
Scope guard for a serialized Seqlock write.
Definition SeqLock.hpp:184
void download_data(const std::shared_ptr< Core::VKImage > &image, void *data, size_t size, const std::shared_ptr< Buffers::VKBuffer > &staging, bool deferred=false, vk::ImageLayout restore_layout=vk::ImageLayout::eShaderReadOnlyOptimal, vk::PipelineStageFlags restore_stage=vk::PipelineStageFlagBits::eFragmentShader)
Download pixel data from a VKImage, reusing a caller-supplied persistent staging buffer.
std::shared_ptr< Core::VKImage > refresh_cached_image(ImageCacheEntry &entry, const ImageKey &key, std::span< const uint8_t > pixels, const std::shared_ptr< Buffers::VKBuffer > &staging={})
Refresh a sampled image in an owner-held cache entry.
void upload_data(const std::shared_ptr< Core::VKImage > &image, const void *data, size_t size)
Upload pixel data to an existing texture.
Portal-level texture creation and management.
@ ContainerProcessing
Container operations (Kakshya - file/stream/region processing)
@ Runtime
General runtime operations (default fallback)
@ Kakshya
Containers[Signalsource, Stream, File], Regions, DataProcessors.
ProcessingState
Represents the current processing lifecycle state of a container.
@ READY
Container has data loaded and is ready for processing.
@ IDLE
Container is inactive with no data or not ready for processing.
ImageFormat
User-friendly image format enum.
Definition ImageData.hpp:12
DataVariant make_empty_storage(ImageFormat format, size_t element_count)
Allocate a zeroed DataVariant of the alternative backing a format.
typename detail::span_const_from_vector_variant< DataVariant >::type DataSpanVariant
Definition NDData.hpp:625
std::span< const float > as_normalised_float(const DataVariant &variant, std::vector< float > &storage)
Extract a DataVariant holding pixel data as a normalised float span.
Definition DataUtils.cpp:61
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
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:70
@ IMAGE_COLOR_ARRAY
4D (idx + 2D + color)
@ IMAGE_COLOR
2D RGB/RGBA image
size_t storage_element_size(ImageFormat format)
Byte width of the DataVariant element backing a given ImageFormat.
MemoryLayout
Memory layout for multi-dimensional data.
Definition NDData.hpp:33
@ ROW_MAJOR
C/C++ style (last dimension varies fastest)
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.
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.
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
static ContainerDataStructure image_interleaved()
Create structure for interleaved image data.
static uint64_t get_total_elements(const std::vector< DataDimension > &dimensions)
Get total elements across all dimensions.
static std::vector< DataDimension > create_dimensions(DataModality modality, const std::vector< uint64_t > &shape, MemoryLayout layout=MemoryLayout::ROW_MAJOR)
Create dimension descriptors for a data modality.
Definition NDData.cpp:127
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
Exact layer count for subclasses, zero allowed.
Immutable properties that determine whether an image can be reused.