MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
StagingUtils.cpp
Go to the documentation of this file.
1#include "StagingUtils.hpp"
2
5
8
11
12namespace MayaFlux::Buffers {
13
14void upload_host_visible(const std::shared_ptr<VKBuffer>& target, const Kakshya::DataVariant& data)
15{
16 Kakshya::DataAccess accessor(
17 const_cast<Kakshya::DataVariant&>(data),
18 {},
19 target->get_modality());
20
21 auto [ptr, bytes, format_hint] = accessor.gpu_buffer();
22
23 if (bytes > target->get_size_bytes()) {
24 error<std::runtime_error>(
27 std::source_location::current(),
28 "Upload data size {} exceeds buffer capacity {}",
29 bytes, target->get_size_bytes());
30 }
31
32 auto& target_resources = target->get_buffer_resources();
33 void* mapped = target_resources.mapped_ptr;
34 if (!mapped) {
35 error<std::runtime_error>(
38 std::source_location::current(),
39 "Host-visible buffer has no mapped pointer");
40 }
41
42 std::memcpy(mapped, ptr, bytes);
43
44 target->mark_dirty_range(0, bytes);
45
46 auto buffer_service = Registry::BackendRegistry::instance()
48
49 if (!buffer_service) {
50 error<std::runtime_error>(
53 std::source_location::current(),
54 "upload_host_visible requires a valid buffer service");
55 }
56
57 auto dirty_ranges = target->get_and_clear_dirty_ranges();
58 for (auto& [offset, size] : dirty_ranges) {
59 buffer_service->flush_range(
60 target_resources.memory,
61 offset,
62 size);
63 }
64}
65
66void upload_device_local(const std::shared_ptr<VKBuffer>& target, const std::shared_ptr<VKBuffer>& staging_buffer, const Kakshya::DataVariant& data)
67{
68 Kakshya::DataAccess accessor(
69 const_cast<Kakshya::DataVariant&>(data),
70 {},
71 target->get_modality());
72
73 auto [ptr, bytes, format_hint] = accessor.gpu_buffer();
74
75 if (bytes > target->get_size_bytes()) {
76 error<std::runtime_error>(
79 std::source_location::current(),
80 "Upload data size {} exceeds buffer capacity {}",
81 bytes, target->get_size_bytes());
82 }
83
84 auto& staging_resources = staging_buffer->get_buffer_resources();
85
86 void* staging_mapped = staging_resources.mapped_ptr;
87 if (!staging_mapped) {
88 error<std::runtime_error>(
91 std::source_location::current(),
92 "Staging buffer has no mapped pointer");
93 }
94
95 std::memcpy(staging_mapped, ptr, bytes);
96 staging_buffer->mark_dirty_range(0, bytes);
97
98 auto buffer_service = Registry::BackendRegistry::instance()
100
101 if (!buffer_service) {
102 error<std::runtime_error>(
105 std::source_location::current(),
106 "upload_host_visible requires a valid buffer service");
107 }
108
109 auto dirty_ranges = staging_buffer->get_and_clear_dirty_ranges();
110 for (auto& [offset, size] : dirty_ranges) {
111 buffer_service->flush_range(
112 staging_resources.memory,
113 offset,
114 size);
115 }
116
117 buffer_service->copy_buffer(
118 static_cast<void*>(staging_buffer->get_buffer()),
119 static_cast<void*>(target->get_buffer()),
120 bytes, 0, 0);
121}
122
123void download_host_visible(const std::shared_ptr<VKBuffer>& source, const std::shared_ptr<VKBuffer>& target)
124{
125 auto& source_resources = source->get_buffer_resources();
126 void* mapped = source_resources.mapped_ptr;
127 if (!mapped) {
128 error<std::runtime_error>(
131 std::source_location::current(),
132 "Host-visible buffer has no mapped pointer");
133 }
134
135 source->mark_invalid_range(0, source->get_size_bytes());
136
137 auto buffer_service = Registry::BackendRegistry::instance()
139
140 if (!buffer_service) {
141 error<std::runtime_error>(
144 std::source_location::current(),
145 "upload_host_visible requires a valid buffer service");
146 }
147
148 auto invalid_ranges = source->get_and_clear_invalid_ranges();
149 for (auto& [offset, size] : invalid_ranges) {
150 buffer_service->invalidate_range(
151 source_resources.memory,
152 offset,
153 size);
154 }
155
156 std::vector<uint8_t> raw_bytes(source->get_size_bytes());
157 std::memcpy(raw_bytes.data(), mapped, source->get_size_bytes());
158
159 std::dynamic_pointer_cast<VKBuffer>(target)->set_data({ raw_bytes });
160}
161
162void download_device_local(const std::shared_ptr<VKBuffer>& source, const std::shared_ptr<VKBuffer>& target, const std::shared_ptr<VKBuffer>& staging_buffer)
163{
164 auto buffer_service = Registry::BackendRegistry::instance()
166
167 if (!buffer_service) {
168 error<std::runtime_error>(
171 std::source_location::current(),
172 "download_device_local requires a valid buffer service");
173 }
174
175 buffer_service->copy_buffer(
176 static_cast<void*>(source->get_buffer()),
177 static_cast<void*>(staging_buffer->get_buffer()),
178 source->get_size_bytes(), 0, 0);
179
180 staging_buffer->mark_invalid_range(0, source->get_size_bytes());
181
182 auto& staging_resources = staging_buffer->get_buffer_resources();
183 auto invalid_ranges = staging_buffer->get_and_clear_invalid_ranges();
184 for (auto& [offset, size] : invalid_ranges) {
185 buffer_service->invalidate_range(
186 staging_resources.memory,
187 offset,
188 size);
189 }
190
191 void* staging_mapped = staging_resources.mapped_ptr;
192 if (!staging_mapped) {
193 error<std::runtime_error>(
196 std::source_location::current(),
197 "Staging buffer has no mapped pointer");
198 }
199
200 std::vector<uint8_t> raw_bytes(source->get_size_bytes());
201 std::memcpy(raw_bytes.data(), staging_mapped, source->get_size_bytes());
202
203 std::dynamic_pointer_cast<VKBuffer>(target)->set_data({ raw_bytes });
204}
205
206bool is_device_local(const std::shared_ptr<VKBuffer>& buffer)
207{
208 return buffer && !buffer->is_host_visible();
209}
210
211std::shared_ptr<VKBuffer> create_staging_buffer(size_t size)
212{
213 auto buffer = std::make_shared<VKBuffer>(
214 size,
215 VKBuffer::Usage::STAGING,
217
218 auto buffer_service = Registry::BackendRegistry::instance()
220 if (!buffer_service) {
221 error<std::runtime_error>(
224 std::source_location::current(),
225 "create_staging_buffer requires a valid buffer service");
226 }
227
228 buffer_service->initialize_buffer(buffer);
229
230 return buffer;
231}
232
233std::shared_ptr<VKBuffer> create_image_staging_buffer(size_t size)
234{
235 auto buf = std::make_shared<VKBuffer>(
236 size,
237 VKBuffer::Usage::STAGING,
239
240 auto buffer_service = Registry::BackendRegistry::instance()
242
243 if (!buffer_service) {
244 error<std::runtime_error>(
247 std::source_location::current(),
248 "create_image_staging_buffer requires a valid buffer service");
249 }
250
251 buffer_service->initialize_buffer(buf);
252
254 "create_image_staging_buffer: allocated {} bytes", size);
255
256 return buf;
257}
258
260 const std::shared_ptr<VKBuffer>& target,
261 const std::shared_ptr<VKBuffer>& staging,
262 size_t required,
263 float growth_factor)
264{
265 if (required <= target->get_size_bytes()) {
266 return;
267 }
268
269 const auto new_size = static_cast<size_t>(static_cast<float>(required) * growth_factor);
270
271 target->resize(new_size, false);
272
273 if (staging) {
274 staging->resize(new_size, false);
275 }
276}
277
279 const void* data,
280 size_t size,
281 const std::shared_ptr<VKBuffer>& target,
282 const std::shared_ptr<VKBuffer>& staging)
283{
284 if (!target) {
285 error<std::invalid_argument>(
288 std::source_location::current(),
289 "upload_to_gpu: target buffer is null");
290 }
291
292 if (size == 0) {
293 return;
294 }
295
296 std::vector<uint8_t> raw_bytes(size);
297 std::memcpy(raw_bytes.data(), data, size);
298 Kakshya::DataVariant data_variant(raw_bytes);
299
300 if (target->is_host_visible()) {
301 upload_host_visible(target, data_variant);
302 } else {
303 std::shared_ptr<VKBuffer> staging_buf = staging;
304
305 if (!staging_buf) {
306 staging_buf = create_staging_buffer(size);
307 }
308
309 upload_device_local(target, staging_buf, data_variant);
310 }
311}
312
314 const void* data,
315 size_t size,
316 const std::shared_ptr<VKBuffer>& target,
317 const std::shared_ptr<VKBuffer>& staging,
318 float growth_factor)
319{
320 ensure_gpu_capacity(target, staging, size, growth_factor);
321 upload_to_gpu(data, size, target, staging);
322}
323
325 const std::shared_ptr<VKBuffer>& source,
326 void* data,
327 size_t size,
328 const std::shared_ptr<VKBuffer>& staging)
329{
330 if (!source) {
331 error<std::invalid_argument>(
334 std::source_location::current(),
335 "download_from_gpu: source buffer is null");
336 }
337
338 if (size == 0) {
339 return;
340 }
341
342 auto temp_target = std::make_shared<VKBuffer>(
343 size, VKBuffer::Usage::STAGING, Kakshya::DataModality::UNKNOWN);
344
345 auto buffer_service = Registry::BackendRegistry::instance()
347
348 if (!buffer_service) {
349 error<std::runtime_error>(
352 std::source_location::current(),
353 "download_from_gpu requires a valid buffer service");
354 }
355
356 buffer_service->initialize_buffer(temp_target);
357
358 if (source->is_host_visible()) {
359 download_host_visible(source, temp_target);
360 } else {
361 std::shared_ptr<VKBuffer> staging_buf = staging;
362
363 if (!staging_buf) {
364 staging_buf = create_staging_buffer(size);
365 }
366
367 download_device_local(source, temp_target, staging_buf);
368 }
369
370 auto temp_data = temp_target->get_data();
371
372 if (temp_data.empty()) {
373 error<std::runtime_error>(
376 std::source_location::current(),
377 "download_from_gpu: failed to retrieve data from temporary buffer");
378 }
379
380 if (temp_data.size() > 1) {
382 "download_from_gpu: unexpected multiple data variants in temporary buffer. Only the first will be used.");
383 }
384
385 Kakshya::DataAccess accessor(
386 const_cast<Kakshya::DataVariant&>(temp_data[0]),
387 {},
388 source->get_modality());
389
390 auto [ptr, bytes, format_hint] = accessor.gpu_buffer();
391
392 std::memcpy(data, ptr, std::min(size, bytes));
393}
394
396 const std::shared_ptr<VKBuffer>& source,
397 void* data,
398 size_t size,
399 std::shared_ptr<VKBuffer>& staging)
400{
401 if (!source || !data || size == 0)
402 return;
403
404 auto buffer_service = Registry::BackendRegistry::instance()
406
407 if (!buffer_service
408 || !buffer_service->execute_fenced
409 || !buffer_service->wait_fenced
410 || !buffer_service->release_fenced
411 || !buffer_service->invalidate_range) {
412 error<std::runtime_error>(
415 std::source_location::current(),
416 "download_from_gpu_async: BufferService unavailable");
417 }
418
419 if (!staging || staging->get_size_bytes() < size)
420 staging = create_staging_buffer(size);
421
422 auto handle = buffer_service->copy_buffer_fenced(
423 static_cast<void*>(source->get_buffer()),
424 static_cast<void*>(staging->get_buffer()),
425 size, 0, 0);
426
427 if (!handle) {
428 error<std::runtime_error>(
431 std::source_location::current(),
432 "download_from_gpu_async: execute_fenced returned null");
433 }
434
435 buffer_service->wait_fenced(handle);
436
437 auto& resources = staging->get_buffer_resources();
438 buffer_service->invalidate_range(resources.memory, 0, size);
439
440 void* ptr = staging->get_mapped_ptr();
441 if (!ptr) {
442 buffer_service->release_fenced(handle);
443 error<std::runtime_error>(
446 std::source_location::current(),
447 "download_from_gpu_async: staging buffer has no mapped pointer");
448 }
449
450 std::memcpy(data, ptr, size);
451 buffer_service->release_fenced(handle);
452}
453
454std::span<const float> download_and_normalise(
455 const std::shared_ptr<Core::VKImage>& image,
456 std::vector<uint8_t>& raw_staging,
457 std::vector<float>& work,
458 const std::shared_ptr<VKBuffer>& gpu_staging)
459{
460 if (!image || !image->is_initialized())
461 return {};
462
464
465 const auto fmt_opt = loom.from_vulkan_format(image->get_format());
466 if (!fmt_opt)
467 return {};
468
469 const size_t byte_size = static_cast<size_t>(image->get_width())
470 * image->get_height()
471 * loom.get_bytes_per_pixel(*fmt_opt);
472
473 if (byte_size == 0)
474 return {};
475
476 raw_staging.resize(byte_size);
477 loom.download_data(image, raw_staging.data(), byte_size, gpu_staging);
478
479 Kakshya::DataVariant variant { raw_staging };
480 return Kakshya::as_normalised_float(variant, work);
481}
482
484 const std::shared_ptr<AudioBuffer>& audio_buffer,
485 const std::shared_ptr<VKBuffer>& gpu_buffer,
486 const std::shared_ptr<VKBuffer>& staging)
487{
488 const auto modality = gpu_buffer->get_modality();
489 if (modality != Kakshya::DataModality::AUDIO_1D
491 && modality != Kakshya::DataModality::UNKNOWN) {
492
493 error<std::runtime_error>(
496 std::source_location::current(),
497 "GPU buffer modality is {} but audio requires AUDIO_1D or AUDIO_MULTICHANNEL. "
498 "Create VKBuffer with DataModality::AUDIO_1D or AUDIO_MULTICHANNEL.",
500 }
501
502 auto& audio_data = audio_buffer->get_data();
503
504 if (audio_data.empty()) {
506 "AudioBuffer contains no data to upload");
507 return;
508 }
509
510 const void* data_ptr = audio_data.data();
511 size_t data_bytes = audio_data.size() * sizeof(double);
512
513 upload_to_gpu(data_ptr, data_bytes, gpu_buffer, staging);
514
516 "Uploaded {} bytes of double-precision audio to GPU", data_bytes);
517}
518
520 const std::shared_ptr<VKBuffer>& gpu_buffer,
521 const std::shared_ptr<AudioBuffer>& audio_buffer,
522 const std::shared_ptr<VKBuffer>& staging)
523{
524 Kakshya::DataVariant downloaded_data;
525
526 auto dimensions = std::vector<Kakshya::DataDimension> {
528 gpu_buffer->get_size_bytes() / sizeof(double),
529 "samples")
530 };
531 Kakshya::DataAccess accessor = download_to_view<double>(
532 gpu_buffer, downloaded_data, dimensions,
534
535 auto double_view = accessor.view<double>();
536
537 audio_buffer->get_data() = std::vector<double>(double_view.begin(), double_view.end());
538
540 "Downloaded {} samples of double-precision audio from GPU", double_view.size());
541}
542
543} // namespace MayaFlux::Buffers
#define MF_ERROR(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
IO::ImageData image
Definition Decoder.cpp:64
const uint8_t * ptr
float offset
auto gpu_buffer() const
Get raw buffer info for GPU upload.
auto view() const
Get explicit typed view of data.
Type-erased accessor for NDData with semantic view construction.
Interface * get_service()
Query for a backend service.
static BackendRegistry & instance()
Get the global registry instance.
void upload_host_visible(const std::shared_ptr< VKBuffer > &target, const Kakshya::DataVariant &data)
Upload data to a host-visible buffer.
std::shared_ptr< VKBuffer > create_image_staging_buffer(size_t size)
Allocate a persistent host-visible staging buffer sized for repeated streaming uploads to an image of...
void upload_audio_to_gpu(const std::shared_ptr< AudioBuffer > &audio_buffer, const std::shared_ptr< VKBuffer > &gpu_buffer, const std::shared_ptr< VKBuffer > &staging)
Upload AudioBuffer to GPU (always double precision)
void upload_resizing(const void *data, size_t size, const std::shared_ptr< VKBuffer > &target, const std::shared_ptr< VKBuffer > &staging, float growth_factor)
Upload size bytes to target, growing both buffers first if needed.
void upload_device_local(const std::shared_ptr< VKBuffer > &target, const std::shared_ptr< VKBuffer > &staging_buffer, const Kakshya::DataVariant &data)
Upload data to a device-local buffer using a staging buffer.
std::span< const float > download_and_normalise(const std::shared_ptr< Core::VKImage > &image, std::vector< uint8_t > &raw_staging, std::vector< float > &work, const std::shared_ptr< VKBuffer > &gpu_staging)
Download a VKImage to CPU and return a normalised float span.
void download_from_gpu_async(const std::shared_ptr< VKBuffer > &source, void *data, size_t size, std::shared_ptr< VKBuffer > &staging)
Download from a device-local GPU buffer without stalling the graphics queue.
std::shared_ptr< VKBuffer > create_staging_buffer(size_t size)
Create staging buffer for transfers.
bool is_device_local(const std::shared_ptr< VKBuffer > &buffer)
Check if buffer is device-local (staging needed)
void download_audio_from_gpu(const std::shared_ptr< VKBuffer > &gpu_buffer, const std::shared_ptr< AudioBuffer > &audio_buffer, const std::shared_ptr< VKBuffer > &staging)
Download GPU buffer to AudioBuffer (expects double precision)
void download_from_gpu(const std::shared_ptr< VKBuffer > &source, void *data, size_t size, const std::shared_ptr< VKBuffer > &staging)
Download from GPU buffer to raw data (auto-detects host-visible vs device-local)
void download_host_visible(const std::shared_ptr< VKBuffer > &source, const std::shared_ptr< VKBuffer > &target)
Download data from a host-visible buffer.
void ensure_gpu_capacity(const std::shared_ptr< VKBuffer > &target, const std::shared_ptr< VKBuffer > &staging, size_t required, float growth_factor)
Grow a GPU buffer (and its paired staging buffer) to fit required bytes.
void download_device_local(const std::shared_ptr< VKBuffer > &source, const std::shared_ptr< VKBuffer > &target, const std::shared_ptr< VKBuffer > &staging_buffer)
Download data from a device-local buffer using a staging buffer.
void upload_to_gpu(const void *data, size_t size, const std::shared_ptr< VKBuffer > &target, const std::shared_ptr< VKBuffer > &staging)
Upload raw data to GPU buffer (auto-detects host-visible vs device-local)
@ BufferProcessing
Buffer processing (Buffers::BufferManager, processing chains)
@ Buffers
Buffers, Managers, processors and processing chains.
std::span< const float > as_normalised_float(const DataVariant &variant, std::vector< float > &storage)
Extract a DataVariant holding pixel data as a normalised float span.
Definition DataUtils.cpp:61
std::variant< std::vector< double >, std::vector< float >, std::vector< uint8_t >, std::vector< uint16_t >, std::vector< uint32_t >, std::vector< std::complex< float > >, std::vector< std::complex< double > >, std::vector< glm::vec2 >, std::vector< glm::vec3 >, std::vector< glm::vec4 >, std::vector< glm::mat4 > > DataVariant
Multi-type data storage for different precision needs.
Definition NDData.hpp:102
@ AUDIO_MULTICHANNEL
Multi-channel audio.
@ UNKNOWN
Unknown or undefined modality.
@ IMAGE_COLOR
2D RGB/RGBA image
std::string_view modality_to_string(DataModality modality)
Convert DataModality enum to string representation.
Definition NDData.cpp:111
static DataDimension time(uint64_t samples, std::string name="time")
Convenience constructor for a temporal (time) dimension.
Definition NDData.cpp:45
Backend buffer management service interface.