MayaFlux 1.0.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);
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);
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.buffer) {
316 m_context.get_device().destroyBuffer(res.buffer);
317 }
318
319 if (res.memory) {
320 m_context.get_device().freeMemory(res.memory);
321 }
322
324 "VKBuffer cleaned up: {:p}", static_cast<void*>(res.buffer));
325
326 res.mapped_ptr = nullptr;
327 res.buffer = vk::Buffer {};
328 res.memory = vk::DeviceMemory {};
329
330 m_managed_buffers.erase(it);
331}
332
334{
335 for (auto& buffer_wrapper : m_managed_buffers) {
336 auto& resources = buffer_wrapper->get_buffer_resources();
337 auto dirty_ranges = buffer_wrapper->get_and_clear_dirty_ranges();
338 if (!dirty_ranges.empty()) {
339 for (auto [offset, size] : dirty_ranges) {
340 vk::MappedMemoryRange range;
341 range.memory = resources.memory;
342 range.offset = offset;
343 range.size = size;
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));
347 }
348 }
350 "Flushed {} dirty ranges for buffer {:p}", dirty_ranges.size(),
351 (void*)buffer_wrapper->get_buffer());
352 }
353
354 auto invalid_ranges = buffer_wrapper->get_and_clear_invalid_ranges();
355 if (!invalid_ranges.empty()) {
356 for (auto [offset, size] : invalid_ranges) {
357 vk::MappedMemoryRange range;
358 range.memory = buffer_wrapper->get_buffer_resources().memory;
359 range.offset = offset;
360 range.size = size;
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));
364 }
365 }
367 "Invalidated {} ranges for buffer {:p}", invalid_ranges.size(),
368 (void*)buffer_wrapper->get_buffer());
369 }
370 }
371}
372
374 const std::shared_ptr<Buffers::VKBuffer>& buffer) const
375{
376 if (!buffer || !buffer->is_initialized()) {
378 "get_buffer_device_address: buffer not initialized");
379 return 0;
380 }
381
382 vk::BufferDeviceAddressInfo info {};
383 info.buffer = buffer->get_buffer();
384 // return static_cast<uint64_t>(m_context.get_device().getBufferAddress(info));
385 return m_context.get_device().getBufferAddress(info);
386}
387
388void BackendResourceManager::initialize_image(const std::shared_ptr<VKImage>& image)
389{
390 if (!image) {
392 "Attempted to initialize null VKImage");
393 return;
394 }
395
396 if (image->is_initialized()) {
398 "VKImage already initialized, skipping");
399 return;
400 }
401
402 // ========================================================================
403 // Step 1: Create VkImage
404 // ========================================================================
405
406 vk::ImageCreateInfo image_info {};
407
408 switch (image->get_type()) {
410 image_info.imageType = vk::ImageType::e1D;
411 break;
414 image_info.imageType = vk::ImageType::e2D;
415 break;
417 image_info.imageType = vk::ImageType::e3D;
418 break;
419 }
420
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; // No MSAA for now
432 image_info.flags = (image->get_type() == VKImage::Type::TYPE_CUBE)
433 ? vk::ImageCreateFlagBits::eCubeCompatible
434 : vk::ImageCreateFlags {};
435
436 vk::Image vk_image;
437 try {
438 vk_image = m_context.get_device().createImage(image_info);
439 } catch (const vk::SystemError& e) {
440 error_rethrow(
443 std::source_location::current(),
444 "Failed to create VkImage: " + std::string(e.what()));
445 }
446
447 // ========================================================================
448 // Step 2: Allocate memory
449 // ========================================================================
450
451 vk::MemoryRequirements mem_requirements;
452 mem_requirements = m_context.get_device().getImageMemoryRequirements(vk_image);
453
454 vk::MemoryAllocateInfo alloc_info {};
455 alloc_info.allocationSize = mem_requirements.size;
456 alloc_info.memoryTypeIndex = find_memory_type(
457 mem_requirements.memoryTypeBits,
458 image->get_memory_properties());
459
460 vk::DeviceMemory memory;
461 try {
462 memory = m_context.get_device().allocateMemory(alloc_info);
463 } catch (const vk::SystemError& e) {
464 m_context.get_device().destroyImage(vk_image);
465 error_rethrow(
468 std::source_location::current(),
469 "Failed to allocate VkDeviceMemory for image: " + std::string(e.what()));
470 }
471
472 // ========================================================================
473 // Step 3: Bind memory to image
474 // ========================================================================
475
476 try {
477 m_context.get_device().bindImageMemory(vk_image, memory, 0);
478 } catch (const vk::SystemError& e) {
479 m_context.get_device().freeMemory(memory);
480 m_context.get_device().destroyImage(vk_image);
481 error_rethrow(
484 std::source_location::current(),
485 "Failed to bind memory to VkImage: " + std::string(e.what()));
486 }
487
488 // ========================================================================
489 // Step 4: Create image view
490 // ========================================================================
491
492 vk::ImageViewCreateInfo view_info {};
493
494 switch (image->get_type()) {
496 view_info.viewType = (image->get_array_layers() > 1)
497 ? vk::ImageViewType::e1DArray
498 : vk::ImageViewType::e1D;
499 break;
501 view_info.viewType = (image->get_array_layers() > 1)
502 ? vk::ImageViewType::e2DArray
503 : vk::ImageViewType::e2D;
504 break;
506 view_info.viewType = vk::ImageViewType::e3D;
507 break;
509 view_info.viewType = vk::ImageViewType::eCube;
510 break;
511 }
512
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();
520
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;
525
526 vk::ImageView image_view;
527 try {
528 image_view = m_context.get_device().createImageView(view_info);
529 } catch (const vk::SystemError& e) {
530 m_context.get_device().freeMemory(memory);
531 m_context.get_device().destroyImage(vk_image);
532 error_rethrow(
535 std::source_location::current(),
536 "Failed to create VkImageView: " + std::string(e.what()));
537 }
538
539 // ========================================================================
540 // Step 5: Store handles in VKImage
541 // ========================================================================
542
543 VKImageResources resources {};
544 resources.image = vk_image;
545 resources.image_view = image_view;
546 resources.memory = memory;
547 resources.sampler = nullptr;
548
549 image->set_image_resources(resources);
550 image->set_current_layout(vk::ImageLayout::eUndefined);
551
553 "VKImage initialized: {}x{}x{}, format: {}, {} mips, {} layers",
554 image->get_width(), image->get_height(), image->get_depth(),
555 vk::to_string(image->get_format()),
556 image->get_mip_levels(), image->get_array_layers());
557}
558
559void BackendResourceManager::cleanup_image(const std::shared_ptr<VKImage>& image)
560{
561 if (!image || !image->is_initialized()) {
562 return;
563 }
564
565 const auto& resources = image->get_image_resources();
566
567 if (resources.image_view) {
568 m_context.get_device().destroyImageView(resources.image_view);
569 }
570
571 if (resources.image) {
572 m_context.get_device().destroyImage(resources.image);
573 }
574
575 if (resources.memory) {
576 m_context.get_device().freeMemory(resources.memory);
577 }
578
579 image->set_image_resources(VKImageResources {});
580
582 "VKImage cleaned up");
583}
584
586 vk::Image image,
587 vk::ImageLayout old_layout,
588 vk::ImageLayout new_layout,
589 uint32_t mip_levels,
590 uint32_t array_layers,
591 vk::ImageAspectFlags aspect_flags)
592{
593 execute_immediate_commands([&](vk::CommandBuffer cmd) {
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;
605
606 vk::PipelineStageFlags src_stage;
607 vk::PipelineStageFlags dst_stage;
608
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;
649 } else {
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;
654
656 "Using generic image layout transition");
657 }
658
659 cmd.pipelineBarrier(
660 src_stage, dst_stage,
661 vk::DependencyFlags {},
662 0, nullptr, // Memory barriers
663 0, nullptr, // Buffer barriers
664 1, &barrier // Image barriers
665 );
666 });
667
669 "Image layout transitioned: {} -> {}",
670 vk::to_string(old_layout), vk::to_string(new_layout));
671}
672
674 std::shared_ptr<VKImage> image,
675 const void* data,
676 size_t size)
677{
678 if (!image || !data) {
680 "Invalid parameters for upload_image_data");
681 return;
682 }
683
684 auto staging = std::make_shared<Buffers::VKBuffer>(
685 size,
686 Buffers::VKBuffer::Usage::STAGING,
688
689 initialize_buffer(staging);
690
691 void* mapped = staging->get_mapped_ptr();
692 if (!mapped) {
694 "Failed to map staging buffer for image upload");
695 cleanup_buffer(staging);
696 return;
697 }
698
699 std::memcpy(mapped, data, size);
700 staging->mark_dirty_range(0, size);
701
702 auto& resources = staging->get_buffer_resources();
703 vk::MappedMemoryRange range { resources.memory, 0, VK_WHOLE_SIZE };
704
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));
708 }
709
710 execute_immediate_commands([&](vk::CommandBuffer cmd) {
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;
724
725 cmd.pipelineBarrier(
726 vk::PipelineStageFlagBits::eFragmentShader,
727 vk::PipelineStageFlagBits::eTransfer,
728 vk::DependencyFlags {},
729 0, nullptr, 0, nullptr, 1, &barrier);
730
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 {
741 image->get_width(),
742 image->get_height(),
743 image->get_depth()
744 };
745
746 cmd.copyBufferToImage(
747 staging->get_buffer(),
748 image->get_image(),
749 vk::ImageLayout::eTransferDstOptimal,
750 1, &region);
751
752 barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
753 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
754 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
755 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
756
757 cmd.pipelineBarrier(
758 vk::PipelineStageFlagBits::eTransfer,
759 vk::PipelineStageFlagBits::eFragmentShader,
760 vk::DependencyFlags {},
761 0, nullptr, 0, nullptr, 1, &barrier);
762 });
763
764 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
765
767 "Uploaded {} bytes to image {}x{}",
768 size, image->get_width(), image->get_height());
769}
770
772 std::shared_ptr<VKImage> image,
773 const void* data,
774 size_t size,
775 const std::shared_ptr<Buffers::VKBuffer>& staging, bool deferred)
776{
777 if (!image || !data || !staging) {
779 "Invalid parameters for upload_image_data_with_staging");
780 return;
781 }
782
783 void* mapped = staging->get_mapped_ptr();
784 if (!mapped) {
786 "upload_image_data_with_staging: staging buffer has no mapped pointer");
787 return;
788 }
789
790 std::memcpy(mapped, data, size);
791 staging->mark_dirty_range(0, size);
792
793 auto& resources = staging->get_buffer_resources();
794 vk::MappedMemoryRange range { resources.memory, 0, VK_WHOLE_SIZE };
795
796 if (auto result = m_context.get_device().flushMappedMemoryRanges(1, &range);
797 result != vk::Result::eSuccess) {
799 "upload_image_data_with_staging: flush failed: {}", vk::to_string(result));
800 }
801
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()
813 };
814 barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
815 barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
816
817 cmd.pipelineBarrier(
818 vk::PipelineStageFlagBits::eFragmentShader,
819 vk::PipelineStageFlagBits::eTransfer,
820 {}, 0, nullptr, 0, nullptr, 1, &barrier);
821
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 {
827 image->get_width(),
828 image->get_height(),
829 image->get_depth()
830 };
831
832 cmd.copyBufferToImage(
833 staging->get_buffer(),
834 image->get_image(),
835 vk::ImageLayout::eTransferDstOptimal,
836 1, &region);
837
838 barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
839 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
840 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
841 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
842
843 cmd.pipelineBarrier(
844 vk::PipelineStageFlagBits::eTransfer,
845 vk::PipelineStageFlagBits::eFragmentShader,
846 {}, 0, nullptr, 0, nullptr, 1, &barrier);
847 };
848
849 if (deferred) {
850 record_deferred_commands(record_command);
851 } else {
852 execute_immediate_commands(record_command);
853 }
854
855 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
856
858 "upload_image_data_with_staging: {} bytes to image {}x{}",
859 size, image->get_width(), image->get_height());
860}
861
863 std::shared_ptr<VKImage> image,
864 void* data,
865 size_t size,
866 const std::shared_ptr<Buffers::VKBuffer>& staging,
867 bool deferred,
868 vk::ImageLayout restore_layout,
869 vk::PipelineStageFlags restore_stage)
870{
871 if (!image || !data) {
873 "download_image_data: invalid parameters");
874 return;
875 }
876
877 std::shared_ptr<Buffers::VKBuffer> local_staging;
878 Buffers::VKBuffer* active_staging {};
879 if (!staging) {
880 local_staging = std::make_shared<Buffers::VKBuffer>(
881 size,
882 Buffers::VKBuffer::Usage::STAGING,
884 initialize_buffer(local_staging);
885 active_staging = local_staging.get();
886 } else {
887 active_staging = staging.get();
888 }
889
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;
904
905 cmd.pipelineBarrier(
906 vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eComputeShader,
907 vk::PipelineStageFlagBits::eTransfer,
908 vk::DependencyFlags {}, {}, {}, barrier);
909
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 {
920 image->get_width(),
921 image->get_height(),
922 image->get_depth()
923 };
924
925 cmd.copyImageToBuffer(
926 image->get_image(),
927 vk::ImageLayout::eTransferSrcOptimal,
928 active_staging->get_buffer(),
929 1, &region);
930
931 barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal;
932 barrier.newLayout = restore_layout;
933 barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead;
934 barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
935
936 cmd.pipelineBarrier(
937 vk::PipelineStageFlagBits::eTransfer,
938 restore_stage,
939 vk::DependencyFlags {}, {}, {}, barrier);
940 };
941
942 if (deferred && staging) {
943 record_deferred_commands(record_command);
944 image->set_current_layout(restore_layout);
945 return;
946 }
947
948 auto device = m_context.get_device();
949
951 record_command(cmd);
952 cmd.end();
953
954 vk::FenceCreateInfo fence_info {};
955 vk::Fence fence = device.createFence(fence_info);
956
957 vk::SubmitInfo submit_info {};
958 submit_info.commandBufferCount = 1;
959 submit_info.pCommandBuffers = &cmd;
960
961 if (auto result = m_context.get_graphics_queue().submit(1, &submit_info, fence);
962 result != vk::Result::eSuccess) {
964 "download_image_data: queue submit failed: {}", vk::to_string(result));
965 device.destroyFence(fence);
967 if (local_staging)
968 cleanup_buffer(local_staging);
969 return;
970 }
971
972 image->set_current_layout(restore_layout);
973
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));
978 }
979
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));
985 }
986
987 void* mapped = active_staging->get_mapped_ptr();
988 if (mapped) {
989 std::memcpy(data, mapped, size);
990 } else {
992 "download_image_data: staging buffer has no mapped pointer");
993 }
994
995 device.destroyFence(fence);
997
998 if (local_staging)
999 cleanup_buffer(local_staging);
1000
1002 "download_image_data: {} bytes from image {}x{}",
1003 size, image->get_width(), image->get_height());
1004}
1005
1007 const std::shared_ptr<VKImage>& image,
1008 uint32_t layer,
1009 const void* data,
1010 size_t size,
1011 const std::shared_ptr<Buffers::VKBuffer>& staging,
1012 bool deferred)
1013{
1014 if (!image || !data || layer >= image->get_array_layers()) {
1016 "upload_image_layer: invalid image, data or layer");
1017 return false;
1018 }
1019
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);
1024 return false;
1025 }
1026
1027 if (deferred && !staging) {
1029 "upload_image_layer: a deferred upload needs a caller staging buffer");
1030 return false;
1031 }
1032
1033 std::shared_ptr<Buffers::VKBuffer> local_staging;
1034 if (!staging) {
1035 local_staging = std::make_shared<Buffers::VKBuffer>(
1036 size,
1037 Buffers::VKBuffer::Usage::STAGING,
1039 initialize_buffer(local_staging);
1040 }
1041 const auto& active_staging = staging ? staging : local_staging;
1042
1043 void* mapped = active_staging->get_mapped_ptr();
1044 if (!mapped) {
1046 "upload_image_layer: staging buffer has no mapped pointer");
1047 if (local_staging) {
1048 cleanup_buffer(local_staging);
1049 }
1050 return false;
1051 }
1052
1053 std::memcpy(mapped, data, size);
1054 active_staging->mark_dirty_range(0, size);
1055
1056 vk::MappedMemoryRange range { active_staging->get_buffer_resources().memory, 0, VK_WHOLE_SIZE };
1057 if (auto result = m_context.get_device().flushMappedMemoryRanges(1, &range);
1058 result != vk::Result::eSuccess) {
1060 "upload_image_layer: flush failed: {}", vk::to_string(result));
1061 }
1062
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()
1074 };
1075 barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
1076 barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
1077
1078 cmd.pipelineBarrier(
1079 vk::PipelineStageFlagBits::eFragmentShader,
1080 vk::PipelineStageFlagBits::eTransfer,
1081 vk::DependencyFlags {}, {}, {}, barrier);
1082
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 {
1087 image->get_width(),
1088 image->get_height(),
1089 image->get_depth()
1090 };
1091
1092 cmd.copyBufferToImage(
1093 active_staging->get_buffer(),
1094 image->get_image(),
1095 vk::ImageLayout::eTransferDstOptimal,
1096 1, &region);
1097
1098 barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
1099 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
1100 barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
1101 barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
1102
1103 cmd.pipelineBarrier(
1104 vk::PipelineStageFlagBits::eTransfer,
1105 vk::PipelineStageFlagBits::eFragmentShader,
1106 vk::DependencyFlags {}, {}, {}, barrier);
1107 };
1108
1109 if (deferred) {
1110 record_deferred_commands(record_command);
1111 } else {
1112 execute_immediate_commands(record_command);
1113 }
1114
1115 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
1116
1117 if (local_staging) {
1118 cleanup_buffer(local_staging);
1119 }
1120
1121 return true;
1122}
1123
1125 vk::Format src_format, vk::Format dst_format, vk::Filter filter) const
1126{
1127 const auto physical = m_context.get_physical_device();
1128 const auto src_features = physical.getFormatProperties(src_format).optimalTilingFeatures;
1129 const auto dst_features = physical.getFormatProperties(dst_format).optimalTilingFeatures;
1130
1131 if (!(src_features & vk::FormatFeatureFlagBits::eBlitSrc)
1132 || !(dst_features & vk::FormatFeatureFlagBits::eBlitDst)) {
1133 return false;
1134 }
1135
1136 return filter != vk::Filter::eLinear
1137 || static_cast<bool>(src_features & vk::FormatFeatureFlagBits::eSampledImageFilterLinear);
1138}
1139
1141 const std::shared_ptr<VKImage>& src,
1142 const std::shared_ptr<VKImage>& dst,
1143 const vk::ImageBlit& region,
1144 vk::Filter filter,
1145 bool deferred)
1146{
1147 if (!src || !dst || src == dst) {
1149 "blit_image: source and destination must be two distinct initialised images");
1150 return false;
1151 }
1152
1153 if (!supports_blit(src->get_format(), dst->get_format(), filter)) {
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));
1157 return false;
1158 }
1159
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;
1166 };
1167 const auto src_restore = settled_layout(src);
1168 const auto dst_restore = settled_layout(dst);
1169
1170 auto record_command = [&](vk::CommandBuffer cmd) {
1171 const auto transition = [&cmd](
1172 const std::shared_ptr<VKImage>& image,
1173 vk::ImageLayout from,
1174 vk::ImageLayout to,
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()
1189 };
1190 barrier.srcAccessMask = src_access;
1191 barrier.dstAccessMask = dst_access;
1192 cmd.pipelineBarrier(src_stage, dst_stage, vk::DependencyFlags {}, {}, {}, barrier);
1193 };
1194
1195 constexpr auto any_access = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
1196
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);
1203
1204 cmd.blitImage(
1205 src->get_image(), vk::ImageLayout::eTransferSrcOptimal,
1206 dst->get_image(), vk::ImageLayout::eTransferDstOptimal,
1207 1, &region, filter);
1208
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);
1215 };
1216
1217 if (deferred) {
1218 record_deferred_commands(record_command);
1219 } else {
1220 execute_immediate_commands(record_command);
1221 }
1222
1223 src->set_current_layout(src_restore);
1224 dst->set_current_layout(dst_restore);
1225
1226 return true;
1227}
1228
1230 vk::Filter filter,
1231 vk::SamplerAddressMode address_mode,
1232 float max_anisotropy)
1233{
1234 size_t hash = 0;
1235 auto hash_combine = [](size_t& seed, size_t value) {
1236 seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2);
1237 };
1238
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));
1242
1243 auto it = m_sampler_cache.find(hash);
1244 if (it != m_sampler_cache.end()) {
1246 "Reusing cached sampler (hash: 0x{:X})", hash);
1247 return it->second;
1248 }
1249
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;
1266
1267 vk::Sampler sampler;
1268 try {
1269 sampler = m_context.get_device().createSampler(sampler_info);
1270 } catch (const vk::SystemError& e) {
1272 "Failed to create sampler: {}", e.what());
1273 return nullptr;
1274 }
1275
1276 m_sampler_cache[hash] = sampler;
1277
1279 "Created sampler (filter: {}, address: {}, anisotropy: {}, hash: 0x{:X})",
1280 vk::to_string(filter), vk::to_string(address_mode), max_anisotropy, hash);
1281
1282 return sampler;
1283}
1284
1286{
1287 if (!sampler) {
1288 return;
1289 }
1290
1291 for (auto it = m_sampler_cache.begin(); it != m_sampler_cache.end(); ++it) {
1292 if (it->second == sampler) {
1293 m_sampler_cache.erase(it);
1294 break;
1295 }
1296 }
1297
1298 m_context.get_device().destroySampler(sampler);
1299
1301 "Destroyed sampler");
1302}
1303
1304uint32_t BackendResourceManager::find_memory_type(uint32_t type_filter, vk::MemoryPropertyFlags properties) const
1305{
1306 vk::PhysicalDeviceMemoryProperties mem_properties;
1307 mem_properties = m_context.get_physical_device().getMemoryProperties();
1308
1309 for (uint32_t i = 0; i < mem_properties.memoryTypeCount; i++) {
1310 if ((type_filter & (1 << i)) && (mem_properties.memoryTypes[i].propertyFlags & properties) == properties) {
1311 return i;
1312 }
1313 }
1314
1315 error<std::runtime_error>(
1318 std::source_location::current(),
1319 "Failed to find suitable memory type");
1320
1321 return 0;
1322}
1323
1324void BackendResourceManager::execute_immediate_commands(const std::function<void(vk::CommandBuffer)>& recorder)
1325{
1326 vk::CommandBuffer cmd = m_command_manager.begin_single_time_commands();
1327 recorder(cmd);
1329}
1330
1331void BackendResourceManager::record_deferred_commands(const std::function<void(vk::CommandBuffer)>& recorder)
1332{
1334}
1335
1337{
1339 return nullptr;
1340
1341 vk::CommandBuffer cmd = m_command_manager.get_deferred_cmd();
1342 cmd.end();
1343
1344 if (!m_deferred_semaphore) {
1345 vk::SemaphoreCreateInfo sem_info {};
1346 m_deferred_semaphore = m_context.get_device().createSemaphore(sem_info);
1347 }
1348
1349 vk::SubmitInfo submit {};
1350 submit.commandBufferCount = 1;
1351 submit.pCommandBuffers = &cmd;
1352 submit.signalSemaphoreCount = 1;
1353 submit.pSignalSemaphores = &m_deferred_semaphore;
1354
1355 if (m_context.get_graphics_queue().submit(1, &submit, nullptr) != vk::Result::eSuccess) {
1357 "Failed to submit deferred command buffer");
1358 }
1359
1361
1362 return m_deferred_semaphore;
1363}
1364
1366{
1367 for (auto& [hash, sampler] : m_sampler_cache) {
1368 if (sampler) {
1369 m_context.get_device().destroySampler(sampler);
1370 }
1371 }
1372 m_sampler_cache.clear();
1373
1375 m_context.get_device().destroySemaphore(m_deferred_semaphore);
1376 m_deferred_semaphore = nullptr;
1377 }
1378
1379 const auto device = m_context.get_device();
1380 auto release_slots = [&device](Buffers::VKBuffer& owner) {
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);
1385 }
1386 if (slot.buffer) {
1387 device.destroyBuffer(slot.buffer);
1388 }
1389 if (slot.memory) {
1390 device.freeMemory(slot.memory);
1391 }
1392 }
1393 slots.clear();
1394 };
1395
1396 auto buffers = m_managed_buffers;
1397 for (const auto& buffer : buffers) {
1398 if (!buffer) {
1399 continue;
1400 }
1401 release_slots(*buffer);
1402 if (buffer->is_initialized()) {
1404 }
1405 }
1406 m_managed_buffers.clear();
1407}
1408
1409size_t BackendResourceManager::compute_sampler_hash(vk::Filter filter, vk::SamplerAddressMode address_mode, float max_anisotropy) const
1410{
1411 size_t hash = 0;
1412 auto hash_combine = [](size_t& seed, size_t value) {
1413 seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2);
1414 };
1415
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));
1419
1420 return hash;
1421}
1422
1423}
#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
uint64_t offset
Definition Composite.cpp:10
IO::ImageData image
Definition Decoder.cpp:47
bool size
std::shared_ptr< NetworkGeometryBuffer > buffer
uint32_t * dst
const uint32_t * src
uint16_t usage
glm::vec3 value
vk::PhysicalDevice device
Definition VKDevice.cpp:121
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)
bool blit_image(const std::shared_ptr< VKImage > &src, const std::shared_ptr< VKImage > &dst, const vk::ImageBlit &region, 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.
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.
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:102
Vulkan image resource handles.
Definition VKImage.hpp:15