26 std::initializer_list<FieldDecl> fields,
27 std::optional<SurfaceConfig> surface)
34 std::vector<FieldDecl> fields,
35 std::optional<SurfaceConfig> surface)
36 :
VKBuffer(surface_storage_bytes(surface),
Usage::VERTEX, Kakshya::DataModality::VERTICES_3D)
38 , m_surface(
std::move(surface))
46 std::optional<SurfaceConfig> surface)
47 :
VKBuffer(surface_storage_bytes(surface),
Usage::VERTEX, Kakshya::DataModality::VERTICES_3D)
49 , m_surface(
std::move(surface))
60 const std::string&
name,
67 "VolumeGridBuffer: field declared with empty name, skipped");
71 if (stride_bytes == 0) {
73 "VolumeGridBuffer: field '{}' declares zero stride, skipped",
name);
79 "VolumeGridBuffer: duplicate field '{}', later declaration discarded",
name);
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");
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));
100 const auto memory_flags =
static_cast<uint32_t
>(
101 static_cast<VkMemoryPropertyFlags
>(vk::MemoryPropertyFlagBits::eDeviceLocal));
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;
109 .slot_a =
static_cast<uint32_t
>(resources.back_buffers.size()),
112 .semantics = semantics,
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;
120 buffer_service->allocate_raw_buffer(
121 field_bytes, usage_flags, memory_flags,
false,
122 out_buffer, out_memory, out_mapped);
125 slot.
buffer =
static_cast<vk::Buffer
>(
static_cast<VkBuffer
>(out_buffer));
126 slot.
memory =
static_cast<vk::DeviceMemory
>(
static_cast<VkDeviceMemory
>(out_memory));
129 resources.back_buffers.push_back(slot);
132 field.slot_b = double_buffered ? field.slot_a + 1 : field.slot_a;
138 "VolumeGridBuffer: field '{}', stride {}, {} slot(s), {} bytes, class {}",
139 name, stride_bytes, slot_count, field_bytes * slot_count,
147 for (
const auto& decl : decls) {
148 allocate_field(decl.name, decl.stride_bytes, decl.double_buffered, decl.semantics);
152 error<std::runtime_error>(
155 std::source_location::current(),
156 "VolumeGridBuffer constructed with no valid fields");
160 "VolumeGridBuffer: {}x{}x{} lattice, {} fields, {} slots",
173 .owner = std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this()),
185 .owner = std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this()),
197 .owner = std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this()),
203 std::vector<std::string> declared;
204 declared.reserve(decls.size());
206 for (
const auto& decl : decls) {
209 "VolumeGridBuffer::ensure_fields: '{}' already present, reused", decl.name);
213 if (
allocate_field(decl.name, decl.stride_bytes, decl.double_buffered, decl.semantics)) {
214 declared.push_back(decl.name);
225 chain = std::make_shared<BufferProcessingChain>();
228 chain->set_preferred_token(token);
232 "VolumeGridBuffer: chain established, no extraction configured");
236 auto self = std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this());
257 layout.vertex_count = max_vertices;
267 "VolumeGridBuffer: surfacing '{}' at {}x{}x{}, {} max vertices",
272 "VolumeGridBuffer: chain established, no stages attached");
279 "setup_rendering: no SurfaceConfig was supplied at construction");
300 const std::string& field,
302 const std::string& shader)
307 "influx: no processing chain, call after registration");
311 auto stage = std::make_shared<InfluxProcessor>(field, shader, spot);
312 chain->add_processor(stage, std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this()));
320 auto self = std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this());
325 "setup_flow: no processing chain, call after registration");
329 const auto require = [
this](
const std::string&
name,
const char* role) {
332 "setup_flow: no field supplied for '{}'", role);
337 "setup_flow: '{}' names no field on this volume, supplied as '{}'",
344 if (!require(config.
velocity,
"velocity")
346 || !require(config.
pressure,
"pressure")) {
354 stages.
self_advect = std::make_shared<AdvectProcessor>(
362 if (!require(
b.temperature,
"buoyancy.temperature")
363 || !require(
b.density,
"buoyancy.density")) {
367 stages.
buoyancy = std::make_shared<BuoyancyProcessor>(
369 b.direction,
"volume_buoyancy.comp.spv");
371 stages.
buoyancy->set_temperature_gain(
b.temperature_gain);
372 stages.
buoyancy->set_density_gain(
b.density_gain);
378 stages.
diffuse = std::make_shared<DiffuseProcessor>(
387 config.
velocity,
"volume_wall.comp.spv");
391 stages.
divergence = std::make_shared<DivergenceProcessor>(
395 stages.
pressure = std::make_shared<PressureProcessor>(
400 stages.
solenoidal = std::make_shared<SolenoidalProcessor>(
406 config.
velocity,
"volume_wall.comp.spv");
410 for (
const auto& carried : config.
carried) {
411 if (!require(carried.field,
"carried")) {
415 auto advect = std::make_shared<AdvectProcessor>(
416 config.
velocity, carried.field,
"volume_advect_scalar.comp.spv");
418 advect->set_dissipation(carried.dissipation);
419 chain->add_processor(advect, self);
421 stages.
carriers.push_back(std::move(advect));
425 "VolumeGridBuffer::setup_flow: {} carried, buoyancy {}, viscosity {}, walls {}",
433 const std::string&
name,
const char* context)
const
438 "VolumeGridBuffer::{}: no field named '{}'", context,
name);
455 return static_cast<size_t>(
get_cell_count()) * it->second.stride_bytes;
464 return it->second.stride_bytes;
473 return it->second.semantics;
488 const uint32_t slot = field->read_is_a ? field->slot_a : field->slot_b;
499 const uint32_t slot = field->read_is_a ? field->slot_b : field->slot_a;
508 "VolumeGridBuffer::swap_field: no field named '{}'",
name);
512 if (it->second.slot_a == it->second.slot_b) {
516 it->second.read_is_a = !it->second.read_is_a;
526 if (decl->stride_bytes !=
sizeof(
float)) {
528 "VolumeGridBuffer::seed: field '{}' has stride {}, SpatialField requires {}",
529 name, decl->stride_bytes,
sizeof(
float));
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) {
555 if (decl->stride_bytes !=
sizeof(glm::vec4)) {
557 "VolumeGridBuffer::seed: field '{}' has stride {}, VectorField requires {}",
558 name, decl->stride_bytes,
sizeof(glm::vec4));
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) {
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 '{}'",
594 const uint32_t slot = field->read_is_a ? field->slot_a : field->slot_b;
606 if (decl->stride_bytes !=
sizeof(
float)) {
608 "VolumeGridBuffer::accumulate: field '{}' has stride {}, SpatialField requires {}",
609 name, decl->stride_bytes,
sizeof(
float));
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) {
637 if (decl->stride_bytes !=
sizeof(glm::vec4)) {
639 "VolumeGridBuffer::accumulate: field '{}' has stride {}, VectorField requires {}",
640 name, decl->stride_bytes,
sizeof(glm::vec4));
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) {
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 '{}'",
680 const uint32_t slot = field->read_is_a ? field->slot_a : field->slot_b;
686 const std::vector<std::string>& names,
687 const std::vector<std::pair<void*, size_t>>& dsts)
689 if (names.size() != dsts.size()) {
691 "VolumeGridBuffer::read_fields: {} names against {} destinations",
692 names.size(), dsts.size());
696 constexpr size_t k_alignment = 16;
700 std::vector<size_t> offsets;
703 for (
size_t i = 0; i < names.size(); ++i) {
704 const auto* field =
find_field(names[i],
"read_fields");
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]);
718 offsets.push_back(
total);
719 total += (dsts[i].second + k_alignment - 1) & ~(k_alignment - 1);
732 std::vector<TransferHandle> handles;
733 handles.reserve(
indices.size());
735 for (
size_t k = 0; k <
indices.size(); ++k) {
737 const uint32_t slot = field->read_is_a ? field->slot_a : field->slot_b;
739 resources.back_buffers[slot], dsts[
indices[k]].second,
743 for (
size_t k = 0; k <
indices.size(); ++k) {
745 const uint32_t slot = field->read_is_a ? field->slot_a : field->slot_b;
747 handles[k], resources.back_buffers[slot],
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;
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
std::vector< uint32_t > indices
BufferProcessingChain * chain
std::vector< std::byte > values
void set_vertex_layout(const Kakshya::VertexLayout &layout)
Set vertex layout for this buffer.
std::shared_ptr< Buffers::BufferProcessingChain > get_processing_chain() override
Access the buffer's processing chain.
void set_default_processor(const std::shared_ptr< BufferProcessor > &processor) override
Set the buffer's default processor.
void set_processing_chain(const std::shared_ptr< BufferProcessingChain > &chain, bool force=false) override
Replace the buffer's processing chain.
void set_needs_depth_attachment(bool needs)
Mark this buffer as requiring depth testing when rendered.
void apply_render_config(const RenderConfig &config, const ShaderConfig &shader_config)
Configure the internal m_render_processor from a RenderConfig.
std::shared_ptr< RenderProcessor > m_render_processor
const VKBufferResources & get_buffer_resources() const
Get all buffer resources at once (read-only)
void force_internal_usage(bool internal) override
Set whether this buffer is for internal engine usage.
Vulkan-backed buffer wrapper used in processing chains.
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.
Kinesis::Lattice3D m_lattice
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.
~VolumeGridBuffer() override
Destructor.
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
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.
ScalarRef pressure
Required.
VectorRef velocity
Required.
VectorRef scratch
Required only when viscosity is above zero.
float viscosity
Zero omits the diffusion stage entirely.
std::optional< Buoyancy > buoyancy
std::vector< Carried > carried
uint32_t jacobi_iterations
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
std::shared_ptr< WallProcessor > wall_advected
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.
AABB3D bounds
Continuous extent subdivided.
size_t cell_count() const noexcept
Total cell count.
glm::uvec3 resolution
Cell count per axis. Zero on any axis is invalid.
glm::vec3 cell_center(const glm::uvec3 &c) const noexcept
World position of a cell's centre.
A regular subdivision of an AABB3D into a cell count per axis.
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.
PrimitiveTopology topology
std::string vertex_shader
std::string fragment_shader
Unified rendering configuration for graphics buffers.
Backend buffer management service interface.