diff --git a/CMakeLists.txt b/CMakeLists.txt index d557ab2..899b09c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,24 @@ cmake_minimum_required(VERSION 3.0) -project(stories) +project(stories VERSION 0.0.1 LANGUAGES CXX) +cmake_policy(SET CMP0076 NEW) +cmake_policy(SET CMP0079 NEW) +set(CMAKE_CXX_STANDARD 17) + +find_package(SDL2 REQUIRED) +find_package(Vulkan REQUIRED) add_executable(stories main.cpp) +target_include_directories(stories PRIVATE ${CMAKE_SOURCE_DIR}) + +target_link_libraries(stories + engine + SDL2::SDL2 + Vulkan::Vulkan +) + +add_subdirectory(engine) +add_subdirectory(shaders) install(TARGETS stories RUNTIME DESTINATION bin) + diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt new file mode 100644 index 0000000..f3014ae --- /dev/null +++ b/engine/CMakeLists.txt @@ -0,0 +1,25 @@ +set(SOURCES + instance.cpp + engine.cpp + utils.cpp + window.cpp + surface.cpp + physicaldevice.cpp + logicaldevice.cpp + swapchain.cpp +) + +set(HEADERS + instance.h + engine.h + utils.h + window.h + surface.h + physicaldevice.h + logicaldevice.h + swapchain.h +) + +add_library(engine STATIC ${SOURCES}) + +target_include_directories(engine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/engine/engine.cpp b/engine/engine.cpp new file mode 100644 index 0000000..ea08e49 --- /dev/null +++ b/engine/engine.cpp @@ -0,0 +1,138 @@ +#include "engine.h" +#include "instance.h" + +constexpr const char* validationLayerName("VK_LAYER_KHRONOS_validation"); + +Engine::Engine::Engine() : + initialized(false), + window(new Window()), + instance(nullptr), + surface(nullptr), + physicalDevice(nullptr), + logicalDevice(nullptr), + layerNames(), + instanceExtensionNames(), + deviceExtensionNames(), + layers(), + instanceExtensions(), + deviceExtensions() +{ + addDeviceExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); +} + +Engine::Engine::~Engine() { + delete window; +} + +bool Engine::Engine::enableValidationLayers() const { + return instanceExtensionNames.count(VK_EXT_DEBUG_REPORT_EXTENSION_NAME) > 0 && + instanceExtensionNames.count(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) > 0 && + layerNames.count(validationLayerName) > 0; +} + +void Engine::Engine::addLayer(const std::string& layerName) { + if (initialized) + throw std::runtime_error("Adding layers to an initialized engine is not supported yet"); + + layerNames.insert(layerName); +} + +void Engine::Engine::addInstanceExtension(const std::string& extensionName) { + if (initialized) + throw std::runtime_error("Adding extension to an initialized engine is not supported yet"); + + std::pair::const_iterator, bool> pair = instanceExtensionNames.insert(extensionName); + if (pair.second) + instanceExtensions.push_back(pair.first->c_str()); +} + +void Engine::Engine::addDeviceExtension(const std::string& extensionName) { + if (initialized) + throw std::runtime_error("Adding extension to an initialized engine is not supported yet"); + + std::pair::const_iterator, bool> pair = deviceExtensionNames.insert(extensionName); + if (pair.second) + deviceExtensions.push_back(pair.first->c_str()); +} + +void Engine::Engine::enableDebug() { + addLayer(validationLayerName); + addInstanceExtension(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + addInstanceExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); +} + +void Engine::Engine::initVulkan() { + instance = new Instance(this); + surface = new Surface(instance, window); + + pickPhysicalDevice(); + logicalDevice = new LogicalDevice(physicalDevice, surface, deviceExtensions, layers); + + logicalDevice->createGraphicsPipeline("shaders/shader.vert.spv", "shaders/shader.frag.spv"); + logicalDevice->createSwapChain(); +} + +void Engine::Engine::cleanup() { + delete logicalDevice; + logicalDevice = nullptr; + + delete physicalDevice; + physicalDevice = nullptr; + + delete surface; + surface = nullptr; + + delete instance; + instance = nullptr; +} + +void Engine::Engine::pickPhysicalDevice() { + std::vector devices = instance->enumeratePhysicalDevices(); + if (devices.size() == 0) + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + + for (const auto& device : devices) { + if (PhysicalDevice::checkDeviceExtensionSupport(device, deviceExtensionNames)) { + QueueFamilyIndices indices = surface->findQueueFamilies(device); + if (indices.isComplete()) { + SwapChainSupportDetails scsd = surface->querySwapChainSupport(device); + if (scsd.adequate()) { + physicalDevice = new PhysicalDevice(device, indices, scsd); + break; + } + } + } + } + + if (physicalDevice == nullptr) + throw std::runtime_error("failed to find a suitable GPU!"); +} + +std::vector Engine::Engine::getRequiredVulkanExtensions() const { + return window->getRequiredVulkanExtensions(); +} + +void Engine::Engine::run() { + initVulkan(); + mainLoop(); + cleanup(); +} + +void Engine::Engine::mainLoop() { + SDL_Event e; + bool bQuit = false; + + //main loop + while (!bQuit) { + //Handle events on queue + while (SDL_PollEvent(&e) != 0) { + //close the window when user clicks the X button or alt-f4s + if (e.type == SDL_QUIT) + bQuit = true; + } + + logicalDevice->drawFrame(); + } + + logicalDevice->waitIdle(); +} diff --git a/engine/engine.h b/engine/engine.h new file mode 100644 index 0000000..3e46bf2 --- /dev/null +++ b/engine/engine.h @@ -0,0 +1,70 @@ +#pragma once + +#include + +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "window.h" +#include "surface.h" +#include "physicaldevice.h" +#include "logicaldevice.h" + +namespace Engine { + +const int MAX_FRAMES_IN_FLIGHT = 2; + +class Instance; + +class Engine { + friend class Instance; +public: + Engine(); + ~Engine(); + + void run(); + bool enableValidationLayers() const; + void addLayer(const std::string& layerName); + void addInstanceExtension(const std::string& extensionName); + void addDeviceExtension(const std::string& extensionName); + void enableDebug(); + std::vector getRequiredVulkanExtensions() const; + +private: + bool initialized; + Window* window; + Instance* instance; + Surface* surface; + PhysicalDevice* physicalDevice; + LogicalDevice* logicalDevice; + std::set layerNames; + std::set instanceExtensionNames; + std::set deviceExtensionNames; + std::vector layers; + std::vector instanceExtensions; + std::vector deviceExtensions; + + void initVulkan(); + void mainLoop(); + void cleanup(); + void pickPhysicalDevice(); +}; + +} + diff --git a/engine/instance.cpp b/engine/instance.cpp new file mode 100644 index 0000000..d2550ef --- /dev/null +++ b/engine/instance.cpp @@ -0,0 +1,110 @@ +#include "instance.h" + +#include "engine.h" + +Engine::Instance::Instance(Engine* eng) : + validationLayersEnabledAndSupported(false), + engine(eng), + vk(), + debugMessenger() +{ + VkApplicationInfo appInfo {}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(0, 0, 1); + appInfo.pEngineName = "Stories"; + appInfo.engineVersion = VK_MAKE_VERSION(0, 0, 1); + appInfo.apiVersion = VK_API_VERSION_1_0; + appInfo.pNext = nullptr; + + //TODO handle exception + Support exts = getVulkanExtensions(engine->instanceExtensionNames, engine->getRequiredVulkanExtensions()); + if (exts.unsupportedCritical.size() > 0) { + std::string error("Unable to create Vulkan instance, following critical extensions are unsupported:\n"); + for (const char* ext : exts.unsupportedCritical) { + error += "\t" + std::string(ext) + "\n"; + } + throw std::runtime_error(error); + } + //TODO log unsupported; + exts.logSupported("Applying extensions:"); + + //TODO handle exception + Support layers = getVulkanLayers(engine->layerNames); + //TODO log unsupported + layers.logSupported("Applying layers:"); + + VkInstanceCreateInfo createInfo {}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + createInfo.flags = 0; + createInfo.enabledExtensionCount = static_cast(exts.supported.size()); + createInfo.ppEnabledExtensionNames = exts.supported.data(); + createInfo.enabledLayerCount = static_cast(layers.supported.size()); + createInfo.ppEnabledLayerNames = layers.supported.data(); + + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo {}; + if (engine->enableValidationLayers()) { + validationLayersEnabledAndSupported = true; //TODO make sure it's actually supported, this shows that they are just enabled; + populateDebugMessengerCreateInfo(debugCreateInfo, debugCallback); + createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; + } + + VkResult res = vkCreateInstance(&createInfo, nullptr, &vk); + switch (res) { + case VK_SUCCESS: + break; + case VK_ERROR_INCOMPATIBLE_DRIVER: + throw std::runtime_error("unable to create vulkan instance, cannot find a compatible Vulkan ICD"); + case VK_ERROR_OUT_OF_HOST_MEMORY: + throw std::runtime_error("unable to create vulkan instance, out of host memory"); + case VK_ERROR_OUT_OF_DEVICE_MEMORY: + throw std::runtime_error("unable to create vulkan instance, out of device memory"); + case VK_ERROR_INITIALIZATION_FAILED: + throw std::runtime_error("unable to create vulkan instance, initialization failed"); + case VK_ERROR_LAYER_NOT_PRESENT: + throw std::runtime_error("unable to create vulkan instance, layer not present"); + case VK_ERROR_EXTENSION_NOT_PRESENT: + throw std::runtime_error("unable to create vulkan instance, extension not present"); + default: + throw std::runtime_error("unable to create Vulkan instance: unknown error"); + } + + if (validationLayersEnabledAndSupported) + setupDebugMessenger(); +} + +Engine::Instance::~Instance() { + if (validationLayersEnabledAndSupported) + destroyDebugUtilsMessengerEXT(vk, debugMessenger, nullptr); + + vkDestroyInstance(vk, nullptr); +} + +void Engine::Instance::setupDebugMessenger() { + VkDebugUtilsMessengerCreateInfoEXT createInfo{}; + populateDebugMessengerCreateInfo(createInfo, debugCallback); + + VkResult res = createDebugUtilsMessengerEXT(vk, &createInfo, nullptr, &debugMessenger); + switch (res) { + case VK_SUCCESS: + break; + case VK_ERROR_EXTENSION_NOT_PRESENT: + throw std::runtime_error("failed to set up debug messenger: extension not present"); + break; + default: + throw std::runtime_error("failed to set up debug messenger"); + } +} + +std::vector Engine::Instance::enumeratePhysicalDevices() const { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(vk, &deviceCount, nullptr); + + std::vector devices(deviceCount); + if (deviceCount > 0) + vkEnumeratePhysicalDevices(vk, &deviceCount, devices.data()); + + return devices; +} + diff --git a/engine/instance.h b/engine/instance.h new file mode 100644 index 0000000..c26d0d2 --- /dev/null +++ b/engine/instance.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include "utils.h" + +namespace Engine { + +class Engine; +class Window; +class Surface; + +class Instance { + friend class Engine; + friend class Surface; +public: + Instance(Engine* engine); + ~Instance(); + + std::vector enumeratePhysicalDevices() const; + +private: + void setupDebugMessenger(); + +private: + bool validationLayersEnabledAndSupported; + Engine* engine; + VkInstance vk; + VkDebugUtilsMessengerEXT debugMessenger; +}; + +} diff --git a/engine/logicaldevice.cpp b/engine/logicaldevice.cpp new file mode 100644 index 0000000..1f225fb --- /dev/null +++ b/engine/logicaldevice.cpp @@ -0,0 +1,568 @@ +#include "logicaldevice.h" + +#include "swapchain.h" + +constexpr int MAX_FRAMES_IN_FLIGHT = 2; +constexpr float queuePriority = 1.0f; + +Engine::LogicalDevice::LogicalDevice( + PhysicalDevice* physicalDevice, + Surface* surface, + const std::vector& extensions, + const std::vector layers +): + phys(physicalDevice), + vk(), + graphicsQueue(), + presentQueue(), + renderPass(), + pipelineLayout(), + graphicsPipeline(), + commandPool(), + commandBuffers(), + imageAvailableSemaphores(), + renderFinishedSemaphores(), + inFlightFences(), + currentFrame(0), + framebufferResized(false), + hasPipeline(false), + surface(surface), + surfaceFormat(surface->chooseSwapSurfaceFormat(phys->swapChainSupport)), + swapChain(nullptr) +{ + const QueueFamilyIndices& indices = phys->indices; + + createDevice(phys->vk, indices, extensions, layers); + createQueues(indices.graphicsFamily.value(), indices.presentFamily.value()); + + createCommandPool(phys->indices.graphicsFamily.value()); + createCommandBuffers(); + createSyncObjects(); + createRenderPass(surfaceFormat.format); +} + +Engine::LogicalDevice::~LogicalDevice() { + clearSwapChain(); + + if (hasPipeline) { + vkDestroyPipeline(vk, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(vk, pipelineLayout, nullptr); + } + + vkDestroyRenderPass(vk, renderPass, nullptr); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(vk, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(vk, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(vk, inFlightFences[i], nullptr); + } + + vkFreeCommandBuffers(vk, commandPool, MAX_FRAMES_IN_FLIGHT, commandBuffers.data()); + vkDestroyCommandPool(vk, commandPool, nullptr); + + vkDestroyDevice(vk, nullptr); +} + +void Engine::LogicalDevice::createSwapChain() { + clearSwapChain(); + swapChain = new SwapChain(this); +} + +void Engine::LogicalDevice::clearSwapChain() { + if (swapChain != nullptr) + delete swapChain; + + swapChain = nullptr; +} + +VkResult Engine::LogicalDevice::waitIdle() { + return vkDeviceWaitIdle(vk); +} + +VkResult Engine::LogicalDevice::waitForFence(const VkFence& fence) { + return vkWaitForFences(vk, 1, &fence, VK_TRUE, UINT64_MAX); +} + +VkResult Engine::LogicalDevice::resetFence(const VkFence& fence) { + return vkResetFences(vk, 1, &fence); +} + +VkResult Engine::LogicalDevice::queueSubmitGraphics( + const VkSemaphore& wait, + const VkCommandBuffer& buffer, + const VkSemaphore& signal, + const VkFence& fence +) { + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = &wait; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &buffer; + + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &signal; + + return vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence); +} + +VkResult Engine::LogicalDevice::queuePresent(const VkSemaphore& signal, uint32_t index) { + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &signal; + + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = &swapChain->vk; + + presentInfo.pImageIndices = &index; + + return vkQueuePresentKHR(presentQueue, &presentInfo); +} + +VkShaderModule Engine::LogicalDevice::createShaderModule(const std::string& path) { + std::vector code = readFile(path); + + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(vk, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) + throw std::runtime_error("failed to create shader module!"); + + return shaderModule; +} + +void Engine::LogicalDevice::destroyShaderModule(VkShaderModule shaderModule) { + vkDestroyShaderModule(vk, shaderModule, nullptr); +} + +void Engine::LogicalDevice::createDevice( + VkPhysicalDevice physicalDevice, + const QueueFamilyIndices& indices, + const std::vector& extensions, + const std::vector layers +) { + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + + 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 createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + createInfo.enabledLayerCount = static_cast(layers.size()); + createInfo.ppEnabledLayerNames = layers.data(); + + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &vk) != VK_SUCCESS) + throw std::runtime_error("failed to create logical device!"); +} + +void Engine::LogicalDevice::createQueues(uint32_t graphicsFamilyIndex, uint32_t presenFamilyIndex) { + vkGetDeviceQueue(vk, graphicsFamilyIndex, 0, &graphicsQueue); + vkGetDeviceQueue(vk, presenFamilyIndex, 0, &presentQueue); +} + +void Engine::LogicalDevice::createSyncObjects() { + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + VkSemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(vk, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(vk, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(vk, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create synchronization objects for a frame!"); + } + } +} + +void Engine::LogicalDevice::createCommandPool(uint32_t queueFamilyIndex) { + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndex; + + if (vkCreateCommandPool(vk, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) + throw std::runtime_error("failed to create command pool!"); +} + +void Engine::LogicalDevice::createCommandBuffers() { + commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); + + if (vkAllocateCommandBuffers(vk, &allocInfo, commandBuffers.data()) != VK_SUCCESS) + throw std::runtime_error("failed to allocate command buffers!"); +} + +void Engine::LogicalDevice::drawFrame() { + waitForFence(inFlightFences[currentFrame]); + + uint32_t imageIndex; + VkResult result = vkAcquireNextImageKHR(vk, swapChain->vk, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + + resetFence(inFlightFences[currentFrame]); + + vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(commandBuffers[currentFrame], imageIndex); + + if (queueSubmitGraphics( + imageAvailableSemaphores[currentFrame], + commandBuffers[currentFrame], + renderFinishedSemaphores[currentFrame], + inFlightFences[currentFrame]) != VK_SUCCESS + ) + throw std::runtime_error("failed to submit draw command buffer!"); + + result = queuePresent(renderFinishedSemaphores[currentFrame], imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; +} + +VkExtent2D Engine::LogicalDevice::chooseSwapExtent() const { + return surface->chooseSwapExtent(phys->swapChainSupport.capabilities); +} + +VkResult Engine::LogicalDevice::createVkSwapChain(const VkExtent2D& extent, VkSwapchainKHR& out) { + const SwapChainSupportDetails& swapChainSupport = phys->swapChainSupport; + + 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 = surface->vk; + + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + createInfo.imageExtent = extent; + createInfo.minImageCount = imageCount; + + const QueueFamilyIndices& indices = phys->indices; + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + createInfo.clipped = VK_TRUE; + + return vkCreateSwapchainKHR(vk, &createInfo, nullptr, &out); +} + +void Engine::LogicalDevice::destroyVkSwapChain(VkSwapchainKHR& swapChain) { + vkDestroySwapchainKHR(vk, swapChain, nullptr); +} + +VkResult Engine::LogicalDevice::getVkSwapChainImages(VkSwapchainKHR& swapChain, std::vector& out) { + uint32_t imageCount; + VkResult result = vkGetSwapchainImagesKHR(vk, swapChain, &imageCount, nullptr); + if (result != VK_SUCCESS) + return result; + + out.resize(imageCount); + return vkGetSwapchainImagesKHR(vk, swapChain, &imageCount, out.data()); +} + +VkResult Engine::LogicalDevice::createVkImageView(const VkImage& image, VkImageView& out) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = image; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = surfaceFormat.format; + 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; + + return vkCreateImageView(vk, &createInfo, nullptr, &out); +} + +void Engine::LogicalDevice::destroyVkImageView(VkImageView& view) { + vkDestroyImageView(vk, view, nullptr); +} + +VkResult Engine::LogicalDevice::createVkFrameBuffer(const VkImageView& imageView, const VkExtent2D& extent, VkFramebuffer& out) { + VkImageView attachments[] = {imageView}; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = extent.width; + framebufferInfo.height = extent.height; + framebufferInfo.layers = 1; + + return vkCreateFramebuffer(vk, &framebufferInfo, nullptr, &out); +} + +void Engine::LogicalDevice::destroyVkFrameBuffer(VkFramebuffer& buffer) { + vkDestroyFramebuffer(vk, buffer, nullptr); +} + +void Engine::LogicalDevice::createRenderPass(VkFormat format) { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = format; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkSubpassDependency dependency{}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if (vkCreateRenderPass(vk, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) + throw std::runtime_error("failed to create render pass!"); +} + +void Engine::LogicalDevice::createGraphicsPipeline(const std::string& vertexShaderPath, const std::string& fragmentShaderPath) { + if (hasPipeline) + throw std::runtime_error("an attempt to create graphics pipeline for the second time"); + + VkShaderModule vertShaderModule = createShaderModule(vertexShaderPath); + VkShaderModule fragShaderModule = createShaderModule(fragmentShaderPath); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(vk, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) + throw std::runtime_error("failed to create pipeline layout!"); + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(vk, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) + throw std::runtime_error("failed to create graphics pipeline!"); + + destroyShaderModule(fragShaderModule); + destroyShaderModule(vertShaderModule); + + hasPipeline = true; +} + +void Engine::LogicalDevice::recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkExtent2D extent = swapChain->getExtent(); + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) + throw std::runtime_error("failed to begin recording command buffer!"); + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChain->getFrameBuffer(imageIndex); + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = extent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float) extent.width; + viewport.height = (float) extent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = extent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) + throw std::runtime_error("failed to record command buffer!"); +} + +void Engine::LogicalDevice::recreateSwapChain() { + surface->waitForResize(); + waitIdle(); + createSwapChain(); +} diff --git a/engine/logicaldevice.h b/engine/logicaldevice.h new file mode 100644 index 0000000..30e13ac --- /dev/null +++ b/engine/logicaldevice.h @@ -0,0 +1,92 @@ +#pragma once + +#include +#include + +#include + +#include "utils.h" +#include "physicaldevice.h" +#include "surface.h" + +namespace Engine { +class Engine; +class SwapChain; + +class LogicalDevice { + friend class Engine; +public: + LogicalDevice( + PhysicalDevice* physicalDevice, + Surface* surface, + const std::vector& extensions, + const std::vector layers + ); + ~LogicalDevice(); + + void createSwapChain(); + void clearSwapChain(); + void createGraphicsPipeline(const std::string& vertexShaderPath, const std::string& fragmentShaderPath); + void recreateSwapChain(); + + void drawFrame(); + + VkResult waitIdle(); + VkResult waitForFence(const VkFence& fence); + VkResult resetFence(const VkFence& fence); + + VkExtent2D chooseSwapExtent() const; + VkResult createVkSwapChain(const VkExtent2D& extent, VkSwapchainKHR& out); + void destroyVkSwapChain(VkSwapchainKHR& swapChain); + VkResult getVkSwapChainImages(VkSwapchainKHR& swapChain, std::vector& out); + VkResult createVkImageView(const VkImage& image, VkImageView& out); + void destroyVkImageView(VkImageView& view); + VkResult createVkFrameBuffer(const VkImageView& imageView, const VkExtent2D& extent, VkFramebuffer& out); + void destroyVkFrameBuffer(VkFramebuffer& buffer); + +private: + VkShaderModule createShaderModule(const std::string& path); + void destroyShaderModule(VkShaderModule shaderModule); + void createDevice( + VkPhysicalDevice physicalDevice, + const QueueFamilyIndices& indices, + const std::vector& extensions, + const std::vector layers + ); + void createQueues(uint32_t graphicsFamilyIndex, uint32_t presenFamilyIndex); + void createCommandPool(uint32_t queueFamilyIndex); + void createCommandBuffers(); + void createSyncObjects(); + void createRenderPass(VkFormat format); + + VkResult queueSubmitGraphics( + const VkSemaphore& wait, + const VkCommandBuffer& buffer, + const VkSemaphore& signal, + const VkFence& fence + ); + VkResult queuePresent(const VkSemaphore& signal, uint32_t index); + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex); + +private: + PhysicalDevice* phys; + VkDevice vk; + VkQueue graphicsQueue; + VkQueue presentQueue; + VkRenderPass renderPass; + VkPipelineLayout pipelineLayout; + VkPipeline graphicsPipeline; + VkCommandPool commandPool; + std::vector commandBuffers; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + uint32_t currentFrame; + bool framebufferResized; + bool hasPipeline; + Surface* surface; + VkSurfaceFormatKHR surfaceFormat; + SwapChain* swapChain; +}; + +} diff --git a/engine/physicaldevice.cpp b/engine/physicaldevice.cpp new file mode 100644 index 0000000..50361ca --- /dev/null +++ b/engine/physicaldevice.cpp @@ -0,0 +1,34 @@ +#include "physicaldevice.h" + +Engine::PhysicalDevice::PhysicalDevice( + VkPhysicalDevice raw, + const QueueFamilyIndices& indices, + const SwapChainSupportDetails& swapChainSupport): + indices(indices), + swapChainSupport(swapChainSupport), + vk(raw) +{} + +bool Engine::PhysicalDevice::checkDeviceExtensionSupport(VkPhysicalDevice device, std::set requiredExtensions) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + for (const auto& extension : availableExtensions) + requiredExtensions.erase(extension.extensionName); + + if (!requiredExtensions.empty()) { + // std::cout << "Available Extensions: " << std::endl; + // for (const auto& ext : availableExtensions) + // std::cout << ext.extensionName << std::endl; + + + std::cout << "Missing Extensions: " << std::endl; + for (const auto& ext : requiredExtensions) + std::cout << ext << std::endl; + } + + return requiredExtensions.empty(); +} diff --git a/engine/physicaldevice.h b/engine/physicaldevice.h new file mode 100644 index 0000000..319fcf1 --- /dev/null +++ b/engine/physicaldevice.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +#include + +#include "utils.h" + +namespace Engine { +class Engine; +class LogicalDevice; + +class PhysicalDevice { + friend class Engine; + friend class LogicalDevice; +public: + PhysicalDevice(VkPhysicalDevice raw, const QueueFamilyIndices& indices, const SwapChainSupportDetails& swapChainSupport); + + static bool checkDeviceExtensionSupport(VkPhysicalDevice device, std::set requiredExtensions); + + const QueueFamilyIndices indices; + const SwapChainSupportDetails swapChainSupport; + +private: + VkPhysicalDevice vk; +}; + +} diff --git a/engine/surface.cpp b/engine/surface.cpp new file mode 100644 index 0000000..d559176 --- /dev/null +++ b/engine/surface.cpp @@ -0,0 +1,88 @@ +#include "surface.h" + +#include "window.h" +#include "instance.h" + +Engine::Surface::Surface(const Instance* instance, const Window* window): + vk(), + instance(instance), + window(window) +{ + window->createSurface(instance->vk, &vk); +} + +Engine::Surface::~Surface() { + vkDestroySurfaceKHR(instance->vk, vk, nullptr); +} + +bool Engine::Surface::isDeviceSutable(VkPhysicalDevice device) const { + QueueFamilyIndices indices = findQueueFamilies(device); + + if (!indices.isComplete()) + return false; + + return querySwapChainSupport(device).adequate(); +} + +Engine::SwapChainSupportDetails Engine::Surface::querySwapChainSupport(VkPhysicalDevice device) const { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, vk, &details.capabilities); + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, vk, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, vk, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, vk, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, vk, &presentModeCount, details.presentModes.data()); + } + + return details; +} + +Engine::QueueFamilyIndices Engine::Surface::findQueueFamilies(VkPhysicalDevice device) const { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + for (std::vector::size_type i = 0; i < queueFamilyCount; ++i) { + const VkQueueFamilyProperties& queueFamily = queueFamilies[i]; + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) + indices.graphicsFamily = i; + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, vk, &presentSupport); + + if (presentSupport) + indices.presentFamily = i; + + if (indices.isComplete()) + break; + } + + return indices; +} + +VkExtent2D Engine::Surface::chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) const { + return window->chooseSwapExtent(capabilities); +} + +VkSurfaceFormatKHR Engine::Surface::chooseSwapSurfaceFormat(const SwapChainSupportDetails& swapChainSupport) const { + return ::Engine::chooseSwapSurfaceFormat(swapChainSupport.formats); +} + +VkExtent2D Engine::Surface::waitForResize() const { + return window->waitForResize(); +} + diff --git a/engine/surface.h b/engine/surface.h new file mode 100644 index 0000000..8a92918 --- /dev/null +++ b/engine/surface.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "utils.h" + +namespace Engine { + +class Window; +class Instance; +class Engine; +class LogicalDevice; + +class Surface { + friend class Engine; + friend class LogicalDevice; +public: + Surface(const Instance* instance, const Window* window); + ~Surface(); + + VkExtent2D waitForResize() const; + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) const; + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const SwapChainSupportDetails& swapChainSupport) const; + bool isDeviceSutable(VkPhysicalDevice device) const; + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) const; + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) const; + +private: + VkSurfaceKHR vk; + const Instance* instance; + const Window* window; +}; + +} + diff --git a/engine/swapchain.cpp b/engine/swapchain.cpp new file mode 100644 index 0000000..182b462 --- /dev/null +++ b/engine/swapchain.cpp @@ -0,0 +1,51 @@ +#include "swapchain.h" + +#include "logicaldevice.h" + +Engine::SwapChain::SwapChain(LogicalDevice* logical): + logical(logical), + vk(), + images(), + extent(logical->chooseSwapExtent()), + imageViews(), + frameBuffers() +{ + VkResult result = logical->createVkSwapChain(extent, vk); + + if (result != VK_SUCCESS) + throw std::runtime_error("failed to create swap chain!"); + + logical->getVkSwapChainImages(vk, images); + + imageViews.resize(images.size()); + frameBuffers.resize(images.size()); + + for (size_t i = 0; i < images.size(); i++) { + if (logical->createVkImageView(images[i], imageViews[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create image views!"); + } + + for (size_t i = 0; i < images.size(); i++) { + if (logical->createVkFrameBuffer(imageViews[i], extent, frameBuffers[i]) != VK_SUCCESS) + throw std::runtime_error("failed to create framebuffer!"); + } +} + +Engine::SwapChain::~SwapChain() { + for (VkFramebuffer& buffer : frameBuffers) + logical->destroyVkFrameBuffer(buffer); + + for (VkImageView& imageView : imageViews) + logical->destroyVkImageView(imageView); + + //images are also indirectly freed by this operation + logical->destroyVkSwapChain(vk); +} + +VkExtent2D Engine::SwapChain::getExtent() const { + return extent; +} + +VkFramebuffer Engine::SwapChain::getFrameBuffer(uint32_t index) { + return frameBuffers[index]; +} diff --git a/engine/swapchain.h b/engine/swapchain.h new file mode 100644 index 0000000..5e5a465 --- /dev/null +++ b/engine/swapchain.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include + +namespace Engine { +class LogicalDevice; + +class SwapChain { + friend class LogicalDevice; +public: + SwapChain(LogicalDevice* logical); + ~SwapChain(); + + VkExtent2D getExtent() const; + VkFramebuffer getFrameBuffer(uint32_t index); + +private: + LogicalDevice* logical; + VkSwapchainKHR vk; + std::vector images; + VkExtent2D extent; + std::vector imageViews; + std::vector frameBuffers; +}; + +} + diff --git a/engine/utils.cpp b/engine/utils.cpp new file mode 100644 index 0000000..8b63217 --- /dev/null +++ b/engine/utils.cpp @@ -0,0 +1,194 @@ +#include "utils.h" + +bool discoveredLayers = false; +bool discoveredExtensions = false; +std::map availableLayers; +std::map availableExtensions; + +Engine::Support Engine::getVulkanLayers(const std::set& requestedLayers) +{ + Engine::Support result; + if (!discoveredLayers) { + std::vector props = getAvailableVulkanLayers(); + for (const VkLayerProperties& prop : props) { + availableLayers.insert(std::make_pair(prop.layerName, prop)); + } + discoveredLayers = true; + } + + for (const std::string& ext : requestedLayers) { + if (availableLayers.count(ext) > 0) { + result.supported.push_back(ext.c_str()); + } else { + result.unsupported.push_back(ext.c_str()); + } + } + + return result; +} + +Engine::Support Engine::getVulkanExtensions( + const std::set& requestedExtensions, + const std::vector& criticalExtensions +) { + Support result; + if (!discoveredExtensions) { + std::vector props = getAvailableVulkanExtensions(); + for (const VkExtensionProperties& prop : props) { + availableExtensions.insert(std::make_pair(prop.extensionName, prop)); + } + discoveredExtensions = true; + } + + for (const char* ext : criticalExtensions) { + if (availableExtensions.count(ext) > 0) { + result.supported.push_back(ext); + } else { + result.unsupportedCritical.push_back(ext); + } + } + + for (const std::string& ext : requestedExtensions) { + if (availableExtensions.count(ext) > 0) { + result.supported.push_back(ext.c_str()); + } else { + result.unsupported.push_back(ext.c_str()); + } + } + + return result; +} + +std::vector Engine::getAvailableVulkanExtensions() { + unsigned int extCount = 0; + VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extCount, nullptr); + if (res != VK_SUCCESS) { + throw std::runtime_error("unable to query vulkan extension property count"); + } + + std::vector extensions(extCount); + res = vkEnumerateInstanceExtensionProperties(nullptr, &extCount, extensions.data()); + if (res != VK_SUCCESS) { + throw std::runtime_error("unable to retrieve vulkan instance layer names"); + } + + return extensions; +} + +void Engine::populateDebugMessengerCreateInfo( + VkDebugUtilsMessengerCreateInfoEXT& createInfo, + PFN_vkDebugUtilsMessengerCallbackEXT debugCallback) +{ + 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_INFO_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 = nullptr; // Optional +} + +std::vector Engine::getAvailableVulkanLayers() { + unsigned int layerCount = 0; + VkResult res = vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + if (res != VK_SUCCESS) { + throw std::runtime_error("unable to query vulkan instance layer property count"); + } + + std::vector layers(layerCount); + res = vkEnumerateInstanceLayerProperties(&layerCount, layers.data()); + if (res != VK_SUCCESS) { + throw std::runtime_error("unable to retrieve vulkan instance layer names"); + } + + return layers; +} + +VkResult Engine::createDebugUtilsMessengerEXT( + VkInstance instance, + const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDebugUtilsMessengerEXT* pDebugMessenger) +{ + PFN_vkVoidFunction vdfunc = vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT)(vdfunc); + if (func != nullptr) + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + else + return VK_ERROR_EXTENSION_NOT_PRESENT; +} + +void Engine::destroyDebugUtilsMessengerEXT( + VkInstance instance, + VkDebugUtilsMessengerEXT debugMessenger, + const VkAllocationCallbacks* pAllocator) +{ + PFN_vkVoidFunction vdfunc = vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + PFN_vkDestroyDebugUtilsMessengerEXT func = (PFN_vkDestroyDebugUtilsMessengerEXT)(vdfunc); + if (func != nullptr) + func(instance, debugMessenger, pAllocator); +} + +Engine::Support::Support(): + supported(), + unsupported(), + unsupportedCritical() +{} + +void Engine::Support::logSupported(const std::string& header) { + if (!supported.empty()) { + std::cout << header << std::endl; + + for (const char* element : supported) + std::cout << "\t" << element << std::endl; + } +} + +bool Engine::SwapChainSupportDetails::adequate() const { + return !formats.empty() && !presentModes.empty(); +} + +bool Engine::QueueFamilyIndices::isComplete() const { + return graphicsFamily.has_value() && presentFamily.has_value(); +} + +std::vector Engine::readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) + throw std::runtime_error("failed to open file!"); + + size_t fileSize = (size_t) file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; +} + +VkSurfaceFormatKHR Engine::chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const VkSurfaceFormatKHR& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; +} + +VkPresentModeKHR Engine::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; +} diff --git a/engine/utils.h b/engine/utils.h new file mode 100644 index 0000000..d031d86 --- /dev/null +++ b/engine/utils.h @@ -0,0 +1,81 @@ +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace Engine { + +struct Support { + Support(); + + void logSupported(const std::string& header); + + std::vector supported; + std::vector unsupported; + std::vector unsupportedCritical; + +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; + + bool adequate() const; +}; + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() const; +}; + +Support getVulkanLayers(const std::set& requestedLayers); +Support getVulkanExtensions(const std::set& requestedExtensions, const std::vector& criticalExtensions); +VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats); +VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes); + +std::vector getAvailableVulkanExtensions(); +std::vector getAvailableVulkanLayers(); + +void populateDebugMessengerCreateInfo( + VkDebugUtilsMessengerCreateInfoEXT& createInfo, + PFN_vkDebugUtilsMessengerCallbackEXT debugCallback +); + +static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* callbackData, + void* userData +) +{ + std::cerr << "validation layer: " << callbackData->pMessage << std::endl; + return true; +} + +VkResult createDebugUtilsMessengerEXT( + VkInstance instance, + const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDebugUtilsMessengerEXT* pDebugMessenger +); + +void destroyDebugUtilsMessengerEXT( + VkInstance instance, + VkDebugUtilsMessengerEXT debugMessenger, + const VkAllocationCallbacks* pAllocator +); + +std::vector readFile(const std::string& filename); +} diff --git a/engine/window.cpp b/engine/window.cpp new file mode 100644 index 0000000..8c1ed3d --- /dev/null +++ b/engine/window.cpp @@ -0,0 +1,90 @@ +#include "window.h" + +Engine::Window::Window(uint32_t width, uint32_t height) : + sdl(createWindow(width, height)), + extent({width, height}) +{ + + //glfwSetWindowUserPointer(window, this); + //glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); +} + + // static void framebufferResizeCallback(SDL_Window* window, int width, int height) { + // auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + // app->framebufferResized = true; + // } + +Engine::Window::~Window() { + SDL_DestroyWindow(sdl); + SDL_Quit(); +} + +SDL_Window* Engine::Window::createWindow(uint32_t width, uint32_t height) { + SDL_Init(SDL_INIT_VIDEO); + SDL_Window* wnd = SDL_CreateWindow( + "Stories", //TODO parametrize window title + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + width, + height, + SDL_WINDOW_VULKAN + ); + + return wnd; +} + +std::vector Engine::Window::getRequiredVulkanExtensions() const { + unsigned int extCount = 0; + if (!SDL_Vulkan_GetInstanceExtensions(sdl, &extCount, nullptr)) { + throw std::runtime_error("Unable to query the number of Vulkan instance extensions"); + } + + std::vector extract(extCount); + if (!SDL_Vulkan_GetInstanceExtensions(sdl, &extCount, extract.data())) { + throw std::runtime_error("Unable to query the number of Vulkan instance extension names"); + } + + return extract; +} + +VkExtent2D Engine::Window::getDrawableSize() const { + int width, height; + SDL_Vulkan_GetDrawableSize(sdl, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + return actualExtent; +} + +void Engine::Window::createSurface(VkInstance instance, VkSurfaceKHR* out) const { + if (SDL_Vulkan_CreateSurface(sdl, instance, out) != SDL_TRUE) { + throw std::runtime_error("failed to create window surface!"); + } +} + +VkExtent2D Engine::Window::chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) const { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + VkExtent2D actualExtent = getDrawableSize(); + + 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; + } +} + +VkExtent2D Engine::Window::waitForResize() const { + VkExtent2D extent = getDrawableSize(); + while (extent.width == 0 || extent.height == 0) { + extent = getDrawableSize(); + SDL_WaitEvent(nullptr); + } + + return extent; +} + diff --git a/engine/window.h b/engine/window.h new file mode 100644 index 0000000..f05c05f --- /dev/null +++ b/engine/window.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include + +namespace Engine { + +class Engine; + +class Window { + friend class Engine; +public: + Window(uint32_t width = 800, uint32_t height = 600); + ~Window(); + + std::vector getRequiredVulkanExtensions() const; + VkExtent2D waitForResize() const; + VkExtent2D getDrawableSize() const; + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) const; + void createSurface(VkInstance instance, VkSurfaceKHR* out) const; + +private: + static SDL_Window* createWindow(uint32_t width = 800, uint32_t height = 600); + +private: + SDL_Window* sdl; + VkExtent2D extent; +}; + +} diff --git a/main.cpp b/main.cpp index 8bb47f1..357e5bb 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,16 @@ -#include +#include "engine/engine.h" -int main(int argc, char **argv) { - std::cout << "Hello, world!" << std::endl; - return 0; +int main() { + Engine::Engine app; + app.enableDebug(); + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } + diff --git a/shaders/CMakeLists.txt b/shaders/CMakeLists.txt new file mode 100644 index 0000000..76c7cc1 --- /dev/null +++ b/shaders/CMakeLists.txt @@ -0,0 +1,24 @@ +set(GLSL_VALIDATOR "glslangValidator") + +file(GLOB_RECURSE GLSL_SOURCE_FILES + "*.frag" + "*.vert" +) + +foreach(GLSL ${GLSL_SOURCE_FILES}) + get_filename_component(FILE_NAME ${GLSL} NAME) + set(SPIRV "${PROJECT_BINARY_DIR}/shaders/${FILE_NAME}.spv") + add_custom_command( + OUTPUT ${SPIRV} + COMMAND ${CMAKE_COMMAND} -E make_directory "${PROJECT_BINARY_DIR}/shaders/" + COMMAND ${GLSL_VALIDATOR} -V ${GLSL} -o ${SPIRV} + DEPENDS ${GLSL}) + list(APPEND SPIRV_BINARY_FILES ${SPIRV}) +endforeach(GLSL) + +add_custom_target( + Shaders + DEPENDS ${SPIRV_BINARY_FILES} +) + +add_dependencies(stories Shaders) diff --git a/shaders/shader.frag b/shaders/shader.frag new file mode 100644 index 0000000..7c5b0e7 --- /dev/null +++ b/shaders/shader.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/shaders/shader.vert b/shaders/shader.vert new file mode 100644 index 0000000..f5b2f8d --- /dev/null +++ b/shaders/shader.vert @@ -0,0 +1,20 @@ +#version 450 + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +}