MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
WindowContainer.hpp
Go to the documentation of this file.
1#pragma once
2
5
6namespace MayaFlux::Core {
7class Window;
8class VKImage;
9}
10
11namespace MayaFlux::Buffers {
12class VKBuffer;
13}
14
15namespace MayaFlux::Kakshya {
16
17/**
18 * @class WindowContainer
19 * @brief SignalSourceContainer wrapping a live GLFW/Vulkan window surface.
20 *
21 * Exposes a window's rendered surface as addressable N-dimensional data.
22 * Dimensions follow IMAGE_COLOR convention:
23 * dims[0] → SPATIAL_Y (height)
24 * dims[1] → SPATIAL_X (width)
25 * dims[2] → CHANNEL
26 *
27 * Region semantics:
28 * Regions are registered through the inherited RegionGroup API
29 * (add_region_group / get_all_region_groups / remove_region_group).
30 * load_region() and unload_region() are no-ops on this container — all
31 * surface data is always available after a GPU readback; region selection
32 * is a processor concern, not a container concern.
33 *
34 * Processing model:
35 * - The default processor (WindowAccessProcessor) performs one full-surface
36 * GPU readback per process() call into processed_data[0].
37 * - The processing chain (SpatialRegionProcessor) extracts registered
38 * regions from the readback as separate DataVariant entries.
39 * - get_region_data() crops directly from processed_data[0] if it is
40 * non-empty; returns empty if no readback has occurred yet.
41 * - The container never calls process() itself — callers drive the chain.
42 * GPU bridge:
43 * - to_image() uploads processed_data[0] to a new VKImage via TextureLoom.
44 * - to_image(staging) does the same reusing a caller-supplied staging buffer
45 * to avoid per-call VkBuffer allocation in per-frame paths.
46 * - region_to_image() performs a CPU-side crop then uploads the result.
47 * - get_image_format() returns the live swapchain format as a Portal
48 * ImageFormat, suitable for constructing a matching TextureBuffer.
49 *
50 * Write semantics (compositing) are deferred to a future processor.
51 */
52class MAYAFLUX_API WindowContainer : public SignalSourceContainer {
53public:
54 /**
55 * @brief Construct from an existing managed window.
56 * @param window Live window whose surface will be addressed as NDData.
57 */
58 explicit WindowContainer(std::shared_ptr<Core::Window> window);
59
60 ~WindowContainer() override = default;
61
66
67 /**
68 * @brief The underlying window.
69 */
70 [[nodiscard]] std::shared_ptr<Core::Window> get_window() const { return m_window; }
71
72 /**
73 * @brief Portal ImageFormat corresponding to the live swapchain surface format.
74 *
75 * Derived from the actual negotiated swapchain format via DisplayService,
76 * not from the WindowCreateInfo declaration. Use this when constructing a
77 * TextureBuffer to receive the output of to_image().
78 */
79 [[nodiscard]] Portal::Graphics::ImageFormat get_image_format() const;
80
81 // =========================================================================
82 // NDDimensionalContainer
83 // =========================================================================
84
85 [[nodiscard]] std::vector<DataDimension> get_dimensions() const override;
86 [[nodiscard]] uint64_t get_total_elements() const override;
87 [[nodiscard]] MemoryLayout get_memory_layout() const override;
88 void set_memory_layout(MemoryLayout layout) override;
89
90 /**
91 * @brief Extract data for all regions across all region groups that
92 * spatially intersect @p region.
93 * Crops from the last full-surface readback — no GPU work.
94 * Returns empty if no readback has been performed yet.
95 */
96 [[nodiscard]] std::vector<DataVariant> get_region_data(const Region& region) const override;
97
98 /**
99 * @brief Upload the full surface readback to a new VKImage.
100 *
101 * Requires at least one completed readback (processed_data[0] non-empty
102 * and of type vector<uint8_t>). A fresh VKImage is created and uploaded
103 * on each call via TextureLoom; callers driving a per-frame path should
104 * prefer the staging-buffer overload to avoid per-call VkBuffer churn.
105 *
106 * @return Newly created VKImage, or nullptr on failure.
107 */
108 [[nodiscard]] std::shared_ptr<Core::VKImage> to_image() const;
109
110 /**
111 * @brief Upload the full surface readback to a new VKImage, reusing a
112 * caller-supplied persistent staging buffer.
113 *
114 * Allocates the VKImage without pixel data, then uploads via the
115 * provided staging buffer, bypassing the per-call VkBuffer allocation
116 * inside TextureLoom. Use TextureLoom::create_streaming_staging() to
117 * allocate the staging buffer once before the render loop.
118 *
119 * @param staging Host-visible staging VKBuffer sized to at least
120 * width * height * bytes_per_pixel.
121 * @return Newly created VKImage, or nullptr on failure.
122 */
123 [[nodiscard]] std::shared_ptr<Core::VKImage> to_image(
124 const std::shared_ptr<Buffers::VKBuffer>& staging) const;
125
126 /**
127 * @brief Crop a region from the last readback and upload it as a VKImage.
128 *
129 * Performs a CPU-side crop via extract_region_data; no additional GPU
130 * work beyond the readback that populated processed_data[0]. Region
131 * coordinates follow IMAGE_COLOR convention: [SPATIAL_Y, SPATIAL_X].
132 * The returned image dimensions are derived from the region extent.
133 *
134 * @param region Pixel rectangle. Must have at least 2 coordinates.
135 * @return VKImage sized to the region, or nullptr on failure.
136 */
137 [[nodiscard]] std::shared_ptr<Core::VKImage> region_to_image(const Region& region) const;
138
139 void set_region_data(const Region& region, const std::vector<DataVariant>& data) override;
140
141 [[nodiscard]] std::vector<DataVariant> get_region_group_data(const RegionGroup& group) const override;
142 [[nodiscard]] std::vector<DataVariant> get_segments_data(const std::vector<RegionSegment>& segments) const override;
143
144 [[nodiscard]] double get_value_at(const std::vector<uint64_t>& coordinates) const override;
145 void set_value_at(const std::vector<uint64_t>& coordinates, double value) override;
146
147 [[nodiscard]] uint64_t coordinates_to_linear_index(const std::vector<uint64_t>& coordinates) const override;
148 [[nodiscard]] std::vector<uint64_t> linear_index_to_coordinates(uint64_t linear_index) const override;
149
150 void clear() override;
151 void lock() override;
152 void unlock() override;
153 bool try_lock() override;
154
155 [[nodiscard]] const void* get_raw_data() const override;
156 [[nodiscard]] bool has_data() const override;
157
158 [[nodiscard]] ContainerDataStructure& get_structure() override { return m_structure; }
159 [[nodiscard]] const ContainerDataStructure& get_structure() const override { return m_structure; }
160 void set_structure(ContainerDataStructure s) override { m_structure = std::move(s); }
161
162 // -------------------------------------------------------------------------
163 // RegionGroup API — primary region registration interface.
164 // -------------------------------------------------------------------------
165
166 void add_region_group(const RegionGroup& group) override;
167 [[nodiscard]] const RegionGroup& get_region_group(const std::string& name) const override;
168 [[nodiscard]] std::unordered_map<std::string, RegionGroup> get_all_region_groups() const override;
169 void remove_region_group(const std::string& name) override;
170
171 /**
172 * @brief No-op. All surface data is continuously available after readback.
173 * Register regions via add_region_group() instead.
174 */
175 void load_region(const Region& region) override;
176
177 /**
178 * @brief No-op. See load_region().
179 */
180 void unload_region(const Region& region) override;
181
182 /**
183 * @brief Always returns true. Surface data is available after any readback.
184 */
185 [[nodiscard]] bool is_region_loaded(const Region& region) const override;
186
187 // =========================================================================
188 // SignalSourceContainer
189 // =========================================================================
190
191 [[nodiscard]] ProcessingState get_processing_state() const override;
192 void update_processing_state(ProcessingState new_state) override;
193
194 void register_state_change_callback(
195 std::function<void(const std::shared_ptr<SignalSourceContainer>&, ProcessingState)> callback) override;
196 void unregister_state_change_callback() override;
197
198 [[nodiscard]] bool is_ready_for_processing() const override;
199 void mark_ready_for_processing(bool ready) override;
200
201 void create_default_processor() override;
202 void process_default() override;
203
204 void set_default_processor(const std::shared_ptr<DataProcessor>& processor) override;
205 [[nodiscard]] std::shared_ptr<DataProcessor> get_default_processor() const override;
206
207 [[nodiscard]] std::shared_ptr<DataProcessingChain> get_processing_chain() override;
208 void set_processing_chain(const std::shared_ptr<DataProcessingChain>& chain) override;
209
210 [[nodiscard]] uint64_t get_frame_size() const override;
211 [[nodiscard]] uint64_t get_num_frames() const override;
212 [[nodiscard]] std::span<const double> get_frame(uint64_t frame_index) const override;
213 void get_frames(std::span<double> output, uint64_t start_frame, uint64_t num_frames) const override;
214
215 // -------------------------------------------------------------------------
216 // Consumer tracking — dimension_index and reader_id are opaque slot handles.
217 // Tracks whether all registered consumers have read processed_data[0] this
218 // cycle. The slot index argument exists for interface compliance; internally
219 // this container tracks a single consumer count across all slots.
220 // -------------------------------------------------------------------------
221 uint32_t register_dimension_reader(uint32_t dimension_index) override;
222 void unregister_dimension_reader(uint32_t dimension_index) override;
223 [[nodiscard]] bool has_active_readers() const override;
224 void mark_dimension_consumed(uint32_t dimension_index, uint32_t reader_id) override;
225 [[nodiscard]] bool all_dimensions_consumed() const override;
226
227 [[nodiscard]] std::vector<DataVariant>& get_processed_data() override;
228 [[nodiscard]] const std::vector<DataVariant>& get_processed_data() const override;
229 [[nodiscard]] const std::vector<DataVariant>& get_data() override;
230
231 void mark_buffers_for_processing(bool) override { }
232 void mark_buffers_for_removal() override { }
233
234 [[nodiscard]] DataAccess channel_data(size_t channel_index) override;
235 [[nodiscard]] std::vector<DataAccess> all_channel_data() override;
236
237private:
238 std::shared_ptr<Core::Window> m_window;
239
241 std::vector<DataVariant> m_data;
242 std::vector<DataVariant> m_processed_data;
243
244 std::unordered_map<std::string, RegionGroup> m_region_groups;
245
246 std::shared_ptr<DataProcessor> m_default_processor;
247 std::shared_ptr<DataProcessingChain> m_processing_chain;
248
249 std::atomic<ProcessingState> m_processing_state { ProcessingState::IDLE };
250 std::atomic<bool> m_ready_for_processing { false };
251
252 std::function<void(const std::shared_ptr<SignalSourceContainer>&, ProcessingState)> m_state_callback;
253
254 mutable std::shared_mutex m_data_mutex;
255 mutable std::mutex m_state_mutex;
256
257 mutable std::vector<double> m_frame_cache;
258
259 std::atomic<uint32_t> m_registered_readers { 0 };
260 std::atomic<uint32_t> m_consumed_readers { 0 };
261 std::atomic<uint32_t> m_next_reader_id { 0 };
262
263 void setup_dimensions();
264};
265
266} // namespace MayaFlux::Kakshya
Type-erased accessor for NDData with semantic view construction.
Data-driven interface for managing arbitrary processable signal sources.
std::function< void(const std::shared_ptr< SignalSourceContainer > &, ProcessingState)> m_state_callback
~WindowContainer() override=default
void mark_buffers_for_removal() override
Mark associated buffers for removal from the system.
WindowContainer & operator=(const WindowContainer &)=delete
WindowContainer(WindowContainer &&)=delete
std::shared_ptr< Core::Window > m_window
ContainerDataStructure & get_structure() override
Get the data structure defining this container's layout.
WindowContainer & operator=(WindowContainer &&)=delete
std::vector< DataVariant > m_processed_data
std::unordered_map< std::string, RegionGroup > m_region_groups
std::vector< DataVariant > m_data
std::shared_ptr< DataProcessingChain > m_processing_chain
std::shared_ptr< DataProcessor > m_default_processor
void set_structure(ContainerDataStructure s) override
Set the data structure for this container.
WindowContainer(const WindowContainer &)=delete
void mark_buffers_for_processing(bool) override
Mark associated buffers for processing in the next cycle.
std::shared_ptr< Core::Window > get_window() const
The underlying window.
const ContainerDataStructure & get_structure() const override
SignalSourceContainer wrapping a live GLFW/Vulkan window surface.
ProcessingState
Represents the current processing lifecycle state of a container.
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.
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:39
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:67