MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
NetworkGeometryBuffer.cpp
Go to the documentation of this file.
2
4
9
12
14
15namespace MayaFlux::Buffers {
16
17namespace {
18 /**
19 * @brief Fill in the shaders a config implies.
20 *
21 * Triangulating is orthogonal to topology, not a line-specific
22 * workaround: any span mills to the same TRIANGLE_LIST pipeline through
23 * milled_shape.vert/frag (see mill_shape.glsl), resolved first and
24 * unconditionally -- config.topology is span ordering under triangulate,
25 * not a pipeline selector.
26 *
27 * macOS forces it on for LINE_LIST/LINE_STRIP only, since that platform
28 * has no geometry shader for line.geom's NDC expansion. Everything else
29 * triangulates only when asked.
30 *
31 * Otherwise, point.vert/point.frag and line.vert/line.frag/line.geom
32 * stay the per-topology defaults: their built-ins
33 * (gl_PointSize/gl_PointCoord, `layout(lines) in`) only make sense for
34 * that native topology.
35 */
36 Portal::Graphics::RenderConfig resolve_config(const Portal::Graphics::RenderConfig& config)
37 {
38 VKBuffer::RenderConfig resolved_config = config;
39
40#ifdef MAYAFLUX_PLATFORM_MACOS
43 resolved_config.triangulate = true;
44 }
45#endif
46
47 if (resolved_config.triangulate) {
48 if (config.vertex_shader.empty())
49 resolved_config.vertex_shader = "milled_shape.vert.spv";
50 if (config.fragment_shader.empty())
51 resolved_config.fragment_shader = "milled_shape.frag.spv";
52 resolved_config.geometry_shader.clear();
53 return resolved_config;
54 }
55
56 switch (config.topology) {
58 if (config.vertex_shader.empty())
59 resolved_config.vertex_shader = "point.vert.spv";
60 if (config.fragment_shader.empty())
61 resolved_config.fragment_shader = "point.frag.spv";
62 break;
65 if (config.vertex_shader.empty())
66 resolved_config.vertex_shader = "line.vert.spv";
67
68 if (config.fragment_shader.empty())
69 resolved_config.fragment_shader = "line.frag.spv";
70
71 if (config.geometry_shader.empty())
72 resolved_config.geometry_shader = "line.geom.spv";
73 break;
76 if (config.vertex_shader.empty())
77 resolved_config.vertex_shader = "triangle.vert.spv";
78 if (config.fragment_shader.empty())
79 resolved_config.fragment_shader = "triangle.frag.spv";
80 break;
81 default:
82 if (config.vertex_shader.empty())
83 resolved_config.vertex_shader = "point.vert.spv";
84 if (config.fragment_shader.empty())
85 resolved_config.fragment_shader = "point.frag.spv";
86 }
87
88 return resolved_config;
89 }
90
91}
92
94 std::shared_ptr<Nodes::Network::NodeNetwork> network,
95 const std::string& binding_name,
96 float over_allocate_factor)
97 : VKBuffer(
98 calculate_buffer_size(network, over_allocate_factor),
99 Usage::VERTEX,
100 Kakshya::DataModality::VERTEX_POSITIONS_3D)
101 , m_network(std::move(network))
102 , m_binding_name(binding_name)
103{
104 if (!m_network) {
105 error<std::invalid_argument>(
108 std::source_location::current(),
109 "Cannot create NetworkGeometryBuffer with null NodeNetwork");
110 }
111
113 "Created NetworkGeometryBuffer '{}' for {} nodes ({} bytes estimated)",
115 m_network->get_node_count(),
117}
118
120{
121 auto self = std::dynamic_pointer_cast<NetworkGeometryBuffer>(shared_from_this());
122
123 m_processor = std::make_shared<NetworkGeometryProcessor>();
124 m_processor->set_processing_token(token);
125 m_processor->bind_network(
127 m_network,
128 self);
129
131
133 if (!chain) {
134 chain = std::make_shared<BufferProcessingChain>();
136 }
137 chain->set_preferred_token(token);
138
140 "Setup NetworkGeometryProcessor for '{}' with token {}",
141 m_binding_name, static_cast<int>(token));
142
144}
145
147{
148 auto resolved_config = resolve_config(config);
149
150 apply_render_config(resolved_config, ShaderConfig { resolved_config.vertex_shader });
151
152 get_processing_chain()->add_processor(m_render_processor, shared_from_this());
153
154 set_default_render_config(resolved_config);
155}
156
158{
159 if (!m_network) {
160 return 0;
161 }
162
163 auto* operator_ptr = m_network->get_operator();
164 if (operator_ptr) {
165 auto* graphics_op = dynamic_cast<Nodes::Network::GraphicsOperator*>(operator_ptr);
166 if (graphics_op) {
167 return static_cast<uint32_t>(graphics_op->get_vertex_count());
168 }
169 }
170
171 return static_cast<uint32_t>(m_network->get_node_count());
172}
173
175 const std::shared_ptr<Nodes::Network::NodeNetwork>& network,
176 float over_allocate_factor)
177{
178 if (!network) {
179 return 0;
180 }
181
182 size_t node_count = network->get_node_count();
183 if (node_count == 0) {
185 "NodeNetwork has zero nodes. Buffer will be created with minimum size.");
186 return 4096;
187 }
188
189 size_t base_size = 0;
190
191 if (auto* operator_ptr = network->get_operator()) {
192 if (auto graphics_op = dynamic_cast<Nodes::Network::GraphicsOperator*>(operator_ptr)) {
193 size_t vertex_count = graphics_op->get_vertex_count();
194 auto layout = graphics_op->get_vertex_layout();
195
196 if (vertex_count > 0 && layout.stride_bytes > 0) {
197 base_size = vertex_count * layout.stride_bytes;
198
200 "Network geometry buffer sizing: {} vertices × {} bytes = {} bytes (operator: {})",
201 vertex_count, layout.stride_bytes, base_size, operator_ptr->get_type_name());
202 }
203 }
204 }
205
206 if (base_size == 0) {
207 size_t vertex_size = sizeof(Kakshya::PointVertex);
208 base_size = node_count * vertex_size;
209
211 "Network geometry buffer fallback sizing: {} nodes × {} bytes = {} bytes",
212 node_count, vertex_size, base_size);
213 }
214
215 auto allocated_size = static_cast<size_t>(
216 static_cast<float>(base_size) * over_allocate_factor);
217
218 if (over_allocate_factor > 1.0F) {
220 "Over-allocated by {}x: {} → {} bytes",
221 over_allocate_factor, base_size, allocated_size);
222 }
223
224 return allocated_size;
225}
226
228{
229 const auto resolved_config = resolve_config(config);
230 auto self = std::dynamic_pointer_cast<VKBuffer>(shared_from_this());
231
232 std::shared_ptr<RenderProcessor> render;
233 apply_render_config(render, resolved_config, ShaderConfig { resolved_config.vertex_shader });
234
235 get_processing_chain()->add_processor(render, shared_from_this());
236 render->set_buffer_vertex_layout(self, Kakshya::VertexLayout::for_lines());
237 render->set_vertex_range(0, 0);
238
239 m_chain_render_processors.push_back({ .render_processor = render });
240
242 "Added chain render processor #{} to NetworkGeometryBuffer",
244}
245
246std::shared_ptr<RenderProcessor> NetworkGeometryBuffer::get_chain_render_processor(size_t index) const
247{
248 if (index >= m_chain_render_processors.size())
249 return nullptr;
250 return m_chain_render_processors[index].render_processor;
251}
252
254 size_t index,
255 uint32_t vertex_offset,
256 uint32_t vertex_count,
257 const std::optional<Kakshya::VertexLayout>& layout,
258 std::vector<Portal::Graphics::DrawRun> runs)
259{
260 if (index == 0) {
261 if (m_render_processor) {
262 m_render_processor->set_vertex_range(vertex_offset, vertex_count);
263 m_render_processor->set_runs(std::move(runs));
264 }
265 return;
266 }
267
268 const size_t ci = index - 1;
269 if (ci >= m_chain_render_processors.size())
270 return;
271
272 auto& entry = m_chain_render_processors[ci];
273 entry.vertex_offset = vertex_offset;
274 entry.vertex_count = vertex_count;
275 entry.render_processor->set_vertex_range(vertex_offset, vertex_count);
276 entry.render_processor->set_runs(std::move(runs));
277
278 if (layout) {
279 auto self = std::dynamic_pointer_cast<VKBuffer>(shared_from_this());
280 entry.render_processor->set_buffer_vertex_layout(self, *layout);
281 }
282}
283
284void NetworkGeometryBuffer::update_render_runs(std::vector<Portal::Graphics::DrawRun> runs)
285{
286 if (m_render_processor) {
287 m_render_processor->set_runs(std::move(runs));
288 }
289}
290
291//-----------------------------------------------------------------------------
292// Auxiliary state
293//-----------------------------------------------------------------------------
294
296 const std::string& name,
297 size_t element_count,
298 size_t stride_bytes,
299 bool double_buffered)
300{
301 if (name.empty()) {
303 "NetworkGeometryBuffer: state field declared with empty name, skipped");
304 return false;
305 }
306
307 if (element_count == 0 || stride_bytes == 0) {
309 "NetworkGeometryBuffer: state field '{}' declares zero element count "
310 "or stride, skipped",
311 name);
312 return false;
313 }
314
315 if (m_state_fields.contains(name)) {
317 "NetworkGeometryBuffer: duplicate state field '{}', later declaration "
318 "discarded",
319 name);
320 return false;
321 }
322
323 auto buffer_service = Registry::BackendRegistry::instance()
325
326 if (!buffer_service || !buffer_service->allocate_raw_buffer) {
327 error<std::runtime_error>(
330 std::source_location::current(),
331 "NetworkGeometryBuffer requires a valid buffer service");
332 }
333
334 const auto usage_flags = static_cast<uint32_t>(
335 static_cast<VkBufferUsageFlags>(
336 vk::BufferUsageFlagBits::eStorageBuffer
337 | vk::BufferUsageFlagBits::eTransferSrc
338 | vk::BufferUsageFlagBits::eTransferDst));
339
340 const auto memory_flags = static_cast<uint32_t>(
341 static_cast<VkMemoryPropertyFlags>(vk::MemoryPropertyFlagBits::eDeviceLocal));
342
343 auto& resources = get_buffer_resources();
344 const size_t field_bytes = element_count * stride_bytes;
345 const uint32_t slot_count = double_buffered ? 2 : 1;
346
347 StateField field {
348 .element_count = element_count,
349 .stride_bytes = stride_bytes,
350 .slot_a = static_cast<uint32_t>(resources.back_buffers.size()),
351 .slot_b = 0,
352 .read_is_a = true,
353 };
354
355 for (uint32_t i = 0; i < slot_count; ++i) {
356 void* out_buffer = nullptr;
357 void* out_memory = nullptr;
358 void* out_mapped = nullptr;
359
360 buffer_service->allocate_raw_buffer(
361 field_bytes, usage_flags, memory_flags, false,
362 out_buffer, out_memory, out_mapped);
363
365 slot.buffer = static_cast<vk::Buffer>(static_cast<VkBuffer>(out_buffer));
366 slot.memory = static_cast<vk::DeviceMemory>(static_cast<VkDeviceMemory>(out_memory));
367 slot.mapped_ptr = out_mapped;
368
369 resources.back_buffers.push_back(slot);
370 }
371
372 field.slot_b = double_buffered ? field.slot_a + 1 : field.slot_a;
373
374 m_state_fields.emplace(name, field);
375
377 "NetworkGeometryBuffer: state field '{}', {} elements, stride {}, {} slot(s), {} bytes",
378 name, element_count, stride_bytes, slot_count, field_bytes * slot_count);
379
380 return true;
381}
382
384 const std::string& name, const char* context) const
385{
386 auto it = m_state_fields.find(name);
387 if (it == m_state_fields.end()) {
389 "NetworkGeometryBuffer::{}: no state field named '{}'", context, name);
390 return nullptr;
391 }
392 return &it->second;
393}
394
395bool NetworkGeometryBuffer::has_state(const std::string& name) const
396{
397 return m_state_fields.contains(name);
398}
399
400size_t NetworkGeometryBuffer::get_state_bytes(const std::string& name) const
401{
402 auto it = m_state_fields.find(name);
403 if (it == m_state_fields.end()) {
404 return 0;
405 }
406 return it->second.element_count * it->second.stride_bytes;
407}
408
409vk::Buffer NetworkGeometryBuffer::read_state_handle(const std::string& name) const
410{
411 const auto* field = find_state_field(name, "read_state_handle");
412 if (!field) {
413 return nullptr;
414 }
415
416 const uint32_t slot = field->read_is_a ? field->slot_a : field->slot_b;
417 return get_buffer_resources().back_buffers[slot].buffer;
418}
419
421{
422 const auto* field = find_state_field(name, "read_state_slot");
423 if (!field) {
424 return {};
425 }
426
427 const uint32_t slot = field->read_is_a ? field->slot_a : field->slot_b;
428 return get_buffer_resources().back_buffers[slot];
429}
430
431vk::Buffer NetworkGeometryBuffer::write_state_handle(const std::string& name) const
432{
433 const auto* field = find_state_field(name, "write_state_handle");
434 if (!field) {
435 return nullptr;
436 }
437
438 const uint32_t slot = field->read_is_a ? field->slot_b : field->slot_a;
439 return get_buffer_resources().back_buffers[slot].buffer;
440}
441
443{
444 const auto* field = find_state_field(name, "write_state_slot");
445 if (!field) {
446 return {};
447 }
448
449 const uint32_t slot = field->read_is_a ? field->slot_b : field->slot_a;
450 return get_buffer_resources().back_buffers[slot];
451}
452
454{
455 auto it = m_state_fields.find(name);
456 if (it == m_state_fields.end()) {
458 "NetworkGeometryBuffer::swap_state: no state field named '{}'", name);
459 return;
460 }
461
462 if (it->second.slot_a == it->second.slot_b) {
463 return;
464 }
465
466 it->second.read_is_a = !it->second.read_is_a;
467}
468
470{
471 if (has_state("hash_cluster_id")) {
472 return true;
473 }
474
475 auto* graphics_op = m_network
476 ? dynamic_cast<Nodes::Network::GraphicsOperator*>(m_network->get_operator())
477 : nullptr;
478 if (!graphics_op) {
479 return false;
480 }
481
482 const size_t vertex_count = graphics_op->get_vertex_count();
483 if (vertex_count == 0) {
484 return false;
485 }
486
487 if (!declare_state("hash_cluster_id", vertex_count, sizeof(uint32_t), false)) {
488 return false;
489 }
490
491 auto cluster_ids = graphics_op->build_cluster_ids();
492 if (cluster_ids.size() != vertex_count) {
493 cluster_ids.assign(vertex_count, 0U);
494 }
495
496 std::shared_ptr<VKBuffer> staging;
498 write_state_slot("hash_cluster_id"),
499 cluster_ids.data(),
500 cluster_ids.size() * sizeof(uint32_t),
501 staging);
502
503 return true;
504}
505
506} // namespace MayaFlux::Buffers
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
Core::GlobalNetworkConfig network
Definition Config.cpp:43
BufferProcessingChain * chain
std::string name
Definition VKDevice.cpp:123
uint32_t index
Definition VKDevice.cpp:122
std::vector< ChainRenderEntry > m_chain_render_processors
void update_chain_render_range(size_t index, uint32_t vertex_offset, uint32_t vertex_count, const std::optional< Kakshya::VertexLayout > &layout, std::vector< Portal::Graphics::DrawRun > runs={})
Update vertex range for a specific operator chain index.
NetworkGeometryBuffer(std::shared_ptr< Nodes::Network::NodeNetwork > network, const std::string &binding_name="network_geometry", float over_allocate_factor=2.0F)
Create geometry buffer from network.
std::shared_ptr< NetworkGeometryProcessor > m_processor
void add_chain_operator_rendering(const RenderConfig &config)
Add a RenderProcessor for a specific operator chain index.
vk::Buffer write_state_handle(const std::string &name) const
Handle a stage should write the named state field to.
size_t get_state_bytes(const std::string &name) const
Byte size of one slot of the named state field, or 0 if undeclared.
uint32_t get_vertex_count() const
Get current vertex count (aggregated from all network nodes)
bool declare_state(const std::string &name, size_t element_count, size_t stride_bytes, bool double_buffered=true)
Declare a named auxiliary state field alongside the vertex data.
std::shared_ptr< Nodes::Network::NodeNetwork > m_network
VKBufferResources::GenerationSlot read_state_slot(const std::string &name) const
Full back_buffers slot a stage should read the named state field from.
const StateField * find_state_field(const std::string &name, const char *context) const
Resolve a declared state field by name.
bool has_state(const std::string &name) const
Whether a state field of this name was declared.
vk::Buffer read_state_handle(const std::string &name) const
Handle a stage should read the named state field from.
void setup_rendering(const RenderConfig &config)
Setup rendering with RenderProcessor.
VKBufferResources::GenerationSlot write_state_slot(const std::string &name) const
Full back_buffers slot a stage should write the named state field to.
void swap_state(const std::string &name)
Exchange read and write slots for the named state field.
std::unordered_map< std::string, StateField > m_state_fields
void wire_field_operators()
Find the first GpuFieldOperator in the network's operator chain and wire its compute stages: a Vertex...
void update_render_runs(std::vector< Portal::Graphics::DrawRun > runs)
Hand the primary processor its spans, without touching its range.
std::shared_ptr< RenderProcessor > get_chain_render_processor(size_t index) const
Get RenderProcessor for a specific operator chain index.
static size_t calculate_buffer_size(const std::shared_ptr< Nodes::Network::NodeNetwork > &network, float over_allocate_factor)
Calculate initial buffer size based on network node count.
void setup_processors(ProcessingToken token) override
Initialize the buffer and its processors.
bool ensure_cluster_ids()
Ensure the hash_cluster_id state field exists, declaring and populating it from the network's own pri...
std::shared_ptr< Buffers::BufferProcessingChain > get_processing_chain() override
Access the buffer's processing chain.
Definition VKBuffer.cpp:271
void set_default_processor(const std::shared_ptr< BufferProcessor > &processor) override
Set the buffer's default processor.
Definition VKBuffer.cpp:255
Portal::Graphics::RenderConfig RenderConfig
Definition VKBuffer.hpp:78
vk::DeviceSize get_size_bytes() const
Definition VKBuffer.hpp:280
void set_processing_chain(const std::shared_ptr< BufferProcessingChain > &chain, bool force=false) override
Replace the buffer's processing chain.
Definition VKBuffer.cpp:276
void apply_render_config(const RenderConfig &config, const ShaderConfig &shader_config)
Configure the internal m_render_processor from a RenderConfig.
Definition VKBuffer.cpp:344
std::shared_ptr< RenderProcessor > m_render_processor
Definition VKBuffer.hpp:654
void set_default_render_config(const RenderConfig &config)
Called by derived classes to set their context-specific defaults.
Definition VKBuffer.hpp:631
const VKBufferResources & get_buffer_resources() const
Get all buffer resources at once (read-only)
Definition VKBuffer.hpp:343
Vulkan-backed buffer wrapper used in processing chains.
Definition VKBuffer.hpp:76
Operator that produces GPU-renderable geometry.
Interface * get_service()
Query for a backend service.
static BackendRegistry & instance()
Get the global registry instance.
ProcessingToken
Bitfield enum defining processing characteristics and backend requirements for buffer operations.
void upload_back_buffer(const VKBufferResources::GenerationSlot &slot, const void *data, size_t size, std::shared_ptr< VKBuffer > &staging)
Upload host memory into a raw back_buffers slot.
@ BufferManagement
Buffer Management (Buffers::BufferManager, creating buffers)
@ Init
Engine/subsystem initialization.
@ Buffers
Buffers, Managers, processors and processing chains.
BufferUsageHint
Semantic usage hint for buffer allocation and memory properties.
std::vector< GenerationSlot > back_buffers
Definition VKBuffer.hpp:48
Vertex type for point primitives (POINT_LIST topology)
static VertexLayout for_lines(uint32_t stride=60)
Factory: layout for LineVertex (position, color, thickness, uv, normal, tangent)
bool triangulate
Reduce supplied spans to triangles before drawing them.
Unified rendering configuration for graphics buffers.
Backend buffer management service interface.