MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VolumeGridBuffer.cpp
Go to the documentation of this file.
2
6
9
12
13#include "AdvectProcessor.hpp"
14#include "BuoyancyProcessor.hpp"
15#include "DiffuseProcessor.hpp"
17#include "PressureProcessor.hpp"
20#include "WallProcessor.hpp"
21
22namespace MayaFlux::Buffers {
23
25 Kinesis::Lattice3D lattice,
26 std::initializer_list<FieldDecl> fields,
27 std::optional<SurfaceConfig> surface)
28 : VolumeGridBuffer(lattice, std::vector<FieldDecl>(fields), std::move(surface))
29{
30}
31
33 Kinesis::Lattice3D lattice,
34 std::vector<FieldDecl> fields,
35 std::optional<SurfaceConfig> surface)
36 : VKBuffer(surface_storage_bytes(surface), Usage::VERTEX, Kakshya::DataModality::VERTICES_3D)
37 , m_lattice(lattice)
38 , m_surface(std::move(surface))
39{
41 allocate_fields(fields);
42}
43
45 Kinesis::Lattice3D lattice,
46 std::optional<SurfaceConfig> surface)
47 : VKBuffer(surface_storage_bytes(surface), Usage::VERTEX, Kakshya::DataModality::VERTICES_3D)
48 , m_lattice(lattice)
49 , m_surface(std::move(surface))
50{
52}
53
58
60 const std::string& name,
61 size_t stride_bytes,
62 bool double_buffered,
64{
65 if (name.empty()) {
67 "VolumeGridBuffer: field declared with empty name, skipped");
68 return false;
69 }
70
71 if (stride_bytes == 0) {
73 "VolumeGridBuffer: field '{}' declares zero stride, skipped", name);
74 return false;
75 }
76
77 if (m_fields.contains(name)) {
79 "VolumeGridBuffer: duplicate field '{}', later declaration discarded", name);
80 return false;
81 }
82
83 auto buffer_service = Registry::BackendRegistry::instance()
85
86 if (!buffer_service || !buffer_service->allocate_raw_buffer) {
87 error<std::runtime_error>(
90 std::source_location::current(),
91 "VolumeGridBuffer requires a valid buffer service");
92 }
93
94 const auto usage_flags = static_cast<uint32_t>(
95 static_cast<VkBufferUsageFlags>(
96 vk::BufferUsageFlagBits::eStorageBuffer
97 | vk::BufferUsageFlagBits::eTransferSrc
98 | vk::BufferUsageFlagBits::eTransferDst));
99
100 const auto memory_flags = static_cast<uint32_t>(
101 static_cast<VkMemoryPropertyFlags>(vk::MemoryPropertyFlagBits::eDeviceLocal));
102
103 auto& resources = get_buffer_resources();
104 const size_t field_bytes = static_cast<size_t>(get_cell_count()) * stride_bytes;
105 const uint32_t slot_count = double_buffered ? 2 : 1;
106
107 Field field {
108 .stride_bytes = stride_bytes,
109 .slot_a = static_cast<uint32_t>(resources.back_buffers.size()),
110 .slot_b = 0,
111 .read_is_a = true,
112 .semantics = semantics,
113 };
114
115 for (uint32_t i = 0; i < slot_count; ++i) {
116 void* out_buffer = nullptr;
117 void* out_memory = nullptr;
118 void* out_mapped = nullptr;
119
120 buffer_service->allocate_raw_buffer(
121 field_bytes, usage_flags, memory_flags, false,
122 out_buffer, out_memory, out_mapped);
123
125 slot.buffer = static_cast<vk::Buffer>(static_cast<VkBuffer>(out_buffer));
126 slot.memory = static_cast<vk::DeviceMemory>(static_cast<VkDeviceMemory>(out_memory));
127 slot.mapped_ptr = out_mapped;
128
129 resources.back_buffers.push_back(slot);
130 }
131
132 field.slot_b = double_buffered ? field.slot_a + 1 : field.slot_a;
133
134 m_fields.emplace(name, field);
135 m_field_order.push_back(name);
136
138 "VolumeGridBuffer: field '{}', stride {}, {} slot(s), {} bytes, class {}",
139 name, stride_bytes, slot_count, field_bytes * slot_count,
141
142 return true;
143}
144
145void VolumeGridBuffer::allocate_fields(const std::vector<FieldDecl>& decls)
146{
147 for (const auto& decl : decls) {
148 allocate_field(decl.name, decl.stride_bytes, decl.double_buffered, decl.semantics);
149 }
150
151 if (m_fields.empty()) {
152 error<std::runtime_error>(
155 std::source_location::current(),
156 "VolumeGridBuffer constructed with no valid fields");
157 }
158
160 "VolumeGridBuffer: {}x{}x{} lattice, {} fields, {} slots",
162 m_fields.size(), get_buffer_resources().back_buffers.size());
163}
164
166{
167 if (!allocate_field(name, sizeof(float), true, semantics)) {
168 return {};
169 }
170
171 return ScalarRef {
172 .name = std::move(name),
173 .owner = std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this()),
174 };
175}
176
178{
179 if (!allocate_field(name, sizeof(float), false, semantics)) {
180 return {};
181 }
182
183 return ScalarRef {
184 .name = std::move(name),
185 .owner = std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this()),
186 };
187}
188
190{
191 if (!allocate_field(name, sizeof(glm::vec4), true, semantics)) {
192 return {};
193 }
194
195 return VectorRef {
196 .name = std::move(name),
197 .owner = std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this()),
198 };
199}
200
201std::vector<std::string> VolumeGridBuffer::ensure_fields(const std::vector<FieldDecl>& decls)
202{
203 std::vector<std::string> declared;
204 declared.reserve(decls.size());
205
206 for (const auto& decl : decls) {
207 if (has_field(decl.name)) {
209 "VolumeGridBuffer::ensure_fields: '{}' already present, reused", decl.name);
210 continue;
211 }
212
213 if (allocate_field(decl.name, decl.stride_bytes, decl.double_buffered, decl.semantics)) {
214 declared.push_back(decl.name);
215 }
216 }
217
218 return declared;
219}
220
222{
224 if (!chain) {
225 chain = std::make_shared<BufferProcessingChain>();
227 }
228 chain->set_preferred_token(token);
229
230 if (!m_surface) {
232 "VolumeGridBuffer: chain established, no extraction configured");
233 return;
234 }
235
236 auto self = std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this());
237
238 m_surface_processor = std::make_shared<VolumeSurfaceProcessor>(
239 self, m_surface->field_name,
240 m_lattice.resampled(m_surface->resolution),
241 m_surface->threshold);
242
245
246 m_counter_buf = std::make_shared<VKBuffer>(
247 sizeof(uint32_t), Usage::HOST_STORAGE, Kakshya::DataModality::UNKNOWN);
248 svc->initialize_buffer(m_counter_buf);
249
250 m_mesh_processor = std::make_shared<SDFMeshProcessor>(
253 m_surface->resolution.x, m_surface->resolution.y, m_surface->resolution.z, 0.0F);
254
255 const uint32_t max_vertices = m_surface_processor->worst_case_vertices();
257 layout.vertex_count = max_vertices;
258 set_vertex_layout(layout);
259
260 m_surface_processor->set_processing_token(token);
261 m_mesh_processor->set_processing_token(token);
262
264 chain->add_processor(m_mesh_processor, self);
265
267 "VolumeGridBuffer: surfacing '{}' at {}x{}x{}, {} max vertices",
268 m_surface->field_name, m_surface->resolution.x, m_surface->resolution.y,
269 m_surface->resolution.z, max_vertices);
270
272 "VolumeGridBuffer: chain established, no stages attached");
273}
274
276{
277 if (!m_surface) {
279 "setup_rendering: no SurfaceConfig was supplied at construction");
280 return;
281 }
282
283 RenderConfig resolved = config;
285
286 if (resolved.vertex_shader.empty())
287 resolved.vertex_shader = "triangle.vert.spv";
288 if (resolved.fragment_shader.empty())
289 resolved.fragment_shader = "triangle.frag.spv";
290
291 apply_render_config(resolved, ShaderConfig { resolved.vertex_shader });
292
293 get_processing_chain()->add_final_processor(m_render_processor, shared_from_this());
294
295 m_render_processor->set_vertex_range(0, 0);
297}
298
299std::shared_ptr<InfluxProcessor> VolumeGridBuffer::influx(
300 const std::string& field,
301 const InfluxProcessor::Spot& spot,
302 const std::string& shader)
303{
305 if (!chain) {
307 "influx: no processing chain, call after registration");
308 return nullptr;
309 }
310
311 auto stage = std::make_shared<InfluxProcessor>(field, shader, spot);
312 chain->add_processor(stage, std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this()));
313 return stage;
314}
315
317{
318 FlowStages stages;
319
320 auto self = std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this());
322
323 if (!chain) {
325 "setup_flow: no processing chain, call after registration");
326 return stages;
327 }
328
329 const auto require = [this](const std::string& name, const char* role) {
330 if (name.empty()) {
332 "setup_flow: no field supplied for '{}'", role);
333 return false;
334 }
335 if (!has_field(name)) {
337 "setup_flow: '{}' names no field on this volume, supplied as '{}'",
338 name, role);
339 return false;
340 }
341 return true;
342 };
343
344 if (!require(config.velocity, "velocity")
345 || !require(config.divergence, "divergence")
346 || !require(config.pressure, "pressure")) {
347 return stages;
348 }
349
350 if (config.viscosity > 0.0F && !require(config.scratch, "scratch")) {
351 return stages;
352 }
353
354 stages.self_advect = std::make_shared<AdvectProcessor>(
355 config.velocity, config.velocity, "volume_advect_vector.comp.spv");
356 stages.self_advect->set_time_step(config.time_step);
357 chain->add_processor(stages.self_advect, self);
358
359 if (config.buoyancy) {
360 const auto& b = *config.buoyancy;
361
362 if (!require(b.temperature, "buoyancy.temperature")
363 || !require(b.density, "buoyancy.density")) {
364 return stages;
365 }
366
367 stages.buoyancy = std::make_shared<BuoyancyProcessor>(
368 b.temperature, b.density, config.velocity,
369 b.direction, "volume_buoyancy.comp.spv");
370 stages.buoyancy->set_time_step(config.time_step);
371 stages.buoyancy->set_temperature_gain(b.temperature_gain);
372 stages.buoyancy->set_density_gain(b.density_gain);
373 stages.buoyancy->set_ambient(b.ambient);
374 chain->add_processor(stages.buoyancy, self);
375 }
376
377 if (config.viscosity > 0.0F) {
378 stages.diffuse = std::make_shared<DiffuseProcessor>(
379 config.velocity, config.scratch, "volume_diffuse_vector.comp.spv");
380 stages.diffuse->set_rate(config.viscosity);
381 stages.diffuse->set_time_step(config.time_step);
382 chain->add_processor(stages.diffuse, self);
383 }
384
385 if (config.walls) {
386 stages.wall_advected = std::make_shared<WallProcessor>(
387 config.velocity, "volume_wall.comp.spv");
388 chain->add_processor(stages.wall_advected, self);
389 }
390
391 stages.divergence = std::make_shared<DivergenceProcessor>(
392 config.velocity, config.divergence, "volume_divergence.comp.spv");
393 chain->add_processor(stages.divergence, self);
394
395 stages.pressure = std::make_shared<PressureProcessor>(
396 config.divergence, config.pressure, "volume_pressure_jacobi.comp.spv");
397 stages.pressure->set_iteration_count(config.jacobi_iterations);
398 chain->add_processor(stages.pressure, self);
399
400 stages.solenoidal = std::make_shared<SolenoidalProcessor>(
401 config.pressure, config.velocity, "volume_solenoidal.comp.spv");
402 chain->add_processor(stages.solenoidal, self);
403
404 if (config.walls) {
405 stages.wall_projected = std::make_shared<WallProcessor>(
406 config.velocity, "volume_wall.comp.spv");
407 chain->add_processor(stages.wall_projected, self);
408 }
409
410 for (const auto& carried : config.carried) {
411 if (!require(carried.field, "carried")) {
412 continue;
413 }
414
415 auto advect = std::make_shared<AdvectProcessor>(
416 config.velocity, carried.field, "volume_advect_scalar.comp.spv");
417 advect->set_time_step(config.time_step);
418 advect->set_dissipation(carried.dissipation);
419 chain->add_processor(advect, self);
420
421 stages.carriers.push_back(std::move(advect));
422 }
423
425 "VolumeGridBuffer::setup_flow: {} carried, buoyancy {}, viscosity {}, walls {}",
426 stages.carriers.size(), config.buoyancy ? "on" : "off",
427 config.viscosity, config.walls ? "on" : "off");
428
429 return stages;
430}
431
433 const std::string& name, const char* context) const
434{
435 auto it = m_fields.find(name);
436 if (it == m_fields.end()) {
438 "VolumeGridBuffer::{}: no field named '{}'", context, name);
439 return nullptr;
440 }
441 return &it->second;
442}
443
444bool VolumeGridBuffer::has_field(const std::string& name) const
445{
446 return m_fields.contains(name);
447}
448
449size_t VolumeGridBuffer::get_field_bytes(const std::string& name) const
450{
451 auto it = m_fields.find(name);
452 if (it == m_fields.end()) {
453 return 0;
454 }
455 return static_cast<size_t>(get_cell_count()) * it->second.stride_bytes;
456}
457
458size_t VolumeGridBuffer::get_field_stride(const std::string& name) const
459{
460 auto it = m_fields.find(name);
461 if (it == m_fields.end()) {
462 return 0;
463 }
464 return it->second.stride_bytes;
465}
466
468{
469 auto it = m_fields.find(name);
470 if (it == m_fields.end()) {
471 return {};
472 }
473 return it->second.semantics;
474}
475
476std::vector<std::string> VolumeGridBuffer::get_field_names() const
477{
478 return m_field_order;
479}
480
481vk::Buffer VolumeGridBuffer::read_handle(const std::string& name) const
482{
483 const auto* field = find_field(name, "read_handle");
484 if (!field) {
485 return nullptr;
486 }
487
488 const uint32_t slot = field->read_is_a ? field->slot_a : field->slot_b;
489 return get_buffer_resources().back_buffers[slot].buffer;
490}
491
492vk::Buffer VolumeGridBuffer::write_handle(const std::string& name) const
493{
494 const auto* field = find_field(name, "write_handle");
495 if (!field) {
496 return nullptr;
497 }
498
499 const uint32_t slot = field->read_is_a ? field->slot_b : field->slot_a;
500 return get_buffer_resources().back_buffers[slot].buffer;
501}
502
503void VolumeGridBuffer::swap_field(const std::string& name)
504{
505 auto it = m_fields.find(name);
506 if (it == m_fields.end()) {
508 "VolumeGridBuffer::swap_field: no field named '{}'", name);
509 return;
510 }
511
512 if (it->second.slot_a == it->second.slot_b) {
513 return;
514 }
515
516 it->second.read_is_a = !it->second.read_is_a;
517}
518
519void VolumeGridBuffer::seed(const std::string& name, const Kinesis::SpatialField& field)
520{
521 const auto* decl = find_field(name, "seed");
522 if (!decl) {
523 return;
524 }
525
526 if (decl->stride_bytes != sizeof(float)) {
528 "VolumeGridBuffer::seed: field '{}' has stride {}, SpatialField requires {}",
529 name, decl->stride_bytes, sizeof(float));
530 return;
531 }
532
533 const glm::uvec3 res = m_lattice.resolution;
534 std::vector<float> values(m_lattice.cell_count());
535
536 size_t i = 0;
537 for (uint32_t z = 0; z < res.z; ++z) {
538 for (uint32_t y = 0; y < res.y; ++y) {
539 for (uint32_t x = 0; x < res.x; ++x) {
540 values[i++] = field(m_lattice.cell_center({ x, y, z }));
541 }
542 }
543 }
544
545 seed_raw(name, values.data(), values.size() * sizeof(float));
546}
547
548void VolumeGridBuffer::seed(const std::string& name, const Kinesis::VectorField& field)
549{
550 const auto* decl = find_field(name, "seed");
551 if (!decl) {
552 return;
553 }
554
555 if (decl->stride_bytes != sizeof(glm::vec4)) {
557 "VolumeGridBuffer::seed: field '{}' has stride {}, VectorField requires {}",
558 name, decl->stride_bytes, sizeof(glm::vec4));
559 return;
560 }
561
562 const glm::uvec3 res = m_lattice.resolution;
563 std::vector<glm::vec4> values(m_lattice.cell_count());
564
565 size_t i = 0;
566 for (uint32_t z = 0; z < res.z; ++z) {
567 for (uint32_t y = 0; y < res.y; ++y) {
568 for (uint32_t x = 0; x < res.x; ++x) {
569 values[i++] = glm::vec4(field(m_lattice.cell_center({ x, y, z })), 0.0F);
570 }
571 }
572 }
573
574 seed_raw(name, values.data(), values.size() * sizeof(glm::vec4));
575}
576
577void VolumeGridBuffer::seed_raw(const std::string& name, const void* data, size_t size)
578{
579 const auto* field = find_field(name, "seed_raw");
580 if (!field) {
581 return;
582 }
583
584 const size_t expected = static_cast<size_t>(get_cell_count()) * field->stride_bytes;
585 if (size != expected) {
587 "VolumeGridBuffer::seed_raw: size {} does not match expected {} for '{}'",
588 size, expected, name);
589 return;
590 }
591
593
594 const uint32_t slot = field->read_is_a ? field->slot_a : field->slot_b;
596 get_buffer_resources().back_buffers[slot], data, size, m_transfer_staging);
597}
598
599void VolumeGridBuffer::accumulate(const std::string& name, const Kinesis::SpatialField& field)
600{
601 const auto* decl = find_field(name, "accumulate");
602 if (!decl) {
603 return;
604 }
605
606 if (decl->stride_bytes != sizeof(float)) {
608 "VolumeGridBuffer::accumulate: field '{}' has stride {}, SpatialField requires {}",
609 name, decl->stride_bytes, sizeof(float));
610 return;
611 }
612
613 const glm::uvec3 res = m_lattice.resolution;
614 std::vector<float> values(m_lattice.cell_count());
615
616 read_field(name, values.data(), values.size() * sizeof(float));
617
618 size_t i = 0;
619 for (uint32_t z = 0; z < res.z; ++z) {
620 for (uint32_t y = 0; y < res.y; ++y) {
621 for (uint32_t x = 0; x < res.x; ++x) {
622 values[i++] += field(m_lattice.cell_center({ x, y, z }));
623 }
624 }
625 }
626
627 seed_raw(name, values.data(), values.size() * sizeof(float));
628}
629
630void VolumeGridBuffer::accumulate(const std::string& name, const Kinesis::VectorField& field)
631{
632 const auto* decl = find_field(name, "accumulate");
633 if (!decl) {
634 return;
635 }
636
637 if (decl->stride_bytes != sizeof(glm::vec4)) {
639 "VolumeGridBuffer::accumulate: field '{}' has stride {}, VectorField requires {}",
640 name, decl->stride_bytes, sizeof(glm::vec4));
641 return;
642 }
643
644 const glm::uvec3 res = m_lattice.resolution;
645 std::vector<glm::vec4> values(m_lattice.cell_count());
646
647 read_field(name, values.data(), values.size() * sizeof(glm::vec4));
648
649 size_t i = 0;
650 for (uint32_t z = 0; z < res.z; ++z) {
651 for (uint32_t y = 0; y < res.y; ++y) {
652 for (uint32_t x = 0; x < res.x; ++x) {
653 const glm::vec3 v = field(m_lattice.cell_center({ x, y, z }));
654 values[i].x += v.x;
655 values[i].y += v.y;
656 values[i].z += v.z;
657 ++i;
658 }
659 }
660 }
661
662 seed_raw(name, values.data(), values.size() * sizeof(glm::vec4));
663}
664
665void VolumeGridBuffer::read_field(const std::string& name, void* data, size_t size)
666{
667 const auto* field = find_field(name, "read_field");
668 if (!field) {
669 return;
670 }
671
672 const size_t expected = static_cast<size_t>(get_cell_count()) * field->stride_bytes;
673 if (size != expected) {
675 "VolumeGridBuffer::read_field: size {} does not match expected {} for '{}'",
676 size, expected, name);
677 return;
678 }
679
680 const uint32_t slot = field->read_is_a ? field->slot_a : field->slot_b;
683}
684
686 const std::vector<std::string>& names,
687 const std::vector<std::pair<void*, size_t>>& dsts)
688{
689 if (names.size() != dsts.size()) {
691 "VolumeGridBuffer::read_fields: {} names against {} destinations",
692 names.size(), dsts.size());
693 return;
694 }
695
696 constexpr size_t k_alignment = 16;
697
698 auto& resources = get_buffer_resources();
699 std::vector<size_t> indices;
700 std::vector<size_t> offsets;
701 size_t total = 0;
702
703 for (size_t i = 0; i < names.size(); ++i) {
704 const auto* field = find_field(names[i], "read_fields");
705 if (!field) {
706 continue;
707 }
708
709 const size_t expected = static_cast<size_t>(get_cell_count()) * field->stride_bytes;
710 if (dsts[i].second != expected) {
712 "VolumeGridBuffer::read_fields: size {} does not match expected {} for '{}'",
713 dsts[i].second, expected, names[i]);
714 continue;
715 }
716
717 indices.push_back(i);
718 offsets.push_back(total);
719 total += (dsts[i].second + k_alignment - 1) & ~(k_alignment - 1);
720 }
721
722 if (indices.empty()) {
723 return;
724 }
725
727
728 if (!m_transfer_staging || m_transfer_staging->get_size_bytes() < total) {
730 }
731
732 std::vector<TransferHandle> handles;
733 handles.reserve(indices.size());
734
735 for (size_t k = 0; k < indices.size(); ++k) {
736 const auto* field = find_field(names[indices[k]], "read_fields");
737 const uint32_t slot = field->read_is_a ? field->slot_a : field->slot_b;
738 handles.push_back(download_back_buffer_async(
739 resources.back_buffers[slot], dsts[indices[k]].second,
740 m_transfer_staging, offsets[k]));
741 }
742
743 for (size_t k = 0; k < indices.size(); ++k) {
744 const auto* field = find_field(names[indices[k]], "read_fields");
745 const uint32_t slot = field->read_is_a ? field->slot_a : field->slot_b;
747 handles[k], resources.back_buffers[slot],
748 dsts[indices[k]].first, dsts[indices[k]].second,
749 m_transfer_staging, offsets[k]);
750 }
751}
752
753size_t VolumeGridBuffer::surface_storage_bytes(const std::optional<SurfaceConfig>& surface)
754{
755 if (!surface) {
756 return 1;
757 }
758
759 const glm::uvec3 res = glm::max(surface->resolution, glm::uvec3(1U));
760 const uint64_t voxels = static_cast<uint64_t>(res.x) * res.y * res.z;
761 return static_cast<size_t>(voxels * 15U * sizeof(Kakshya::MeshVertex));
762}
763
764std::optional<uint32_t> VolumeGridBuffer::get_live_vertex_count() const
765{
766 return m_mesh_processor ? m_mesh_processor->live_vertex_count() : std::nullopt;
767}
768
769} // namespace MayaFlux::Buffers
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
bool size
std::vector< uint32_t > indices
BufferProcessingChain * chain
uint32_t total
size_t b
std::vector< std::byte > values
Definition VDBWriter.cpp:26
std::string name
Definition VKDevice.cpp:123
void set_vertex_layout(const Kakshya::VertexLayout &layout)
Set vertex layout for this buffer.
Definition VKBuffer.cpp:453
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
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 set_needs_depth_attachment(bool needs)
Mark this buffer as requiring depth testing when rendered.
Definition VKBuffer.hpp:591
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
const VKBufferResources & get_buffer_resources() const
Get all buffer resources at once (read-only)
Definition VKBuffer.hpp:343
void force_internal_usage(bool internal) override
Set whether this buffer is for internal engine usage.
Definition VKBuffer.hpp:542
Vulkan-backed buffer wrapper used in processing chains.
Definition VKBuffer.hpp:76
const Field * find_field(const std::string &name, const char *context) const
Resolve a field by name.
vk::Buffer write_handle(const std::string &name) const
Handle a stage should write the named field to.
std::unordered_map< std::string, Field > m_fields
TransferHandle m_pending_transfer
In-flight seed upload, resolved before the next transfer.
void read_fields(const std::vector< std::string > &names, const std::vector< std::pair< void *, size_t > > &dsts)
Read several fields' current values into host memory at once.
uint32_t get_cell_count() const
Total cell count.
std::optional< uint32_t > get_live_vertex_count() const
The live vertex count of the extracted surface from the most recent dispatch.
std::optional< SurfaceConfig > m_surface
std::vector< std::string > get_field_names() const
Names of every declared field, in declaration order.
FlowStages setup_flow(const FlowConfig &config)
Build and append the incompressible flow stages.
static size_t surface_storage_bytes(const std::optional< SurfaceConfig > &surface)
Bytes of vertex storage the extraction resolution requires.
void seed(const std::string &name, const Kinesis::SpatialField &field)
Write initial values into a scalar field from a Kinesis field.
std::shared_ptr< VKBuffer > m_transfer_staging
Reused across seed and read calls.
ScalarRef declare_scratch(std::string name, Kinesis::LatticeSemantics semantics={})
Declare a single-slot scalar field.
void accumulate(const std::string &name, const Kinesis::SpatialField &field)
Add sampled values into a scalar field.
std::shared_ptr< VolumeSurfaceProcessor > m_surface_processor
std::vector< std::string > m_field_order
vk::Buffer read_handle(const std::string &name) const
Handle a stage should read the named field from.
void setup_processors(ProcessingToken token) override
Establish the processing chain without attaching any stage.
VectorRef declare_vector(std::string name, Kinesis::LatticeSemantics semantics={})
Declare a double-buffered vector field.
Kinesis::LatticeSemantics get_field_semantics(const std::string &name) const
Interpretation declared for the named field.
void read_field(const std::string &name, void *data, size_t size)
Copy the current read slot of a field to host memory.
std::vector< std::string > ensure_fields(const std::vector< FieldDecl > &decls)
Declare whichever of decls are not already present.
std::shared_ptr< InfluxProcessor > influx(const std::string &field, const InfluxProcessor::Spot &spot, const std::string &shader="volume_influx_blob.comp.spv")
Add an influx stage to this volume's chain and return it.
size_t get_field_stride(const std::string &name) const
Bytes per cell of the named field, or 0 if undeclared.
bool allocate_field(const std::string &name, size_t stride_bytes, bool double_buffered)
Allocate one field's slots and register it.
bool has_field(const std::string &name) const
Whether a field of this name was declared.
void setup_rendering(const RenderConfig &config)
Attach a RenderProcessor drawing the extracted surface.
VolumeGridBuffer(Kinesis::Lattice3D lattice, std::initializer_list< FieldDecl > fields, std::optional< SurfaceConfig > surface=std::nullopt)
Construct an unregistered multi-field volume.
void seed_raw(const std::string &name, const void *data, size_t size)
Write initial values into a field from raw host memory.
std::shared_ptr< VKBuffer > m_counter_buf
Atomic vertex counter for the extraction stage.
size_t get_field_bytes(const std::string &name) const
Byte size of one slot of the named field, or 0 if undeclared.
void allocate_fields(const std::vector< FieldDecl > &decls)
Populate m_fields from declarations.
ScalarRef declare_scalar(std::string name, Kinesis::LatticeSemantics semantics={})
Declare a double-buffered scalar field.
std::shared_ptr< SDFMeshProcessor > m_mesh_processor
void swap_field(const std::string &name)
Exchange read and write slots for the named field.
GPU-resident state for multi-field simulations evaluated over a fixed 3D topology: incompressible flu...
Interface * get_service()
Query for a backend service.
static BackendRegistry & instance()
Get the global registry instance.
void resolve_transfer(TransferHandle &handle)
Wait for a transfer and release its resources.
TransferHandle upload_back_buffer_async(const VKBufferResources::GenerationSlot &slot, const void *data, size_t size, std::shared_ptr< VKBuffer > &staging)
Upload to a back_buffers slot without waiting for completion.
ProcessingToken
Bitfield enum defining processing characteristics and backend requirements for buffer operations.
std::shared_ptr< VKBuffer > create_staging_buffer(size_t size)
Create staging buffer for transfers.
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 resolve_back_buffer_read(TransferHandle &handle, const VKBufferResources::GenerationSlot &slot, void *data, size_t size, std::shared_ptr< VKBuffer > &staging, size_t staging_offset)
Wait for a recorded download, then copy it out of staging.
TransferHandle download_back_buffer_async(const VKBufferResources::GenerationSlot &slot, size_t size, std::shared_ptr< VKBuffer > &staging, size_t staging_offset)
Record a slot-to-staging copy without waiting for completion.
@ BufferManagement
Buffer Management (Buffers::BufferManager, creating buffers)
@ Init
Engine/subsystem initialization.
@ Buffers
Buffers, Managers, processors and processing chains.
@ UNKNOWN
Unknown or undefined modality.
BufferUsageHint
Semantic usage hint for buffer allocation and memory properties.
constexpr std::string_view enum_to_string(EnumType value) noexcept
Universal enum to string converter using magic_enum (original case)
Where and how an influx adds, in one aggregate, for configure().
A scalar field's name, issued by the volume that owns it.
std::vector< GenerationSlot > back_buffers
Definition VKBuffer.hpp:48
A vector field's name, issued by the volume that owns it.
One field's declaration within a volume.
float time_step
Applied to every stage that integrates.
bool walls
Free-slip on the six faces, twice per cycle.
VectorRef scratch
Required only when viscosity is above zero.
float viscosity
Zero omits the diffusion stage entirely.
ScalarRef divergence
Required. Single-slot is correct.
Parameters for the incompressible flow stage arrangement.
std::shared_ptr< AdvectProcessor > self_advect
std::shared_ptr< PressureProcessor > pressure
std::shared_ptr< WallProcessor > wall_projected
std::vector< std::shared_ptr< AdvectProcessor > > carriers
Parallel to config.carried.
std::shared_ptr< DiffuseProcessor > diffuse
std::shared_ptr< DivergenceProcessor > divergence
std::shared_ptr< SolenoidalProcessor > solenoidal
std::shared_ptr< BuoyancyProcessor > buoyancy
Every stage setup_flow built, in no particular order.
Vertex type for indexed triangle mesh primitives (TRIANGLE_LIST topology)
static VertexLayout for_meshes(uint32_t stride=60)
Factory: layout for MeshVertex (position, color, weight, uv, normal, tangent)
Lattice3D resampled(const glm::uvec3 &res) const noexcept
A lattice over the same bounds at a different resolution.
Definition Lattice.hpp:103
AABB3D bounds
Continuous extent subdivided.
Definition Lattice.hpp:27
size_t cell_count() const noexcept
Total cell count.
Definition Lattice.hpp:36
glm::uvec3 resolution
Cell count per axis. Zero on any axis is invalid.
Definition Lattice.hpp:26
glm::vec3 cell_center(const glm::uvec3 &c) const noexcept
World position of a cell's centre.
Definition Lattice.hpp:70
A regular subdivision of an AABB3D into a cell count per axis.
Definition Lattice.hpp:25
LatticeValueClass value_class
Geometric meaning of the values.
Interpretation attached to one named quantity sampled over a lattice.
Typed, composable, stateless callable from domain D to range R.
Definition Tendency.hpp:22
Unified rendering configuration for graphics buffers.
Backend buffer management service interface.