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