VEX Engine
Retro looking game engine made in vulkan mostly because i wanted to understand it better.
Loading...
Searching...
No Matches
Interface.cpp
1#define VMA_IMPLEMENTATION
2#define VMA_STATIC_VULKAN_FUNCTIONS 0
3#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
4
5#include "Interface.hpp"
6#include <vector>
7#include "components/ErrorUtils.hpp"
8#include <algorithm>
9#include <set>
10#include <fstream>
11
12#include <immintrin.h>
13#include "components/HardwareInfo.hpp"
14
15namespace vex {
16
17bool Interface::CheckValidationLayerSupport() {
18 uint32_t layerCount;
19 vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
20 std::vector<VkLayerProperties> availableLayers(layerCount);
21 vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
22
23 for (const auto& layerProperties : availableLayers) {
24 if (strcmp("VK_LAYER_KHRONOS_validation", layerProperties.layerName) == 0) {
25 return true;
26 }
27 }
28 return false;
29}
30
31uint32_t Interface::GetBestDeviceVersion() {
32 uint32_t bestVersion = VK_API_VERSION_1_0;
33
34 VkApplicationInfo appInfo = { VK_STRUCTURE_TYPE_APPLICATION_INFO };
35 appInfo.apiVersion = VK_API_VERSION_1_0;
36 appInfo.pApplicationName = "VEX_PROBE";
37 appInfo.pEngineName = "VEX_PROBE";
38
39 VkInstanceCreateInfo createInfo = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO };
40 createInfo.pApplicationInfo = &appInfo;
41
42 VkInstance probeInstance = VK_NULL_HANDLE;
43 if (vkCreateInstance(&createInfo, nullptr, &probeInstance) != VK_SUCCESS) {
44 return VK_API_VERSION_1_1;
45 }
46
47 volkLoadInstance(probeInstance);
48
49 uint32_t gpuCount = 0;
50 vkEnumeratePhysicalDevices(probeInstance, &gpuCount, nullptr);
51
52 if (gpuCount > 0) {
53 std::vector<VkPhysicalDevice> devices(gpuCount);
54 vkEnumeratePhysicalDevices(probeInstance, &gpuCount, devices.data());
55
56 for (const auto& device : devices) {
57 VkPhysicalDeviceProperties props;
58 vkGetPhysicalDeviceProperties(device, &props);
59
60 if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
61 if (props.apiVersion > bestVersion) {
62 bestVersion = props.apiVersion;
63 }
64 } else if (bestVersion == VK_API_VERSION_1_0) {
65 bestVersion = props.apiVersion;
66 }
67 }
68 }
69
70 vkDestroyInstance(probeInstance, nullptr);
71 return bestVersion;
72}
73
74 Interface::Interface(SDL_Window* window, glm::uvec2 initialResolution, GameInfo gInfo, VirtualFileSystem* vfs) : m_p_window(window), m_vfs(vfs) {
75
76 try {
77
78 log("Loading Vulkan library...");
79 if (!SDL_Vulkan_LoadLibrary(nullptr)) {
80 throw_error(SDL_GetError());
81 }
82
83 log("Initializing Volk...");
84 volkInitializeCustom(reinterpret_cast<PFN_vkGetInstanceProcAddr>(SDL_Vulkan_GetVkGetInstanceProcAddr()));
85
86 uint32_t loaderVersion = VK_API_VERSION_1_0;
87 if (vkEnumerateInstanceVersion) {
88 vkEnumerateInstanceVersion(&loaderVersion);
89 }
90
91 uint32_t deviceVersion = GetBestDeviceVersion();
92
93 uint32_t apiVersion = std::min(loaderVersion, deviceVersion);
94
95 if (apiVersion > VK_API_VERSION_1_3) {
96 apiVersion = VK_API_VERSION_1_3;
97 }
98
99 log("API Negotiation: Loader %u.%u | Device %u.%u -> Selected %u.%u",
100 VK_VERSION_MAJOR(loaderVersion), VK_VERSION_MINOR(loaderVersion),
101 VK_VERSION_MAJOR(deviceVersion), VK_VERSION_MINOR(deviceVersion),
102 VK_VERSION_MAJOR(apiVersion), VK_VERSION_MINOR(apiVersion));
103
104 m_context.vulkanVersion = apiVersion;
105 m_context.currentRenderResolution = initialResolution;
106 m_context.graphicsQueueFamily = UINT32_MAX;
107 m_context.presentQueueFamily = UINT32_MAX;
108
109 log("Creating Vulkan instance...");
110 uint32_t sdlExtensionCount = 0;
111 const char* const* sdlExtensions = SDL_Vulkan_GetInstanceExtensions(&sdlExtensionCount);
112 std::vector<const char*> extensions(sdlExtensions, sdlExtensions + sdlExtensionCount);
113
114 extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
115#ifdef __APPLE__
116 extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
117#endif
118
119#if DEBUG
120 std::vector<const char*> validationLayers;
121 if (CheckValidationLayerSupport()) {
122 validationLayers.push_back("VK_LAYER_KHRONOS_validation");
123 } else {
124 log(LogLevel::WARNING, "Validation layers requested but not available!");
125 }
126#else
127 const std::vector<const char*> validationLayers;
128#endif
129
130 VkApplicationInfo appInfo = {};
131 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
132 appInfo.pApplicationName = gInfo.projectName.c_str();
133 appInfo.applicationVersion = VK_MAKE_VERSION(gInfo.versionMajor, gInfo.versionMinor, gInfo.versionPatch);
134 appInfo.pEngineName = "VEX";
135 appInfo.engineVersion = VK_MAKE_VERSION(VEX_VERSION_MAJOR, VEX_VERSION_MINOR, VEX_VERSION_PATCH);
136 appInfo.apiVersion = apiVersion;
137
138 VkInstanceCreateInfo createInfo = {};
139 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
140 createInfo.pApplicationInfo = &appInfo;
141 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
142 createInfo.ppEnabledExtensionNames = extensions.data();
143 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
144 createInfo.ppEnabledLayerNames = validationLayers.data();
145
146#ifdef __APPLE__
147 createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
148#endif
149
150 if (vkCreateInstance(&createInfo, nullptr, &m_context.instance) != VK_SUCCESS) {
151 throw_error("Failed to create Vulkan instance");
152 }
153
154 volkLoadInstance(m_context.instance);
155
156 log("Binding window...");
157 if (!SDL_Vulkan_CreateSurface(window, m_context.instance, nullptr, &m_context.surface)) {
158 throw_error("Failed to create Vulkan surface: " + std::string(SDL_GetError()));
159 }
160
161 uint32_t deviceCount = 0;
162 vkEnumeratePhysicalDevices(m_context.instance, &deviceCount, nullptr);
163 if (deviceCount == 0) {
164 throw_error("Failed to find GPUs with Vulkan support");
165 }
166
167 std::vector<VkPhysicalDevice> devices(deviceCount);
168 vkEnumeratePhysicalDevices(m_context.instance, &deviceCount, devices.data());
169
170 VkPhysicalDevice selectedDevice = VK_NULL_HANDLE;
171 int bestScore = -1;
172
173 for (const auto& device : devices) {
174 VkPhysicalDeviceProperties deviceProperties;
175 vkGetPhysicalDeviceProperties(device, &deviceProperties);
176
177 log("Avaiable GPU (%s): %s", deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU ? "DISCRETE" : "INTEGRATED", deviceProperties.deviceName);
178 int score = 0;
179
180 bool hasCore13 = deviceProperties.apiVersion >= VK_API_VERSION_1_3;
181
182 uint32_t extCount;
183 vkEnumerateDeviceExtensionProperties(device, nullptr, &extCount, nullptr);
184 std::vector<VkExtensionProperties> availableExtensions(extCount);
185 vkEnumerateDeviceExtensionProperties(device, nullptr, &extCount, availableExtensions.data());
186
187 bool hasKHR = false;
188 for (const auto& ext : availableExtensions) {
189 if (strcmp(ext.extensionName, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME) == 0) {
190 hasKHR = true;
191 break;
192 }
193 }
194
195 if (!hasCore13 && !hasKHR) {
196 log("Skipping GPU %s due to lack of Vulkan 1.3 or KHR dynamic rendering extension", deviceProperties.deviceName);
197 continue;
198 }
199
200 uint32_t formatCount;
201 vkGetPhysicalDeviceSurfaceFormatsKHR(device, m_context.surface, &formatCount, nullptr);
202
203 uint32_t presentModeCount;
204 vkGetPhysicalDeviceSurfacePresentModesKHR(device, m_context.surface, &presentModeCount, nullptr);
205
206 if (formatCount == 0 || presentModeCount == 0) {
207 log("Skipping GPU %s due to lack of formats or present modes", deviceProperties.deviceName);
208 continue;
209 }
210
211
212 if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
213 {
214 score += 1000;
215 }
216
217 if (hasCore13){
218 score += 250;
219 }
220
221 uint32_t queueFamilyCount = 0;
222 vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
223 std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
224 vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
225 uint32_t graphicsIdx = UINT32_MAX;
226 uint32_t presentIdx = UINT32_MAX;
227
228 int i = 0;
229 for (const auto& queueFamily : queueFamilies) {
230 bool hasGraphics = queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT;
231 VkBool32 hasPresent = false;
232 vkGetPhysicalDeviceSurfaceSupportKHR(device, i, m_context.surface, &hasPresent);
233
234 if (hasGraphics && hasPresent) {
235 graphicsIdx = i;
236 presentIdx = i;
237 break;
238 }
239
240 if (hasGraphics && graphicsIdx == UINT32_MAX) {
241 graphicsIdx = i;
242 }
243 if (hasPresent && presentIdx == UINT32_MAX) {
244 presentIdx = i;
245 }
246 i++;
247 }
248
249 if (graphicsIdx != UINT32_MAX && presentIdx != UINT32_MAX)
250 {
251 if (score > bestScore)
252 {
253 bestScore = score;
254 selectedDevice = device;
255 m_context.graphicsQueueFamily = graphicsIdx;
256 m_context.presentQueueFamily = presentIdx;
257 }
258 }
259 }
260
261 VkPhysicalDeviceProperties deviceProperties;
262
263 if (selectedDevice != VK_NULL_HANDLE)
264 {
265 vkGetPhysicalDeviceProperties(selectedDevice, &deviceProperties);
266 log("Selected GPU: %s", deviceProperties.deviceName);
267
269 deviceProperties.deviceName,
270 deviceProperties.vendorID,
271 deviceProperties.driverVersion
272 );
273
274 m_context.physicalDevice = selectedDevice;
275 }
276
277 if (m_context.physicalDevice == VK_NULL_HANDLE) {
278 throw_error("Failed to find a suitable GPU");
279 }
280
281 if (m_context.graphicsQueueFamily == UINT32_MAX) {
282 throw_error("Failed to find graphics queue family - GPU may not support graphics operations");
283 }
284 if (m_context.presentQueueFamily == UINT32_MAX) {
285 throw_error("Failed to find present queue family - GPU may not support presentation");
286 }
287
288 std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
289 std::set<uint32_t> uniqueQueueFamilies = {m_context.graphicsQueueFamily, m_context.presentQueueFamily};
290
291 float queuePriority = 1.0f;
292 for (uint32_t queueFamily : uniqueQueueFamilies) {
293 VkDeviceQueueCreateInfo queueCreateInfo = {};
294 queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
295 queueCreateInfo.queueFamilyIndex = queueFamily;
296 queueCreateInfo.queueCount = 1;
297 queueCreateInfo.pQueuePriorities = &queuePriority;
298 queueCreateInfos.push_back(queueCreateInfo);
299 }
300
301 std::vector<const char*> deviceExtensions = {
302 VK_KHR_SWAPCHAIN_EXTENSION_NAME,
303 VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME
304 };
305
306 uint32_t extCount = 0;
307 vkEnumerateDeviceExtensionProperties(m_context.physicalDevice, nullptr, &extCount, nullptr);
308 std::vector<VkExtensionProperties> availableExts(extCount);
309 vkEnumerateDeviceExtensionProperties(m_context.physicalDevice, nullptr, &extCount, availableExts.data());
310
311 for(const auto& ext : availableExts) {
312 if(strcmp(ext.extensionName, "VK_KHR_portability_subset") == 0) {
313 deviceExtensions.push_back("VK_KHR_portability_subset");
314 break;
315 }
316 }
317
318 if (apiVersion < VK_API_VERSION_1_3) {
319 deviceExtensions.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
320 deviceExtensions.push_back(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
321 deviceExtensions.push_back(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME);
322 }
323
324 if (apiVersion < VK_API_VERSION_1_2) {
325 deviceExtensions.push_back("VK_EXT_descriptor_indexing");
326 }
327
328 VkPhysicalDeviceFeatures2 deviceFeatures2 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 };
329 VkPhysicalDeviceVulkan11Features features11 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES };
330 VkPhysicalDeviceVulkan12Features features12 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES };
331 VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES };
332 VkPhysicalDeviceMultiDrawFeaturesEXT multiDrawFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTI_DRAW_FEATURES_EXT };
333 VkPhysicalDeviceExtendedDynamicState2FeaturesEXT extendedDynamicState2Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_2_FEATURES_EXT };
334
335 void** tail = &deviceFeatures2.pNext;
336
337 *tail = &features11;
338 tail = &features11.pNext;
339
340 if (apiVersion >= VK_API_VERSION_1_2) {
341 *tail = &features12;
342 tail = &features12.pNext;
343 }
344
345 *tail = &dynamicRenderingFeatures;
346 tail = &dynamicRenderingFeatures.pNext;
347
348 *tail = &multiDrawFeatures;
349 tail = &multiDrawFeatures.pNext;
350
351 *tail = &extendedDynamicState2Features;
352 tail = &extendedDynamicState2Features.pNext;
353
354 *tail = nullptr;
355
356 vkGetPhysicalDeviceFeatures2(m_context.physicalDevice, &deviceFeatures2);
357
358 if (deviceFeatures2.features.samplerAnisotropy) {
359 deviceFeatures2.features.samplerAnisotropy = VK_TRUE;
360 } else {
361 log(LogLevel::WARNING, "Sampler Anisotropy not supported.");
362 }
363
364 if (deviceFeatures2.features.multiDrawIndirect) {
365 m_context.supportsIndirectDraw = true;
366 VkPhysicalDeviceProperties properties;
367 vkGetPhysicalDeviceProperties(m_context.physicalDevice, &properties);
368 m_context.maxDrawIndirectCount = properties.limits.maxDrawIndirectCount;
369 } else {
370 m_context.supportsIndirectDraw = false;
371 deviceFeatures2.features.multiDrawIndirect = VK_FALSE;
372 }
373
374 if (features11.shaderDrawParameters) {
375 features11.shaderDrawParameters = VK_TRUE;
376 m_context.supportsShaderDrawParameters = true;
377 } else {
378 features11.shaderDrawParameters = VK_FALSE;
379 m_context.supportsShaderDrawParameters = false;
380 }
381
382 if (apiVersion >= VK_API_VERSION_1_2) {
383 VkPhysicalDeviceDescriptorIndexingProperties indexingProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES };
384 VkPhysicalDeviceProperties2 props2 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 };
385 props2.pNext = &indexingProps;
386 vkGetPhysicalDeviceProperties2(m_context.physicalDevice, &props2);
387
388 const uint32_t requiredBindlessCount = MAX_TEXTURES;
389 uint32_t samplerLimit = indexingProps.maxPerStageDescriptorUpdateAfterBindSamplers;
390 uint32_t imageLimit = indexingProps.maxPerStageDescriptorUpdateAfterBindSampledImages;
391
392 log("Bindless Limits -> Samplers: %u | Images: %u (Req: %u)", samplerLimit, imageLimit, requiredBindlessCount);
393
394 if (imageLimit < requiredBindlessCount || samplerLimit < requiredBindlessCount) {
395 log(LogLevel::WARNING, "Device limits too low. Forcing Bindless OFF to prevent validation errors.");
396 m_context.supportsBindlessTextures = false;
397 } else {
398 m_context.supportsBindlessTextures =
399 features12.descriptorBindingPartiallyBound &&
400 features12.runtimeDescriptorArray;
401 }
402 } else {
403 m_context.supportsBindlessTextures = false;
404 }
405
406 if (apiVersion >= VK_API_VERSION_1_2) {
407 if (m_context.supportsBindlessTextures) {
408 features12.descriptorBindingPartiallyBound = VK_TRUE;
409 features12.runtimeDescriptorArray = VK_TRUE;
410 features12.shaderSampledImageArrayNonUniformIndexing = VK_TRUE;
411 features12.descriptorBindingSampledImageUpdateAfterBind = VK_TRUE;
412 features12.descriptorBindingVariableDescriptorCount = VK_TRUE;
413 } else {
414 features12.descriptorBindingPartiallyBound = VK_FALSE;
415 features12.runtimeDescriptorArray = VK_FALSE;
416 features12.shaderSampledImageArrayNonUniformIndexing = VK_FALSE;
417 features12.descriptorBindingVariableDescriptorCount = VK_FALSE;
418
419 features12.descriptorBindingSampledImageUpdateAfterBind = VK_FALSE;
420 features12.descriptorBindingStorageImageUpdateAfterBind = VK_FALSE;
421
422 features12.descriptorBindingUniformBufferUpdateAfterBind = VK_FALSE;
423 features12.descriptorBindingStorageBufferUpdateAfterBind = VK_FALSE;
424 features12.descriptorBindingUniformTexelBufferUpdateAfterBind = VK_FALSE;
425 features12.descriptorBindingStorageTexelBufferUpdateAfterBind = VK_FALSE;
426 }
427
428 if (deviceFeatures2.features.robustBufferAccess) {
429 features12.descriptorBindingUniformBufferUpdateAfterBind = VK_FALSE;
430 features12.descriptorBindingStorageBufferUpdateAfterBind = VK_FALSE;
431 features12.descriptorBindingUniformTexelBufferUpdateAfterBind = VK_FALSE;
432 features12.descriptorBindingStorageTexelBufferUpdateAfterBind = VK_FALSE;
433 }
434 }
435
436 if (dynamicRenderingFeatures.dynamicRendering) {
437 dynamicRenderingFeatures.dynamicRendering = VK_TRUE;
438 } else {
439 throw_error("Dynamic Rendering not supported by GPU!");
440 }
441
442 if (multiDrawFeatures.multiDraw) {
443 m_context.supportsMultiDraw = true;
444 multiDrawFeatures.multiDraw = VK_TRUE;
445 deviceExtensions.push_back(VK_EXT_MULTI_DRAW_EXTENSION_NAME);
446
447 VkPhysicalDeviceMultiDrawPropertiesEXT multiDrawProps{};
448 multiDrawProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTI_DRAW_PROPERTIES_EXT;
449
450 VkPhysicalDeviceProperties2 props2{};
451 props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
452 props2.pNext = &multiDrawProps;
453
454 vkGetPhysicalDeviceProperties2(m_context.physicalDevice, &props2);
455 m_context.maxMultiDrawCount = multiDrawProps.maxMultiDrawCount;
456 } else {
457 m_context.supportsMultiDraw = false;
458 multiDrawFeatures.multiDraw = VK_FALSE;
459 }
460
461 if (extendedDynamicState2Features.extendedDynamicState2) {
462 extendedDynamicState2Features.extendedDynamicState2 = VK_TRUE;
463 } else {
464 extendedDynamicState2Features.extendedDynamicState2 = VK_FALSE;
465 }
466 extendedDynamicState2Features.extendedDynamicState2LogicOp =
467 extendedDynamicState2Features.extendedDynamicState2LogicOp ? VK_TRUE : VK_FALSE;
468 extendedDynamicState2Features.extendedDynamicState2PatchControlPoints =
469 extendedDynamicState2Features.extendedDynamicState2PatchControlPoints ? VK_TRUE : VK_FALSE;
470
471 VkDeviceCreateInfo deviceCreateInfo = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
472 deviceCreateInfo.pNext = &deviceFeatures2;
473 deviceCreateInfo.pEnabledFeatures = nullptr;
474
475 deviceCreateInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
476 deviceCreateInfo.pQueueCreateInfos = queueCreateInfos.data();
477 deviceCreateInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
478 deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data();
479
480 if (vkCreateDevice(m_context.physicalDevice, &deviceCreateInfo, nullptr, &m_context.device) != VK_SUCCESS) {
481 throw_error("Failed to create logical device");
482 }
483
484 uint32_t deviceApiVersion = deviceProperties.apiVersion;
485
486 uint32_t major = VK_VERSION_MAJOR(deviceApiVersion);
487 uint32_t minor = VK_VERSION_MINOR(deviceApiVersion);
488 uint32_t patch = VK_VERSION_PATCH(deviceApiVersion);
489
490 std::stringstream deviceVerSS, reqVerSS;
491 deviceVerSS << major << "." << minor << "." << patch;
492 reqVerSS << VK_VERSION_MAJOR(apiVersion) << "." << VK_VERSION_MINOR(apiVersion);
493
494 HardwareInfo::SetVulkanVersions(deviceVerSS.str(), reqVerSS.str());
495
496 VulkanFeatures features;
497 features.multiDraw = m_context.supportsMultiDraw;
498 features.indirectDraw = m_context.supportsIndirectDraw;
499 features.bindlessTextures = m_context.supportsBindlessTextures;
500 features.shaderDrawParameters = m_context.supportsShaderDrawParameters;
501
503
504 log(" ======= Supported Features =======");
505 log("GPU:");
506 log("Vulkan Device API version: %u.%u.%u", major, minor, patch);
507 log("Vulkan Requested API version: %u.%u", VK_VERSION_MAJOR(apiVersion), VK_VERSION_MINOR(apiVersion));
508 log("supportsMultiDraw: %s", m_context.supportsMultiDraw ? "true" : "false");
509 log("supportsIndirectDraw: %s", m_context.supportsIndirectDraw ? "true" : "false");
510 log("supportsBindlessTextures: %s", m_context.supportsBindlessTextures ? "true" : "false");
511 log("supportsShaderDrawParameters: %s", m_context.supportsShaderDrawParameters ? "true" : "false");
512 log("CPU:");
513 log("supports AVX2: %s", HardwareInfo::HasAVX2() ? "true" : "false");
514 log(" ==================================");
515
516 volkLoadDevice(m_context.device);
517
518 if (apiVersion < VK_API_VERSION_1_3) {
519 if (!vkCmdBeginRendering) {
520 vkCmdBeginRendering = reinterpret_cast<PFN_vkCmdBeginRendering>(vkGetDeviceProcAddr(m_context.device, "vkCmdBeginRenderingKHR"));
521 }
522 if (!vkCmdEndRendering) {
523 vkCmdEndRendering = reinterpret_cast<PFN_vkCmdEndRendering>(vkGetDeviceProcAddr(m_context.device, "vkCmdEndRenderingKHR"));
524 }
525 if (!vkCmdPipelineBarrier2) {
526 vkCmdPipelineBarrier2 = reinterpret_cast<PFN_vkCmdPipelineBarrier2>(vkGetDeviceProcAddr(m_context.device, "vkCmdPipelineBarrier2KHR"));
527 }
528 }
529
530 vkGetDeviceQueue(m_context.device, m_context.graphicsQueueFamily, 0, &m_context.graphicsQueue);
531 vkGetDeviceQueue(m_context.device, m_context.presentQueueFamily, 0, &m_context.presentQueue);
532
533 if (m_context.graphicsQueue == VK_NULL_HANDLE) {
534 throw_error("Failed to retrieve graphics queue - device creation corrupted or driver issue");
535 }
536 if (m_context.presentQueue == VK_NULL_HANDLE) {
537 throw_error("Failed to retrieve present queue - device creation corrupted or driver issue");
538 }
539
540 VmaVulkanFunctions vmaFuncs = {};
541 vmaFuncs.vkGetInstanceProcAddr = vkGetInstanceProcAddr;
542 vmaFuncs.vkGetDeviceProcAddr = vkGetDeviceProcAddr;
543 vmaFuncs.vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties;
544 vmaFuncs.vkGetPhysicalDeviceMemoryProperties = vkGetPhysicalDeviceMemoryProperties;
545 vmaFuncs.vkAllocateMemory = vkAllocateMemory;
546 vmaFuncs.vkFreeMemory = vkFreeMemory;
547 vmaFuncs.vkMapMemory = vkMapMemory;
548 vmaFuncs.vkUnmapMemory = vkUnmapMemory;
549 vmaFuncs.vkFlushMappedMemoryRanges = vkFlushMappedMemoryRanges;
550 vmaFuncs.vkInvalidateMappedMemoryRanges = vkInvalidateMappedMemoryRanges;
551 vmaFuncs.vkBindBufferMemory = vkBindBufferMemory;
552 vmaFuncs.vkBindImageMemory = vkBindImageMemory;
553 vmaFuncs.vkGetBufferMemoryRequirements = vkGetBufferMemoryRequirements;
554 vmaFuncs.vkGetImageMemoryRequirements = vkGetImageMemoryRequirements;
555 vmaFuncs.vkCreateBuffer = vkCreateBuffer;
556 vmaFuncs.vkDestroyBuffer = vkDestroyBuffer;
557 vmaFuncs.vkCreateImage = vkCreateImage;
558 vmaFuncs.vkDestroyImage = vkDestroyImage;
559 vmaFuncs.vkCmdCopyBuffer = vkCmdCopyBuffer;
560
561 VmaAllocatorCreateInfo allocatorInfo = {};
562 allocatorInfo.physicalDevice = m_context.physicalDevice;
563 allocatorInfo.device = m_context.device;
564 allocatorInfo.instance = m_context.instance;
565 allocatorInfo.vulkanApiVersion = apiVersion;
566 allocatorInfo.pVulkanFunctions = &vmaFuncs;
567
568 if (vmaCreateAllocator(&allocatorInfo, &m_context.allocator) != VK_SUCCESS) {
569 throw_error("Failed to create VMA allocator");
570 }
571
572 log("Initializing Swapchain Manager...");
573 m_p_swapchainManager = std::make_unique<VulkanSwapchainManager>(m_context, m_p_window);
574 m_p_swapchainManager->createSwapchain();
575
576 log("Initializing Resources...");
577 m_p_resources = std::make_unique<VulkanResources>(m_context, m_vfs);
578
579 log("Initializing Mesh Manager...");
580 m_p_meshManager = std::make_unique<MeshManager>(m_context, m_p_resources, m_vfs);
581
582 log("Initializing Pipeline...");
583 m_p_pipeline = std::make_unique<VulkanPipeline>(m_context);
584
585 VkVertexInputBindingDescription bindingDesc{};
586 bindingDesc.binding = 0;
587 bindingDesc.stride = sizeof(Vertex);
588 bindingDesc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
589
590 std::vector<VkVertexInputAttributeDescription> attributes(3);
591 attributes[0] = {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, position)};
592 attributes[1] = {1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal)};
593 attributes[2] = {2, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)};
594
595 std::string opaqueFrag = m_context.supportsBindlessTextures
596 ? "Engine/shaders/OpaqueFragBindless.spv"
597 : "Engine/shaders/OpaqueFrag.spv";
598
599 m_p_pipeline->createGraphicsPipeline(
600 "Engine/shaders/OpaqueVert.spv",
601 opaqueFrag,
602 bindingDesc,
603 attributes
604 );
605
606 log("Initializing Masked Pipeline...");
607 m_p_maskPipeline = std::make_unique<VulkanPipeline>(m_context);
608
609 std::string maskedFrag = m_context.supportsBindlessTextures
610 ? "Engine/shaders/MaskedFragBindless.spv"
611 : "Engine/shaders/MaskedFrag.spv";
612
613 m_p_maskPipeline->createMaskedPipeline(
614 "Engine/shaders/MaskedVert.spv",
615 maskedFrag,
616 bindingDesc,
617 attributes
618 );
619
620 log("Initializing Transparent Pipeline...");
621 m_p_transPipeline = std::make_unique<VulkanPipeline>(m_context);
622
623 std::string transFrag = m_context.supportsBindlessTextures
624 ? "Engine/shaders/TransparentFragBindless.spv"
625 : "Engine/shaders/TransparentFrag.spv";
626
627 m_p_transPipeline->createTransparentPipeline(
628 "Engine/shaders/TransparentVert.spv",
629 transFrag,
630 bindingDesc,
631 attributes
632 );
633
634 log("Initializing Billboard and Particle Pipelines...");
635 m_p_billboardMaskedPipeline = std::make_unique<VulkanPipeline>(m_context);
636 m_p_billboardTransPipeline = std::make_unique<VulkanPipeline>(m_context);
637 m_p_particleMaskedPipeline = std::make_unique<VulkanPipeline>(m_context);
638 m_p_particleTransPipeline = std::make_unique<VulkanPipeline>(m_context);
639
640 std::string spriteOpaqueFrag = m_context.supportsBindlessTextures ? "Engine/shaders/SpriteOpaqueFragBindless.spv" : "Engine/shaders/SpriteOpaqueFrag.spv";
641 std::string spriteTransFrag = m_context.supportsBindlessTextures ? "Engine/shaders/SpriteTransFragBindless.spv" : "Engine/shaders/SpriteTransFrag.spv";
642
643 m_p_billboardMaskedPipeline->createSpritePipeline("Engine/shaders/BillboardVert.spv", spriteOpaqueFrag, false, false);
644 m_p_billboardTransPipeline->createSpritePipeline("Engine/shaders/BillboardVert.spv", spriteTransFrag, true, false);
645 m_p_particleMaskedPipeline->createSpritePipeline("Engine/shaders/ParticleVert.spv", spriteOpaqueFrag, false, true);
646 m_p_particleTransPipeline->createSpritePipeline("Engine/shaders/ParticleVert.spv", spriteTransFrag, true, true);
647
648 log("Initializing UI Pipeline...");
649 m_p_uiPipeline = std::make_unique<VulkanPipeline>(m_context);
650
651 VkVertexInputBindingDescription uiBinding{};
652 uiBinding.binding = 0;
653 uiBinding.stride = sizeof(UIVertex);
654 uiBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
655
656 std::vector<VkVertexInputAttributeDescription> uiAttrs(4);
657 uiAttrs[0] = {0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(UIVertex, position)};
658 uiAttrs[1] = {1, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(UIVertex, uv)};
659 uiAttrs[2] = {2, 0, VK_FORMAT_R32G32B32A32_SFLOAT,offsetof(UIVertex, color)};
660 uiAttrs[3] = {3, 0, VK_FORMAT_R32_SFLOAT, offsetof(UIVertex, texIndex)};
661
662
663 std::string uiFrag = m_context.supportsBindlessTextures
664 ? "Engine/shaders/UiFragBindless.spv"
665 : "Engine/shaders/UiFrag.spv";
666
667 m_p_uiPipeline->createUIPipeline(
668 "Engine/shaders/UiVert.spv",
669 uiFrag,
670 uiBinding,
671 uiAttrs
672 );
673
674
675 log("Initializing Composite Pipeline...");
676 m_p_compositePipeline = std::make_unique<VulkanPipeline>(m_context);
677 m_p_compositePipeline->createFullscreenPipeline(
678 "Engine/shaders/CompositeVert.spv",
679 "Engine/shaders/CompositeFrag.spv"
680 );
681
682 log("Initializing Fullscreen Pipeline...");
683 m_p_fullscreenPipeline = std::make_unique<VulkanPipeline>(m_context);
684
685 VkVertexInputBindingDescription emptyBinding{};
686 std::vector<VkVertexInputAttributeDescription> emptyAttrs;
687
688 m_p_fullscreenPipeline->createFullscreenPipeline(
689 "Engine/shaders/ScreenVert.spv",
690 "Engine/shaders/ScreenFrag.spv"
691 );
692
693 #if DEBUG
694 log("Initializing Physics Debug...");
695 m_p_physicsDebug = std::make_unique<VulkanPhysicsDebug>();
696
697 m_p_debugPipeline = std::make_unique<VulkanPipeline>(m_context);
698 m_p_debugPipeline->createDebugPipeline("Engine/shaders/DebugVert.spv", "Engine/shaders/DebugFrag.spv");
699 #endif
700
701 log("Initializing Renderer...");
702 m_p_renderer = std::make_unique<Renderer>(
703 m_context,
704 m_p_resources,
705 m_p_pipeline,
706 m_p_transPipeline,
707 m_p_maskPipeline,
708 m_p_billboardTransPipeline,
709 m_p_billboardMaskedPipeline,
710 m_p_particleTransPipeline,
711 m_p_particleMaskedPipeline,
712 m_p_uiPipeline,
713 m_p_fullscreenPipeline,
714 m_p_compositePipeline,
715 m_p_swapchainManager,
716 m_p_meshManager
717 );
718
719 #if DEBUG
720 m_p_renderer->setDebugPipeline(&m_p_debugPipeline);
721 #endif
722
723 log("Vulkan interface initialized successfully");
724
725 } catch (const std::exception& e) {
727 }
728 }
729
731 vkDeviceWaitIdle(m_context.device);
732
733 m_p_renderer.reset();
734 m_p_meshManager.reset();
735 m_p_resources.reset();
736 m_p_pipeline.reset();
737 m_p_transPipeline.reset();
738 m_p_maskPipeline.reset();
739 m_p_billboardMaskedPipeline.reset();
740 m_p_billboardTransPipeline.reset();
741 m_p_particleMaskedPipeline.reset();
742 m_p_particleTransPipeline.reset();
743 m_p_uiPipeline.reset();
744 m_p_fullscreenPipeline.reset();
745 m_p_compositePipeline.reset();
746 #if DEBUG
747 m_p_debugPipeline.reset();
748 m_p_physicsDebug.reset();
749 #endif
750 m_p_swapchainManager->cleanupSwapchain();
751 m_p_swapchainManager.reset();
752
753 if (m_context.textureDescriptorSetLayout != VK_NULL_HANDLE) {
754 vkDestroyDescriptorSetLayout(m_context.device, m_context.textureDescriptorSetLayout, nullptr);
755 m_context.textureDescriptorSetLayout = VK_NULL_HANDLE;
756 }
757 if (m_context.uboDescriptorSetLayout != VK_NULL_HANDLE) {
758 vkDestroyDescriptorSetLayout(m_context.device, m_context.uboDescriptorSetLayout, nullptr);
759 m_context.uboDescriptorSetLayout = VK_NULL_HANDLE;
760 }
761
762 if (m_context.allocator) {
763 vmaDestroyAllocator(m_context.allocator);
764 m_context.allocator = nullptr;
765 }
766
767 if (m_context.device) {
768 vkDestroyDevice(m_context.device, nullptr);
769 m_context.device = VK_NULL_HANDLE;
770 }
771
772 if (m_context.surface) {
773 vkDestroySurfaceKHR(m_context.instance, m_context.surface, nullptr);
774 m_context.surface = VK_NULL_HANDLE;
775 }
776
777 if (m_context.instance) {
778 vkDestroyInstance(m_context.instance, nullptr);
779 m_context.instance = VK_NULL_HANDLE;
780 }
781
782 SDL_Vulkan_UnloadLibrary();
783 }
784
786 m_p_resources->createDefaultTexture();
787 }
788
790 vkDeviceWaitIdle(m_context.device);
791 }
792
793 void Interface::setVSync(bool enabled) {
794 m_p_swapchainManager->setVSync(enabled);
795 vkDeviceWaitIdle(m_context.device);
796 m_context.requestSwapchainRecreation = true;
797 }
798
799 void Interface::bindWindow(SDL_Window *m_p_window) {
800 log("Binding window...");
801 if (m_context.surface) return;
802
803 if (!SDL_Vulkan_CreateSurface(m_p_window, m_context.instance, nullptr, &m_context.surface)) {
804 throw_error("Failed to create Vulkan surface: " + std::string(SDL_GetError()));
805 }
806
807 this->m_p_window = m_p_window;
808
809 log("Initializing Swapchain...");
810 m_p_swapchainManager->createSwapchain();
811 }
812
814 if (!m_context.surface) return;
815
816 vkDeviceWaitIdle(m_context.device);
817 m_p_swapchainManager->cleanupSwapchain();
818
819 vkDestroySurfaceKHR(m_context.instance, m_context.surface, nullptr);
820 m_context.surface = VK_NULL_HANDLE;
821 }
822}
This file defines interface Class for vulkan backend.
static void SetVulkanVersions(const std::string &deviceVer, const std::string &requestedVer)
Sets the Vulkan API versions.
static bool HasAVX2()
Checks if the CPU supports the AVX2 instruction set.
static void SetVulkanFeatures(const VulkanFeatures &features)
Sets the active Vulkan features.
static void SetGPUInfo(const std::string &name, uint32_t vendorID, uint32_t driverVersion)
Sets the stored GPU information.
void bindWindow(SDL_Window *window)
Binds the backend to a window.
void unbindWindow()
Unbinds the current window.
~Interface()
Simple destructor cleaning up resources.
Interface(SDL_Window *window, glm::uvec2 initialResolution, GameInfo gInfo, VirtualFileSystem *vfs)
Constructor for Interface class.
Definition Interface.cpp:74
void WaitForGPUToFinish()
Helper function to wait for GPU to finish.
void createDefaultTexture()
Creates the default texture.
void setVSync(bool enabled)
Sets VSync (Vertical Synchronization).
This class provides abstraction of file system needed for loading packed and unpacked assets.
const uint32_t MAX_TEXTURES
Definition limits.hpp:13
void VEX_EXPORT throw_error(const std::string &msg)
Throws an error or terminates the application.
void VEX_EXPORT log(const char *fmt,...)
Logs a formatted message.
void VEX_EXPORT handle_critical_exception(const std::exception &e)
Handles a critical exception, forcing termination.
this struct contains information about the game. like project name and version.
Definition GameInfo.hpp:15
Represents a vertex for a UI element.
Definition UIVertex.hpp:12
Vertex structure for mesh data.
Definition Mesh.hpp:25
Holds the status of various Vulkan features.
bool indirectDraw
Indicates if indirect draw calls are supported.
bool shaderDrawParameters
Indicates if VK_KHR_shader_draw_parameters is supported.
bool multiDraw
Indicates if VK_EXT_multi_draw is supported.
bool bindlessTextures
Indicates if bindless textures (descriptor indexing) are supported.