MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
StagingUtils.cpp
Go to the documentation of this file.
1#include "StagingUtils.hpp"
2
6
12
15
16namespace MayaFlux::Buffers {
17
18void upload_host_visible(const std::shared_ptr<VKBuffer>& target, const Kakshya::DataVariant& data, size_t dst_offset)
19{
20 Kakshya::DataAccess accessor(
21 const_cast<Kakshya::DataVariant&>(data),
22 {},
23 target->get_modality());
24
25 auto [ptr, bytes, format_hint] = accessor.gpu_buffer();
26
27 if (dst_offset + bytes > target->get_size_bytes()) {
28 error<std::runtime_error>(
31 std::source_location::current(),
32 "Upload data size {} at offset {} exceeds buffer capacity {}",
33 bytes, dst_offset, target->get_size_bytes());
34 }
35
36 auto& target_resources = target->get_buffer_resources();
37 void* mapped = target_resources.mapped_ptr;
38 if (!mapped) {
39 error<std::runtime_error>(
42 std::source_location::current(),
43 "Host-visible buffer has no mapped pointer");
44 }
45
46 std::memcpy(static_cast<uint8_t*>(mapped) + dst_offset, ptr, bytes);
47
48 target->mark_dirty_range(dst_offset, bytes);
49
50 auto buffer_service = Registry::BackendRegistry::instance()
52
53 if (!buffer_service) {
54 error<std::runtime_error>(
57 std::source_location::current(),
58 "upload_host_visible requires a valid buffer service");
59 }
60
61 auto dirty_ranges = target->get_and_clear_dirty_ranges();
62 for (auto& [offset, size] : dirty_ranges) {
63 buffer_service->flush_range(
64 target_resources.memory,
65 offset,
66 size);
67 }
68}
69
70void upload_device_local(const std::shared_ptr<VKBuffer>& target, const std::shared_ptr<VKBuffer>& staging_buffer, const Kakshya::DataVariant& data, size_t dst_offset)
71{
72 Kakshya::DataAccess accessor(
73 const_cast<Kakshya::DataVariant&>(data),
74 {},
75 target->get_modality());
76
77 auto [ptr, bytes, format_hint] = accessor.gpu_buffer();
78
79 if (dst_offset + bytes > target->get_size_bytes()) {
80 error<std::runtime_error>(
83 std::source_location::current(),
84 "Upload data size {} at offset {} exceeds buffer capacity {}",
85 bytes, dst_offset, target->get_size_bytes());
86 }
87
88 auto& staging_resources = staging_buffer->get_buffer_resources();
89
90 void* staging_mapped = staging_resources.mapped_ptr;
91 if (!staging_mapped) {
92 error<std::runtime_error>(
95 std::source_location::current(),
96 "Staging buffer has no mapped pointer");
97 }
98
99 std::memcpy(staging_mapped, ptr, bytes);
100 staging_buffer->mark_dirty_range(0, bytes);
101
102 auto buffer_service = Registry::BackendRegistry::instance()
104
105 if (!buffer_service) {
106 error<std::runtime_error>(
109 std::source_location::current(),
110 "upload_host_visible requires a valid buffer service");
111 }
112
113 auto dirty_ranges = staging_buffer->get_and_clear_dirty_ranges();
114 for (auto& [offset, size] : dirty_ranges) {
115 buffer_service->flush_range(
116 staging_resources.memory,
117 offset,
118 size);
119 }
120
121 buffer_service->copy_buffer(
122 static_cast<void*>(staging_buffer->get_buffer()),
123 static_cast<void*>(target->get_buffer()),
124 bytes, 0, dst_offset);
125}
126
129 const void* data,
130 size_t size,
131 std::shared_ptr<VKBuffer>& staging)
132{
133 auto handle = upload_back_buffer_async(slot, data, size, staging);
134 resolve_transfer(handle);
135}
136
139 const void* data,
140 size_t size,
141 std::shared_ptr<VKBuffer>& staging)
142{
143 if (!data || size == 0)
144 return {};
145
146 if (slot.mapped_ptr) {
147 std::memcpy(slot.mapped_ptr, data, size);
148 return {};
149 }
150
151 auto buffer_service = Registry::BackendRegistry::instance()
153
154 if (!buffer_service
155 || !buffer_service->execute_fenced
156 || !buffer_service->wait_fenced
157 || !buffer_service->release_fenced
158 || !buffer_service->flush_range) {
159 error<std::runtime_error>(
162 std::source_location::current(),
163 "upload_back_buffer_async: BufferService unavailable");
164 }
165
166 if (!staging || staging->get_size_bytes() < size)
167 staging = create_staging_buffer(size);
168
169 void* ptr = staging->get_mapped_ptr();
170 if (!ptr) {
171 error<std::runtime_error>(
174 std::source_location::current(),
175 "upload_back_buffer_async: staging buffer has no mapped pointer");
176 }
177
178 std::memcpy(ptr, data, size);
179
180 auto& resources = staging->get_buffer_resources();
181 buffer_service->flush_range(resources.memory, 0, size);
182
183 auto handle = buffer_service->copy_buffer_fenced(
184 static_cast<void*>(staging->get_buffer()),
185 static_cast<void*>(slot.buffer),
186 size, 0, 0);
187
188 if (!handle) {
189 error<std::runtime_error>(
192 std::source_location::current(),
193 "upload_back_buffer_async: copy_buffer_fenced returned null");
194 }
195
196 return handle;
197}
198
200{
201 if (!handle)
202 return;
203
204 auto buffer_service = Registry::BackendRegistry::instance()
206
207 if (buffer_service && buffer_service->wait_fenced && buffer_service->release_fenced) {
208 buffer_service->wait_fenced(handle);
209 buffer_service->release_fenced(handle);
210 }
211
212 handle.reset();
213}
214
215void download_host_visible(const std::shared_ptr<VKBuffer>& source, const std::shared_ptr<VKBuffer>& target)
216{
217 auto& source_resources = source->get_buffer_resources();
218 void* mapped = source_resources.mapped_ptr;
219 if (!mapped) {
220 error<std::runtime_error>(
223 std::source_location::current(),
224 "Host-visible buffer has no mapped pointer");
225 }
226
227 source->mark_invalid_range(0, source->get_size_bytes());
228
229 auto buffer_service = Registry::BackendRegistry::instance()
231
232 if (!buffer_service) {
233 error<std::runtime_error>(
236 std::source_location::current(),
237 "upload_host_visible requires a valid buffer service");
238 }
239
240 auto invalid_ranges = source->get_and_clear_invalid_ranges();
241 for (auto& [offset, size] : invalid_ranges) {
242 buffer_service->invalidate_range(
243 source_resources.memory,
244 offset,
245 size);
246 }
247
248 std::vector<uint8_t> raw_bytes(source->get_size_bytes());
249 std::memcpy(raw_bytes.data(), mapped, source->get_size_bytes());
250
251 std::dynamic_pointer_cast<VKBuffer>(target)->set_data({ raw_bytes });
252}
253
254void download_device_local(const std::shared_ptr<VKBuffer>& source, const std::shared_ptr<VKBuffer>& target, const std::shared_ptr<VKBuffer>& staging_buffer)
255{
256 auto buffer_service = Registry::BackendRegistry::instance()
258
259 if (!buffer_service) {
260 error<std::runtime_error>(
263 std::source_location::current(),
264 "download_device_local requires a valid buffer service");
265 }
266
267 buffer_service->copy_buffer(
268 static_cast<void*>(source->get_buffer()),
269 static_cast<void*>(staging_buffer->get_buffer()),
270 source->get_size_bytes(), 0, 0);
271
272 staging_buffer->mark_invalid_range(0, source->get_size_bytes());
273
274 auto& staging_resources = staging_buffer->get_buffer_resources();
275 auto invalid_ranges = staging_buffer->get_and_clear_invalid_ranges();
276 for (auto& [offset, size] : invalid_ranges) {
277 buffer_service->invalidate_range(
278 staging_resources.memory,
279 offset,
280 size);
281 }
282
283 void* staging_mapped = staging_resources.mapped_ptr;
284 if (!staging_mapped) {
285 error<std::runtime_error>(
288 std::source_location::current(),
289 "Staging buffer has no mapped pointer");
290 }
291
292 std::vector<uint8_t> raw_bytes(source->get_size_bytes());
293 std::memcpy(raw_bytes.data(), staging_mapped, source->get_size_bytes());
294
295 std::dynamic_pointer_cast<VKBuffer>(target)->set_data({ raw_bytes });
296}
297
300 size_t size,
301 std::shared_ptr<VKBuffer>& staging,
302 size_t staging_offset)
303{
304 if (size == 0 || slot.mapped_ptr)
305 return {};
306
307 auto buffer_service = Registry::BackendRegistry::instance()
309
310 if (!buffer_service
311 || !buffer_service->execute_fenced
312 || !buffer_service->wait_fenced
313 || !buffer_service->release_fenced
314 || !buffer_service->invalidate_range) {
315 error<std::runtime_error>(
318 std::source_location::current(),
319 "download_back_buffer_async: BufferService unavailable");
320 }
321
322 if (!staging || staging->get_size_bytes() < staging_offset + size)
323 staging = create_staging_buffer(staging_offset + size);
324
325 return buffer_service->copy_buffer_fenced(
326 static_cast<void*>(slot.buffer),
327 static_cast<void*>(staging->get_buffer()),
328 size, 0, staging_offset);
329}
330
332 TransferHandle& handle,
334 void* data,
335 size_t size,
336 std::shared_ptr<VKBuffer>& staging,
337 size_t staging_offset)
338{
339 if (!data || size == 0)
340 return;
341
342 if (!handle) {
343 if (slot.mapped_ptr) {
344 std::memcpy(data, slot.mapped_ptr, size);
345 }
346 return;
347 }
348
349 auto buffer_service = Registry::BackendRegistry::instance()
351
352 buffer_service->wait_fenced(handle);
353
354 auto& resources = staging->get_buffer_resources();
355 buffer_service->invalidate_range(resources.memory, staging_offset, size);
356
357 const auto* base = static_cast<const uint8_t*>(staging->get_mapped_ptr());
358 std::memcpy(data, base + staging_offset, size);
359
360 buffer_service->release_fenced(handle);
361 handle.reset();
362}
363
366 void* data,
367 size_t size,
368 std::shared_ptr<VKBuffer>& staging)
369{
370 auto handle = download_back_buffer_async(slot, size, staging);
371 resolve_back_buffer_read(handle, slot, data, size, staging);
372}
373
374bool is_device_local(const std::shared_ptr<VKBuffer>& buffer)
375{
376 return buffer && !buffer->is_host_visible();
377}
378
379bool is_transfer_source(const std::shared_ptr<VKBuffer>& buffer)
380{
381 return buffer
382 && static_cast<bool>(buffer->get_usage_flags() & vk::BufferUsageFlagBits::eTransferSrc);
383}
384
386{
387 if (!buffer) {
388 return {};
389 }
390
391 const auto& resources = buffer->get_buffer_resources();
392 return {
393 .buffer = resources.buffer,
394 .mapped_ptr = resources.mapped_ptr,
395 .size_bytes = buffer->get_size_bytes(),
396 };
397}
398
401 size_t size_bytes)
402{
403 if (!slot.buffer) {
404 return {};
405 }
406
407 return {
408 .buffer = slot.buffer,
409 .mapped_ptr = slot.mapped_ptr,
410 .size_bytes = size_bytes,
411 };
412}
413
421
422std::optional<Kakshya::VertexLayout> resolve_vertex_layout(
423 const std::shared_ptr<VKBuffer>& source)
424{
425 if (!source || !source->is_initialized()) {
426 return std::nullopt;
427 }
428
429 auto layout = source->get_vertex_layout();
430 if (!layout || layout->stride_bytes == 0 || layout->vertex_count == 0
431 || layout->vertex_count > source->get_size_bytes() / layout->stride_bytes) {
432 return std::nullopt;
433 }
434
435 return layout;
436}
437
438std::optional<Kakshya::MeshData> triangle_soup_mesh(std::span<const uint8_t> vertex_bytes)
439{
440 std::vector<uint32_t> indices(vertex_bytes.size() / sizeof(Kakshya::MeshVertex));
441 std::ranges::iota(indices, 0U);
442
443 auto mesh_data = Kakshya::MeshData::empty();
444 Kakshya::MeshInsertion ins(mesh_data.vertex_variant, mesh_data.index_variant);
445 ins.insert_flat(
446 vertex_bytes,
447 std::span<const uint32_t>(indices),
449 auto access = ins.build();
450 if (!access) {
452 "triangle_soup_mesh: MeshInsertion::build() failed");
453 return std::nullopt;
454 }
455 mesh_data.layout = access->layout;
456
457 return mesh_data;
458}
459
460std::optional<Kakshya::MeshData> download_compute_mesh(
461 const std::shared_ptr<ComputeMeshBuffer>& buffer)
462{
463 if (!buffer) {
465 "download_compute_mesh: null buffer");
466 return std::nullopt;
467 }
468
469 const auto vertex_count = buffer->get_live_vertex_count();
470 if (!vertex_count) {
472 "download_compute_mesh: no mesh processor (setup_processors() has not been called) "
473 "or the counter buffer is not host-visible or not allocated yet");
474 return std::nullopt;
475 }
476 if (*vertex_count == 0) {
478 "download_compute_mesh: live vertex count is zero");
479 return std::nullopt;
480 }
481
482 std::vector<uint8_t> bytes(static_cast<size_t>(*vertex_count) * sizeof(Kakshya::MeshVertex));
483 std::shared_ptr<VKBuffer> staging;
484 download_from_gpu_async(buffer, bytes.data(), bytes.size(), staging);
485
486 return triangle_soup_mesh(bytes);
487}
488
489std::shared_ptr<VKBuffer> create_staging_buffer(size_t size)
490{
491 auto buffer = std::make_shared<VKBuffer>(
492 size,
493 VKBuffer::Usage::STAGING,
495
496 auto buffer_service = Registry::BackendRegistry::instance()
498 if (!buffer_service) {
499 error<std::runtime_error>(
502 std::source_location::current(),
503 "create_staging_buffer requires a valid buffer service");
504 }
505
506 buffer_service->initialize_buffer(buffer);
507
508 return buffer;
509}
510
511std::shared_ptr<VKBuffer> create_image_staging_buffer(size_t size)
512{
513 auto buf = std::make_shared<VKBuffer>(
514 size,
515 VKBuffer::Usage::STAGING,
517
518 auto buffer_service = Registry::BackendRegistry::instance()
520
521 if (!buffer_service) {
522 error<std::runtime_error>(
525 std::source_location::current(),
526 "create_image_staging_buffer requires a valid buffer service");
527 }
528
529 buffer_service->initialize_buffer(buf);
530
532 "create_image_staging_buffer: allocated {} bytes", size);
533
534 return buf;
535}
536
538 const std::shared_ptr<VKBuffer>& target,
539 const std::shared_ptr<VKBuffer>& staging,
540 size_t required,
541 float growth_factor)
542{
543 if (required <= target->get_size_bytes()) {
544 return;
545 }
546
547 const auto new_size = static_cast<size_t>(static_cast<float>(required) * growth_factor);
548
549 target->resize(new_size, false);
550
551 if (staging) {
552 staging->resize(new_size, false);
553 }
554}
555
557 const void* base,
558 std::span<const std::pair<size_t, size_t>> intervals,
559 const std::shared_ptr<VKBuffer>& target,
560 std::shared_ptr<VKBuffer>& staging)
561{
562 if (!target) {
563 error<std::invalid_argument>(
566 std::source_location::current(),
567 "upload_intervals: target buffer is null");
568 }
569
570 std::vector<std::pair<size_t, size_t>> pending;
571 pending.reserve(intervals.size());
572 for (const auto& interval : intervals) {
573 if (interval.first < interval.second) {
574 pending.push_back(interval);
575 }
576 }
577
578 if (!base || pending.empty()) {
579 return;
580 }
581
582 const auto merged = Kinesis::Discrete::merge_intervals(pending);
583
584 if (merged.back().second > target->get_size_bytes()) {
585 error<std::runtime_error>(
588 std::source_location::current(),
589 "upload_intervals: range end {} exceeds buffer capacity {}",
590 merged.back().second, target->get_size_bytes());
591 }
592
593 auto buffer_service = Registry::BackendRegistry::instance()
595
596 if (!buffer_service || !buffer_service->flush_range) {
597 error<std::runtime_error>(
600 std::source_location::current(),
601 "upload_intervals requires a valid buffer service");
602 }
603
604 const auto* src = static_cast<const uint8_t*>(base);
605
606 if (target->is_host_visible()) {
607 auto* mapped = static_cast<uint8_t*>(target->get_mapped_ptr());
608 if (!mapped) {
609 error<std::runtime_error>(
612 std::source_location::current(),
613 "Host-visible buffer has no mapped pointer");
614 }
615
616 const auto memory = target->get_buffer_resources().memory;
617 for (const auto& [start, end] : merged) {
618 std::memcpy(mapped + start, src + start, end - start);
619 buffer_service->flush_range(memory, start, end - start);
620 }
621 return;
622 }
623
624 if (!buffer_service->execute_immediate) {
625 error<std::runtime_error>(
628 std::source_location::current(),
629 "upload_intervals requires execute_immediate for device-local targets");
630 }
631
632 size_t packed_bytes = 0;
633 for (const auto& [start, end] : merged) {
634 packed_bytes += end - start;
635 }
636
637 if (!staging || staging->get_size_bytes() < packed_bytes) {
638 staging = create_staging_buffer(packed_bytes);
639 }
640
641 auto* staged = static_cast<uint8_t*>(staging->get_mapped_ptr());
642 if (!staged) {
643 error<std::runtime_error>(
646 std::source_location::current(),
647 "Staging buffer has no mapped pointer");
648 }
649
650 std::vector<vk::BufferCopy> regions;
651 regions.reserve(merged.size());
652 size_t packed_offset = 0;
653 for (const auto& [start, end] : merged) {
654 const size_t length = end - start;
655 std::memcpy(staged + packed_offset, src + start, length);
656 regions.emplace_back(packed_offset, start, length);
657 packed_offset += length;
658 }
659
660 auto& staging_resources = staging->get_buffer_resources();
661 buffer_service->flush_range(staging_resources.memory, 0, packed_bytes);
662
663 const vk::Buffer source = staging_resources.buffer;
664 const vk::Buffer destination = target->get_buffer_resources().buffer;
665 buffer_service->execute_immediate([&](void* command_buffer) {
666 vk::CommandBuffer cmd(static_cast<VkCommandBuffer>(command_buffer));
667 cmd.copyBuffer(source, destination, regions);
668 });
669}
670
672 const void* data,
673 size_t size,
674 const std::shared_ptr<VKBuffer>& target,
675 const std::shared_ptr<VKBuffer>& staging,
676 size_t dst_offset)
677{
678 if (!target) {
679 error<std::invalid_argument>(
682 std::source_location::current(),
683 "upload_to_gpu: target buffer is null");
684 }
685
686 if (size == 0) {
687 return;
688 }
689
690 std::vector<uint8_t> raw_bytes(size);
691 std::memcpy(raw_bytes.data(), data, size);
692 Kakshya::DataVariant data_variant(raw_bytes);
693
694 if (target->is_host_visible()) {
695 upload_host_visible(target, data_variant, dst_offset);
696 } else {
697 std::shared_ptr<VKBuffer> staging_buf = staging;
698
699 if (!staging_buf) {
700 staging_buf = create_staging_buffer(size);
701 }
702
703 upload_device_local(target, staging_buf, data_variant, dst_offset);
704 }
705}
706
707bool clear_gpu_buffer(const std::shared_ptr<VKBuffer>& target)
708{
709 if (!target || !target->is_initialized()) {
711 "clear_gpu_buffer: buffer is null or not registered");
712 return false;
713 }
714
715 if (target->is_host_visible() && target->get_mapped_ptr()) {
716 std::memset(target->get_mapped_ptr(), 0, target->get_size_bytes());
717 return true;
718 }
719
720 if (!(target->get_usage_flags() & vk::BufferUsageFlagBits::eTransferDst)) {
722 "clear_gpu_buffer: unmapped buffer usage lacks transfer destination");
723 return false;
724 }
725
726 auto* buffer_service = Registry::BackendRegistry::instance()
728
729 if (!buffer_service || !buffer_service->execute_immediate) {
731 "clear_gpu_buffer: BufferService unavailable");
732 return false;
733 }
734
735 const vk::Buffer buffer = target->get_buffer_resources().buffer;
736 buffer_service->execute_immediate([buffer](void* command_buffer) {
737 vk::CommandBuffer cmd(static_cast<VkCommandBuffer>(command_buffer));
738 cmd.fillBuffer(buffer, 0, VK_WHOLE_SIZE, 0);
739 });
740
741 return true;
742}
743
745 const void* data,
746 size_t size,
747 const std::shared_ptr<VKBuffer>& target,
748 const std::shared_ptr<VKBuffer>& staging,
749 float growth_factor)
750{
751 ensure_gpu_capacity(target, staging, size, growth_factor);
752 upload_to_gpu(data, size, target, staging);
753}
754
756 const std::shared_ptr<VKBuffer>& source,
757 void* data,
758 size_t size,
759 const std::shared_ptr<VKBuffer>& staging)
760{
761 if (!source) {
762 error<std::invalid_argument>(
765 std::source_location::current(),
766 "download_from_gpu: source buffer is null");
767 }
768
769 if (size == 0) {
770 return;
771 }
772
773 auto temp_target = std::make_shared<VKBuffer>(
774 size, VKBuffer::Usage::STAGING, Kakshya::DataModality::UNKNOWN);
775
776 auto buffer_service = Registry::BackendRegistry::instance()
778
779 if (!buffer_service) {
780 error<std::runtime_error>(
783 std::source_location::current(),
784 "download_from_gpu requires a valid buffer service");
785 }
786
787 buffer_service->initialize_buffer(temp_target);
788
789 if (source->is_host_visible()) {
790 download_host_visible(source, temp_target);
791 } else {
792 std::shared_ptr<VKBuffer> staging_buf = staging;
793
794 if (!staging_buf) {
795 staging_buf = create_staging_buffer(size);
796 }
797
798 download_device_local(source, temp_target, staging_buf);
799 }
800
801 auto temp_data = temp_target->get_data();
802
803 if (temp_data.empty()) {
804 error<std::runtime_error>(
807 std::source_location::current(),
808 "download_from_gpu: failed to retrieve data from temporary buffer");
809 }
810
811 if (temp_data.size() > 1) {
813 "download_from_gpu: unexpected multiple data variants in temporary buffer. Only the first will be used.");
814 }
815
816 Kakshya::DataAccess accessor(
817 const_cast<Kakshya::DataVariant&>(temp_data[0]),
818 {},
819 source->get_modality());
820
821 auto [ptr, bytes, format_hint] = accessor.gpu_buffer();
822
823 std::memcpy(data, ptr, std::min(size, bytes));
824}
825
827 const std::shared_ptr<VKBuffer>& source,
828 void* data,
829 size_t size,
830 std::shared_ptr<VKBuffer>& staging)
831{
832 if (!source || !data || size == 0)
833 return;
834
835 auto buffer_service = Registry::BackendRegistry::instance()
837
838 if (!buffer_service
839 || !buffer_service->execute_fenced
840 || !buffer_service->wait_fenced
841 || !buffer_service->release_fenced
842 || !buffer_service->invalidate_range) {
843 error<std::runtime_error>(
846 std::source_location::current(),
847 "download_from_gpu_async: BufferService unavailable");
848 }
849
850 if (!staging || staging->get_size_bytes() < size)
851 staging = create_staging_buffer(size);
852
853 auto handle = buffer_service->copy_buffer_fenced(
854 static_cast<void*>(source->get_buffer()),
855 static_cast<void*>(staging->get_buffer()),
856 size, 0, 0);
857
858 if (!handle) {
859 error<std::runtime_error>(
862 std::source_location::current(),
863 "download_from_gpu_async: execute_fenced returned null");
864 }
865
866 buffer_service->wait_fenced(handle);
867
868 auto& resources = staging->get_buffer_resources();
869 buffer_service->invalidate_range(resources.memory, 0, size);
870
871 void* ptr = staging->get_mapped_ptr();
872 if (!ptr) {
873 buffer_service->release_fenced(handle);
874 error<std::runtime_error>(
877 std::source_location::current(),
878 "download_from_gpu_async: staging buffer has no mapped pointer");
879 }
880
881 std::memcpy(data, ptr, size);
882 buffer_service->release_fenced(handle);
883}
884
885std::span<const float> download_and_normalise(
886 const std::shared_ptr<Core::VKImage>& image,
887 std::vector<uint8_t>& raw_staging,
888 std::vector<float>& work,
889 const std::shared_ptr<VKBuffer>& gpu_staging)
890{
891 if (!image || !image->is_initialized())
892 return {};
893
895
896 const auto fmt_opt = loom.from_vulkan_format(image->get_format());
897 if (!fmt_opt)
898 return {};
899
900 const size_t byte_size = static_cast<size_t>(image->get_width())
901 * image->get_height()
902 * loom.get_bytes_per_pixel(*fmt_opt);
903
904 if (byte_size == 0)
905 return {};
906
907 raw_staging.resize(byte_size);
908 loom.download_data(image, raw_staging.data(), byte_size, gpu_staging);
909
910 Kakshya::DataVariant variant { raw_staging };
911 return Kakshya::as_normalised_float(variant, work);
912}
913
915 const std::shared_ptr<AudioBuffer>& audio_buffer,
916 const std::shared_ptr<VKBuffer>& gpu_buffer,
917 const std::shared_ptr<VKBuffer>& staging)
918{
919 const auto modality = gpu_buffer->get_modality();
920 if (modality != Kakshya::DataModality::AUDIO_1D
922 && modality != Kakshya::DataModality::UNKNOWN) {
923
924 error<std::runtime_error>(
927 std::source_location::current(),
928 "GPU buffer modality is {} but audio requires AUDIO_1D or AUDIO_MULTICHANNEL. "
929 "Create VKBuffer with DataModality::AUDIO_1D or AUDIO_MULTICHANNEL.",
931 }
932
933 auto& audio_data = audio_buffer->get_data();
934
935 if (audio_data.empty()) {
937 "AudioBuffer contains no data to upload");
938 return;
939 }
940
941 const void* data_ptr = audio_data.data();
942 size_t data_bytes = audio_data.size() * sizeof(double);
943
944 upload_to_gpu(data_ptr, data_bytes, gpu_buffer, staging);
945
947 "Uploaded {} bytes of double-precision audio to GPU", data_bytes);
948}
949
951 const std::shared_ptr<VKBuffer>& gpu_buffer,
952 const std::shared_ptr<AudioBuffer>& audio_buffer,
953 const std::shared_ptr<VKBuffer>& staging)
954{
955 Kakshya::DataVariant downloaded_data;
956
957 auto dimensions = std::vector<Kakshya::DataDimension> {
959 gpu_buffer->get_size_bytes() / sizeof(double),
960 "samples")
961 };
962 Kakshya::DataAccess accessor = download_to_view<double>(
963 gpu_buffer, downloaded_data, dimensions,
965
966 auto double_view = accessor.view<double>();
967
968 audio_buffer->get_data() = std::vector<double>(double_view.begin(), double_view.end());
969
971 "Downloaded {} samples of double-precision audio from GPU", double_view.size());
972}
973
974} // namespace MayaFlux::Buffers
#define MF_ERROR(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
vk::CommandBuffer cmd
uint64_t length
Definition Composite.cpp:11
uint64_t offset
Definition Composite.cpp:10
IO::ImageData image
Definition Decoder.cpp:47
Discrete sequence extraction primitives for MayaFlux::Kinesis.
bool size
std::vector< uint32_t > indices
std::shared_ptr< NetworkGeometryBuffer > buffer
const uint32_t * src
const uint8_t * ptr
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.
std::optional< MeshAccess > build() const
Produce a MeshAccess over the current variant contents.
void insert_flat(std::span< const uint8_t > vertex_bytes, std::span< const uint32_t > index_data, const VertexLayout &layout)
Insert a single flat mesh (no submesh tracking).
Write counterpart to MeshAccess.
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_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.
Portal::Graphics::GpuBufferHandle gpu_buffer_handle(const std::shared_ptr< VKBuffer > &buffer)
View a buffer's primary GPU storage without reading it back.
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.
void upload_intervals(const void *base, std::span< const std::pair< size_t, size_t > > intervals, const std::shared_ptr< VKBuffer > &target, std::shared_ptr< VKBuffer > &staging)
Upload several byte ranges of one source to the same offsets in target.
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)
std::optional< Kakshya::MeshData > triangle_soup_mesh(std::span< const uint8_t > vertex_bytes)
Wrap non-indexed triangle vertices as MeshData.
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.
VKBufferResources::GenerationSlot generation_slot(const Portal::Graphics::GpuBufferHandle &handle)
Express a buffer handle as the slot the back_buffers transfer helpers take.
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)
std::optional< Kakshya::MeshData > download_compute_mesh(const std::shared_ptr< ComputeMeshBuffer > &buffer)
Download a ComputeMeshBuffer's current live geometry to CPU.
bool clear_gpu_buffer(const std::shared_ptr< VKBuffer > &target)
Zero a registered GPU buffer in place (auto-detects host-visible vs device-local)
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)
std::optional< Kakshya::VertexLayout > resolve_vertex_layout(const std::shared_ptr< VKBuffer > &source)
Resolve the vertex extent of a buffer's primary storage for reading.
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 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 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 resolve_back_buffer_read(TransferHandle &handle, const VKBufferResources::GenerationSlot &slot, void *data, size_t size, std::shared_ptr< VKBuffer > &staging, size_t staging_offset)
Wait for a recorded download, then copy it out of staging.
bool is_transfer_source(const std::shared_ptr< VKBuffer > &buffer)
Whether a buffer's memory can be the source of a transfer.
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.
TransferHandle download_back_buffer_async(const VKBufferResources::GenerationSlot &slot, size_t size, std::shared_ptr< VKBuffer > &staging, size_t staging_offset)
Record a slot-to-staging copy without waiting for completion.
@ 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:70
@ 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:102
std::vector< std::pair< size_t, size_t > > merge_intervals(const std::vector< std::pair< size_t, size_t > > &intervals)
Merge overlapping or adjacent half-open intervals.
Definition Extract.cpp:14
static DataDimension time(uint64_t samples, std::string name="time")
Convenience constructor for a temporal (time) dimension.
Definition NDData.cpp:17
static MeshData empty()
Construct an empty MeshData with the canonical 60-byte mesh layout.
Definition MeshData.hpp:49
Vertex type for indexed triangle mesh primitives (TRIANGLE_LIST topology)
static VertexLayout for_meshes(uint32_t stride=60)
Factory: layout for MeshVertex (position, color, weight, uv, normal, tangent)
Non-owning view of a GPU storage buffer produced by a compute path.
std::function< void(const std::shared_ptr< void > &)> wait_fenced
Wait for a fenced submission to complete.
Backend buffer management service interface.