78 if (!is_field_compatible_network(
m_network.get())) {
87 std::shared_ptr<Nodes::Network::GpuFieldOperator>
field_op;
88 for (
const auto& op : op_chain->operators()) {
89 field_op = std::dynamic_pointer_cast<Nodes::Network::GpuFieldOperator>(op);
98 auto self = std::dynamic_pointer_cast<NetworkGeometryBuffer>(shared_from_this());
100 if (!self || !
chain) {
102 "NetworkGeometryBuffer: no processing chain to wire field stages onto");
106 const DeferredFieldPass field_pass {
107 .chain =
field_op->binding_count() > 0 ?
chain.get() :
nullptr,
112 const auto& pconfig =
field_op->get_field_config();
113 if (!pconfig.absorb_radius.has_value() && !pconfig.density_color) {
117 if (pconfig.transfer_on_claim && !pconfig.absorb_radius.has_value()) {
119 "NetworkGeometryBuffer: transfer_on_claim requires absorb_radius (there is no "
120 "claim to reinterpret without it); transfer disabled");
125 float cell_size = 0.0F;
126 if (pconfig.cell_size.has_value()) {
127 cell_size = *pconfig.cell_size;
128 }
else if (physics) {
132 "NetworkGeometryBuffer: SpatialFieldConfig has no cell_size and the network's "
133 "primary operator is not a PhysicsOperator, so one cannot be derived");
138 if (!hash_config.has_value()) {
140 "NetworkGeometryBuffer: SpatialHashConfig::from_network failed");
144 const uint32_t live_count = hash_config->particle_count;
145 bool reserve_enabled = pconfig.reserve_fraction > 0.0F;
147 if (reserve_enabled && !pconfig.absorb_radius.has_value()) {
149 "NetworkGeometryBuffer: reserve_fraction requires absorb_radius (destroy-on-"
150 "absorption has nothing to destroy without claims); reserve capacity disabled");
151 reserve_enabled =
false;
154 uint32_t total_count = live_count;
155 if (reserve_enabled) {
156 const auto reserve_count =
static_cast<uint32_t
>(
157 std::ceil(
static_cast<float>(live_count) * pconfig.reserve_fraction));
158 total_count = live_count + reserve_count;
160 const size_t needed_bytes =
static_cast<size_t>(total_count) * hash_config->stride_words * 4;
161 self->resize(needed_bytes,
true);
163 hash_config->particle_count = total_count;
166 if (total_count != live_count) {
167 hash_config->declare_fields(self);
169 std::vector<uint32_t> cluster_ids(total_count, 0U);
170 if (
auto* graphics_op
172 auto live_ids = graphics_op->build_cluster_ids();
173 std::copy_n(live_ids.begin(),
174 std::min<size_t>(live_ids.size(), live_count), cluster_ids.begin());
176 std::shared_ptr<VKBuffer> cluster_staging;
178 cluster_ids.size() *
sizeof(uint32_t), cluster_staging);
180 self->ensure_cluster_ids();
181 hash_config->declare_fields(self);
184 std::optional<PopulationConfig> pop_config;
185 if (reserve_enabled) {
188 chain->add_processor(std::make_shared<PopulationInitProcessor>(*pop_config), self);
191 chain->add_processor(std::make_shared<HashClearProcessor>(*hash_config), self);
192 chain->add_processor(std::make_shared<HashCountProcessor>(*hash_config, reserve_enabled), self);
193 chain->add_processor(std::make_shared<HashScanProcessor>(*hash_config), self);
194 chain->add_processor(std::make_shared<HashScatterProcessor>(*hash_config, reserve_enabled), self);
196 if (pconfig.density_color) {
197 chain->add_processor(
198 std::make_shared<HashDensityColorProcessor>(*hash_config,
field_op), self);
201 if (!pconfig.absorb_radius.has_value()) {
205 MutationConfig claim_config { .
hash = *hash_config, .absorb_radius = *pconfig.absorb_radius };
208 const bool transfer_on_claim = pconfig.transfer_on_claim;
210 chain->add_processor(std::make_shared<ClaimInitProcessor>(claim_config), self);
211 chain->add_processor(
212 std::make_shared<ClaimProcessor>(claim_config,
field_op, reserve_enabled), self);
213 chain->add_processor(std::make_shared<ClaimFlattenProcessor>(claim_config), self);
214 chain->add_processor(
215 std::make_shared<ClaimAccumulateProcessor>(
216 claim_config, physics, reserve_enabled ? live_count : 0U,
field_op, transfer_on_claim),
219 if (pconfig.cosmetic_swallow) {
220 chain->add_processor(
221 std::make_shared<ClaimSwallowProcessor>(claim_config,
field_op, transfer_on_claim), self);
224 if (transfer_on_claim) {
225 chain->add_processor(std::make_shared<ClaimTransferProcessor>(claim_config), self);
228 if (pop_config.has_value()) {
229 chain->add_processor(
230 std::make_shared<PopulationSpawnProcessor>(*pop_config,
field_op), self);