MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
RelaxationGridBuffer.hpp
Go to the documentation of this file.
1#pragma once
2
6
7namespace MayaFlux::Buffers {
8
9class RelaxationStepProcessor;
10class RelaxationEmitProcessor;
11class RenderProcessor;
12
13/**
14 * @class RelaxationGridBuffer
15 * @brief GPU-resident, double-buffered SSBO state for synchronous
16 * local-rule systems evaluated over a fixed 2D topology: cellular
17 * automata, Jacobi/Gauss-Seidel relaxation, discrete diffusion, and
18 * any rule where every cell updates from its own and neighbors'
19 * previous-generation state.
20 *
21 * Grid state lives entirely as two raw Vulkan handle pairs inside
22 * VKBufferResources::back_buffers, allocated once at construction. No
23 * VKBuffer object represents the grid state at any point; descriptor
24 * writes for these handles are issued directly against ShaderFoundry by
25 * RelaxationStepProcessor and RelaxationEmitProcessor, bypassing
26 * ShaderProcessor::bind_buffer entirely, since that call requires a
27 * VKBuffer wrapper this design does not create.
28 *
29 * The VKBuffer base itself (this object) owns only the emitted vertex
30 * output (Usage::VERTEX), written each generation by
31 * RelaxationEmitProcessor from the front generation's state, then drawn
32 * directly by RenderProcessor as ordinary untextured point geometry.
33 *
34 * Chain order:
35 * default — RelaxationStepProcessor (rule shader: state front -> back)
36 * flat[0] — RelaxationEmitProcessor (emit shader: front state -> vertices)
37 * final — RenderProcessor
38 *
39 * Snapshot delivery uses a BroadcastSource<std::vector<uint8_t>>, owned by
40 * this buffer and exposed via snapshot_source(). RelaxationStepProcessor
41 * signals it directly once a requested download completes; there is no
42 * intermediate queue or poll. Subscribe via Kriya::on_signal() or
43 * on_signal_matching() at whatever cadence the caller chooses — the class
44 * imposes none.
45 *
46 * Usage:
47 * @code
48 * auto grid = std::make_shared<RelaxationGridBuffer>(
49 * 256, 256, sizeof(uint32_t), "ca_rule.comp", "ca_emit.comp");
50 * grid->setup_processors(ProcessingToken::GRAPHICS_BACKEND);
51 * grid->setup_rendering({ .target_window = window });
52 * @endcode
53 */
54class MAYAFLUX_API RelaxationGridBuffer : public VKBuffer {
55public:
56 /**
57 * @brief Either a shader file path or a generated ShaderSpec, resolved at
58 * construction time by setup_processors().
59 */
60 using ShaderSource = std::variant<std::string, Portal::Graphics::ShaderSpec>;
61
62 /**
63 * @brief Construct an unregistered double-buffered grid buffer.
64 * @param width Grid width in cells.
65 * @param height Grid height in cells.
66 * @param cell_stride_bytes Size in bytes of one cell's state.
67 * @param rule_source Either a path to a hand-written rule shader (for
68 * rules needing branchy multi-state logic, e.g. Conway, Wireworld)
69 * or a generated ShaderSpec (e.g. RelaxationSpecs::jacobi_diffusion()).
70 * @param emit_source Either a path to a hand-written emit shader, or a
71 * generated ShaderSpec (e.g. RelaxationSpecs::emit_binary()).
72 *
73 * rule_source and emit_source are independent: a hand-written rule paired
74 * with a generated emit spec, or any other combination, is fully
75 * supported. Processors are constructed from whichever alternative each
76 * holds in setup_processors().
77 */
79 uint32_t width,
80 uint32_t height,
81 size_t cell_stride_bytes,
82 ShaderSource rule_source,
83 ShaderSource emit_source);
84
85 /**
86 * @brief Destructor.
87 *
88 * Raw handles in m_resources.back_buffers are released by the backend
89 * during buffer service teardown, consistent with how VKBuffer's own
90 * primary buffer/memory are released; this class performs no manual
91 * Vulkan destruction itself.
92 */
93 ~RelaxationGridBuffer() override = default;
94
95 /**
96 * @brief Create and attach RelaxationStepProcessor (default processor)
97 * and RelaxationEmitProcessor (flat processor), and set the
98 * per-cell vertex layout on this buffer's own storage.
99 * @param token Processing domain (typically GRAPHICS_BACKEND).
100 */
101 void setup_processors(ProcessingToken token) override;
102
103 /**
104 * @brief Attach a RenderProcessor drawing the emitted vertex output as
105 * ordinary untextured point geometry.
106 * @param config Render target; vertex/fragment shaders default to the
107 * standard untextured pair unless overridden. Topology
108 * is always forced to POINT_LIST regardless of what is
109 * supplied in @p config.
110 */
111 void setup_rendering(const RenderConfig& config);
112
113 /** @brief Grid width in cells. */
114 [[nodiscard]] uint32_t get_grid_width() const { return m_width; }
115
116 /** @brief Grid height in cells. */
117 [[nodiscard]] uint32_t get_grid_height() const { return m_height; }
118
119 /** @brief Total cell count, width * height. */
120 [[nodiscard]] uint32_t get_cell_count() const { return m_width * m_height; }
121
122 /** @brief Total byte size of one generation's state buffer. */
123 [[nodiscard]] size_t get_state_bytes() const
124 {
125 return static_cast<size_t>(m_width) * m_height * m_cell_stride_bytes;
126 }
127
128 /**
129 * @brief Write initial state directly into the current front generation.
130 * @param data Pointer to cell_count * cell_stride_bytes of state data.
131 * @param size Byte count; must equal get_state_bytes().
132 *
133 * Direct host-visible memcpy into back_buffers[front_index()], mirroring
134 * VKBuffer::set_data's own host-visible write path. Valid only before the
135 * buffer's processing chain has run at least once, or between generations
136 * where overwriting the current front state is intended (e.g. a runtime
137 * reset). Does not touch the back generation or trigger a swap.
138 */
139 void seed_state(const void* data, size_t size);
140
141 /**
142 * @brief Index into m_resources.back_buffers currently holding the
143 * most recently completed generation's state.
144 */
145 [[nodiscard]] uint32_t front_index() const { return m_front_is_a ? 0 : 1; }
146
147 /**
148 * @brief Index into m_resources.back_buffers currently serving as the
149 * write target for the next rule dispatch.
150 */
151 [[nodiscard]] uint32_t back_index() const { return m_front_is_a ? 1 : 0; }
152
153 /**
154 * @brief Request a snapshot of the next completed generation.
155 *
156 * Wait-free. Consumed by RelaxationStepProcessor on the next
157 * on_after_execute call, which stages a device-to-host download of the
158 * newly-front state buffer and signals snapshot_source() with the result.
159 */
160 void request_snapshot() { m_snapshot_requested.store(true, std::memory_order_release); }
161
162 /**
163 * @brief BroadcastSource of completed generation snapshots.
164 *
165 * Signaled by RelaxationStepProcessor exactly once per fulfilled
166 * request_snapshot() call, at the point the device-to-host download
167 * actually completes — not polled, not tied to any particular pacing.
168 * Subscribe via Kriya::on_signal()/on_signal_matching() at whatever
169 * cadence suits the caller; the class itself imposes none.
170 */
171 [[nodiscard]] std::shared_ptr<Vruta::BroadcastSource<std::vector<uint8_t>>> snapshot_source() const
172 {
173 return m_snapshot_source;
174 }
175
176 /** @brief The attached rule-stage processor, valid after setup_processors(). */
177 [[nodiscard]] std::shared_ptr<RelaxationStepProcessor> step_processor() const { return m_step_processor; }
178
179 /** @brief The attached emit-stage processor, valid after setup_processors(). */
180 [[nodiscard]] std::shared_ptr<RelaxationEmitProcessor> emit_processor() const { return m_emit_processor; }
181
182private:
185
186 [[nodiscard]] bool consume_snapshot_request()
187 {
188 return m_snapshot_requested.exchange(false, std::memory_order_acq_rel);
189 }
190
191 inline void swap_generation() { m_front_is_a = !m_front_is_a; }
192
193 /** @brief Signaled by RelaxationStepProcessor when a requested snapshot completes. */
194 std::shared_ptr<Vruta::BroadcastSource<std::vector<uint8_t>>> m_snapshot_source {
195 std::make_shared<Vruta::BroadcastSource<std::vector<uint8_t>>>()
196 };
197
198 uint32_t m_width; ///< Grid width in cells.
199 uint32_t m_height; ///< Grid height in cells.
200 size_t m_cell_stride_bytes; ///< Size in bytes of one cell's state, fixed at construction.
201
202 ShaderSource m_rule_source; ///< Either a path to a hand-written rule shader or a generated ShaderSpec.
203 ShaderSource m_emit_source; ///< Either a path to a hand-written emit shader or a generated ShaderSpec.
204
205 bool m_front_is_a { true }; ///< True when back_buffers[0] holds the current front generation.
206 std::atomic<bool> m_snapshot_requested { false }; ///< Set by request_snapshot(), cleared by consume_snapshot_request().
207
208 std::shared_ptr<RelaxationStepProcessor> m_step_processor; ///< Rule stage, retained for parameter access.
209 std::shared_ptr<RelaxationEmitProcessor> m_emit_processor; ///< Emit stage, retained for parameter access.
210};
211
212} // namespace MayaFlux::Buffers
uint32_t width
Definition Decoder.cpp:66
uint32_t height
ComputeProcessor that reads the current front generation's raw state buffer from a RelaxationGridBuff...
~RelaxationGridBuffer() override=default
Destructor.
std::shared_ptr< RelaxationEmitProcessor > m_emit_processor
Emit stage, retained for parameter access.
ShaderSource m_emit_source
Either a path to a hand-written emit shader or a generated ShaderSpec.
std::shared_ptr< RelaxationEmitProcessor > emit_processor() const
The attached emit-stage processor, valid after setup_processors().
uint32_t get_cell_count() const
Total cell count, width * height.
uint32_t get_grid_width() const
Grid width in cells.
std::shared_ptr< RelaxationStepProcessor > m_step_processor
Rule stage, retained for parameter access.
size_t get_state_bytes() const
Total byte size of one generation's state buffer.
size_t m_cell_stride_bytes
Size in bytes of one cell's state, fixed at construction.
std::shared_ptr< RelaxationStepProcessor > step_processor() const
The attached rule-stage processor, valid after setup_processors().
ShaderSource m_rule_source
Either a path to a hand-written rule shader or a generated ShaderSpec.
std::variant< std::string, Portal::Graphics::ShaderSpec > ShaderSource
Either a shader file path or a generated ShaderSpec, resolved at construction time by setup_processor...
std::shared_ptr< Vruta::BroadcastSource< std::vector< uint8_t > > > snapshot_source() const
BroadcastSource of completed generation snapshots.
uint32_t back_index() const
Index into m_resources.back_buffers currently serving as the write target for the next rule dispatch.
uint32_t front_index() const
Index into m_resources.back_buffers currently holding the most recently completed generation's state.
uint32_t get_grid_height() const
Grid height in cells.
void request_snapshot()
Request a snapshot of the next completed generation.
GPU-resident, double-buffered SSBO state for synchronous local-rule systems evaluated over a fixed 2D...
ComputeProcessor specialization driving one generation step of a RelaxationGridBuffer per dispatch.
Vulkan-backed buffer wrapper used in processing chains.
Definition VKBuffer.hpp:76
ProcessingToken
Bitfield enum defining processing characteristics and backend requirements for buffer operations.
Unified rendering configuration for graphics buffers.