18using Portal::Graphics::TextureLoom;
24 enum class StorageKind : uint8_t { U8,
28 StorageKind storage_kind_for(ImageFormat format)
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;
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;
51 case ImageFormat::R32F:
52 case ImageFormat::RG32F:
53 case ImageFormat::RGBA32F:
54 case ImageFormat::DEPTH32F:
55 return StorageKind::F32;
58 return StorageKind::U8;
62 bool is_float_format(ImageFormat 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:
78 DataVariant make_empty_storage(ImageFormat format,
size_t element_count)
80 switch (storage_kind_for(format)) {
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);
88 return std::vector<uint8_t>(element_count, 0U);
93 std::pair<const uint8_t*, size_t> variant_bytes(
const DataVariant& v)
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>) {
102 reinterpret_cast<const uint8_t*
>(vec.data()),
103 vec.size() *
sizeof(
T)
106 return {
nullptr, 0 };
112 std::pair<uint8_t*, size_t> variant_bytes_mut(
DataVariant& v)
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>) {
121 reinterpret_cast<uint8_t*
>(vec.data()),
122 vec.size() *
sizeof(
T)
125 return {
nullptr, 0 };
131 double read_normalized_at(
const DataVariant& v, ImageFormat format,
size_t elem_index)
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())
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]);
153 void write_normalized_at(
DataVariant& v, ImageFormat format,
size_t elem_index,
double value)
156 [format, elem_index,
value](
auto& vec) {
157 using T =
typename std::decay_t<
decltype(vec)>::value_type;
158 if (elem_index >= vec.size())
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);
167 vec[elem_index] =
static_cast<uint16_t
>(
168 std::clamp(
value * 65535.0, 0.0, 65535.0));
170 }
else if constexpr (std::is_same_v<T, float>) {
171 vec[elem_index] =
static_cast<float>(
value);
187 , m_channels(TextureLoom::get_channel_count(format))
188 , m_bpp(TextureLoom::get_bytes_per_pixel(format))
190 m_chain = std::make_shared<DataProcessingChain>();
191 const size_t element_count =
static_cast<size_t>(m_width) * m_height * m_channels;
193 for (uint32_t i = 0; i < std::max(layers, 1U); ++i) {
194 m_data.emplace_back(make_empty_storage(m_format, element_count));
197 m_normalised_cache.resize(m_data.size());
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);
203 m_slot_locks.resize(m_data.size());
206 m_ready_for_processing.store(
true);
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);
213TextureContainer::TextureContainer(
const std::shared_ptr<Core::VKImage>&
image, ImageFormat format)
214 : TextureContainer(
image->get_width(),
image->get_height(), format)
216 from_image(
image, 0);
223void TextureContainer::setup_dimensions()
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());
230 m_structure = ContainerDataStructure::image_interleaved();
233 m_structure.dimensions = DataDimension::create_dimensions(
234 DataModality::IMAGE_COLOR_ARRAY, { n,
h, w, c }, MemoryLayout::ROW_MAJOR);
236 m_structure.dimensions = DataDimension::create_dimensions(
237 DataModality::IMAGE_COLOR, {
h, w, c }, MemoryLayout::ROW_MAJOR);
245void TextureContainer::from_image(
const std::shared_ptr<Core::VKImage>&
image, uint32_t layer)
248 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
249 "TextureContainer::from_image called with uninitialised image");
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());
259 const size_t sz = byte_size();
260 const size_t element_count =
static_cast<size_t>(m_width) * m_height * m_channels;
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);
271 TextureLoom::instance().download_data(
image,
ptr, sz,
nullptr);
274 m_normalised_dirty[layer].store(
true, std::memory_order_release);
275 update_processing_state(ProcessingState::READY);
278void TextureContainer::from_image(
279 const std::shared_ptr<Core::VKImage>&
image,
280 const std::shared_ptr<Buffers::VKBuffer>& staging,
284 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
285 "TextureContainer::from_image(staging) called with uninitialised image");
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());
295 const size_t sz = byte_size();
296 const size_t element_count =
static_cast<size_t>(m_width) * m_height * m_channels;
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);
307 TextureLoom::instance().download_data(
image,
ptr, sz, staging);
310 m_normalised_dirty[layer].store(
true, std::memory_order_release);
311 update_processing_state(ProcessingState::READY);
314void TextureContainer::from_image_array(
const std::shared_ptr<Core::VKImage>&
image)
317 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
318 "TextureContainer::from_image_array called with uninitialised image");
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);
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);
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) {
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);
342 m_normalised_dirty[i].store(
true, std::memory_order_release);
345 update_processing_state(ProcessingState::READY);
348void TextureContainer::from_image_array(
349 const std::shared_ptr<Core::VKImage>&
image,
350 const std::shared_ptr<Buffers::VKBuffer>& staging)
353 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
354 "TextureContainer::from_image_array(staging) called with uninitialised image");
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);
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);
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) {
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);
378 m_normalised_dirty[i].store(
true, std::memory_order_release);
381 update_processing_state(ProcessingState::READY);
384std::shared_ptr<Core::VKImage> TextureContainer::to_image(uint32_t layer)
const
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());
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)
397 img = TextureLoom::instance().create_2d(m_width, m_height, m_format,
ptr);
401 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
402 "TextureContainer::to_image: TextureLoom failed to create VKImage");
407std::shared_ptr<Core::VKImage> TextureContainer::to_image(
408 uint32_t layer,
const std::shared_ptr<Buffers::VKBuffer>& staging)
const
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());
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");
424 auto& loom = TextureLoom::instance();
425 img = loom.create_2d(m_width, m_height, m_format,
nullptr);
427 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
428 "TextureContainer::to_image(staging): VKImage allocation failed");
431 loom.upload_data(img,
ptr, bytes, staging);
436std::shared_ptr<Core::VKImage> TextureContainer::to_image_array()
const
438 const auto n =
static_cast<uint32_t
>(m_data.size());
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)
448 img = TextureLoom::instance().create_2d(m_width, m_height, m_format,
ptr);
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);
464 std::memcpy(combined.data() + i * layer_bytes,
ptr, layer_bytes);
470 return TextureLoom::instance().create_2d_array(m_width, m_height, n, m_format, combined.data());
473std::shared_ptr<Core::VKImage> TextureContainer::to_image_array(
474 const std::shared_ptr<Buffers::VKBuffer>& staging)
const
476 const auto n =
static_cast<uint32_t
>(m_data.size());
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)
486 auto& loom = TextureLoom::instance();
487 img = loom.create_2d(m_width, m_height, m_format,
nullptr);
489 loom.upload_data(img,
ptr, bytes, staging);
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);
504 std::memcpy(combined.data() + i * layer_bytes,
ptr, layer_bytes);
511 auto& loom = TextureLoom::instance();
512 auto img = loom.create_2d_array(m_width, m_height, n, m_format,
nullptr);
515 loom.upload_data(img, combined.data(), combined.size(), staging);
523std::span<const uint8_t> TextureContainer::pixel_bytes(uint32_t layer)
const
525 if (layer >= m_data.size())
527 auto [
ptr, bytes] = variant_bytes(m_data[layer]);
528 return ptr ? std::span<const uint8_t>(
ptr, bytes) : std::span<const uint8_t> {};
531std::span<uint8_t> TextureContainer::pixel_bytes(uint32_t layer)
533 if (layer >= m_data.size())
535 auto [
ptr, bytes] = variant_bytes_mut(m_data[layer]);
536 return ptr ? std::span<uint8_t>(
ptr, bytes) : std::span<uint8_t> {};
539std::span<const uint8_t> TextureContainer::as_uint8(uint32_t layer)
const
541 if (layer >= m_data.size())
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> {};
547std::span<const uint16_t> TextureContainer::as_uint16(uint32_t layer)
const
549 if (layer >= m_data.size())
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> {};
555std::span<const float> TextureContainer::as_float(uint32_t layer)
const
557 if (layer >= m_data.size())
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> {};
563void TextureContainer::set_pixels(std::span<const uint8_t> data, uint32_t layer)
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);
570 auto* buf = std::get_if<std::vector<uint8_t>>(&m_data[layer]);
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));
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());
585 std::ranges::copy(data, buf->begin());
586 m_normalised_dirty[layer].store(
true, std::memory_order_release);
589void TextureContainer::set_pixels(std::span<const uint16_t> data, uint32_t layer)
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);
596 auto* buf = std::get_if<std::vector<uint16_t>>(&m_data[layer]);
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));
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());
611 std::ranges::copy(data, buf->begin());
612 m_normalised_dirty[layer].store(
true, std::memory_order_release);
615void TextureContainer::set_pixels(std::span<const float> data, uint32_t layer)
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);
622 auto* buf = std::get_if<std::vector<float>>(&m_data[layer]);
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));
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());
637 std::ranges::copy(data, buf->begin());
638 m_normalised_dirty[layer].store(
true, std::memory_order_release);
641std::span<const float> TextureContainer::as_normalised_float(uint32_t layer)
const
643 if (layer >= m_data.size())
646 if (!m_normalised_dirty[layer].load(std::memory_order_acquire))
647 return { m_normalised_cache[layer] };
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]);
655 m_normalised_dirty[layer].
store(
false, std::memory_order_release);
664std::vector<DataDimension> TextureContainer::get_dimensions()
const
666 return m_structure.dimensions;
669uint64_t TextureContainer::get_total_elements()
const
671 return m_structure.get_total_elements();
676 return m_structure.memory_layout;
681 m_structure.memory_layout = layout;
684uint64_t TextureContainer::get_frame_size()
const
686 return static_cast<uint64_t
>(m_width) * m_channels;
689uint64_t TextureContainer::get_num_frames()
const
694std::vector<DataVariant> TextureContainer::get_region_data(
const Region& region)
const
703 if (layer >= m_data.size())
706 std::optional<std::vector<DataVariant>> result;
707 seqlock_read_void(m_slot_locks[layer], 8, [&] {
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()),
717 m_structure.dimensions);
725 return result.value_or(std::vector<DataVariant> {});
728std::vector<DataVariant> TextureContainer::get_segments_data(
729 const std::vector<RegionSegment>& )
const
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]);
741void TextureContainer::set_region_data(
742 const Region& region,
const std::vector<DataVariant>& data)
753 if (layer >= m_data.size())
756 const size_t coord_offset = (m_data.size() > 1) ? 1 : 0;
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));
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");
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];
791std::type_index TextureContainer::value_element_type()
const
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);
801 return typeid(uint8_t);
804uint64_t TextureContainer::coordinates_to_linear_index(
const std::vector<uint64_t>& coords)
const
809std::vector<uint64_t> TextureContainer::linear_index_to_coordinates(uint64_t index)
const
814void TextureContainer::clear()
816 const size_t element_count =
static_cast<size_t>(m_width) * m_height * m_channels;
818 for (
size_t i = 0; i < m_data.size(); ++i) {
820 m_data[i] = make_empty_storage(m_format, element_count);
823 update_processing_state(ProcessingState::IDLE);
832 return m_processing_state.load();
841 seqlock_read_void(m_cb_lock, 8, [&] {
843 m_state_cb(shared_from_this(), state);
847void TextureContainer::register_state_change_callback(
848 std::function<
void(
const std::shared_ptr<SignalSourceContainer>&,
ProcessingState)> cb)
851 m_state_cb = std::move(cb);
854void TextureContainer::unregister_state_change_callback()
857 m_state_cb =
nullptr;
860bool TextureContainer::is_ready_for_processing()
const
862 return m_ready_for_processing.load(std::memory_order_acquire);
865void TextureContainer::mark_ready_for_processing(
bool ready)
867 m_ready_for_processing.store(ready, std::memory_order_release);
870std::vector<DataVariant>& TextureContainer::get_processed_data()
872 return m_processed_data;
875const std::vector<DataVariant>& TextureContainer::get_processed_data()
const
877 return m_processed_data;
880const std::vector<DataVariant>& TextureContainer::get_data()
885DataAccess TextureContainer::channel_data(
size_t channel_index)
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 };
895 return { m_data[0], m_structure.dimensions, DataModality::IMAGE_COLOR };
898std::vector<DataAccess> TextureContainer::all_channel_data()
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));
910 m_region_groups[group.
name] = group;
913RegionGroup TextureContainer::get_region_group(
const std::string& name)
const
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;
921 return result.value_or(empty);
924std::unordered_map<std::string, RegionGroup> TextureContainer::get_all_region_groups()
const
926 std::optional<std::unordered_map<std::string, RegionGroup>> result;
927 seqlock_read_void(m_region_lock, 8, [&] {
928 result = m_region_groups;
930 return result.value_or(std::unordered_map<std::string, RegionGroup> {});
933void TextureContainer::remove_region_group(
const std::string& name)
936 m_region_groups.erase(name);
939const void* TextureContainer::get_raw_data()
const
941 if (m_data.empty()) {
945 auto [
ptr, bytes] = variant_bytes(m_data[0]);
946 return (
ptr && bytes > 0) ?
static_cast<const void*
>(
ptr) :
nullptr;
949bool TextureContainer::has_data()
const
951 if (m_data.empty()) {
955 auto [
ptr, bytes] = variant_bytes(m_data[0]);
956 return ptr && bytes > 0;
959std::shared_ptr<DataProcessingChain> TextureContainer::get_processing_chain()
962 m_chain = std::make_shared<DataProcessingChain>();
967void TextureContainer::get_frames_impl(
970 uint64_t start_frame,
972 const std::type_info& type)
const
974 get_frames_typed(
output,
count, start_frame, num_frames, type);
979 if (frame_index >= m_data.size()) {
980 return { std::span<const uint8_t> {} };
983 const size_t layer_elems =
static_cast<size_t>(m_width) * m_height * m_channels;
985 seqlock_read_void(m_slot_locks[frame_index], 8, [&] {
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());
993 return { std::span<const uint8_t> {} };
996 m_data[frame_index]);
1001void TextureContainer::get_frames_typed(
1004 uint64_t start_frame,
1005 uint64_t num_frames,
1006 const std::type_info& type)
const
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);
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);
1016 if (type ==
typeid(
float)) {
1017 get_frames_typed_as<float>(std::span<float>(
static_cast<float*
>(
output),
count), start_frame, num_frames);
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");
1028template <
typename T>
1029auto TextureContainer::get_frame_typed_as(uint64_t frame_index)
const -> std::span<const T>
1031 if (frame_index >= m_data.size())
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())
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);
1047template <
typename T>
1048void TextureContainer::get_frames_typed_as(std::span<T>
output, uint64_t start_frame, uint64_t num_frames)
const
1050 const size_t layer_elems =
static_cast<size_t>(m_width) * m_height * m_channels;
1053 for (uint64_t layer = start_frame;
1054 layer < start_frame + num_frames && layer < m_data.size() && out_idx <
output.size();
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())
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));
1066 if (out_idx <
output.size()) {
1067 std::fill(
output.begin() +
static_cast<std::ptrdiff_t
>(out_idx),
output.end(), T {});
1071void TextureContainer::get_value_impl(
1072 const std::vector<uint64_t>& coords,
1074 const std::type_info& type)
const
1076 if (coords.empty() || m_data.empty())
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))
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];
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())
1091 *
static_cast<T*
>(out) = vec[idx];
1097void TextureContainer::set_value_impl(
1098 const std::vector<uint64_t>& coords,
1100 const std::type_info& type)
1102 if (coords.empty() || m_data.empty())
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))
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];
1113 std::visit([&](
auto& vec) {
1114 using T =
typename std::decay_t<
decltype(vec)>::value_type;
1115 if (type !=
typeid(T) || idx >= vec.size())
1117 vec[idx] = *
static_cast<const T*
>(in);
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
std::shared_ptr< Core::VKImage > output
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.
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.
typename detail::span_const_from_vector_variant< DataVariant >::type DataSpanVariant
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.
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.
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.
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)
std::vector< uint64_t > start_coordinates
Starting frame index (inclusive)
Represents a point or span in N-dimensional space.