18 const std::shared_ptr<NetworkGeometryBuffer>& buffer,
float cell_size)
22 "SpatialHashConfig::from_network: cell_size {} is not positive",
cell_size);
26 const auto network = buffer->get_network();
29 if (
auto particle_net = std::dynamic_pointer_cast<Nodes::Network::ParticleNetwork>(
network)) {
30 bounds = particle_net->get_bounds();
31 }
else if (
auto cloud_net = std::dynamic_pointer_cast<Nodes::Network::PointCloudNetwork>(
network)) {
32 bounds = cloud_net->get_bounds();
35 "SpatialHashConfig::from_network: buffer's network is neither a ParticleNetwork "
36 "nor a PointCloudNetwork");
43 "SpatialHashConfig::from_network: network's primary operator is not a "
44 "GraphicsOperator, or none is set. create_operator<PhysicsOperator>() (or "
45 "equivalent) must run before this.");
49 const auto layout = graphics_op->get_vertex_layout();
51 if (layout.stride_bytes == 0 || layout.stride_bytes % 4 != 0) {
53 "SpatialHashConfig::from_network: stride {} is not a nonzero multiple of 4",
59 if (!position_offset.has_value()) {
61 "SpatialHashConfig::from_network: vertex layout carries no word-aligned "
62 "position attribute");
66 const glm::vec3 extent = bounds.
max - bounds.
min;
68 const glm::uvec3 dims {
69 std::max(1U,
static_cast<uint32_t
>(std::ceil(extent.x /
cell_size))),
70 std::max(1U,
static_cast<uint32_t
>(std::ceil(extent.y /
cell_size))),
71 std::max(1U,
static_cast<uint32_t
>(std::ceil(extent.z /
cell_size))),
78 .particle_count =
static_cast<uint32_t
>(graphics_op->get_vertex_count()),
80 .position_word_offset = *position_offset,
87 buffer->declare_state(
"hash_cell_count", cells,
sizeof(uint32_t),
false);
88 buffer->declare_state(
"hash_cell_start", cells,
sizeof(uint32_t),
false);
89 buffer->declare_state(
"hash_cell_cursor", cells,
sizeof(uint32_t),
false);
90 buffer->declare_state(
"hash_particle_index",
particle_count,
sizeof(uint32_t),
false);
92 if (!buffer->has_state(
"hash_cluster_id")) {
93 buffer->declare_state(
"hash_cluster_id",
particle_count,
sizeof(uint32_t),
false);
104 , m_bindings(
std::move(bindings))
113 =
ShaderBinding(0, entry.binding, vk::DescriptorType::eStorageBuffer);
120 if (entry.
field.empty()) {
124 MF_ERROR(Journal::Component::Buffers, Journal::Context::BufferProcessing,
125 "NetworkStateFieldProcessor: buffer has no state field '{}' for binding '{}'",
126 entry.field, entry.name);
137 m_buffer = std::dynamic_pointer_cast<NetworkGeometryBuffer>(buffer);
159 vk::Buffer handle {};
162 if (entry.field.empty()) {
166 handle =
m_buffer->read_state_handle(entry.field);
167 bytes =
m_buffer->get_state_bytes(entry.field);
170 foundry.update_descriptor_buffer(
192 const std::shared_ptr<VKBuffer>& buffer)
194 return m_buffer && std::dynamic_pointer_cast<NetworkGeometryBuffer>(buffer) !=
nullptr;
216 void add_cell_of_function(ShaderSpec::Assemble& assemble)
219 body +=
" ivec3 c = ivec3(floor((p - grid_min) / cell_size));\n";
220 body +=
" ivec3 d = ivec3(dims) - ivec3(1);\n";
221 body +=
" c = clamp(c, ivec3(0), d);\n";
222 body +=
" return uint(c.x) + uint(c.y) * dims.x + uint(c.z) * dims.x * dims.y;\n";
224 assemble.function(
"uint",
"cell_of",
225 "vec3 p, vec3 grid_min, float cell_size, uvec3 dims", std::move(body));
236 ShaderSpec build_clear_spec()
238 ShaderSpec::Assemble assemble;
245 body +=
" if (i < cell_count_total) {\n";
246 body +=
" cell_count[i] = 0u;\n";
249 assemble.
kernel(KernelSource { .body = std::move(body) });
251 return assemble.build();
258 {
FieldBinding { .
name =
"cell_count", .binding = 0, .field =
"hash_cell_count" } },
260 , m_params { .cell_count_total = config.cell_count() }
275 ShaderSpec build_count_spec(
bool gate_alive)
277 ShaderSpec::Assemble assemble;
299 add_cell_of_function(assemble);
302 body +=
" if (i >= particle_count) { return; }\n";
304 body +=
" if (alive[i] == 0u) { return; }\n";
306 body +=
" uint b = i * stride_words;\n";
307 body +=
" vec3 p = vec3(vertices[b + position_offset], "
308 "vertices[b + position_offset + 1u], vertices[b + position_offset + 2u]);\n";
309 body +=
" vec3 gmin = vec3(grid_min_x, grid_min_y, grid_min_z);\n";
310 body +=
" uvec3 dims = uvec3(dim_x, dim_y, dim_z);\n";
311 body +=
" uint cell = cell_of(p, gmin, cell_size, dims);\n";
312 body +=
" atomicAdd(cell_count[cell], 1u);\n";
314 assemble.kernel(KernelSource { .body = std::move(body) });
316 return assemble.build();
319 std::vector<NetworkStateFieldProcessor::FieldBinding> count_bindings(
bool gate_alive)
321 std::vector<NetworkStateFieldProcessor::FieldBinding> bindings {
322 { .name =
"vertices", .binding = 0, .field = {} },
323 { .name =
"cell_count", .binding = 1, .field =
"hash_cell_count" },
326 bindings.push_back({ .name =
"alive", .binding = 2, .field =
"mutation_alive" });
350 ShaderSpec build_scan_spec()
352 ShaderSpec::Assemble assemble;
361 body +=
" if (i == 0u) {\n";
362 body +=
" uint running = 0u;\n";
363 body +=
" for (uint c = 0u; c < cell_count_total; c = c + 1u) {\n";
364 body +=
" cell_start[c] = running;\n";
365 body +=
" cell_cursor[c] = running;\n";
366 body +=
" running = running + cell_count[c];\n";
370 assemble.kernel(KernelSource { .body = std::move(body) });
372 return assemble.build();
379 {
FieldBinding { .
name =
"cell_count", .binding = 0, .field =
"hash_cell_count" },
380 FieldBinding { .
name =
"cell_start", .binding = 1, .field =
"hash_cell_start" },
381 FieldBinding { .
name =
"cell_cursor", .binding = 2, .field =
"hash_cell_cursor" } },
383 , m_params { .cell_count_total = config.cell_count() }
399 ShaderSpec build_scatter_spec(
bool gate_alive)
401 ShaderSpec::Assemble assemble;
424 add_cell_of_function(assemble);
427 body +=
" if (i >= particle_count) { return; }\n";
429 body +=
" if (alive[i] == 0u) { return; }\n";
431 body +=
" uint b = i * stride_words;\n";
432 body +=
" vec3 p = vec3(vertices[b + position_offset], "
433 "vertices[b + position_offset + 1u], vertices[b + position_offset + 2u]);\n";
434 body +=
" vec3 gmin = vec3(grid_min_x, grid_min_y, grid_min_z);\n";
435 body +=
" uvec3 dims = uvec3(dim_x, dim_y, dim_z);\n";
436 body +=
" uint cell = cell_of(p, gmin, cell_size, dims);\n";
437 body +=
" uint slot = atomicAdd(cell_cursor[cell], 1u);\n";
438 body +=
" particle_index[slot] = i;\n";
440 assemble.kernel(KernelSource { .body = std::move(body) });
442 return assemble.build();
445 std::vector<NetworkStateFieldProcessor::FieldBinding> scatter_bindings(
bool gate_alive)
447 std::vector<NetworkStateFieldProcessor::FieldBinding> bindings {
448 { .name =
"vertices", .binding = 0, .field = {} },
449 { .name =
"cell_cursor", .binding = 1, .field =
"hash_cell_cursor" },
450 { .name =
"particle_index", .binding = 2, .field =
"hash_particle_index" },
453 bindings.push_back({ .name =
"alive", .binding = 3, .field =
"mutation_alive" });
477 ShaderSpec build_density_spec()
479 ShaderSpec::Assemble assemble;
502 body +=
" if (i >= particle_count) { return; }\n";
503 body +=
" uint b = i * stride_words;\n";
504 body +=
" vec3 p = vec3(vertices[b + position_offset], "
505 "vertices[b + position_offset + 1u], vertices[b + position_offset + 2u]);\n";
506 body +=
" uint my_cluster = cluster_id[i];\n";
508 body +=
" uint neighbor_count = 0u;\n";
510 .cluster_scoped =
true,
511 .on_hit =
"neighbor_count = neighbor_count + 1u;",
514 body +=
" float density = clamp(float(neighbor_count) / density_saturation_count, 0.0, 1.0);\n";
515 body +=
" vec3 ember = vec3(0.03, 0.0, 0.06);\n";
516 body +=
" vec3 fire = vec3(0.95, 0.25, 0.02);\n";
517 body +=
" vec3 white_hot = vec3(1.0, 0.95, 0.7);\n";
518 body +=
" vec3 col = density < 0.5\n";
519 body +=
" ? mix(ember, fire, density * 2.0)\n";
520 body +=
" : mix(fire, white_hot, (density - 0.5) * 2.0);\n";
521 body +=
" vertices[b + color_offset] = col.x;\n";
522 body +=
" vertices[b + color_offset + 1u] = col.y;\n";
523 body +=
" vertices[b + color_offset + 2u] = col.z;\n";
525 assemble.kernel(KernelSource { .body = std::move(body) });
527 return assemble.build();
539 uint32_t require_color_offset(
540 const std::shared_ptr<Nodes::Network::GpuFieldOperator>& particle_op)
543 error<std::invalid_argument>(
546 std::source_location::current(),
547 "HashDensityColorProcessor: null particle operator");
551 if (!
offset.has_value()) {
552 error<std::invalid_argument>(
555 std::source_location::current(),
556 "HashDensityColorProcessor: vertex layout carries no word-aligned colour attribute");
566 std::shared_ptr<Nodes::Network::GpuFieldOperator> particle_op)
569 FieldBinding { .
name =
"cell_start", .binding = 1, .field =
"hash_cell_start" },
570 FieldBinding { .
name =
"cell_count", .binding = 2, .field =
"hash_cell_count" },
571 FieldBinding { .
name =
"particle_index", .binding = 3, .field =
"hash_particle_index" },
572 FieldBinding { .
name =
"cluster_id", .binding = 4, .field =
"hash_cluster_id" } },
573 build_density_spec())
575 .particle_count = config.particle_count,
576 .stride_words = config.stride_words,
577 .position_offset = config.position_word_offset,
578 .color_offset = require_color_offset(particle_op),
579 .grid_min_x = config.grid_min.x,
580 .grid_min_y = config.grid_min.y,
581 .grid_min_z = config.grid_min.z,
582 .cell_size = config.cell_size,
583 .dim_x = config.grid_dims.x,
584 .dim_y = config.grid_dims.y,
585 .dim_z = config.grid_dims.z,
586 .density_saturation_count = particle_op->get_field_config().density_saturation_count,
587 .cross_cluster = particle_op->get_field_config().cross_cluster ? 1U : 0U,
589 , m_particle_op(std::move(particle_op))
590 , m_built_revision(m_particle_op->revision())
601 const std::shared_ptr<VKBuffer>& buffer)
604 const auto& pconfig = m_particle_op->get_field_config();
605 m_params.density_saturation_count = pconfig.density_saturation_count;
606 m_params.cross_cluster = pconfig.cross_cluster ? 1U : 0U;
607 set_push_constant_data(m_params);
#define MF_ERROR(comp, ctx,...)
Core::GlobalNetworkConfig network
virtual void on_attach(const std::shared_ptr< Buffer > &)
Called when this processor is attached to a buffer.
virtual void processing_function(const std::shared_ptr< Buffer > &buffer)=0
The core processing function that must be implemented by derived classes.
void set_manual_dispatch(uint32_t x, uint32_t y=1, uint32_t z=1)
Set manual dispatch group counts.
Specialized ShaderProcessor for Compute Pipelines.
void on_buffer_ready() override
Hook for subclass setup, called at the end of on_attach.
HashClearProcessor(const SpatialHashConfig &config)
void on_buffer_ready() override
Hook for subclass setup, called at the end of on_attach.
GridPushConstants m_params
HashCountProcessor(const SpatialHashConfig &config, bool gate_alive=false)
uint64_t m_built_revision
std::shared_ptr< Nodes::Network::GpuFieldOperator > m_particle_op
bool on_before_execute(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr< VKBuffer > &buffer) override
Called before each process callback.
void on_buffer_ready() override
Hook for subclass setup, called at the end of on_attach.
HashDensityColorProcessor(const SpatialHashConfig &config, std::shared_ptr< Nodes::Network::GpuFieldOperator > particle_op)
HashScanProcessor(const SpatialHashConfig &config)
void on_buffer_ready() override
Hook for subclass setup, called at the end of on_attach.
void on_buffer_ready() override
Hook for subclass setup, called at the end of on_attach.
HashScatterProcessor(const SpatialHashConfig &config, bool gate_alive=false)
GridPushConstants m_params
void write_field_descriptors()
Issue descriptor writes for every entry in the binding table.
std::shared_ptr< NetworkGeometryBuffer > m_buffer
void register_bindings()
Register the binding table into m_config.bindings.
bool on_before_execute(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr< VKBuffer > &buffer) override
Reject buffers that are not the validated NetworkGeometryBuffer.
std::vector< FieldBinding > m_bindings
virtual void on_buffer_ready()
Hook for subclass setup, called at the end of on_attach.
void on_descriptors_created() override
Write every binding in the table for the current buffer state.
bool validate_fields()
Check every named state field exists on the attached buffer.
void on_attach(const std::shared_ptr< Buffer > &buffer) override
Cache and validate the buffer, then call on_buffer_ready.
void dispatch_one_thread_per(uint32_t element_count, const T ¶ms)
Configure manual dispatch for one thread per element, and stage the given push constant data.
NetworkStateFieldProcessor(std::vector< FieldBinding > bindings, const Portal::Graphics::ShaderSpec &spec)
Construct from a generated ShaderSpec.
bool guard_and_resync(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr< VKBuffer > &buffer, uint64_t current_revision, uint64_t &built_revision, F &&reload)
Base on_before_execute guard, then a revision-gated tuning reload.
void processing_function(const std::shared_ptr< Buffer > &buffer) override
Rewrite field descriptors, then run the shader.
ComputeProcessor operating on named state fields of a NetworkGeometryBuffer, plus optionally the buff...
void set_push_constant_data(const T &data)
Update push constant data (type-safe)
bool are_descriptors_ready() const
Check if descriptors are initialized.
std::vector< Portal::Graphics::DescriptorSetID > m_descriptor_set_ids
Operator that produces GPU-renderable geometry.
void append_neighbour_walk(std::string &body, const NeighbourWalk &walk)
Append the spatial-hash neighbour-gather loop nest to a kernel body.
GridPushConstants make_grid_push_constants(const SpatialHashConfig &c)
Populate a GridPushConstants from grid config.
@ BufferProcessing
Buffer processing (Buffers::BufferManager, processing chains)
@ Buffers
Buffers, Managers, processors and processing chains.
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
BindingDirection
Data flow direction for a shader binding slot.
uint32_t cell_count_total
std::string name
Shader binding name, as declared in ShaderConfig.
std::string field
State field name, or empty for the buffer's own vertex storage.
One shader binding and where it draws from.
Describes how a VKBuffer binds to a shader descriptor.
std::unordered_map< std::string, ShaderBinding > bindings
static std::optional< SpatialHashConfig > from_network(const std::shared_ptr< NetworkGeometryBuffer > &buffer, float cell_size)
Build hash grid parameters from a NetworkGeometryBuffer's network and its primary operator.
void declare_fields(const std::shared_ptr< NetworkGeometryBuffer > &buffer) const
Declare the four state fields the hash build stages read and write, sized from this config.
uint32_t cell_count() const
Total cell count, grid_dims.x * grid_dims.y * grid_dims.z.
Uniform grid parameters shared by every stage of the hash build.
Spatial domain for vertex generation.
Parsed representation of a user-supplied kernel lambda.
std::optional< KernelSource > kernel
When set, KernelOp is ignored.
Complete declarative description of a generated compute shader.