MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
BackendResoureManager.cpp
Go to the documentation of this file.
2
6
10
11namespace MayaFlux::Core {
12
13namespace {
14 struct FencedSubmission {
15 vk::CommandBuffer cmd;
16 vk::Fence fence;
17 };
18}
19
21 : m_context(context)
22 , m_command_manager(command_manager)
23{
24}
25
26void BackendResourceManager::setup_backend_service(const std::shared_ptr<Registry::Service::BufferService>& buffer_service)
27{
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);
30 this->initialize_buffer(buffer);
31 };
32
33 buffer_service->destroy_buffer = [this](const std::shared_ptr<void>& vk_buf) {
34 auto buffer = std::static_pointer_cast<Buffers::VKBuffer>(vk_buf);
35 this->cleanup_buffer(buffer);
36 };
37
38 buffer_service->get_buffer_device_address = [this](const std::shared_ptr<void>& vk_buf) -> uint64_t {
39 auto buffer = std::static_pointer_cast<Buffers::VKBuffer>(vk_buf);
40 auto address = this->get_buffer_device_address(buffer);
41 return static_cast<uint64_t>(address);
42 };
43
44 buffer_service->execute_immediate = [this](const std::function<void(void*)>& recorder) {
45 this->execute_immediate_commands([recorder](vk::CommandBuffer cmd) {
46 recorder(static_cast<void*>(cmd));
47 });
48 };
49
50 buffer_service->record_deferred = [this](const std::function<void(void*)>& recorder) {
51 this->record_deferred_commands([recorder](vk::CommandBuffer cmd) {
52 recorder(static_cast<void*>(cmd));
53 });
54 };
55
56 buffer_service->flush_range = [this](void* memory, size_t offset, size_t size) {
57 vk::DeviceMemory mem(reinterpret_cast<VkDeviceMemory>(memory));
58 vk::MappedMemoryRange range { mem, offset, size == 0 ? VK_WHOLE_SIZE : size };
59 if (auto result = m_context.get_device().flushMappedMemoryRanges(1, &range); result != vk::Result::eSuccess) {
61 "Failed to flush mapped memory range: {}", vk::to_string(result));
62 }
63 };
64
65 buffer_service->invalidate_range = [this](void* memory, size_t offset, size_t size) {
66 vk::DeviceMemory mem(reinterpret_cast<VkDeviceMemory>(memory));
67 vk::MappedMemoryRange range { mem, offset, size == 0 ? VK_WHOLE_SIZE : size };
68 if (auto result = m_context.get_device().invalidateMappedMemoryRanges(1, &range); result != vk::Result::eSuccess) {
70 "Failed to invalidate mapped memory range: {}", vk::to_string(result));
71 }
72 };
73
74 buffer_service->map_buffer = [this](void* memory, size_t offset, size_t size) -> void* {
75 vk::DeviceMemory mem(reinterpret_cast<VkDeviceMemory>(memory));
76 return m_context.get_device().mapMemory(mem, offset, size == 0 ? VK_WHOLE_SIZE : size);
77 };
78
79 buffer_service->unmap_buffer = [this](void* memory) {
80 vk::DeviceMemory mem(reinterpret_cast<VkDeviceMemory>(memory));
81 m_context.get_device().unmapMemory(mem);
82 };
83
84 buffer_service->copy_buffer = [this](void* src, void* dst, size_t size, size_t src_off, size_t dst_off) {
85 vk::Buffer s(static_cast<VkBuffer>(src));
86 vk::Buffer d(static_cast<VkBuffer>(dst));
87 execute_immediate_commands([&](vk::CommandBuffer cmd) {
88 vk::BufferCopy region { src_off, dst_off, size };
89 cmd.copyBuffer(s, d, 1, &region);
90 });
91 };
92
93 buffer_service->execute_fenced = [this](const std::function<void(void*)>& recorder)
94 -> std::shared_ptr<void> {
96
97 recorder(static_cast<void*>(cmd));
98
99 cmd.end();
100
101 auto device = m_context.get_device();
102 vk::FenceCreateInfo fence_info {};
103 vk::Fence fence = device.createFence(fence_info);
104
105 vk::SubmitInfo submit_info {};
106 submit_info.commandBufferCount = 1;
107 submit_info.pCommandBuffers = &cmd;
108
109 if (auto result = m_context.get_graphics_queue().submit(1, &submit_info, fence);
110 result != vk::Result::eSuccess) {
112 "execute_fenced: queue submit failed: {}", vk::to_string(result));
113 device.destroyFence(fence);
115 return nullptr;
116 }
117
118 auto handle = std::make_shared<FencedSubmission>();
119 handle->cmd = cmd;
120 handle->fence = fence;
121 return handle;
122 };
123
124 buffer_service->wait_fenced = [this](const std::shared_ptr<void>& handle) {
125 if (!handle)
126 return;
127 auto sub = std::static_pointer_cast<FencedSubmission>(handle);
128 if (!sub->fence)
129 return;
130
131 auto device = m_context.get_device();
132 if (auto result = device.waitForFences(1, &sub->fence, VK_TRUE, UINT64_MAX);
133 result != vk::Result::eSuccess) {
135 "wait_fenced: waitForFences failed: {}", vk::to_string(result));
136 }
137 };
138
139 buffer_service->release_fenced = [this](const std::shared_ptr<void>& handle) {
140 if (!handle)
141 return;
142 auto sub = std::static_pointer_cast<FencedSubmission>(handle);
143
144 auto device = m_context.get_device();
145 if (sub->fence) {
146 device.destroyFence(sub->fence);
147 sub->fence = nullptr;
148 }
149 if (sub->cmd) {
151 sub->cmd = nullptr;
152 }
153 };
154
155 buffer_service->copy_buffer_fenced = [this, buffer_service](void* src, void* dst, size_t size, size_t src_off, size_t dst_off)
156 -> std::shared_ptr<void> {
157 vk::Buffer s(static_cast<VkBuffer>(src));
158 vk::Buffer d(static_cast<VkBuffer>(dst));
159 return buffer_service->execute_fenced([&](void* cmd_ptr) {
160 vk::CommandBuffer cmd(static_cast<VkCommandBuffer>(cmd_ptr));
161 vk::BufferCopy region { src_off, dst_off, static_cast<vk::DeviceSize>(size) };
162 cmd.copyBuffer(s, d, 1, &region);
163 });
164 };
165}
166
167void BackendResourceManager::initialize_buffer(const std::shared_ptr<Buffers::VKBuffer>& buffer)
168{
169 if (!buffer) {
171 "Attempted to initialize null VulkanBuffer");
172 return;
173 }
174
175 if (buffer->is_initialized()) {
177 "VulkanBuffer already initialized, skipping");
178 return;
179 }
180
181 vk::BufferCreateInfo buffer_info {};
182 buffer_info.size = buffer->get_size_bytes();
183 buffer_info.usage = buffer->get_usage_flags();
184 buffer_info.sharingMode = vk::SharingMode::eExclusive;
185
186 vk::Buffer vk_buffer;
187 try {
188 vk_buffer = m_context.get_device().createBuffer(buffer_info);
189 } catch (const vk::SystemError& e) {
190 error_rethrow(
193 std::source_location::current(),
194 "Failed to create VkBuffer: " + std::string(e.what()));
195 }
196
197 vk::MemoryRequirements mem_requirements;
198 mem_requirements = m_context.get_device().getBufferMemoryRequirements(vk_buffer);
199
200 vk::MemoryAllocateInfo alloc_info;
201 alloc_info.allocationSize = mem_requirements.size;
202
203 alloc_info.memoryTypeIndex = find_memory_type(
204 mem_requirements.memoryTypeBits,
205 vk::MemoryPropertyFlags(buffer->get_memory_properties()));
206
207 vk::DeviceMemory memory;
208 try {
209 memory = m_context.get_device().allocateMemory(alloc_info);
210 } catch (const vk::SystemError& e) {
211 m_context.get_device().destroyBuffer(vk_buffer);
212 error_rethrow(
215 std::source_location::current(),
216 "Failed to allocate VkDeviceMemory: " + std::string(e.what()));
217 }
218
219 try {
220 m_context.get_device().bindBufferMemory(vk_buffer, memory, 0);
221 } catch (const vk::SystemError& e) {
222 m_context.get_device().freeMemory(memory);
223 m_context.get_device().destroyBuffer(vk_buffer);
224
225 error_rethrow(
228 std::source_location::current(),
229 "Failed to bind buffer memory: " + std::string(e.what()));
230 }
231
232 void* mapped_ptr = nullptr;
233 if (buffer->is_host_visible()) {
234 try {
235 mapped_ptr = m_context.get_device().mapMemory(memory, 0, buffer->get_size_bytes());
236 } catch (const vk::SystemError& e) {
237 m_context.get_device().freeMemory(memory);
238 m_context.get_device().destroyBuffer(vk_buffer);
239
240 error_rethrow(
243 std::source_location::current(),
244 "Failed to map buffer memory: " + std::string(e.what()));
245 }
246 }
247
248 Buffers::VKBufferResources resources { .buffer = vk_buffer, .memory = memory, .mapped_ptr = mapped_ptr };
249 buffer->set_buffer_resources(resources);
250 m_managed_buffers.push_back(buffer);
251
253 "VulkanBuffer initialized: {} bytes, modality: {}, VkBuffer: {:p}",
254 buffer->get_size_bytes(),
255 Kakshya::modality_to_string(buffer->get_modality()),
256 (void*)buffer->get_buffer());
257}
258
259void BackendResourceManager::cleanup_buffer(const std::shared_ptr<Buffers::VKBuffer>& buffer)
260{
261 if (!buffer) {
263 "Attempted to cleanup null VulkanBuffer");
264 return;
265 }
266
267 auto it = std::ranges::find(m_managed_buffers, buffer);
268 if (it == m_managed_buffers.end()) {
269 return;
270 }
271
272 auto& res = it->get()->get_buffer_resources();
273
274 if (res.mapped_ptr) {
275 m_context.get_device().unmapMemory(res.memory);
276 }
277
278 if (res.index_buffer) {
279 m_context.get_device().destroyBuffer(res.index_buffer);
280 }
281
282 if (res.index_memory) {
283 m_context.get_device().freeMemory(res.index_memory);
284 }
285
286 if (res.buffer) {
287 m_context.get_device().destroyBuffer(res.buffer);
288 }
289
290 if (res.memory) {
291 m_context.get_device().freeMemory(res.memory);
292 }
293
295 "VulkanBuffer cleaned up: {:p}", static_cast<void*>(res.buffer));
296
297 m_managed_buffers.erase(it);
298}
299
301{
302 for (auto& buffer_wrapper : m_managed_buffers) {
303 auto& resources = buffer_wrapper->get_buffer_resources();
304 auto dirty_ranges = buffer_wrapper->get_and_clear_dirty_ranges();
305 if (!dirty_ranges.empty()) {
306 for (auto [offset, size] : dirty_ranges) {
307 vk::MappedMemoryRange range;
308 range.memory = resources.memory;
309 range.offset = offset;
310 range.size = size;
311 if (auto result = m_context.get_device().flushMappedMemoryRanges(1, &range); result != vk::Result::eSuccess) {
313 "Failed to flush mapped memory range: {}", vk::to_string(result));
314 }
315 }
317 "Flushed {} dirty ranges for buffer {:p}", dirty_ranges.size(),
318 (void*)buffer_wrapper->get_buffer());
319 }
320
321 auto invalid_ranges = buffer_wrapper->get_and_clear_invalid_ranges();
322 if (!invalid_ranges.empty()) {
323 for (auto [offset, size] : invalid_ranges) {
324 vk::MappedMemoryRange range;
325 range.memory = buffer_wrapper->get_buffer_resources().memory;
326 range.offset = offset;
327 range.size = size;
328 if (auto result = m_context.get_device().invalidateMappedMemoryRanges(1, &range); result != vk::Result::eSuccess) {
330 "Failed to invalidate mapped memory range: {}", vk::to_string(result));
331 }
332 }
334 "Invalidated {} ranges for buffer {:p}", invalid_ranges.size(),
335 (void*)buffer_wrapper->get_buffer());
336 }
337 }
338}
339
341 const std::shared_ptr<Buffers::VKBuffer>& buffer) const
342{
343 if (!buffer || !buffer->is_initialized()) {
345 "get_buffer_device_address: buffer not initialized");
346 return 0;
347 }
348
349 vk::BufferDeviceAddressInfo info {};
350 info.buffer = buffer->get_buffer();
351 // return static_cast<uint64_t>(m_context.get_device().getBufferAddress(info));
352 return m_context.get_device().getBufferAddress(info);
353}
354
355void BackendResourceManager::initialize_image(const std::shared_ptr<VKImage>& image)
356{
357 if (!image) {
359 "Attempted to initialize null VKImage");
360 return;
361 }
362
363 if (image->is_initialized()) {
365 "VKImage already initialized, skipping");
366 return;
367 }
368
369 // ========================================================================
370 // Step 1: Create VkImage
371 // ========================================================================
372
373 vk::ImageCreateInfo image_info {};
374
375 switch (image->get_type()) {
377 image_info.imageType = vk::ImageType::e1D;
378 break;
381 image_info.imageType = vk::ImageType::e2D;
382 break;
384 image_info.imageType = vk::ImageType::e3D;
385 break;
386 }
387
388 image_info.extent.width = image->get_width();
389 image_info.extent.height = image->get_height();
390 image_info.extent.depth = image->get_depth();
391 image_info.mipLevels = image->get_mip_levels();
392 image_info.arrayLayers = image->get_array_layers();
393 image_info.format = image->get_format();
394 image_info.tiling = vk::ImageTiling::eOptimal;
395 image_info.initialLayout = vk::ImageLayout::eUndefined;
396 image_info.usage = image->get_usage_flags();
397 image_info.sharingMode = vk::SharingMode::eExclusive;
398 image_info.samples = vk::SampleCountFlagBits::e1; // No MSAA for now
399 image_info.flags = (image->get_type() == VKImage::Type::TYPE_CUBE)
400 ? vk::ImageCreateFlagBits::eCubeCompatible
401 : vk::ImageCreateFlags {};
402
403 vk::Image vk_image;
404 try {
405 vk_image = m_context.get_device().createImage(image_info);
406 } catch (const vk::SystemError& e) {
407 error_rethrow(
410 std::source_location::current(),
411 "Failed to create VkImage: " + std::string(e.what()));
412 }
413
414 // ========================================================================
415 // Step 2: Allocate memory
416 // ========================================================================
417
418 vk::MemoryRequirements mem_requirements;
419 mem_requirements = m_context.get_device().getImageMemoryRequirements(vk_image);
420
421 vk::MemoryAllocateInfo alloc_info {};
422 alloc_info.allocationSize = mem_requirements.size;
423 alloc_info.memoryTypeIndex = find_memory_type(
424 mem_requirements.memoryTypeBits,
425 image->get_memory_properties());
426
427 vk::DeviceMemory memory;
428 try {
429 memory = m_context.get_device().allocateMemory(alloc_info);
430 } catch (const vk::SystemError& e) {
431 m_context.get_device().destroyImage(vk_image);
432 error_rethrow(
435 std::source_location::current(),
436 "Failed to allocate VkDeviceMemory for image: " + std::string(e.what()));
437 }
438
439 // ========================================================================
440 // Step 3: Bind memory to image
441 // ========================================================================
442
443 try {
444 m_context.get_device().bindImageMemory(vk_image, memory, 0);
445 } catch (const vk::SystemError& e) {
446 m_context.get_device().freeMemory(memory);
447 m_context.get_device().destroyImage(vk_image);
448 error_rethrow(
451 std::source_location::current(),
452 "Failed to bind memory to VkImage: " + std::string(e.what()));
453 }
454
455 // ========================================================================
456 // Step 4: Create image view
457 // ========================================================================
458
459 vk::ImageViewCreateInfo view_info {};
460
461 switch (image->get_type()) {
463 view_info.viewType = (image->get_array_layers() > 1)
464 ? vk::ImageViewType::e1DArray
465 : vk::ImageViewType::e1D;
466 break;
468 view_info.viewType = (image->get_array_layers() > 1)
469 ? vk::ImageViewType::e2DArray
470 : vk::ImageViewType::e2D;
471 break;
473 view_info.viewType = vk::ImageViewType::e3D;
474 break;
476 view_info.viewType = vk::ImageViewType::eCube;
477 break;
478 }
479
480 view_info.image = vk_image;
481 view_info.format = image->get_format();
482 view_info.subresourceRange.aspectMask = image->get_aspect_flags();
483 view_info.subresourceRange.baseMipLevel = 0;
484 view_info.subresourceRange.levelCount = image->get_mip_levels();
485 view_info.subresourceRange.baseArrayLayer = 0;
486 view_info.subresourceRange.layerCount = image->get_array_layers();
487
488 view_info.components.r = vk::ComponentSwizzle::eIdentity;
489 view_info.components.g = vk::ComponentSwizzle::eIdentity;
490 view_info.components.b = vk::ComponentSwizzle::eIdentity;
491 view_info.components.a = vk::ComponentSwizzle::eIdentity;
492
493 vk::ImageView image_view;
494 try {
495 image_view = m_context.get_device().createImageView(view_info);
496 } catch (const vk::SystemError& e) {
497 m_context.get_device().freeMemory(memory);
498 m_context.get_device().destroyImage(vk_image);
499 error_rethrow(
502 std::source_location::current(),
503 "Failed to create VkImageView: " + std::string(e.what()));
504 }
505
506 // ========================================================================
507 // Step 5: Store handles in VKImage
508 // ========================================================================
509
510 VKImageResources resources {};
511 resources.image = vk_image;
512 resources.image_view = image_view;
513 resources.memory = memory;
514 resources.sampler = nullptr;
515
516 image->set_image_resources(resources);
517 image->set_current_layout(vk::ImageLayout::eUndefined);
518
520 "VKImage initialized: {}x{}x{}, format: {}, {} mips, {} layers",
521 image->get_width(), image->get_height(), image->get_depth(),
522 vk::to_string(image->get_format()),
523 image->get_mip_levels(), image->get_array_layers());
524}
525
526void BackendResourceManager::cleanup_image(const std::shared_ptr<VKImage>& image)
527{
528 if (!image || !image->is_initialized()) {
529 return;
530 }
531
532 const auto& resources = image->get_image_resources();
533
534 if (resources.image_view) {
535 m_context.get_device().destroyImageView(resources.image_view);
536 }
537
538 if (resources.image) {
539 m_context.get_device().destroyImage(resources.image);
540 }
541
542 if (resources.memory) {
543 m_context.get_device().freeMemory(resources.memory);
544 }
545
547 "VKImage cleaned up");
548}
549
551 vk::Image image,
552 vk::ImageLayout old_layout,
553 vk::ImageLayout new_layout,
554 uint32_t mip_levels,
555 uint32_t array_layers,
556 vk::ImageAspectFlags aspect_flags)
557{
558 execute_immediate_commands([&](vk::CommandBuffer cmd) {
559 vk::ImageMemoryBarrier barrier {};
560 barrier.oldLayout = old_layout;
561 barrier.newLayout = new_layout;
562 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
563 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
564 barrier.image = image;
565 barrier.subresourceRange.aspectMask = aspect_flags;
566 barrier.subresourceRange.baseMipLevel = 0;
567 barrier.subresourceRange.levelCount = mip_levels;
568 barrier.subresourceRange.baseArrayLayer = 0;
569 barrier.subresourceRange.layerCount = array_layers;
570
571 vk::PipelineStageFlags src_stage;
572 vk::PipelineStageFlags dst_stage;
573
574 if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eTransferDstOptimal) {
575 barrier.srcAccessMask = vk::AccessFlags {};
576 barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
577 src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
578 dst_stage = vk::PipelineStageFlagBits::eTransfer;
579 } else if (old_layout == vk::ImageLayout::eTransferDstOptimal && new_layout == vk::ImageLayout::eShaderReadOnlyOptimal) {
580 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
581 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
582 src_stage = vk::PipelineStageFlagBits::eTransfer;
583 dst_stage = vk::PipelineStageFlagBits::eFragmentShader;
584 } else if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eShaderReadOnlyOptimal) {
585 barrier.srcAccessMask = vk::AccessFlags {};
586 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
587 src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
588 dst_stage = vk::PipelineStageFlagBits::eFragmentShader;
589 } else if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eColorAttachmentOptimal) {
590 barrier.srcAccessMask = vk::AccessFlags {};
591 barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
592 src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
593 dst_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
594 } else if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eDepthStencilAttachmentOptimal) {
595 barrier.srcAccessMask = vk::AccessFlags {};
596 barrier.dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite;
597 src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
598 dst_stage = vk::PipelineStageFlagBits::eEarlyFragmentTests;
599 } else if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eGeneral) {
600 barrier.srcAccessMask = vk::AccessFlags {};
601 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
602 src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
603 dst_stage = vk::PipelineStageFlagBits::eComputeShader;
604 } else {
605 barrier.srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
606 barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
607 src_stage = vk::PipelineStageFlagBits::eAllCommands;
608 dst_stage = vk::PipelineStageFlagBits::eAllCommands;
609
611 "Using generic image layout transition");
612 }
613
614 cmd.pipelineBarrier(
615 src_stage, dst_stage,
616 vk::DependencyFlags {},
617 0, nullptr, // Memory barriers
618 0, nullptr, // Buffer barriers
619 1, &barrier // Image barriers
620 );
621 });
622
624 "Image layout transitioned: {} -> {}",
625 vk::to_string(old_layout), vk::to_string(new_layout));
626}
627
629 std::shared_ptr<VKImage> image,
630 const void* data,
631 size_t size)
632{
633 if (!image || !data) {
635 "Invalid parameters for upload_image_data");
636 return;
637 }
638
639 auto staging = std::make_shared<Buffers::VKBuffer>(
640 size,
643
644 initialize_buffer(staging);
645
646 void* mapped = staging->get_mapped_ptr();
647 if (!mapped) {
649 "Failed to map staging buffer for image upload");
650 cleanup_buffer(staging);
651 return;
652 }
653
654 std::memcpy(mapped, data, size);
655 staging->mark_dirty_range(0, size);
656
657 auto& resources = staging->get_buffer_resources();
658 vk::MappedMemoryRange range { resources.memory, 0, VK_WHOLE_SIZE };
659
660 if (auto result = m_context.get_device().flushMappedMemoryRanges(1, &range); result != vk::Result::eSuccess) {
662 "Failed to flush mapped memory range: {}", vk::to_string(result));
663 }
664
665 execute_immediate_commands([&](vk::CommandBuffer cmd) {
666 vk::ImageMemoryBarrier barrier {};
667 barrier.oldLayout = image->get_current_layout();
668 barrier.newLayout = vk::ImageLayout::eTransferDstOptimal;
669 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
670 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
671 barrier.image = image->get_image();
672 barrier.subresourceRange.aspectMask = image->get_aspect_flags();
673 barrier.subresourceRange.baseMipLevel = 0;
674 barrier.subresourceRange.levelCount = image->get_mip_levels();
675 barrier.subresourceRange.baseArrayLayer = 0;
676 barrier.subresourceRange.layerCount = image->get_array_layers();
677 barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
678 barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
679
680 cmd.pipelineBarrier(
681 vk::PipelineStageFlagBits::eFragmentShader,
682 vk::PipelineStageFlagBits::eTransfer,
683 vk::DependencyFlags {},
684 0, nullptr, 0, nullptr, 1, &barrier);
685
686 vk::BufferImageCopy region {};
687 region.bufferOffset = 0;
688 region.bufferRowLength = 0;
689 region.bufferImageHeight = 0;
690 region.imageSubresource.aspectMask = image->get_aspect_flags();
691 region.imageSubresource.mipLevel = 0;
692 region.imageSubresource.baseArrayLayer = 0;
693 region.imageSubresource.layerCount = image->get_array_layers();
694 region.imageOffset = vk::Offset3D { 0, 0, 0 };
695 region.imageExtent = vk::Extent3D {
696 image->get_width(),
697 image->get_height(),
698 image->get_depth()
699 };
700
701 cmd.copyBufferToImage(
702 staging->get_buffer(),
703 image->get_image(),
704 vk::ImageLayout::eTransferDstOptimal,
705 1, &region);
706
707 barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
708 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
709 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
710 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
711
712 cmd.pipelineBarrier(
713 vk::PipelineStageFlagBits::eTransfer,
714 vk::PipelineStageFlagBits::eFragmentShader,
715 vk::DependencyFlags {},
716 0, nullptr, 0, nullptr, 1, &barrier);
717 });
718
719 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
720
722 "Uploaded {} bytes to image {}x{}",
723 size, image->get_width(), image->get_height());
724}
725
727 std::shared_ptr<VKImage> image,
728 const void* data,
729 size_t size,
730 const std::shared_ptr<Buffers::VKBuffer>& staging, bool deferred)
731{
732 if (!image || !data || !staging) {
734 "Invalid parameters for upload_image_data_with_staging");
735 return;
736 }
737
738 void* mapped = staging->get_mapped_ptr();
739 if (!mapped) {
741 "upload_image_data_with_staging: staging buffer has no mapped pointer");
742 return;
743 }
744
745 std::memcpy(mapped, data, size);
746 staging->mark_dirty_range(0, size);
747
748 auto& resources = staging->get_buffer_resources();
749 vk::MappedMemoryRange range { resources.memory, 0, VK_WHOLE_SIZE };
750
751 if (auto result = m_context.get_device().flushMappedMemoryRanges(1, &range);
752 result != vk::Result::eSuccess) {
754 "upload_image_data_with_staging: flush failed: {}", vk::to_string(result));
755 }
756
757 auto record_command = [&](vk::CommandBuffer cmd) {
758 vk::ImageMemoryBarrier barrier {};
759 barrier.oldLayout = image->get_current_layout();
760 barrier.newLayout = vk::ImageLayout::eTransferDstOptimal;
761 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
762 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
763 barrier.image = image->get_image();
764 barrier.subresourceRange = {
765 image->get_aspect_flags(), 0,
766 image->get_mip_levels(), 0,
767 image->get_array_layers()
768 };
769 barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
770 barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
771
772 cmd.pipelineBarrier(
773 vk::PipelineStageFlagBits::eFragmentShader,
774 vk::PipelineStageFlagBits::eTransfer,
775 {}, 0, nullptr, 0, nullptr, 1, &barrier);
776
777 vk::BufferImageCopy region {};
778 region.imageSubresource.aspectMask = image->get_aspect_flags();
779 region.imageSubresource.layerCount = image->get_array_layers();
780 region.imageOffset = vk::Offset3D { 0, 0, 0 };
781 region.imageExtent = vk::Extent3D {
782 image->get_width(),
783 image->get_height(),
784 image->get_depth()
785 };
786
787 cmd.copyBufferToImage(
788 staging->get_buffer(),
789 image->get_image(),
790 vk::ImageLayout::eTransferDstOptimal,
791 1, &region);
792
793 barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
794 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
795 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
796 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
797
798 cmd.pipelineBarrier(
799 vk::PipelineStageFlagBits::eTransfer,
800 vk::PipelineStageFlagBits::eFragmentShader,
801 {}, 0, nullptr, 0, nullptr, 1, &barrier);
802 };
803
804 if (deferred) {
805 record_deferred_commands(record_command);
806 } else {
807 execute_immediate_commands(record_command);
808 }
809
810 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
811
813 "upload_image_data_with_staging: {} bytes to image {}x{}",
814 size, image->get_width(), image->get_height());
815}
816
818 std::shared_ptr<VKImage> image,
819 void* data,
820 size_t size,
821 vk::ImageLayout restore_layout,
822 vk::PipelineStageFlags restore_stage)
823{
824 if (!image || !data) {
826 "Invalid parameters for download_image_data");
827 return;
828 }
829
830 auto staging = std::make_shared<Buffers::VKBuffer>(
831 size,
834
835 initialize_buffer(staging);
836
837 execute_immediate_commands([&](vk::CommandBuffer cmd) {
838 vk::ImageMemoryBarrier barrier {};
839 barrier.oldLayout = image->get_current_layout();
840 barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal;
841 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
842 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
843 barrier.image = image->get_image();
844 barrier.subresourceRange.aspectMask = image->get_aspect_flags();
845 barrier.subresourceRange.baseMipLevel = 0;
846 barrier.subresourceRange.levelCount = image->get_mip_levels();
847 barrier.subresourceRange.baseArrayLayer = 0;
848 barrier.subresourceRange.layerCount = image->get_array_layers();
849 barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
850 barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead;
851
852 cmd.pipelineBarrier(
853 vk::PipelineStageFlagBits::eFragmentShader,
854 vk::PipelineStageFlagBits::eTransfer,
855 vk::DependencyFlags {}, {}, {}, barrier);
856
857 vk::BufferImageCopy region {};
858 region.bufferOffset = 0;
859 region.bufferRowLength = 0;
860 region.bufferImageHeight = 0;
861 region.imageSubresource.aspectMask = image->get_aspect_flags();
862 region.imageSubresource.mipLevel = 0;
863 region.imageSubresource.baseArrayLayer = 0;
864 region.imageSubresource.layerCount = image->get_array_layers();
865 region.imageOffset = vk::Offset3D { 0, 0, 0 };
866 region.imageExtent = vk::Extent3D {
867 image->get_width(),
868 image->get_height(),
869 image->get_depth()
870 };
871
872 cmd.copyImageToBuffer(
873 image->get_image(),
874 vk::ImageLayout::eTransferSrcOptimal,
875 staging->get_buffer(),
876 1, &region);
877
878 barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal;
879 barrier.newLayout = restore_layout;
880 barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead;
881 barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
882
883 cmd.pipelineBarrier(
884 vk::PipelineStageFlagBits::eTransfer,
885 restore_stage,
886 vk::DependencyFlags {}, {}, {}, barrier);
887 });
888
889 staging->mark_invalid_range(0, size);
890 auto& resources = staging->get_buffer_resources();
891 vk::MappedMemoryRange range { resources.memory, 0, VK_WHOLE_SIZE };
892
893 if (auto result = m_context.get_device().invalidateMappedMemoryRanges(1, &range);
894 result != vk::Result::eSuccess) {
896 "Failed to invalidate mapped memory range: {}", vk::to_string(result));
897 }
898
899 if (void* mapped = staging->get_mapped_ptr()) {
900 std::memcpy(data, mapped, size);
901 }
902
903 cleanup_buffer(staging);
904
906 "Downloaded {} bytes from image {}x{}",
907 size, image->get_width(), image->get_height());
908}
909
911 vk::Filter filter,
912 vk::SamplerAddressMode address_mode,
913 float max_anisotropy)
914{
915 size_t hash = 0;
916 auto hash_combine = [](size_t& seed, size_t value) {
917 seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2);
918 };
919
920 hash_combine(hash, static_cast<size_t>(filter));
921 hash_combine(hash, static_cast<size_t>(address_mode));
922 hash_combine(hash, std::hash<float> {}(max_anisotropy));
923
924 auto it = m_sampler_cache.find(hash);
925 if (it != m_sampler_cache.end()) {
927 "Reusing cached sampler (hash: 0x{:X})", hash);
928 return it->second;
929 }
930
931 vk::SamplerCreateInfo sampler_info;
932 sampler_info.magFilter = filter;
933 sampler_info.minFilter = filter;
934 sampler_info.mipmapMode = vk::SamplerMipmapMode::eLinear;
935 sampler_info.addressModeU = address_mode;
936 sampler_info.addressModeV = address_mode;
937 sampler_info.addressModeW = address_mode;
938 sampler_info.mipLodBias = 0.0F;
939 sampler_info.anisotropyEnable = max_anisotropy > 0.0F;
940 sampler_info.maxAnisotropy = max_anisotropy;
941 sampler_info.compareEnable = VK_FALSE;
942 sampler_info.compareOp = vk::CompareOp::eAlways;
943 sampler_info.minLod = 0.0F;
944 sampler_info.maxLod = VK_LOD_CLAMP_NONE;
945 sampler_info.borderColor = vk::BorderColor::eFloatOpaqueBlack;
946 sampler_info.unnormalizedCoordinates = VK_FALSE;
947
948 vk::Sampler sampler;
949 try {
950 sampler = m_context.get_device().createSampler(sampler_info);
951 } catch (const vk::SystemError& e) {
953 "Failed to create sampler: {}", e.what());
954 return nullptr;
955 }
956
957 m_sampler_cache[hash] = sampler;
958
960 "Created sampler (filter: {}, address: {}, anisotropy: {}, hash: 0x{:X})",
961 vk::to_string(filter), vk::to_string(address_mode), max_anisotropy, hash);
962
963 return sampler;
964}
965
967{
968 if (!sampler) {
969 return;
970 }
971
972 for (auto it = m_sampler_cache.begin(); it != m_sampler_cache.end(); ++it) {
973 if (it->second == sampler) {
974 m_sampler_cache.erase(it);
975 break;
976 }
977 }
978
979 m_context.get_device().destroySampler(sampler);
980
982 "Destroyed sampler");
983}
984
985uint32_t BackendResourceManager::find_memory_type(uint32_t type_filter, vk::MemoryPropertyFlags properties) const
986{
987 vk::PhysicalDeviceMemoryProperties mem_properties;
988 mem_properties = m_context.get_physical_device().getMemoryProperties();
989
990 for (uint32_t i = 0; i < mem_properties.memoryTypeCount; i++) {
991 if ((type_filter & (1 << i)) && (mem_properties.memoryTypes[i].propertyFlags & properties) == properties) {
992 return i;
993 }
994 }
995
996 error<std::runtime_error>(
999 std::source_location::current(),
1000 "Failed to find suitable memory type");
1001
1002 return 0;
1003}
1004
1005void BackendResourceManager::execute_immediate_commands(const std::function<void(vk::CommandBuffer)>& recorder)
1006{
1007 vk::CommandBuffer cmd = m_command_manager.begin_single_time_commands();
1008 recorder(cmd);
1010}
1011
1012void BackendResourceManager::record_deferred_commands(const std::function<void(vk::CommandBuffer)>& recorder)
1013{
1015}
1016
1018{
1020 return nullptr;
1021
1022 vk::CommandBuffer cmd = m_command_manager.get_deferred_cmd();
1023 cmd.end();
1024
1025 if (!m_deferred_semaphore) {
1026 vk::SemaphoreCreateInfo sem_info {};
1027 m_deferred_semaphore = m_context.get_device().createSemaphore(sem_info);
1028 }
1029
1030 vk::SubmitInfo submit {};
1031 submit.commandBufferCount = 1;
1032 submit.pCommandBuffers = &cmd;
1033 submit.signalSemaphoreCount = 1;
1034 submit.pSignalSemaphores = &m_deferred_semaphore;
1035
1036 if (m_context.get_graphics_queue().submit(1, &submit, nullptr) != vk::Result::eSuccess) {
1038 "Failed to submit deferred command buffer");
1039 }
1040
1042
1043 return m_deferred_semaphore;
1044}
1045
1047{
1048 for (auto& [hash, sampler] : m_sampler_cache) {
1049 if (sampler) {
1050 m_context.get_device().destroySampler(sampler);
1051 }
1052 }
1053 m_sampler_cache.clear();
1054
1056 m_context.get_device().destroySemaphore(m_deferred_semaphore);
1057 m_deferred_semaphore = nullptr;
1058 }
1059
1060 for (auto& buffer : m_managed_buffers) {
1061 if (buffer && buffer->is_initialized()) {
1062 cleanup_buffer(buffer);
1063 }
1064 }
1065 m_managed_buffers.clear();
1066}
1067
1068size_t BackendResourceManager::compute_sampler_hash(vk::Filter filter, vk::SamplerAddressMode address_mode, float max_anisotropy) const
1069{
1070 size_t hash = 0;
1071 auto hash_combine = [](size_t& seed, size_t value) {
1072 seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2);
1073 };
1074
1075 hash_combine(hash, static_cast<size_t>(filter));
1076 hash_combine(hash, static_cast<size_t>(address_mode));
1077 hash_combine(hash, std::hash<float> {}(max_anisotropy));
1078
1079 return hash;
1080}
1081
1082}
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
vk::Fence fence
vk::CommandBuffer cmd
IO::ImageData image
Definition Decoder.cpp:57
@ STAGING
Host-visible staging buffer (CPU-writable, eTransferSrc|Dst)
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)
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 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.
void download_image_data(std::shared_ptr< VKImage > image, void *data, size_t size, vk::ImageLayout restore_layout=vk::ImageLayout::eShaderReadOnlyOptimal, vk::PipelineStageFlags restore_stage=vk::PipelineStageFlagBits::eFragmentShader)
Download data from an image into a caller-supplied buffer.
uint32_t find_memory_type(uint32_t type_filter, vk::MemoryPropertyFlags properties) const
Find a suitable memory type for Vulkan buffer allocation.
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.
void initialize_buffer(const std::shared_ptr< Buffers::VKBuffer > &buffer)
Initialize a buffer for use with the graphics backend.
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.
Definition VKContext.hpp:49
vk::Queue get_graphics_queue() const
Get graphics queue.
Definition VKContext.hpp:54
vk::PhysicalDevice get_physical_device() const
Get physical device.
Definition VKContext.hpp:44
High-level wrapper for Vulkan instance and device.
Definition VKContext.hpp:16
@ 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.
Definition NDData.cpp:83
Raw Vulkan handles owned by a VKBuffer instance.
Definition VKBuffer.hpp:32
Vulkan image resource handles.
Definition VKImage.hpp:15