MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VolumeGridBuffer.hpp
Go to the documentation of this file.
1#pragma once
2
6
8
9#include "InfluxProcessor.hpp"
10
11namespace MayaFlux::Buffers {
12
13class VolumeSurfaceProcessor;
14class SDFMeshProcessor;
15class RenderProcessor;
16class VolumeGridBuffer;
17class AdvectProcessor;
18class BuoyancyProcessor;
19class WallProcessor;
20class DivergenceProcessor;
21class DiffuseProcessor;
22class PressureProcessor;
23class SolenoidalProcessor;
24
25/**
26 * @struct ScalarRef
27 * @brief A scalar field's name, issued by the volume that owns it.
28 *
29 * Exists so a field is spelled once, at declaration, rather than at every
30 * site that addresses it. A name typed twice is two independent literals
31 * that must agree; a mismatch surfaces as a stage that runs and does
32 * nothing, which is the hardest failure in this subsystem to diagnose.
33 *
34 * Converts implicitly to the field name, so a ref passes anywhere a
35 * processor expects a string. The type distinction bites where a
36 * signature demands one kind: a parameter taking ScalarRef cannot be
37 * handed a vector field.
38 *
39 * Carries a weak reference to its issuer so a ref from one volume used
40 * against another can be rejected rather than silently resolving to a
41 * same-named field.
42 */
43struct ScalarRef {
44 std::string name;
45 std::weak_ptr<VolumeGridBuffer> owner;
46
47 static constexpr size_t stride = sizeof(float);
48
49 operator const std::string&() const { return name; }
50
51 /**
52 * @brief Whether this ref was issued by the given volume.
53 * @param volume Volume to test against.
54 */
55 [[nodiscard]] bool issued_by(const VolumeGridBuffer* volume) const
56 {
57 auto o = owner.lock();
58 return o && o.get() == volume;
59 }
60};
61
62/**
63 * @struct VectorRef
64 * @brief A vector field's name, issued by the volume that owns it.
65 *
66 * As ScalarRef, for fields of glm::vec4 stride. The fourth component is
67 * carried through by every current stage and read by none.
68 */
69struct VectorRef {
70 std::string name;
71 std::weak_ptr<VolumeGridBuffer> owner;
72
73 static constexpr size_t stride = sizeof(glm::vec4);
74
75 operator const std::string&() const { return name; }
76
77 /**
78 * @brief Whether this ref was issued by the given volume.
79 * @param volume Volume to test against.
80 */
81 [[nodiscard]] bool issued_by(const VolumeGridBuffer* volume) const
82 {
83 auto o = owner.lock();
84 return o && o.get() == volume;
85 }
86};
87
88/**
89 * @class VolumeGridBuffer
90 * @brief GPU-resident state for multi-field simulations evaluated over a
91 * fixed 3D topology: incompressible fluid, gas and smoke, reaction
92 * systems, and any process where several quantities co-evolve over
93 * the same cell lattice.
94 *
95 * Where RelaxationGridBuffer carries one field over a 2D topology and
96 * advances it with one rule dispatch per cycle, this carries N named
97 * fields over a 3D topology and advances them with a chain of processors,
98 * each responsible for one stage. A velocity field, a pressure field, a
99 * divergence scratch field and one or more carried scalars are the
100 * ordinary case; nothing in the class knows what any of them mean.
101 *
102 * All field storage lives as raw Vulkan handle pairs inside
103 * VKBufferResources::back_buffers, allocated once at construction as
104 * device-local with transfer source and destination usage. Fields are
105 * addressed by name; each declares its own component stride and whether
106 * it needs a second slot for ping-pong. Single-slot fields resolve read
107 * and write to the same handle, which suits scratch quantities
108 * recomputed from nothing each stage.
109 *
110 * No VKBuffer object represents any field. Processors write descriptors
111 * for these handles directly against ShaderFoundry, as
112 * RelaxationStepProcessor does, resolving them through read_handle() and
113 * write_handle().
114 *
115 * Chain layout:
116 * default - VolumeSurfaceProcessor, resampling the surfaced field
117 * flat[0] - SDFMeshProcessor, extracting the isosurface
118 * flat[1..] - simulation stages, added by the caller at any point
119 * final - RenderProcessor
120 *
121 * Extraction occupies the default and first flat slots so that
122 * simulation stages appended by the caller always follow it. The surface
123 * therefore reflects the previous cycle's field rather than the current
124 * one, which at frame rate is not observable.
125 *
126 * The VKBuffer base owns no vertex output. Extraction is a separate
127 * concern: attach SDFMeshProcessor against a scalar field for an
128 * isosurface, or a bespoke processor for raymarching or debug points.
129 * The buffer emits nothing on its own.
130 *
131 * Usage:
132 * @code
133 * auto vol = std::make_shared<VolumeGridBuffer>(
134 * 64, 64, 64,
135 * { { "velocity", sizeof(glm::vec4) },
136 * { "pressure", sizeof(float) },
137 * { "divergence", sizeof(float), false },
138 * { "density", sizeof(float) } },
139 * Kinesis::AABB3D { { -1, -1, -1 }, { 1, 1, 1 } });
140 *
141 * vol->seed("density", Kinesis::SpatialField { ... });
142 * vol->setup_processors(ProcessingToken::GRAPHICS_BACKEND);
143 * @endcode
144 */
145class MAYAFLUX_API VolumeGridBuffer : public VKBuffer {
146public:
147 /**
148 * @struct FieldDecl
149 * @brief One field's declaration within a volume.
150 */
151 struct FieldDecl {
152 std::string name; ///< Lookup key, unique within the volume.
153 size_t stride_bytes; ///< Bytes per cell.
154 bool double_buffered = true; ///< False resolves read and write to one slot.
155 Kinesis::LatticeSemantics semantics {}; ///< What the values mean. Unused by this class.
156 };
157
158 /**
159 * @struct SurfaceConfig
160 * @brief Isosurface extraction parameters for a scalar field.
161 *
162 * Supplied at construction because the extraction resolution
163 * determines this buffer's own vertex storage size: mc_emit allocates
164 * slots by atomicAdd without a capacity check, so storage is sized to
165 * the worst case of fifteen vertices per voxel. At 48 cubed that is
166 * roughly 95 MB, at 64 cubed roughly 225 MB.
167 */
169 std::string field_name; ///< Scalar field surfaced. Stride must be sizeof(float).
170 glm::uvec3 resolution; ///< Extraction cell count per axis, over the volume's own bounds.
171 float threshold; ///< Field value the surface is placed at.
172 };
173
174 /**
175 * @struct FlowConfig
176 * @brief Parameters for the incompressible flow stage arrangement.
177 *
178 * Carries what varies between simulations. What does not vary, the
179 * stage ordering, is fixed by setup_flow: buoyancy must precede the
180 * projection or the solve removes the divergence it injects, the wall
181 * condition must follow every stage that writes velocity, and carried
182 * scalars must be advected by the projected velocity rather than the
183 * raw one. Those are correctness constraints, not preferences, and
184 * they are the part callers get wrong.
185 */
186 struct FlowConfig {
187 /**
188 * @struct Carried
189 * @brief A scalar transported by the velocity field.
190 */
191 struct Carried {
192 ScalarRef field; ///< Field advected.
193 float dissipation { 1.0F }; ///< Per-cycle multiplier. One conserves.
194 };
195
196 /**
197 * @struct Buoyancy
198 * @brief Body force accumulated into velocity from two scalars.
199 */
200 struct Buoyancy {
201 ScalarRef temperature; ///< Drives motion along direction.
202 ScalarRef density; ///< Drives motion against it.
203 glm::vec3 direction { 0.0F, 1.0F, 0.0F }; ///< Axis and magnitude.
204 float temperature_gain { 1.0F };
205 float density_gain { 0.0F };
206 float ambient { 0.0F }; ///< Temperature at which the rise term vanishes.
207 };
208
209 VectorRef velocity; ///< Required.
210 ScalarRef divergence; ///< Required. Single-slot is correct.
211 ScalarRef pressure; ///< Required.
212 VectorRef scratch; ///< Required only when viscosity is above zero.
213
214 float time_step { 1.0F / 60.0F }; ///< Applied to every stage that integrates.
215 float viscosity { 0.0F }; ///< Zero omits the diffusion stage entirely.
216 uint32_t jacobi_iterations { 32 };
217 bool walls { true }; ///< Free-slip on the six faces, twice per cycle.
218
219 std::vector<Carried> carried;
220 std::optional<Buoyancy> buoyancy;
221 };
222
223 /**
224 * @struct FlowStages
225 * @brief Every stage setup_flow built, in no particular order.
226 *
227 * Returned rather than stored so each remains reachable for retuning,
228 * feeding, or inspection. Members are null where the config omitted
229 * the corresponding stage.
230 */
231 struct FlowStages {
232 std::shared_ptr<AdvectProcessor> self_advect;
233 std::shared_ptr<BuoyancyProcessor> buoyancy;
234 std::shared_ptr<DiffuseProcessor> diffuse;
235 std::shared_ptr<WallProcessor> wall_advected;
236 std::shared_ptr<DivergenceProcessor> divergence;
237 std::shared_ptr<PressureProcessor> pressure;
238 std::shared_ptr<SolenoidalProcessor> solenoidal;
239 std::shared_ptr<WallProcessor> wall_projected;
240 std::vector<std::shared_ptr<AdvectProcessor>> carriers; ///< Parallel to config.carried.
241 };
242
243 /**
244 * @brief Construct an unregistered multi-field volume.
245 * @param lattice Discretized extent every field is stored over.
246 * @param fields Field declarations. Duplicated names are rejected with
247 * an error and the later declaration discarded.
248 * @param surface Optional isosurface extraction parameters.
249 */
251 Kinesis::Lattice3D lattice,
252 std::initializer_list<FieldDecl> fields,
253 std::optional<SurfaceConfig> surface = std::nullopt);
254
255 /**
256 * @brief Construct from a runtime-built field list.
257 * @param lattice Discretized extent every field is stored over.
258 * @param fields Field declarations.
259 * @param surface Optional isosurface extraction parameters.
260 */
262 Kinesis::Lattice3D lattice,
263 std::vector<FieldDecl> fields,
264 std::optional<SurfaceConfig> surface = std::nullopt);
265
266 /**
267 * @brief Construct a volume with no fields, to be declared after.
268 * @param lattice Discretized extent every field is stored over.
269 * @param surface Optional isosurface extraction parameters. Its field
270 * name resolves at setup_processors, so it may name a field
271 * declared after construction.
272 *
273 * Extraction storage is sized here because it depends only on the
274 * extraction resolution, so the SurfaceConfig cannot move later.
275 */
276 explicit VolumeGridBuffer(
277 Kinesis::Lattice3D lattice,
278 std::optional<SurfaceConfig> surface = std::nullopt);
279
280 /**
281 * @brief Destructor.
282 *
283 * Raw handles in m_resources.back_buffers are released by the backend
284 * during buffer service teardown, as with RelaxationGridBuffer. This
285 * class performs no manual Vulkan destruction.
286 */
287 ~VolumeGridBuffer() override;
288
289 /**
290 * @brief Establish the processing chain without attaching any stage.
291 * @param token Processing domain, typically GRAPHICS_BACKEND.
292 *
293 * The volume declares no default processor and no stages of its own.
294 * Simulation identity is the sequence of processors the caller adds,
295 * not a property of this class.
296 *
297 * When a SurfaceConfig was supplied at construction, two stages are
298 * appended: the field-to-corner-grid resample and the marching cubes
299 * extraction writing into this buffer's own vertex storage. Simulation
300 * stages added before this call keep their position ahead of them.
301 */
302 void setup_processors(ProcessingToken token) override;
303
304 /**
305 * @brief Attach a RenderProcessor drawing the extracted surface.
306 * @param config Render target. Vertex and fragment shaders default to
307 * the untextured triangle pair; topology is forced to
308 * TRIANGLE_LIST.
309 *
310 * Requires a SurfaceConfig at construction. Without one this buffer
311 * has no vertex storage and nothing to draw.
312 */
313 void setup_rendering(const RenderConfig& config);
314
315 /**
316 * @brief Build and append the incompressible flow stages.
317 * @param config Fields and parameters. Every ref must have been issued
318 * by this volume.
319 * @return The stages built, all already in the chain.
320 *
321 * Appends in the order: self-advection, buoyancy, diffusion, wall,
322 * divergence, pressure, solenoidal, wall, then one advection per
323 * carried scalar. Carriers run last so they are transported by the
324 * projected velocity.
325 *
326 * Stages added to the chain before this call keep their position
327 * ahead of it, which is where an influx stage belongs.
328 */
329 FlowStages setup_flow(const FlowConfig& config);
330
331 /**
332 * @brief Add an influx stage to this volume's chain and return it.
333 * @param field Name of the double buffered field added into.
334 * @param spot Placement and strength.
335 * @param shader Shader defining shape(). The default is a soft round blob.
336 * @return The stage, already in the chain, or nullptr if the volume is not
337 * registered yet. Retune it with its own setters.
338 *
339 * Call after registration. A stage added before setup_flow runs ahead of
340 * the flow stages, which is where an influx belongs. One added after runs
341 * behind them, so what it adds is not transported until the next cycle.
342 */
343 std::shared_ptr<InfluxProcessor> influx(
344 const std::string& field,
345 const InfluxProcessor::Spot& spot,
346 const std::string& shader = "volume_influx_blob.comp.spv");
347
348 /** @brief The surface extraction stage, valid after setup_rendering. */
349 [[nodiscard]] std::shared_ptr<VolumeSurfaceProcessor> surface_processor() const { return m_surface_processor; }
350
351 /** @brief The marching cubes stage, valid after setup_rendering. */
352 [[nodiscard]] std::shared_ptr<SDFMeshProcessor> mesh_processor() const { return m_mesh_processor; }
353
354 /**
355 * @brief The live vertex count of the extracted surface from the most recent dispatch.
356 *
357 * The buffer's allocation is worst-case capacity, not this count.
358 *
359 * @return The count, or nullopt when no surface is configured or its counter is not mapped.
360 */
361 [[nodiscard]] std::optional<uint32_t> get_live_vertex_count() const;
362
363 /** @brief The lattice every field is discretized over. */
364 [[nodiscard]] const Kinesis::Lattice3D& get_lattice() const { return m_lattice; }
365
366 /** @brief Cell count along X. */
367 [[nodiscard]] uint32_t get_width() const { return m_lattice.resolution.x; }
368
369 /** @brief Cell count along Y. */
370 [[nodiscard]] uint32_t get_height() const { return m_lattice.resolution.y; }
371
372 /** @brief Cell count along Z. */
373 [[nodiscard]] uint32_t get_depth() const { return m_lattice.resolution.z; }
374
375 /** @brief Total cell count. */
376 [[nodiscard]] uint32_t get_cell_count() const { return static_cast<uint32_t>(m_lattice.cell_count()); }
377
378 /** @brief World-space extent the lattice covers. */
379 [[nodiscard]] const Kinesis::AABB3D& get_bounds() const { return m_lattice.bounds; }
380
381 /** @brief World-space size of one cell along each axis. */
382 [[nodiscard]] glm::vec3 get_cell_size() const { return m_lattice.cell_size(); }
383
384 /** @brief Whether a field of this name was declared. */
385 [[nodiscard]] bool has_field(const std::string& name) const;
386
387 /** @brief Byte size of one slot of the named field, or 0 if undeclared. */
388 [[nodiscard]] size_t get_field_bytes(const std::string& name) const;
389
390 /** @brief Bytes per cell of the named field, or 0 if undeclared. */
391 [[nodiscard]] size_t get_field_stride(const std::string& name) const;
392
393 /**
394 * @brief Interpretation declared for the named field.
395 * @param name Field name.
396 * @return The declared semantics, or a default-constructed value if
397 * undeclared.
398 *
399 * Returned as declared. A scalar field's variance member is meaningless
400 * and is not zeroed here: a consumer that cares about the distinction
401 * reads get_field_stride() alongside this.
402 */
403 [[nodiscard]] Kinesis::LatticeSemantics get_field_semantics(const std::string& name) const;
404
405 /** @brief Names of every declared field, in declaration order. */
406 [[nodiscard]] std::vector<std::string> get_field_names() const;
407
408 /**
409 * @brief Handle a stage should read the named field from.
410 * @param name Field name.
411 * @return Vulkan buffer handle, or nullptr if undeclared.
412 */
413 [[nodiscard]] vk::Buffer read_handle(const std::string& name) const;
414
415 /**
416 * @brief Handle a stage should write the named field to.
417 * @param name Field name.
418 * @return Vulkan buffer handle, or nullptr if undeclared.
419 *
420 * Equals read_handle() for single-slot fields.
421 */
422 [[nodiscard]] vk::Buffer write_handle(const std::string& name) const;
423
424 /**
425 * @brief Exchange read and write slots for the named field.
426 * @param name Field name. No effect on single-slot fields.
427 *
428 * Called by whichever stage last wrote the field, after its dispatch,
429 * so the next stage reading it observes the new values. A stage that
430 * writes a field it also reads must swap; a stage writing a scratch
431 * field consumed immediately after need not.
432 */
433 void swap_field(const std::string& name);
434
435 /**
436 * @brief Declare a double-buffered scalar field.
437 * @param name Lookup key, unique within this volume.
438 * @param semantics What the values mean. Carried for consumers that
439 * resample or write the field; nothing in this class reads it.
440 * @return Ref naming the field, or a ref with an empty name if the
441 * declaration was rejected.
442 */
443 ScalarRef declare_scalar(std::string name, Kinesis::LatticeSemantics semantics = {});
444
445 /**
446 * @brief Declare a single-slot scalar field.
447 * @param name Lookup key, unique within this volume.
448 * @param semantics What the values mean. A divergence field wants
449 * @return Ref naming the field, or a ref with an empty name if the
450 * declaration was rejected.
451 *
452 * Read and write resolve to one handle, which suits a quantity
453 * recomputed from other fields every cycle and never carried across
454 * cycles: divergence is the ordinary case. A stage that reads a
455 * neighbourhood of a field it also writes cannot use one of these,
456 * and VolumeFieldProcessor rejects that arrangement at attach.
457 */
458 ScalarRef declare_scratch(std::string name, Kinesis::LatticeSemantics semantics = {});
459
460 /**
461 * @brief Declare a double-buffered vector field.
462 * @param name Lookup key, unique within this volume.
463 * @param semantics What the values mean. A velocity field wants
464 * VectorVariance::ContravariantRelative; the default assumes
465 * three unrelated scalars.
466 * @return Ref naming the field, or a ref with an empty name if the
467 * declaration was rejected.
468 */
469 VectorRef declare_vector(std::string name, Kinesis::LatticeSemantics semantics = {});
470
471 /**
472 * @brief Declare whichever of @p decls are not already present.
473 *
474 * Built for a buffer whose fields were not all declared by the same
475 * call: IOManager::load_volume declares one field per grid a file
476 * reported, and a caller then wanting to run a simulation over that
477 * data needs velocity, pressure and the rest besides. Declaring those
478 * blind risks exactly what allocate_field already logs an error for —
479 * a name the file also happened to use, silently discarded, cascading
480 * into an empty-name ref everywhere that field is passed next. This
481 * checks first, so a name already present — whether reported by a
482 * loader or declared earlier by hand, nothing here distinguishes the
483 * two — is left alone rather than collided with.
484 *
485 * A caller who wants a ScalarRef/VectorRef for an entry already present
486 * builds one directly: `ScalarRef{name, owner}` from its own name and
487 * this buffer, the same way declare_scalar builds the ref for a field
488 * it also just allocated. This does not return refs itself, since
489 * FieldDecl does not distinguish scalar from vector cleanly enough to
490 * hand back a uniformly typed collection.
491 *
492 * @param decls Fields to ensure exist. Semantics and stride only take
493 * effect for an entry actually declared here; an entry
494 * already present keeps whatever it already has.
495 * @return Names of the entries actually declared — not already present,
496 * and not rejected for the reasons allocate_field logs. A
497 * caller seeding initial values should seed exactly these and
498 * leave every other named field's existing data untouched.
499 */
500 std::vector<std::string> ensure_fields(const std::vector<FieldDecl>& decls);
501
502 /**
503 * @brief Write initial values into a scalar field from a Kinesis field.
504 * @param name Field name. Must have stride sizeof(float).
505 * @param field Sampled at each cell centre in world space.
506 */
507 void seed(const std::string& name, const Kinesis::SpatialField& field);
508
509 /**
510 * @brief Write initial values into a vector field from a Kinesis field.
511 * @param name Field name. Must have stride sizeof(glm::vec4).
512 * @param field Sampled at each cell centre in world space. The fourth
513 * component of every cell is zeroed.
514 */
515 void seed(const std::string& name, const Kinesis::VectorField& field);
516
517 /**
518 * @brief Write initial values into a field from raw host memory.
519 * @param name Field name.
520 * @param data Pointer to at least @p size bytes.
521 * @param size Byte count; must equal get_field_bytes(name).
522 *
523 * Writes into the current read slot. For double-buffered fields the
524 * write slot is left untouched, which is correct when the first stage
525 * to touch the field reads before it writes.
526 */
527 void seed_raw(const std::string& name, const void* data, size_t size);
528
529 /**
530 * @brief Add sampled values into a scalar field.
531 * @param name Field name. Must have stride sizeof(float).
532 * @param field Sampled at each cell centre in world space and added
533 * to whatever the field already holds.
534 *
535 * Blocking. Reads the current read slot to host memory, adds, and
536 * writes back, so it costs a full round trip at the lattice's byte
537 * size. Intended for authored injection on a coarse clock, not as a
538 * chain stage.
539 */
540 void accumulate(const std::string& name, const Kinesis::SpatialField& field);
541
542 /**
543 * @brief Add sampled values into a vector field.
544 * @param name Field name. Must have stride sizeof(glm::vec4).
545 * @param field Sampled at each cell centre in world space and added
546 * to the first three components. The fourth is left as found.
547 */
548 void accumulate(const std::string& name, const Kinesis::VectorField& field);
549
550 /**
551 * @brief Copy the current read slot of a field to host memory.
552 * @param name Field name.
553 * @param data Destination pointer, at least get_field_bytes(name) bytes.
554 * @param size Byte count; must equal get_field_bytes(name).
555 *
556 * Blocking. Records a fenced device-to-host copy and waits on it from
557 * the calling thread. Not for per-frame use on the graphics thread.
558 */
559 void read_field(const std::string& name, void* data, size_t size);
560
561 /**
562 * @brief Read several fields' current values into host memory at once.
563 *
564 * One staging buffer and one fence wait for the whole batch, rather
565 * than a full round trip per field. Prefer this over repeated
566 * read_field when more than one field is wanted from the same cycle,
567 * which is the ordinary case for interchange export.
568 *
569 * Reads from each field's current read slot, as read_field does.
570 * A name that is not declared, or a size that does not match that
571 * field, is logged and skipped; the rest of the batch proceeds.
572 *
573 * @param names Field names.
574 * @param dsts Destination pointer and byte count per name, parallel
575 * to @p names.
576 */
577 void read_fields(
578 const std::vector<std::string>& names,
579 const std::vector<std::pair<void*, size_t>>& dsts);
580
581private:
589
590 /**
591 * @brief Populate m_fields from declarations.
592 * @param decls Field declarations in declaration order.
593 */
594 void allocate_fields(const std::vector<FieldDecl>& decls);
595
596 /**
597 * @brief Allocate one field's slots and register it.
598 * @param name Lookup key.
599 * @param stride_bytes Bytes per cell.
600 * @param double_buffered Whether to allocate a second slot.
601 * @return True if the field was registered.
602 */
603 bool allocate_field(const std::string& name, size_t stride_bytes, bool double_buffered);
604
605 /**
606 * @brief Resolve a field by name.
607 * @param name Field name.
608 * @param context Caller identifier used in the error path.
609 * @return Pointer to the field, or nullptr with an error logged.
610 */
611 [[nodiscard]] const Field* find_field(const std::string& name, const char* context) const;
612
614
615 std::vector<std::string> m_field_order;
616 std::unordered_map<std::string, Field> m_fields;
617 std::optional<SurfaceConfig> m_surface;
618
619 std::shared_ptr<VKBuffer> m_transfer_staging; ///< Reused across seed and read calls.
620 std::shared_ptr<VolumeSurfaceProcessor> m_surface_processor;
621 std::shared_ptr<SDFMeshProcessor> m_mesh_processor;
622 std::shared_ptr<VKBuffer> m_counter_buf; ///< Atomic vertex counter for the extraction stage.
623
624 TransferHandle m_pending_transfer; ///< In-flight seed upload, resolved before the next transfer.
625
626 /**
627 * @brief Bytes of vertex storage the extraction resolution requires.
628 * @param surface Extraction parameters, or nullopt for no extraction.
629 * @return Worst-case vertex bytes, or 1 when no surface is configured.
630 */
631 static size_t surface_storage_bytes(const std::optional<SurfaceConfig>& surface);
632
633 /**
634 * @brief Allocate one field's slots and register it.
635 * @param name Lookup key.
636 * @param stride_bytes Bytes per cell.
637 * @param double_buffered Whether to allocate a second slot.
638 * @param semantics What the values mean. Stored and never acted on.
639 * @return True if the field was registered.
640 */
641 bool allocate_field(
642 const std::string& name,
643 size_t stride_bytes,
644 bool double_buffered,
645 Kinesis::LatticeSemantics semantics);
646};
647
648} // namespace MayaFlux::Buffers
float density_gain
float ambient
float temperature_gain
bool size
float time_step
std::string name
Definition VKDevice.cpp:123
Vulkan-backed buffer wrapper used in processing chains.
Definition VKBuffer.hpp:76
uint32_t get_width() const
Cell count along X.
const Kinesis::Lattice3D & get_lattice() const
The lattice every field is discretized over.
std::unordered_map< std::string, Field > m_fields
void accumulate(const std::string &name, const Kinesis::VectorField &field)
Add sampled values into a vector field.
TransferHandle m_pending_transfer
In-flight seed upload, resolved before the next transfer.
uint32_t get_cell_count() const
Total cell count.
uint32_t get_depth() const
Cell count along Z.
const Kinesis::AABB3D & get_bounds() const
World-space extent the lattice covers.
glm::vec3 get_cell_size() const
World-space size of one cell along each axis.
std::optional< SurfaceConfig > m_surface
std::shared_ptr< SDFMeshProcessor > mesh_processor() const
The marching cubes stage, valid after setup_rendering.
std::shared_ptr< VolumeSurfaceProcessor > surface_processor() const
The surface extraction stage, valid after setup_rendering.
std::shared_ptr< VKBuffer > m_transfer_staging
Reused across seed and read calls.
std::shared_ptr< VolumeSurfaceProcessor > m_surface_processor
std::vector< std::string > m_field_order
void seed(const std::string &name, const Kinesis::VectorField &field)
Write initial values into a vector field from a Kinesis field.
bool allocate_field(const std::string &name, size_t stride_bytes, bool double_buffered)
Allocate one field's slots and register it.
std::shared_ptr< VKBuffer > m_counter_buf
Atomic vertex counter for the extraction stage.
std::shared_ptr< SDFMeshProcessor > m_mesh_processor
uint32_t get_height() const
Cell count along Y.
GPU-resident state for multi-field simulations evaluated over a fixed 3D topology: incompressible flu...
std::shared_ptr< void > TransferHandle
Opaque handle to an in-flight transfer.
ProcessingToken
Bitfield enum defining processing characteristics and backend requirements for buffer operations.
Where and how an influx adds, in one aggregate, for configure().
std::weak_ptr< VolumeGridBuffer > owner
static constexpr size_t stride
bool issued_by(const VolumeGridBuffer *volume) const
Whether this ref was issued by the given volume.
A scalar field's name, issued by the volume that owns it.
static constexpr size_t stride
bool issued_by(const VolumeGridBuffer *volume) const
Whether this ref was issued by the given volume.
std::weak_ptr< VolumeGridBuffer > owner
A vector field's name, issued by the volume that owns it.
std::string name
Lookup key, unique within the volume.
One field's declaration within a volume.
Body force accumulated into velocity from two scalars.
A scalar transported by the velocity field.
VectorRef scratch
Required only when viscosity is above zero.
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.
std::string field_name
Scalar field surfaced. Stride must be sizeof(float).
glm::uvec3 resolution
Extraction cell count per axis, over the volume's own bounds.
float threshold
Field value the surface is placed at.
Isosurface extraction parameters for a scalar field.
Axis-aligned bounding box in 3D world space.
Definition Bounds.hpp:188
A regular subdivision of an AABB3D into a cell count per axis.
Definition Lattice.hpp:25
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.