#if !APP_VERSION #include "compile_config.hpp" #endif #include "Renderer.hpp" #include "Logger.h" #include #include #include #include Renderer::Renderer(SDL_Window *window) : isRunning(false) { this->window = window; } Renderer::~Renderer() { Release(); } void Renderer::InitVulkan() { std::vector vulkanExtensions { // }; std::vector vulkanLayers { VALIDATION_LAYER }; // Get Vulkan extensions list uint32_t extensionsSDLCount = 0; if ( !SDL_Vulkan_GetInstanceExtensions(window, &extensionsSDLCount, nullptr) ) LOG(CRITICAL, "Fail to get SDL Vulkan extensions number! %hs", SDL_GetError()); std::vector vulkanExtensionsSDL(extensionsSDLCount); if ( !SDL_Vulkan_GetInstanceExtensions(window, &extensionsSDLCount, vulkanExtensionsSDL.data()) ) { LOG(CRITICAL, "Fail to register SDL Vulkan extensions! %hs", SDL_GetError()); vulkanExtensionsSDL.clear(); } else vulkanExtensions.insert(vulkanExtensions.end(), vulkanExtensionsSDL.begin(), vulkanExtensionsSDL.end()); for (const char *extension : vulkanExtensions) { LOG(INFO, "%hs Vulkan extension registered", extension); } for (const char *layer : vulkanLayers) { LOG(INFO, "%hs Vulkan layer registered", layer); } // Create VkInstance VkApplicationInfo vkAppConfig { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pNext = nullptr, .pApplicationName = "Etredia Empires", .applicationVersion = VK_MAKE_VERSION(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION), .pEngineName = "No Engine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = VK_API_VERSION_1_0 }; VkInstanceCreateInfo vkCreateConfig { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pNext = nullptr, .pApplicationInfo = &vkAppConfig, .enabledLayerCount = static_cast(vulkanLayers.size()), .ppEnabledLayerNames = vulkanLayers.data(), .enabledExtensionCount = static_cast(vulkanExtensions.size()), .ppEnabledExtensionNames = vulkanExtensions.data(), }; VkResult vkResult = vkCreateInstance(&vkCreateConfig, nullptr, &vulkan); if (vkResult != VK_SUCCESS) LOG(CRITICAL, "Vulkan is not instantiated! Code: %d", vkResult); else LOG(INFO, "Vulkan is instantiated"); // Create surface if ( !SDL_Vulkan_CreateSurface(window, vulkan, &surface) ) LOG(CRITICAL, "Failed to create SDL Vulkan surface. %hs", SDL_GetError()); else LOG(INFO, "SDL Vulkan surface created"); } void Renderer::SetupDevice() { // Find physical devices VkResult vkResult; uint32_t devicesCount = 0; vkResult = vkEnumeratePhysicalDevices(vulkan, &devicesCount, nullptr); if (vkResult != VK_SUCCESS) { LOG(CRITICAL, "Failed to enumerate physical devices. Code: %d", vkResult); return; } if (devicesCount == 0) { LOG(CRITICAL, "No devices with Vulkan support found!"); return; } std::vector devices(devicesCount); vkResult = vkEnumeratePhysicalDevices(vulkan, &devicesCount, devices.data()); if (vkResult != VK_SUCCESS) { LOG(CRITICAL, "Failed to enumerate %d physical devices. Code: %d", devicesCount, vkResult); return; } else { LOG(INFO, "Found %d Vulkan compatible physical devices.", devices.size()); } // Filter and select device gpu = VK_NULL_HANDLE; VkPhysicalDeviceProperties deviceProperties {}; VkPhysicalDeviceFeatures deviceFeatures {}; VkPhysicalDeviceFeatures enabledFeatures {}; uint32_t best_priority = 0; for (const VkPhysicalDevice &physicalDevice : devices) { uint32_t priority = 1; // Check device features and properties vkGetPhysicalDeviceFeatures(physicalDevice, &deviceFeatures); if (deviceFeatures.wideLines) enabledFeatures.wideLines = VK_TRUE; else continue; if (deviceFeatures.independentBlend) enabledFeatures.independentBlend = VK_TRUE; else continue; vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties); // Check queue families uint32_t queueFamiliesCount = 0; vkGetPhysicalDeviceQueueFamilyProperties( physicalDevice, &queueFamiliesCount, nullptr ); std::vector queueFamiliesProperties(queueFamiliesCount); vkGetPhysicalDeviceQueueFamilyProperties( physicalDevice, &queueFamiliesCount, queueFamiliesProperties.data() ); uint32_t i = 0; QueueFamiliesBundle queueFamiliesBuffer {}; for (const VkQueueFamilyProperties &familyProps : queueFamiliesProperties) { if (familyProps.queueFlags & VK_QUEUE_GRAPHICS_BIT) queueFamiliesBuffer.graphics = i; if ( queueFamiliesBuffer.graphics != queueFamiliesBuffer.present || queueFamiliesBuffer.graphics == QUEUE_FAMILY_NO_VALUE ) { VkBool32 presentSupport = VK_FALSE; vkResult = vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface, &presentSupport); if (vkResult != VK_SUCCESS) LOG(WARNING, "Failed to check surface present support. Code: %d. Device: %hs", vkResult, deviceProperties.deviceName); if (presentSupport) queueFamiliesBuffer.present = i; } i++; } if ( queueFamiliesBuffer.graphics == QUEUE_FAMILY_NO_VALUE || queueFamiliesBuffer.present == QUEUE_FAMILY_NO_VALUE ) continue; // Check surface info SurfaceInfo surfaceInfoBuffer {}; uint32_t surfaceFormatsCount = 0; vkResult = vkGetPhysicalDeviceSurfaceFormatsKHR( physicalDevice, surface, &surfaceFormatsCount, nullptr ); if (vkResult != VK_SUCCESS) LOG(WARNING, "Failed to get surface formats number. Code: %d. Device: %hs", vkResult, deviceProperties.deviceName); surfaceInfoBuffer.availableFormats.resize(surfaceFormatsCount); vkResult = vkGetPhysicalDeviceSurfaceFormatsKHR( physicalDevice, surface, &surfaceFormatsCount, surfaceInfoBuffer.availableFormats.data() ); if (vkResult != VK_SUCCESS) LOG(WARNING, "Failed to get surface formats. Code: %d. Device: %hs", vkResult, deviceProperties.deviceName); uint32_t presentModesCount = 0; vkResult = vkGetPhysicalDeviceSurfacePresentModesKHR( physicalDevice, surface, &presentModesCount, nullptr ); if (vkResult != VK_SUCCESS) LOG(WARNING, "Failed to get surface present modes number. Code: %d. Device: %hs", vkResult, deviceProperties.deviceName); surfaceInfoBuffer.availablePresentModes.resize(presentModesCount); vkResult = vkGetPhysicalDeviceSurfacePresentModesKHR( physicalDevice, surface, &surfaceFormatsCount, surfaceInfoBuffer.availablePresentModes.data() ); if (vkResult != VK_SUCCESS) LOG(WARNING, "Failed to get surface present modes. Code: %d. Device: %hs", vkResult, deviceProperties.deviceName); if ( surfaceInfoBuffer.availableFormats.empty() || surfaceInfoBuffer.availablePresentModes.empty() ) continue; // Rate and select physical device if (queueFamiliesBuffer.graphics == queueFamiliesBuffer.present) priority += GPU_GRAPHICS_WITH_PRESENT_PRIORITY; if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) priority += GPU_DISCRETE_PRIORITY; for (VkSurfaceFormatKHR format : surfaceInfoBuffer.availableFormats) { surfaceInfoBuffer.format = format; if (format.format == VK_FORMAT_B8G8R8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { priority += GPU_B8G8R8_SRGB_PRIORITY; break; } } surfaceInfoBuffer.presentMode = VK_PRESENT_MODE_FIFO_KHR; for (VkPresentModeKHR presentMode : surfaceInfoBuffer.availablePresentModes) { if (presentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) { surfaceInfoBuffer.presentMode = presentMode; break; } } if (priority > best_priority) { best_priority = priority; gpu = physicalDevice; queueFamilies = queueFamiliesBuffer; surfaceInfo = surfaceInfoBuffer; } } if (gpu == VK_NULL_HANDLE) { LOG(CRITICAL, "Failed to select suitable physical device"); return; } // Define queues std::vector queuesCreateConfigs; std::set uniqueQueueFamilies = { queueFamilies.graphics, queueFamilies.present }; for (uint32_t queueFamily : uniqueQueueFamilies) { VkDeviceQueueCreateInfo queueCreateConfig { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .pNext = nullptr, .queueFamilyIndex = queueFamily, .queueCount = 1, .pQueuePriorities = &defaultQueuePriority }; queuesCreateConfigs.push_back(queueCreateConfig); } // Create logical device std::vector deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; VkDeviceCreateInfo deviceInfo = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = nullptr, .queueCreateInfoCount = static_cast(queuesCreateConfigs.size()), .pQueueCreateInfos = queuesCreateConfigs.data(), .enabledExtensionCount = static_cast(deviceExtensions.size()), .ppEnabledExtensionNames = deviceExtensions.data(), .pEnabledFeatures = &enabledFeatures, }; vkResult = vkCreateDevice(gpu, &deviceInfo, nullptr, &device); if (vkResult != VK_SUCCESS) { LOG(CRITICAL, "Failed to create logical device on %hs. Code: %d", deviceProperties.deviceName, vkResult); return; } else LOG(INFO, "%hs is selected for render with %d priority score", deviceProperties.deviceName, best_priority); // Save queues descriptor vkGetDeviceQueue(device, queueFamilies.graphics, 0, &queues.graphics); vkGetDeviceQueue(device, queueFamilies.present, 0, &queues.present); } void Renderer::SetupSwapchain() { VkResult vkResult; vkResult = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(gpu, surface, &surfaceInfo.capabilities); if (vkResult != VK_SUCCESS) LOG(CRITICAL, "Failed to get surface capabilities. Code: %d", vkResult); int width = 0, height = 0; SDL_Vulkan_GetDrawableSize(window, &width, &height); // Clamp extent surfaceInfo.extent.width = (std::max)( surfaceInfo.capabilities.minImageExtent.width, (std::min)(surfaceInfo.capabilities.maxImageExtent.width, static_cast(width)) ); surfaceInfo.extent.height = (std::max)( surfaceInfo.capabilities.minImageExtent.height, (std::min)(surfaceInfo.capabilities.maxImageExtent.height, static_cast(height)) ); uint32_t imagesCount = surfaceInfo.capabilities.minImageCount + 1; // Clamp min images count if (surfaceInfo.capabilities.maxImageCount > 0 && imagesCount > surfaceInfo.capabilities.maxImageCount) imagesCount = surfaceInfo.capabilities.maxImageCount; VkSwapchainCreateInfoKHR swapchainCreateConfig { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .pNext = nullptr, .surface = surface, .minImageCount = imagesCount, .imageFormat = surfaceInfo.format.format, .imageColorSpace = surfaceInfo.format.colorSpace, .imageExtent = surfaceInfo.extent, .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, .preTransform = surfaceInfo.capabilities.currentTransform, .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, .presentMode = surfaceInfo.presentMode, .clipped = VK_TRUE, .oldSwapchain = VK_NULL_HANDLE }; if (queueFamilies.graphics != queueFamilies.present) { uint32_t queueFamiliesShare[] = {queueFamilies.graphics, queueFamilies.present}; swapchainCreateConfig.imageSharingMode = VK_SHARING_MODE_CONCURRENT; swapchainCreateConfig.queueFamilyIndexCount = 2; swapchainCreateConfig.pQueueFamilyIndices = queueFamiliesShare; } else swapchainCreateConfig.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; vkResult = vkCreateSwapchainKHR(device, &swapchainCreateConfig, nullptr, &swapchain); if (vkResult != VK_SUCCESS) LOG(CRITICAL, "Failed to create swapchain. Code: %d", vkResult); else LOG(INFO, "Swapchain is created"); vkResult = vkGetSwapchainImagesKHR(device, swapchain, &imagesCount, nullptr); if (vkResult != VK_SUCCESS) LOG(CRITICAL, "Failed to get frames number. Code: %d", vkResult); frames.resize(imagesCount); vkResult = vkGetSwapchainImagesKHR(device, swapchain, &imagesCount, frames.data()); if (vkResult != VK_SUCCESS) LOG(CRITICAL, "Failed to get frames descriptors. Code: %d", vkResult); else LOG(INFO, "Frames are ready"); frameViews.resize(imagesCount); for (size_t i = 0; i < frames.size(); i++) { VkImageViewCreateInfo frameViewConfig { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = nullptr, .image = frames[i], .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = surfaceInfo.format.format, .components = { .r = VK_COMPONENT_SWIZZLE_IDENTITY, .g = VK_COMPONENT_SWIZZLE_IDENTITY, .b = VK_COMPONENT_SWIZZLE_IDENTITY, .a = VK_COMPONENT_SWIZZLE_IDENTITY }, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1 } }; vkResult = vkCreateImageView(device, &frameViewConfig, nullptr, &frameViews[i]); if (vkResult != VK_SUCCESS) LOG(CRITICAL, "Failed to create image view for frame. Code: %d", vkResult); } LOG(INFO, "Frame views are ready."); } void Renderer::Start() { if (isRunning) throw std::exception("Renderer can be started only once"); isRunning = true; renderThread = std::jthread([this](std::stop_token stop) { this->stoken = &stop; Threading(); }); } void Renderer::Threading() { Logger::trace->Register_Thread(L"Renderer", 0); LOG(INFO, "Renderer thread started"); InitVulkan(); SetupDevice(); SetupSwapchain(); Loop(); Release(); } void Renderer::Loop() { RenderCommand commandBuffer; while (!stoken->stop_requested()) { while (commands.pop(commandBuffer)) { ProcessCommand(commandBuffer); } } } void Renderer::ProcessCommand(const RenderCommand &command) { switch(command.type) { case RenderCommand::SHUTDOWN: { LOG(INFO, "SHUTDOWN command"); Shutdown(); break; } case RenderCommand::RESIZE_WINDOW: { LOG(INFO, "RESIZE_WINDOW command"); break; } default: { LOG(WARNING, "Unhandled command. Type id: %d", command.type); break; } } } void Renderer::Shutdown() { renderThread.request_stop(); } void Renderer::Request(const RenderCommand &command) { commands.push(command); } void Renderer::Request(RenderCommand::Type type) { commands.push(RenderCommand {.type = type}); } void Renderer::Release() { for (VkImageView frameView : frameViews) { vkDestroyImageView(device, frameView, nullptr); } vkDestroySwapchainKHR(device, swapchain, nullptr); vkDestroyDevice(device, nullptr); vkDestroySurfaceKHR(vulkan, surface, nullptr); vkDestroyInstance(vulkan, nullptr); LOG(INFO, "Renderer is released"); isRunning.store(false); }