MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
MutationClaimProcessor.cpp
Go to the documentation of this file.
2
5
8
13
14namespace MayaFlux::Buffers {
15
16void MutationConfig::declare_fields(const std::shared_ptr<NetworkGeometryBuffer>& buffer) const
17{
18 buffer->declare_state("mutation_claimed_by", hash.particle_count, sizeof(uint32_t), false);
19 buffer->declare_state("mutation_swallow_count", hash.particle_count, sizeof(uint32_t), false);
20 buffer->declare_state("mutation_claim_events", 1, sizeof(uint32_t), false);
21 buffer->declare_state("mutation_accreted_mass", hash.particle_count, sizeof(float), false);
22}
23
24namespace {
25
29
30 /**
31 * @brief Resolve colour and size word offsets or fail loudly.
32 *
33 * A free function rather than inline in a member-initialiser list, the
34 * same shape HashDensityColorProcessor's require_color_offset uses: the
35 * constructor needs both before it can build m_params.
36 */
37 std::pair<uint32_t, uint32_t> require_color_and_size_offset(
38 const std::shared_ptr<Nodes::Network::GpuFieldOperator>& particle_op)
39 {
40 if (!particle_op) {
41 error<std::invalid_argument>(
44 std::source_location::current(),
45 "require_color_and_size_offset: null particle operator");
46 }
47
48 const auto& layout = particle_op->get_layout();
49
50 const auto color_offset = layout.find_word_offset(Kakshya::DataModality::VERTEX_COLORS_RGB);
51 const auto size_attr = std::ranges::find_if(layout.attributes,
52 [](const auto& attr) { return attr.name == "size"; });
53 const bool have_size = size_attr != layout.attributes.end() && size_attr->offset_in_vertex % 4 == 0;
54
55 if (!color_offset.has_value() || !have_size) {
56 error<std::invalid_argument>(
59 std::source_location::current(),
60 "require_color_and_size_offset: vertex layout is missing a colour or "
61 "word-aligned size attribute");
62 }
63
64 return { *color_offset, size_attr->offset_in_vertex / 4 };
65 }
66
67} // namespace
68
69//=============================================================================
70// ClaimInitProcessor
71//=============================================================================
72
73namespace {
74
75 ShaderSpec build_claim_init_spec()
76 {
77 ShaderSpec::Assemble assemble;
78 assemble
79 .ssbo("claimed_by", BindingDirection::Output, Kakshya::GpuDataFormat::UINT32)
80 .ssbo("swallow_count", BindingDirection::Output, Kakshya::GpuDataFormat::UINT32)
81 .ssbo("claim_events", BindingDirection::Output, Kakshya::GpuDataFormat::UINT32)
82 .pc("particle_count", Kakshya::GpuDataFormat::UINT32)
83 .workgroup(256);
84
85 std::string body;
86 body += " if (i >= particle_count) { return; }\n";
87 body += " claimed_by[i] = i;\n";
88 body += " swallow_count[i] = 0u;\n";
89 body += " if (i == 0u) { claim_events[0] = 0u; }\n";
90
91 assemble.kernel(KernelSource { .body = std::move(body) });
92
93 return assemble.build();
94 }
95
96} // namespace
97
100 { FieldBinding { .name = "claimed_by", .binding = 0, .field = "mutation_claimed_by" },
101 FieldBinding { .name = "swallow_count", .binding = 1, .field = "mutation_swallow_count" },
102 FieldBinding { .name = "claim_events", .binding = 2, .field = "mutation_claim_events" } },
103 build_claim_init_spec())
104 , m_params { .particle_count = config.hash.particle_count }
105{
106}
107
112
113//=============================================================================
114// ClaimProcessor
115//=============================================================================
116
117namespace {
118
119 ShaderSpec build_claim_spec(bool gate_alive)
120 {
121 ShaderSpec::Assemble assemble;
122 assemble
123 .ssbo("vertices", BindingDirection::Input, Kakshya::GpuDataFormat::FLOAT32)
124 .ssbo("cell_start", BindingDirection::Input, Kakshya::GpuDataFormat::UINT32)
125 .ssbo("cell_count", BindingDirection::Input, Kakshya::GpuDataFormat::UINT32)
126 .ssbo("particle_index", BindingDirection::Input, Kakshya::GpuDataFormat::UINT32)
127 .ssbo("claimed_by", BindingDirection::InOut, Kakshya::GpuDataFormat::UINT32)
128 .ssbo("claim_events", BindingDirection::InOut, Kakshya::GpuDataFormat::UINT32)
129 .ssbo("accreted_mass", BindingDirection::Input, Kakshya::GpuDataFormat::FLOAT32)
130 .ssbo("cluster_id", BindingDirection::Input, Kakshya::GpuDataFormat::UINT32);
131
132 if (gate_alive) {
133 assemble.ssbo("alive", BindingDirection::Input, Kakshya::GpuDataFormat::UINT32);
134 }
135
136 assemble
137 .pc("particle_count", Kakshya::GpuDataFormat::UINT32)
138 .pc("stride_words", Kakshya::GpuDataFormat::UINT32)
139 .pc("position_offset", Kakshya::GpuDataFormat::UINT32)
140 .pc("absorb_radius", Kakshya::GpuDataFormat::FLOAT32)
141 .pc("capture_growth", Kakshya::GpuDataFormat::FLOAT32)
142 .pc("grid_min_x", Kakshya::GpuDataFormat::FLOAT32)
143 .pc("grid_min_y", Kakshya::GpuDataFormat::FLOAT32)
144 .pc("grid_min_z", Kakshya::GpuDataFormat::FLOAT32)
145 .pc("cell_size", Kakshya::GpuDataFormat::FLOAT32)
149 .pc("cross_cluster", Kakshya::GpuDataFormat::UINT32)
150 .workgroup(256);
151
152 std::string body;
153 body += " if (i >= particle_count) { return; }\n";
154 if (gate_alive) {
155 body += " if (alive[i] == 0u) { return; }\n";
156 }
157 body += " uint b = i * stride_words;\n";
158 body += " vec3 p = vec3(vertices[b + position_offset], "
159 "vertices[b + position_offset + 1u], vertices[b + position_offset + 2u]);\n";
160 body += " float capture_radius = max(absorb_radius, "
161 "pow(max(accreted_mass[i], 0.0001), 1.0 / 3.0) * capture_growth);\n";
162 body += " uint my_cluster = cluster_id[i];\n";
163 body += "\n";
164 body += " int reach = clamp(int(ceil(capture_radius / cell_size)), 1, 8);\n";
166 .reach = "reach",
167 .skip_self = "j <= i",
168 .cluster_scoped = true,
169 .radius = "capture_radius",
170 .on_hit = "atomicMin(claimed_by[j], i);\natomicAdd(claim_events[0], 1u);",
171 });
172
173 assemble.kernel(KernelSource { .body = std::move(body) });
174
175 return assemble.build();
176 }
177
178 std::vector<NetworkStateFieldProcessor::FieldBinding> claim_bindings(bool gate_alive)
179 {
180 std::vector<NetworkStateFieldProcessor::FieldBinding> bindings {
181 { .name = "vertices", .binding = 0, .field = {} },
182 { .name = "cell_start", .binding = 1, .field = "hash_cell_start" },
183 { .name = "cell_count", .binding = 2, .field = "hash_cell_count" },
184 { .name = "particle_index", .binding = 3, .field = "hash_particle_index" },
185 { .name = "claimed_by", .binding = 4, .field = "mutation_claimed_by" },
186 { .name = "claim_events", .binding = 5, .field = "mutation_claim_events" },
187 { .name = "accreted_mass", .binding = 6, .field = "mutation_accreted_mass" },
188 { .name = "cluster_id", .binding = 7, .field = "hash_cluster_id" },
189 };
190 if (gate_alive) {
191 bindings.push_back({ .name = "alive", .binding = 8, .field = "mutation_alive" });
192 }
193 return bindings;
194 }
195
196} // namespace
197
199 const MutationConfig& config,
200 std::shared_ptr<Nodes::Network::GpuFieldOperator> particle_op,
201 bool gate_alive)
202 : NetworkStateFieldProcessor(claim_bindings(gate_alive), build_claim_spec(gate_alive))
203 , m_params {
204 .particle_count = config.hash.particle_count,
205 .stride_words = config.hash.stride_words,
206 .position_offset = config.hash.position_word_offset,
207 .absorb_radius = config.absorb_radius,
208 .capture_growth = particle_op->get_field_config().capture_growth,
209 .grid_min_x = config.hash.grid_min.x,
210 .grid_min_y = config.hash.grid_min.y,
211 .grid_min_z = config.hash.grid_min.z,
212 .cell_size = config.hash.cell_size,
213 .dim_x = config.hash.grid_dims.x,
214 .dim_y = config.hash.grid_dims.y,
215 .dim_z = config.hash.grid_dims.z,
216 .cross_cluster = particle_op->get_field_config().cross_cluster ? 1U : 0U,
217 }
218 , m_particle_op(std::move(particle_op))
219 , m_built_revision(m_particle_op->revision())
220{
221}
222
227
230 const std::shared_ptr<VKBuffer>& buffer)
231{
232 return guard_and_resync(cmd_id, buffer, m_particle_op->revision(), m_built_revision, [this] {
233 const auto& pconfig = m_particle_op->get_field_config();
234 m_params.capture_growth = pconfig.capture_growth;
235 m_params.cross_cluster = pconfig.cross_cluster ? 1U : 0U;
236 set_push_constant_data(m_params);
237 });
238}
239
240//=============================================================================
241// ClaimFlattenProcessor
242//=============================================================================
243
244namespace {
245
246 ShaderSpec build_claim_flatten_spec()
247 {
248 ShaderSpec::Assemble assemble;
249 assemble
250 .ssbo("claimed_by", BindingDirection::InOut, Kakshya::GpuDataFormat::UINT32)
251 .pc("particle_count", Kakshya::GpuDataFormat::UINT32)
252 .workgroup(256);
253
254 std::string body;
255 body += " if (i >= particle_count) { return; }\n";
256 body += " claimed_by[i] = claimed_by[claimed_by[i]];\n";
257
258 assemble.kernel(KernelSource { .body = std::move(body) });
259
260 return assemble.build();
261 }
262
263 /**
264 * @brief Smallest k such that 2^k >= n, floored at 1.
265 *
266 * Number of pointer-jumping rounds sufficient to flatten any chain up to
267 * length n: each round at most halves the distance from an element to
268 * its root, and no chain can exceed particle_count links.
269 */
270 uint32_t rounds_to_flatten(uint32_t n)
271 {
272 uint32_t k = 0;
273 while ((1U << k) < n) {
274 ++k;
275 }
276 return std::max(1U, k);
277 }
278
279} // namespace
280
283 { FieldBinding { .name = "claimed_by", .binding = 0, .field = "mutation_claimed_by" } },
284 build_claim_flatten_spec())
285 , m_params { .particle_count = config.hash.particle_count }
286{
287}
288
294
297 const std::shared_ptr<VKBuffer>& /*buffer*/,
298 uint32_t /*index*/)
299{
300 auto handle = get_network_buffer()->read_state_handle("mutation_claimed_by");
301
303 cmd_id, handle,
304 vk::AccessFlagBits::eShaderWrite,
305 vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite,
306 vk::PipelineStageFlagBits::eComputeShader,
307 vk::PipelineStageFlagBits::eComputeShader);
308}
309
310//=============================================================================
311// ClaimAccumulateProcessor
312//=============================================================================
313
314namespace {
315
316 ShaderSpec build_claim_accumulate_spec(bool gate_alive, bool transfer_on_claim)
317 {
318 ShaderSpec::Assemble assemble;
319 assemble
320 .ssbo("claimed_by", BindingDirection::Input, Kakshya::GpuDataFormat::UINT32)
321 .ssbo("swallow_count", BindingDirection::InOut, Kakshya::GpuDataFormat::UINT32);
322
323 if (gate_alive) {
324 assemble
325 .ssbo("alive", BindingDirection::InOut, Kakshya::GpuDataFormat::UINT32)
326 .ssbo("vertices", BindingDirection::InOut, Kakshya::GpuDataFormat::FLOAT32);
327 }
328
329 if (transfer_on_claim) {
330 assemble.ssbo("cluster_id", BindingDirection::Input, Kakshya::GpuDataFormat::UINT32);
331 }
332
333 assemble.pc("particle_count", Kakshya::GpuDataFormat::UINT32)
334 .pc("stride_words", Kakshya::GpuDataFormat::UINT32)
335 .pc("size_offset", Kakshya::GpuDataFormat::UINT32)
336 .workgroup(256);
337
338 std::string body;
339 body += " if (i >= particle_count) { return; }\n";
340 if (gate_alive) {
341 body += " if (alive[i] == 0u) {\n";
342 body += " vertices[i * stride_words + size_offset] = 0.0;\n";
343 body += " return;\n";
344 body += " }\n";
345 }
346 body += " uint root = claimed_by[i];\n";
347 body += " if (root != i) {\n";
348 if (transfer_on_claim) {
349 body += " if (cluster_id[root] != cluster_id[i]) { return; }\n";
350 }
351 body += " atomicAdd(swallow_count[root], 1u);\n";
352 if (gate_alive) {
353 body += " alive[i] = 0u;\n";
354 body += " vertices[i * stride_words + size_offset] = 0.0;\n";
355 }
356 body += " }\n";
357
358 assemble.kernel(KernelSource { .body = std::move(body) });
359
360 return assemble.build();
361 }
362
363 std::vector<NetworkStateFieldProcessor::FieldBinding> claim_accumulate_bindings(
364 bool gate_alive, bool transfer_on_claim)
365 {
366 std::vector<NetworkStateFieldProcessor::FieldBinding> bindings {
367 { .name = "claimed_by", .binding = 0, .field = "mutation_claimed_by" },
368 { .name = "swallow_count", .binding = 1, .field = "mutation_swallow_count" },
369 };
370 uint32_t next = 2;
371 if (gate_alive) {
372 bindings.push_back({ .name = "alive", .binding = next++, .field = "mutation_alive" });
373 bindings.push_back({ .name = "vertices", .binding = next++, .field = {} });
374 }
375 if (transfer_on_claim) {
376 bindings.push_back({ .name = "cluster_id", .binding = next++, .field = "hash_cluster_id" });
377 }
378 return bindings;
379 }
380
381} // namespace
382
384 const MutationConfig& config,
386 uint32_t live_count,
387 const std::shared_ptr<Nodes::Network::GpuFieldOperator>& particle_op,
388 bool transfer_on_claim)
390 claim_accumulate_bindings(live_count != 0, transfer_on_claim),
391 build_claim_accumulate_spec(live_count != 0, transfer_on_claim))
392 , m_params {
393 .particle_count = config.hash.particle_count,
394 .stride_words = config.hash.stride_words,
395 .size_offset = live_count != 0 ? require_color_and_size_offset(particle_op).second : 0U,
396 }
397 , m_physics_op(physics_op)
398 , m_live_count(live_count != 0 ? live_count : config.hash.particle_count)
399{
400}
401
406
407void ClaimAccumulateProcessor::processing_function(const std::shared_ptr<Buffer>& buffer)
408{
410
411 auto network_buffer = get_network_buffer();
412 if (!network_buffer || !m_physics_op) {
413 return;
414 }
415
416 uint32_t claim_events = 0;
418 network_buffer->read_state_slot("mutation_claim_events"),
419 &claim_events, sizeof(claim_events), m_readback_staging);
420
421 if (claim_events == 0u) {
423 } else {
426 network_buffer->read_state_slot("mutation_claimed_by"),
428 m_claimed_by_readback.size() * sizeof(uint32_t),
430
432 std::span<const uint32_t>(m_claimed_by_readback).first(m_live_count));
433 }
434
435 auto accreted_mass = m_physics_op->get_accreted_mass_span();
436 if (!accreted_mass.empty()) {
439 network_buffer->write_state_slot("mutation_accreted_mass"),
440 accreted_mass.data(),
441 accreted_mass.size() * sizeof(float),
443 } else {
445 std::copy_n(accreted_mass.begin(),
446 std::min<size_t>(accreted_mass.size(), m_live_count),
447 m_accreted_mass_padded.begin());
449 network_buffer->write_state_slot("mutation_accreted_mass"),
451 m_accreted_mass_padded.size() * sizeof(float),
453 }
454 }
455}
456
457//=============================================================================
458// ClaimSwallowProcessor
459//=============================================================================
460
461namespace {
462
463 ShaderSpec build_claim_swallow_spec(bool transfer_on_claim)
464 {
465 ShaderSpec::Assemble assemble;
466 assemble
467 .ssbo("vertices", BindingDirection::InOut, Kakshya::GpuDataFormat::FLOAT32)
468 .ssbo("claimed_by", BindingDirection::Input, Kakshya::GpuDataFormat::UINT32)
469 .ssbo("swallow_count", BindingDirection::Input, Kakshya::GpuDataFormat::UINT32);
470
471 if (transfer_on_claim) {
472 assemble.ssbo("cluster_id", BindingDirection::Input, Kakshya::GpuDataFormat::UINT32);
473 }
474
475 assemble
476 .pc("particle_count", Kakshya::GpuDataFormat::UINT32)
477 .pc("stride_words", Kakshya::GpuDataFormat::UINT32)
478 .pc("position_offset", Kakshya::GpuDataFormat::UINT32)
479 .pc("color_offset", Kakshya::GpuDataFormat::UINT32)
480 .pc("size_offset", Kakshya::GpuDataFormat::UINT32)
481 .pc("base_size", Kakshya::GpuDataFormat::FLOAT32)
482 .pc("growth_rate", Kakshya::GpuDataFormat::FLOAT32)
483 .pc("max_size", Kakshya::GpuDataFormat::FLOAT32)
484 .pc("dim_factor", Kakshya::GpuDataFormat::FLOAT32)
485 .workgroup(256);
486
487 std::string body;
488 body += " if (i >= particle_count) { return; }\n";
489 body += " uint root = claimed_by[i];\n";
490 if (transfer_on_claim) {
491 body += " if (root != i && cluster_id[root] != cluster_id[i]) { return; }\n";
492 }
493 body += " uint b = i * stride_words;\n";
494 body += " uint rb = root * stride_words;\n";
495 body += "\n";
496 body += " float count = float(swallow_count[root]);\n";
497 body += " float size = clamp(base_size + count * growth_rate, base_size, max_size);\n";
498 body += " float t = clamp((size - base_size) / max(max_size - base_size, 0.001), 0.0, 1.0);\n";
499 body += " vec3 ember = vec3(0.03, 0.0, 0.06);\n";
500 body += " vec3 fire = vec3(0.95, 0.25, 0.02);\n";
501 body += " vec3 white_hot = vec3(1.0, 0.95, 0.7);\n";
502 body += " vec3 heat = t < 0.5\n";
503 body += " ? mix(ember, fire, t * 2.0)\n";
504 body += " : mix(fire, white_hot, (t - 0.5) * 2.0);\n";
505 body += "\n";
506 body += " if (root == i) {\n";
507 body += " vertices[b + size_offset] = size;\n";
508 body += " vertices[b + color_offset] = heat.x;\n";
509 body += " vertices[b + color_offset + 1u] = heat.y;\n";
510 body += " vertices[b + color_offset + 2u] = heat.z;\n";
511 body += " return;\n";
512 body += " }\n";
513 body += "\n";
514 body += " vec3 root_pos = vec3(vertices[rb + position_offset], "
515 "vertices[rb + position_offset + 1u], vertices[rb + position_offset + 2u]);\n";
516 body += " vertices[b + position_offset] = root_pos.x;\n";
517 body += " vertices[b + position_offset + 1u] = root_pos.y;\n";
518 body += " vertices[b + position_offset + 2u] = root_pos.z;\n";
519 body += "\n";
520 body += " vec3 dim_col = heat * dim_factor;\n";
521 body += " vertices[b + color_offset] = dim_col.x;\n";
522 body += " vertices[b + color_offset + 1u] = dim_col.y;\n";
523 body += " vertices[b + color_offset + 2u] = dim_col.z;\n";
524
525 assemble.kernel(KernelSource { .body = std::move(body) });
526
527 return assemble.build();
528 }
529
530 std::vector<NetworkStateFieldProcessor::FieldBinding> claim_swallow_bindings(bool transfer_on_claim)
531 {
532 std::vector<NetworkStateFieldProcessor::FieldBinding> bindings {
533 { .name = "vertices", .binding = 0, .field = {} },
534 { .name = "claimed_by", .binding = 1, .field = "mutation_claimed_by" },
535 { .name = "swallow_count", .binding = 2, .field = "mutation_swallow_count" },
536 };
537 if (transfer_on_claim) {
538 bindings.push_back({ .name = "cluster_id", .binding = 3, .field = "hash_cluster_id" });
539 }
540 return bindings;
541 }
542
543} // namespace
544
546 const MutationConfig& config,
547 std::shared_ptr<Nodes::Network::GpuFieldOperator> particle_op,
548 bool transfer_on_claim)
550 claim_swallow_bindings(transfer_on_claim),
551 build_claim_swallow_spec(transfer_on_claim))
552 , m_params {
553 .particle_count = config.hash.particle_count,
554 .stride_words = config.hash.stride_words,
555 .position_offset = config.hash.position_word_offset,
556 .color_offset = require_color_and_size_offset(particle_op).first,
557 .size_offset = require_color_and_size_offset(particle_op).second,
558 .base_size = particle_op->get_field_config().swallow_base_size,
559 .growth_rate = particle_op->get_field_config().swallow_growth_rate,
560 .max_size = particle_op->get_field_config().swallow_max_size,
561 .dim_factor = particle_op->get_field_config().swallow_dim_factor,
562 }
563 , m_particle_op(std::move(particle_op))
564 , m_built_revision(m_particle_op->revision())
565{
566}
567
572
575 const std::shared_ptr<VKBuffer>& buffer)
576{
577 return guard_and_resync(cmd_id, buffer, m_particle_op->revision(), m_built_revision, [this] {
578 const auto& pconfig = m_particle_op->get_field_config();
579 m_params.base_size = pconfig.swallow_base_size;
580 m_params.growth_rate = pconfig.swallow_growth_rate;
581 m_params.max_size = pconfig.swallow_max_size;
582 m_params.dim_factor = pconfig.swallow_dim_factor;
583 set_push_constant_data(m_params);
584 });
585}
586
587//=============================================================================
588// ClaimTransferProcessor
589//=============================================================================
590
591namespace {
592
593 ShaderSpec build_claim_transfer_spec()
594 {
595 ShaderSpec::Assemble assemble;
596 assemble
597 .ssbo("claimed_by", BindingDirection::Input, Kakshya::GpuDataFormat::UINT32)
598 .ssbo("cluster_id", BindingDirection::InOut, Kakshya::GpuDataFormat::UINT32)
599 .pc("particle_count", Kakshya::GpuDataFormat::UINT32)
600 .workgroup(256);
601
602 std::string body;
603 body += " if (i >= particle_count) { return; }\n";
604 body += " uint root = claimed_by[i];\n";
605 body += " if (root == i) { return; }\n";
606 body += " if (cluster_id[root] == cluster_id[i]) { return; }\n";
607 body += " cluster_id[i] = cluster_id[root];\n";
608
609 assemble.kernel(KernelSource { .body = std::move(body) });
610
611 return assemble.build();
612 }
613
614} // namespace
615
618 { FieldBinding { .name = "claimed_by", .binding = 0, .field = "mutation_claimed_by" },
619 FieldBinding { .name = "cluster_id", .binding = 1, .field = "hash_cluster_id" } },
620 build_claim_transfer_spec())
621 , m_params { .particle_count = config.hash.particle_count }
622{
623}
624
629
630} // namespace MayaFlux::Buffers
float k
void processing_function(const std::shared_ptr< Buffer > &buffer) override
Dispatch as usual, then conditionally read back the result.
void on_buffer_ready() override
Hook for subclass setup, called at the end of on_attach.
ClaimAccumulateProcessor(const MutationConfig &config, Nodes::Network::PhysicsOperator *physics_op, uint32_t live_count=0, const std::shared_ptr< Nodes::Network::GpuFieldOperator > &particle_op=nullptr, bool transfer_on_claim=false)
ClaimFlattenProcessor(const MutationConfig &config)
void on_iteration_barrier(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr< VKBuffer > &buffer, uint32_t index) override
Called after each iteration except the last.
void on_buffer_ready() override
Hook for subclass setup, called at the end of on_attach.
void on_buffer_ready() override
Hook for subclass setup, called at the end of on_attach.
ClaimInitProcessor(const MutationConfig &config)
ClaimProcessor(const MutationConfig &config, std::shared_ptr< Nodes::Network::GpuFieldOperator > particle_op, bool gate_alive=false)
std::shared_ptr< Nodes::Network::GpuFieldOperator > m_particle_op
bool on_before_execute(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr< VKBuffer > &buffer) override
Re-sync capture_growth from particle_op when its revision changes.
void on_buffer_ready() override
Hook for subclass setup, called at the end of on_attach.
std::shared_ptr< Nodes::Network::GpuFieldOperator > m_particle_op
void on_buffer_ready() override
Hook for subclass setup, called at the end of on_attach.
ClaimSwallowProcessor(const MutationConfig &config, std::shared_ptr< Nodes::Network::GpuFieldOperator > particle_op, bool transfer_on_claim=false)
bool on_before_execute(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr< VKBuffer > &buffer) override
Called before each process callback.
void on_buffer_ready() override
Hook for subclass setup, called at the end of on_attach.
void set_iteration_count(uint32_t count)
Set how many dispatches are recorded per execute cycle.
void dispatch_one_thread_per(uint32_t element_count, const T &params)
Configure manual dispatch for one thread per element, and stage the given push constant data.
bool guard_and_resync(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr< VKBuffer > &buffer, uint64_t current_revision, uint64_t &built_revision, F &&reload)
Base on_before_execute guard, then a revision-gated tuning reload.
void processing_function(const std::shared_ptr< Buffer > &buffer) override
Rewrite field descriptors, then run the shader.
const std::shared_ptr< NetworkGeometryBuffer > & get_network_buffer() const
The attached buffer, or null if attachment failed validation.
ComputeProcessor operating on named state fields of a NetworkGeometryBuffer, plus optionally the buff...
std::span< const float > get_accreted_mass_span()
Every particle's currently accreted mass, in global index order.
void sync_bonds_from_claims(std::span< const uint32_t > claimed_by)
Adopt this cycle's GPU claim/absorption clustering as bonds.
void clear_bonds()
Drop every adopted bond.
N-body physics simulation with point rendering.
void buffer_barrier(CommandBufferID cmd_id, vk::Buffer buffer, vk::AccessFlags src_access, vk::AccessFlags dst_access, vk::PipelineStageFlags src_stage, vk::PipelineStageFlags dst_stage)
Insert buffer memory barrier.
void append_neighbour_walk(std::string &body, const NeighbourWalk &walk)
Append the spatial-hash neighbour-gather loop nest to a kernel body.
void download_back_buffer(const VKBufferResources::GenerationSlot &slot, void *data, size_t size, std::shared_ptr< VKBuffer > &staging)
Download a raw back_buffers slot to host memory.
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.
@ BufferProcessing
Buffer processing (Buffers::BufferManager, processing chains)
@ Buffers
Buffers, Managers, processors and processing chains.
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
BindingDirection
Data flow direction for a shader binding slot.
void declare_fields(const std::shared_ptr< NetworkGeometryBuffer > &buffer) const
Declare the state fields the claim pipeline reads and writes.
Parameters for the deterministic pairwise claim protocol built on top of a completed spatial hash.
std::string name
Shader binding name, as declared in ShaderConfig.
Parsed representation of a user-supplied kernel lambda.
Complete declarative description of a generated compute shader.