561{
562 if (!target) {
563 error<std::invalid_argument>(
564 Journal::Component::Buffers,
565 Journal::Context::BufferProcessing,
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>(
586 Journal::Component::Buffers,
587 Journal::Context::BufferProcessing,
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()
594 .get_service<Registry::Service::BufferService>();
595
596 if (!buffer_service || !buffer_service->flush_range) {
597 error<std::runtime_error>(
598 Journal::Component::Buffers,
599 Journal::Context::BufferProcessing,
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>(
610 Journal::Component::Buffers,
611 Journal::Context::BufferProcessing,
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>(
626 Journal::Component::Buffers,
627 Journal::Context::BufferProcessing,
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) {
639 }
640
641 auto* staged = static_cast<uint8_t*>(staging->get_mapped_ptr());
642 if (!staged) {
643 error<std::runtime_error>(
644 Journal::Component::Buffers,
645 Journal::Context::BufferProcessing,
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);
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}
std::shared_ptr< VKBuffer > create_staging_buffer(size_t size)
Create staging buffer for transfers.