HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
gfx_device_vulkan_intf.hpp
1// Copyright Take Vos 2019-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "gfx_system_globals.hpp"
8#include "gfx_queue_vulkan.hpp"
9#include "gfx_pipeline_image_vulkan_intf.hpp"
10#include "gfx_pipeline_box_vulkan_intf.hpp"
11#include "gfx_pipeline_SDF_vulkan_intf.hpp"
12#include "gfx_pipeline_override_vulkan_intf.hpp"
13#include "gfx_pipeline_tone_mapper_vulkan_intf.hpp"
14#include "../settings/settings.hpp"
15#include "../macros.hpp"
16#include <vulkan/vulkan.hpp>
17#include <vma/vk_mem_alloc.h>
18#include <filesystem>
19#include <unordered_set>
20#include <string>
21
22hi_export_module(hikogui.GFX : gfx_device_intf);
23
24hi_export namespace hi::inline v1 {
25
27public:
28 std::string deviceName = "<no device>";
29 uint32_t vendorID = 0;
30 uint32_t deviceID = 0;
31 uuid deviceUUID = {};
32
33 vk::PhysicalDevice physicalIntrinsic;
34 vk::Device intrinsic;
35 VmaAllocator allocator;
36
37 vk::PhysicalDeviceType deviceType = vk::PhysicalDeviceType::eOther;
38 vk::PhysicalDeviceProperties physicalProperties;
39
41
44 vk::PhysicalDeviceFeatures device_features;
45
56 vk::Buffer quadIndexBuffer;
57 VmaAllocation quadIndexBufferAllocation = {};
58
64
68
69 bool supportsLazyTransientImages = false;
70 vk::ImageUsageFlags transientImageUsageFlags = vk::ImageUsageFlags{};
71 VmaMemoryUsage lazyMemoryUsage = VMA_MEMORY_USAGE_GPU_ONLY;
72
74 {
75 try {
76 auto const lock = std::scoped_lock(gfx_system_mutex);
77
78 tone_mapper_pipeline->destroy(this);
79 tone_mapper_pipeline = nullptr;
80 override_pipeline->destroy(this);
81 override_pipeline = nullptr;
82 SDF_pipeline->destroy(this);
83 SDF_pipeline = nullptr;
84 image_pipeline->destroy(this);
85 image_pipeline = nullptr;
86 box_pipeline->destroy(this);
87 box_pipeline = nullptr;
88
89 destroy_quad_index_buffer();
90
91 vmaDestroyAllocator(allocator);
92
93 for (auto const& queue : _queues) {
94 intrinsic.destroy(queue.command_pool);
95 }
96
97 intrinsic.destroy();
98
99 } catch (std::exception const& e) {
100 hi_log_fatal("Could not properly destruct gfx_device. '{}'", e.what());
101 }
102 }
103
104 gfx_device(const gfx_device&) = delete;
105 gfx_device& operator=(const gfx_device&) = delete;
106 gfx_device(gfx_device&&) = delete;
107 gfx_device& operator=(gfx_device&&) = delete;
108 gfx_device(vk::PhysicalDevice physicalDevice);
109
110 std::string string() const noexcept
111 {
112 auto const lock = std::scoped_lock(gfx_system_mutex);
113
114 return std::format("{0:04x}:{1:04x} {2} {3}", vendorID, deviceID, deviceName, deviceUUID.uuid_string());
115 }
116
120 [[nodiscard]] gfx_queue_vulkan const& get_graphics_queue() const noexcept
121 {
122 for (auto& queue : _queues) {
123 if (queue.flags & vk::QueueFlagBits::eGraphics) {
124 return queue;
125 }
126 }
127 hi_no_default();
128 }
129
134 [[nodiscard]] gfx_queue_vulkan const& get_graphics_queue(vk::SurfaceKHR surface) const noexcept
135 {
136 // First try to find a graphics queue which can also present.
137 gfx_queue_vulkan const *graphics_queue = nullptr;
138 for (auto& queue : _queues) {
139 if (queue.flags & vk::QueueFlagBits::eGraphics) {
140 if (physicalIntrinsic.getSurfaceSupportKHR(queue.family_queue_index, surface)) {
141 return queue;
142 }
143 if (not graphics_queue) {
144 graphics_queue = &queue;
145 }
146 }
147 }
148
149 hi_assert_not_null(graphics_queue);
150 return *graphics_queue;
151 }
152
157 [[nodiscard]] gfx_queue_vulkan const& get_present_queue(vk::SurfaceKHR surface) const noexcept
158 {
159 // First try to find a graphics queue which can also present.
160 gfx_queue_vulkan const *present_queue = nullptr;
161 for (auto& queue : _queues) {
162 if (physicalIntrinsic.getSurfaceSupportKHR(queue.family_queue_index, surface)) {
163 if (queue.flags & vk::QueueFlagBits::eGraphics) {
164 return queue;
165 }
166 if (not present_queue) {
167 present_queue = &queue;
168 }
169 }
170 }
171
172 hi_assert_not_null(present_queue);
173 return *present_queue;
174 }
175
184 [[nodiscard]] vk::SurfaceFormatKHR get_surface_format(vk::SurfaceKHR surface, int *score = nullptr) const noexcept
185 {
186 auto best_surface_format = vk::SurfaceFormatKHR{};
187 auto best_surface_format_score = 0;
188 for (auto surface_format : physicalIntrinsic.getSurfaceFormatsKHR(surface)) {
189 auto surface_format_score = 0;
190
191 switch (surface_format.colorSpace) {
192 case vk::ColorSpaceKHR::eSrgbNonlinear:
193 surface_format_score += 1;
194 break;
195 case vk::ColorSpaceKHR::eExtendedSrgbNonlinearEXT:
196 surface_format_score += 10;
197 break;
198 default:;
199 }
200
201 switch (surface_format.format) {
202 case vk::Format::eR16G16B16A16Sfloat:
203 if (os_settings::uniform_HDR()) {
204 surface_format_score += 12;
205 } else {
206 // XXX add override for application that require HDR.
207 surface_format_score -= 100;
208 }
209 break;
210 case vk::Format::eR16G16B16Sfloat:
211 if (os_settings::uniform_HDR()) {
212 surface_format_score += 11;
213 } else {
214 // XXX add override for application that require HDR.
215 surface_format_score -= 100;
216 }
217 break;
218 case vk::Format::eA2B10G10R10UnormPack32:
219 // This is a wire format for HDR, the GPU will not automatically convert linear shader-space to this wire format.
220 surface_format_score -= 100;
221 break;
222 case vk::Format::eR8G8B8A8Srgb:
223 surface_format_score += 4;
224 break;
225 case vk::Format::eB8G8R8A8Srgb:
226 surface_format_score += 4;
227 break;
228 case vk::Format::eR8G8B8Srgb:
229 surface_format_score += 3;
230 break;
231 case vk::Format::eB8G8R8Srgb:
232 surface_format_score += 3;
233 break;
234 case vk::Format::eB8G8R8A8Unorm:
235 surface_format_score += 2;
236 break;
237 case vk::Format::eR8G8B8A8Unorm:
238 surface_format_score += 2;
239 break;
240 case vk::Format::eB8G8R8Unorm:
241 surface_format_score += 1;
242 break;
243 case vk::Format::eR8G8B8Unorm:
244 surface_format_score += 1;
245 break;
246 default:;
247 }
248
249 if (score) {
250 hi_log_info(
251 " - color-space={}, format={}, score={}",
252 vk::to_string(surface_format.colorSpace),
253 vk::to_string(surface_format.format),
254 surface_format_score);
255 }
256
257 if (surface_format_score > best_surface_format_score) {
258 best_surface_format_score = surface_format_score;
259 best_surface_format = surface_format;
260 }
261 }
262
263 if (score) {
264 *score = best_surface_format_score;
265 }
266 return best_surface_format;
267 }
268
277 [[nodiscard]] vk::PresentModeKHR get_present_mode(vk::SurfaceKHR surface, int *score = nullptr) const noexcept
278 {
279 auto best_present_mode = vk::PresentModeKHR{};
280 auto best_present_mode_score = 0;
281 for (auto const& present_mode : physicalIntrinsic.getSurfacePresentModesKHR(surface)) {
282 int present_mode_score = 0;
283
284 switch (present_mode) {
285 case vk::PresentModeKHR::eImmediate:
286 present_mode_score += 1;
287 break;
288 case vk::PresentModeKHR::eFifoRelaxed:
289 present_mode_score += 2;
290 break;
291 case vk::PresentModeKHR::eFifo:
292 present_mode_score += 3;
293 break;
294 case vk::PresentModeKHR::eMailbox:
295 present_mode_score += 1;
296 break; // mailbox does not wait for vsync.
297 default:
298 continue;
299 }
300
301 if (score) {
302 hi_log_info(" - present-mode={} score={}", vk::to_string(present_mode), present_mode_score);
303 }
304
305 if (present_mode_score > best_present_mode_score) {
306 best_present_mode_score = present_mode_score;
307 best_present_mode = present_mode;
308 }
309 }
310
311 if (score) {
312 *score = best_present_mode_score;
313 }
314 return best_present_mode;
315 }
316
324 int score(vk::SurfaceKHR surface) const;
325
334
336 createBuffer(const vk::BufferCreateInfo& bufferCreateInfo, const VmaAllocationCreateInfo& allocationCreateInfo) const
337 {
338 hi_axiom(gfx_system_mutex.recurse_lock_count());
339
340 VkBuffer buffer;
341 VmaAllocation allocation;
342
343 auto const bufferCreateInfo_ = static_cast<VkBufferCreateInfo>(bufferCreateInfo);
344 auto const result =
345 vk::Result{vmaCreateBuffer(allocator, &bufferCreateInfo_, &allocationCreateInfo, &buffer, &allocation, nullptr)};
346
347 if (result != vk::Result::eSuccess) {
348 throw gui_error(std::format("vmaCreateBuffer() failed {}", to_string(result)));
349 }
350
351 return {buffer, allocation};
352 }
353
354 void destroyBuffer(const vk::Buffer& buffer, const VmaAllocation& allocation) const
355 {
356 hi_axiom(gfx_system_mutex.recurse_lock_count());
357
358 vmaDestroyBuffer(allocator, buffer, allocation);
359 }
360
362 createImage(const vk::ImageCreateInfo& imageCreateInfo, const VmaAllocationCreateInfo& allocationCreateInfo) const
363 {
364 hi_axiom(gfx_system_mutex.recurse_lock_count());
365
366 VkImage image;
367 VmaAllocation allocation;
368
369 auto const imageCreateInfo_ = static_cast<VkImageCreateInfo>(imageCreateInfo);
370 auto const result =
371 vk::Result{vmaCreateImage(allocator, &imageCreateInfo_, &allocationCreateInfo, &image, &allocation, nullptr)};
372
373 if (result != vk::Result::eSuccess) {
374 throw gui_error(std::format("vmaCreateImage() failed {}", to_string(result)));
375 }
376
377 return {image, allocation};
378 }
379
380 void destroyImage(const vk::Image& image, const VmaAllocation& allocation) const
381 {
382 hi_axiom(gfx_system_mutex.recurse_lock_count());
383
384 vmaDestroyImage(allocator, image, allocation);
385 }
386
387 vk::CommandBuffer beginSingleTimeCommands() const
388 {
389 hi_axiom(gfx_system_mutex.recurse_lock_count());
390
391 auto const& queue = get_graphics_queue();
392 auto const commandBuffers = intrinsic.allocateCommandBuffers({queue.command_pool, vk::CommandBufferLevel::ePrimary, 1});
393 auto const commandBuffer = commandBuffers.at(0);
394
395 commandBuffer.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
396 return commandBuffer;
397 }
398
399 void endSingleTimeCommands(vk::CommandBuffer commandBuffer) const
400 {
401 hi_axiom(gfx_system_mutex.recurse_lock_count());
402
403 commandBuffer.end();
404
405 std::vector<vk::CommandBuffer> const commandBuffers = {commandBuffer};
406
407 auto const& queue = get_graphics_queue();
408 queue.queue.submit(
409 {{
410 0,
411 nullptr,
412 nullptr, // wait semaphores, wait stages
413 narrow_cast<uint32_t>(commandBuffers.size()),
414 commandBuffers.data(),
415 0,
416 nullptr // signal semaphores
417 }},
418 vk::Fence());
419
420 queue.queue.waitIdle();
421 intrinsic.freeCommandBuffers(queue.command_pool, commandBuffers);
422 }
423
424 static void transition_layout(
425 vk::CommandBuffer command_buffer,
426 vk::Image image,
427 vk::Format format,
428 vk::ImageLayout src_layout,
429 vk::ImageLayout dst_layout)
430 {
431 hi_axiom(gfx_system_mutex.recurse_lock_count());
432
433 auto const[srcAccessMask, srcStage] = access_and_stage_from_layout(src_layout);
434 auto const[dstAccessMask, dstStage] = access_and_stage_from_layout(dst_layout);
435
437 {srcAccessMask,
438 dstAccessMask,
439 src_layout,
440 dst_layout,
441 VK_QUEUE_FAMILY_IGNORED,
442 VK_QUEUE_FAMILY_IGNORED,
443 image,
444 {
445 vk::ImageAspectFlagBits::eColor,
446 0, // baseMipLevel
447 1, // levelCount
448 0, // baseArrayLayer
449 1 // layerCount
450 }}};
451
452 command_buffer.pipelineBarrier(
453 srcStage,
454 dstStage,
455 vk::DependencyFlags(),
456 0,
457 nullptr,
458 0,
459 nullptr,
460 narrow_cast<uint32_t>(barriers.size()),
461 barriers.data());
462 }
463
464 void transition_layout(vk::Image image, vk::Format format, vk::ImageLayout src_layout, vk::ImageLayout dst_layout) const
465 {
466 hi_axiom(gfx_system_mutex.recurse_lock_count());
467
468 auto const command_buffer = beginSingleTimeCommands();
469
470 transition_layout(command_buffer, image, format, src_layout, dst_layout);
471
472 endSingleTimeCommands(command_buffer);
473 }
474
475 void copyImage(
476 vk::Image srcImage,
477 vk::ImageLayout srcLayout,
478 vk::Image dstImage,
479 vk::ImageLayout dstLayout,
480 vk::ArrayProxy<vk::ImageCopy const> regions) const
481 {
482 hi_axiom(gfx_system_mutex.recurse_lock_count());
483
484 auto const commandBuffer = beginSingleTimeCommands();
485
486 commandBuffer.copyImage(srcImage, srcLayout, dstImage, dstLayout, regions);
487
488 endSingleTimeCommands(commandBuffer);
489 }
490
491 void clearColorImage(
492 vk::Image image,
493 vk::ImageLayout layout,
494 vk::ClearColorValue const& color,
495 vk::ArrayProxy<const vk::ImageSubresourceRange> ranges) const
496 {
497 hi_axiom(gfx_system_mutex.recurse_lock_count());
498
499 auto const commandBuffer = beginSingleTimeCommands();
500
501 commandBuffer.clearColorImage(image, layout, color, ranges);
502
503 endSingleTimeCommands(commandBuffer);
504 }
505
506 template<typename T>
507 std::span<T> mapMemory(const VmaAllocation& allocation) const
508 {
509 hi_axiom(gfx_system_mutex.recurse_lock_count());
510
511 void *mapping;
512 auto const result = vk::Result{vmaMapMemory(allocator, allocation, &mapping)};
513 if (result != vk::Result::eSuccess) {
514 throw gui_error(std::format("vmaMapMemory failed {}", to_string(result)));
515 }
516
517 VmaAllocationInfo allocationInfo;
518 vmaGetAllocationInfo(allocator, allocation, &allocationInfo);
519
520 // Should we launder the pointer? The GPU has created the objects, not the C++ application.
521 T *mappingT = static_cast<T *>(mapping);
522 return std::span<T>{mappingT, allocationInfo.size / sizeof(T)};
523 }
524
525 void unmapMemory(const VmaAllocation& allocation) const
526 {
527 hi_axiom(gfx_system_mutex.recurse_lock_count());
528
529 vmaUnmapMemory(allocator, allocation);
530 }
531
532 void flushAllocation(const VmaAllocation& allocation, VkDeviceSize offset, VkDeviceSize size) const
533 {
534 hi_axiom(gfx_system_mutex.recurse_lock_count());
535
536 auto const alignment = physicalProperties.limits.nonCoherentAtomSize;
537
538 auto const alignedOffset = (offset / alignment) * alignment;
539 auto const adjustedSize = size + (offset - alignedOffset);
540 auto const alignedSize = ((adjustedSize + (alignment - 1)) / alignment) * alignment;
541
542 vmaFlushAllocation(allocator, allocation, alignedOffset, alignedSize);
543 }
544
545 vk::ShaderModule loadShader(uint32_t const *data, std::size_t size) const
546 {
547 hi_axiom(gfx_system_mutex.recurse_lock_count());
548
549 hi_log_info("Loading shader");
550
551 // Check uint32_t alignment of pointer.
552 hi_assert((reinterpret_cast<std::uintptr_t>(data) & 3) == 0);
553
554 return intrinsic.createShaderModule({vk::ShaderModuleCreateFlags(), size, data});
555 }
556
557 vk::ShaderModule loadShader(std::span<std::byte const> shaderObjectBytes) const
558 {
559 // no lock, only local variable.
560
561 // Make sure the address is aligned to uint32_t;
562 auto const address = reinterpret_cast<uintptr_t>(shaderObjectBytes.data());
563 hi_assert((address & 2) == 0);
564
565 auto const shaderObjectBytes32 = reinterpret_cast<uint32_t const *>(shaderObjectBytes.data());
566 return loadShader(shaderObjectBytes32, shaderObjectBytes.size());
567 }
568
569 vk::ShaderModule loadShader(std::filesystem::path const& path) const
570 {
571 // no lock, only local variable.
572
573 return loadShader(as_span<std::byte const>(file_view{path}));
574 }
575
576 void waitIdle() const
577 {
578 hi_axiom(gfx_system_mutex.recurse_lock_count());
579 return intrinsic.waitIdle();
580 }
581
582 vk::Result waitForFences(vk::ArrayProxy<const vk::Fence> fences, vk::Bool32 waitAll, uint64_t timeout) const
583 {
584 hi_axiom(gfx_system_mutex.recurse_lock_count());
585 return intrinsic.waitForFences(fences, waitAll, timeout);
586 }
587
588 vk::Result acquireNextImageKHR(
589 vk::SwapchainKHR swapchain,
590 uint64_t timeout,
591 vk::Semaphore semaphore,
592 vk::Fence fence,
593 uint32_t *pImageIndex) const
594 {
595 hi_axiom(gfx_system_mutex.recurse_lock_count());
596 return intrinsic.acquireNextImageKHR(swapchain, timeout, semaphore, fence, pImageIndex);
597 }
598
599 void resetFences(vk::ArrayProxy<const vk::Fence> fences) const
600 {
601 hi_axiom(gfx_system_mutex.recurse_lock_count());
602 return intrinsic.resetFences(fences);
603 }
604
605 vk::Result createSwapchainKHR(
606 const vk::SwapchainCreateInfoKHR *pCreateInfo,
607 const vk::AllocationCallbacks *pAllocator,
608 vk::SwapchainKHR *pSwapchain) const
609 {
610 hi_axiom(gfx_system_mutex.recurse_lock_count());
611 return intrinsic.createSwapchainKHR(pCreateInfo, pAllocator, pSwapchain);
612 }
613
614 std::vector<vk::Image> getSwapchainImagesKHR(vk::SwapchainKHR swapchain) const
615 {
616 hi_axiom(gfx_system_mutex.recurse_lock_count());
617 return intrinsic.getSwapchainImagesKHR(swapchain);
618 }
619
620 vk::ImageView createImageView(const vk::ImageViewCreateInfo& createInfo) const
621 {
622 hi_axiom(gfx_system_mutex.recurse_lock_count());
623 return intrinsic.createImageView(createInfo);
624 }
625
626 vk::Framebuffer createFramebuffer(const vk::FramebufferCreateInfo& createInfo) const
627 {
628 hi_axiom(gfx_system_mutex.recurse_lock_count());
629 return intrinsic.createFramebuffer(createInfo);
630 }
631
632 vk::RenderPass createRenderPass(const vk::RenderPassCreateInfo& createInfo) const
633 {
634 hi_axiom(gfx_system_mutex.recurse_lock_count());
635 return intrinsic.createRenderPass(createInfo);
636 }
637
638 vk::Extent2D getRenderAreaGranularity(const vk::RenderPass& render_pass) const noexcept
639 {
640 hi_axiom(gfx_system_mutex.recurse_lock_count());
641 vk::Extent2D r;
642 intrinsic.getRenderAreaGranularity(render_pass, &r);
643 return r;
644 }
645
646 vk::Semaphore createSemaphore(const vk::SemaphoreCreateInfo& createInfo = vk::SemaphoreCreateInfo{}) const
647 {
648 hi_axiom(gfx_system_mutex.recurse_lock_count());
649 return intrinsic.createSemaphore(createInfo);
650 }
651
652 vk::Fence createFence(const vk::FenceCreateInfo& createInfo) const
653 {
654 hi_axiom(gfx_system_mutex.recurse_lock_count());
655 return intrinsic.createFence(createInfo);
656 }
657
658 vk::DescriptorSetLayout createDescriptorSetLayout(const vk::DescriptorSetLayoutCreateInfo& createInfo) const
659 {
660 hi_axiom(gfx_system_mutex.recurse_lock_count());
661 return intrinsic.createDescriptorSetLayout(createInfo);
662 }
663
664 vk::DescriptorPool createDescriptorPool(const vk::DescriptorPoolCreateInfo& createInfo) const
665 {
666 hi_axiom(gfx_system_mutex.recurse_lock_count());
667 return intrinsic.createDescriptorPool(createInfo);
668 }
669
670 vk::PipelineLayout createPipelineLayout(const vk::PipelineLayoutCreateInfo& createInfo) const
671 {
672 hi_axiom(gfx_system_mutex.recurse_lock_count());
673 return intrinsic.createPipelineLayout(createInfo);
674 }
675
676 vk::Pipeline createGraphicsPipeline(vk::PipelineCache pipelineCache, const vk::GraphicsPipelineCreateInfo& createInfo) const
677 {
678 hi_axiom(gfx_system_mutex.recurse_lock_count());
679 return intrinsic.createGraphicsPipeline(pipelineCache, createInfo).value;
680 }
681
682 vk::Sampler createSampler(const vk::SamplerCreateInfo& createInfo) const
683 {
684 hi_axiom(gfx_system_mutex.recurse_lock_count());
685 return intrinsic.createSampler(createInfo);
686 }
687
688 std::vector<vk::DescriptorSet> allocateDescriptorSets(const vk::DescriptorSetAllocateInfo& allocateInfo) const
689 {
690 hi_axiom(gfx_system_mutex.recurse_lock_count());
691 return intrinsic.allocateDescriptorSets(allocateInfo);
692 }
693
694 std::vector<vk::CommandBuffer> allocateCommandBuffers(const vk::CommandBufferAllocateInfo& allocateInfo) const
695 {
696 hi_axiom(gfx_system_mutex.recurse_lock_count());
697 return intrinsic.allocateCommandBuffers(allocateInfo);
698 }
699
700 void updateDescriptorSets(
701 vk::ArrayProxy<const vk::WriteDescriptorSet> descriptorWrites,
702 vk::ArrayProxy<const vk::CopyDescriptorSet> descriptorCopies) const
703 {
704 hi_axiom(gfx_system_mutex.recurse_lock_count());
705 return intrinsic.updateDescriptorSets(descriptorWrites, descriptorCopies);
706 }
707
708 void freeCommandBuffers(vk::CommandPool commandPool, vk::ArrayProxy<const vk::CommandBuffer> commandBuffers) const
709 {
710 hi_axiom(gfx_system_mutex.recurse_lock_count());
711 return intrinsic.freeCommandBuffers(commandPool, commandBuffers);
712 }
713
714 void setDebugUtilsObjectNameEXT(vk::DebugUtilsObjectNameInfoEXT const& name_info) const;
715
716 void setDebugUtilsObjectNameEXT(vk::Image image, char const *name) const
717 {
718 return setDebugUtilsObjectNameEXT(
719 vk::DebugUtilsObjectNameInfoEXT{vk::ObjectType::eImage, std::bit_cast<uint64_t>(image), name});
720 }
721
722 void setDebugUtilsObjectNameEXT(vk::Buffer buffer, char const *name) const
723 {
724 return setDebugUtilsObjectNameEXT(
725 vk::DebugUtilsObjectNameInfoEXT{vk::ObjectType::eBuffer, std::bit_cast<uint64_t>(buffer), name});
726 }
727
728 void setDebugUtilsObjectNameEXT(vk::Sampler sampler, char const *name) const
729 {
730 return setDebugUtilsObjectNameEXT(
731 vk::DebugUtilsObjectNameInfoEXT{vk::ObjectType::eSampler, std::bit_cast<uint64_t>(sampler), name});
732 }
733
734 void setDebugUtilsObjectNameEXT(vk::ShaderModule shader_module, char const *name) const
735 {
736 return setDebugUtilsObjectNameEXT(
737 vk::DebugUtilsObjectNameInfoEXT{vk::ObjectType::eShaderModule, std::bit_cast<uint64_t>(shader_module), name});
738 }
739
740 void cmdBeginDebugUtilsLabelEXT(vk::CommandBuffer buffer, vk::DebugUtilsLabelEXT const& create_info) const;
741
742 void cmdEndDebugUtilsLabelEXT(vk::CommandBuffer buffer) const;
743
744 void cmdBeginDebugUtilsLabelEXT(vk::CommandBuffer buffer, char const *name) const
745 {
746 return cmdBeginDebugUtilsLabelEXT(buffer, vk::DebugUtilsLabelEXT{name});
747 }
748
749 template<typename T>
750 void destroy(T x) const
751 {
752 hi_axiom(gfx_system_mutex.recurse_lock_count());
753 intrinsic.destroy(x);
754 }
755
756 vk::SurfaceCapabilitiesKHR getSurfaceCapabilitiesKHR(vk::SurfaceKHR surface) const
757 {
758 hi_axiom(gfx_system_mutex.recurse_lock_count());
759 return physicalIntrinsic.getSurfaceCapabilitiesKHR(surface);
760 }
761
762 void log_memory_usage() const noexcept
763 {
764 hi_log_info("Memory usage for gfx device {}:", string());
765
766 char *stat_string;
767 vmaBuildStatsString(allocator, &stat_string, VK_TRUE);
768 hi_log_info(" * {}", stat_string);
769 vmaFreeStatsString(allocator, stat_string);
770 }
771
772private:
773 static bool
774 hasRequiredExtensions(const vk::PhysicalDevice& physicalDevice, const std::vector<const char *>& requiredExtensions)
775 {
776 auto availableExtensions = std::unordered_set<std::string>();
777 for (auto availableExtensionProperties : physicalDevice.enumerateDeviceExtensionProperties()) {
778 availableExtensions.insert(std::string(availableExtensionProperties.extensionName.data()));
779 }
780
781 for (auto requiredExtension : requiredExtensions) {
782 if (availableExtensions.count(requiredExtension) == 0) {
783 return false;
784 }
785 }
786 return true;
787 }
788
789 static bool meetsRequiredLimits(const vk::PhysicalDevice& physicalDevice, const vk::PhysicalDeviceLimits& requiredLimits)
790 {
791 return true;
792 }
793
794 static bool hasRequiredFeatures(const vk::PhysicalDevice& physicalDevice, const vk::PhysicalDeviceFeatures& requiredFeatures)
795 {
796 auto const availableFeatures = physicalDevice.getFeatures();
797 auto meetsRequirements = true;
798
799 meetsRequirements &=
800 (requiredFeatures.robustBufferAccess == VK_TRUE) ? (availableFeatures.robustBufferAccess == VK_TRUE) : true;
801 meetsRequirements &=
802 (requiredFeatures.fullDrawIndexUint32 == VK_TRUE) ? (availableFeatures.fullDrawIndexUint32 == VK_TRUE) : true;
803 meetsRequirements &= (requiredFeatures.imageCubeArray == VK_TRUE) ? (availableFeatures.imageCubeArray == VK_TRUE) : true;
804 meetsRequirements &=
805 (requiredFeatures.independentBlend == VK_TRUE) ? (availableFeatures.independentBlend == VK_TRUE) : true;
806 meetsRequirements &= (requiredFeatures.geometryShader == VK_TRUE) ? (availableFeatures.geometryShader == VK_TRUE) : true;
807 meetsRequirements &=
808 (requiredFeatures.tessellationShader == VK_TRUE) ? (availableFeatures.tessellationShader == VK_TRUE) : true;
809 meetsRequirements &=
810 (requiredFeatures.sampleRateShading == VK_TRUE) ? (availableFeatures.sampleRateShading == VK_TRUE) : true;
811 meetsRequirements &= (requiredFeatures.dualSrcBlend == VK_TRUE) ? (availableFeatures.dualSrcBlend == VK_TRUE) : true;
812 meetsRequirements &= (requiredFeatures.logicOp == VK_TRUE) ? (availableFeatures.logicOp == VK_TRUE) : true;
813 meetsRequirements &=
814 (requiredFeatures.multiDrawIndirect == VK_TRUE) ? (availableFeatures.multiDrawIndirect == VK_TRUE) : true;
815 meetsRequirements &= (requiredFeatures.drawIndirectFirstInstance == VK_TRUE) ?
816 (availableFeatures.drawIndirectFirstInstance == VK_TRUE) :
817 true;
818 meetsRequirements &= (requiredFeatures.depthClamp == VK_TRUE) ? (availableFeatures.depthClamp == VK_TRUE) : true;
819 meetsRequirements &= (requiredFeatures.depthBiasClamp == VK_TRUE) ? (availableFeatures.depthBiasClamp == VK_TRUE) : true;
820 meetsRequirements &=
821 (requiredFeatures.fillModeNonSolid == VK_TRUE) ? (availableFeatures.fillModeNonSolid == VK_TRUE) : true;
822 meetsRequirements &= (requiredFeatures.depthBounds == VK_TRUE) ? (availableFeatures.depthBounds == VK_TRUE) : true;
823 meetsRequirements &= (requiredFeatures.wideLines == VK_TRUE) ? (availableFeatures.wideLines == VK_TRUE) : true;
824 meetsRequirements &= (requiredFeatures.largePoints == VK_TRUE) ? (availableFeatures.largePoints == VK_TRUE) : true;
825 meetsRequirements &= (requiredFeatures.alphaToOne == VK_TRUE) ? (availableFeatures.alphaToOne == VK_TRUE) : true;
826 meetsRequirements &= (requiredFeatures.multiViewport == VK_TRUE) ? (availableFeatures.multiViewport == VK_TRUE) : true;
827 meetsRequirements &=
828 (requiredFeatures.samplerAnisotropy == VK_TRUE) ? (availableFeatures.samplerAnisotropy == VK_TRUE) : true;
829 meetsRequirements &=
830 (requiredFeatures.textureCompressionETC2 == VK_TRUE) ? (availableFeatures.textureCompressionETC2 == VK_TRUE) : true;
831 meetsRequirements &= (requiredFeatures.textureCompressionASTC_LDR == VK_TRUE) ?
832 (availableFeatures.textureCompressionASTC_LDR == VK_TRUE) :
833 true;
834 meetsRequirements &=
835 (requiredFeatures.textureCompressionBC == VK_TRUE) ? (availableFeatures.textureCompressionBC == VK_TRUE) : true;
836 meetsRequirements &=
837 (requiredFeatures.occlusionQueryPrecise == VK_TRUE) ? (availableFeatures.occlusionQueryPrecise == VK_TRUE) : true;
838 meetsRequirements &=
839 (requiredFeatures.pipelineStatisticsQuery == VK_TRUE) ? (availableFeatures.pipelineStatisticsQuery == VK_TRUE) : true;
840 meetsRequirements &= (requiredFeatures.vertexPipelineStoresAndAtomics == VK_TRUE) ?
841 (availableFeatures.vertexPipelineStoresAndAtomics == VK_TRUE) :
842 true;
843 meetsRequirements &= (requiredFeatures.fragmentStoresAndAtomics == VK_TRUE) ?
844 (availableFeatures.fragmentStoresAndAtomics == VK_TRUE) :
845 true;
846 meetsRequirements &= (requiredFeatures.shaderTessellationAndGeometryPointSize == VK_TRUE) ?
847 (availableFeatures.shaderTessellationAndGeometryPointSize == VK_TRUE) :
848 true;
849 meetsRequirements &= (requiredFeatures.shaderImageGatherExtended == VK_TRUE) ?
850 (availableFeatures.shaderImageGatherExtended == VK_TRUE) :
851 true;
852 meetsRequirements &= (requiredFeatures.shaderStorageImageExtendedFormats == VK_TRUE) ?
853 (availableFeatures.shaderStorageImageExtendedFormats == VK_TRUE) :
854 true;
855 meetsRequirements &= (requiredFeatures.shaderStorageImageMultisample == VK_TRUE) ?
856 (availableFeatures.shaderStorageImageMultisample == VK_TRUE) :
857 true;
858 meetsRequirements &= (requiredFeatures.shaderStorageImageReadWithoutFormat == VK_TRUE) ?
859 (availableFeatures.shaderStorageImageReadWithoutFormat == VK_TRUE) :
860 true;
861 meetsRequirements &= (requiredFeatures.shaderStorageImageWriteWithoutFormat == VK_TRUE) ?
862 (availableFeatures.shaderStorageImageWriteWithoutFormat == VK_TRUE) :
863 true;
864 meetsRequirements &= (requiredFeatures.shaderUniformBufferArrayDynamicIndexing == VK_TRUE) ?
865 (availableFeatures.shaderUniformBufferArrayDynamicIndexing == VK_TRUE) :
866 true;
867 meetsRequirements &= (requiredFeatures.shaderSampledImageArrayDynamicIndexing == VK_TRUE) ?
868 (availableFeatures.shaderSampledImageArrayDynamicIndexing == VK_TRUE) :
869 true;
870 meetsRequirements &= (requiredFeatures.shaderStorageBufferArrayDynamicIndexing == VK_TRUE) ?
871 (availableFeatures.shaderStorageBufferArrayDynamicIndexing == VK_TRUE) :
872 true;
873 meetsRequirements &= (requiredFeatures.shaderStorageImageArrayDynamicIndexing == VK_TRUE) ?
874 (availableFeatures.shaderStorageImageArrayDynamicIndexing == VK_TRUE) :
875 true;
876 meetsRequirements &=
877 (requiredFeatures.shaderClipDistance == VK_TRUE) ? (availableFeatures.shaderClipDistance == VK_TRUE) : true;
878 meetsRequirements &=
879 (requiredFeatures.shaderCullDistance == VK_TRUE) ? (availableFeatures.shaderCullDistance == VK_TRUE) : true;
880 meetsRequirements &= (requiredFeatures.shaderFloat64 == VK_TRUE) ? (availableFeatures.shaderFloat64 == VK_TRUE) : true;
881 meetsRequirements &= (requiredFeatures.shaderInt64 == VK_TRUE) ? (availableFeatures.shaderInt64 == VK_TRUE) : true;
882 meetsRequirements &= (requiredFeatures.shaderInt16 == VK_TRUE) ? (availableFeatures.shaderInt16 == VK_TRUE) : true;
883 meetsRequirements &=
884 (requiredFeatures.shaderResourceResidency == VK_TRUE) ? (availableFeatures.shaderResourceResidency == VK_TRUE) : true;
885 meetsRequirements &=
886 (requiredFeatures.shaderResourceMinLod == VK_TRUE) ? (availableFeatures.shaderResourceMinLod == VK_TRUE) : true;
887 meetsRequirements &= (requiredFeatures.sparseBinding == VK_TRUE) ? (availableFeatures.sparseBinding == VK_TRUE) : true;
888 meetsRequirements &=
889 (requiredFeatures.sparseResidencyBuffer == VK_TRUE) ? (availableFeatures.sparseResidencyBuffer == VK_TRUE) : true;
890 meetsRequirements &=
891 (requiredFeatures.sparseResidencyImage2D == VK_TRUE) ? (availableFeatures.sparseResidencyImage2D == VK_TRUE) : true;
892 meetsRequirements &=
893 (requiredFeatures.sparseResidencyImage3D == VK_TRUE) ? (availableFeatures.sparseResidencyImage3D == VK_TRUE) : true;
894 meetsRequirements &=
895 (requiredFeatures.sparseResidency2Samples == VK_TRUE) ? (availableFeatures.sparseResidency2Samples == VK_TRUE) : true;
896 meetsRequirements &=
897 (requiredFeatures.sparseResidency4Samples == VK_TRUE) ? (availableFeatures.sparseResidency4Samples == VK_TRUE) : true;
898 meetsRequirements &=
899 (requiredFeatures.sparseResidency8Samples == VK_TRUE) ? (availableFeatures.sparseResidency8Samples == VK_TRUE) : true;
900 meetsRequirements &= (requiredFeatures.sparseResidency16Samples == VK_TRUE) ?
901 (availableFeatures.sparseResidency16Samples == VK_TRUE) :
902 true;
903 meetsRequirements &=
904 (requiredFeatures.sparseResidencyAliased == VK_TRUE) ? (availableFeatures.sparseResidencyAliased == VK_TRUE) : true;
905 meetsRequirements &=
906 (requiredFeatures.variableMultisampleRate == VK_TRUE) ? (availableFeatures.variableMultisampleRate == VK_TRUE) : true;
907 meetsRequirements &=
908 (requiredFeatures.inheritedQueries == VK_TRUE) ? (availableFeatures.inheritedQueries == VK_TRUE) : true;
909
910 return meetsRequirements;
911 }
912
913 [[nodiscard]] std::vector<vk::DeviceQueueCreateInfo> make_device_queue_create_infos() const noexcept
914 {
915 auto const default_queue_priority = std::array{1.0f};
916 uint32_t queue_family_index = 0;
917
919 for (auto queue_family_properties : physicalIntrinsic.getQueueFamilyProperties()) {
920 auto const num_queues = 1;
921 hi_assert(size(default_queue_priority) >= num_queues);
922 r.emplace_back(vk::DeviceQueueCreateFlags(), queue_family_index++, num_queues, default_queue_priority.data());
923 }
924 return r;
925 }
926
927 static std::pair<vk::AccessFlags, vk::PipelineStageFlags> access_and_stage_from_layout(vk::ImageLayout layout) noexcept
928 {
929 switch (layout) {
930 case vk::ImageLayout::eUndefined:
931 return {vk::AccessFlags(), vk::PipelineStageFlagBits::eTopOfPipe};
932
933 // GPU Texture Maps
934 case vk::ImageLayout::eTransferDstOptimal:
935 return {vk::AccessFlagBits::eTransferWrite, vk::PipelineStageFlagBits::eTransfer};
936
937 case vk::ImageLayout::eShaderReadOnlyOptimal:
938 return {vk::AccessFlagBits::eShaderRead, vk::PipelineStageFlagBits::eFragmentShader};
939
940 // CPU Staging texture maps
941 case vk::ImageLayout::eGeneral:
942 return {vk::AccessFlagBits::eHostWrite, vk::PipelineStageFlagBits::eHost};
943
944 case vk::ImageLayout::eTransferSrcOptimal:
945 return {vk::AccessFlagBits::eTransferRead, vk::PipelineStageFlagBits::eTransfer};
946
947 // If we are explicitly transferring an image for ePresentSrcKHR, then we are doing this
948 // because we want to reuse the swapchain images in subsequent rendering. Make sure it
949 // is ready for the fragment shader.
950 case vk::ImageLayout::ePresentSrcKHR:
951 return {
952 vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite,
953 vk::PipelineStageFlagBits::eColorAttachmentOutput};
954
955 default:
956 hi_no_default();
957 }
958 }
959
960 void initialize_queues(std::vector<vk::DeviceQueueCreateInfo> const& device_queue_create_infos) noexcept
961 {
962 auto const queue_family_properties = physicalIntrinsic.getQueueFamilyProperties();
963
964 for (auto const& device_queue_create_info : device_queue_create_infos) {
965 auto const queue_family_index = device_queue_create_info.queueFamilyIndex;
966 auto const& queue_family_property = queue_family_properties[queue_family_index];
967 auto const queue_flags = queue_family_property.queueFlags;
968
969 for (uint32_t queue_index = 0; queue_index != device_queue_create_info.queueCount; ++queue_index) {
970 auto queue = intrinsic.getQueue(queue_family_index, queue_index);
971 auto command_pool = intrinsic.createCommandPool(
972 {vk::CommandPoolCreateFlagBits::eTransient | vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
973 queue_family_index});
974
975 _queues.emplace_back(queue_family_index, queue_index, queue_flags, std::move(queue), std::move(command_pool));
976 }
977 }
978 }
979
980 void initialize_device();
981
982 void initialize_quad_index_buffer()
983 {
984 hi_axiom(gfx_system_mutex.recurse_lock_count());
985
986 using vertex_index_type = uint16_t;
987 constexpr ssize_t maximum_number_of_vertices = 1 << (sizeof(vertex_index_type) * CHAR_BIT);
988 constexpr ssize_t maximum_number_of_quads = maximum_number_of_vertices / 4;
989 constexpr ssize_t maximum_number_of_triangles = maximum_number_of_quads * 2;
990 constexpr ssize_t maximum_number_of_indices = maximum_number_of_triangles * 3;
991
992 // Create vertex index buffer
993 {
994 vk::BufferCreateInfo const bufferCreateInfo = {
995 vk::BufferCreateFlags(),
996 sizeof(vertex_index_type) * maximum_number_of_indices,
997 vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eTransferDst,
998 vk::SharingMode::eExclusive};
999 VmaAllocationCreateInfo allocationCreateInfo = {};
1000 allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
1001 allocationCreateInfo.pUserData = const_cast<char *>("vertex index buffer");
1002 allocationCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
1003 std::tie(quadIndexBuffer, quadIndexBufferAllocation) = createBuffer(bufferCreateInfo, allocationCreateInfo);
1004 setDebugUtilsObjectNameEXT(quadIndexBuffer, "vertex index buffer");
1005 }
1006
1007 // Fill in the vertex index buffer, using a staging buffer, then copying.
1008 {
1009 // Create staging vertex index buffer.
1010 vk::BufferCreateInfo const bufferCreateInfo = {
1011 vk::BufferCreateFlags(),
1012 sizeof(vertex_index_type) * maximum_number_of_indices,
1013 vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eTransferSrc,
1014 vk::SharingMode::eExclusive};
1015 VmaAllocationCreateInfo allocationCreateInfo = {};
1016 allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
1017 allocationCreateInfo.pUserData = const_cast<char *>("staging vertex index buffer");
1018 allocationCreateInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
1019 auto const[stagingvertexIndexBuffer, stagingvertexIndexBufferAllocation] =
1020 createBuffer(bufferCreateInfo, allocationCreateInfo);
1021 setDebugUtilsObjectNameEXT(stagingvertexIndexBuffer, "staging vertex index buffer");
1022
1023 // Initialize indices.
1024 auto const stagingvertexIndexBufferData = mapMemory<vertex_index_type>(stagingvertexIndexBufferAllocation);
1025 for (std::size_t i = 0; i < maximum_number_of_indices; i++) {
1026 auto const vertexInRectangle = i % 6;
1027 auto const rectangleNr = i / 6;
1028 auto const rectangleBase = rectangleNr * 4;
1029
1030 switch (vertexInRectangle) {
1031 case 0:
1032 stagingvertexIndexBufferData[i] = narrow_cast<vertex_index_type>(rectangleBase + 0);
1033 break;
1034 case 1:
1035 stagingvertexIndexBufferData[i] = narrow_cast<vertex_index_type>(rectangleBase + 1);
1036 break;
1037 case 2:
1038 stagingvertexIndexBufferData[i] = narrow_cast<vertex_index_type>(rectangleBase + 2);
1039 break;
1040 case 3:
1041 stagingvertexIndexBufferData[i] = narrow_cast<vertex_index_type>(rectangleBase + 2);
1042 break;
1043 case 4:
1044 stagingvertexIndexBufferData[i] = narrow_cast<vertex_index_type>(rectangleBase + 1);
1045 break;
1046 case 5:
1047 stagingvertexIndexBufferData[i] = narrow_cast<vertex_index_type>(rectangleBase + 3);
1048 break;
1049 default:
1050 hi_no_default();
1051 }
1052 }
1053 flushAllocation(stagingvertexIndexBufferAllocation, 0, VK_WHOLE_SIZE);
1054 unmapMemory(stagingvertexIndexBufferAllocation);
1055
1056 // Copy indices to vertex index buffer.
1057 auto& queue = get_graphics_queue();
1058 auto commands = allocateCommandBuffers({queue.command_pool, vk::CommandBufferLevel::ePrimary, 1}).at(0);
1059
1060 commands.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
1061 cmdBeginDebugUtilsLabelEXT(commands, "copy vertex index buffer");
1062 commands.copyBuffer(
1063 stagingvertexIndexBuffer, quadIndexBuffer, {{0, 0, sizeof(vertex_index_type) * maximum_number_of_indices}});
1064 cmdEndDebugUtilsLabelEXT(commands);
1065 commands.end();
1066
1067 std::vector<vk::CommandBuffer> const commandBuffersToSubmit = {commands};
1068 std::vector<vk::SubmitInfo> const submitInfo = {
1069 {0,
1070 nullptr,
1071 nullptr,
1072 narrow_cast<uint32_t>(commandBuffersToSubmit.size()),
1073 commandBuffersToSubmit.data(),
1074 0,
1075 nullptr}};
1076 queue.queue.submit(submitInfo, vk::Fence());
1077 queue.queue.waitIdle();
1078
1079 freeCommandBuffers(queue.command_pool, {commands});
1080 destroyBuffer(stagingvertexIndexBuffer, stagingvertexIndexBufferAllocation);
1081 }
1082 }
1083
1084 void destroy_quad_index_buffer()
1085 {
1086 hi_axiom(gfx_system_mutex.recurse_lock_count());
1087 destroyBuffer(quadIndexBuffer, quadIndexBufferAllocation);
1088 }
1089};
1090
1091} // namespace hi::inline v1
std::ptrdiff_t ssize_t
Signed size/index into an array.
Definition misc.hpp:32
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
unfair_recursive_mutex gfx_system_mutex
Global mutex for GUI elements, like gfx_system, gfx_device, Windows and Widgets.
Definition gfx_system_globals.hpp:18
Definition gfx_device_vulkan_intf.hpp:26
gfx_queue_vulkan const & get_graphics_queue() const noexcept
Get a graphics queue.
Definition gfx_device_vulkan_intf.hpp:120
std::vector< std::pair< uint32_t, uint8_t > > find_best_queue_family_indices(vk::SurfaceKHR surface) const
vk::SurfaceFormatKHR get_surface_format(vk::SurfaceKHR surface, int *score=nullptr) const noexcept
Get the surface format.
Definition gfx_device_vulkan_intf.hpp:184
gfx_queue_vulkan const & get_present_queue(vk::SurfaceKHR surface) const noexcept
Get a present queue.
Definition gfx_device_vulkan_intf.hpp:157
vk::PhysicalDeviceFeatures device_features
The device features that have been turned on for this device.
Definition gfx_device_vulkan_intf.hpp:44
gfx_queue_vulkan const & get_graphics_queue(vk::SurfaceKHR surface) const noexcept
Get a graphics queue.
Definition gfx_device_vulkan_intf.hpp:134
std::vector< const char * > requiredExtensions
Definition gfx_device_vulkan_intf.hpp:67
vk::PresentModeKHR get_present_mode(vk::SurfaceKHR surface, int *score=nullptr) const noexcept
Get the present mode.
Definition gfx_device_vulkan_intf.hpp:277
vk::Buffer quadIndexBuffer
Shared index buffer containing indices for drawing quads.
Definition gfx_device_vulkan_intf.hpp:56
Definition gfx_queue_vulkan.hpp:15
T data(T... args)
T emplace_back(T... args)
T lock(T... args)
T move(T... args)
T size(T... args)
T tie(T... args)
T to_string(T... args)
T what(T... args)