MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
NetworkGeometryFieldWiring.cpp
Go to the documentation of this file.
2
6
10
18
20
21namespace MayaFlux::Buffers {
22
23namespace {
24
25 /**
26 * @brief Whether this network type participates in GpuFieldOperator
27 * chain wiring at all.
28 *
29 * ParticleNetwork and PointCloudNetwork both aggregate an unordered
30 * point set the field dispatch and spatial-hash stages address
31 * uniformly. MeshNetwork and InstanceNetwork carry structured
32 * connectivity none of these stages model, so their buffers skip the
33 * scan. Subclass if this list ever needs to diverge per network.
34 */
35 bool is_field_compatible_network(const Nodes::Network::NodeNetwork* network)
36 {
37 return dynamic_cast<const Nodes::Network::ParticleNetwork*>(network) != nullptr
38 || dynamic_cast<const Nodes::Network::PointCloudNetwork*>(network) != nullptr;
39 }
40
41 /**
42 * @brief Adds the field pass to the chain on scope exit, after every spatial
43 * stage the enclosing scope went on to add.
44 *
45 * The field pass must be the last stage to write vertex records, which is
46 * what the postprocess slot used to buy it. Expressing the same order in the
47 * flat process list means adding it after the hash, claim and population
48 * stages, and wire_field_operators leaves by several paths, most of which add
49 * no stages at all. Deferring to scope exit keeps one ordering rule true on
50 * all of them without splitting the wiring up.
51 *
52 * A null chain means no pass is wanted, which is the operator carrying no
53 * bindings to dispatch.
54 */
55 struct DeferredFieldPass {
56 BufferProcessingChain* chain;
57 std::shared_ptr<Nodes::Network::GpuFieldOperator> field_op;
58 std::shared_ptr<NetworkGeometryBuffer> buffer;
59
60 ~DeferredFieldPass()
61 {
62 if (!chain) {
63 return;
64 }
65 try {
66 chain->add_processor(std::make_shared<VertexFieldProcessor>(field_op), buffer);
67 } catch (const std::exception& e) {
69 "NetworkGeometryBuffer: field pass could not be wired: {}", e.what());
70 }
71 }
72 };
73
74} // namespace
75
77{
78 if (!is_field_compatible_network(m_network.get())) {
79 return;
80 }
81
82 auto op_chain = m_network ? m_network->get_operator_chain() : nullptr;
83 if (!op_chain) {
84 return;
85 }
86
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);
90 if (field_op) {
91 break;
92 }
93 }
94 if (!field_op) {
95 return;
96 }
97
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");
103 return;
104 }
105
106 const DeferredFieldPass field_pass {
107 .chain = field_op->binding_count() > 0 ? chain.get() : nullptr,
108 .field_op = field_op,
109 .buffer = self
110 };
111
112 const auto& pconfig = field_op->get_field_config();
113 if (!pconfig.absorb_radius.has_value() && !pconfig.density_color) {
114 return;
115 }
116
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");
121 }
122
123 auto* physics = dynamic_cast<Nodes::Network::PhysicsOperator*>(get_network()->get_operator());
124
125 float cell_size = 0.0F;
126 if (pconfig.cell_size.has_value()) {
127 cell_size = *pconfig.cell_size;
128 } else if (physics) {
129 cell_size = physics->get_interaction_radius();
130 } else {
132 "NetworkGeometryBuffer: SpatialFieldConfig has no cell_size and the network's "
133 "primary operator is not a PhysicsOperator, so one cannot be derived");
134 return;
135 }
136
137 auto hash_config = SpatialHashConfig::from_network(self, cell_size);
138 if (!hash_config.has_value()) {
140 "NetworkGeometryBuffer: SpatialHashConfig::from_network failed");
141 return;
142 }
143
144 const uint32_t live_count = hash_config->particle_count;
145 bool reserve_enabled = pconfig.reserve_fraction > 0.0F;
146
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;
152 }
153
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;
159
160 const size_t needed_bytes = static_cast<size_t>(total_count) * hash_config->stride_words * 4;
161 self->resize(needed_bytes, true);
162
163 hash_config->particle_count = total_count;
164 }
165
166 if (total_count != live_count) {
167 hash_config->declare_fields(self);
168
169 std::vector<uint32_t> cluster_ids(total_count, 0U);
170 if (auto* graphics_op
171 = dynamic_cast<Nodes::Network::GraphicsOperator*>(get_network()->get_operator())) {
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());
175 }
176 std::shared_ptr<VKBuffer> cluster_staging;
177 upload_back_buffer(self->write_state_slot("hash_cluster_id"), cluster_ids.data(),
178 cluster_ids.size() * sizeof(uint32_t), cluster_staging);
179 } else {
180 self->ensure_cluster_ids();
181 hash_config->declare_fields(self);
182 }
183
184 std::optional<PopulationConfig> pop_config;
185 if (reserve_enabled) {
186 pop_config = PopulationConfig { .hash = *hash_config, .live_count = live_count };
187 pop_config->declare_fields(self);
188 chain->add_processor(std::make_shared<PopulationInitProcessor>(*pop_config), self);
189 }
190
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);
195
196 if (pconfig.density_color) {
197 chain->add_processor(
198 std::make_shared<HashDensityColorProcessor>(*hash_config, field_op), self);
199 }
200
201 if (!pconfig.absorb_radius.has_value()) {
202 return;
203 }
204
205 MutationConfig claim_config { .hash = *hash_config, .absorb_radius = *pconfig.absorb_radius };
206 claim_config.declare_fields(self);
207
208 const bool transfer_on_claim = pconfig.transfer_on_claim;
209
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),
217 self);
218
219 if (pconfig.cosmetic_swallow) {
220 chain->add_processor(
221 std::make_shared<ClaimSwallowProcessor>(claim_config, field_op, transfer_on_claim), self);
222 }
223
224 if (transfer_on_claim) {
225 chain->add_processor(std::make_shared<ClaimTransferProcessor>(claim_config), self);
226 }
227
228 if (pop_config.has_value()) {
229 chain->add_processor(
230 std::make_shared<PopulationSpawnProcessor>(*pop_config, field_op), self);
231 }
232}
233
234} // namespace MayaFlux::Buffers
#define MF_ERROR(comp, ctx,...)
Core::GlobalNetworkConfig network
Definition Config.cpp:43
std::shared_ptr< NetworkGeometryBuffer > buffer
BufferProcessingChain * chain
std::shared_ptr< Nodes::Network::GpuFieldOperator > field_op
std::shared_ptr< Nodes::Network::NodeNetwork > m_network
std::shared_ptr< Nodes::Network::NodeNetwork > get_network() const
Get the network driving this buffer.
void wire_field_operators()
Find the first GpuFieldOperator in the network's operator chain and wire its compute stages: a Vertex...
std::shared_ptr< Buffers::BufferProcessingChain > get_processing_chain() override
Access the buffer's processing chain.
Definition VKBuffer.cpp:271
Operator that produces GPU-renderable geometry.
float get_interaction_radius() const
Get the interaction radius for physics calculations.
N-body physics simulation with point rendering.
void upload_back_buffer(const VKBufferResources::GenerationSlot &slot, const void *data, size_t size, std::shared_ptr< VKBuffer > &staging)
Upload host memory into a raw back_buffers slot.
@ Init
Engine/subsystem initialization.
@ Buffers
Buffers, Managers, processors and processing chains.
Parameters for the deterministic pairwise claim protocol built on top of a completed spatial hash.
Parameters for GPU-local population dynamics: destruction and spawn-as-copy over a seed's fixed,...
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.