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, size_t dst_offset)
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 (dst_offset + bytes > target->get_size_bytes()) {
24 error<std::runtime_error>(
27 std::source_location::current(),
28 "Upload data size {} at offset {} exceeds buffer capacity {}",
29 bytes, dst_offset, 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(static_cast<uint8_t*>(mapped) + dst_offset, ptr, bytes);
43
44 target->mark_dirty_range(dst_offset, 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, size_t dst_offset)
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 (dst_offset + bytes > target->get_size_bytes()) {
76 error<std::runtime_error>(
79 std::source_location::current(),
80 "Upload data size {} at offset {} exceeds buffer capacity {}",
81 bytes, dst_offset, 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, dst_offset);
121}
122
125 const void* data,
126 size_t size,
127 std::shared_ptr<VKBuffer>& staging)
128{
129 auto handle = upload_back_buffer_async(slot, data, size, staging);
130 resolve_transfer(handle);
131}
132
135 const void* data,
136 size_t size,
137 std::shared_ptr<VKBuffer>& staging)
138{
139 if (!data || size == 0)
140 return {};
141
142 if (slot.mapped_ptr) {
143 std::memcpy(slot.mapped_ptr, data, size);
144 return {};
145 }
146
147 auto buffer_service = Registry::BackendRegistry::instance()
149
150 if (!buffer_service
151 || !buffer_service->execute_fenced
152 || !buffer_service->wait_fenced
153 || !buffer_service->release_fenced
154 || !buffer_service->flush_range) {
155 error<std::runtime_error>(
158 std::source_location::current(),
159 "upload_back_buffer_async: BufferService unavailable");
160 }
161
162 if (!staging || staging->get_size_bytes() < size)
163 staging = create_staging_buffer(size);
164
165 void* ptr = staging->get_mapped_ptr();
166 if (!ptr) {
167 error<std::runtime_error>(
170 std::source_location::current(),
171 "upload_back_buffer_async: staging buffer has no mapped pointer");
172 }
173
174 std::memcpy(ptr, data, size);
175
176 auto& resources = staging->get_buffer_resources();
177 buffer_service->flush_range(resources.memory, 0, size);
178
179 auto handle = buffer_service->copy_buffer_fenced(
180 static_cast<void*>(staging->get_buffer()),
181 static_cast<void*>(slot.buffer),
182 size, 0, 0);
183
184 if (!handle) {
185 error<std::runtime_error>(
188 std::source_location::current(),
189 "upload_back_buffer_async: copy_buffer_fenced returned null");
190 }
191
192 return handle;
193}
194
196{
197 if (!handle)
198 return;
199
200 auto buffer_service = Registry::BackendRegistry::instance()
202
203 if (buffer_service && buffer_service->wait_fenced && buffer_service->release_fenced) {
204 buffer_service->wait_fenced(handle);
205 buffer_service->release_fenced(handle);
206 }
207
208 handle.reset();
209}
210
211void download_host_visible(const std::shared_ptr<VKBuffer>& source, const std::shared_ptr<VKBuffer>& target)
212{
213 auto& source_resources = source->get_buffer_resources();
214 void* mapped = source_resources.mapped_ptr;
215 if (!mapped) {
216 error<std::runtime_error>(
219 std::source_location::current(),
220 "Host-visible buffer has no mapped pointer");
221 }
222
223 source->mark_invalid_range(0, source->get_size_bytes());
224
225 auto buffer_service = Registry::BackendRegistry::instance()
227
228 if (!buffer_service) {
229 error<std::runtime_error>(
232 std::source_location::current(),
233 "upload_host_visible requires a valid buffer service");
234 }
235
236 auto invalid_ranges = source->get_and_clear_invalid_ranges();
237 for (auto& [offset, size] : invalid_ranges) {
238 buffer_service->invalidate_range(
239 source_resources.memory,
240 offset,
241 size);
242 }
243
244 std::vector<uint8_t> raw_bytes(source->get_size_bytes());
245 std::memcpy(raw_bytes.data(), mapped, source->get_size_bytes());
246
247 std::dynamic_pointer_cast<VKBuffer>(target)->set_data({ raw_bytes });
248}
249
250void download_device_local(const std::shared_ptr<VKBuffer>& source, const std::shared_ptr<VKBuffer>& target, const std::shared_ptr<VKBuffer>& staging_buffer)
251{
252 auto buffer_service = Registry::BackendRegistry::instance()
254
255 if (!buffer_service) {
256 error<std::runtime_error>(
259 std::source_location::current(),
260 "download_device_local requires a valid buffer service");
261 }
262
263 buffer_service->copy_buffer(
264 static_cast<void*>(source->get_buffer()),
265 static_cast<void*>(staging_buffer->get_buffer()),
266 source->get_size_bytes(), 0, 0);
267
268 staging_buffer->mark_invalid_range(0, source->get_size_bytes());
269
270 auto& staging_resources = staging_buffer->get_buffer_resources();
271 auto invalid_ranges = staging_buffer->get_and_clear_invalid_ranges();
272 for (auto& [offset, size] : invalid_ranges) {
273 buffer_service->invalidate_range(
274 staging_resources.memory,
275 offset,
276 size);
277 }
278
279 void* staging_mapped = staging_resources.mapped_ptr;
280 if (!staging_mapped) {
281 error<std::runtime_error>(
284 std::source_location::current(),
285 "Staging buffer has no mapped pointer");
286 }
287
288 std::vector<uint8_t> raw_bytes(source->get_size_bytes());
289 std::memcpy(raw_bytes.data(), staging_mapped, source->get_size_bytes());
290
291 std::dynamic_pointer_cast<VKBuffer>(target)->set_data({ raw_bytes });
292}
293
296 void* data,
297 size_t size,
298 std::shared_ptr<VKBuffer>& staging)
299{
300 if (!data || size == 0)
301 return;
302
303 if (slot.mapped_ptr) {
304 std::memcpy(data, slot.mapped_ptr, size);
305 return;
306 }
307
308 auto buffer_service = Registry::BackendRegistry::instance()
310
311 if (!buffer_service
312 || !buffer_service->execute_fenced
313 || !buffer_service->wait_fenced
314 || !buffer_service->release_fenced
315 || !buffer_service->invalidate_range) {
316 error<std::runtime_error>(
319 std::source_location::current(),
320 "download_back_buffer: BufferService unavailable");
321 }
322
323 if (!staging || staging->get_size_bytes() < size)
324 staging = create_staging_buffer(size);
325
326 auto handle = buffer_service->copy_buffer_fenced(
327 static_cast<void*>(slot.buffer),
328 static_cast<void*>(staging->get_buffer()),
329 size, 0, 0);
330
331 buffer_service->wait_fenced(handle);
332
333 auto& resources = staging->get_buffer_resources();
334 buffer_service->invalidate_range(resources.memory, 0, size);
335
336 std::memcpy(data, staging->get_mapped_ptr(), size);
337 buffer_service->release_fenced(handle);
338}
339
340bool is_device_local(const std::shared_ptr<VKBuffer>& buffer)
341{
342 return buffer && !buffer->is_host_visible();
343}
344
345std::shared_ptr<VKBuffer> create_staging_buffer(size_t size)
346{
347 auto buffer = std::make_shared<VKBuffer>(
348 size,
349 VKBuffer::Usage::STAGING,
351
352 auto buffer_service = Registry::BackendRegistry::instance()
354 if (!buffer_service) {
355 error<std::runtime_error>(
358 std::source_location::current(),
359 "create_staging_buffer requires a valid buffer service");
360 }
361
362 buffer_service->initialize_buffer(buffer);
363
364 return buffer;
365}
366
367std::shared_ptr<VKBuffer> create_image_staging_buffer(size_t size)
368{
369 auto buf = std::make_shared<VKBuffer>(
370 size,
371 VKBuffer::Usage::STAGING,
373
374 auto buffer_service = Registry::BackendRegistry::instance()
376
377 if (!buffer_service) {
378 error<std::runtime_error>(
381 std::source_location::current(),
382 "create_image_staging_buffer requires a valid buffer service");
383 }
384
385 buffer_service->initialize_buffer(buf);
386
388 "create_image_staging_buffer: allocated {} bytes", size);
389
390 return buf;
391}
392
394 const std::shared_ptr<VKBuffer>& target,
395 const std::shared_ptr<VKBuffer>& staging,
396 size_t required,
397 float growth_factor)
398{
399 if (required <= target->get_size_bytes()) {
400 return;
401 }
402
403 const auto new_size = static_cast<size_t>(static_cast<float>(required) * growth_factor);
404
405 target->resize(new_size, false);
406
407 if (staging) {
408 staging->resize(new_size, false);
409 }
410}
411
413 const void* data,
414 size_t size,
415 const std::shared_ptr<VKBuffer>& target,
416 const std::shared_ptr<VKBuffer>& staging,
417 size_t dst_offset)
418{
419 if (!target) {
420 error<std::invalid_argument>(
423 std::source_location::current(),
424 "upload_to_gpu: target buffer is null");
425 }
426
427 if (size == 0) {
428 return;
429 }
430
431 std::vector<uint8_t> raw_bytes(size);
432 std::memcpy(raw_bytes.data(), data, size);
433 Kakshya::DataVariant data_variant(raw_bytes);
434
435 if (target->is_host_visible()) {
436 upload_host_visible(target, data_variant, dst_offset);
437 } else {
438 std::shared_ptr<VKBuffer> staging_buf = staging;
439
440 if (!staging_buf) {
441 staging_buf = create_staging_buffer(size);
442 }
443
444 upload_device_local(target, staging_buf, data_variant, dst_offset);
445 }
446}
447
449 const void* data,
450 size_t size,
451 const std::shared_ptr<VKBuffer>& target,
452 const std::shared_ptr<VKBuffer>& staging,
453 float growth_factor)
454{
455 ensure_gpu_capacity(target, staging, size, growth_factor);
456 upload_to_gpu(data, size, target, staging);
457}
458
460 const std::shared_ptr<VKBuffer>& source,
461 void* data,
462 size_t size,
463 const std::shared_ptr<VKBuffer>& staging)
464{
465 if (!source) {
466 error<std::invalid_argument>(
469 std::source_location::current(),
470 "download_from_gpu: source buffer is null");
471 }
472
473 if (size == 0) {
474 return;
475 }
476
477 auto temp_target = std::make_shared<VKBuffer>(
478 size, VKBuffer::Usage::STAGING, Kakshya::DataModality::UNKNOWN);
479
480 auto buffer_service = Registry::BackendRegistry::instance()
482
483 if (!buffer_service) {
484 error<std::runtime_error>(
487 std::source_location::current(),
488 "download_from_gpu requires a valid buffer service");
489 }
490
491 buffer_service->initialize_buffer(temp_target);
492
493 if (source->is_host_visible()) {
494 download_host_visible(source, temp_target);
495 } else {
496 std::shared_ptr<VKBuffer> staging_buf = staging;
497
498 if (!staging_buf) {
499 staging_buf = create_staging_buffer(size);
500 }
501
502 download_device_local(source, temp_target, staging_buf);
503 }
504
505 auto temp_data = temp_target->get_data();
506
507 if (temp_data.empty()) {
508 error<std::runtime_error>(
511 std::source_location::current(),
512 "download_from_gpu: failed to retrieve data from temporary buffer");
513 }
514
515 if (temp_data.size() > 1) {
517 "download_from_gpu: unexpected multiple data variants in temporary buffer. Only the first will be used.");
518 }
519
520 Kakshya::DataAccess accessor(
521 const_cast<Kakshya::DataVariant&>(temp_data[0]),
522 {},
523 source->get_modality());
524
525 auto [ptr, bytes, format_hint] = accessor.gpu_buffer();
526
527 std::memcpy(data, ptr, std::min(size, bytes));
528}
529
531 const std::shared_ptr<VKBuffer>& source,
532 void* data,
533 size_t size,
534 std::shared_ptr<VKBuffer>& staging)
535{
536 if (!source || !data || size == 0)
537 return;
538
539 auto buffer_service = Registry::BackendRegistry::instance()
541
542 if (!buffer_service
543 || !buffer_service->execute_fenced
544 || !buffer_service->wait_fenced
545 || !buffer_service->release_fenced
546 || !buffer_service->invalidate_range) {
547 error<std::runtime_error>(
550 std::source_location::current(),
551 "download_from_gpu_async: BufferService unavailable");
552 }
553
554 if (!staging || staging->get_size_bytes() < size)
555 staging = create_staging_buffer(size);
556
557 auto handle = buffer_service->copy_buffer_fenced(
558 static_cast<void*>(source->get_buffer()),
559 static_cast<void*>(staging->get_buffer()),
560 size, 0, 0);
561
562 if (!handle) {
563 error<std::runtime_error>(
566 std::source_location::current(),
567 "download_from_gpu_async: execute_fenced returned null");
568 }
569
570 buffer_service->wait_fenced(handle);
571
572 auto& resources = staging->get_buffer_resources();
573 buffer_service->invalidate_range(resources.memory, 0, size);
574
575 void* ptr = staging->get_mapped_ptr();
576 if (!ptr) {
577 buffer_service->release_fenced(handle);
578 error<std::runtime_error>(
581 std::source_location::current(),
582 "download_from_gpu_async: staging buffer has no mapped pointer");
583 }
584
585 std::memcpy(data, ptr, size);
586 buffer_service->release_fenced(handle);
587}
588
589std::span<const float> download_and_normalise(
590 const std::shared_ptr<Core::VKImage>& image,
591 std::vector<uint8_t>& raw_staging,
592 std::vector<float>& work,
593 const std::shared_ptr<VKBuffer>& gpu_staging)
594{
595 if (!image || !image->is_initialized())
596 return {};
597
599
600 const auto fmt_opt = loom.from_vulkan_format(image->get_format());
601 if (!fmt_opt)
602 return {};
603
604 const size_t byte_size = static_cast<size_t>(image->get_width())
605 * image->get_height()
606 * loom.get_bytes_per_pixel(*fmt_opt);
607
608 if (byte_size == 0)
609 return {};
610
611 raw_staging.resize(byte_size);
612 loom.download_data(image, raw_staging.data(), byte_size, gpu_staging);
613
614 Kakshya::DataVariant variant { raw_staging };
615 return Kakshya::as_normalised_float(variant, work);
616}
617
619 const std::shared_ptr<AudioBuffer>& audio_buffer,
620 const std::shared_ptr<VKBuffer>& gpu_buffer,
621 const std::shared_ptr<VKBuffer>& staging)
622{
623 const auto modality = gpu_buffer->get_modality();
624 if (modality != Kakshya::DataModality::AUDIO_1D
626 && modality != Kakshya::DataModality::UNKNOWN) {
627
628 error<std::runtime_error>(
631 std::source_location::current(),
632 "GPU buffer modality is {} but audio requires AUDIO_1D or AUDIO_MULTICHANNEL. "
633 "Create VKBuffer with DataModality::AUDIO_1D or AUDIO_MULTICHANNEL.",
635 }
636
637 auto& audio_data = audio_buffer->get_data();
638
639 if (audio_data.empty()) {
641 "AudioBuffer contains no data to upload");
642 return;
643 }
644
645 const void* data_ptr = audio_data.data();
646 size_t data_bytes = audio_data.size() * sizeof(double);
647
648 upload_to_gpu(data_ptr, data_bytes, gpu_buffer, staging);
649
651 "Uploaded {} bytes of double-precision audio to GPU", data_bytes);
652}
653
655 const std::shared_ptr<VKBuffer>& gpu_buffer,
656 const std::shared_ptr<AudioBuffer>& audio_buffer,
657 const std::shared_ptr<VKBuffer>& staging)
658{
659 Kakshya::DataVariant downloaded_data;
660
661 auto dimensions = std::vector<Kakshya::DataDimension> {
663 gpu_buffer->get_size_bytes() / sizeof(double),
664 "samples")
665 };
666 Kakshya::DataAccess accessor = download_to_view<double>(
667 gpu_buffer, downloaded_data, dimensions,
669
670 auto double_view = accessor.view<double>();
671
672 audio_buffer->get_data() = std::vector<double>(double_view.begin(), double_view.end());
673
675 "Downloaded {} samples of double-precision audio from GPU", double_view.size());
676}
677
678} // 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 resolve_transfer(TransferHandle &handle)
Wait for a transfer and release its resources.
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...
std::shared_ptr< void > TransferHandle
Opaque handle to an in-flight transfer.
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.
TransferHandle upload_back_buffer_async(const VKBufferResources::GenerationSlot &slot, const void *data, size_t size, std::shared_ptr< VKBuffer > &staging)
Upload to a back_buffers slot without waiting for completion.
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 upload_to_gpu(const void *data, size_t size, const std::shared_ptr< VKBuffer > &target, const std::shared_ptr< VKBuffer > &staging, size_t dst_offset)
Upload raw data to GPU buffer (auto-detects host-visible vs device-local)
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_back_buffer(const VKBufferResources::GenerationSlot &slot, void *data, size_t size, std::shared_ptr< VKBuffer > &staging)
Download a raw back_buffers slot to host memory.
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_host_visible(const std::shared_ptr< VKBuffer > &target, const Kakshya::DataVariant &data, size_t dst_offset)
Upload data to a host-visible buffer.
void upload_device_local(const std::shared_ptr< VKBuffer > &target, const std::shared_ptr< VKBuffer > &staging_buffer, const Kakshya::DataVariant &data, size_t dst_offset)
Upload data to a device-local buffer using a staging buffer.
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.
@ 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:130
static DataDimension time(uint64_t samples, std::string name="time")
Convenience constructor for a temporal (time) dimension.
Definition NDData.cpp:45
std::function< void(const std::shared_ptr< void > &)> wait_fenced
Wait for a fenced submission to complete.
Backend buffer management service interface.