From d35a31e4bc631c09d07330bf24f5ccb7bc74d155 Mon Sep 17 00:00:00 2001 From: tulpenkiste Date: Sun, 30 Jul 2023 18:53:27 +0200 Subject: [PATCH] Initial Aethiopicus renderer (vulkan) --- .../application.cpp} | 0 .../application.hpp} | 0 renderer/aethiopicus/aethiopicus.cpp | 496 ++++++++++++++++++ renderer/aethiopicus/aethiopicus.hpp | 100 ++++ renderer/aethiopicus/vkutil.cpp | 99 ++++ renderer/aethiopicus/vkutil.hpp | 30 ++ 6 files changed, 725 insertions(+) rename core/{engine/engine.cpp => application/application.cpp} (100%) rename core/{engine/engine.hpp => application/application.hpp} (100%) create mode 100644 renderer/aethiopicus/aethiopicus.cpp create mode 100644 renderer/aethiopicus/aethiopicus.hpp create mode 100644 renderer/aethiopicus/vkutil.cpp create mode 100644 renderer/aethiopicus/vkutil.hpp diff --git a/core/engine/engine.cpp b/core/application/application.cpp similarity index 100% rename from core/engine/engine.cpp rename to core/application/application.cpp diff --git a/core/engine/engine.hpp b/core/application/application.hpp similarity index 100% rename from core/engine/engine.hpp rename to core/application/application.hpp diff --git a/renderer/aethiopicus/aethiopicus.cpp b/renderer/aethiopicus/aethiopicus.cpp new file mode 100644 index 0000000..f6a8a9f --- /dev/null +++ b/renderer/aethiopicus/aethiopicus.cpp @@ -0,0 +1,496 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "aethiopicus.hpp" +#include "vkutil.hpp" + +#include "enginever.hpp" + +std::vector split(std::string phrase, std::string delimiter){ + std::vector list; + size_t pos = 0; + std::string token; + while ((pos = phrase.find(delimiter)) != std::string::npos) { + token = phrase.substr(0, pos); + list.push_back(stoi(token)); + phrase.erase(0, pos + delimiter.length()); + } + +#ifndef NDEBUG + if ((pos = phrase.find("-")) != std::string::npos) { + phrase = phrase.substr(0, 1); + } +#endif + + list.push_back(stoi(phrase)); + return list; +} + +VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) { + + if (hibis::aethiopicus::Aethiopicus* renderer = static_cast(pUserData)) { + LoggingSeverity severity; + + switch (messageSeverity) { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + severity = Message; + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + severity = Information; + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + severity = Warning; + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + severity = Error; + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_FLAG_BITS_MAX_ENUM_EXT: + severity = Message; + break; + } + + renderer->mLogger(severity, "VULKAN: " + (std::string)(pCallbackData->pMessage)); + } else std::cout << "pUserData is not equivalent to hibis::aethiopicus::Aethiopicus." << std::endl; + + return VK_FALSE; +} + +void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { + if (hibis::aethiopicus::Aethiopicus* renderer = static_cast(glfwGetWindowUserPointer(window))) { + // Prepare type + hibis::PressTypes type; + switch (action) { + case GLFW_PRESS: + type = hibis::Pressed; + break; + case GLFW_REPEAT: + type = hibis::Held; + break; + case GLFW_RELEASE: + type = hibis::Released; + break; + default: + type = hibis::Unknown; + break; + } + + // Tell input manager the new state + renderer->mInputManager.setKeyPressState(scancode, type); + } +} + +namespace hibis::aethiopicus { + Aethiopicus::Aethiopicus(std::string title, std::string internalName, std::string version, IntVec2 size, LoggerCallback logger, bool useValidationLayers) : Renderer(logger), mUseValidationLayers(useValidationLayers) { + // GLFW Window + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + mWindow = glfwCreateWindow(size.x, size.y, title.c_str(), nullptr, nullptr); + + if (mWindow == NULL) throw std::runtime_error("Couldn't create a window"); + + glfwSetWindowUserPointer(mWindow, this); + glfwSetKeyCallback(mWindow, keyCallback); + + std::vector hibisVerNums = split(HIBIS_VERSION, "."); + std::vector appVerNums = split(version, "."); + + VkApplicationInfo appInfo {}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = internalName.c_str(); + appInfo.applicationVersion = VK_MAKE_VERSION(appVerNums[0], appVerNums[1], appVerNums[2]); + appInfo.pEngineName = "Hibis"; + appInfo.engineVersion = VK_MAKE_VERSION(hibisVerNums[0], hibisVerNums[1], hibisVerNums[2]); + appInfo.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo instanceCreateInfo{}; + instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instanceCreateInfo.pApplicationInfo = &appInfo; + + mGLFWExtensions = glfwGetRequiredInstanceExtensions(&mGLFWExtensionCount); + + for(uint32_t i = 0; i < mGLFWExtensionCount; i++) { + mGLFWExtensionVector.emplace_back(mGLFWExtensions[i]); + } + + mGLFWExtensionVector.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + + if (useValidationLayers) { + mGLFWExtensionVector.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + if (useValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + instanceCreateInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + + instanceCreateInfo.enabledExtensionCount = (uint32_t) mGLFWExtensionVector.size(); + instanceCreateInfo.ppEnabledExtensionNames = mGLFWExtensionVector.data(); + + uint32_t extensionCount = 0; + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + + mVulkanExtensions.resize(extensionCount); + + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, mVulkanExtensions.data()); + + if (useValidationLayers) { + instanceCreateInfo.enabledLayerCount = static_cast(mValidationLayers.size()); + instanceCreateInfo.ppEnabledLayerNames = mValidationLayers.data(); + } else { + instanceCreateInfo.enabledLayerCount = 0; + } + + VkResult result = vkCreateInstance(&instanceCreateInfo, nullptr, &mVulkanInstance); + + if (useValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + if (result != VK_SUCCESS) { + logger(Fatal, "Could not create an instance of Vulkan"); + throw std::runtime_error("aethiopicus launch err, check logs"); + } + + if (glfwCreateWindowSurface(mVulkanInstance, mWindow, nullptr, &mVulkanSurface) != VK_SUCCESS) throw std::runtime_error("Couldn't make a surface for vulkan to use"); + + if (useValidationLayers) { + VkDebugUtilsMessengerCreateInfoEXT createInfo; + + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + createInfo.pUserData = (void*)this; + + // Remove vulkan warnings + createInfo.pNext = NULL; + createInfo.flags = 0; + + if (createDebugUtilsMessengerEXT(mVulkanInstance, &createInfo, nullptr, &mDebugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } + } + + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(mVulkanInstance, &deviceCount, nullptr); + + if (deviceCount == 0) throw std::runtime_error("No vulkan support on available graphics cards"); + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(mVulkanInstance, &deviceCount, devices.data()); + + std::multimap candidates; + + for (const auto& device : devices) { + // Get properties here for logging purposes + VkPhysicalDeviceProperties deviceProperties; + vkGetPhysicalDeviceProperties(device, &deviceProperties); + + int score = rateDeviceSuitability(device, deviceProperties); + + std::string deviceTypeAsString; + + switch (deviceProperties.deviceType) { + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + deviceTypeAsString = "VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU"; + break; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + deviceTypeAsString = "VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU"; + break; + case VK_PHYSICAL_DEVICE_TYPE_CPU: + deviceTypeAsString = "VK_PHYSICAL_DEVICE_TYPE_CPU"; + break; + case VK_PHYSICAL_DEVICE_TYPE_MAX_ENUM: + deviceTypeAsString = "VK_PHYSICAL_DEVICE_TYPE_MAX_ENUM"; + break; + case VK_PHYSICAL_DEVICE_TYPE_OTHER: + deviceTypeAsString = "VK_PHYSICAL_DEVICE_TYPE_OTHER"; + break; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + deviceTypeAsString = "VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU"; + break; + } + + // Logging the info + mLogger(Information, fmt::format("Device {} has a score of {}", deviceProperties.deviceName, score)); + mLogger(Information, fmt::format("{}'s device type: {}", deviceProperties.deviceName, deviceTypeAsString)); + + // Insert into candidates map + candidates.insert(std::make_pair(score, device)); + } + + // Validate that the first candidate has a better score than 0. + if (candidates.rbegin()->first > 0) { + mVulkanGraphicsDevice = candidates.rbegin()->second; + } else { + throw std::runtime_error("failed to find a suitable GPU!"); + } + + // Logical device creation + QueueFamilyIndicies indices = findQueueFamilies(mVulkanGraphicsDevice, mVulkanSurface); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo logicalDeviceCreateInfo{}; + logicalDeviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + logicalDeviceCreateInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + logicalDeviceCreateInfo.pQueueCreateInfos = queueCreateInfos.data(); + + logicalDeviceCreateInfo.pEnabledFeatures = &deviceFeatures; + + logicalDeviceCreateInfo.enabledExtensionCount = static_cast(mRequiredDeviceExtensions.size()); + logicalDeviceCreateInfo.ppEnabledExtensionNames = mRequiredDeviceExtensions.data(); + + if (mUseValidationLayers) { + logicalDeviceCreateInfo.enabledLayerCount = static_cast(mValidationLayers.size()); + logicalDeviceCreateInfo.ppEnabledLayerNames = mValidationLayers.data(); + } else { + logicalDeviceCreateInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(mVulkanGraphicsDevice, &logicalDeviceCreateInfo, nullptr, &mVulkanLogicalDevice) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + + vkGetDeviceQueue(mVulkanLogicalDevice, indices.graphicsFamily.value(), 0, &mVulkanGraphicsQueue); + vkGetDeviceQueue(mVulkanLogicalDevice, indices.presentFamily.value(), 0, &mVulkanPresentQueue); + + // Create a swap chain + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(mVulkanGraphicsDevice, mVulkanSurface); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities, mWindow); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = mVulkanSurface; + + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndicies graphicsIndicies = findQueueFamilies(mVulkanGraphicsDevice, mVulkanSurface); + uint32_t queueFamilyIndices[] = {graphicsIndicies.graphicsFamily.value(), graphicsIndicies.presentFamily.value()}; + + if (graphicsIndicies.graphicsFamily != graphicsIndicies.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.queueFamilyIndexCount = 0; // Optional + createInfo.pQueueFamilyIndices = nullptr; // Optional + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(mVulkanLogicalDevice, &createInfo, nullptr, &mVulkanSwapchain) != VK_SUCCESS) { + throw std::runtime_error("failed to create swap chain!"); + } + + mSwapChainImageFormat = surfaceFormat.format; + mSwapChainExtent = extent; + + vkGetSwapchainImagesKHR(mVulkanLogicalDevice, mVulkanSwapchain, &imageCount, nullptr); + mSwapchainImages.resize(imageCount); + vkGetSwapchainImagesKHR(mVulkanLogicalDevice, mVulkanSwapchain, &imageCount, mSwapchainImages.data()); + + // We're still fucking going with initialization, fucking christ + mSwapChainImageViews.resize(mSwapchainImages.size()); + for (size_t i = 0; i < mSwapchainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = mSwapchainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = mSwapChainImageFormat; + + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(mVulkanLogicalDevice, &createInfo, nullptr, &mSwapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create image views!"); + } + } + } + + Aethiopicus::~Aethiopicus() { + if (mUseValidationLayers) { + destroyDebugUtilsMessengerEXT(mVulkanInstance, mDebugMessenger, nullptr); + } + + for (auto imageView : mSwapChainImageViews) { + vkDestroyImageView(mVulkanLogicalDevice, imageView, nullptr); + } + + vkDestroySwapchainKHR(mVulkanLogicalDevice, mVulkanSwapchain, nullptr); + vkDestroyDevice(mVulkanLogicalDevice, nullptr); + vkDestroySurfaceKHR(mVulkanInstance, mVulkanSurface, nullptr); + vkDestroyInstance(mVulkanInstance, nullptr); + glfwDestroyWindow(mWindow); + glfwTerminate(); + } + + bool Aethiopicus::checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : mValidationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; + } + + int Aethiopicus::rateDeviceSuitability(VkPhysicalDevice device, VkPhysicalDeviceProperties deviceProperties) { + // TODO: Better scoring, accounting for 3D limits + VkPhysicalDeviceFeatures deviceFeatures; + vkGetPhysicalDeviceFeatures(device, &deviceFeatures); + + QueueFamilyIndicies indicies = findQueueFamilies(device, mVulkanSurface); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + int score = 0; + + if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) score += 1000; + else if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) score += 500; + + score += deviceProperties.limits.maxImageDimension2D; + + if (!deviceFeatures.geometryShader) return 0; + + if (!indicies.isComplete()) return 0; + + if (!extensionsSupported) return 0; + + bool swapChainAdequate = false; + + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device, mVulkanSurface); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + if (!swapChainAdequate) return 0; + + return score; + } + + bool Aethiopicus::checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(mRequiredDeviceExtensions.begin(), mRequiredDeviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + VkResult Aethiopicus::createDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + } + + void Aethiopicus::destroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } + } + + void Aethiopicus::clearScreen(Color col) {} + + void Aethiopicus::renderCurrent() {} + + void Aethiopicus::drawText(Resource* resource, std::string text, IntVec2 pos, Color color) {} + void Aethiopicus::drawTexture(Resource* resource, IntRect size) {} + + void Aethiopicus::useShader(Shader* shader, Point2D points[3]) {} + void Aethiopicus::stopUsingShaders() {} + + // Pre and Post draw + void Aethiopicus::preDraw() {} + void Aethiopicus::postDraw() {} + + // Update + void Aethiopicus::update() {} + + // Util + void Aethiopicus::compileShader(Shader* shader) {} + void Aethiopicus::toggleWireframe() {} + void Aethiopicus::setWindowTitle(std::string title) {} + void Aethiopicus::resize(unsigned int width, unsigned int height) {} +} diff --git a/renderer/aethiopicus/aethiopicus.hpp b/renderer/aethiopicus/aethiopicus.hpp new file mode 100644 index 0000000..cba4270 --- /dev/null +++ b/renderer/aethiopicus/aethiopicus.hpp @@ -0,0 +1,100 @@ +#pragma once + +#define GLFW_INCLUDE_VULKAN +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include +#include + +#include +#include + +namespace hibis::aethiopicus { + /** \class Aethiopicus + * Vulkan-based renderer for the Hibis game engine. + * + * @param title Application window title + * @param internalName Application internal name (used by Aethiopicus::mVulkanInstance) + * @param version A semantic version stored in an std::string + * @param size Size of the application window + * @param callback Logging callback + */ + class Aethiopicus : public Renderer { + public: + Aethiopicus(std::string title, std::string internalName, std::string version, IntVec2 size, LoggerCallback callback, bool useValidationLayers = true); + ~Aethiopicus(); + + // Draw + void clearScreen(Color col = Color {0, 0, 0, 255}) override; + void renderCurrent() override; + + void drawText(Resource* resource, std::string text, IntVec2 pos, Color color) override; + void drawTexture(Resource* resource, IntRect size) override; + + void useShader(Shader* shader, Point2D points[3]) override; + void stopUsingShaders() override; + + // Pre and Post draw + void preDraw() override; + void postDraw() override; + + // Update + void update() override; + + // Util + void compileShader(Shader* shader) override; + void toggleWireframe() override; + void setWindowTitle(std::string title) override; + void resize(unsigned int width, unsigned int height); + + std::vector mGLFWExtensionVector; + uint32_t mGLFWExtensionCount = 0; + const char** mGLFWExtensions; + + std::vector mVulkanExtensions; + private: + // Functions + bool checkValidationLayerSupport(); + + int rateDeviceSuitability(VkPhysicalDevice device, VkPhysicalDeviceProperties deviceProperties); + + bool checkDeviceExtensionSupport(VkPhysicalDevice device); + + VkResult createDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger); + void destroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator); + // GLFW + GLFWwindow* mWindow; + + // Vulkan + VkInstance mVulkanInstance; + VkPhysicalDevice mVulkanGraphicsDevice = VK_NULL_HANDLE; + VkDevice mVulkanLogicalDevice; + + VkQueue mVulkanGraphicsQueue; + VkQueue mVulkanPresentQueue; + + VkSurfaceKHR mVulkanSurface; + VkSwapchainKHR mVulkanSwapchain; + + VkDebugUtilsMessengerEXT mDebugMessenger; + + VkFormat mSwapChainImageFormat; + VkExtent2D mSwapChainExtent; + + std::vector mSwapchainImages; + std::vector mSwapChainImageViews; + + bool mUseValidationLayers = false; + + const std::vector mValidationLayers = { + "VK_LAYER_KHRONOS_validation" + }; + + const std::vector mRequiredDeviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME + }; + }; +} diff --git a/renderer/aethiopicus/vkutil.cpp b/renderer/aethiopicus/vkutil.cpp new file mode 100644 index 0000000..a1b3fd9 --- /dev/null +++ b/renderer/aethiopicus/vkutil.cpp @@ -0,0 +1,99 @@ +#include +#include +#include +#include + +#include "vkutil.hpp" + +QueueFamilyIndicies findQueueFamilies(VkPhysicalDevice device, VkSurfaceKHR surface) { + QueueFamilyIndicies indicies; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indicies.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + + if (presentSupport) { + indicies.presentFamily = i; + } + + if (indicies.isComplete()) break; + + i++; + } + + return indicies; +} + +SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device, VkSurfaceKHR surface) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + } + + return details; +} + +VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; +} + +VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; +} + +VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, GLFWwindow* window) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } +} diff --git a/renderer/aethiopicus/vkutil.hpp b/renderer/aethiopicus/vkutil.hpp new file mode 100644 index 0000000..af482e6 --- /dev/null +++ b/renderer/aethiopicus/vkutil.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#define GLFW_INCLUDE_VULKAN +#include + +struct QueueFamilyIndicies { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { return graphicsFamily.has_value() && presentFamily.has_value(); } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +extern QueueFamilyIndicies findQueueFamilies(VkPhysicalDevice device, VkSurfaceKHR surface); + +extern SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device, VkSurfaceKHR surface); + +extern VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats); + +extern VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes); + +extern VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, GLFWwindow* window);