MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
PopulationProcessor.hpp
Go to the documentation of this file.
1#pragma once
2
4
6class GpuFieldOperator;
7}
8
9namespace MayaFlux::Buffers {
10
11/**
12 * @struct PopulationConfig
13 * @brief Parameters for GPU-local population dynamics: destruction and
14 * spawn-as-copy over a seed's fixed, over-allocated capacity.
15 *
16 * hash.particle_count here is expected to already be live_count plus
17 * whatever reserve capacity SpatialFieldConfig::reserve_fraction asked for:
18 * the caller overrides SpatialHashConfig::particle_count to that total
19 * before constructing this, so every hash/claim stage already dispatches
20 * over the full reserve range. live_count is the boundary this struct
21 * itself adds: everything at or past it starts dead, and is the only region
22 * PopulationSpawnProcessor is allowed to write a new particle into.
23 *
24 * This mechanism is not reflected to the CPU. Whatever PhysicsOperator
25 * simulates is the authoritative population; what exists here is GPU-local
26 * bookkeeping about which of the seed's records currently render as alive,
27 * rebuilt from scratch on the next reseed. ClaimAccumulateProcessor is the
28 * one enforcement point: it never lets anything past live_count reach
29 * PhysicsOperator's own bond/mass tracking, however far spawn has grown the
30 * live-looking population beyond it.
31 */
34 uint32_t live_count;
35
36 /**
37 * @brief Declare the two state fields population dynamics needs.
38 * @param buffer Buffer to declare on. Already-present names are left
39 * untouched, matching every other declare_fields in this family.
40 *
41 * Declares mutation_alive at hash.particle_count elements (the total
42 * seed + reserve capacity) and mutation_spawn_cursor as a single
43 * element. Neither is reset by any per-cycle stage: PopulationInitProcessor
44 * writes both exactly once, and every cycle after that only
45 * ClaimAccumulateProcessor (destroy) and PopulationSpawnProcessor (spawn)
46 * ever touch them again, forward-only, until the next reseed.
47 */
48 void declare_fields(const std::shared_ptr<NetworkGeometryBuffer>& buffer) const;
49};
50
51/**
52 * @class PopulationInitProcessor
53 * @brief One-shot: splits mutation_alive into live/reserve, zeros the
54 * reserve region's vertex records, and seeds mutation_spawn_cursor.
55 *
56 * Dispatches exactly once, on the first cycle it attaches to a ready
57 * buffer, then refuses every subsequent dispatch via on_before_execute: a
58 * second pass would reset every destroy/spawn decision made since the
59 * first. Must run before HashCountProcessor/HashScatterProcessor's first
60 * dispatch reads mutation_alive, so it belongs at the front of the chain,
61 * ahead of HashClearProcessor.
62 *
63 * Zeroing the reserve region's vertex records (not just marking them dead)
64 * matters because nothing else guarantees what those bytes held before this
65 * ran: a stray large or NaN position/size would be a visible artifact if
66 * alive-gating ever has a bug, rather than invisible.
67 */
69public:
70 explicit PopulationInitProcessor(const PopulationConfig& config);
71
72protected:
73 void on_buffer_ready() override;
74
75 /** @brief True only on the first call; every call after returns false. */
76 bool on_before_execute(
78 const std::shared_ptr<VKBuffer>& buffer) override;
79
80private:
81 struct Params {
82 uint32_t live_count;
83 uint32_t total_count;
84 uint32_t stride_words;
85 };
86
88 bool m_done {};
89};
90
91/**
92 * @class PopulationSpawnProcessor
93 * @brief Copies a crowded live particle's full vertex record into a fresh
94 * reserve slot, claimed via a monotonic bump allocator.
95 *
96 * One thread per particle (dead particles skip immediately). Walks the same
97 * 27-cell neighbourhood HashDensityColorProcessor does, over the completed
98 * hash, and when a live particle's real neighbour count clears
99 * spawn_density_threshold, does atomicAdd(spawn_cursor[0], 1u) to claim the
100 * next slot. A claim landing at or past total_count is over capacity and is
101 * silently dropped: the reserve never grows past what
102 * SpatialFieldConfig::reserve_fraction allocated at wiring time, matching
103 * "always over-allocate at seed time" rather than any live growth.
104 *
105 * The claimed slot is guaranteed to belong to no other thread this
106 * dispatch (atomicAdd returns a distinct value per caller) and to be
107 * read by nothing else this cycle (every stage that consumes hash_*
108 * mutation_* state already ran earlier in the chain), so the copy has no
109 * write hazard with any other thread. The one thing it does not
110 * synchronise against is its own claimed slot's thread running the same
111 * dispatch: that thread may observe mutation_alive for its own index
112 * before or after this write lands, but either way it only ever reads,
113 * never writes, so the race is benign: worst case, a freshly spawned
114 * particle waits one extra cycle before it can itself become a source.
115 *
116 * Density is global, not cluster-scoped: a crowded region spawns regardless
117 * of which PhysicsOperator collection its particles came from.
118 *
119 * Reads spawn_density_threshold from the owning GpuFieldOperator
120 * fresh whenever its revision() changes, the same on_before_execute check
121 * every other tunable in this family uses.
122 */
124public:
126 const PopulationConfig& config,
127 std::shared_ptr<Nodes::Network::GpuFieldOperator> particle_op);
128
129protected:
130 void on_buffer_ready() override;
131
132 bool on_before_execute(
134 const std::shared_ptr<VKBuffer>& buffer) override;
135
136private:
141
143 std::shared_ptr<Nodes::Network::GpuFieldOperator> m_particle_op;
145};
146
147} // namespace MayaFlux::Buffers
ComputeProcessor operating on named state fields of a NetworkGeometryBuffer, plus optionally the buff...
One-shot: splits mutation_alive into live/reserve, zeros the reserve region's vertex records,...
std::shared_ptr< Nodes::Network::GpuFieldOperator > m_particle_op
Copies a crowded live particle's full vertex record into a fresh reserve slot, claimed via a monotoni...
Uniform-grid push-constant block shared by the hash count/scatter and population-spawn kernels.
void declare_fields(const std::shared_ptr< NetworkGeometryBuffer > &buffer) const
Declare the two state fields population dynamics needs.
Parameters for GPU-local population dynamics: destruction and spawn-as-copy over a seed's fixed,...
Uniform grid parameters shared by every stage of the hash build.