MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
TextureContainer.hpp
Go to the documentation of this file.
1#pragma once
2
5
7
8namespace MayaFlux::Core {
9class VKImage;
10}
11
12namespace MayaFlux::Buffers {
13class VKBuffer;
14}
15
16namespace MayaFlux::Kakshya {
17
18/**
19 * @class TextureContainer
20 * @brief SignalSourceContainer wrapping GPU texture data as addressable pixel bytes.
21 *
22 * TextureContainer is the Kakshya-layer representation of a 2D texture.
23 * It owns a flat interleaved RGBA/float pixel buffer matching the declared
24 * ImageFormat, described with IMAGE_COLOR or IMAGE_2D dimensions.
25 *
26 * The container is the data carrier for Yantra::Texture workflow pipelines.
27 * GPU materialization is intentionally not automatic: callers drive upload
28 * and download explicitly via from_image() and to_image(), keeping the
29 * container decoupled from any specific VKImage lifetime.
30 *
31 * Dimension convention (IMAGE_COLOR, INTERLEAVED):
32 * dims[0] -> SPATIAL_Y (height)
33 * dims[1] -> SPATIAL_X (width)
34 * dims[2] -> CHANNEL (bytes per pixel for format)
35 *
36 * Processing model:
37 * No default processor is created. The container holds bytes passively.
38 * Workflow operations receive and return TextureContainer instances;
39 * TextureExecutionContext dispatches compute shaders against them.
40 */
41class MAYAFLUX_API TextureContainer : public SignalSourceContainer {
42public:
43 /**
44 * @brief Construct an empty container with declared dimensions.
45 * @param width Texture width in pixels.
46 * @param height Texture height in pixels.
47 * @param format Pixel format; determines bytes per pixel.
48 * @param layers Number of array layers (default 1). set when using with array textures.
49 *
50 * Allocates a zeroed pixel buffer. No GPU resource is created.
51 */
52 TextureContainer(uint32_t width, uint32_t height, Portal::Graphics::ImageFormat format, uint32_t layers = 1);
53
54 /**
55 * @brief Construct from an existing VKImage, downloading its pixel data.
56 * @param image Source GPU texture. Must be initialized.
57 * @param format Pixel format of the image.
58 *
59 * Performs a blocking GPU->CPU download via TextureLoom.
60 * Equivalent to constructing empty then calling from_image().
61 */
63 const std::shared_ptr<Core::VKImage>& image,
65
66 ~TextureContainer() override = default;
67
72
73 [[nodiscard]] uint32_t get_layer_count() const { return static_cast<uint32_t>(m_data.size()); }
74
75 //=========================================================================
76 // GPU bridge
77 //=========================================================================
78
79 /**
80 * @brief Download pixel data from a VKImage into this container.
81 * @param image Source GPU texture. Must be initialized and match dimensions.
82 * @param layer Array layer index for array textures (default 0).
83 *
84 * Blocking. Overwrites the existing pixel buffer. Does not change
85 * declared width, height, or format — caller must ensure consistency.
86 */
87 void from_image(const std::shared_ptr<Core::VKImage>& image, uint32_t layer = 0);
88
89 /**
90 * @brief Download pixel data from a VKImage, reusing a caller-supplied
91 * persistent staging buffer.
92 *
93 * Identical to from_image() but passes @p staging through to
94 * TextureLoom::download_data, eliminating the per-call Vulkan object
95 * allocation. Use Buffers::create_image_staging_buffer(byte_size())
96 * to allocate the staging buffer once before the render loop.
97 *
98 * @param image Source GPU texture. Must be initialised and match dimensions.
99 * @param staging Host-visible staging VKBuffer sized to at least byte_size().
100 * @param layer Array layer index (default 0).
101 */
102 void from_image(
103 const std::shared_ptr<Core::VKImage>& image,
104 const std::shared_ptr<Buffers::VKBuffer>& staging,
105 uint32_t layer = 0);
106
107 /**
108 * @brief Download each array layer of a Vulkan 2D array image into
109 * the corresponding layer slot.
110 *
111 * Expects @p image to have array_layers == get_layer_count(). Downloads
112 * each layer via a separate blocking TextureLoom::download_data call.
113 * Resizes m_data to match if necessary.
114 *
115 * @param image Source VKImage with array_layers >= get_layer_count().
116 */
117 void from_image_array(const std::shared_ptr<Core::VKImage>& image);
118
119 /**
120 * @brief Download all array layers from a VKImage, reusing a caller-supplied
121 * persistent staging buffer.
122 *
123 * Identical to from_image_array() but passes @p staging through to
124 * TextureLoom::download_data. The staging buffer must be at least
125 * byte_size() * get_layer_count() bytes.
126 * Use Buffers::create_image_staging_buffer(byte_size() * get_layer_count())
127 * to allocate it once before the render loop.
128 *
129 * @param image Source VKImage with array_layers >= get_layer_count().
130 * @param staging Host-visible staging VKBuffer sized to at least
131 * byte_size() * get_layer_count().
132 */
133 void from_image_array(
134 const std::shared_ptr<Core::VKImage>& image,
135 const std::shared_ptr<Buffers::VKBuffer>& staging);
136
137 /**
138 * @brief Upload the pixel buffer to a new VKImage via TextureLoom.
139 * @param layer Array layer index for array textures (default 0).
140 * @return Newly created and uploaded VKImage. Null on failure.
141 *
142 * Creates a fresh 2D texture each call. Does not cache the result.
143 * The returned image is owned by TextureLoom's internal registry.
144 */
145 [[nodiscard]] std::shared_ptr<Core::VKImage> to_image(uint32_t layer = 0) const;
146
147 /**
148 * @brief Upload one layer to a new VKImage, reusing a caller-supplied staging buffer.
149 *
150 * Allocates the VKImage without pixel data (create_2d with nullptr), then
151 * uploads via the provided staging buffer, bypassing the per-call VkBuffer
152 * allocation inside TextureLoom. Use TextureLoom::create_streaming_staging()
153 * to allocate the staging buffer once before the render loop.
154 *
155 * @param layer Array layer index (default 0).
156 * @param staging Host-visible staging VKBuffer sized to at least byte_size().
157 * @return Newly created VKImage, or nullptr on failure.
158 */
159 [[nodiscard]] std::shared_ptr<Core::VKImage> to_image(
160 uint32_t layer,
161 const std::shared_ptr<Buffers::VKBuffer>& staging) const;
162
163 /**
164 * @brief Upload all layers as a Vulkan 2D array texture.
165 *
166 * Concatenates pixel data from all layers in order (layer 0 first) and
167 * calls TextureLoom::create_2d_array. The returned VKImage has
168 * array_layers == get_layer_count() and an image view of type
169 * VK_IMAGE_VIEW_TYPE_2D_ARRAY, making it bindable as sampler2DArray
170 * in GLSL.
171 *
172 * All layers must have been populated before calling this. Empty layers
173 * contribute zero bytes and will produce incorrect GPU data.
174 *
175 * @return Initialised VKImage with array_layers > 1, or nullptr on failure.
176 */
177 [[nodiscard]] std::shared_ptr<Core::VKImage> to_image_array() const;
178
179 /**
180 * @brief Upload all layers as a Vulkan 2D array texture, reusing a staging buffer.
181 *
182 * Concatenates pixel data from all layers in order (layer 0 first) and
183 * calls TextureLoom::create_2d_array. The returned VKImage has
184 * array_layers == get_layer_count() and an image view of type
185 * VK_IMAGE_VIEW_TYPE_2D_ARRAY, making it bindable as sampler2DArray
186 * in GLSL.
187 *
188 * All layers must have been populated before calling this. Empty layers
189 * contribute zero bytes and will produce incorrect GPU data.
190 *
191 * @param staging Host-visible staging VKBuffer sized to at least byte_size().
192 * @return Initialised VKImage with array_layers > 1, or nullptr on failure.
193 */
194 [[nodiscard]] std::shared_ptr<Core::VKImage> to_image_array(
195 const std::shared_ptr<Buffers::VKBuffer>& staging) const;
196
197 //=========================================================================
198 // Pixel access
199 //=========================================================================
200
201 /**
202 * @brief Read-only byte-level view over the pixel buffer.
203 *
204 * The returned span covers the raw byte footprint of the layer,
205 * regardless of the underlying variant type. For uint8 formats this
206 * is the natural view; for uint16 and float formats the caller can
207 * reinterpret_cast for typed access, or prefer the as_uint16 /
208 * as_float accessors below.
209 *
210 * @param layer Array layer index for array textures (default 0).
211 */
212 [[nodiscard]] std::span<const uint8_t> pixel_bytes(uint32_t layer = 0) const;
213
214 /**
215 * @brief Read-write byte-level view over the pixel buffer.
216 * @param layer Array layer index for array textures (default 0).
217 */
218 [[nodiscard]] std::span<uint8_t> pixel_bytes(uint32_t layer = 0);
219
220 /**
221 * @brief Typed view over the uint8 variant.
222 * Returns an empty span if the layer's variant is not uint8.
223 */
224 [[nodiscard]] std::span<const uint8_t> as_uint8(uint32_t layer = 0) const;
225
226 /**
227 * @brief Typed view over the uint16 variant.
228 * Returns an empty span if the layer's variant is not uint16.
229 * 16-bit UNORM formats and half-float formats both reside here;
230 * for half-float, the uint16 bits are the IEEE-754 binary16
231 * encoding.
232 */
233 [[nodiscard]] std::span<const uint16_t> as_uint16(uint32_t layer = 0) const;
234
235 /**
236 * @brief Typed view over the float variant.
237 * Returns an empty span if the layer's variant is not float.
238 */
239 [[nodiscard]] std::span<const float> as_float(uint32_t layer = 0) const;
240
241 /**
242 * @brief Replace the layer's pixel buffer with a byte source.
243 * Valid only when the declared format is uint8-backed.
244 * Size must equal width * height * bytes_per_pixel.
245 */
246 void set_pixels(std::span<const uint8_t> data, uint32_t layer = 0);
247
248 /**
249 * @brief Replace the layer's pixel buffer with a uint16 source.
250 * Valid when the declared format is uint16-backed
251 * (R16/RG16/RGBA16, R16F/RG16F/RGBA16F).
252 * Size must equal width * height * channels.
253 */
254 void set_pixels(std::span<const uint16_t> data, uint32_t layer = 0);
255
256 /**
257 * @brief Replace the layer's pixel buffer with a float source.
258 * Valid when the declared format is float-backed
259 * (R32F/RG32F/RGBA32F).
260 * Size must equal width * height * channels.
261 */
262 void set_pixels(std::span<const float> data, uint32_t layer = 0);
263
264 /**
265 * @brief Layer pixel data as a normalised float span.
266 *
267 * uint8_t source is divided by 255.0f. uint16_t by 65535.0f.
268 * float source is zero-copy into the variant's own storage.
269 * Returns empty span if layer is out of range or the variant
270 * holds a non-pixel type.
271 *
272 * Result is cached per layer and reused until set_pixels()
273 * or from_image() invalidates it for that layer.
274 *
275 * @param layer Array layer index. Defaults to 0.
276 * @return Normalised float span, w * h * channels elements.
277 */
278 [[nodiscard]] std::span<const float> as_normalised_float(uint32_t layer = 0) const;
279
280 //=========================================================================
281 // Metadata
282 //=========================================================================
283
284 [[nodiscard]] uint32_t get_width() const { return m_width; }
285 [[nodiscard]] uint32_t get_height() const { return m_height; }
286 [[nodiscard]] Portal::Graphics::ImageFormat get_format() const { return m_format; }
287 [[nodiscard]] uint32_t get_channel_count() const { return m_channels; }
288
289 /** @brief Byte count of one complete pixel row. */
290 [[nodiscard]] size_t row_stride() const { return static_cast<size_t>(m_width) * m_bpp; }
291
292 /** @brief Total byte count of the pixel buffer. */
293 [[nodiscard]] size_t byte_size() const { return static_cast<size_t>(m_width) * m_height * m_bpp; }
294
295 //=========================================================================
296 // NDDimensionalContainer
297 //=========================================================================
298
299 [[nodiscard]] std::vector<DataDimension> get_dimensions() const override;
300 [[nodiscard]] uint64_t get_total_elements() const override;
301 [[nodiscard]] MemoryLayout get_memory_layout() const override;
302 void set_memory_layout(MemoryLayout layout) override;
303 [[nodiscard]] uint64_t get_frame_size() const override;
304 [[nodiscard]] uint64_t get_num_frames() const override;
305
306 [[nodiscard]] std::vector<DataVariant> get_region_data(const Region& region) const override;
307 [[nodiscard]] std::vector<DataVariant> get_segments_data(
308 const std::vector<RegionSegment>& segments) const override;
309 [[nodiscard]] std::vector<DataVariant> get_region_group_data(const RegionGroup&) const override { return m_processed_data; }
310
311 [[nodiscard]] std::type_index value_element_type() const override;
312 [[nodiscard]] uint64_t coordinates_to_linear_index(const std::vector<uint64_t>& coords) const override;
313 [[nodiscard]] std::vector<uint64_t> linear_index_to_coordinates(uint64_t index) const override;
314 void clear() override;
315
316 //=========================================================================
317 // SignalSourceContainer
318 //=========================================================================
319
320 /**
321 * @brief Write DataVariant bytes into the pixel buffer at the region bounds.
322 *
323 * The region's SPATIAL_Y / SPATIAL_X coordinates are used as the destination
324 * rectangle. The first DataVariant in @p data must hold std::vector<uint8_t>
325 * with exactly region_width * region_height * channels bytes.
326 *
327 * @param region Destination region (start/end SPATIAL_Y, SPATIAL_X coords).
328 * @param data Source data. First element must be vector<uint8_t>.
329 */
330 void set_region_data(const Region& region,
331 const std::vector<DataVariant>& data) override;
332
333 [[nodiscard]] ProcessingState get_processing_state() const override;
334 void update_processing_state(ProcessingState state) override;
335 void register_state_change_callback(
336 std::function<void(const std::shared_ptr<SignalSourceContainer>&, ProcessingState)> cb) override;
337 void unregister_state_change_callback() override;
338
339 [[nodiscard]] bool is_ready_for_processing() const override;
340 void mark_ready_for_processing(bool ready) override;
341
342 [[nodiscard]] std::vector<DataVariant>& get_processed_data() override;
343 [[nodiscard]] const std::vector<DataVariant>& get_processed_data() const override;
344 [[nodiscard]] const std::vector<DataVariant>& get_data() override;
345
346 /** @brief No-op. TextureContainer has no BufferManager integration. */
347 void mark_buffers_for_processing(bool) override { }
348
349 /** @brief No-op. TextureContainer has no BufferManager integration. */
350 void mark_buffers_for_removal() override { }
351
352 [[nodiscard]] DataAccess channel_data(size_t channel_index) override;
353 [[nodiscard]] std::vector<DataAccess> all_channel_data() override;
354
355 /** @brief No-op. All pixel data is always resident; no streaming regions. */
356 void load_region(const Region&) override { }
357
358 /** @brief No-op. All pixel data is always resident; no streaming regions. */
359 void unload_region(const Region&) override { }
360 [[nodiscard]] bool is_region_loaded(const Region&) const override { return true; }
361
362 void add_region_group(const RegionGroup& group) override;
363 [[nodiscard]] RegionGroup get_region_group(const std::string& name) const override;
364 [[nodiscard]] std::unordered_map<std::string, RegionGroup> get_all_region_groups() const override;
365 void remove_region_group(const std::string& name) override;
366
367 [[nodiscard]] const void* get_raw_data() const override;
368 [[nodiscard]] bool has_data() const override;
369
370 [[nodiscard]] ContainerDataStructure& get_structure() override { return m_structure; }
371 [[nodiscard]] const ContainerDataStructure& get_structure() const override { return m_structure; }
372 void set_structure(ContainerDataStructure s) override { m_structure = std::move(s); }
373
374 void reset_processing_token() { m_processing_token.store(-1); }
376 {
377 int expected = -1;
378 return m_processing_token.compare_exchange_strong(expected, ch);
379 }
380 [[nodiscard]] bool has_processing_token(int ch) const
381 {
382 return m_processing_token.load() == ch;
383 }
384
385 /** @brief No-op. TextureContainer holds bytes passively; no processor lifecycle. */
386 void create_default_processor() override { }
387
388 /** @brief No-op. TextureContainer holds bytes passively; no processor lifecycle. */
389 void process_default() override { }
390
391 void set_default_processor(const std::shared_ptr<DataProcessor>& p) override { m_processor = p; }
392 std::shared_ptr<DataProcessor> get_default_processor() const override { return m_processor; }
393 std::shared_ptr<DataProcessingChain> get_processing_chain() override;
394 void set_processing_chain(const std::shared_ptr<DataProcessingChain>& c) override { m_chain = c; }
395
396 /**
397 * @brief No-op. TextureContainer does not track concurrent dimension readers.
398 * @return Always 0.
399 */
400 uint32_t register_dimension_reader(uint32_t) override { return 0; }
401
402 /** @brief No-op. TextureContainer does not track concurrent dimension readers. */
403 void unregister_dimension_reader(uint32_t) override { }
404
405 /**
406 * @brief No-op. TextureContainer does not track concurrent dimension readers.
407 * @return Always false.
408 */
409 [[nodiscard]] bool has_active_readers() const override { return false; }
410
411 /** @brief No-op. TextureContainer does not track concurrent dimension readers. */
412 void mark_dimension_consumed(uint32_t, uint32_t) override { }
413
414 /**
415 * @brief No-op. TextureContainer does not track concurrent dimension readers.
416 * @return Always true.
417 */
418 [[nodiscard]] bool all_dimensions_consumed() const override { return true; }
419
420protected:
421 [[nodiscard]] auto get_frame_span_impl(uint64_t frame_index) const -> DataSpanVariant override
422 {
423 return get_frame_typed(frame_index);
424 }
425
426 void get_frames_impl(void* output, size_t count, uint64_t start_frame, uint64_t num_frames, const std::type_info& type) const override;
427
428 void get_value_impl(const std::vector<uint64_t>& coords,
429 void* out, const std::type_info& type) const override;
430
431 void set_value_impl(const std::vector<uint64_t>& coords,
432 const void* in, const std::type_info& type) override;
433
434private:
435 void setup_dimensions();
436
437 uint32_t m_width {};
438 uint32_t m_height {};
440 uint32_t m_channels {};
441 size_t m_bpp {};
442
443 std::vector<DataVariant> m_data;
444 std::vector<DataVariant> m_processed_data;
445 std::shared_ptr<DataProcessor> m_processor;
446 std::shared_ptr<DataProcessingChain> m_chain;
447
448 mutable std::vector<std::vector<float>> m_normalised_cache;
449 mutable std::vector<std::atomic<bool>> m_normalised_dirty;
450
452
453 std::atomic<ProcessingState> m_processing_state { ProcessingState::IDLE };
454 std::atomic<bool> m_ready_for_processing { false };
455 std::atomic<int> m_processing_token { -1 };
456
460
461 std::function<void(const std::shared_ptr<SignalSourceContainer>&, ProcessingState)> m_state_cb;
462 std::unordered_map<std::string, RegionGroup> m_region_groups;
463
464 /** @brief Row cache backing the double span returned by get_frame(). */
465 mutable std::vector<double> m_frame_cache;
466
467 [[nodiscard]] auto get_frame_typed(uint64_t frame_index) const -> DataSpanVariant;
468 void get_frames_typed(void* output, size_t count, uint64_t start_frame, uint64_t num_frames, const std::type_info& type) const;
469
470 template <typename T>
471 [[nodiscard]] auto get_frame_typed_as(uint64_t frame_index) const -> std::span<const T>;
472
473 template <typename T>
474 void get_frames_typed_as(std::span<T> output, uint64_t start_frame, uint64_t num_frames) const;
475};
476
477} // namespace MayaFlux::Kakshya
IO::ImageData image
Definition Decoder.cpp:64
uint32_t width
Definition Decoder.cpp:66
size_t count
std::shared_ptr< Core::VKImage > output
uint32_t height
Type-erased accessor for NDData with semantic view construction.
Data-driven interface for managing arbitrary processable signal sources.
void unload_region(const Region &) override
No-op.
void load_region(const Region &) override
No-op.
std::vector< std::atomic< bool > > m_normalised_dirty
std::unordered_map< std::string, RegionGroup > m_region_groups
void set_processing_chain(const std::shared_ptr< DataProcessingChain > &c) override
Set the processing chain for this container.
void set_structure(ContainerDataStructure s) override
Set the data structure for this container.
~TextureContainer() override=default
TextureContainer(const std::shared_ptr< Core::VKImage > &image, Portal::Graphics::ImageFormat format)
Construct from an existing VKImage, downloading its pixel data.
TextureContainer(const TextureContainer &)=delete
ContainerDataStructure & get_structure() override
Get the data structure defining this container's layout.
TextureContainer(uint32_t width, uint32_t height, Portal::Graphics::ImageFormat format, uint32_t layers=1)
Construct an empty container with declared dimensions.
bool has_active_readers() const override
No-op.
TextureContainer(TextureContainer &&)=delete
std::function< void(const std::shared_ptr< SignalSourceContainer > &, ProcessingState)> m_state_cb
std::vector< DataVariant > m_processed_data
void unregister_dimension_reader(uint32_t) override
No-op.
bool all_dimensions_consumed() const override
No-op.
const ContainerDataStructure & get_structure() const override
auto get_frame_span_impl(uint64_t frame_index) const -> DataSpanVariant override
Implementation-specific method to retrieve a frame span.
void set_default_processor(const std::shared_ptr< DataProcessor > &p) override
Set the default data processor for this container.
void mark_buffers_for_removal() override
No-op.
size_t row_stride() const
Byte count of one complete pixel row.
std::shared_ptr< DataProcessor > get_default_processor() const override
Get the current default data processor.
std::shared_ptr< DataProcessingChain > m_chain
uint32_t register_dimension_reader(uint32_t) override
No-op.
Portal::Graphics::ImageFormat get_format() const
std::vector< double > m_frame_cache
Row cache backing the double span returned by get_frame().
void create_default_processor() override
No-op.
TextureContainer & operator=(const TextureContainer &)=delete
bool is_region_loaded(const Region &) const override
Check if a region is loaded in memory.
void mark_buffers_for_processing(bool) override
No-op.
std::vector< DataVariant > get_region_group_data(const RegionGroup &) const override
Get data for multiple regions efficiently.
std::vector< std::vector< float > > m_normalised_cache
std::shared_ptr< DataProcessor > m_processor
TextureContainer & operator=(TextureContainer &&)=delete
void mark_dimension_consumed(uint32_t, uint32_t) override
No-op.
size_t byte_size() const
Total byte count of the pixel buffer.
SignalSourceContainer wrapping GPU texture data as addressable pixel bytes.
Indexed collection of independent, equal-ranked Seqlock instances.
Definition SeqLock.hpp:269
Single-writer multiple-reader sequence lock for fixed-size data regions.
Definition SeqLock.hpp:44
ProcessingState
Represents the current processing lifecycle state of a container.
typename detail::span_const_from_vector_variant< DataVariant >::type DataSpanVariant
Definition NDData.hpp:592
std::optional< RegionGroup > get_region_group(const std::unordered_map< std::string, RegionGroup > &groups, const std::string &name)
Get a RegionGroup by name from a group map.
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
void add_region_group(std::unordered_map< std::string, RegionGroup > &groups, const RegionGroup &group)
Add a RegionGroup to a group map.
MemoryLayout
Memory layout for multi-dimensional data.
Definition NDData.hpp:65
void remove_region_group(std::unordered_map< std::string, RegionGroup > &groups, const std::string &name)
Remove a RegionGroup by name from a group map.
ImageFormat
User-friendly image format enum.
Container structure for consistent dimension ordering.
Organizes related signal regions into a categorized collection.
Represents a point or span in N-dimensional space.
Definition Region.hpp:73