MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VKDevice.cpp
Go to the documentation of this file.
1#include "VKDevice.hpp"
4
5#include "set"
6
7#ifdef GLFW_BACKEND
8#define GLFW_INCLUDE_VULKAN
9#include <GLFW/glfw3.h>
10#endif
11
12#if defined(WIN32_BACKEND)
13#ifndef WIN32_LEAN_AND_MEAN
14#define WIN32_LEAN_AND_MEAN
15#endif
16#ifndef NOMINMAX
17#define NOMINMAX
18#endif
19#include <windows.h>
20// Windows header should be included before vulkan_win32.h, dummy comment to prevent auto sort
21#include <vulkan/vulkan_win32.h>
22#endif
23
24#if defined(WAYLAND_BACKEND)
25#include <vulkan/vulkan_wayland.h>
26#include <wayland-client.h>
27#endif
28
29namespace MayaFlux::Core {
30
31namespace {
32 /**
33 * @brief Scratch native connection plus surfaceless presentation query.
34 *
35 * Device selection runs before any window exists, so
36 * vkGetPhysicalDeviceSurfaceSupportKHR is unavailable: it needs a surface,
37 * which needs a window. The platform entry points answer the same question
38 * from a native display connection instead.
39 *
40 * On Wayland no compositor connection exists at selection time either, since
41 * each WaylandWindow calls wl_display_connect in its own constructor. One
42 * scratch connection is opened for the selection pass and closed after; the
43 * query does not care which connection it is given.
44 *
45 * When the platform has no entry point, or the connection fails, available is
46 * false and supports() returns true for every family so selection proceeds
47 * unverified rather than rejecting every device.
48 */
49 struct PresentationProbe {
50 vk::Instance instance;
52 bool available {};
53 const char* mechanism { "unavailable" };
54
55 explicit PresentationProbe(vk::Instance inst)
56 : instance(inst)
57 {
58#if defined(GLFW_BACKEND)
59 available = glfwVulkanSupported() == GLFW_TRUE;
60 mechanism = available ? "glfw" : "unavailable";
61#elif defined(WIN32_BACKEND)
62 available = true;
63 mechanism = "win32";
64#elif defined(WAYLAND_BACKEND)
65 native_display = wl_display_connect(nullptr);
66 available = native_display != nullptr;
67 mechanism = available ? "wayland" : "unavailable";
68#endif
69 }
70
71 ~PresentationProbe()
72 {
73#if defined(WAYLAND_BACKEND)
74 if (native_display)
75 wl_display_disconnect(static_cast<wl_display*>(native_display));
76#endif
77 }
78
79 PresentationProbe(const PresentationProbe&) = delete;
80 PresentationProbe& operator=(const PresentationProbe&) = delete;
81 PresentationProbe(PresentationProbe&&) = delete;
82 PresentationProbe& operator=(PresentationProbe&&) = delete;
83
84 [[nodiscard]] bool supports(vk::PhysicalDevice device, uint32_t family_index) const
85 {
86 if (!available)
87 return true;
88
89#if defined(GLFW_BACKEND)
90 return glfwGetPhysicalDevicePresentationSupport(
91 static_cast<VkInstance>(instance),
92 static_cast<VkPhysicalDevice>(device),
93 family_index)
94 == GLFW_TRUE;
95#elif defined(WIN32_BACKEND)
96 return vkGetPhysicalDeviceWin32PresentationSupportKHR(
97 static_cast<VkPhysicalDevice>(device), family_index)
98 == VK_TRUE;
99#elif defined(WAYLAND_BACKEND)
100 return vkGetPhysicalDeviceWaylandPresentationSupportKHR(
101 static_cast<VkPhysicalDevice>(device), family_index,
102 static_cast<wl_display*>(native_display))
103 == VK_TRUE;
104#else
105 return true;
106#endif
107 }
108 };
109
110 /** @brief Format a Vulkan UUID as 32 lowercase hex characters, no separators. */
111 std::string uuid_to_hex(const uint8_t* uuid)
112 {
113 std::string out;
114 out.reserve(VK_UUID_SIZE * 2);
115 for (size_t i = 0; i < VK_UUID_SIZE; ++i) {
116 out += std::format("{:02x}", uuid[i]);
117 }
118 return out;
119 }
120
121 /** @brief Case-insensitive substring test. */
122 bool contains_nocase(std::string_view haystack, std::string_view needle)
123 {
124 if (needle.empty() || needle.size() > haystack.size())
125 return false;
126
127 auto it = std::search(haystack.begin(), haystack.end(),
128 needle.begin(), needle.end(),
129 [](char a, char b) { return std::tolower(static_cast<unsigned char>(a)) == std::tolower(static_cast<unsigned char>(b)); });
130
131 return it != haystack.end();
132 }
133
134 /**
135 * @brief Everything selection needs to know about one enumerated device.
136 *
137 * Gathered in a single pass so that scoring, selector matching, and the
138 * candidate log all read the same values.
139 */
140 struct DeviceCandidate {
141 vk::PhysicalDevice device;
142 uint32_t index {};
143 std::string name;
144 std::string uuid_hex;
145 std::array<uint8_t, VK_UUID_SIZE> uuid {};
146 vk::PhysicalDeviceType type {};
147 uint32_t api_version {};
148 vk::DriverId driver_id {};
149 std::string driver_name;
150 QueueFamilyIndices families;
156 uint32_t pci_bus {};
158 int64_t score {};
159 const char* reject_reason {};
160
161 [[nodiscard]] bool viable() const { return reject_reason == nullptr; }
162 };
163
164 /** @brief Base score by device class, before preference and capability bonuses. */
165 int64_t type_base_score(vk::PhysicalDeviceType type)
166 {
167 switch (type) {
168 case vk::PhysicalDeviceType::eDiscreteGpu:
169 return 4000;
170 case vk::PhysicalDeviceType::eIntegratedGpu:
171 return 2000;
172 case vk::PhysicalDeviceType::eVirtualGpu:
173 return 1000;
174 case vk::PhysicalDeviceType::eCpu:
175 return 100;
176 default:
177 return 500;
178 }
179 }
180
181 /**
182 * @brief Read MAYAFLUX_GPU, an all-digits value being an index and anything
183 * else a name substring.
184 * @param out_index Receives the parsed index, or -1.
185 * @param out_name Receives the name substring, or empty.
186 * @return true if the variable was set and non-empty.
187 */
188 bool read_gpu_env(int32_t& out_index, std::string& out_name)
189 {
190 const char* raw = std::getenv("MAYAFLUX_GPU");
191 if (!raw || *raw == '\0')
192 return false;
193
194 std::string_view value(raw);
195 if (std::ranges::all_of(value, [](char c) { return std::isdigit(static_cast<unsigned char>(c)) != 0; })) {
196 out_index = static_cast<int32_t>(std::strtol(raw, nullptr, 10));
197 out_name.clear();
198 } else {
199 out_index = -1;
200 out_name = value;
201 }
202 return true;
203 }
204
205} // namespace
206
208{
209 cleanup();
210}
211
213 : m_physical_device(other.m_physical_device)
214 , m_logical_device(other.m_logical_device)
215 , m_graphics_queue(other.m_graphics_queue)
216 , m_compute_queue(other.m_compute_queue)
217 , m_transfer_queue(other.m_transfer_queue)
218 , m_queue_families(other.m_queue_families)
219 , m_graphics_presents(other.m_graphics_presents)
220 , m_present_queues(std::move(other.m_present_queues))
221 , m_device_name(std::move(other.m_device_name))
222 , m_device_uuid(other.m_device_uuid)
223 , m_supports_mesh_shaders(other.m_supports_mesh_shaders)
224{
225 other.m_physical_device = VK_NULL_HANDLE;
226 other.m_logical_device = VK_NULL_HANDLE;
227 other.m_graphics_queue = VK_NULL_HANDLE;
228 other.m_compute_queue = VK_NULL_HANDLE;
229 other.m_transfer_queue = VK_NULL_HANDLE;
230}
231
233{
234 if (this != &other) {
235 cleanup();
236 m_physical_device = other.m_physical_device;
237 m_logical_device = other.m_logical_device;
238 m_graphics_queue = other.m_graphics_queue;
239 m_compute_queue = other.m_compute_queue;
240 m_transfer_queue = other.m_transfer_queue;
241 m_queue_families = other.m_queue_families;
242 m_graphics_presents = other.m_graphics_presents;
243 m_present_queues = std::move(other.m_present_queues);
244 m_device_name = std::move(other.m_device_name);
245 m_device_uuid = other.m_device_uuid;
246 m_supports_mesh_shaders = other.m_supports_mesh_shaders;
247
248 other.m_physical_device = VK_NULL_HANDLE;
249 other.m_logical_device = VK_NULL_HANDLE;
250 other.m_graphics_queue = VK_NULL_HANDLE;
251 other.m_compute_queue = VK_NULL_HANDLE;
252 other.m_transfer_queue = VK_NULL_HANDLE;
253 }
254 return *this;
255}
256
257bool VKDevice::initialize(vk::Instance instance, const GraphicsBackendInfo& backend_info)
258{
259 if (!pick_physical_device(instance, backend_info)) {
260 return false;
261 }
262
263 return create_logical_device(instance, backend_info);
264}
265
267{
268 if (m_logical_device) {
269 m_logical_device.destroy();
270 m_logical_device = nullptr;
271 MF_INFO(Journal::Component::Core, Journal::Context::GraphicsBackend, "Vulkan logical device destroyed.");
272 }
273 m_physical_device = nullptr;
274 m_graphics_queue = nullptr;
275 m_compute_queue = nullptr;
276 m_transfer_queue = nullptr;
277 m_queue_families = {};
278 m_present_queues.clear();
279 m_device_name.clear();
280 m_device_uuid = {};
281 m_graphics_presents = false;
283}
284
285bool VKDevice::pick_physical_device(vk::Instance instance, const GraphicsBackendInfo& backend_info)
286{
287 auto devices = instance.enumeratePhysicalDevices();
288
289 if (devices.empty()) {
291 std::source_location::current(),
292 "Failed to find GPUs with Vulkan support!");
293 }
294
295 const PresentationProbe probe(instance);
296
297 if (backend_info.require_presentation && !probe.available) {
299 "Presentation support cannot be verified on this platform; "
300 "device selection will not filter on it");
301 }
302
303 std::vector<DeviceCandidate> candidates;
304 candidates.reserve(devices.size());
305
306 for (uint32_t i = 0; i < devices.size(); ++i) {
307 const auto& device = devices[i];
308
309 DeviceCandidate cand;
310 cand.device = device;
311 cand.index = i;
312
313 auto available_extensions = device.enumerateDeviceExtensionProperties();
314 bool has_pci_ext = false;
315
316 for (const auto& ext : available_extensions) {
317 if (strcmp(ext.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0)
318 cand.has_swapchain = true;
319 if (strcmp(ext.extensionName, VK_EXT_MESH_SHADER_EXTENSION_NAME) == 0)
320 cand.has_mesh_shader = true;
321 if (strcmp(ext.extensionName, VK_EXT_PCI_BUS_INFO_EXTENSION_NAME) == 0)
322 has_pci_ext = true;
323 }
324
325 auto prop_chain = vk::StructureChain {
326 vk::PhysicalDeviceProperties2 {},
327 vk::PhysicalDeviceVulkan11Properties {},
328 vk::PhysicalDeviceVulkan12Properties {},
329 vk::PhysicalDevicePCIBusInfoPropertiesEXT {}
330 };
331
332 if (!has_pci_ext) {
333 prop_chain.unlink<vk::PhysicalDevicePCIBusInfoPropertiesEXT>();
334 }
335
336 device.getProperties2(&prop_chain.get<vk::PhysicalDeviceProperties2>());
337
338 const auto& props = prop_chain.get<vk::PhysicalDeviceProperties2>().properties;
339 const auto& props11 = prop_chain.get<vk::PhysicalDeviceVulkan11Properties>();
340 const auto& props12 = prop_chain.get<vk::PhysicalDeviceVulkan12Properties>();
341
342 cand.name = props.deviceName.data();
343 cand.type = props.deviceType;
344 cand.api_version = props.apiVersion;
345 cand.driver_id = props12.driverID;
346 cand.driver_name = props12.driverName.data();
347 std::copy_n(props11.deviceUUID.data(), VK_UUID_SIZE, cand.uuid.begin());
348 cand.uuid_hex = uuid_to_hex(cand.uuid.data());
349
350 if (has_pci_ext) {
351 cand.has_pci_info = true;
352 cand.pci_bus = prop_chain.get<vk::PhysicalDevicePCIBusInfoPropertiesEXT>().pciBus;
353 }
354
355 auto memory_props = device.getMemoryProperties();
356 for (uint32_t h = 0; h < memory_props.memoryHeapCount; ++h) {
357 if (memory_props.memoryHeaps[h].flags & vk::MemoryHeapFlagBits::eDeviceLocal) {
358 cand.device_local_bytes = std::max(cand.device_local_bytes,
359 static_cast<uint64_t>(memory_props.memoryHeaps[h].size));
360 }
361 }
362
363 cand.families = find_queue_families(device);
364
365 if (cand.has_mesh_shader) {
366 vk::PhysicalDeviceMeshShaderFeaturesEXT mesh_features;
367 vk::PhysicalDeviceFeatures2 features;
368 features.pNext = &mesh_features;
369 device.getFeatures2(&features);
370
371 cand.has_mesh_shader = mesh_features.meshShader == VK_TRUE
372 && mesh_features.taskShader == VK_TRUE;
373 }
374
375 {
376 auto family_props = device.getQueueFamilyProperties();
377 const uint32_t probe_count = std::min<uint32_t>(static_cast<uint32_t>(family_props.size()), QueueFamilyIndices::MAX_TRACKED_FAMILIES);
378
379 for (uint32_t f = 0; f < probe_count; ++f) {
380 if (probe.supports(device, f))
381 cand.present_family_mask |= (1U << f);
382 }
383
384 cand.families.present_family_mask = cand.present_family_mask;
385
386 if (cand.families.graphics_family.has_value())
387 cand.graphics_presents = cand.families.can_present(cand.families.graphics_family.value());
388 }
389
390 if (!cand.families.graphics_family.has_value()) {
391 cand.reject_reason = "no graphics queue family";
392 } else if (cand.api_version < VK_API_VERSION_1_3) {
393 cand.reject_reason = "device API version below 1.3";
394 } else if (!cand.has_swapchain) {
395 cand.reject_reason = "no VK_KHR_swapchain";
396 } else if (backend_info.require_presentation && !cand.graphics_presents) {
397 cand.reject_reason = "graphics family cannot present";
398 }
399
400 cand.score = type_base_score(cand.type);
401
402 switch (backend_info.device_preference) {
404 if (cand.type == vk::PhysicalDeviceType::eDiscreteGpu)
405 cand.score += 10000;
406 break;
408 if (cand.type == vk::PhysicalDeviceType::eIntegratedGpu)
409 cand.score += 10000;
410 break;
412 if (cand.type == vk::PhysicalDeviceType::eVirtualGpu)
413 cand.score += 10000;
414 break;
416 if (cand.type == vk::PhysicalDeviceType::eDiscreteGpu) {
417 cand.score += 10000;
418 if (cand.has_pci_info)
419 cand.score += static_cast<int64_t>(cand.pci_bus) * 4;
420 }
421 break;
423 default:
424 break;
425 }
426
427 if (cand.graphics_presents)
428 cand.score += 500;
429 if (cand.has_mesh_shader)
430 cand.score += 100;
431
432 cand.score += static_cast<int64_t>(cand.device_local_bytes >> 30U);
433
434 candidates.push_back(std::move(cand));
435 }
436
437 for (const auto& cand : candidates) {
439 "GPU [{}] {} | type={} driver={} ({}) api={}.{}.{} | uuid={} | "
440 "gfx={} compute={} transfer={} | present=gfx:{} mask:{:#x} mesh={} vram={}MB pci_bus={} | score={}{}{}",
441 cand.index, cand.name, vk::to_string(cand.type),
442 vk::to_string(cand.driver_id), cand.driver_name,
443 VK_API_VERSION_MAJOR(cand.api_version),
444 VK_API_VERSION_MINOR(cand.api_version),
445 VK_API_VERSION_PATCH(cand.api_version),
446 cand.uuid_hex,
447 cand.families.graphics_family.has_value() ? std::to_string(cand.families.graphics_family.value()) : "none",
448 cand.families.compute_family.has_value() ? std::to_string(cand.families.compute_family.value()) : "none",
449 cand.families.transfer_family.has_value() ? std::to_string(cand.families.transfer_family.value()) : "none",
450 cand.graphics_presents ? "yes" : "no",
451 cand.present_family_mask,
452 cand.has_mesh_shader ? "yes" : "no",
453 cand.device_local_bytes >> 20U,
454 cand.has_pci_info ? std::to_string(cand.pci_bus) : "n/a",
455 cand.score,
456 cand.viable() ? "" : " | REJECTED: ",
457 cand.reject_reason ? cand.reject_reason : "");
458 }
459
461 "Presentation probe mechanism: {}, require_presentation={}, device_preference={}",
462 probe.mechanism, backend_info.require_presentation ? "true" : "false",
464
465 int32_t want_index = backend_info.device_index;
466 std::string want_name = backend_info.device_name;
467 std::string want_uuid = backend_info.device_uuid;
468
469 if (int32_t env_index = -1; read_gpu_env(env_index, want_name)) {
470 want_index = env_index;
471 want_uuid.clear();
473 "MAYAFLUX_GPU override active (index={}, name='{}')", want_index, want_name);
474 }
475
476 const DeviceCandidate* selected = nullptr;
477 const char* selection_basis = "score";
478
479 if (want_index >= 0) {
480 for (const auto& cand : candidates) {
481 if (static_cast<int32_t>(cand.index) == want_index && cand.viable()) {
482 selected = &cand;
483 selection_basis = "device_index";
484 break;
485 }
486 }
487 }
488
489 if (!selected && !want_uuid.empty()) {
490 for (const auto& cand : candidates) {
491 if (cand.uuid_hex == want_uuid && cand.viable()) {
492 selected = &cand;
493 selection_basis = "device_uuid";
494 break;
495 }
496 }
497 }
498
499 if (!selected && !want_name.empty()) {
500 for (const auto& cand : candidates) {
501 if (!cand.viable() || !contains_nocase(cand.name, want_name))
502 continue;
503 if (!selected || cand.score > selected->score) {
504 selected = &cand;
505 selection_basis = "device_name";
506 }
507 }
508 }
509
510 const bool selector_requested = want_index >= 0 || !want_uuid.empty() || !want_name.empty();
511
512 if (selector_requested && !selected) {
513 if (backend_info.strict_device_selection) {
515 std::source_location::current(),
516 "No viable physical device matched the requested selector "
517 "(index={}, uuid='{}', name='{}') and strict_device_selection is set",
518 want_index, want_uuid, want_name);
519 }
520
522 "No viable physical device matched the requested selector "
523 "(index={}, uuid='{}', name='{}'); falling back to score",
524 want_index, want_uuid, want_name);
525 }
526
527 if (!selected) {
528 for (const auto& cand : candidates) {
529 if (!cand.viable())
530 continue;
531 if (!selected || cand.score > selected->score)
532 selected = &cand;
533 }
534 }
535
536 if (!selected) {
538 std::source_location::current(),
539 "No suitable GPU found among {} enumerated device(s); "
540 "see the candidate list above for rejection reasons",
541 candidates.size());
542 }
543
544 m_physical_device = selected->device;
545 m_queue_families = selected->families;
546 m_supports_mesh_shaders = selected->has_mesh_shader;
547 m_graphics_presents = selected->graphics_presents;
548 m_device_name = selected->name;
549 m_device_uuid = selected->uuid;
550
552 "Selected GPU [{}] {} by {} (uuid={}, score={})",
553 selected->index, selected->name, selection_basis, selected->uuid_hex, selected->score);
554
555 return true;
556}
557
559{
560 QueueFamilyIndices indices;
561 auto queue_families = device.getQueueFamilyProperties();
562
563 int i = 0;
564 for (const auto& queue_family : queue_families) {
565 if (queue_family.queueCount > 0 && queue_family.queueFlags & vk::QueueFlagBits::eGraphics) {
566 indices.graphics_family = i;
567 }
568
569 if (queue_family.queueCount > 0 && queue_family.queueFlags & vk::QueueFlagBits::eCompute && !(queue_family.queueFlags & vk::QueueFlagBits::eGraphics)) {
570 indices.compute_family = i;
571 }
572
573 if (queue_family.queueCount > 0 && queue_family.queueFlags & vk::QueueFlagBits::eTransfer && !(queue_family.queueFlags & vk::QueueFlagBits::eGraphics) && !(queue_family.queueFlags & vk::QueueFlagBits::eCompute)) {
574 indices.transfer_family = i;
575 }
576
577 i++;
578 }
579
580 if (indices.graphics_family.has_value()) {
581 if (!indices.compute_family.has_value()) {
582 indices.compute_family = indices.graphics_family;
583 }
584 if (!indices.transfer_family.has_value()) {
585 indices.transfer_family = indices.graphics_family;
586 }
587 }
588
589 return indices;
590}
591
592bool VKDevice::graphics_family_can_present(vk::SurfaceKHR surface) const
593{
594 if (!surface || !m_queue_families.graphics_family.has_value())
595 return false;
596
597 return m_physical_device.getSurfaceSupportKHR(
598 m_queue_families.graphics_family.value(), surface)
599 == VK_TRUE;
600}
601
602vk::Queue VKDevice::get_present_queue(uint32_t family_index) const
603{
604 auto it = m_present_queues.find(family_index);
605 return it != m_present_queues.end() ? it->second : nullptr;
606}
607
609{
611 return family.has_value() ? get_present_queue(family.value()) : nullptr;
612}
613
615{
616 std::vector<vk::ExtensionProperties> availableExtensions = m_physical_device.enumerateDeviceExtensionProperties();
617
618 MF_LOG(Journal::Component::Core, Journal::Context::GraphicsBackend, "Available physical device extensions:");
619 for (const auto& extension : availableExtensions) {
620 std::cout << "\t- " << extension.extensionName << " (Version: " << extension.specVersion << ")\n";
621 }
623}
624
625bool VKDevice::create_logical_device(vk::Instance /*instance*/, const GraphicsBackendInfo& backend_info)
626{
627 if (!m_queue_families.graphics_family.has_value()) {
629 std::source_location::current(),
630 "No graphics queue family found!");
631 }
632
633 std::set<uint32_t> unique_queue_families;
634 unique_queue_families.insert(m_queue_families.graphics_family.value());
635
636 if (m_queue_families.compute_family.has_value()) {
637 unique_queue_families.insert(m_queue_families.compute_family.value());
638 }
639
641 unique_queue_families.insert(m_queue_families.transfer_family.value());
642 }
643
644 for (uint32_t f = 0; f < QueueFamilyIndices::MAX_TRACKED_FAMILIES; ++f) {
646 unique_queue_families.insert(f);
647 }
648
649 std::vector<vk::DeviceQueueCreateInfo> queue_create_infos;
650 float queue_priority = 1.0F;
651
652 for (uint32_t queue_family : unique_queue_families) {
653 vk::DeviceQueueCreateInfo queue_create_info {};
654 queue_create_info.queueFamilyIndex = queue_family;
655 queue_create_info.queueCount = 1;
656 queue_create_info.pQueuePriorities = &queue_priority;
657 queue_create_infos.push_back(queue_create_info);
658 }
659
660 vk::PhysicalDeviceFeatures device_features {};
661 device_features.samplerAnisotropy = backend_info.required_features.sampler_anisotropy;
662 device_features.geometryShader = backend_info.required_features.geometry_shaders;
663 device_features.tessellationShader = backend_info.required_features.tessellation_shaders;
664 device_features.multiViewport = backend_info.required_features.multi_viewport;
665 device_features.fillModeNonSolid = backend_info.required_features.fill_mode_non_solid;
666
667 std::vector<const char*> device_extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
668
669 auto supported_extensions = m_physical_device.enumerateDeviceExtensionProperties();
670
671 auto is_supported = [&supported_extensions](std::string_view name) {
672 return std::ranges::any_of(supported_extensions, [name](const auto& ext) {
673 return name == ext.extensionName.data();
674 });
675 };
676
677#ifdef MAYAFLUX_PLATFORM_MACOS
678 if (is_supported("VK_KHR_portability_subset")) {
679 device_extensions.push_back("VK_KHR_portability_subset");
680 }
681
682 auto feature_chain = vk::StructureChain {
683 vk::PhysicalDeviceFeatures2 {},
684 vk::PhysicalDeviceVulkan13Features {},
685 vk::PhysicalDeviceVulkan12Features {}
686 };
687
688#else
690 device_extensions.push_back(VK_EXT_MESH_SHADER_EXTENSION_NAME);
691 }
692
693 auto feature_chain = vk::StructureChain {
694 vk::PhysicalDeviceFeatures2 {},
695 vk::PhysicalDeviceVulkan13Features {},
696 vk::PhysicalDeviceVulkan12Features {},
697 vk::PhysicalDeviceMeshShaderFeaturesEXT {}
698 };
699
701 feature_chain.unlink<vk::PhysicalDeviceMeshShaderFeaturesEXT>();
702 } else {
703 feature_chain.get<vk::PhysicalDeviceMeshShaderFeaturesEXT>().taskShader = VK_TRUE;
704 feature_chain.get<vk::PhysicalDeviceMeshShaderFeaturesEXT>().meshShader = VK_TRUE;
705 }
706#endif
707
708 feature_chain.get<vk::PhysicalDeviceFeatures2>().features = device_features;
709 feature_chain.get<vk::PhysicalDeviceVulkan13Features>().dynamicRendering = VK_TRUE;
710 feature_chain.get<vk::PhysicalDeviceVulkan13Features>().synchronization2 = VK_TRUE;
711 feature_chain.get<vk::PhysicalDeviceVulkan12Features>().bufferDeviceAddress = VK_TRUE;
712
713 std::vector<std::string> missing_required;
714
715 for (const auto& ext : backend_info.required_extensions) {
716 if (is_supported(ext)) {
717 device_extensions.push_back(ext.c_str());
718 } else {
719 missing_required.push_back(ext);
720 }
721 }
722
723 if (!missing_required.empty()) {
724 std::string names;
725 for (const auto& ext : missing_required) {
726 if (!names.empty())
727 names += ", ";
728 names += ext;
729 }
730
732 std::source_location::current(),
733 "Device '{}' does not support required extension(s): {}",
734 m_device_name, names);
735 }
736
737 for (const auto& ext : backend_info.optional_extensions) {
738 if (is_supported(ext)) {
739 device_extensions.push_back(ext.c_str());
740 } else {
742 "Device '{}' does not support optional extension '{}'; skipping",
743 m_device_name, ext);
744 }
745 }
746
747 vk::DeviceCreateInfo create_info {};
748 create_info.queueCreateInfoCount = static_cast<uint32_t>(queue_create_infos.size());
749 create_info.pQueueCreateInfos = queue_create_infos.data();
750 create_info.pNext = &feature_chain.get<vk::PhysicalDeviceFeatures2>();
751 create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
752 create_info.ppEnabledExtensionNames = device_extensions.data();
753
754 try {
755 m_logical_device = m_physical_device.createDevice(create_info);
756 VULKAN_HPP_DEFAULT_DISPATCHER.init(m_logical_device);
757
758 } catch (const std::exception& e) {
760 std::source_location::current(),
761 "Failed to create logical device: {}", e.what());
762 }
763
765
766 if (backend_info.enable_compute_queue && m_queue_families.compute_family.has_value()) {
768 } else {
770 }
771
772 if (backend_info.enable_transfer_queue && m_queue_families.transfer_family.has_value()) {
774 } else {
776 }
777
778 for (uint32_t f = 0; f < QueueFamilyIndices::MAX_TRACKED_FAMILIES; ++f) {
780 m_present_queues[f] = m_logical_device.getQueue(f, 0);
781 }
782
783 return true;
784}
785
786}
#define MF_INFO(comp, ctx,...)
#define MF_LOG(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
uint32_t h
Definition InkPress.cpp:28
size_t a
size_t b
bool has_mesh_shader
Definition VKDevice.cpp:152
bool graphics_presents
Definition VKDevice.cpp:154
vk::PhysicalDevice device
Definition VKDevice.cpp:141
bool has_swapchain
Definition VKDevice.cpp:151
vk::PhysicalDeviceType type
Definition VKDevice.cpp:146
vk::DriverId driver_id
Definition VKDevice.cpp:148
const char * reject_reason
Definition VKDevice.cpp:159
uint32_t api_version
Definition VKDevice.cpp:147
std::array< uint8_t, VK_UUID_SIZE > uuid
Definition VKDevice.cpp:145
vk::Instance instance
Definition VKDevice.cpp:50
uint64_t device_local_bytes
Definition VKDevice.cpp:157
QueueFamilyIndices families
Definition VKDevice.cpp:150
bool has_pci_info
Definition VKDevice.cpp:155
uint32_t pci_bus
Definition VKDevice.cpp:156
bool available
Definition VKDevice.cpp:52
std::string name
Definition VKDevice.cpp:143
std::string uuid_hex
Definition VKDevice.cpp:144
uint32_t index
Definition VKDevice.cpp:142
int64_t score
Definition VKDevice.cpp:158
std::string driver_name
Definition VKDevice.cpp:149
const char * mechanism
Definition VKDevice.cpp:53
uint32_t present_family_mask
Definition VKDevice.cpp:153
void * native_display
Definition VKDevice.cpp:51
float value
static QueueFamilyIndices find_queue_families(vk::PhysicalDevice device)
Find queue families on the given physical device.
Definition VKDevice.cpp:558
void query_supported_extensions()
Query and log supported device extensions.
Definition VKDevice.cpp:614
std::array< uint8_t, VK_UUID_SIZE > m_device_uuid
Selected device UUID.
Definition VKDevice.hpp:208
bool graphics_family_can_present(vk::SurfaceKHR surface) const
Confirm the graphics family presents to a concrete surface.
Definition VKDevice.cpp:592
void cleanup()
Cleanup device resources.
Definition VKDevice.cpp:266
bool m_supports_mesh_shaders
Whether the device supports mesh shaders.
Definition VKDevice.hpp:209
bool pick_physical_device(vk::Instance instance, const GraphicsBackendInfo &backend_info)
Select a physical device by config selector or score.
Definition VKDevice.cpp:285
VKDevice & operator=(const VKDevice &)=delete
bool initialize(vk::Instance instance, const GraphicsBackendInfo &backend_info)
Select a physical device and create the logical device.
Definition VKDevice.cpp:257
vk::PhysicalDevice m_physical_device
Selected physical device (GPU)
Definition VKDevice.hpp:172
vk::Queue get_preferred_present_queue() const
Queue for the preferred presentation family.
Definition VKDevice.cpp:608
vk::Queue m_compute_queue
Compute queue handle.
Definition VKDevice.hpp:176
bool create_logical_device(vk::Instance instance, const GraphicsBackendInfo &backend_info)
Create the logical device and retrieve queue handles.
Definition VKDevice.cpp:625
vk::Queue m_transfer_queue
Transfer queue handle.
Definition VKDevice.hpp:177
vk::Device m_logical_device
Logical device handle.
Definition VKDevice.hpp:173
bool m_graphics_presents
Graphics family passed the surfaceless presentation probe.
Definition VKDevice.hpp:205
std::unordered_map< uint32_t, vk::Queue > m_present_queues
One queue per presentation-capable family.
Definition VKDevice.hpp:206
std::string m_device_name
Selected device name, cached for logging.
Definition VKDevice.hpp:207
vk::Queue get_present_queue(uint32_t family_index) const
Queue for a presentation-capable family.
Definition VKDevice.cpp:602
vk::Queue m_graphics_queue
Graphics queue handle.
Definition VKDevice.hpp:175
QueueFamilyIndices m_queue_families
Indices of required queue families.
Definition VKDevice.hpp:179
Manages Vulkan physical device selection and logical device creation.
Definition VKDevice.hpp:63
@ GraphicsBackend
Graphics/visual rendering backend (Vulkan, OpenGL)
@ Core
Core engine, backend, subsystems.
constexpr std::string_view enum_to_string(EnumType value) noexcept
Universal enum to string converter using magic_enum (original case)
enum MayaFlux::Core::GraphicsBackendInfo::DevicePreference device_preference
bool enable_compute_queue
Enable compute queue (separate from graphics)
std::vector< std::string > required_extensions
Backend-specific extensions to request.
bool enable_transfer_queue
Enable transfer queue (separate from graphics)
struct MayaFlux::Core::GraphicsBackendInfo::@0 required_features
Required device features (Vulkan-specific)
std::vector< std::string > optional_extensions
std::string device_name
Case-insensitive substring of the device name; empty disables.
int32_t device_index
Index into enumeration order; negative disables.
bool require_presentation
Require the graphics queue family to support presentation.
bool strict_device_selection
Treat an unmatched selector as fatal rather than falling back to scoring.
std::string device_uuid
Device UUID as 32 lowercase hex chars, no separators; empty disables.
Configuration for graphics API backend (Vulkan/OpenGL/etc.)
static constexpr uint32_t MAX_TRACKED_FAMILIES
Width of present_family_mask.
Definition VKDevice.hpp:18
bool can_present(uint32_t index) const
Whether family index can present.
Definition VKDevice.hpp:33
std::optional< uint32_t > transfer_family
Definition VKDevice.hpp:22
std::optional< uint32_t > graphics_family
Definition VKDevice.hpp:20
std::optional< uint32_t > preferred_present_family() const
Family the engine presents on by default.
Definition VKDevice.hpp:43
std::optional< uint32_t > compute_family
Definition VKDevice.hpp:21
Stores indices of queue families we need.
Definition VKDevice.hpp:17