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,
size_t stride_bytes,
bool double_buffered)
64 "VolumeGridBuffer: field declared with empty name, skipped");
68 if (stride_bytes == 0) {
70 "VolumeGridBuffer: field '{}' declares zero stride, skipped", name);
76 "VolumeGridBuffer: duplicate field '{}', later declaration discarded", name);
83 if (!buffer_service || !buffer_service->allocate_raw_buffer) {
84 error<std::runtime_error>(
87 std::source_location::current(),
88 "VolumeGridBuffer requires a valid buffer service");
91 const auto usage_flags =
static_cast<uint32_t
>(
92 static_cast<VkBufferUsageFlags
>(
93 vk::BufferUsageFlagBits::eStorageBuffer
94 | vk::BufferUsageFlagBits::eTransferSrc
95 | vk::BufferUsageFlagBits::eTransferDst));
97 const auto memory_flags =
static_cast<uint32_t
>(
98 static_cast<VkMemoryPropertyFlags
>(vk::MemoryPropertyFlagBits::eDeviceLocal));
101 const size_t field_bytes =
static_cast<size_t>(
get_cell_count()) * stride_bytes;
102 const uint32_t slot_count = double_buffered ? 2 : 1;
106 .slot_a =
static_cast<uint32_t
>(resources.back_buffers.size()),
111 for (uint32_t i = 0; i < slot_count; ++i) {
112 void* out_buffer =
nullptr;
113 void* out_memory =
nullptr;
114 void* out_mapped =
nullptr;
116 buffer_service->allocate_raw_buffer(
117 field_bytes, usage_flags, memory_flags,
false,
118 out_buffer, out_memory, out_mapped);
121 slot.
buffer =
static_cast<vk::Buffer
>(
static_cast<VkBuffer
>(out_buffer));
122 slot.
memory =
static_cast<vk::DeviceMemory
>(
static_cast<VkDeviceMemory
>(out_memory));
125 resources.back_buffers.push_back(slot);
128 field.slot_b = double_buffered ? field.slot_a + 1 : field.slot_a;
134 "VolumeGridBuffer: field '{}', stride {}, {} slot(s), {} bytes",
135 name, stride_bytes, slot_count, field_bytes * slot_count);
142 for (
const auto& decl : decls) {
143 allocate_field(decl.name, decl.stride_bytes, decl.double_buffered);
147 error<std::runtime_error>(
150 std::source_location::current(),
151 "VolumeGridBuffer constructed with no valid fields");
155 "VolumeGridBuffer: {}x{}x{} lattice, {} fields, {} slots",
167 .
name = std::move(name),
168 .owner = std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this()),
179 .
name = std::move(name),
180 .owner = std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this()),
191 .
name = std::move(name),
192 .owner = std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this()),
200 chain = std::make_shared<BufferProcessingChain>();
203 chain->set_preferred_token(token);
207 "VolumeGridBuffer: chain established, no extraction configured");
211 auto self = std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this());
232 layout.vertex_count = max_vertices;
242 "VolumeGridBuffer: surfacing '{}' at {}x{}x{}, {} max vertices",
247 "VolumeGridBuffer: chain established, no stages attached");
254 "setup_rendering: no SurfaceConfig was supplied at construction");
278 auto self = std::dynamic_pointer_cast<VolumeGridBuffer>(shared_from_this());
283 "setup_flow: no processing chain, call after registration");
287 const auto require = [
this](
const std::string& name,
const char* role) {
290 "setup_flow: no field supplied for '{}'", role);
295 "setup_flow: '{}' names no field on this volume, supplied as '{}'",
302 if (!require(config.
velocity,
"velocity")
304 || !require(config.
pressure,
"pressure")) {
312 stages.
self_advect = std::make_shared<AdvectProcessor>(
320 if (!require(
b.temperature,
"buoyancy.temperature")
321 || !require(
b.density,
"buoyancy.density")) {
325 stages.
buoyancy = std::make_shared<BuoyancyProcessor>(
327 b.direction,
"volume_buoyancy.comp.spv");
329 stages.
buoyancy->set_temperature_gain(
b.temperature_gain);
330 stages.
buoyancy->set_density_gain(
b.density_gain);
332 chain->add_processor(stages.
buoyancy, self);
336 stages.
diffuse = std::make_shared<DiffuseProcessor>(
340 chain->add_processor(stages.
diffuse, self);
345 config.
velocity,
"volume_wall.comp.spv");
349 stages.
divergence = std::make_shared<DivergenceProcessor>(
351 chain->add_processor(stages.
divergence, self);
353 stages.
pressure = std::make_shared<PressureProcessor>(
356 chain->add_processor(stages.
pressure, self);
358 stages.
solenoidal = std::make_shared<SolenoidalProcessor>(
360 chain->add_processor(stages.
solenoidal, self);
364 config.
velocity,
"volume_wall.comp.spv");
368 for (
const auto& carried : config.
carried) {
369 if (!require(carried.field,
"carried")) {
373 auto advect = std::make_shared<AdvectProcessor>(
374 config.
velocity, carried.field,
"volume_advect_scalar.comp.spv");
376 advect->set_dissipation(carried.dissipation);
377 chain->add_processor(advect, self);
379 stages.
carriers.push_back(std::move(advect));
383 "VolumeGridBuffer::setup_flow: {} carried, buoyancy {}, viscosity {}, walls {}",
391 const std::string& name,
const char* context)
const
396 "VolumeGridBuffer::{}: no field named '{}'", context, name);
413 return static_cast<size_t>(
get_cell_count()) * it->second.stride_bytes;
423 const auto* field =
find_field(name,
"read_handle");
428 const uint32_t slot = field->read_is_a ? field->slot_a : field->slot_b;
434 const auto* field =
find_field(name,
"write_handle");
439 const uint32_t slot = field->read_is_a ? field->slot_b : field->slot_a;
448 "VolumeGridBuffer::swap_field: no field named '{}'", name);
452 if (it->second.slot_a == it->second.slot_b) {
456 it->second.read_is_a = !it->second.read_is_a;
466 if (decl->stride_bytes !=
sizeof(
float)) {
468 "VolumeGridBuffer::seed: field '{}' has stride {}, SpatialField requires {}",
469 name, decl->stride_bytes,
sizeof(
float));
477 for (uint32_t z = 0; z < res.z; ++z) {
478 for (uint32_t y = 0; y < res.y; ++y) {
479 for (uint32_t x = 0; x < res.x; ++x) {
485 seed_raw(name, values.data(), values.size() *
sizeof(
float));
495 if (decl->stride_bytes !=
sizeof(glm::vec4)) {
497 "VolumeGridBuffer::seed: field '{}' has stride {}, VectorField requires {}",
498 name, decl->stride_bytes,
sizeof(glm::vec4));
506 for (uint32_t z = 0; z < res.z; ++z) {
507 for (uint32_t y = 0; y < res.y; ++y) {
508 for (uint32_t x = 0; x < res.x; ++x) {
514 seed_raw(name, values.data(), values.size() *
sizeof(glm::vec4));
519 const auto* field =
find_field(name,
"seed_raw");
524 const size_t expected =
static_cast<size_t>(
get_cell_count()) * field->stride_bytes;
525 if (size != expected) {
527 "VolumeGridBuffer::seed_raw: size {} does not match expected {} for '{}'",
528 size, expected, name);
534 const uint32_t slot = field->read_is_a ? field->slot_a : field->slot_b;
541 const auto* decl =
find_field(name,
"accumulate");
546 if (decl->stride_bytes !=
sizeof(
float)) {
548 "VolumeGridBuffer::accumulate: field '{}' has stride {}, SpatialField requires {}",
549 name, decl->stride_bytes,
sizeof(
float));
556 read_field(name, values.data(), values.size() *
sizeof(
float));
559 for (uint32_t z = 0; z < res.z; ++z) {
560 for (uint32_t y = 0; y < res.y; ++y) {
561 for (uint32_t x = 0; x < res.x; ++x) {
567 seed_raw(name, values.data(), values.size() *
sizeof(
float));
572 const auto* decl =
find_field(name,
"accumulate");
577 if (decl->stride_bytes !=
sizeof(glm::vec4)) {
579 "VolumeGridBuffer::accumulate: field '{}' has stride {}, VectorField requires {}",
580 name, decl->stride_bytes,
sizeof(glm::vec4));
587 read_field(name, values.data(), values.size() *
sizeof(glm::vec4));
590 for (uint32_t z = 0; z < res.z; ++z) {
591 for (uint32_t y = 0; y < res.y; ++y) {
592 for (uint32_t x = 0; x < res.x; ++x) {
602 seed_raw(name, values.data(), values.size() *
sizeof(glm::vec4));
607 const auto* field =
find_field(name,
"read_field");
612 const size_t expected =
static_cast<size_t>(
get_cell_count()) * field->stride_bytes;
613 if (size != expected) {
615 "VolumeGridBuffer::read_field: size {} does not match expected {} for '{}'",
616 size, expected, name);
620 const uint32_t slot = field->read_is_a ? field->slot_a : field->slot_b;
631 const glm::uvec3 res = glm::max(surface->resolution, glm::uvec3(1U));
632 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,...)
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.
uint32_t get_cell_count() const
Total cell count.
Kinesis::Lattice3D m_lattice
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.
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.
~VolumeGridBuffer() override
Destructor.
void read_field(const std::string &name, void *data, size_t size)
Copy the current read slot of a field to host memory.
VectorRef declare_vector(std::string name)
Declare a double-buffered vector field.
ScalarRef declare_scalar(std::string name)
Declare a double-buffered scalar field.
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.
ScalarRef declare_scratch(std::string name)
Declare a single-slot scalar field.
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.
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.
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.
@ 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.
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.
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.