MayaFlux 1.0.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 * Registers in the main process phase, after everything setup_processors
116 * wired, since the chain runs that phase in insertion order and
117 * setup_processors must precede this call for the chain to exist at all.
118 * Ordering matters more here than for a plain draw: a triangulating
119 * processor mills the vertex buffer into a copy at process time rather than
120 * recording a binding read at present time, so every pass that writes those
121 * vertices has to come first.
122 */
123 void setup_rendering(const RenderConfig& config);
124
125 /**
126 * @brief Add a RenderProcessor for a specific operator chain index
127 * @param config Rendering configuration
128 *
129 * This allows rendering different subsets of the network geometry with different pipelines.
130 * Each chain index corresponds to a specific node/operator in the network.
131 *
132 * Each processor added here gets its own spans through
133 * update_chain_render_range, so one that triangulates mills only its own
134 * operator's slice.
135 */
136 void add_chain_operator_rendering(const RenderConfig& config);
137
138 /**
139 * @brief Get RenderProcessor for a specific operator chain index
140 * @param index Operator chain index
141 * @return Optional containing RenderProcessor if exists
142 *
143 * Each chain index corresponds to a specific node/operator in the network.
144 */
145 [[nodiscard]] std::shared_ptr<RenderProcessor> get_chain_render_processor(size_t index) const;
146
147 /**
148 * @brief Update vertex range for a specific operator chain index
149 * @param index Operator chain index
150 * @param vertex_offset Starting vertex offset for this chain
151 * @param vertex_count Number of vertices for this chain
152 * @param layout Optional vertex layout for this chain (if different from primary)
153 * @param runs This operator's spans, in this buffer's vertex coordinates.
154 * Consumed only by a triangulating processor; empty leaves it to
155 * treat its whole range as one span.
156 *
157 * This allows the processor to push per-chain vertex ranges to the RenderProcessor,
158 * enabling it to issue draw calls for specific subsets of the geometry.
159 *
160 * Offsets in @p runs must already be rebased off this operator's slice base:
161 * GraphicsOperator::topology_runs() reports operator-local offsets, and only
162 * whoever concatenated the slices knows where each one landed.
163 */
164 void update_chain_render_range(
165 size_t index,
166 uint32_t vertex_offset,
167 uint32_t vertex_count,
168 const std::optional<Kakshya::VertexLayout>& layout,
169 std::vector<Portal::Graphics::DrawRun> runs = {});
170
171 /**
172 * @brief Hand the primary processor its spans, without touching its range.
173 * @param runs Spans in this buffer's vertex coordinates.
174 *
175 * For a network with no operator chain, where one producer owns the whole
176 * buffer and there is no slice table to rebase against. Inert on a processor
177 * that does not triangulate.
178 */
179 void update_render_runs(std::vector<Portal::Graphics::DrawRun> runs);
180
181 //-------------------------------------------------------------------------
182 // Auxiliary state
183 //-------------------------------------------------------------------------
184
185 /**
186 * @brief Declare a named auxiliary state field alongside the vertex data.
187 * @param name Lookup key, unique within this buffer.
188 * @param element_count Number of elements the field holds. Fixed at
189 * declaration: it does not track later growth of the vertex
190 * buffer itself, since NetworkGeometryProcessor's resize path
191 * (1.5x headroom on overflow) has no knowledge of declared state
192 * fields. Declare at the network's expected particle count.
193 * @param stride_bytes Bytes per element.
194 * @param double_buffered False resolves read and write to one slot,
195 * which suits a value recomputed from other state each cycle.
196 * @return True if the field was registered.
197 *
198 * Mirrors VolumeGridBuffer::allocate_field: storage lives as raw handle
199 * pairs in the base VKBuffer's back_buffers, addressed by name rather
200 * than wrapped in a VKBuffer of its own. A processor resolves the
201 * handle it needs through read_state_handle/write_state_handle and
202 * writes its own descriptor directly against ShaderFoundry, the way
203 * VolumeFieldProcessor does for volume fields, rather than going
204 * through bind_buffer or the buffer's shared pipeline_context.
205 */
206 bool declare_state(
207 const std::string& name,
208 size_t element_count,
209 size_t stride_bytes,
210 bool double_buffered = true);
211
212 /** @brief Whether a state field of this name was declared. */
213 [[nodiscard]] bool has_state(const std::string& name) const;
214
215 /** @brief Byte size of one slot of the named state field, or 0 if undeclared. */
216 [[nodiscard]] size_t get_state_bytes(const std::string& name) const;
217
218 /**
219 * @brief Handle a stage should read the named state field from.
220 * @return Vulkan buffer handle, or nullptr if undeclared.
221 */
222 [[nodiscard]] vk::Buffer read_state_handle(const std::string& name) const;
223
224 /**
225 * @brief Full back_buffers slot a stage should read the named state
226 * field from.
227 * @return The slot, or a default-constructed (null-handle) slot if
228 * undeclared.
229 *
230 * Same resolution as read_state_handle, without narrowing to just the
231 * Vulkan handle: a CPU-side readback through StagingUtils::download_back_buffer
232 * needs the whole slot (it checks mapped_ptr for the host-visible fast
233 * path), not only .buffer. Returned by value since GenerationSlot is
234 * three handles wide.
235 */
236 [[nodiscard]] VKBufferResources::GenerationSlot read_state_slot(const std::string& name) const;
237
238 /**
239 * @brief Handle a stage should write the named state field to.
240 * @return Vulkan buffer handle, or nullptr if undeclared. Equals
241 * read_state_handle() for single-slot fields.
242 */
243 [[nodiscard]] vk::Buffer write_state_handle(const std::string& name) const;
244
245 /**
246 * @brief Full back_buffers slot a stage should write the named state
247 * field to.
248 * @return The slot, or a default-constructed (null-handle) slot if
249 * undeclared. Equals read_state_slot() for single-slot fields.
250 *
251 * Same resolution as write_state_handle, without narrowing to just the
252 * Vulkan handle: a CPU-side upload through StagingUtils::upload_back_buffer
253 * needs the whole slot (it checks mapped_ptr for the host-visible fast
254 * path), not only .buffer.
255 */
256 [[nodiscard]] VKBufferResources::GenerationSlot write_state_slot(const std::string& name) const;
257
258 /**
259 * @brief Exchange read and write slots for the named state field.
260 * @param name Field name. No effect on single-slot fields.
261 *
262 * Called by whichever stage last wrote the field, after its dispatch,
263 * so the next stage reading it observes the new values.
264 */
265 void swap_state(const std::string& name);
266
267 /**
268 * @brief Ensure the hash_cluster_id state field exists, declaring and
269 * populating it from the network's own primary GraphicsOperator
270 * if nothing has already done so.
271 * @return True if hash_cluster_id exists by the time this returns
272 * (already did, or was just created); false if there is no
273 * primary GraphicsOperator to size it from, or it reports zero
274 * vertices.
275 *
276 * Sized to get_vertex_count(), not get_point_count(): a GPU consumer
277 * indexes this field in lockstep with the vertex buffer itself, and for
278 * an operator whose rendered vertex count differs from its source point
279 * count (TopologyOperator/PathOperator after interpolation) those are
280 * two different numbers. Coincide for PhysicsOperator's point-sprite
281 * geometry, so this changes nothing for the particle path.
282 *
283 * has_state-guarded, so whichever caller reaches this first (a
284 * this buffer's own field-operator wiring, or a VertexFieldProcessor
285 * attaching with a cluster-scoped GpuFieldOperator binding) does the
286 * real work and the other finds it already done. Values come from
287 * GraphicsOperator::build_cluster_ids(): every entry 0 for any operator
288 * that has never overridden it, which is every one except PhysicsOperator
289 * today, so calling this against a PointCloudNetwork or plain
290 * FieldOperator-driven network is safe and simply declares a field
291 * whose only value is 0.
292 */
293 bool ensure_cluster_ids();
294
295protected:
296 std::shared_ptr<Nodes::Network::NodeNetwork> m_network;
297 std::shared_ptr<NetworkGeometryProcessor> m_processor;
298 std::string m_binding_name;
299
300private:
302 std::shared_ptr<RenderProcessor> render_processor;
303 uint32_t vertex_offset {};
304 uint32_t vertex_count {};
305 };
306 std::vector<ChainRenderEntry> m_chain_render_processors;
307
308 struct StateField {
311 uint32_t slot_a;
312 uint32_t slot_b;
314 };
315 std::unordered_map<std::string, StateField> m_state_fields;
316
317 /**
318 * @brief Resolve a declared state field by name.
319 * @param context Caller identifier used in the error path.
320 * @return Pointer to the field, or nullptr with an error logged.
321 */
322 [[nodiscard]] const StateField* find_state_field(const std::string& name, const char* context) const;
323
324 /**
325 * @brief Calculate initial buffer size based on network node count
326 */
327 static size_t calculate_buffer_size(
328 const std::shared_ptr<Nodes::Network::NodeNetwork>& network,
329 float over_allocate_factor);
330
331 /**
332 * @brief Find the first GpuFieldOperator in the network's operator chain
333 * and wire its compute stages: a VertexFieldProcessor when it
334 * carries bindings, plus whichever spatial-hash / claim / density /
335 * population stages its SpatialFieldConfig calls for.
336 *
337 * No-op unless the network is one of the field-compatible types
338 * (ParticleNetwork, PointCloudNetwork); mesh and instance buffers never
339 * reach the scan. Called at the end of setup_processors, so a scene that
340 * puts a GpuFieldOperator in the chain needs no hand-wired
341 * VertexFieldProcessor and must not add one itself. Implemented in
342 * NetworkGeometryFieldWiring.cpp to keep the compute-processor headers out
343 * of the base translation unit.
344 */
345 void wire_field_operators();
346};
347
348} // namespace MayaFlux::Buffers
Core::GlobalNetworkConfig network
Definition Config.cpp:43
std::string name
Definition VKDevice.cpp:123
uint32_t index
Definition VKDevice.cpp:122
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.