13using Portal::Graphics::TextureLoom;
19 enum class StorageKind : uint8_t { U8,
23 StorageKind storage_kind_for(ImageFormat format)
27 case ImageFormat::RG8:
28 case ImageFormat::RGB8:
29 case ImageFormat::RGBA8:
30 case ImageFormat::RGBA8_SRGB:
31 case ImageFormat::BGRA8:
32 case ImageFormat::BGRA8_SRGB:
33 case ImageFormat::DEPTH24:
34 case ImageFormat::DEPTH24_STENCIL8:
35 return StorageKind::U8;
37 case ImageFormat::R16:
38 case ImageFormat::RG16:
39 case ImageFormat::RGBA16:
40 case ImageFormat::R16F:
41 case ImageFormat::RG16F:
42 case ImageFormat::RGBA16F:
43 case ImageFormat::DEPTH16:
44 return StorageKind::U16;
46 case ImageFormat::R32F:
47 case ImageFormat::RG32F:
48 case ImageFormat::RGBA32F:
49 case ImageFormat::DEPTH32F:
50 return StorageKind::F32;
53 return StorageKind::U8;
57 bool is_float_format(ImageFormat format)
60 case ImageFormat::R16F:
61 case ImageFormat::RG16F:
62 case ImageFormat::RGBA16F:
63 case ImageFormat::R32F:
64 case ImageFormat::RG32F:
65 case ImageFormat::RGBA32F:
66 case ImageFormat::DEPTH32F:
73 DataVariant make_empty_storage(ImageFormat format,
size_t element_count)
75 switch (storage_kind_for(format)) {
77 return std::vector<uint8_t>(element_count, 0U);
78 case StorageKind::U16:
79 return std::vector<uint16_t>(element_count, 0U);
80 case StorageKind::F32:
81 return std::vector<float>(element_count, 0.0F);
83 return std::vector<uint8_t>(element_count, 0U);
88 std::pair<const uint8_t*, size_t> variant_bytes(
const DataVariant& v)
91 [](
const auto& vec) -> std::pair<const uint8_t*, size_t> {
92 using T =
typename std::decay_t<
decltype(vec)>::value_type;
93 if constexpr (std::is_same_v<T, uint8_t>
94 || std::is_same_v<T, uint16_t>
95 || std::is_same_v<T, float>) {
97 reinterpret_cast<const uint8_t*
>(vec.data()),
98 vec.size() *
sizeof(
T)
101 return {
nullptr, 0 };
107 std::pair<uint8_t*, size_t> variant_bytes_mut(
DataVariant& v)
110 [](
auto& vec) -> std::pair<uint8_t*, size_t> {
111 using T =
typename std::decay_t<
decltype(vec)>::value_type;
112 if constexpr (std::is_same_v<T, uint8_t>
113 || std::is_same_v<T, uint16_t>
114 || std::is_same_v<T, float>) {
116 reinterpret_cast<uint8_t*
>(vec.data()),
117 vec.size() *
sizeof(
T)
120 return {
nullptr, 0 };
126 double read_normalized_at(
const DataVariant& v, ImageFormat format,
size_t elem_index)
129 [format, elem_index](
const auto& vec) ->
double {
130 using T =
typename std::decay_t<
decltype(vec)>::value_type;
131 if (elem_index >= vec.size())
133 if constexpr (std::is_same_v<T, uint8_t>) {
134 return static_cast<double>(vec[elem_index]) / 255.0;
135 }
else if constexpr (std::is_same_v<T, uint16_t>) {
136 return is_float_format(format)
137 ?
static_cast<double>(vec[elem_index])
138 : static_cast<double>(vec[elem_index]) / 65535.0;
139 }
else if constexpr (std::is_same_v<T, float>) {
140 return static_cast<double>(vec[elem_index]);
148 void write_normalized_at(
DataVariant& v, ImageFormat format,
size_t elem_index,
double value)
151 [format, elem_index, value](
auto& vec) {
152 using T =
typename std::decay_t<
decltype(vec)>::value_type;
153 if (elem_index >= vec.size())
155 if constexpr (std::is_same_v<T, uint8_t>) {
156 vec[elem_index] =
static_cast<uint8_t
>(
157 std::clamp(value * 255.0, 0.0, 255.0));
158 }
else if constexpr (std::is_same_v<T, uint16_t>) {
159 if (is_float_format(format)) {
160 vec[elem_index] =
static_cast<uint16_t
>(value);
162 vec[elem_index] =
static_cast<uint16_t
>(
163 std::clamp(value * 65535.0, 0.0, 65535.0));
165 }
else if constexpr (std::is_same_v<T, float>) {
166 vec[elem_index] =
static_cast<float>(value);
182 , m_channels(TextureLoom::get_channel_count(
format))
183 , m_bpp(TextureLoom::get_bytes_per_pixel(
format))
185 const size_t element_count =
static_cast<size_t>(m_width) * m_height * m_channels;
187 for (uint32_t i = 0; i < std::max(layers, 1U); ++i) {
188 m_data.emplace_back(make_empty_storage(m_format, element_count));
189 m_processed_data.emplace_back(make_empty_storage(m_format, element_count));
194 m_ready_for_processing.store(
true);
196 MF_INFO(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
197 "TextureContainer created: {}x{} layers={} fmt={} bpp={}",
198 m_width, m_height, m_data.size(),
static_cast<int>(m_format), m_bpp);
201TextureContainer::TextureContainer(
const std::shared_ptr<Core::VKImage>&
image, ImageFormat format)
204 from_image(
image, 0);
211void TextureContainer::setup_dimensions()
213 const uint64_t
h = m_height;
214 const uint64_t w = m_width;
215 const uint64_t c = m_channels;
216 const auto n =
static_cast<uint64_t
>(m_data.size());
218 m_structure = ContainerDataStructure::image_interleaved();
221 m_structure.dimensions = DataDimension::create_dimensions(
222 DataModality::IMAGE_COLOR, { n,
h, w, c }, MemoryLayout::ROW_MAJOR);
224 m_structure.dimensions = DataDimension::create_dimensions(
225 DataModality::IMAGE_COLOR, {
h, w, c }, MemoryLayout::ROW_MAJOR);
233void TextureContainer::from_image(
const std::shared_ptr<Core::VKImage>&
image, uint32_t layer)
236 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
237 "TextureContainer::from_image called with uninitialised image");
241 if (layer >= m_data.size()) {
242 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
243 "TextureContainer::from_image layer {} out of range ({})", layer, m_data.size());
247 const size_t sz = byte_size();
249 const size_t element_count =
static_cast<size_t>(m_width) * m_height * m_channels;
250 m_data[layer] = make_empty_storage(m_format, element_count);
252 auto [ptr, bytes] = variant_bytes_mut(m_data[layer]);
253 if (!ptr || bytes != sz) {
254 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
255 "TextureContainer::from_image variant size mismatch ({} vs {})", bytes, sz);
259 TextureLoom::instance().download_data(
image, ptr, sz);
262 std::unique_lock lock(m_data_mutex);
263 m_processed_data[layer] = m_data[layer];
266 update_processing_state(ProcessingState::READY);
268 MF_DEBUG(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
269 "TextureContainer: downloaded {} bytes from VKImage", sz);
272std::shared_ptr<Core::VKImage> TextureContainer::to_image(uint32_t layer)
const
274 std::shared_lock lock(m_data_mutex);
276 if (layer >= m_data.size()) {
277 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
278 "TextureContainer::to_image layer {} out of range ({})", layer, m_data.size());
282 auto [ptr, bytes] = variant_bytes(m_data[layer]);
283 if (!ptr || bytes == 0) {
284 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
285 "TextureContainer::to_image called on empty/invalid buffer");
289 auto img = TextureLoom::instance().create_2d(m_width, m_height, m_format, ptr);
291 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
292 "TextureContainer::to_image: TextureLoom failed to create VKImage");
301std::span<const uint8_t> TextureContainer::pixel_bytes(uint32_t layer)
const
303 if (layer >= m_data.size())
305 auto [ptr, bytes] = variant_bytes(m_data[layer]);
306 return ptr ? std::span<const uint8_t>(ptr, bytes) : std::span<const uint8_t> {};
309std::span<uint8_t> TextureContainer::pixel_bytes(uint32_t layer)
311 if (layer >= m_data.size())
313 auto [ptr, bytes] = variant_bytes_mut(m_data[layer]);
314 return ptr ? std::span<uint8_t>(ptr, bytes) : std::span<uint8_t> {};
317std::span<const uint8_t> TextureContainer::as_uint8(uint32_t layer)
const
319 if (layer >= m_data.size())
321 const auto* v = std::get_if<std::vector<uint8_t>>(&m_data[layer]);
322 return v ? std::span<const uint8_t>(v->data(), v->size()) : std::span<const uint8_t> {};
325std::span<const uint16_t> TextureContainer::as_uint16(uint32_t layer)
const
327 if (layer >= m_data.size())
329 const auto* v = std::get_if<std::vector<uint16_t>>(&m_data[layer]);
330 return v ? std::span<const uint16_t>(v->data(), v->size()) : std::span<const uint16_t> {};
333std::span<const float> TextureContainer::as_float(uint32_t layer)
const
335 if (layer >= m_data.size())
337 const auto* v = std::get_if<std::vector<float>>(&m_data[layer]);
338 return v ? std::span<const float>(v->data(), v->size()) : std::span<const float> {};
341void TextureContainer::set_pixels(std::span<const uint8_t> data, uint32_t layer)
343 if (layer >= m_data.size()) {
344 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
345 "TextureContainer::set_pixels(u8) layer {} out of range", layer);
348 auto* buf = std::get_if<std::vector<uint8_t>>(&m_data[layer]);
350 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
351 "TextureContainer::set_pixels(u8) called on non-uint8 format {}",
352 static_cast<int>(m_format));
355 if (data.size() != buf->size()) {
356 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
357 "TextureContainer::set_pixels(u8) size mismatch: got {} expected {}",
358 data.size(), buf->size());
361 std::unique_lock lock(m_data_mutex);
362 std::ranges::copy(data, buf->begin());
363 m_processed_data[layer] = m_data[layer];
366void TextureContainer::set_pixels(std::span<const uint16_t> data, uint32_t layer)
368 if (layer >= m_data.size()) {
369 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
370 "TextureContainer::set_pixels(u16) layer {} out of range", layer);
373 auto* buf = std::get_if<std::vector<uint16_t>>(&m_data[layer]);
375 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
376 "TextureContainer::set_pixels(u16) called on non-uint16 format {}",
377 static_cast<int>(m_format));
380 if (data.size() != buf->size()) {
381 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
382 "TextureContainer::set_pixels(u16) size mismatch: got {} expected {}",
383 data.size(), buf->size());
386 std::unique_lock lock(m_data_mutex);
387 std::ranges::copy(data, buf->begin());
388 m_processed_data[layer] = m_data[layer];
391void TextureContainer::set_pixels(std::span<const float> data, uint32_t layer)
393 if (layer >= m_data.size()) {
394 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
395 "TextureContainer::set_pixels(f32) layer {} out of range", layer);
398 auto* buf = std::get_if<std::vector<float>>(&m_data[layer]);
400 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
401 "TextureContainer::set_pixels(f32) called on non-float format {}",
402 static_cast<int>(m_format));
405 if (data.size() != buf->size()) {
406 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
407 "TextureContainer::set_pixels(f32) size mismatch: got {} expected {}",
408 data.size(), buf->size());
411 std::unique_lock lock(m_data_mutex);
412 std::ranges::copy(data, buf->begin());
413 m_processed_data[layer] = m_data[layer];
420std::vector<DataDimension> TextureContainer::get_dimensions()
const
422 return m_structure.dimensions;
425uint64_t TextureContainer::get_total_elements()
const
427 return m_structure.get_total_elements();
432 return m_structure.memory_layout;
437 m_structure.memory_layout = layout;
440uint64_t TextureContainer::get_frame_size()
const
442 return static_cast<uint64_t
>(m_width) * m_channels;
445uint64_t TextureContainer::get_num_frames()
const
450std::span<const double> TextureContainer::get_frame(uint64_t frame_index)
const
452 if (frame_index >= m_height)
455 std::shared_lock lock(m_data_mutex);
459 const size_t row_elems =
static_cast<size_t>(m_width) * m_channels;
460 const size_t offset =
static_cast<size_t>(frame_index) * row_elems;
462 m_frame_cache.resize(row_elems);
463 for (
size_t i = 0; i < row_elems; ++i)
464 m_frame_cache[i] = read_normalized_at(m_data[0], m_format, offset + i);
466 return { m_frame_cache.data(), m_frame_cache.size() };
469void TextureContainer::get_frames(
470 std::span<double> output, uint64_t start_frame, uint64_t num_frames)
const
472 std::shared_lock lock(m_data_mutex);
476 const size_t row_elems =
static_cast<size_t>(m_width) * m_channels;
479 for (uint64_t r = start_frame; r < start_frame + num_frames && r < m_height; ++r) {
480 const size_t offset =
static_cast<size_t>(r) * row_elems;
481 for (
size_t i = 0; i < row_elems && out_idx < output.size(); ++i, ++out_idx)
482 output[out_idx] = read_normalized_at(m_data[0], m_format, offset + i);
486std::vector<DataVariant> TextureContainer::get_region_data(
const Region& region)
const
488 std::shared_lock lock(m_data_mutex);
493 [&](
const auto& vec) -> std::vector<DataVariant> {
494 using T =
typename std::decay_t<
decltype(vec)>::value_type;
495 if constexpr (std::is_same_v<T, uint8_t>
496 || std::is_same_v<T, uint16_t>
497 || std::is_same_v<T, float>) {
498 auto extracted = extract_region_data<T>(
499 std::span<const T>(vec.data(), vec.size()),
501 m_structure.dimensions);
510std::vector<DataVariant> TextureContainer::get_segments_data(
511 const std::vector<RegionSegment>& )
const
513 std::shared_lock lock(m_data_mutex);
514 return m_processed_data;
517void TextureContainer::set_region_data(
518 const Region& region,
const std::vector<DataVariant>& data)
527 const uint64_t y1 = std::min(region.
end_coordinates[0],
static_cast<uint64_t
>(m_height - 1));
528 const uint64_t x1 = std::min(region.
end_coordinates[1],
static_cast<uint64_t
>(m_width - 1));
530 std::unique_lock lock(m_data_mutex);
534 using T =
typename std::decay_t<
decltype(dst_vec)>::value_type;
535 if constexpr (std::is_same_v<T, uint8_t>
536 || std::is_same_v<T, uint16_t>
537 || std::is_same_v<T, float>) {
538 const auto* src = std::get_if<std::vector<T>>(&data[0]);
539 if (!src || src->empty()) {
540 MF_ERROR(Journal::Component::Kakshya, Journal::Context::ContainerProcessing,
541 "TextureContainer::set_region_data source variant does not match "
542 "container element type");
547 for (uint64_t y = y0; y <= y1 && src_idx < src->size(); ++y) {
548 for (uint64_t x = x0; x <= x1 && src_idx < src->size(); ++x) {
549 const size_t dst_idx = (y * m_width + x) * m_channels;
550 for (uint32_t c = 0; c < m_channels && src_idx < src->size(); ++c, ++src_idx) {
551 if (dst_idx + c < dst_vec.size())
552 dst_vec[dst_idx + c] = (*src)[src_idx];
560 m_processed_data[0] = m_data[0];
563double TextureContainer::get_value_at(
const std::vector<uint64_t>& coordinates)
const
565 if (coordinates.size() < 3 || m_data.empty())
568 const size_t elem_idx = (coordinates[0] * m_width + coordinates[1]) * m_channels
571 std::shared_lock lock(m_data_mutex);
572 return read_normalized_at(m_data[0], m_format, elem_idx);
575void TextureContainer::set_value_at(
const std::vector<uint64_t>& coordinates,
double value)
577 if (coordinates.size() < 3 || m_data.empty())
580 const size_t elem_idx = (coordinates[0] * m_width + coordinates[1]) * m_channels
583 std::unique_lock lock(m_data_mutex);
584 write_normalized_at(m_data[0], m_format, elem_idx, value);
587uint64_t TextureContainer::coordinates_to_linear_index(
const std::vector<uint64_t>& coords)
const
592std::vector<uint64_t> TextureContainer::linear_index_to_coordinates(uint64_t index)
const
597void TextureContainer::clear()
599 std::unique_lock lock(m_data_mutex);
600 const size_t element_count =
static_cast<size_t>(m_width) * m_height * m_channels;
602 for (
size_t i = 0; i < m_data.size(); ++i) {
603 m_data[i] = make_empty_storage(m_format, element_count);
604 m_processed_data[i] = make_empty_storage(m_format, element_count);
607 update_processing_state(ProcessingState::IDLE);
616 return m_processing_state.load();
625 std::lock_guard lock(m_state_mutex);
627 m_state_cb(shared_from_this(), state);
630void TextureContainer::register_state_change_callback(
631 std::function<
void(
const std::shared_ptr<SignalSourceContainer>&,
ProcessingState)> cb)
633 std::lock_guard lock(m_state_mutex);
634 m_state_cb = std::move(cb);
637void TextureContainer::unregister_state_change_callback()
639 std::lock_guard lock(m_state_mutex);
640 m_state_cb =
nullptr;
643bool TextureContainer::is_ready_for_processing()
const
645 return m_ready_for_processing.load(std::memory_order_acquire);
648void TextureContainer::mark_ready_for_processing(
bool ready)
650 m_ready_for_processing.store(ready, std::memory_order_release);
653std::vector<DataVariant>& TextureContainer::get_processed_data()
655 return m_processed_data;
658const std::vector<DataVariant>& TextureContainer::get_processed_data()
const
660 return m_processed_data;
663const std::vector<DataVariant>& TextureContainer::get_data()
668DataAccess TextureContainer::channel_data(
size_t channel_index)
672 if (m_data.empty()) {
673 static DataVariant empty = std::vector<uint8_t> {};
674 static std::vector<DataDimension> empty_dims;
675 return { empty, empty_dims, DataModality::IMAGE_COLOR };
678 return { m_data[0], m_structure.dimensions, DataModality::IMAGE_COLOR };
681std::vector<DataAccess> TextureContainer::all_channel_data()
683 std::vector<DataAccess> result;
684 result.reserve(m_channels);
685 for (
size_t c = 0; c < m_channels; ++c)
686 result.push_back(channel_data(c));
692 std::lock_guard lock(m_state_mutex);
693 m_region_groups[group.
name] = group;
696const RegionGroup& TextureContainer::get_region_group(
const std::string& name)
const
699 std::shared_lock lock(m_data_mutex);
700 auto it = m_region_groups.find(name);
701 return it != m_region_groups.end() ? it->second : empty;
704std::unordered_map<std::string, RegionGroup> TextureContainer::get_all_region_groups()
const
706 std::shared_lock lock(m_data_mutex);
707 return m_region_groups;
710void TextureContainer::remove_region_group(
const std::string& name)
712 std::lock_guard lock(m_state_mutex);
713 m_region_groups.erase(name);
716void TextureContainer::lock() { m_data_mutex.lock(); }
717void TextureContainer::unlock() { m_data_mutex.unlock(); }
718bool TextureContainer::try_lock() {
return m_data_mutex.try_lock(); }
720const void* TextureContainer::get_raw_data()
const
722 if (m_data.empty()) {
726 auto [ptr, bytes] = variant_bytes(m_data[0]);
727 return (ptr && bytes > 0) ?
static_cast<const void*
>(ptr) :
nullptr;
730bool TextureContainer::has_data()
const
732 if (m_data.empty()) {
736 auto [ptr, bytes] = variant_bytes(m_data[0]);
737 return ptr && bytes > 0;
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
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.
std::string format(format_string< std::remove_cvref_t< Args >... > fmt_str, Args &&... args)
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.
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::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.