MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
NetworkGeometryBuffer.hpp
Go to the documentation of this file.
1#pragma once
2
6
7namespace MayaFlux::Buffers {
8
9class RenderProcessor;
10
11/**
12 * @class NetworkGeometryBuffer
13 * @brief Specialized buffer for geometry from NodeNetwork instances
14 *
15 * Aggregates geometry from all nodes within a network into a single GPU buffer.
16 * Designed for networks like ParticleNetwork (1000+ points in PointCollectionNode), PointCloudNetwork,
17 * and other multi-node generative systems.
18 *
19 * Philosophy:
20 * - Networks are collections of MANY nodes with relationships
21 * - This buffer aggregates all node geometry → single draw call
22 * - Supports dynamic growth as networks evolve
23 *
24 * Key Differences from GeometryBuffer:
25 * - Accepts NodeNetwork (not single GeometryWriterNode)
26 * - Aggregates vertices from ALL internal nodes
27 * - Handles network-specific processing patterns
28 *
29 * Usage:
30 * ```cpp
31 * // Create particle network with 1000 particles
32 * auto particles = std::make_shared<ParticleNetwork>(1000);
33 * particles->set_topology(Topology::SPATIAL);
34 * particles->set_output_mode(OutputMode::GRAPHICS_BIND);
35 *
36 * // Create buffer that aggregates all 1000 Points in PointCollectionNode inside the ParticleNetwork
37 * auto buffer = std::make_shared<NetworkGeometryBuffer>(particles);
38 * buffer->setup_processors(ProcessingToken::VISUAL_RATE);
39 *
40 * // Render all particles in one draw call
41 * auto render = std::make_shared<RenderProcessor>(config);
42 * render->set_target_window(window);
43 * buffer->add_processor(render);
44 * ```
45 */
46class MAYAFLUX_API NetworkGeometryBuffer : public VKBuffer {
47public:
48 /**
49 * @brief Create geometry buffer from network
50 * @param network NodeNetwork containing geometry nodes (e.g., ParticleNetwork)
51 * @param binding_name Logical name for this geometry binding (default: "network_geometry")
52 * @param over_allocate_factor Buffer size multiplier for dynamic growth (default: 2.0x)
53 *
54 * Buffer size is calculated based on network node count and estimated vertex size.
55 * Higher over_allocate_factor recommended for networks that may grow dynamically.
56 */
57 explicit NetworkGeometryBuffer(
58 std::shared_ptr<Nodes::Network::NodeNetwork> network,
59 const std::string& binding_name = "network_geometry",
60 float over_allocate_factor = 2.0F);
61
62 ~NetworkGeometryBuffer() override = default;
63
64 /**
65 * @brief Initialize the buffer and its processors
66 */
67 void setup_processors(ProcessingToken token) override;
68
69 /**
70 * @brief Get the network driving this buffer
71 */
72 [[nodiscard]] std::shared_ptr<Nodes::Network::NodeNetwork> get_network() const
73 {
74 return m_network;
75 }
76
77 /**
78 * @brief Get the processor managing uploads
79 */
80 [[nodiscard]] std::shared_ptr<NetworkGeometryProcessor> get_processor() const
81 {
82 return m_processor;
83 }
84
85 /**
86 * @brief Get the logical binding name
87 */
88 [[nodiscard]] const std::string& get_binding_name() const
89 {
90 return m_binding_name;
91 }
92
93 /**
94 * @brief Get current vertex count (aggregated from all network nodes)
95 */
96 [[nodiscard]] uint32_t get_vertex_count() const;
97
98 /**
99 * @brief Trigger network processing
100 *
101 * Calls network->process_batch() to update physics/state.
102 * Geometry aggregation happens automatically in processor.
103 */
104 void update_network(unsigned int num_samples = 1)
105 {
106 if (m_network && m_network->is_enabled()) {
107 m_network->process_batch(num_samples);
108 }
109 }
110
111 /**
112 * @brief Setup rendering with RenderProcessor
113 * @param config Rendering configuration
114 */
115 void setup_rendering(const RenderConfig& config);
116
117 /**
118 * @brief Add a RenderProcessor for a specific operator chain index
119 * @param config Rendering configuration
120 *
121 * This allows rendering different subsets of the network geometry with different pipelines.
122 * Each chain index corresponds to a specific node/operator in the network.
123 */
124 void add_chain_operator_rendering(const RenderConfig& config);
125
126 /**
127 * @brief Get RenderProcessor for a specific operator chain index
128 * @param index Operator chain index
129 * @return Optional containing RenderProcessor if exists
130 *
131 * Each chain index corresponds to a specific node/operator in the network.
132 */
133 [[nodiscard]] std::shared_ptr<RenderProcessor> get_chain_render_processor(size_t index) const;
134
135 /**
136 * @brief Update vertex range for a specific operator chain index
137 * @param index Operator chain index
138 * @param vertex_offset Starting vertex offset for this chain
139 * @param vertex_count Number of vertices for this chain
140 * @param layout Optional vertex layout for this chain (if different from primary)
141 *
142 * This allows the processor to push per-chain vertex ranges to the RenderProcessor,
143 * enabling it to issue draw calls for specific subsets of the geometry.
144 */
145 void update_chain_render_range(
146 size_t index,
147 uint32_t vertex_offset,
148 uint32_t vertex_count,
149 const std::optional<Kakshya::VertexLayout>& layout);
150
151 //-------------------------------------------------------------------------
152 // Auxiliary state
153 //-------------------------------------------------------------------------
154
155 /**
156 * @brief Declare a named auxiliary state field alongside the vertex data.
157 * @param name Lookup key, unique within this buffer.
158 * @param element_count Number of elements the field holds. Fixed at
159 * declaration: it does not track later growth of the vertex
160 * buffer itself, since NetworkGeometryProcessor's resize path
161 * (1.5x headroom on overflow) has no knowledge of declared state
162 * fields. Declare at the network's expected particle count.
163 * @param stride_bytes Bytes per element.
164 * @param double_buffered False resolves read and write to one slot,
165 * which suits a value recomputed from other state each cycle.
166 * @return True if the field was registered.
167 *
168 * Mirrors VolumeGridBuffer::allocate_field: storage lives as raw handle
169 * pairs in the base VKBuffer's back_buffers, addressed by name rather
170 * than wrapped in a VKBuffer of its own. A processor resolves the
171 * handle it needs through read_state_handle/write_state_handle and
172 * writes its own descriptor directly against ShaderFoundry, the way
173 * VolumeFieldProcessor does for volume fields, rather than going
174 * through bind_buffer or the buffer's shared pipeline_context.
175 */
176 bool declare_state(
177 const std::string& name,
178 size_t element_count,
179 size_t stride_bytes,
180 bool double_buffered = true);
181
182 /** @brief Whether a state field of this name was declared. */
183 [[nodiscard]] bool has_state(const std::string& name) const;
184
185 /** @brief Byte size of one slot of the named state field, or 0 if undeclared. */
186 [[nodiscard]] size_t get_state_bytes(const std::string& name) const;
187
188 /**
189 * @brief Handle a stage should read the named state field from.
190 * @return Vulkan buffer handle, or nullptr if undeclared.
191 */
192 [[nodiscard]] vk::Buffer read_state_handle(const std::string& name) const;
193
194 /**
195 * @brief Full back_buffers slot a stage should read the named state
196 * field from.
197 * @return The slot, or a default-constructed (null-handle) slot if
198 * undeclared.
199 *
200 * Same resolution as read_state_handle, without narrowing to just the
201 * Vulkan handle: a CPU-side readback through StagingUtils::download_back_buffer
202 * needs the whole slot (it checks mapped_ptr for the host-visible fast
203 * path), not only .buffer. Returned by value since GenerationSlot is
204 * three handles wide.
205 */
206 [[nodiscard]] VKBufferResources::GenerationSlot read_state_slot(const std::string& name) const;
207
208 /**
209 * @brief Handle a stage should write the named state field to.
210 * @return Vulkan buffer handle, or nullptr if undeclared. Equals
211 * read_state_handle() for single-slot fields.
212 */
213 [[nodiscard]] vk::Buffer write_state_handle(const std::string& name) const;
214
215 /**
216 * @brief Full back_buffers slot a stage should write the named state
217 * field to.
218 * @return The slot, or a default-constructed (null-handle) slot if
219 * undeclared. Equals read_state_slot() for single-slot fields.
220 *
221 * Same resolution as write_state_handle, without narrowing to just the
222 * Vulkan handle: a CPU-side upload through StagingUtils::upload_back_buffer
223 * needs the whole slot (it checks mapped_ptr for the host-visible fast
224 * path), not only .buffer.
225 */
226 [[nodiscard]] VKBufferResources::GenerationSlot write_state_slot(const std::string& name) const;
227
228 /**
229 * @brief Exchange read and write slots for the named state field.
230 * @param name Field name. No effect on single-slot fields.
231 *
232 * Called by whichever stage last wrote the field, after its dispatch,
233 * so the next stage reading it observes the new values.
234 */
235 void swap_state(const std::string& name);
236
237 /**
238 * @brief Ensure the hash_cluster_id state field exists, declaring and
239 * populating it from the network's own primary GraphicsOperator
240 * if nothing has already done so.
241 * @return True if hash_cluster_id exists by the time this returns
242 * (already did, or was just created); false if there is no
243 * primary GraphicsOperator to size it from, or it reports zero
244 * vertices.
245 *
246 * Sized to get_vertex_count(), not get_point_count(): a GPU consumer
247 * indexes this field in lockstep with the vertex buffer itself, and for
248 * an operator whose rendered vertex count differs from its source point
249 * count (TopologyOperator/PathOperator after interpolation) those are
250 * two different numbers. Coincide for PhysicsOperator's point-sprite
251 * geometry, so this changes nothing for the particle path.
252 *
253 * has_state-guarded, so whichever caller reaches this first (a
254 * this buffer's own field-operator wiring, or a VertexFieldProcessor
255 * attaching with a cluster-scoped GpuFieldOperator binding) does the
256 * real work and the other finds it already done. Values come from
257 * GraphicsOperator::build_cluster_ids(): every entry 0 for any operator
258 * that has never overridden it, which is every one except PhysicsOperator
259 * today, so calling this against a PointCloudNetwork or plain
260 * FieldOperator-driven network is safe and simply declares a field
261 * whose only value is 0.
262 */
263 bool ensure_cluster_ids();
264
265protected:
266 std::shared_ptr<Nodes::Network::NodeNetwork> m_network;
267 std::shared_ptr<NetworkGeometryProcessor> m_processor;
268 std::string m_binding_name;
269
270private:
272 std::shared_ptr<RenderProcessor> render_processor;
273 uint32_t vertex_offset {};
274 uint32_t vertex_count {};
275 };
276 std::vector<ChainRenderEntry> m_chain_render_processors;
277
278 struct StateField {
281 uint32_t slot_a;
282 uint32_t slot_b;
284 };
285 std::unordered_map<std::string, StateField> m_state_fields;
286
287 /**
288 * @brief Resolve a declared state field by name.
289 * @param context Caller identifier used in the error path.
290 * @return Pointer to the field, or nullptr with an error logged.
291 */
292 [[nodiscard]] const StateField* find_state_field(const std::string& name, const char* context) const;
293
294 /**
295 * @brief Calculate initial buffer size based on network node count
296 */
297 static size_t calculate_buffer_size(
298 const std::shared_ptr<Nodes::Network::NodeNetwork>& network,
299 float over_allocate_factor);
300
301 /**
302 * @brief Find the first GpuFieldOperator in the network's operator chain
303 * and wire its compute stages: a VertexFieldProcessor when it
304 * carries bindings, plus whichever spatial-hash / claim / density /
305 * population stages its SpatialFieldConfig calls for.
306 *
307 * No-op unless the network is one of the field-compatible types
308 * (ParticleNetwork, PointCloudNetwork); mesh and instance buffers never
309 * reach the scan. Called at the end of setup_processors, so a scene that
310 * puts a GpuFieldOperator in the chain needs no hand-wired
311 * VertexFieldProcessor and must not add one itself. Implemented in
312 * NetworkGeometryFieldWiring.cpp to keep the compute-processor headers out
313 * of the base translation unit.
314 */
315 void wire_field_operators();
316};
317
318} // namespace MayaFlux::Buffers
Core::GlobalNetworkConfig network
Definition Config.cpp:39
std::string name
Definition VKDevice.cpp:143
uint32_t index
Definition VKDevice.cpp:142
std::vector< ChainRenderEntry > m_chain_render_processors
const std::string & get_binding_name() const
Get the logical binding name.
std::shared_ptr< NetworkGeometryProcessor > m_processor
std::shared_ptr< Nodes::Network::NodeNetwork > m_network
std::shared_ptr< Nodes::Network::NodeNetwork > get_network() const
Get the network driving this buffer.
std::unordered_map< std::string, StateField > m_state_fields
std::shared_ptr< NetworkGeometryProcessor > get_processor() const
Get the processor managing uploads.
void update_network(unsigned int num_samples=1)
Trigger network processing.
Specialized buffer for geometry from NodeNetwork instances.
Vulkan-backed buffer wrapper used in processing chains.
Definition VKBuffer.hpp:76
ProcessingToken
Bitfield enum defining processing characteristics and backend requirements for buffer operations.