MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
GpuDispatchCore.hpp
Go to the documentation of this file.
1#pragma once
2
4
6
8
9namespace MayaFlux::Core {
10class VKImage;
11}
12
13namespace MayaFlux::Yantra {
14
15class GpuDispatchCore;
16
17/**
18 * @struct GpuChannelResult
19 * @brief Erased output of a GPU dispatch: reconstructed float data plus
20 * any raw auxiliary outputs keyed by binding index.
21 */
23 std::vector<float> primary;
24 std::unordered_map<size_t, std::vector<uint8_t>> aux;
25};
26
27/**
28 * @struct DependencyStage
29 * @brief One stage in a dispatch_core_dependency call.
30 *
31 * stage_fn is invoked with this context as the active target, exactly as
32 * if the caller had just called swap_shader(config) and were about to call
33 * dispatch_core — stage_image_at, set_binding_data, stage_passthrough,
34 * set_push_constants all apply normally inside stage_fn. Works identically
35 * for image-bearing and buffer-only shaders; nothing about this struct is
36 * image-specific.
37 *
38 * hazard_fn runs immediately after stage_fn, once this stage's resources
39 * (e.g. an image just allocated inside stage_fn) actually exist. Returns
40 * the hazard list for this stage; may be empty if nothing downstream in
41 * the same dependency call depends on this stage's output.
42 */
45 std::function<void(GpuDispatchCore&)> stage_fn;
46 std::function<std::vector<Portal::Graphics::HazardResource>(GpuDispatchCore&)> hazard_fn;
47 std::optional<std::array<uint32_t, 3>> explicit_groups;
48};
49
50/**
51 * @class GpuDispatchCore
52 * @brief Non-template base that owns all type-independent GPU dispatch logic.
53 *
54 * Separates resource management, buffer staging, and dispatch orchestration
55 * from the type-parameterised boundary in GpuExecutionContext. All virtual
56 * override points that do not reference InputType/OutputType live here so
57 * that implementations can be placed in a .cpp file.
58 *
59 * Subclasses (including GpuExecutionContext) implement the two remaining
60 * type-dependent steps -- channel extraction and output reconstruction --
61 * without duplicating anything that is type-independent.
62 */
63class MAYAFLUX_API GpuDispatchCore {
64public:
65 explicit GpuDispatchCore(GpuComputeConfig config);
66 virtual ~GpuDispatchCore() = default;
67
72
73 /**
74 * @brief Set push constant data from a raw byte pointer.
75 * @param data Pointer to trivially-copyable push constant struct.
76 * @param bytes Size in bytes.
77 */
78 void set_push_constants(const void* data, size_t bytes);
79
80 /**
81 * @brief Typed convenience wrapper for set_push_constants(const void*, size_t).
82 * @tparam T Trivially copyable type matching shader push constant layout.
83 */
84 template <typename T>
85 void set_push_constants(const T& data)
86 {
87 set_push_constants(&data, sizeof(T));
88 }
89
90 /**
91 * @brief Pre-stage typed data for a specific binding slot, bypassing
92 * the default channel-flattening path in prepare_gpu_inputs.
93 * @tparam T Trivially copyable element type.
94 * @param index Binding index matching declare_buffer_bindings order.
95 * @param data Elements to upload.
96 */
97 template <typename T>
98 void set_binding_data(size_t index, std::span<const T> data)
99 {
100 if (index >= m_binding_data.size())
101 m_binding_data.resize(index + 1);
102 auto& slot = m_binding_data[index];
103 slot.resize(data.size_bytes());
104 std::memcpy(slot.data(), data.data(), data.size_bytes());
105 }
106
107 template <typename T>
108 void set_binding_data(size_t index, const std::vector<T>& data)
109 {
110 set_binding_data(index, std::span<const T>(data));
111 }
112
113 void ensure_shared_buffer(uint32_t set, size_t binding_index, size_t element_count,
115 Portal::Graphics::BufferUsageHint usage_hint = Portal::Graphics::BufferUsageHint::COMPUTE_STORAGE)
116 {
117 m_resources.ensure_shared_buffer(set, binding_index, element_count, element_type, usage_hint);
118 m_shared_bindings.insert({ set, binding_index });
119 }
120
121 void upload_shared_raw(uint32_t set, size_t binding_index, const uint8_t* data, size_t byte_size)
122 {
123 m_resources.upload_shared_raw(set, binding_index, data, byte_size);
124 }
125
126 void download_shared(uint32_t set, size_t binding_index, void* dest, size_t byte_size)
127 {
128 m_resources.download_shared(set, binding_index, dest, byte_size);
129 }
130
132 const GpuBufferBinding& spec) const
133 {
134 return m_resources.make_shared_buffer_hazard(spec);
135 }
136
137 /**
138 * @brief Declare the byte capacity of an output binding independently
139 * of input data. Required for edge lists, histograms, count
140 * buffers, and any output whose size cannot be derived from input.
141 * @param index Binding index.
142 * @param byte_size Required allocation in bytes.
143 */
144 void set_output_size(size_t index, size_t byte_size);
145
146 /**
147 * @brief Ensure GPU resources are initialised. Safe to call repeatedly.
148 * @return True if GPU is ready after this call.
149 */
150 bool ensure_gpu_ready();
151
152 /**
153 * @brief Query GPU readiness without attempting initialisation.
154 */
155 [[nodiscard]] bool is_gpu_ready() const;
156
157 /**
158 * @brief Return the image registered at an IMAGE_STORAGE output binding.
159 *
160 * Valid after dispatch_core completes (dispatch is synchronous via
161 * submit_and_wait). Callers may then bind it directly to a render pass
162 * or read it back via TextureLoom.
163 *
164 * @param binding_index Index of the IMAGE_STORAGE binding.
165 * @return Shared pointer to the VKImage, or nullptr if not registered.
166 */
167 [[nodiscard]] std::shared_ptr<Core::VKImage> get_output_image(size_t binding_index) const;
168
169 /**
170 * @brief Read back a specific binding into a caller-provided destination.
171 *
172 * @param index Binding index to read back.
173 * @param dest Pointer to caller-allocated memory for the data.
174 * @param byte_size Size in bytes to read back (must not exceed allocated size).
175 */
176 void download_binding(size_t index, void* dest, size_t byte_size);
177
178 /**
179 * @brief Switch which shader subsequent dispatch_core calls target.
180 *
181 * No longer destroys any GpuResourceManager state: each shader path
182 * is a persistent key in GpuResourceManager, created once and reused.
183 * Switching back to a previously-used shader is free. Preserves all
184 * staged image and buffer bindings, same as before.
185 *
186 * Use to drive a multi-op sequence through one context: stage the input
187 * image, call swap_shader + dispatch_core per op, pipe get_output_image(0)
188 * back in via stage_image between steps.
189 *
190 * @param config Shader config for the next dispatch.
191 */
193 {
194 m_gpu_config = std::move(config);
195 update_dispatch_key_cache();
196 }
197
198 /**
199 * @brief Register a VKImage at an explicit binding index.
200 *
201 * Dispatches to the storage or sampled path based on kind. The image
202 * will be transitioned to eGeneral (STORAGE) or eShaderReadOnlyOptimal
203 * (SAMPLED) if not already there.
204 *
205 * @param binding_index Index matching the declared image binding.
206 * @param image Initialised VKImage.
207 * @param kind IMAGE_STORAGE or IMAGE_SAMPLED.
208 * @param sampler Vulkan sampler handle. Required for IMAGE_SAMPLED,
209 * ignored for IMAGE_STORAGE.
210 */
211 void stage_image_at(size_t binding_index,
212 std::shared_ptr<Core::VKImage> image,
214 vk::Sampler sampler = nullptr);
215
216 /**
217 * @brief The key used for this context's GpuResourceManager unit.
218 *
219 * shader_path when non-empty (named .comp files). Falls back to the
220 * numeric shader_id when shader_path is empty, which is always the case
221 * for assembled ShaderSpec configs (config_from_spec never sets
222 * shader_path — only shader_id, via ShaderFoundry's content-hash cache).
223 * Without this fallback, every assembled shader collapses onto the same
224 * empty-string key and silently overwrites the previous assembled
225 * shader's pipeline/bindings.
226 */
227 [[nodiscard]] const std::string& dispatch_key() const { return m_cached_dispatch_key; }
228
229protected:
230 /**
231 * @brief Declare the storage buffers the shader expects.
232 *
233 * Default: INPUT at (0,0) FLOAT32, OUTPUT at (0,1) FLOAT32.
234 */
235 [[nodiscard]] virtual std::vector<GpuBufferBinding> declare_buffer_bindings() const;
236
237 /**
238 * @brief Called immediately before dispatch. Override to write push
239 * constants or perform any per-dispatch reconfiguration.
240 */
241 virtual void on_before_gpu_dispatch(
242 const std::vector<std::vector<double>>& channels,
243 const DataStructureInfo& structure_info);
244
245 /**
246 * @brief Marshal channel data into GPU input buffers.
247 *
248 * Handles FLOAT32, UINT32, INT32, PASSTHROUGH, IMAGE_STORAGE, and
249 * IMAGE_SAMPLED binding kinds. Called after flatten_channels_to_staging.
250 */
251 virtual void prepare_gpu_inputs(
252 const std::vector<std::vector<double>>& channels,
253 const DataStructureInfo& structure_info);
254
255 /**
256 * @brief Calculate workgroup dispatch counts from structure dimensions.
257 *
258 * Reads SPATIAL_X/Y/Z roles for 2D/3D shaders; falls back to 1D
259 * element-count dispatch when no spatial dimensions exist.
260 *
261 * @param total_elements Flat element count for the 1D fallback.
262 * @param structure_info Dimension metadata.
263 */
264 [[nodiscard]] virtual std::array<uint32_t, 3> calculate_dispatch_size(
265 size_t total_elements,
266 const DataStructureInfo& structure_info) const;
267
268 /**
269 * @brief Stage raw bytes for a PASSTHROUGH binding before dispatch.
270 * @param binding_index Index matching declare_buffer_bindings order.
271 * @param data Raw byte pointer.
272 * @param byte_size Size in bytes.
273 */
274 void stage_passthrough(size_t binding_index, const void* data, size_t byte_size);
275
276 /**
277 * @brief Stage a flat native-typed byte buffer for FLOAT32 bindings,
278 * bypassing the double-to-float cast in flatten_channels_to_staging.
279 *
280 * Intended for pixel data (uint8_t, uint16_t) and pre-converted float
281 * buffers that should reach the GPU without an intermediate double widening.
282 * Once staged, prepare_gpu_inputs will upload these bytes directly via
283 * upload_raw rather than the float staging path.
284 *
285 * Calling this clears any previously staged native bytes for the slot.
286 * It does not affect m_binding_data (PASSTHROUGH) or m_staging_floats.
287 *
288 * @param binding_index Binding slot. Must match a FLOAT32 INPUT binding.
289 * @param data Raw bytes in the element type the shader expects.
290 * @param byte_size Total byte count.
291 */
292 void stage_native_bytes(size_t binding_index, const void* data, size_t byte_size);
293
294 [[nodiscard]] const GpuComputeConfig& gpu_config() const;
295
296 /**
297 * @brief Full single-pass dispatch. Drives prepare_gpu_inputs,
298 * on_before_gpu_dispatch, bind_descriptor, and GpuResourceManager::dispatch.
299 *
300 * @param channels Extracted double channels from the input Datum.
301 * @param structure_info Dimension/modality metadata from OperationHelper.
302 * @return GpuChannelResult containing primary float readback and aux buffers.
303 */
304 GpuChannelResult dispatch_core(
305 const std::vector<std::vector<double>>& channels,
306 const DataStructureInfo& structure_info);
307
308 /**
309 * @brief Multi-pass (chained) dispatch. Calls dispatch_batched on
310 * GpuResourceManager and reads back once after all passes.
311 *
312 * @param channels Extracted double channels.
313 * @param structure_info Dimension/modality metadata.
314 * @param ctx ExecutionContext carrying pass_count and pc_updater.
315 * @return GpuChannelResult containing primary float readback and aux buffers.
316 */
317 GpuChannelResult dispatch_core_chained(
318 const std::vector<std::vector<double>>& channels,
319 const DataStructureInfo& structure_info,
320 const ExecutionContext& ctx);
321
322 /**
323 * @brief Multi-pass dispatch where a GPU-resident indirect buffer gates
324 * each pass's workgroup count instead of a fixed pass_count.
325 *
326 * Calls dispatch_batched_indirect on GpuResourceManager and reads back
327 * once after all passes.
328 *
329 * @param channels Extracted double channels.
330 * @param structure_info Dimension/modality metadata.
331 * @param ctx ExecutionContext carrying pass_count, pc_updater,
332 * and indirect_dispatch_binding.
333 * @return GpuChannelResult containing primary float readback and aux buffers.
334 */
335 GpuChannelResult dispatch_core_chained_indirect(
336 const std::vector<std::vector<double>>& channels,
337 const DataStructureInfo& structure_info,
338 const ExecutionContext& ctx);
339
340 /**
341 * @brief Non-blocking variant of dispatch_core.
342 *
343 * Performs the full setup (on_before_gpu_dispatch, prepare_gpu_inputs,
344 * bind_descriptor) then calls GpuResourceManager::dispatch_async.
345 * Returns immediately with a FenceID. The caller must poll
346 * ShaderFoundry::is_fence_signaled on the returned ID, and once
347 * signaled call readback_primary / readback_aux to collect results.
348 *
349 * @param channels Extracted double channels from the input Datum.
350 * @param structure_info Dimension/modality metadata from OperationHelper.
351 * @return FenceID to poll. INVALID_FENCE if dispatch fails.
352 */
353 [[nodiscard]] Portal::Graphics::FenceID dispatch_core_async(
354 const std::vector<std::vector<double>>& channels,
355 const DataStructureInfo& structure_info);
356
357 /**
358 * @brief Multi-pipeline dependency dispatch.
359 *
360 * For each stage, in order: applies its shader config (swap_shader
361 * equivalent), invokes stage_fn to perform whatever staging that stage
362 * needs, ensures its GpuResourceManager unit exists, binds its
363 * descriptors, invokes hazard_fn to resolve hazard resources now that
364 * this stage's data exists, then accumulates a Portal::Graphics::ComputeStage.
365 * After every stage is prepared, records and submits the full sequence in
366 * one command buffer via GpuResourceManager::dispatch_sequence.
367 *
368 * Restores the context's original shader config, bindings, and staged
369 * data after the sequence runs, so a subsequent unrelated dispatch_core
370 * call on this context is unaffected.
371 *
372 * @param stages Ordered list of stage descriptions.
373 */
374 void dispatch_core_dependency(const std::vector<DependencyStage>& stages);
375
376 /**
377 * @brief Effective element count used by the last dispatch_core or
378 * dispatch_core_async call.
379 *
380 * Cached after each dispatch so callers can pass the correct count to
381 * readback_primary without re-deriving it.
382 */
383 [[nodiscard]] size_t last_effective_element_count() const
384 {
385 return m_last_effective_element_count;
386 }
387
388 /**
389 * @brief Read back the primary output buffer into a float vector.
390 *
391 * Selects the first OUTPUT or INPUT_OUTPUT binding. Caps readback to
392 * the lesser of the requested float count and the allocated buffer size.
393 *
394 * @param float_count Number of float elements to attempt to read.
395 * @return Float vector of length min(float_count, allocated / sizeof(float)).
396 */
397 [[nodiscard]] std::vector<float> readback_primary(size_t float_count);
398
399 /**
400 * @brief Read back all OUTPUT bindings that have explicit size overrides
401 * into the aux map of a GpuChannelResult.
402 *
403 * @param result GpuChannelResult to write aux entries into.
404 */
405 void readback_aux(GpuChannelResult& result);
406
407 /**
408 * @brief Flatten planar double channels into m_staging_floats.
409 *
410 * Skipped for structured modalities (glm::vec3 etc.) since those are
411 * handled per-binding via PASSTHROUGH or integer paths.
412 */
413 void flatten_channels_to_staging(
414 const std::vector<std::vector<double>>& channels,
415 const DataStructureInfo& structure_info);
416
417 /**
418 * @brief Flatten native-typed DataVariant channels into m_native_staging_bytes
419 * without any conversion.
420 *
421 * Called by prepare_gpu_inputs when structure_info.original_type indicates
422 * a non-double native type and no explicit stage_native_bytes call has been
423 * made. Visits the variant's active alternative and memcpys bytes directly.
424 *
425 * No-ops when channels is empty or the modality is structured.
426 *
427 * @param variants Per-channel DataVariants from the container.
428 * @param structure_info Dimension/modality metadata.
429 */
430 void flatten_native_variants_to_staging(
431 const std::vector<Kakshya::DataVariant>& variants,
432 const DataStructureInfo& structure_info);
433
434 [[nodiscard]] size_t find_first_output_index() const;
435 [[nodiscard]] size_t largest_binding_data_element_count() const;
436
438 std::vector<GpuBufferBinding> m_bindings;
439 std::vector<float> m_staging_floats;
440 std::vector<uint8_t> m_push_constants;
441 std::vector<size_t> m_output_size_overrides;
442 std::vector<std::vector<uint8_t>> m_passthrough_bytes;
443 std::vector<std::vector<uint8_t>> m_binding_data;
444 std::set<std::pair<uint32_t, size_t>> m_shared_bindings;
445
447 std::shared_ptr<Core::VKImage> image;
448 vk::Sampler sampler;
450 };
451 std::vector<ImageBinding> m_image_bindings;
452
453private:
456
457 size_t m_last_effective_element_count {};
458
459 /// Native-typed staging buffer. Non-empty when stage_native_bytes() has
460 /// been called or flatten_native_variants_to_staging() produced output.
461 /// Takes precedence over m_staging_floats in prepare_gpu_inputs.
462 std::vector<uint8_t> m_native_staging_bytes;
463
464 void update_dispatch_key_cache();
465 void bind_all_descriptors();
466};
467
468} // namespace MayaFlux::Yantra
IO::ImageData image
Definition Decoder.cpp:64
std::vector< ImageBinding > m_image_bindings
std::vector< uint8_t > m_native_staging_bytes
Native-typed staging buffer.
GpuDispatchCore(GpuDispatchCore &&)=delete
std::set< std::pair< uint32_t, size_t > > m_shared_bindings
Portal::Graphics::HazardResource shared_buffer_hazard(const GpuBufferBinding &spec) const
void ensure_shared_buffer(uint32_t set, size_t binding_index, size_t element_count, GpuBufferBinding::ElementType element_type, Portal::Graphics::BufferUsageHint usage_hint=Portal::Graphics::BufferUsageHint::COMPUTE_STORAGE)
const std::string & dispatch_key() const
The key used for this context's GpuResourceManager unit.
void download_shared(uint32_t set, size_t binding_index, void *dest, size_t byte_size)
std::vector< uint8_t > m_push_constants
void upload_shared_raw(uint32_t set, size_t binding_index, const uint8_t *data, size_t byte_size)
void set_binding_data(size_t index, const std::vector< T > &data)
std::vector< std::vector< uint8_t > > m_binding_data
void swap_shader(GpuComputeConfig config)
Switch which shader subsequent dispatch_core calls target.
std::vector< GpuBufferBinding > m_bindings
std::vector< size_t > m_output_size_overrides
virtual ~GpuDispatchCore()=default
void set_push_constants(const T &data)
Typed convenience wrapper for set_push_constants(const void*, size_t).
size_t last_effective_element_count() const
Effective element count used by the last dispatch_core or dispatch_core_async call.
GpuDispatchCore & operator=(GpuDispatchCore &&)=delete
std::vector< std::vector< uint8_t > > m_passthrough_bytes
GpuDispatchCore(const GpuDispatchCore &)=delete
GpuDispatchCore & operator=(const GpuDispatchCore &)=delete
void set_binding_data(size_t index, std::span< const T > data)
Pre-stage typed data for a specific binding slot, bypassing the default channel-flattening path in pr...
Non-template base that owns all type-independent GPU dispatch logic.
Encapsulates all Vulkan resource lifecycle behind Portal facades.
BufferUsageHint
Semantic usage hint for buffer allocation and memory properties.
ElementType
Element type the shader expects in this binding.
Declares a single storage buffer or image binding a compute shader expects.
Plain-data description of the compute shader to dispatch.
One resource this stage's dispatch reads/writes that a later stage in the sequence depends on,...
Metadata about data structure for reconstruction.
std::function< void(GpuDispatchCore &)> stage_fn
std::function< std::vector< Portal::Graphics::HazardResource >(GpuDispatchCore &)> hazard_fn
std::optional< std::array< uint32_t, 3 > > explicit_groups
One stage in a dispatch_core_dependency call.
Context information controlling how a compute operation executes.
std::unordered_map< size_t, std::vector< uint8_t > > aux
Erased output of a GPU dispatch: reconstructed float data plus any raw auxiliary outputs keyed by bin...