14 struct FencedSubmission {
15 vk::CommandBuffer
cmd;
22 , m_command_manager(command_manager)
28 buffer_service->initialize_buffer = [
this](
const std::shared_ptr<void>& vk_buf) ->
void {
29 auto buffer = std::static_pointer_cast<Buffers::VKBuffer>(vk_buf);
33 buffer_service->allocate_raw_buffer = [
this](
36 uint32_t memory_property_flags,
40 void*& out_mapped_ptr) ->
void {
43 void* mapped =
nullptr;
46 vk::BufferUsageFlags(usage_flags),
47 vk::MemoryPropertyFlags(memory_property_flags),
50 out_buffer =
static_cast<VkBuffer
>(buf);
51 out_memory =
static_cast<VkDeviceMemory
>(mem);
52 out_mapped_ptr = mapped;
55 buffer_service->destroy_buffer = [
this](
const std::shared_ptr<void>& vk_buf) {
56 auto buffer = std::static_pointer_cast<Buffers::VKBuffer>(vk_buf);
60 buffer_service->get_buffer_device_address = [
this](
const std::shared_ptr<void>& vk_buf) -> uint64_t {
61 auto buffer = std::static_pointer_cast<Buffers::VKBuffer>(vk_buf);
63 return static_cast<uint64_t
>(address);
66 buffer_service->execute_immediate = [
this](
const std::function<void(
void*)>& recorder) {
68 recorder(
static_cast<void*
>(
cmd));
72 buffer_service->record_deferred = [
this](
const std::function<void(
void*)>& recorder) {
74 recorder(
static_cast<void*
>(
cmd));
78 buffer_service->flush_range = [
this](
void* memory,
size_t offset,
size_t size) {
79 vk::DeviceMemory mem(
reinterpret_cast<VkDeviceMemory
>(memory));
80 vk::MappedMemoryRange range { mem,
offset,
size == 0 ? VK_WHOLE_SIZE :
size };
81 if (
auto result =
m_context.
get_device().flushMappedMemoryRanges(1, &range); result != vk::Result::eSuccess) {
83 "Failed to flush mapped memory range: {}", vk::to_string(result));
87 buffer_service->invalidate_range = [
this](
void* memory,
size_t offset,
size_t size) {
88 vk::DeviceMemory mem(
reinterpret_cast<VkDeviceMemory
>(memory));
89 vk::MappedMemoryRange range { mem,
offset,
size == 0 ? VK_WHOLE_SIZE :
size };
90 if (
auto result =
m_context.
get_device().invalidateMappedMemoryRanges(1, &range); result != vk::Result::eSuccess) {
92 "Failed to invalidate mapped memory range: {}", vk::to_string(result));
96 buffer_service->map_buffer = [
this](
void* memory,
size_t offset,
size_t size) ->
void* {
97 vk::DeviceMemory mem(
reinterpret_cast<VkDeviceMemory
>(memory));
101 buffer_service->unmap_buffer = [
this](
void* memory) {
102 vk::DeviceMemory mem(
reinterpret_cast<VkDeviceMemory
>(memory));
106 buffer_service->copy_buffer = [
this](
void*
src,
void*
dst,
size_t size,
size_t src_off,
size_t dst_off) {
107 vk::Buffer s(
static_cast<VkBuffer
>(
src));
108 vk::Buffer d(
static_cast<VkBuffer
>(
dst));
110 vk::BufferCopy region { src_off, dst_off,
size };
111 cmd.copyBuffer(s, d, 1, ®ion);
115 buffer_service->execute_fenced = [
this](
const std::function<void(
void*)>& recorder)
116 -> std::shared_ptr<void> {
119 recorder(
static_cast<void*
>(
cmd));
124 vk::FenceCreateInfo fence_info {};
127 vk::SubmitInfo submit_info {};
128 submit_info.commandBufferCount = 1;
129 submit_info.pCommandBuffers = &
cmd;
132 result != vk::Result::eSuccess) {
134 "execute_fenced: queue submit failed: {}", vk::to_string(result));
140 auto handle = std::make_shared<FencedSubmission>();
142 handle->fence =
fence;
146 buffer_service->wait_fenced = [
this](
const std::shared_ptr<void>& handle) {
149 auto sub = std::static_pointer_cast<FencedSubmission>(handle);
154 if (
auto result =
device.waitForFences(1, &sub->fence, VK_TRUE, UINT64_MAX);
155 result != vk::Result::eSuccess) {
157 "wait_fenced: waitForFences failed: {}", vk::to_string(result));
161 buffer_service->release_fenced = [
this](
const std::shared_ptr<void>& handle) {
164 auto sub = std::static_pointer_cast<FencedSubmission>(handle);
168 device.destroyFence(sub->fence);
169 sub->fence =
nullptr;
177 buffer_service->copy_buffer_fenced = [
this, buffer_service](
void*
src,
void*
dst,
size_t size,
size_t src_off,
size_t dst_off)
178 -> std::shared_ptr<void> {
179 vk::Buffer s(
static_cast<VkBuffer
>(
src));
180 vk::Buffer d(
static_cast<VkBuffer
>(
dst));
181 return buffer_service->execute_fenced([&](
void* cmd_ptr) {
182 vk::CommandBuffer
cmd(
static_cast<VkCommandBuffer
>(cmd_ptr));
183 vk::BufferCopy region { src_off, dst_off,
static_cast<vk::DeviceSize
>(
size) };
184 cmd.copyBuffer(s, d, 1, ®ion);
191 vk::BufferUsageFlags
usage,
192 vk::MemoryPropertyFlags memory_properties,
194 vk::Buffer& out_buffer,
195 vk::DeviceMemory& out_memory,
196 void*& out_mapped_ptr)
198 vk::BufferCreateInfo buffer_info {};
199 buffer_info.size = size_bytes;
200 buffer_info.usage =
usage;
201 buffer_info.sharingMode = vk::SharingMode::eExclusive;
205 }
catch (
const vk::SystemError& e) {
209 std::source_location::current(),
210 "Failed to create raw VkBuffer: " + std::string(e.what()));
213 vk::MemoryRequirements mem_requirements =
m_context.
get_device().getBufferMemoryRequirements(out_buffer);
215 vk::MemoryAllocateInfo alloc_info;
216 alloc_info.allocationSize = mem_requirements.size;
217 alloc_info.memoryTypeIndex =
find_memory_type(mem_requirements.memoryTypeBits, memory_properties);
221 }
catch (
const vk::SystemError& e) {
226 std::source_location::current(),
227 "Failed to allocate raw VkDeviceMemory: " + std::string(e.what()));
232 }
catch (
const vk::SystemError& e) {
238 std::source_location::current(),
239 "Failed to bind raw buffer memory: " + std::string(e.what()));
242 out_mapped_ptr =
nullptr;
246 }
catch (
const vk::SystemError& e) {
252 std::source_location::current(),
253 "Failed to map raw buffer memory: " + std::string(e.what()));
262 "Attempted to initialize null VKBuffer");
265 if (
buffer->is_initialized()) {
267 "VKBuffer already initialized, skipping");
271 vk::Buffer vk_buffer;
272 vk::DeviceMemory memory;
273 void* mapped_ptr =
nullptr;
277 buffer->get_usage_flags(),
278 vk::MemoryPropertyFlags(
buffer->get_memory_properties()),
279 buffer->is_host_visible(),
280 vk_buffer, memory, mapped_ptr);
282 auto& resources =
buffer->get_buffer_resources();
283 resources.buffer = vk_buffer;
284 resources.memory = memory;
285 resources.mapped_ptr = mapped_ptr;
290 "VKBuffer initialized: {} bytes, modality: {}, VkBuffer: {:p}",
293 (
void*)
buffer->get_buffer());
300 "Attempted to cleanup null VKBuffer");
309 auto& res = it->get()->get_buffer_resources();
311 if (res.mapped_ptr) {
324 "VKBuffer cleaned up: {:p}",
static_cast<void*
>(res.buffer));
326 res.mapped_ptr =
nullptr;
327 res.buffer = vk::Buffer {};
328 res.memory = vk::DeviceMemory {};
336 auto& resources = buffer_wrapper->get_buffer_resources();
337 auto dirty_ranges = buffer_wrapper->get_and_clear_dirty_ranges();
338 if (!dirty_ranges.empty()) {
340 vk::MappedMemoryRange range;
341 range.memory = resources.memory;
344 if (
auto result =
m_context.
get_device().flushMappedMemoryRanges(1, &range); result != vk::Result::eSuccess) {
346 "Failed to flush mapped memory range: {}", vk::to_string(result));
350 "Flushed {} dirty ranges for buffer {:p}", dirty_ranges.size(),
351 (
void*)buffer_wrapper->get_buffer());
354 auto invalid_ranges = buffer_wrapper->get_and_clear_invalid_ranges();
355 if (!invalid_ranges.empty()) {
357 vk::MappedMemoryRange range;
358 range.memory = buffer_wrapper->get_buffer_resources().memory;
361 if (
auto result =
m_context.
get_device().invalidateMappedMemoryRanges(1, &range); result != vk::Result::eSuccess) {
363 "Failed to invalidate mapped memory range: {}", vk::to_string(result));
367 "Invalidated {} ranges for buffer {:p}", invalid_ranges.size(),
368 (
void*)buffer_wrapper->get_buffer());
374 const std::shared_ptr<Buffers::VKBuffer>&
buffer)
const
378 "get_buffer_device_address: buffer not initialized");
382 vk::BufferDeviceAddressInfo info {};
383 info.buffer =
buffer->get_buffer();
392 "Attempted to initialize null VKImage");
396 if (
image->is_initialized()) {
398 "VKImage already initialized, skipping");
406 vk::ImageCreateInfo image_info {};
408 switch (
image->get_type()) {
410 image_info.imageType = vk::ImageType::e1D;
414 image_info.imageType = vk::ImageType::e2D;
417 image_info.imageType = vk::ImageType::e3D;
421 image_info.extent.width =
image->get_width();
422 image_info.extent.height =
image->get_height();
423 image_info.extent.depth =
image->get_depth();
424 image_info.mipLevels =
image->get_mip_levels();
425 image_info.arrayLayers =
image->get_array_layers();
426 image_info.format =
image->get_format();
427 image_info.tiling = vk::ImageTiling::eOptimal;
428 image_info.initialLayout = vk::ImageLayout::eUndefined;
429 image_info.usage =
image->get_usage_flags();
430 image_info.sharingMode = vk::SharingMode::eExclusive;
431 image_info.samples = vk::SampleCountFlagBits::e1;
433 ? vk::ImageCreateFlagBits::eCubeCompatible
434 : vk::ImageCreateFlags {};
439 }
catch (
const vk::SystemError& e) {
443 std::source_location::current(),
444 "Failed to create VkImage: " + std::string(e.what()));
451 vk::MemoryRequirements mem_requirements;
454 vk::MemoryAllocateInfo alloc_info {};
455 alloc_info.allocationSize = mem_requirements.size;
457 mem_requirements.memoryTypeBits,
458 image->get_memory_properties());
460 vk::DeviceMemory memory;
463 }
catch (
const vk::SystemError& e) {
468 std::source_location::current(),
469 "Failed to allocate VkDeviceMemory for image: " + std::string(e.what()));
478 }
catch (
const vk::SystemError& e) {
484 std::source_location::current(),
485 "Failed to bind memory to VkImage: " + std::string(e.what()));
492 vk::ImageViewCreateInfo view_info {};
494 switch (
image->get_type()) {
496 view_info.viewType = (
image->get_array_layers() > 1)
497 ? vk::ImageViewType::e1DArray
498 : vk::ImageViewType::e1D;
501 view_info.viewType = (
image->get_array_layers() > 1)
502 ? vk::ImageViewType::e2DArray
503 : vk::ImageViewType::e2D;
506 view_info.viewType = vk::ImageViewType::e3D;
509 view_info.viewType = vk::ImageViewType::eCube;
513 view_info.image = vk_image;
514 view_info.format =
image->get_format();
515 view_info.subresourceRange.aspectMask =
image->get_aspect_flags();
516 view_info.subresourceRange.baseMipLevel = 0;
517 view_info.subresourceRange.levelCount =
image->get_mip_levels();
518 view_info.subresourceRange.baseArrayLayer = 0;
519 view_info.subresourceRange.layerCount =
image->get_array_layers();
521 view_info.components.r = vk::ComponentSwizzle::eIdentity;
522 view_info.components.g = vk::ComponentSwizzle::eIdentity;
523 view_info.components.b = vk::ComponentSwizzle::eIdentity;
524 view_info.components.a = vk::ComponentSwizzle::eIdentity;
526 vk::ImageView image_view;
529 }
catch (
const vk::SystemError& e) {
535 std::source_location::current(),
536 "Failed to create VkImageView: " + std::string(e.what()));
544 resources.
image = vk_image;
545 resources.image_view = image_view;
546 resources.memory = memory;
547 resources.sampler =
nullptr;
549 image->set_image_resources(resources);
550 image->set_current_layout(vk::ImageLayout::eUndefined);
553 "VKImage initialized: {}x{}x{}, format: {}, {} mips, {} layers",
555 vk::to_string(
image->get_format()),
556 image->get_mip_levels(),
image->get_array_layers());
565 const auto& resources =
image->get_image_resources();
567 if (resources.image_view) {
571 if (resources.image) {
575 if (resources.memory) {
582 "VKImage cleaned up");
587 vk::ImageLayout old_layout,
588 vk::ImageLayout new_layout,
590 uint32_t array_layers,
591 vk::ImageAspectFlags aspect_flags)
594 vk::ImageMemoryBarrier barrier {};
595 barrier.oldLayout = old_layout;
596 barrier.newLayout = new_layout;
597 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
598 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
599 barrier.image =
image;
600 barrier.subresourceRange.aspectMask = aspect_flags;
601 barrier.subresourceRange.baseMipLevel = 0;
602 barrier.subresourceRange.levelCount = mip_levels;
603 barrier.subresourceRange.baseArrayLayer = 0;
604 barrier.subresourceRange.layerCount = array_layers;
606 vk::PipelineStageFlags src_stage;
607 vk::PipelineStageFlags dst_stage;
609 if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eTransferDstOptimal) {
610 barrier.srcAccessMask = vk::AccessFlags {};
611 barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
612 src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
613 dst_stage = vk::PipelineStageFlagBits::eTransfer;
614 }
else if (old_layout == vk::ImageLayout::eTransferDstOptimal && new_layout == vk::ImageLayout::eShaderReadOnlyOptimal) {
615 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
616 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
617 src_stage = vk::PipelineStageFlagBits::eTransfer;
618 dst_stage = vk::PipelineStageFlagBits::eFragmentShader;
619 }
else if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eShaderReadOnlyOptimal) {
620 barrier.srcAccessMask = vk::AccessFlags {};
621 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
622 src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
623 dst_stage = vk::PipelineStageFlagBits::eFragmentShader;
624 }
else if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eColorAttachmentOptimal) {
625 barrier.srcAccessMask = vk::AccessFlags {};
626 barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
627 src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
628 dst_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
629 }
else if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eDepthStencilAttachmentOptimal) {
630 barrier.srcAccessMask = vk::AccessFlags {};
631 barrier.dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite;
632 src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
633 dst_stage = vk::PipelineStageFlagBits::eEarlyFragmentTests;
634 }
else if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eGeneral) {
635 barrier.srcAccessMask = vk::AccessFlags {};
636 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
637 src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
638 dst_stage = vk::PipelineStageFlagBits::eComputeShader;
639 }
else if (old_layout == vk::ImageLayout::eGeneral && new_layout == vk::ImageLayout::eShaderReadOnlyOptimal) {
640 barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite;
641 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
642 src_stage = vk::PipelineStageFlagBits::eComputeShader;
643 dst_stage = vk::PipelineStageFlagBits::eComputeShader;
644 }
else if (old_layout == vk::ImageLayout::eShaderReadOnlyOptimal && new_layout == vk::ImageLayout::eGeneral) {
645 barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
646 barrier.dstAccessMask = vk::AccessFlagBits::eShaderWrite;
647 src_stage = vk::PipelineStageFlagBits::eComputeShader;
648 dst_stage = vk::PipelineStageFlagBits::eComputeShader;
650 barrier.srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
651 barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
652 src_stage = vk::PipelineStageFlagBits::eAllCommands;
653 dst_stage = vk::PipelineStageFlagBits::eAllCommands;
656 "Using generic image layout transition");
660 src_stage, dst_stage,
661 vk::DependencyFlags {},
669 "Image layout transitioned: {} -> {}",
670 vk::to_string(old_layout), vk::to_string(new_layout));
674 std::shared_ptr<VKImage>
image,
678 if (!
image || !data) {
680 "Invalid parameters for upload_image_data");
684 auto staging = std::make_shared<Buffers::VKBuffer>(
686 Buffers::VKBuffer::Usage::STAGING,
691 void* mapped = staging->get_mapped_ptr();
694 "Failed to map staging buffer for image upload");
699 std::memcpy(mapped, data,
size);
700 staging->mark_dirty_range(0,
size);
702 auto& resources = staging->get_buffer_resources();
703 vk::MappedMemoryRange range { resources.memory, 0, VK_WHOLE_SIZE };
705 if (
auto result =
m_context.
get_device().flushMappedMemoryRanges(1, &range); result != vk::Result::eSuccess) {
707 "Failed to flush mapped memory range: {}", vk::to_string(result));
711 vk::ImageMemoryBarrier barrier {};
712 barrier.oldLayout =
image->get_current_layout();
713 barrier.newLayout = vk::ImageLayout::eTransferDstOptimal;
714 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
715 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
716 barrier.image =
image->get_image();
717 barrier.subresourceRange.aspectMask =
image->get_aspect_flags();
718 barrier.subresourceRange.baseMipLevel = 0;
719 barrier.subresourceRange.levelCount =
image->get_mip_levels();
720 barrier.subresourceRange.baseArrayLayer = 0;
721 barrier.subresourceRange.layerCount =
image->get_array_layers();
722 barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
723 barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
726 vk::PipelineStageFlagBits::eFragmentShader,
727 vk::PipelineStageFlagBits::eTransfer,
728 vk::DependencyFlags {},
729 0,
nullptr, 0,
nullptr, 1, &barrier);
731 vk::BufferImageCopy region {};
732 region.bufferOffset = 0;
733 region.bufferRowLength = 0;
734 region.bufferImageHeight = 0;
735 region.imageSubresource.aspectMask =
image->get_aspect_flags();
736 region.imageSubresource.mipLevel = 0;
737 region.imageSubresource.baseArrayLayer = 0;
738 region.imageSubresource.layerCount =
image->get_array_layers();
739 region.imageOffset = vk::Offset3D { 0, 0, 0 };
740 region.imageExtent = vk::Extent3D {
746 cmd.copyBufferToImage(
747 staging->get_buffer(),
749 vk::ImageLayout::eTransferDstOptimal,
752 barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
753 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
754 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
755 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
758 vk::PipelineStageFlagBits::eTransfer,
759 vk::PipelineStageFlagBits::eFragmentShader,
760 vk::DependencyFlags {},
761 0,
nullptr, 0,
nullptr, 1, &barrier);
764 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
767 "Uploaded {} bytes to image {}x{}",
772 std::shared_ptr<VKImage>
image,
775 const std::shared_ptr<Buffers::VKBuffer>& staging,
bool deferred)
777 if (!
image || !data || !staging) {
779 "Invalid parameters for upload_image_data_with_staging");
783 void* mapped = staging->get_mapped_ptr();
786 "upload_image_data_with_staging: staging buffer has no mapped pointer");
790 std::memcpy(mapped, data,
size);
791 staging->mark_dirty_range(0,
size);
793 auto& resources = staging->get_buffer_resources();
794 vk::MappedMemoryRange range { resources.memory, 0, VK_WHOLE_SIZE };
797 result != vk::Result::eSuccess) {
799 "upload_image_data_with_staging: flush failed: {}", vk::to_string(result));
802 auto record_command = [&](vk::CommandBuffer
cmd) {
803 vk::ImageMemoryBarrier barrier {};
804 barrier.oldLayout =
image->get_current_layout();
805 barrier.newLayout = vk::ImageLayout::eTransferDstOptimal;
806 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
807 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
808 barrier.image =
image->get_image();
809 barrier.subresourceRange = {
810 image->get_aspect_flags(), 0,
811 image->get_mip_levels(), 0,
812 image->get_array_layers()
814 barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
815 barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
818 vk::PipelineStageFlagBits::eFragmentShader,
819 vk::PipelineStageFlagBits::eTransfer,
820 {}, 0,
nullptr, 0,
nullptr, 1, &barrier);
822 vk::BufferImageCopy region {};
823 region.imageSubresource.aspectMask =
image->get_aspect_flags();
824 region.imageSubresource.layerCount =
image->get_array_layers();
825 region.imageOffset = vk::Offset3D { 0, 0, 0 };
826 region.imageExtent = vk::Extent3D {
832 cmd.copyBufferToImage(
833 staging->get_buffer(),
835 vk::ImageLayout::eTransferDstOptimal,
838 barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
839 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
840 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
841 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
844 vk::PipelineStageFlagBits::eTransfer,
845 vk::PipelineStageFlagBits::eFragmentShader,
846 {}, 0,
nullptr, 0,
nullptr, 1, &barrier);
855 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
858 "upload_image_data_with_staging: {} bytes to image {}x{}",
863 std::shared_ptr<VKImage>
image,
866 const std::shared_ptr<Buffers::VKBuffer>& staging,
868 vk::ImageLayout restore_layout,
869 vk::PipelineStageFlags restore_stage)
871 if (!
image || !data) {
873 "download_image_data: invalid parameters");
877 std::shared_ptr<Buffers::VKBuffer> local_staging;
880 local_staging = std::make_shared<Buffers::VKBuffer>(
882 Buffers::VKBuffer::Usage::STAGING,
885 active_staging = local_staging.get();
887 active_staging = staging.get();
890 auto record_command = [&](vk::CommandBuffer
cmd) {
891 vk::ImageMemoryBarrier barrier {};
892 barrier.oldLayout =
image->get_current_layout();
893 barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal;
894 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
895 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
896 barrier.image =
image->get_image();
897 barrier.subresourceRange.aspectMask =
image->get_aspect_flags();
898 barrier.subresourceRange.baseMipLevel = 0;
899 barrier.subresourceRange.levelCount =
image->get_mip_levels();
900 barrier.subresourceRange.baseArrayLayer = 0;
901 barrier.subresourceRange.layerCount =
image->get_array_layers();
902 barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eShaderRead;
903 barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead;
906 vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eComputeShader,
907 vk::PipelineStageFlagBits::eTransfer,
908 vk::DependencyFlags {}, {}, {}, barrier);
910 vk::BufferImageCopy region {};
911 region.bufferOffset = 0;
912 region.bufferRowLength = 0;
913 region.bufferImageHeight = 0;
914 region.imageSubresource.aspectMask =
image->get_aspect_flags();
915 region.imageSubresource.mipLevel = 0;
916 region.imageSubresource.baseArrayLayer = 0;
917 region.imageSubresource.layerCount =
image->get_array_layers();
918 region.imageOffset = vk::Offset3D { 0, 0, 0 };
919 region.imageExtent = vk::Extent3D {
925 cmd.copyImageToBuffer(
927 vk::ImageLayout::eTransferSrcOptimal,
928 active_staging->get_buffer(),
931 barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal;
932 barrier.newLayout = restore_layout;
933 barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead;
934 barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
937 vk::PipelineStageFlagBits::eTransfer,
939 vk::DependencyFlags {}, {}, {}, barrier);
942 if (deferred && staging) {
944 image->set_current_layout(restore_layout);
954 vk::FenceCreateInfo fence_info {};
957 vk::SubmitInfo submit_info {};
958 submit_info.commandBufferCount = 1;
959 submit_info.pCommandBuffers = &
cmd;
962 result != vk::Result::eSuccess) {
964 "download_image_data: queue submit failed: {}", vk::to_string(result));
972 image->set_current_layout(restore_layout);
974 if (
auto result =
device.waitForFences(1, &
fence, VK_TRUE, UINT64_MAX);
975 result != vk::Result::eSuccess) {
977 "download_image_data: waitForFences failed: {}", vk::to_string(result));
980 vk::MappedMemoryRange range { active_staging->get_buffer_resources().memory, 0, VK_WHOLE_SIZE };
981 if (
auto result =
device.invalidateMappedMemoryRanges(1, &range);
982 result != vk::Result::eSuccess) {
984 "download_image_data: invalidate failed: {}", vk::to_string(result));
987 void* mapped = active_staging->get_mapped_ptr();
989 std::memcpy(data, mapped,
size);
992 "download_image_data: staging buffer has no mapped pointer");
1002 "download_image_data: {} bytes from image {}x{}",
1007 const std::shared_ptr<VKImage>&
image,
1011 const std::shared_ptr<Buffers::VKBuffer>& staging,
1014 if (!
image || !data || layer >=
image->get_array_layers()) {
1016 "upload_image_layer: invalid image, data or layer");
1020 const size_t layer_bytes =
image->get_size_bytes() /
image->get_array_layers();
1021 if (
size != layer_bytes) {
1023 "upload_image_layer: {} bytes given, layer needs {}",
size, layer_bytes);
1027 if (deferred && !staging) {
1029 "upload_image_layer: a deferred upload needs a caller staging buffer");
1033 std::shared_ptr<Buffers::VKBuffer> local_staging;
1035 local_staging = std::make_shared<Buffers::VKBuffer>(
1037 Buffers::VKBuffer::Usage::STAGING,
1041 const auto& active_staging = staging ? staging : local_staging;
1043 void* mapped = active_staging->get_mapped_ptr();
1046 "upload_image_layer: staging buffer has no mapped pointer");
1047 if (local_staging) {
1053 std::memcpy(mapped, data,
size);
1054 active_staging->mark_dirty_range(0,
size);
1056 vk::MappedMemoryRange range { active_staging->get_buffer_resources().memory, 0, VK_WHOLE_SIZE };
1058 result != vk::Result::eSuccess) {
1060 "upload_image_layer: flush failed: {}", vk::to_string(result));
1063 auto record_command = [&](vk::CommandBuffer
cmd) {
1064 vk::ImageMemoryBarrier barrier {};
1065 barrier.oldLayout =
image->get_current_layout();
1066 barrier.newLayout = vk::ImageLayout::eTransferDstOptimal;
1067 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1068 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1069 barrier.image =
image->get_image();
1070 barrier.subresourceRange = {
1071 image->get_aspect_flags(), 0,
1072 image->get_mip_levels(), 0,
1073 image->get_array_layers()
1075 barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
1076 barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
1078 cmd.pipelineBarrier(
1079 vk::PipelineStageFlagBits::eFragmentShader,
1080 vk::PipelineStageFlagBits::eTransfer,
1081 vk::DependencyFlags {}, {}, {}, barrier);
1083 vk::BufferImageCopy region {};
1084 region.imageSubresource = {
image->get_aspect_flags(), 0, layer, 1 };
1085 region.imageOffset = vk::Offset3D { 0, 0, 0 };
1086 region.imageExtent = vk::Extent3D {
1088 image->get_height(),
1092 cmd.copyBufferToImage(
1093 active_staging->get_buffer(),
1095 vk::ImageLayout::eTransferDstOptimal,
1098 barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
1099 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
1100 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
1101 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
1103 cmd.pipelineBarrier(
1104 vk::PipelineStageFlagBits::eTransfer,
1105 vk::PipelineStageFlagBits::eFragmentShader,
1106 vk::DependencyFlags {}, {}, {}, barrier);
1115 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
1117 if (local_staging) {
1125 vk::Format src_format, vk::Format dst_format, vk::Filter filter)
const
1128 const auto src_features = physical.getFormatProperties(src_format).optimalTilingFeatures;
1129 const auto dst_features = physical.getFormatProperties(dst_format).optimalTilingFeatures;
1131 if (!(src_features & vk::FormatFeatureFlagBits::eBlitSrc)
1132 || !(dst_features & vk::FormatFeatureFlagBits::eBlitDst)) {
1136 return filter != vk::Filter::eLinear
1137 ||
static_cast<bool>(src_features & vk::FormatFeatureFlagBits::eSampledImageFilterLinear);
1141 const std::shared_ptr<VKImage>&
src,
1142 const std::shared_ptr<VKImage>&
dst,
1143 const vk::ImageBlit& region,
1149 "blit_image: source and destination must be two distinct initialised images");
1155 "blit_image: formats {} to {} do not support a {} blit",
1156 vk::to_string(
src->get_format()), vk::to_string(
dst->get_format()), vk::to_string(filter));
1160 const auto settled_layout = [](
const std::shared_ptr<VKImage>&
image) {
1161 const auto layout =
image->get_current_layout();
1162 const bool transient = layout == vk::ImageLayout::eUndefined
1163 || layout == vk::ImageLayout::eTransferSrcOptimal
1164 || layout == vk::ImageLayout::eTransferDstOptimal;
1165 return transient ? vk::ImageLayout::eShaderReadOnlyOptimal : layout;
1167 const auto src_restore = settled_layout(
src);
1168 const auto dst_restore = settled_layout(
dst);
1170 auto record_command = [&](vk::CommandBuffer
cmd) {
1171 const auto transition = [&
cmd](
1172 const std::shared_ptr<VKImage>&
image,
1173 vk::ImageLayout from,
1175 vk::AccessFlags src_access,
1176 vk::AccessFlags dst_access,
1177 vk::PipelineStageFlags src_stage,
1178 vk::PipelineStageFlags dst_stage) {
1179 vk::ImageMemoryBarrier barrier {};
1180 barrier.oldLayout = from;
1181 barrier.newLayout = to;
1182 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1183 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1184 barrier.image =
image->get_image();
1185 barrier.subresourceRange = {
1186 image->get_aspect_flags(), 0,
1187 image->get_mip_levels(), 0,
1188 image->get_array_layers()
1190 barrier.srcAccessMask = src_access;
1191 barrier.dstAccessMask = dst_access;
1192 cmd.pipelineBarrier(src_stage, dst_stage, vk::DependencyFlags {}, {}, {}, barrier);
1195 constexpr auto any_access = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
1197 transition(
src,
src->get_current_layout(), vk::ImageLayout::eTransferSrcOptimal,
1198 any_access, vk::AccessFlagBits::eTransferRead,
1199 vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer);
1200 transition(
dst,
dst->get_current_layout(), vk::ImageLayout::eTransferDstOptimal,
1201 any_access, vk::AccessFlagBits::eTransferWrite,
1202 vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer);
1205 src->get_image(), vk::ImageLayout::eTransferSrcOptimal,
1206 dst->get_image(), vk::ImageLayout::eTransferDstOptimal,
1207 1, ®ion, filter);
1209 transition(
src, vk::ImageLayout::eTransferSrcOptimal, src_restore,
1210 vk::AccessFlagBits::eTransferRead, any_access,
1211 vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllCommands);
1212 transition(
dst, vk::ImageLayout::eTransferDstOptimal, dst_restore,
1213 vk::AccessFlagBits::eTransferWrite, any_access,
1214 vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllCommands);
1223 src->set_current_layout(src_restore);
1224 dst->set_current_layout(dst_restore);
1231 vk::SamplerAddressMode address_mode,
1232 float max_anisotropy)
1235 auto hash_combine = [](
size_t& seed,
size_t value) {
1236 seed ^=
value + 0x9e3779b9 + (seed << 6) + (seed >> 2);
1239 hash_combine(hash,
static_cast<size_t>(filter));
1240 hash_combine(hash,
static_cast<size_t>(address_mode));
1241 hash_combine(hash, std::hash<float> {}(max_anisotropy));
1246 "Reusing cached sampler (hash: 0x{:X})", hash);
1250 vk::SamplerCreateInfo sampler_info;
1251 sampler_info.magFilter = filter;
1252 sampler_info.minFilter = filter;
1253 sampler_info.mipmapMode = vk::SamplerMipmapMode::eLinear;
1254 sampler_info.addressModeU = address_mode;
1255 sampler_info.addressModeV = address_mode;
1256 sampler_info.addressModeW = address_mode;
1257 sampler_info.mipLodBias = 0.0F;
1258 sampler_info.anisotropyEnable = max_anisotropy > 0.0F;
1259 sampler_info.maxAnisotropy = max_anisotropy;
1260 sampler_info.compareEnable = VK_FALSE;
1261 sampler_info.compareOp = vk::CompareOp::eAlways;
1262 sampler_info.minLod = 0.0F;
1263 sampler_info.maxLod = VK_LOD_CLAMP_NONE;
1264 sampler_info.borderColor = vk::BorderColor::eFloatOpaqueBlack;
1265 sampler_info.unnormalizedCoordinates = VK_FALSE;
1267 vk::Sampler sampler;
1270 }
catch (
const vk::SystemError& e) {
1272 "Failed to create sampler: {}", e.what());
1279 "Created sampler (filter: {}, address: {}, anisotropy: {}, hash: 0x{:X})",
1280 vk::to_string(filter), vk::to_string(address_mode), max_anisotropy, hash);
1292 if (it->second == sampler) {
1301 "Destroyed sampler");
1306 vk::PhysicalDeviceMemoryProperties mem_properties;
1309 for (uint32_t i = 0; i < mem_properties.memoryTypeCount; i++) {
1310 if ((type_filter & (1 << i)) && (mem_properties.memoryTypes[i].propertyFlags & properties) == properties) {
1315 error<std::runtime_error>(
1318 std::source_location::current(),
1319 "Failed to find suitable memory type");
1345 vk::SemaphoreCreateInfo sem_info {};
1349 vk::SubmitInfo submit {};
1350 submit.commandBufferCount = 1;
1351 submit.pCommandBuffers = &
cmd;
1352 submit.signalSemaphoreCount = 1;
1357 "Failed to submit deferred command buffer");
1381 auto& slots = owner.get_buffer_resources().back_buffers;
1382 for (
auto& slot : slots) {
1383 if (slot.mapped_ptr && slot.memory) {
1384 device.unmapMemory(slot.memory);
1387 device.destroyBuffer(slot.buffer);
1390 device.freeMemory(slot.memory);
1397 for (
const auto&
buffer : buffers) {
1402 if (
buffer->is_initialized()) {
1412 auto hash_combine = [](
size_t& seed,
size_t value) {
1413 seed ^=
value + 0x9e3779b9 + (seed << 6) + (seed >> 2);
1416 hash_combine(hash,
static_cast<size_t>(filter));
1417 hash_combine(hash,
static_cast<size_t>(address_mode));
1418 hash_combine(hash, std::hash<float> {}(max_anisotropy));
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
std::shared_ptr< NetworkGeometryBuffer > buffer
vk::PhysicalDevice device
Vulkan-backed buffer wrapper used in processing chains.
vk::Semaphore m_deferred_semaphore
void cleanup_image(const std::shared_ptr< VKImage > &image)
Cleanup a VKImage (destroy view, image, and free memory)
void cleanup_buffer(const std::shared_ptr< Buffers::VKBuffer > &buffer)
Cleanup a buffer and release associated resources.
void flush_pending_buffer_operations()
Flush any pending buffer operations (e.g., uploads/downloads)
bool blit_image(const std::shared_ptr< VKImage > &src, const std::shared_ptr< VKImage > &dst, const vk::ImageBlit ®ion, vk::Filter filter, bool deferred=false)
Blit a region of one image into a region of another.
vk::DeviceAddress get_buffer_device_address(const std::shared_ptr< Buffers::VKBuffer > &buffer) const
Query the Vulkan device address of an initialized BDA-capable buffer.
size_t compute_sampler_hash(vk::Filter filter, vk::SamplerAddressMode address_mode, float max_anisotropy) const
void download_image_data(std::shared_ptr< VKImage > image, void *data, size_t size, const std::shared_ptr< Buffers::VKBuffer > &staging=nullptr, bool deferred=false, vk::ImageLayout restore_layout=vk::ImageLayout::eShaderReadOnlyOptimal, vk::PipelineStageFlags restore_stage=vk::PipelineStageFlagBits::eFragmentShader)
Download image data to a host pointer.
void execute_immediate_commands(const std::function< void(vk::CommandBuffer)> &recorder)
Execute immediate command recording for buffer operations.
BackendResourceManager(VKContext &context, VKCommandManager &command_manager)
std::unordered_map< size_t, vk::Sampler > m_sampler_cache
void initialize_image(const std::shared_ptr< VKImage > &image)
Initialize a VKImage (allocate VkImage, memory, and create image view)
void destroy_sampler(vk::Sampler sampler)
Destroy sampler.
vk::Sampler create_sampler(vk::Filter filter=vk::Filter::eLinear, vk::SamplerAddressMode address_mode=vk::SamplerAddressMode::eRepeat, float max_anisotropy=0.0F)
Create sampler.
bool supports_blit(vk::Format src_format, vk::Format dst_format, vk::Filter filter=vk::Filter::eNearest) const
Whether a blit between two formats is supported with a filter.
uint32_t find_memory_type(uint32_t type_filter, vk::MemoryPropertyFlags properties) const
Find a suitable memory type for Vulkan buffer allocation.
VKCommandManager & m_command_manager
void transition_image_layout(vk::Image image, vk::ImageLayout old_layout, vk::ImageLayout new_layout, uint32_t mip_levels=1, uint32_t array_layers=1, vk::ImageAspectFlags aspect_flags=vk::ImageAspectFlagBits::eColor)
Transition image layout using a pipeline barrier.
bool upload_image_layer(const std::shared_ptr< VKImage > &image, uint32_t layer, const void *data, size_t size, const std::shared_ptr< Buffers::VKBuffer > &staging=nullptr, bool deferred=false)
Upload pixel data into one layer of an array image.
void initialize_buffer(const std::shared_ptr< Buffers::VKBuffer > &buffer)
Initialize a buffer for use with the graphics backend.
void allocate_raw_buffer(size_t size_bytes, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags memory_properties, bool host_visible, vk::Buffer &out_buffer, vk::DeviceMemory &out_memory, void *&out_mapped_ptr)
Allocate a raw VkBuffer/VkDeviceMemory pair without an owning VKBuffer object.
void setup_backend_service(const std::shared_ptr< Registry::Service::BufferService > &buffer_service)
vk::Semaphore flush_deferred_commands()
Flush deferred commands and return a semaphore that signals when they are complete.
void record_deferred_commands(const std::function< void(vk::CommandBuffer)> &recorder)
Record deferred command recording for buffer operations.
void upload_image_data(std::shared_ptr< VKImage > image, const void *data, size_t size)
Upload data to an image (creates staging buffer internally)
std::vector< std::shared_ptr< Buffers::VKBuffer > > m_managed_buffers
void reset_deferred()
Reset deferred command buffer and clear pending state.
void record_deferred_commands(const std::function< void(vk::CommandBuffer)> &recorder)
Record deferred commands for later submission.
void end_single_time_commands(vk::CommandBuffer command_buffer, vk::Queue queue)
End and submit single-time command.
bool has_deferred_commands() const
End and submit single-time command for compute operations.
void free_command_buffer(vk::CommandBuffer command_buffer)
Free a command buffer back to the pool.
vk::CommandBuffer get_deferred_cmd() const
Get the deferred command buffer (if any)
vk::CommandBuffer begin_single_time_commands()
Begin single-time command (for transfers, etc.)
Manages Vulkan command pools and command buffers.
vk::Device get_device() const
Get logical device.
vk::Queue get_graphics_queue() const
Get graphics queue.
vk::PhysicalDevice get_physical_device() const
Get physical device.
High-level wrapper for Vulkan instance and device.
@ GraphicsBackend
Graphics/visual rendering backend (Vulkan, OpenGL)
@ Core
Core engine, backend, subsystems.
@ IMAGE_COLOR
2D RGB/RGBA image
std::string_view modality_to_string(DataModality modality)
Convert DataModality enum to string representation.
Vulkan image resource handles.