SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
VulkanDevice.cpp
Go to the documentation of this file.
3
4#include <SDL3/SDL_vulkan.h>
5#include <Core/Window.hpp>
6#include <algorithm>
7#include <cstdint>
8#include <cstring>
9#include <format>
10#include <set>
11#include <stdexcept>
12#include <string>
13#include <vector>
14#include "Core/Logger.hpp"
15#include "SDL3/SDL_error.h"
16#ifdef PLATFORM_LINUX
17 #include "vulkan/vulkan_wayland.h"
18#elif defined(PLATFORM_WIN)
19 #include <vulkan/vulkan_win32.h>
20#endif
21
22namespace Sleak {
23 namespace RenderEngine {
24
25/// Creates the Vulkan instance with validation layers when available.
26bool VulkanRenderer::InitVulkan() {
27 try {
28 instance = VK_NULL_HANDLE;
29
30 std::vector<const char*> requiredExtensions = {
31 VK_KHR_SURFACE_EXTENSION_NAME,
32 VK_EXT_DEBUG_UTILS_EXTENSION_NAME
33 };
34
35 #ifdef PLATFORM_LINUX
36 {
37 // Only request surface extensions actually available
38 // (blindly requesting both breaks capture tools like RenderDoc)
39 uint32_t surfExtCount = 0;
40 vkEnumerateInstanceExtensionProperties(nullptr, &surfExtCount, nullptr);
41 std::vector<VkExtensionProperties> surfExts(surfExtCount);
42 vkEnumerateInstanceExtensionProperties(nullptr, &surfExtCount, surfExts.data());
43 auto hasSurfExt = [&](const char* name) {
44 for (auto& e : surfExts)
45 if (strcmp(e.extensionName, name) == 0) return true;
46 return false;
47 };
48 if (hasSurfExt("VK_KHR_wayland_surface"))
49 requiredExtensions.push_back("VK_KHR_wayland_surface");
50 if (hasSurfExt("VK_KHR_xlib_surface"))
51 requiredExtensions.push_back("VK_KHR_xlib_surface");
52 }
53 #elif defined(PLATFORM_WIN)
54 requiredExtensions.push_back(
55 VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
56 #elif defined(TARGET_OS_MAC) || defined(TARGET_OS_IOS)
57 requiredExtensions.push_back(VK_MVK_MOLTENVK_EXTENSION_NAME);
58 #endif
59
60 // Always attempt to enable validation layers so GPU errors are
61 // reported via the debug messenger as SLEAK_ERROR messages rather
62 // than silent VK_ERROR_DEVICE_LOST crashes. If the layer is not
63 // installed the instance still creates successfully (empty list).
64 std::vector<const char*> enabledLayers;
65 {
66 uint32_t layerCount = 0;
67 vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
68 std::vector<VkLayerProperties> availableLayers(layerCount);
69 vkEnumerateInstanceLayerProperties(&layerCount,
70 availableLayers.data());
71
72 const char* desiredLayer = "VK_LAYER_KHRONOS_validation";
73 for (const auto& layer : availableLayers) {
74 if (strcmp(layer.layerName, desiredLayer) == 0) {
75 enabledLayers.push_back(desiredLayer);
76 SLEAK_INFO("Vulkan validation layer enabled");
77 break;
78 }
79 }
80 if (enabledLayers.empty()) {
81 SLEAK_WARN("VK_LAYER_KHRONOS_validation not available — GPU errors will not be reported");
82 }
83 }
84 if (enabledLayers.empty()) {
85 auto it = std::find_if(
86 requiredExtensions.begin(), requiredExtensions.end(),
87 [](const char* ext) {
88 return strcmp(ext,
89 VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0;
90 });
91 if (it != requiredExtensions.end()) {
92 requiredExtensions.erase(it);
93 }
94 }
95
96 // Check and list vulkan extensions
97 uint32_t extensionCount = 0;
98 vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount,
99 nullptr);
100 std::vector<VkExtensionProperties> extensions(extensionCount);
101 vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount,
102 extensions.data());
103
104 VkApplicationInfo appInfo{};
105 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
106 appInfo.pApplicationName = "SleakEngine";
107 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
108 appInfo.pEngineName = "Sleak Engine";
109 appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
110 appInfo.apiVersion = VK_API_VERSION_1_1;
111
112 VkInstanceCreateInfo createInfo{};
113 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
114 createInfo.pApplicationInfo = &appInfo;
115 createInfo.enabledExtensionCount =
116 static_cast<uint32_t>(requiredExtensions.size());
117 createInfo.ppEnabledExtensionNames = requiredExtensions.data();
118 createInfo.enabledLayerCount =
119 static_cast<uint32_t>(enabledLayers.size());
120 createInfo.ppEnabledLayerNames = enabledLayers.data();
121
122 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{};
123 if (!enabledLayers.empty()) {
124 PopulateDebugMessengerCreateInfo(debugCreateInfo);
125 createInfo.pNext = &debugCreateInfo;
126 }
127
128 VkResult result =
129 vkCreateInstance(&createInfo, nullptr, &instance);
130 if (result != VK_SUCCESS) {
131 SLEAK_ERROR("Failed to create Vulkan instance!");
132 return false;
133 }
134
135 SLEAK_INFO("Vulkan instance created successfully.");
136 return true;
137
138 } catch (const std::exception& e) {
139 SLEAK_ERROR("Exception in InitVulkan: {}", e.what());
140 return false;
141 }
142}
143
144/// Selects the physical GPU and creates the logical device and queues.
145bool VulkanRenderer::CreateDevice() {
146 // Enumerate physical devices
147 uint32_t deviceCount = 0;
148 VkResult result = vkEnumeratePhysicalDevices(instance, &deviceCount,
149 nullptr);
150 if (result != VK_SUCCESS || deviceCount == 0)
151 SLEAK_RETURN_ERR("No device found in computer!")
152
153 GPUs.resize(deviceCount);
154 vkEnumeratePhysicalDevices(instance, &deviceCount, GPUs.data());
155
156 SLEAK_INFO("Found totally {} devices in computer", deviceCount);
157
158 // Pick best GPU (prefer discrete)
159 physicalDevice = GPUs[0];
160 for (auto& dev : GPUs) {
161 VkPhysicalDeviceProperties props;
162 VkPhysicalDeviceMemoryProperties memprops;
163 vkGetPhysicalDeviceProperties(dev, &props);
164 vkGetPhysicalDeviceMemoryProperties(dev, &memprops);
165
166 std::string type;
167 switch (props.deviceType) {
168 case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
169 type = "Integrated GPU"; break;
170 case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
171 type = "Discrete GPU"; break;
172 case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
173 type = "Virtual GPU"; break;
174 case VK_PHYSICAL_DEVICE_TYPE_CPU:
175 type = "CPU"; break;
176 default:
177 type = "Other";
178 }
179
180 VkDeviceSize totalDedicatedMemory = 0;
181 for (uint32_t i = 0; i < memprops.memoryHeapCount; i++) {
182 if (memprops.memoryHeaps[i].flags &
183 VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) {
184 totalDedicatedMemory += memprops.memoryHeaps[i].size;
185 }
186 }
187 float totalDedicatedMemoryGB =
188 static_cast<float>(totalDedicatedMemory) /
189 (1024.0f * 1024.0f * 1024.0f);
190
191 SLEAK_INFO("Name: {0} Type: {1} memory: {2}",
192 props.deviceName, type, totalDedicatedMemoryGB);
193
194 if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
195 physicalDevice = dev;
196 }
197
198 // Make physical device available for VRAM tracking
199 VulkanBuffer::SetPhysicalDevice(physicalDevice);
200
201 // Find queue families
202 uint32_t familyCount = 0;
203 vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount,
204 nullptr);
205 std::vector<VkQueueFamilyProperties> queueFamilies(familyCount);
206 vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount,
207 queueFamilies.data());
208
209 QueueIDs.GraphicsIndex = UINT32_MAX;
210 QueueIDs.ComputeIndex = UINT32_MAX;
211 QueueIDs.TransferIndex = UINT32_MAX;
212 QueueIDs.PresentIndex = UINT32_MAX;
213
214 for (uint32_t i = 0; i < familyCount; i++) {
215 if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT &&
216 QueueIDs.GraphicsIndex == UINT32_MAX) {
217 QueueIDs.GraphicsIndex = i;
218 }
219 if (queueFamilies[i].queueFlags & VK_QUEUE_COMPUTE_BIT &&
220 QueueIDs.ComputeIndex == UINT32_MAX) {
221 QueueIDs.ComputeIndex = i;
222 }
223 if (queueFamilies[i].queueFlags & VK_QUEUE_TRANSFER_BIT &&
224 QueueIDs.TransferIndex == UINT32_MAX) {
225 QueueIDs.TransferIndex = i;
226 }
227
228 if (QueueIDs.PresentIndex == UINT32_MAX) {
229 VkBool32 presentSupport = VK_FALSE;
230 vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i,
231 surface,
232 &presentSupport);
233 if (presentSupport) {
234 QueueIDs.PresentIndex = i;
235 }
236 }
237 }
238
239 if (QueueIDs.GraphicsIndex == UINT32_MAX ||
240 QueueIDs.PresentIndex == UINT32_MAX)
241 SLEAK_RETURN_ERR("Failed to find required queue families!")
242
243 // If compute/transfer not found, fall back to graphics
244 if (QueueIDs.ComputeIndex == UINT32_MAX)
245 QueueIDs.ComputeIndex = QueueIDs.GraphicsIndex;
246 if (QueueIDs.TransferIndex == UINT32_MAX)
247 QueueIDs.TransferIndex = QueueIDs.GraphicsIndex;
248
249 #ifdef _DEBUG
251 "Graphics: {0} Compute: {1} Transfer: {2} Present: {3}",
252 QueueIDs.GraphicsIndex, QueueIDs.ComputeIndex,
253 QueueIDs.TransferIndex, QueueIDs.PresentIndex);
254 #endif
255
256 // Build unique queue create infos (no duplicates!)
257 auto queueCreateInfos = GetUniqueQueueCreateInfos();
258
259 // Get supported features
260 VkPhysicalDeviceFeatures features{};
261 vkGetPhysicalDeviceFeatures(physicalDevice, &features);
262
263 // Query max MSAA sample count
264 VkPhysicalDeviceProperties deviceProperties;
265 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
266 VkSampleCountFlags counts = deviceProperties.limits.framebufferColorSampleCounts
267 & deviceProperties.limits.framebufferDepthSampleCounts;
269 if (counts & VK_SAMPLE_COUNT_8_BIT) m_maxMsaaSampleCount = 8;
270 else if (counts & VK_SAMPLE_COUNT_4_BIT) m_maxMsaaSampleCount = 4;
271 else if (counts & VK_SAMPLE_COUNT_2_BIT) m_maxMsaaSampleCount = 2;
272 SLEAK_INFO("Max MSAA sample count: {}", m_maxMsaaSampleCount);
273
274 std::vector<const char*> requiredExtensions = {
275 VK_KHR_SWAPCHAIN_EXTENSION_NAME};
276
277 // Create logical device
278 VkDeviceCreateInfo deviceInfo{};
279 deviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
280 deviceInfo.pQueueCreateInfos = queueCreateInfos.data();
281 deviceInfo.queueCreateInfoCount =
282 static_cast<uint32_t>(queueCreateInfos.size());
283 deviceInfo.ppEnabledExtensionNames = requiredExtensions.data();
284 deviceInfo.enabledExtensionCount =
285 static_cast<uint32_t>(requiredExtensions.size());
286 deviceInfo.pEnabledFeatures = &features;
287 deviceInfo.enabledLayerCount = 0;
288
289 result = vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device);
290
291 if (result != VK_SUCCESS)
292 SLEAK_RETURN_ERR("Failed to create a logical device!")
293
294 // Retrieve queues
295 vkGetDeviceQueue(device, QueueIDs.GraphicsIndex, 0, &graphicsQueue);
296 vkGetDeviceQueue(device, QueueIDs.ComputeIndex, 0, &computeQueue);
297 vkGetDeviceQueue(device, QueueIDs.TransferIndex, 0, &transferQueue);
298 vkGetDeviceQueue(device, QueueIDs.PresentIndex, 0, &presentQueue);
299
300 // VMA allocator — backs all VulkanBuffer allocations.
301 VulkanBuffer::InitAllocator(instance, physicalDevice, device);
302
303 return true;
304}
305
306/// Builds one queue create info per unique queue family index.
307// Build one VkDeviceQueueCreateInfo per *unique* family index
308std::vector<VkDeviceQueueCreateInfo>
309VulkanRenderer::GetUniqueQueueCreateInfos() {
310 std::set<uint32_t> uniqueFamilies = {
311 QueueIDs.GraphicsIndex, QueueIDs.ComputeIndex,
312 QueueIDs.TransferIndex, QueueIDs.PresentIndex};
313
314 std::vector<VkDeviceQueueCreateInfo> result;
315
316 for (uint32_t family : uniqueFamilies) {
317 VkDeviceQueueCreateInfo info{};
318 info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
319 info.queueFamilyIndex = family;
320 info.queueCount = 1;
321 info.pQueuePriorities = &QueueIDs.GraphicsPriority;
322 result.push_back(info);
323 }
324
325 return result;
326}
327
328/// Registers the debug messenger callback for validation output.
329bool VulkanRenderer::SetupDebugMessenger() {
330 VkDebugUtilsMessengerCreateInfoEXT createInfo{};
331 PopulateDebugMessengerCreateInfo(createInfo);
332
333 auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
334 instance, "vkCreateDebugUtilsMessengerEXT");
335
336 if (func) {
337 VkResult result =
338 func(instance, &createInfo, nullptr, &debugMessenger);
339 if (result != VK_SUCCESS)
340 SLEAK_RETURN_ERR("Failed to setup Debug Messenger!")
341
342 vkDestroyDebugUtilsMessengerEXT =
343 (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
344 instance, "vkDestroyDebugUtilsMessengerEXT");
345 if (!vkDestroyDebugUtilsMessengerEXT)
347 "Failed to setup Debug Messenger Destroy Function!")
348 } else {
350 "vkCreateDebugUtilsMessengerEXT could not found!")
351 }
352
353 return true;
354}
355
356/// Creates the SDL-backed Vulkan presentation surface.
357bool VulkanRenderer::CreateSurface() {
358 SDL_Vulkan_LoadLibrary(NULL);
359 bool result = SDL_Vulkan_CreateSurface(sdlWindow->GetSDLWindow(),
360 instance, nullptr, &surface);
361
362 if (!result || surface == VK_NULL_HANDLE) {
363 const char* error = SDL_GetError();
364 SLEAK_ERROR("Caught an SDL error! {}", error);
365 SLEAK_RETURN_ERR("Failed to create a render surface for Vulkan!");
366 }
367
368 return true;
369}
370
371/// Fills the debug messenger create info with severity and callback.
372void VulkanRenderer::PopulateDebugMessengerCreateInfo(
373 VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
374 createInfo = {};
375 createInfo.sType =
376 VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
377 createInfo.messageSeverity =
378 VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
379 VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
380 VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
381 createInfo.messageType =
382 VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
383 VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
384 VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
385 createInfo.pfnUserCallback = &VulkanRenderer::Validation;
386}
387
388/// Debug messenger callback that routes Vulkan messages to the logger.
389VkBool32 VulkanRenderer::Validation(
390 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
391 VkDebugUtilsMessageTypeFlagsEXT messageTypes,
392 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
393 void* pUserData) {
394 std::string out = std::format("Vulkan: {} \n Type: {}",
395 pCallbackData->pMessage, messageTypes);
396
397 switch (messageSeverity) {
398 case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
399 SLEAK_WARN(out);
400 break;
401 case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
402 SLEAK_ERROR(out);
403 break;
404 default:
405 SLEAK_INFO(out);
406 }
407
408 return VK_FALSE;
409}
410
411}
412}
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
#define SLEAK_RETURN_ERR(...)
Definition Logger.hpp:25
#define SLEAK_INFO(...)
Definition Logger.hpp:20
#define SLEAK_WARN(...)
Definition Logger.hpp:21
static void SetPhysicalDevice(VkPhysicalDevice device)
Backend-facing rendering layer shared by the four graphics backends.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10