MayaFlux 0.5.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->allocate_raw_buffer = [this](
34 size_t size_bytes,
35 uint32_t usage_flags,
36 uint32_t memory_property_flags,
37 bool host_visible,
38 void*& out_buffer,
39 void*& out_memory,
40 void*& out_mapped_ptr) -> void {
41 vk::Buffer buf;
42 vk::DeviceMemory mem;
43 void* mapped = nullptr;
45 size_bytes,
46 vk::BufferUsageFlags(usage_flags),
47 vk::MemoryPropertyFlags(memory_property_flags),
48 host_visible,
49 buf, mem, mapped);
50 out_buffer = static_cast<VkBuffer>(buf);
51 out_memory = static_cast<VkDeviceMemory>(mem);
52 out_mapped_ptr = mapped;
53 };
54
55 buffer_service->destroy_buffer = [this](const std::shared_ptr<void>& vk_buf) {
56 auto buffer = std::static_pointer_cast<Buffers::VKBuffer>(vk_buf);
57 this->cleanup_buffer(buffer);
58 };
59
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);
62 auto address = this->get_buffer_device_address(buffer);
63 return static_cast<uint64_t>(address);
64 };
65
66 buffer_service->execute_immediate = [this](const std::function<void(void*)>& recorder) {
67 this->execute_immediate_commands([recorder](vk::CommandBuffer cmd) {
68 recorder(static_cast<void*>(cmd));
69 });
70 };
71
72 buffer_service->record_deferred = [this](const std::function<void(void*)>& recorder) {
73 this->record_deferred_commands([recorder](vk::CommandBuffer cmd) {
74 recorder(static_cast<void*>(cmd));
75 });
76 };
77
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));
84 }
85 };
86
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));
93 }
94 };
95
96 buffer_service->map_buffer = [this](void* memory, size_t offset, size_t size) -> void* {
97 vk::DeviceMemory mem(reinterpret_cast<VkDeviceMemory>(memory));
98 return m_context.get_device().mapMemory(mem, offset, size == 0 ? VK_WHOLE_SIZE : size);
99 };
100
101 buffer_service->unmap_buffer = [this](void* memory) {
102 vk::DeviceMemory mem(reinterpret_cast<VkDeviceMemory>(memory));
103 m_context.get_device().unmapMemory(mem);
104 };
105
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));
109 execute_immediate_commands([&](vk::CommandBuffer cmd) {
110 vk::BufferCopy region { src_off, dst_off, size };
111 cmd.copyBuffer(s, d, 1, &region);
112 });
113 };
114
115 buffer_service->execute_fenced = [this](const std::function<void(void*)>& recorder)
116 -> std::shared_ptr<void> {
118
119 recorder(static_cast<void*>(cmd));
120
121 cmd.end();
122
123 auto device = m_context.get_device();
124 vk::FenceCreateInfo fence_info {};
125 vk::Fence fence = device.createFence(fence_info);
126
127 vk::SubmitInfo submit_info {};
128 submit_info.commandBufferCount = 1;
129 submit_info.pCommandBuffers = &cmd;
130
131 if (auto result = m_context.get_graphics_queue().submit(1, &submit_info, fence);
132 result != vk::Result::eSuccess) {
134 "execute_fenced: queue submit failed: {}", vk::to_string(result));
135 device.destroyFence(fence);
137 return nullptr;
138 }
139
140 auto handle = std::make_shared<FencedSubmission>();
141 handle->cmd = cmd;
142 handle->fence = fence;
143 return handle;
144 };
145
146 buffer_service->wait_fenced = [this](const std::shared_ptr<void>& handle) {
147 if (!handle)
148 return;
149 auto sub = std::static_pointer_cast<FencedSubmission>(handle);
150 if (!sub->fence)
151 return;
152
153 auto device = m_context.get_device();
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));
158 }
159 };
160
161 buffer_service->release_fenced = [this](const std::shared_ptr<void>& handle) {
162 if (!handle)
163 return;
164 auto sub = std::static_pointer_cast<FencedSubmission>(handle);
165
166 auto device = m_context.get_device();
167 if (sub->fence) {
168 device.destroyFence(sub->fence);
169 sub->fence = nullptr;
170 }
171 if (sub->cmd) {
173 sub->cmd = nullptr;
174 }
175 };
176
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, &region);
185 });
186 };
187}
188
190 size_t size_bytes,
191 vk::BufferUsageFlags usage,
192 vk::MemoryPropertyFlags memory_properties,
193 bool host_visible,
194 vk::Buffer& out_buffer,
195 vk::DeviceMemory& out_memory,
196 void*& out_mapped_ptr)
197{
198 vk::BufferCreateInfo buffer_info {};
199 buffer_info.size = size_bytes;
200 buffer_info.usage = usage;
201 buffer_info.sharingMode = vk::SharingMode::eExclusive;
202
203 try {
204 out_buffer = m_context.get_device().createBuffer(buffer_info);
205 } catch (const vk::SystemError& e) {
206 error_rethrow(
209 std::source_location::current(),
210 "Failed to create raw VkBuffer: " + std::string(e.what()));
211 }
212
213 vk::MemoryRequirements mem_requirements = m_context.get_device().getBufferMemoryRequirements(out_buffer);
214
215 vk::MemoryAllocateInfo alloc_info;
216 alloc_info.allocationSize = mem_requirements.size;
217 alloc_info.memoryTypeIndex = find_memory_type(mem_requirements.memoryTypeBits, memory_properties);
218
219 try {
220 out_memory = m_context.get_device().allocateMemory(alloc_info);
221 } catch (const vk::SystemError& e) {
222 m_context.get_device().destroyBuffer(out_buffer);
223 error_rethrow(
226 std::source_location::current(),
227 "Failed to allocate raw VkDeviceMemory: " + std::string(e.what()));
228 }
229
230 try {
231 m_context.get_device().bindBufferMemory(out_buffer, out_memory, 0);
232 } catch (const vk::SystemError& e) {
233 m_context.get_device().freeMemory(out_memory);
234 m_context.get_device().destroyBuffer(out_buffer);
235 error_rethrow(
238 std::source_location::current(),
239 "Failed to bind raw buffer memory: " + std::string(e.what()));
240 }
241
242 out_mapped_ptr = nullptr;
243 if (host_visible) {
244 try {
245 out_mapped_ptr = m_context.get_device().mapMemory(out_memory, 0, size_bytes);
246 } catch (const vk::SystemError& e) {
247 m_context.get_device().freeMemory(out_memory);
248 m_context.get_device().destroyBuffer(out_buffer);
249 error_rethrow(
252 std::source_location::current(),
253 "Failed to map raw buffer memory: " + std::string(e.what()));
254 }
255 }
256}
257
258void BackendResourceManager::initialize_buffer(const std::shared_ptr<Buffers::VKBuffer>& buffer)
259{
260 if (!buffer) {
262 "Attempted to initialize null VKBuffer");
263 return;
264 }
265 if (buffer->is_initialized()) {
267 "VKBuffer already initialized, skipping");
268 return;
269 }
270
271 vk::Buffer vk_buffer;
272 vk::DeviceMemory memory;
273 void* mapped_ptr = nullptr;
274
276 buffer->get_size_bytes(),
277 buffer->get_usage_flags(),
278 vk::MemoryPropertyFlags(buffer->get_memory_properties()),
279 buffer->is_host_visible(),
280 vk_buffer, memory, mapped_ptr);
281
282 auto& resources = buffer->get_buffer_resources();
283 resources.buffer = vk_buffer;
284 resources.memory = memory;
285 resources.mapped_ptr = mapped_ptr;
286
287 m_managed_buffers.push_back(buffer);
288
290 "VKBuffer initialized: {} bytes, modality: {}, VkBuffer: {:p}",
291 buffer->get_size_bytes(),
292 Kakshya::modality_to_string(buffer->get_modality()),
293 (void*)buffer->get_buffer());
294}
295
296void BackendResourceManager::cleanup_buffer(const std::shared_ptr<Buffers::VKBuffer>& buffer)
297{
298 if (!buffer) {
300 "Attempted to cleanup null VKBuffer");
301 return;
302 }
303
304 auto it = std::ranges::find(m_managed_buffers, buffer);
305 if (it == m_managed_buffers.end()) {
306 return;
307 }
308
309 auto& res = it->get()->get_buffer_resources();
310
311 if (res.mapped_ptr) {
312 m_context.get_device().unmapMemory(res.memory);
313 }
314
315 if (res.index_buffer) {
316 m_context.get_device().destroyBuffer(res.index_buffer);
317 }
318
319 if (res.index_memory) {
320 m_context.get_device().freeMemory(res.index_memory);
321 }
322
323 if (res.buffer) {
324 m_context.get_device().destroyBuffer(res.buffer);
325 }
326
327 if (res.memory) {
328 m_context.get_device().freeMemory(res.memory);
329 }
330
332 "VKBuffer cleaned up: {:p}", static_cast<void*>(res.buffer));
333
334 m_managed_buffers.erase(it);
335}
336
338{
339 for (auto& buffer_wrapper : m_managed_buffers) {
340 auto& resources = buffer_wrapper->get_buffer_resources();
341 auto dirty_ranges = buffer_wrapper->get_and_clear_dirty_ranges();
342 if (!dirty_ranges.empty()) {
343 for (auto [offset, size] : dirty_ranges) {
344 vk::MappedMemoryRange range;
345 range.memory = resources.memory;
346 range.offset = offset;
347 range.size = size;
348 if (auto result = m_context.get_device().flushMappedMemoryRanges(1, &range); result != vk::Result::eSuccess) {
350 "Failed to flush mapped memory range: {}", vk::to_string(result));
351 }
352 }
354 "Flushed {} dirty ranges for buffer {:p}", dirty_ranges.size(),
355 (void*)buffer_wrapper->get_buffer());
356 }
357
358 auto invalid_ranges = buffer_wrapper->get_and_clear_invalid_ranges();
359 if (!invalid_ranges.empty()) {
360 for (auto [offset, size] : invalid_ranges) {
361 vk::MappedMemoryRange range;
362 range.memory = buffer_wrapper->get_buffer_resources().memory;
363 range.offset = offset;
364 range.size = size;
365 if (auto result = m_context.get_device().invalidateMappedMemoryRanges(1, &range); result != vk::Result::eSuccess) {
367 "Failed to invalidate mapped memory range: {}", vk::to_string(result));
368 }
369 }
371 "Invalidated {} ranges for buffer {:p}", invalid_ranges.size(),
372 (void*)buffer_wrapper->get_buffer());
373 }
374 }
375}
376
378 const std::shared_ptr<Buffers::VKBuffer>& buffer) const
379{
380 if (!buffer || !buffer->is_initialized()) {
382 "get_buffer_device_address: buffer not initialized");
383 return 0;
384 }
385
386 vk::BufferDeviceAddressInfo info {};
387 info.buffer = buffer->get_buffer();
388 // return static_cast<uint64_t>(m_context.get_device().getBufferAddress(info));
389 return m_context.get_device().getBufferAddress(info);
390}
391
392void BackendResourceManager::initialize_image(const std::shared_ptr<VKImage>& image)
393{
394 if (!image) {
396 "Attempted to initialize null VKImage");
397 return;
398 }
399
400 if (image->is_initialized()) {
402 "VKImage already initialized, skipping");
403 return;
404 }
405
406 // ========================================================================
407 // Step 1: Create VkImage
408 // ========================================================================
409
410 vk::ImageCreateInfo image_info {};
411
412 switch (image->get_type()) {
414 image_info.imageType = vk::ImageType::e1D;
415 break;
418 image_info.imageType = vk::ImageType::e2D;
419 break;
421 image_info.imageType = vk::ImageType::e3D;
422 break;
423 }
424
425 image_info.extent.width = image->get_width();
426 image_info.extent.height = image->get_height();
427 image_info.extent.depth = image->get_depth();
428 image_info.mipLevels = image->get_mip_levels();
429 image_info.arrayLayers = image->get_array_layers();
430 image_info.format = image->get_format();
431 image_info.tiling = vk::ImageTiling::eOptimal;
432 image_info.initialLayout = vk::ImageLayout::eUndefined;
433 image_info.usage = image->get_usage_flags();
434 image_info.sharingMode = vk::SharingMode::eExclusive;
435 image_info.samples = vk::SampleCountFlagBits::e1; // No MSAA for now
436 image_info.flags = (image->get_type() == VKImage::Type::TYPE_CUBE)
437 ? vk::ImageCreateFlagBits::eCubeCompatible
438 : vk::ImageCreateFlags {};
439
440 vk::Image vk_image;
441 try {
442 vk_image = m_context.get_device().createImage(image_info);
443 } catch (const vk::SystemError& e) {
444 error_rethrow(
447 std::source_location::current(),
448 "Failed to create VkImage: " + std::string(e.what()));
449 }
450
451 // ========================================================================
452 // Step 2: Allocate memory
453 // ========================================================================
454
455 vk::MemoryRequirements mem_requirements;
456 mem_requirements = m_context.get_device().getImageMemoryRequirements(vk_image);
457
458 vk::MemoryAllocateInfo alloc_info {};
459 alloc_info.allocationSize = mem_requirements.size;
460 alloc_info.memoryTypeIndex = find_memory_type(
461 mem_requirements.memoryTypeBits,
462 image->get_memory_properties());
463
464 vk::DeviceMemory memory;
465 try {
466 memory = m_context.get_device().allocateMemory(alloc_info);
467 } catch (const vk::SystemError& e) {
468 m_context.get_device().destroyImage(vk_image);
469 error_rethrow(
472 std::source_location::current(),
473 "Failed to allocate VkDeviceMemory for image: " + std::string(e.what()));
474 }
475
476 // ========================================================================
477 // Step 3: Bind memory to image
478 // ========================================================================
479
480 try {
481 m_context.get_device().bindImageMemory(vk_image, memory, 0);
482 } catch (const vk::SystemError& e) {
483 m_context.get_device().freeMemory(memory);
484 m_context.get_device().destroyImage(vk_image);
485 error_rethrow(
488 std::source_location::current(),
489 "Failed to bind memory to VkImage: " + std::string(e.what()));
490 }
491
492 // ========================================================================
493 // Step 4: Create image view
494 // ========================================================================
495
496 vk::ImageViewCreateInfo view_info {};
497
498 switch (image->get_type()) {
500 view_info.viewType = (image->get_array_layers() > 1)
501 ? vk::ImageViewType::e1DArray
502 : vk::ImageViewType::e1D;
503 break;
505 view_info.viewType = (image->get_array_layers() > 1)
506 ? vk::ImageViewType::e2DArray
507 : vk::ImageViewType::e2D;
508 break;
510 view_info.viewType = vk::ImageViewType::e3D;
511 break;
513 view_info.viewType = vk::ImageViewType::eCube;
514 break;
515 }
516
517 view_info.image = vk_image;
518 view_info.format = image->get_format();
519 view_info.subresourceRange.aspectMask = image->get_aspect_flags();
520 view_info.subresourceRange.baseMipLevel = 0;
521 view_info.subresourceRange.levelCount = image->get_mip_levels();
522 view_info.subresourceRange.baseArrayLayer = 0;
523 view_info.subresourceRange.layerCount = image->get_array_layers();
524
525 view_info.components.r = vk::ComponentSwizzle::eIdentity;
526 view_info.components.g = vk::ComponentSwizzle::eIdentity;
527 view_info.components.b = vk::ComponentSwizzle::eIdentity;
528 view_info.components.a = vk::ComponentSwizzle::eIdentity;
529
530 vk::ImageView image_view;
531 try {
532 image_view = m_context.get_device().createImageView(view_info);
533 } catch (const vk::SystemError& e) {
534 m_context.get_device().freeMemory(memory);
535 m_context.get_device().destroyImage(vk_image);
536 error_rethrow(
539 std::source_location::current(),
540 "Failed to create VkImageView: " + std::string(e.what()));
541 }
542
543 // ========================================================================
544 // Step 5: Store handles in VKImage
545 // ========================================================================
546
547 VKImageResources resources {};
548 resources.image = vk_image;
549 resources.image_view = image_view;
550 resources.memory = memory;
551 resources.sampler = nullptr;
552
553 image->set_image_resources(resources);
554 image->set_current_layout(vk::ImageLayout::eUndefined);
555
557 "VKImage initialized: {}x{}x{}, format: {}, {} mips, {} layers",
558 image->get_width(), image->get_height(), image->get_depth(),
559 vk::to_string(image->get_format()),
560 image->get_mip_levels(), image->get_array_layers());
561}
562
563void BackendResourceManager::cleanup_image(const std::shared_ptr<VKImage>& image)
564{
565 if (!image || !image->is_initialized()) {
566 return;
567 }
568
569 const auto& resources = image->get_image_resources();
570
571 if (resources.image_view) {
572 m_context.get_device().destroyImageView(resources.image_view);
573 }
574
575 if (resources.image) {
576 m_context.get_device().destroyImage(resources.image);
577 }
578
579 if (resources.memory) {
580 m_context.get_device().freeMemory(resources.memory);
581 }
582
584 "VKImage cleaned up");
585}
586
588 vk::Image image,
589 vk::ImageLayout old_layout,
590 vk::ImageLayout new_layout,
591 uint32_t mip_levels,
592 uint32_t array_layers,
593 vk::ImageAspectFlags aspect_flags)
594{
595 execute_immediate_commands([&](vk::CommandBuffer cmd) {
596 vk::ImageMemoryBarrier barrier {};
597 barrier.oldLayout = old_layout;
598 barrier.newLayout = new_layout;
599 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
600 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
601 barrier.image = image;
602 barrier.subresourceRange.aspectMask = aspect_flags;
603 barrier.subresourceRange.baseMipLevel = 0;
604 barrier.subresourceRange.levelCount = mip_levels;
605 barrier.subresourceRange.baseArrayLayer = 0;
606 barrier.subresourceRange.layerCount = array_layers;
607
608 vk::PipelineStageFlags src_stage;
609 vk::PipelineStageFlags dst_stage;
610
611 if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eTransferDstOptimal) {
612 barrier.srcAccessMask = vk::AccessFlags {};
613 barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
614 src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
615 dst_stage = vk::PipelineStageFlagBits::eTransfer;
616 } else if (old_layout == vk::ImageLayout::eTransferDstOptimal && new_layout == vk::ImageLayout::eShaderReadOnlyOptimal) {
617 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
618 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
619 src_stage = vk::PipelineStageFlagBits::eTransfer;
620 dst_stage = vk::PipelineStageFlagBits::eFragmentShader;
621 } else if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eShaderReadOnlyOptimal) {
622 barrier.srcAccessMask = vk::AccessFlags {};
623 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
624 src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
625 dst_stage = vk::PipelineStageFlagBits::eFragmentShader;
626 } else if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eColorAttachmentOptimal) {
627 barrier.srcAccessMask = vk::AccessFlags {};
628 barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
629 src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
630 dst_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
631 } else if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eDepthStencilAttachmentOptimal) {
632 barrier.srcAccessMask = vk::AccessFlags {};
633 barrier.dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite;
634 src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
635 dst_stage = vk::PipelineStageFlagBits::eEarlyFragmentTests;
636 } else if (old_layout == vk::ImageLayout::eUndefined && new_layout == vk::ImageLayout::eGeneral) {
637 barrier.srcAccessMask = vk::AccessFlags {};
638 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
639 src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
640 dst_stage = vk::PipelineStageFlagBits::eComputeShader;
641 } else if (old_layout == vk::ImageLayout::eGeneral && new_layout == vk::ImageLayout::eShaderReadOnlyOptimal) {
642 barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite;
643 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
644 src_stage = vk::PipelineStageFlagBits::eComputeShader;
645 dst_stage = vk::PipelineStageFlagBits::eComputeShader;
646 } else if (old_layout == vk::ImageLayout::eShaderReadOnlyOptimal && new_layout == vk::ImageLayout::eGeneral) {
647 barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
648 barrier.dstAccessMask = vk::AccessFlagBits::eShaderWrite;
649 src_stage = vk::PipelineStageFlagBits::eComputeShader;
650 dst_stage = vk::PipelineStageFlagBits::eComputeShader;
651 } else {
652 barrier.srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
653 barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
654 src_stage = vk::PipelineStageFlagBits::eAllCommands;
655 dst_stage = vk::PipelineStageFlagBits::eAllCommands;
656
658 "Using generic image layout transition");
659 }
660
661 cmd.pipelineBarrier(
662 src_stage, dst_stage,
663 vk::DependencyFlags {},
664 0, nullptr, // Memory barriers
665 0, nullptr, // Buffer barriers
666 1, &barrier // Image barriers
667 );
668 });
669
671 "Image layout transitioned: {} -> {}",
672 vk::to_string(old_layout), vk::to_string(new_layout));
673}
674
676 std::shared_ptr<VKImage> image,
677 const void* data,
678 size_t size)
679{
680 if (!image || !data) {
682 "Invalid parameters for upload_image_data");
683 return;
684 }
685
686 auto staging = std::make_shared<Buffers::VKBuffer>(
687 size,
688 Buffers::VKBuffer::Usage::STAGING,
690
691 initialize_buffer(staging);
692
693 void* mapped = staging->get_mapped_ptr();
694 if (!mapped) {
696 "Failed to map staging buffer for image upload");
697 cleanup_buffer(staging);
698 return;
699 }
700
701 std::memcpy(mapped, data, size);
702 staging->mark_dirty_range(0, size);
703
704 auto& resources = staging->get_buffer_resources();
705 vk::MappedMemoryRange range { resources.memory, 0, VK_WHOLE_SIZE };
706
707 if (auto result = m_context.get_device().flushMappedMemoryRanges(1, &range); result != vk::Result::eSuccess) {
709 "Failed to flush mapped memory range: {}", vk::to_string(result));
710 }
711
712 execute_immediate_commands([&](vk::CommandBuffer cmd) {
713 vk::ImageMemoryBarrier barrier {};
714 barrier.oldLayout = image->get_current_layout();
715 barrier.newLayout = vk::ImageLayout::eTransferDstOptimal;
716 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
717 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
718 barrier.image = image->get_image();
719 barrier.subresourceRange.aspectMask = image->get_aspect_flags();
720 barrier.subresourceRange.baseMipLevel = 0;
721 barrier.subresourceRange.levelCount = image->get_mip_levels();
722 barrier.subresourceRange.baseArrayLayer = 0;
723 barrier.subresourceRange.layerCount = image->get_array_layers();
724 barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
725 barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
726
727 cmd.pipelineBarrier(
728 vk::PipelineStageFlagBits::eFragmentShader,
729 vk::PipelineStageFlagBits::eTransfer,
730 vk::DependencyFlags {},
731 0, nullptr, 0, nullptr, 1, &barrier);
732
733 vk::BufferImageCopy region {};
734 region.bufferOffset = 0;
735 region.bufferRowLength = 0;
736 region.bufferImageHeight = 0;
737 region.imageSubresource.aspectMask = image->get_aspect_flags();
738 region.imageSubresource.mipLevel = 0;
739 region.imageSubresource.baseArrayLayer = 0;
740 region.imageSubresource.layerCount = image->get_array_layers();
741 region.imageOffset = vk::Offset3D { 0, 0, 0 };
742 region.imageExtent = vk::Extent3D {
743 image->get_width(),
744 image->get_height(),
745 image->get_depth()
746 };
747
748 cmd.copyBufferToImage(
749 staging->get_buffer(),
750 image->get_image(),
751 vk::ImageLayout::eTransferDstOptimal,
752 1, &region);
753
754 barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
755 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
756 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
757 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
758
759 cmd.pipelineBarrier(
760 vk::PipelineStageFlagBits::eTransfer,
761 vk::PipelineStageFlagBits::eFragmentShader,
762 vk::DependencyFlags {},
763 0, nullptr, 0, nullptr, 1, &barrier);
764 });
765
766 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
767
769 "Uploaded {} bytes to image {}x{}",
770 size, image->get_width(), image->get_height());
771}
772
774 std::shared_ptr<VKImage> image,
775 const void* data,
776 size_t size,
777 const std::shared_ptr<Buffers::VKBuffer>& staging, bool deferred)
778{
779 if (!image || !data || !staging) {
781 "Invalid parameters for upload_image_data_with_staging");
782 return;
783 }
784
785 void* mapped = staging->get_mapped_ptr();
786 if (!mapped) {
788 "upload_image_data_with_staging: staging buffer has no mapped pointer");
789 return;
790 }
791
792 std::memcpy(mapped, data, size);
793 staging->mark_dirty_range(0, size);
794
795 auto& resources = staging->get_buffer_resources();
796 vk::MappedMemoryRange range { resources.memory, 0, VK_WHOLE_SIZE };
797
798 if (auto result = m_context.get_device().flushMappedMemoryRanges(1, &range);
799 result != vk::Result::eSuccess) {
801 "upload_image_data_with_staging: flush failed: {}", vk::to_string(result));
802 }
803
804 auto record_command = [&](vk::CommandBuffer cmd) {
805 vk::ImageMemoryBarrier barrier {};
806 barrier.oldLayout = image->get_current_layout();
807 barrier.newLayout = vk::ImageLayout::eTransferDstOptimal;
808 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
809 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
810 barrier.image = image->get_image();
811 barrier.subresourceRange = {
812 image->get_aspect_flags(), 0,
813 image->get_mip_levels(), 0,
814 image->get_array_layers()
815 };
816 barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
817 barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
818
819 cmd.pipelineBarrier(
820 vk::PipelineStageFlagBits::eFragmentShader,
821 vk::PipelineStageFlagBits::eTransfer,
822 {}, 0, nullptr, 0, nullptr, 1, &barrier);
823
824 vk::BufferImageCopy region {};
825 region.imageSubresource.aspectMask = image->get_aspect_flags();
826 region.imageSubresource.layerCount = image->get_array_layers();
827 region.imageOffset = vk::Offset3D { 0, 0, 0 };
828 region.imageExtent = vk::Extent3D {
829 image->get_width(),
830 image->get_height(),
831 image->get_depth()
832 };
833
834 cmd.copyBufferToImage(
835 staging->get_buffer(),
836 image->get_image(),
837 vk::ImageLayout::eTransferDstOptimal,
838 1, &region);
839
840 barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
841 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
842 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
843 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
844
845 cmd.pipelineBarrier(
846 vk::PipelineStageFlagBits::eTransfer,
847 vk::PipelineStageFlagBits::eFragmentShader,
848 {}, 0, nullptr, 0, nullptr, 1, &barrier);
849 };
850
851 if (deferred) {
852 record_deferred_commands(record_command);
853 } else {
854 execute_immediate_commands(record_command);
855 }
856
857 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
858
860 "upload_image_data_with_staging: {} bytes to image {}x{}",
861 size, image->get_width(), image->get_height());
862}
863
865 std::shared_ptr<VKImage> image,
866 void* data,
867 size_t size,
868 const std::shared_ptr<Buffers::VKBuffer>& staging,
869 bool deferred,
870 vk::ImageLayout restore_layout,
871 vk::PipelineStageFlags restore_stage)
872{
873 if (!image || !data) {
875 "download_image_data: invalid parameters");
876 return;
877 }
878
879 std::shared_ptr<Buffers::VKBuffer> local_staging;
880 Buffers::VKBuffer* active_staging {};
881 if (!staging) {
882 local_staging = std::make_shared<Buffers::VKBuffer>(
883 size,
884 Buffers::VKBuffer::Usage::STAGING,
886 initialize_buffer(local_staging);
887 active_staging = local_staging.get();
888 } else {
889 active_staging = staging.get();
890 }
891
892 auto record_command = [&](vk::CommandBuffer cmd) {
893 vk::ImageMemoryBarrier barrier {};
894 barrier.oldLayout = image->get_current_layout();
895 barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal;
896 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
897 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
898 barrier.image = image->get_image();
899 barrier.subresourceRange.aspectMask = image->get_aspect_flags();
900 barrier.subresourceRange.baseMipLevel = 0;
901 barrier.subresourceRange.levelCount = image->get_mip_levels();
902 barrier.subresourceRange.baseArrayLayer = 0;
903 barrier.subresourceRange.layerCount = image->get_array_layers();
904 barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eShaderRead;
905 barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead;
906
907 cmd.pipelineBarrier(
908 vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eComputeShader,
909 vk::PipelineStageFlagBits::eTransfer,
910 vk::DependencyFlags {}, {}, {}, barrier);
911
912 vk::BufferImageCopy region {};
913 region.bufferOffset = 0;
914 region.bufferRowLength = 0;
915 region.bufferImageHeight = 0;
916 region.imageSubresource.aspectMask = image->get_aspect_flags();
917 region.imageSubresource.mipLevel = 0;
918 region.imageSubresource.baseArrayLayer = 0;
919 region.imageSubresource.layerCount = image->get_array_layers();
920 region.imageOffset = vk::Offset3D { 0, 0, 0 };
921 region.imageExtent = vk::Extent3D {
922 image->get_width(),
923 image->get_height(),
924 image->get_depth()
925 };
926
927 cmd.copyImageToBuffer(
928 image->get_image(),
929 vk::ImageLayout::eTransferSrcOptimal,
930 active_staging->get_buffer(),
931 1, &region);
932
933 barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal;
934 barrier.newLayout = restore_layout;
935 barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead;
936 barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
937
938 cmd.pipelineBarrier(
939 vk::PipelineStageFlagBits::eTransfer,
940 restore_stage,
941 vk::DependencyFlags {}, {}, {}, barrier);
942 };
943
944 if (deferred && staging) {
945 record_deferred_commands(record_command);
946 image->set_current_layout(restore_layout);
947 return;
948 }
949
950 auto device = m_context.get_device();
951
953 record_command(cmd);
954 cmd.end();
955
956 vk::FenceCreateInfo fence_info {};
957 vk::Fence fence = device.createFence(fence_info);
958
959 vk::SubmitInfo submit_info {};
960 submit_info.commandBufferCount = 1;
961 submit_info.pCommandBuffers = &cmd;
962
963 if (auto result = m_context.get_graphics_queue().submit(1, &submit_info, fence);
964 result != vk::Result::eSuccess) {
966 "download_image_data: queue submit failed: {}", vk::to_string(result));
967 device.destroyFence(fence);
969 if (local_staging)
970 cleanup_buffer(local_staging);
971 return;
972 }
973
974 image->set_current_layout(restore_layout);
975
976 if (auto result = device.waitForFences(1, &fence, VK_TRUE, UINT64_MAX);
977 result != vk::Result::eSuccess) {
979 "download_image_data: waitForFences failed: {}", vk::to_string(result));
980 }
981
982 vk::MappedMemoryRange range { active_staging->get_buffer_resources().memory, 0, VK_WHOLE_SIZE };
983 if (auto result = device.invalidateMappedMemoryRanges(1, &range);
984 result != vk::Result::eSuccess) {
986 "download_image_data: invalidate failed: {}", vk::to_string(result));
987 }
988
989 void* mapped = active_staging->get_mapped_ptr();
990 if (mapped) {
991 std::memcpy(data, mapped, size);
992 } else {
994 "download_image_data: staging buffer has no mapped pointer");
995 }
996
997 device.destroyFence(fence);
999
1000 if (local_staging)
1001 cleanup_buffer(local_staging);
1002
1004 "download_image_data: {} bytes from image {}x{}",
1005 size, image->get_width(), image->get_height());
1006}
1007
1009 vk::Filter filter,
1010 vk::SamplerAddressMode address_mode,
1011 float max_anisotropy)
1012{
1013 size_t hash = 0;
1014 auto hash_combine = [](size_t& seed, size_t value) {
1015 seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2);
1016 };
1017
1018 hash_combine(hash, static_cast<size_t>(filter));
1019 hash_combine(hash, static_cast<size_t>(address_mode));
1020 hash_combine(hash, std::hash<float> {}(max_anisotropy));
1021
1022 auto it = m_sampler_cache.find(hash);
1023 if (it != m_sampler_cache.end()) {
1025 "Reusing cached sampler (hash: 0x{:X})", hash);
1026 return it->second;
1027 }
1028
1029 vk::SamplerCreateInfo sampler_info;
1030 sampler_info.magFilter = filter;
1031 sampler_info.minFilter = filter;
1032 sampler_info.mipmapMode = vk::SamplerMipmapMode::eLinear;
1033 sampler_info.addressModeU = address_mode;
1034 sampler_info.addressModeV = address_mode;
1035 sampler_info.addressModeW = address_mode;
1036 sampler_info.mipLodBias = 0.0F;
1037 sampler_info.anisotropyEnable = max_anisotropy > 0.0F;
1038 sampler_info.maxAnisotropy = max_anisotropy;
1039 sampler_info.compareEnable = VK_FALSE;
1040 sampler_info.compareOp = vk::CompareOp::eAlways;
1041 sampler_info.minLod = 0.0F;
1042 sampler_info.maxLod = VK_LOD_CLAMP_NONE;
1043 sampler_info.borderColor = vk::BorderColor::eFloatOpaqueBlack;
1044 sampler_info.unnormalizedCoordinates = VK_FALSE;
1045
1046 vk::Sampler sampler;
1047 try {
1048 sampler = m_context.get_device().createSampler(sampler_info);
1049 } catch (const vk::SystemError& e) {
1051 "Failed to create sampler: {}", e.what());
1052 return nullptr;
1053 }
1054
1055 m_sampler_cache[hash] = sampler;
1056
1058 "Created sampler (filter: {}, address: {}, anisotropy: {}, hash: 0x{:X})",
1059 vk::to_string(filter), vk::to_string(address_mode), max_anisotropy, hash);
1060
1061 return sampler;
1062}
1063
1065{
1066 if (!sampler) {
1067 return;
1068 }
1069
1070 for (auto it = m_sampler_cache.begin(); it != m_sampler_cache.end(); ++it) {
1071 if (it->second == sampler) {
1072 m_sampler_cache.erase(it);
1073 break;
1074 }
1075 }
1076
1077 m_context.get_device().destroySampler(sampler);
1078
1080 "Destroyed sampler");
1081}
1082
1083uint32_t BackendResourceManager::find_memory_type(uint32_t type_filter, vk::MemoryPropertyFlags properties) const
1084{
1085 vk::PhysicalDeviceMemoryProperties mem_properties;
1086 mem_properties = m_context.get_physical_device().getMemoryProperties();
1087
1088 for (uint32_t i = 0; i < mem_properties.memoryTypeCount; i++) {
1089 if ((type_filter & (1 << i)) && (mem_properties.memoryTypes[i].propertyFlags & properties) == properties) {
1090 return i;
1091 }
1092 }
1093
1094 error<std::runtime_error>(
1097 std::source_location::current(),
1098 "Failed to find suitable memory type");
1099
1100 return 0;
1101}
1102
1103void BackendResourceManager::execute_immediate_commands(const std::function<void(vk::CommandBuffer)>& recorder)
1104{
1105 vk::CommandBuffer cmd = m_command_manager.begin_single_time_commands();
1106 recorder(cmd);
1108}
1109
1110void BackendResourceManager::record_deferred_commands(const std::function<void(vk::CommandBuffer)>& recorder)
1111{
1113}
1114
1116{
1118 return nullptr;
1119
1120 vk::CommandBuffer cmd = m_command_manager.get_deferred_cmd();
1121 cmd.end();
1122
1123 if (!m_deferred_semaphore) {
1124 vk::SemaphoreCreateInfo sem_info {};
1125 m_deferred_semaphore = m_context.get_device().createSemaphore(sem_info);
1126 }
1127
1128 vk::SubmitInfo submit {};
1129 submit.commandBufferCount = 1;
1130 submit.pCommandBuffers = &cmd;
1131 submit.signalSemaphoreCount = 1;
1132 submit.pSignalSemaphores = &m_deferred_semaphore;
1133
1134 if (m_context.get_graphics_queue().submit(1, &submit, nullptr) != vk::Result::eSuccess) {
1136 "Failed to submit deferred command buffer");
1137 }
1138
1140
1141 return m_deferred_semaphore;
1142}
1143
1145{
1146 for (auto& [hash, sampler] : m_sampler_cache) {
1147 if (sampler) {
1148 m_context.get_device().destroySampler(sampler);
1149 }
1150 }
1151 m_sampler_cache.clear();
1152
1154 m_context.get_device().destroySemaphore(m_deferred_semaphore);
1155 m_deferred_semaphore = nullptr;
1156 }
1157
1158 for (auto& buffer : m_managed_buffers) {
1159 if (buffer && buffer->is_initialized()) {
1160 cleanup_buffer(buffer);
1161 }
1162 }
1163 m_managed_buffers.clear();
1164}
1165
1166size_t BackendResourceManager::compute_sampler_hash(vk::Filter filter, vk::SamplerAddressMode address_mode, float max_anisotropy) const
1167{
1168 size_t hash = 0;
1169 auto hash_combine = [](size_t& seed, size_t value) {
1170 seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2);
1171 };
1172
1173 hash_combine(hash, static_cast<size_t>(filter));
1174 hash_combine(hash, static_cast<size_t>(address_mode));
1175 hash_combine(hash, std::hash<float> {}(max_anisotropy));
1176
1177 return hash;
1178}
1179
1180}
#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:64
float value
float offset
Vulkan-backed buffer wrapper used in processing chains.
Definition VKBuffer.hpp:76
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 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.
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 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.
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:111
Vulkan image resource handles.
Definition VKImage.hpp:15