MayaFlux 0.3.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
GeometryWriterNode.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "GpuSync.hpp"
4#include "VertexSpec.hpp"
5
7
9
10/**
11 * @class GeometryContext
12 * @brief Context for GeometryWriterNode - provides vertex buffer access
13 */
14class MAYAFLUX_API GeometryContext : public NodeContext, public GpuStructuredData {
15public:
16 GeometryContext(double value,
17 std::span<const uint8_t> vertex_data,
18 size_t vertex_stride,
19 uint32_t vertex_count)
20 : NodeContext(value, typeid(GeometryContext).name())
21 , GpuStructuredData(vertex_data, vertex_stride, vertex_count)
22 , vertex_count(vertex_count)
23 , vertex_stride(vertex_stride)
24 {
25 }
26
27 uint32_t vertex_count;
29
30protected:
31 friend class GeometryWriterNode;
32};
33
34/**
35 * @class GeometryWriterNode
36 * @brief Base class for nodes that generate 3D geometry data
37 *
38 * Analogous to TextureNode but for vertex geometry instead of pixel data.
39 * Each frame (at VISUAL_RATE), compute_frame() is called to generate new vertex data.
40 * Vertex data is stored as a flat array in configurable interleaved or non-interleaved format.
41 *
42 * Subclasses implement compute_frame() to populate m_vertex_buffer with vertex data.
43 * The buffer follows a user-defined stride (bytes per vertex) allowing flexible vertex formats:
44 * - Positions only (3 floats = 12 bytes per vertex)
45 * - Positions + Colors (3 + 3 floats = 24 bytes per vertex)
46 * - Positions + Normals + Texcoords (3 + 3 + 2 floats = 32 bytes per vertex)
47 * - Any custom interleaved format
48 *
49 * Usage:
50 * ```cpp
51 * class MyParticleSystem : public GeometryWriterNode {
52 * void compute_frame() override {
53 * std::vector<glm::vec3> positions;
54 * for (int i = 0; i < 1000; i++) {
55 * positions.push_back(generate_particle(i));
56 * }
57 *
58 * set_vertex_stride(sizeof(glm::vec3));
59 * resize_vertex_buffer(positions.size());
60 * memcpy(m_vertex_buffer.data(), positions.data(),
61 * positions.size() * sizeof(glm::vec3));
62 * m_vertex_count = positions.size();
63 * }
64 * };
65 * ```
66 */
67class MAYAFLUX_API GeometryWriterNode : public GpuSync {
68protected:
69 /// @brief Vertex data buffer (flat array of bytes)
70 std::vector<uint8_t> m_vertex_buffer;
71
72 /// @brief Number of vertices in buffer
73 uint32_t m_vertex_count {};
74
75 /// @brief Bytes per vertex (stride for vertex buffer binding)
76 size_t m_vertex_stride {};
77
78 /// @brief Cached vertex layout for descriptor binding
79 std::optional<Kakshya::VertexLayout> m_vertex_layout;
80
81 /// @brief Flag indicating if layout needs update
82 bool m_needs_layout_update {};
83
84 /**
85 * @brief Flag: vertex data or layout changed since last GPU upload
86 *
87 * Set to true whenever compute_frame() modifies vertex buffer or layout.
88 * Checked by GeometryBindingsProcessor before staging GPU transfer.
89 * Cleared by processor after successful upload.
90 */
91 bool m_vertex_data_dirty { true };
92
93public:
94 /**
95 * @brief Constructor
96 * @param initial_capacity Initial number of vertices to reserve space for
97 */
98 GeometryWriterNode(uint32_t initial_capacity = 1024);
99
100 ~GeometryWriterNode() override = default;
101
102 /**
103 * @brief Process batch for geometry generation
104 * @param num_samples Number of frames to process
105 * @return Empty vector (geometry nodes don't produce audio output)
106 *
107 * Calls compute_frame() once per batch.
108 * Subclasses can override for custom batch behavior.
109 */
110 std::vector<double> process_batch(unsigned int num_samples) override;
111
112 /**
113 * @brief Get raw vertex buffer data
114 * @return Span of float data (interpreted as bytes, stride-aligned)
115 */
116 [[nodiscard]] std::span<const uint8_t> get_vertex_data() const { return m_vertex_buffer; }
117
118 /**
119 * @brief Get vertex buffer size in bytes
120 * @return Total size of vertex data buffer
121 */
122 [[nodiscard]] size_t get_vertex_buffer_size_bytes() const;
123
124 /**
125 * @brief Get number of vertices
126 * @return Current vertex count
127 */
128 [[nodiscard]] uint32_t get_vertex_count() const { return m_vertex_count; }
129
130 /**
131 * @brief Get stride (bytes per vertex)
132 * @return Stride in bytes
133 */
134 [[nodiscard]] size_t get_vertex_stride() const { return m_vertex_stride; }
135
136 /**
137 * @brief Set vertex stride (bytes per vertex)
138 * @param stride Stride in bytes
139 *
140 * Must be called before filling buffer. Common values:
141 * - 12 (vec3 positions only)
142 * - 24 (vec3 positions + vec3 colors)
143 * - 32 (vec3 positions + vec3 normals + vec2 texcoords)
144 */
145 void set_vertex_stride(size_t stride);
146
147 /**
148 * @brief Resize vertex buffer to hold specified number of vertices
149 * @param vertex_count Number of vertices
150 * @param preserve_data If true, keep existing data; if false, clear buffer
151 *
152 * Allocates/reallocates buffer to hold vertex_count vertices at current stride.
153 * If stride is 0, logs error and does nothing.
154 */
155 void resize_vertex_buffer(uint32_t vertex_count, bool preserve_data = false);
156
157 /**
158 * @brief Copy raw vertex data into buffer
159 * @param data Pointer to vertex data
160 * @param size_bytes Number of bytes to copy
161 *
162 * Direct memory copy into m_vertex_buffer.
163 * User is responsible for ensuring data format matches stride.
164 */
165 void set_vertex_data(const void* data, size_t size_bytes);
166
167 /**
168 * @brief Set a single vertex by index
169 * @param vertex_index Index of vertex to set
170 * @param data Pointer to vertex data
171 * @param size_bytes Size of vertex data (should match stride)
172 *
173 * Copies size_bytes starting at (vertex_index * stride).
174 */
175 void set_vertex(uint32_t vertex_index, const void* data, size_t size_bytes);
176
177 /**
178 * @brief Get a single vertex by index
179 * @param vertex_index Index of vertex
180 * @return Span covering this vertex's data
181 */
182 [[nodiscard]] std::span<const uint8_t> get_vertex(uint32_t vertex_index) const;
183
184 /**
185 * @brief Set multiple vertices from typed array
186 * @tparam T Vertex type
187 * @param vertices Span of vertex data
188 *
189 * Sets vertex stride to sizeof(T), resizes buffer, and copies data.
190 */
191 template <typename T>
192 void set_vertices(std::span<const T> vertices)
193 {
194 set_vertex_stride(sizeof(T));
195 resize_vertex_buffer(vertices.size());
196 std::memcpy(m_vertex_buffer.data(), vertices.data(),
197 vertices.size() * sizeof(T));
198 m_vertex_count = static_cast<uint32_t>(vertices.size());
199 m_needs_layout_update = true;
200 m_vertex_data_dirty = true;
201 }
202
203 /**
204 * @brief Set a single vertex by index from typed data
205 * @tparam T Vertex type
206 * @param index Index of vertex to set
207 * @param vertex Vertex data
208 */
209 template <typename T>
210 void set_vertex_typed(uint32_t index, const T& vertex)
211 {
212 set_vertex(index, &vertex, sizeof(T));
213 }
214
215 /**
216 * @brief Get a single vertex by index as typed data
217 * @tparam T Vertex type
218 * @param index Index of vertex
219 * @return Vertex data of type T
220 */
221 template <typename T>
222 T get_vertex_typed(uint32_t index) const
223 {
224 auto data = get_vertex(index);
225 if (data.size_bytes() < sizeof(T)) {
226 return T {};
227 }
228 T result;
229 std::memcpy(&result, data.data(), sizeof(T));
230 return result;
231 }
232
233 /**
234 * @brief Clear vertex buffer and reset count
235 */
236 void clear();
237
238 /**
239 * @brief Clear vertex buffer and resize to specified count
240 * @param vertex_count Number of vertices
241 */
242 void clear_and_resize(uint32_t vertex_count);
243
244 /**
245 * @brief Set cached vertex layout
246 * @param layout VertexLayout describing attribute structure
247 *
248 * Called internally or by GeometryBindingsProcessor to describe
249 * the semantic meaning of vertex data (positions, colors, normals, etc.)
250 */
251 void set_vertex_layout(const Kakshya::VertexLayout& layout) { m_vertex_layout = layout; }
252
253 /**
254 * @brief Get cached vertex layout
255 * @return Optional containing layout if set
256 */
257 [[nodiscard]] std::optional<Kakshya::VertexLayout> get_vertex_layout() const { return m_vertex_layout; }
258
259 /**
260 * @brief Check if layout needs update
261 * @return True if stride or vertex count changed
262 */
263 [[nodiscard]] bool needs_layout_update() const { return m_needs_layout_update; }
264
265 /**
266 * @brief Clear layout update flag
267 */
268 void clear_layout_update_flag() { m_needs_layout_update = false; }
269
270 /**
271 * @brief Save current geometry state
272 *
273 * Saves vertex buffer, count, stride, and layout.
274 * Subclasses can override to save additional state.
275 */
276 void save_state() override;
277
278 /**
279 * @brief Restore saved geometry state
280 *
281 * Restores vertex buffer, count, stride, and layout.
282 * Subclasses can override to restore additional state.
283 */
284 void restore_state() override;
285
286 /**
287 * @brief Check if vertex data or layout changed since last GPU sync
288 * @return True if m_vertex_buffer or m_vertex_layout has been modified
289 *
290 * For geometry, this combines data mutation (vertex_buffer changed) and
291 * structural change (stride or layout changed).
292 */
293 [[nodiscard]] bool needs_gpu_update() const override
294 {
295 return m_vertex_data_dirty || m_needs_layout_update;
296 }
297
298 /**
299 * @brief Check if vertex data has changed since last GPU sync
300 * @return True if vertex buffer content has been modified
301 *
302 * This is a more specific check for data changes, ignoring layout/stride.
303 * Useful for optimizations that only care about vertex content changes.
304 */
305 [[nodiscard]] bool needs_vertex_data_update() const { return m_vertex_data_dirty; }
306
307 /**
308 * @brief Set vertex data dirty flag
309 * @param state New state of the dirty flag
310 */
311 void mark_vertex_data_dirty(bool state) { m_vertex_data_dirty = state; }
312
313 /**
314 * @brief Clear the dirty flag after GPU upload completes
315 *
316 * Called by GeometryBindingsProcessor after it stages the vertex data
317 * into a GPU transfer buffer and submits the command.
318 */
319 void clear_gpu_update_flag() override
320 {
321 m_vertex_data_dirty = false;
322 m_needs_layout_update = false;
323 }
324
325 /**
326 * @brief Retrieves the last created context object
327 * @return Reference to the last GeometryContext object
328 *
329 * This method provides access to the most recent GeometryContext object
330 * created by the geometry writer node. This context contains information
331 * about the node's state at the time of the last output generation.
332 */
333 NodeContext& get_last_context() override;
334
335 /**
336 * @brief Updates the context object with the current node state
337 * @param value The current sample value
338 */
339 void update_context(double value) override;
340
341 /**
342 * @brief Get primitive topology for rendering
343 *
344 * Default is POINT_LIST, but operators can override for lines/triangles.
345 */
347 {
348 return Portal::Graphics::PrimitiveTopology::POINT_LIST;
349 }
350
351protected:
352#ifdef MAYAFLUX_PLATFORM_MACOS
353 /**
354 * @brief Convert line segments (pairs of LineVertex) into triangle strip vertices
355 * @param line_segments Input LINE_LIST vertices (2N vertices = N line segments)
356 * @return Triangle vertices (6N vertices = 2N triangles)
357 */
358 static std::vector<LineVertex> expand_lines_to_triangles(
359 const std::vector<LineVertex>& line_segments);
360#endif
361
362 GeometryContext m_context { 0.0, {}, 0, 0 };
363
364private:
366 std::vector<uint8_t> vertex_buffer;
367 uint32_t vertex_count;
369 std::optional<Kakshya::VertexLayout> vertex_layout;
370 };
371
372 std::optional<GeometryState> m_saved_state;
373};
374
375} // namespace MayaFlux::Nodes
GPU-uploadable structured data (arrays of POD structs)
GeometryContext(double value, std::span< const uint8_t > vertex_data, size_t vertex_stride, uint32_t vertex_count)
Context for GeometryWriterNode - provides vertex buffer access.
uint32_t get_vertex_count() const
Get number of vertices.
bool needs_vertex_data_update() const
Check if vertex data has changed since last GPU sync.
virtual Portal::Graphics::PrimitiveTopology get_primitive_topology() const
Get primitive topology for rendering.
std::vector< uint8_t > m_vertex_buffer
Vertex data buffer (flat array of bytes)
void mark_vertex_data_dirty(bool state)
Set vertex data dirty flag.
void clear_gpu_update_flag() override
Clear the dirty flag after GPU upload completes.
bool needs_layout_update() const
Check if layout needs update.
std::optional< Kakshya::VertexLayout > get_vertex_layout() const
Get cached vertex layout.
void set_vertex_layout(const Kakshya::VertexLayout &layout)
Set cached vertex layout.
void set_vertices(std::span< const T > vertices)
Set multiple vertices from typed array.
std::optional< Kakshya::VertexLayout > m_vertex_layout
Cached vertex layout for descriptor binding.
void clear_layout_update_flag()
Clear layout update flag.
size_t get_vertex_stride() const
Get stride (bytes per vertex)
bool needs_gpu_update() const override
Check if vertex data or layout changed since last GPU sync.
void set_vertex_typed(uint32_t index, const T &vertex)
Set a single vertex by index from typed data.
std::span< const uint8_t > get_vertex_data() const
Get raw vertex buffer data.
T get_vertex_typed(uint32_t index) const
Get a single vertex by index as typed data.
Base class for nodes that generate 3D geometry data.
Base context class for node callbacks.
Definition Node.hpp:30
PrimitiveTopology
Vertex assembly primitive topology.
Complete description of vertex data layout in a buffer.