#include "vulkan-game.hpp" #include #include #include #include "consts.hpp" #include "logger.hpp" #include "utils.hpp" using namespace std; struct UniformBufferObject { alignas(16) mat4 model; alignas(16) mat4 view; alignas(16) mat4 proj; }; VulkanGame::VulkanGame(int maxFramesInFlight) : MAX_FRAMES_IN_FLIGHT(maxFramesInFlight) { gui = nullptr; window = nullptr; currentFrame = 0; framebufferResized = false; } VulkanGame::~VulkanGame() { } void VulkanGame::run(int width, int height, unsigned char guiFlags) { cout << "DEBUGGING IS " << (ENABLE_VALIDATION_LAYERS ? "ON" : "OFF") << endl; cout << "Vulkan Game" << endl; // This gets the runtime version, use SDL_VERSION() for the comppile-time version // TODO: Create a game-gui function to get the gui version and retrieve it that way SDL_GetVersion(&sdlVersion); // TODO: Refactor the logger api to be more flexible, // esp. since gl_log() and gl_log_err() have issues printing anything besides stirngs restart_gl_log(); gl_log("starting SDL\n%s.%s.%s", to_string(sdlVersion.major).c_str(), to_string(sdlVersion.minor).c_str(), to_string(sdlVersion.patch).c_str()); open_log(); get_log() << "starting SDL" << endl; get_log() << (int)sdlVersion.major << "." << (int)sdlVersion.minor << "." << (int)sdlVersion.patch << endl; if (initWindow(width, height, guiFlags) == RTWO_ERROR) { return; } initVulkan(); mainLoop(); cleanup(); close_log(); } // TODO: Make some more initi functions, or call this initUI if the // amount of things initialized here keeps growing bool VulkanGame::initWindow(int width, int height, unsigned char guiFlags) { // TODO: Put all fonts, textures, and images in the assets folder gui = new GameGui_SDL(); if (gui->init() == RTWO_ERROR) { // TODO: Also print these sorts of errors to the log cout << "UI library could not be initialized!" << endl; cout << gui->getError() << endl; return RTWO_ERROR; } window = (SDL_Window*) gui->createWindow("Vulkan Game", width, height, guiFlags & GUI_FLAGS_WINDOW_FULLSCREEN); if (window == nullptr) { cout << "Window could not be created!" << endl; cout << gui->getError() << endl; return RTWO_ERROR; } cout << "Target window size: (" << width << ", " << height << ")" << endl; cout << "Actual window size: (" << gui->getWindowWidth() << ", " << gui->getWindowHeight() << ")" << endl; renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (renderer == nullptr) { cout << "Renderer could not be created!" << endl; cout << gui->getError() << endl; return RTWO_ERROR; } SDL_VERSION(&sdlVersion); // In SDL 2.0.10 (currently, the latest), SDL_TEXTUREACCESS_TARGET is required to get a transparent overlay working // However, the latest SDL version available through homebrew on Mac is 2.0.9, which requires SDL_TEXTUREACCESS_STREAMING // I tried building sdl 2.0.10 (and sdl_image and sdl_ttf) from source on Mac, but had some issues, so this is easier // until the homebrew recipe is updated if (sdlVersion.major == 2 && sdlVersion.minor == 0 && sdlVersion.patch == 9) { uiOverlay = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, gui->getWindowWidth(), gui->getWindowHeight()); } else { uiOverlay = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, gui->getWindowWidth(), gui->getWindowHeight()); } if (uiOverlay == nullptr) { cout << "Unable to create blank texture! SDL Error: " << SDL_GetError() << endl; } if (SDL_SetTextureBlendMode(uiOverlay, SDL_BLENDMODE_BLEND) != 0) { cout << "Unable to set texture blend mode! SDL Error: " << SDL_GetError() << endl; } return RTWO_SUCCESS; } void VulkanGame::initVulkan() { const vector validationLayers = { "VK_LAYER_KHRONOS_validation" }; const vector deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; createVulkanInstance(validationLayers); setupDebugMessenger(); createVulkanSurface(); pickPhysicalDevice(deviceExtensions); createLogicalDevice(validationLayers, deviceExtensions); createSwapChain(); createImageViews(); createRenderPass(); createCommandPool(); createImageResources(); createFramebuffers(); createUniformBuffers(); vector sceneVertices = { {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}}, {{ 0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}}, {{ 0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}}, {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}, {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}}, {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}}, {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}}, {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}} }; vector sceneIndices = { 0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4 }; graphicsPipelines.push_back(GraphicsPipeline_Vulkan(physicalDevice, device, renderPass, { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, sizeof(Vertex))); graphicsPipelines.back().bindData(sceneVertices, sceneIndices, commandPool, graphicsQueue); graphicsPipelines.back().addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&Vertex::pos)); graphicsPipelines.back().addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&Vertex::color)); graphicsPipelines.back().addAttribute(VK_FORMAT_R32G32_SFLOAT, offset_of(&Vertex::texCoord)); graphicsPipelines.back().addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, &uniformBufferInfoList); graphicsPipelines.back().addDescriptorInfo(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, &floorTextureImageDescriptor); graphicsPipelines.back().createDescriptorSetLayout(); graphicsPipelines.back().createPipeline("shaders/scene-vert.spv", "shaders/scene-frag.spv"); graphicsPipelines.back().createDescriptorPool(swapChainImages); graphicsPipelines.back().createDescriptorSets(swapChainImages); vector overlayVertices = { {{-1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, {{ 1.0f, 1.0f, 0.0f}, {1.0f, 1.0f}}, {{ 1.0f, -1.0f, 0.0f}, {1.0f, 0.0f}}, {{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f}} }; vector overlayIndices = { 0, 1, 2, 2, 3, 0 }; graphicsPipelines.push_back(GraphicsPipeline_Vulkan(physicalDevice, device, renderPass, { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, sizeof(OverlayVertex))); graphicsPipelines.back().bindData(overlayVertices, overlayIndices, commandPool, graphicsQueue); graphicsPipelines.back().addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&OverlayVertex::pos)); graphicsPipelines.back().addAttribute(VK_FORMAT_R32G32_SFLOAT, offset_of(&OverlayVertex::texCoord)); graphicsPipelines.back().addDescriptorInfo(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, &sdlOverlayImageDescriptor); graphicsPipelines.back().createDescriptorSetLayout(); graphicsPipelines.back().createPipeline("shaders/overlay-vert.spv", "shaders/overlay-frag.spv"); graphicsPipelines.back().createDescriptorPool(swapChainImages); graphicsPipelines.back().createDescriptorSets(swapChainImages); // TODO: Creating the descriptor pool and descriptor sets might need to be redone when the // swap chain is recreated cout << "Created " << graphicsPipelines.size() << " graphics pipelines" << endl; createCommandBuffers(); createSyncObjects(); } void VulkanGame::mainLoop() { UIEvent e; bool quit = false; while (!quit) { gui->processEvents(); while (gui->pollEvent(&e)) { switch(e.type) { case UI_EVENT_QUIT: cout << "Quit event detected" << endl; quit = true; break; case UI_EVENT_WINDOW: cout << "Window event detected" << endl; // Currently unused break; case UI_EVENT_WINDOWRESIZE: cout << "Window resize event detected" << endl; framebufferResized = true; break; case UI_EVENT_KEY: if (e.key.keycode == SDL_SCANCODE_ESCAPE) { quit = true; } else { cout << "Key event detected" << endl; } break; case UI_EVENT_MOUSEBUTTONDOWN: cout << "Mouse button down event detected" << endl; break; case UI_EVENT_MOUSEBUTTONUP: cout << "Mouse button up event detected" << endl; break; case UI_EVENT_MOUSEMOTION: break; case UI_EVENT_UNKNOWN: cout << "Unknown event type: 0x" << hex << e.unknown.eventType << dec << endl; break; default: cout << "Unhandled UI event: " << e.type << endl; } } renderUI(); renderScene(); } vkDeviceWaitIdle(device); } void VulkanGame::renderUI() { // TODO: Since I currently don't use any other render targets, // I may as well set this once before the render loop SDL_SetRenderTarget(renderer, uiOverlay); SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00); SDL_RenderClear(renderer); SDL_Rect rect = {280, 220, 100, 100}; SDL_SetRenderDrawColor(renderer, 0x00, 0xFF, 0x00, 0xFF); SDL_RenderFillRect(renderer, &rect); SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0xFF, 0xFF); SDL_RenderDrawLine(renderer, 50, 5, 150, 500); VulkanUtils::populateVulkanImageFromSDLTexture(device, physicalDevice, commandPool, uiOverlay, renderer, sdlOverlayImage, graphicsQueue); } void VulkanGame::renderScene() { vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, numeric_limits::max()); uint32_t imageIndex; VkResult result = vkAcquireNextImageKHR(device, swapChain, numeric_limits::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 runtime_error("failed to acquire swap chain image!"); } VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; VkSemaphore waitSemaphores[] = { imageAvailableSemaphores[currentFrame] }; VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; VkSemaphore signalSemaphores[] = { renderFinishedSemaphores[currentFrame] }; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; vkResetFences(device, 1, &inFlightFences[currentFrame]); if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { throw runtime_error("failed to submit draw command buffer!"); } VkPresentInfoKHR presentInfo = {}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; presentInfo.pWaitSemaphores = signalSemaphores; VkSwapchainKHR swapChains[] = { swapChain }; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = swapChains; presentInfo.pImageIndices = &imageIndex; presentInfo.pResults = nullptr; result = vkQueuePresentKHR(presentQueue, &presentInfo); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { framebufferResized = false; recreateSwapChain(); } else if (result != VK_SUCCESS) { throw runtime_error("failed to present swap chain image!"); } currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } void VulkanGame::cleanup() { cleanupSwapChain(); VulkanUtils::destroyVulkanImage(device, floorTextureImage); VulkanUtils::destroyVulkanImage(device, sdlOverlayImage); vkDestroySampler(device, textureSampler, nullptr); for (GraphicsPipeline_Vulkan pipeline : graphicsPipelines) { pipeline.cleanupBuffers(); } for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); vkDestroyFence(device, inFlightFences[i], nullptr); } vkDestroyCommandPool(device, commandPool, nullptr); vkDestroyDevice(device, nullptr); vkDestroySurfaceKHR(instance, surface, nullptr); if (ENABLE_VALIDATION_LAYERS) { VulkanUtils::destroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); } vkDestroyInstance(instance, nullptr); // TODO: Check if any of these functions accept null parameters // If they do, I don't need to check for that if (uiOverlay != nullptr) { SDL_DestroyTexture(uiOverlay); uiOverlay = nullptr; } SDL_DestroyRenderer(renderer); renderer = nullptr; gui->destroyWindow(); gui->shutdown(); delete gui; } void VulkanGame::createVulkanInstance(const vector &validationLayers) { if (ENABLE_VALIDATION_LAYERS && !VulkanUtils::checkValidationLayerSupport(validationLayers)) { throw runtime_error("validation layers requested, but not available!"); } VkApplicationInfo appInfo = {}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Vulkan Game"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.pEngineName = "No Engine"; appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; VkInstanceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; vector extensions = gui->getRequiredExtensions(); if (ENABLE_VALIDATION_LAYERS) { extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); cout << endl << "Extensions:" << endl; for (const char* extensionName : extensions) { cout << extensionName << endl; } cout << endl; VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo; if (ENABLE_VALIDATION_LAYERS) { createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); populateDebugMessengerCreateInfo(debugCreateInfo); createInfo.pNext = &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; createInfo.pNext = nullptr; } if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw runtime_error("failed to create instance!"); } } void VulkanGame::setupDebugMessenger() { if (!ENABLE_VALIDATION_LAYERS) return; VkDebugUtilsMessengerCreateInfoEXT createInfo; populateDebugMessengerCreateInfo(createInfo); if (VulkanUtils::createDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { throw runtime_error("failed to set up debug messenger!"); } } void VulkanGame::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { 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; } VKAPI_ATTR VkBool32 VKAPI_CALL VulkanGame::debugCallback( VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { cerr << "validation layer: " << pCallbackData->pMessage << endl; return VK_FALSE; } void VulkanGame::createVulkanSurface() { if (gui->createVulkanSurface(instance, &surface) == RTWO_ERROR) { throw runtime_error("failed to create window surface!"); } } void VulkanGame::pickPhysicalDevice(const vector& deviceExtensions) { uint32_t deviceCount = 0; vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); if (deviceCount == 0) { throw runtime_error("failed to find GPUs with Vulkan support!"); } vector devices(deviceCount); vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); cout << endl << "Graphics cards:" << endl; for (const VkPhysicalDevice& device : devices) { if (isDeviceSuitable(device, deviceExtensions)) { physicalDevice = device; break; } } cout << endl; if (physicalDevice == VK_NULL_HANDLE) { throw runtime_error("failed to find a suitable GPU!"); } } bool VulkanGame::isDeviceSuitable(VkPhysicalDevice physicalDevice, const vector& deviceExtensions) { VkPhysicalDeviceProperties deviceProperties; vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties); cout << "Device: " << deviceProperties.deviceName << endl; QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface); bool extensionsSupported = VulkanUtils::checkDeviceExtensionSupport(physicalDevice, deviceExtensions); bool swapChainAdequate = false; if (extensionsSupported) { SwapChainSupportDetails swapChainSupport = VulkanUtils::querySwapChainSupport(physicalDevice, surface); swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); } VkPhysicalDeviceFeatures supportedFeatures; vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures); return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; } void VulkanGame::createLogicalDevice( const vector validationLayers, const vector& deviceExtensions) { QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface); vector queueCreateInfoList; 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; queueCreateInfoList.push_back(queueCreateInfo); } VkPhysicalDeviceFeatures deviceFeatures = {}; deviceFeatures.samplerAnisotropy = VK_TRUE; VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createInfo.queueCreateInfoCount = static_cast(queueCreateInfoList.size()); createInfo.pQueueCreateInfos = queueCreateInfoList.data(); createInfo.pEnabledFeatures = &deviceFeatures; createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); // These fields are ignored by up-to-date Vulkan implementations, // but it's a good idea to set them for backwards compatibility if (ENABLE_VALIDATION_LAYERS) { createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw runtime_error("failed to create logical device!"); } vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void VulkanGame::createSwapChain() { SwapChainSupportDetails swapChainSupport = VulkanUtils::querySwapChainSupport(physicalDevice, surface); VkSurfaceFormatKHR surfaceFormat = VulkanUtils::chooseSwapSurfaceFormat(swapChainSupport.formats); VkPresentModeKHR presentMode = VulkanUtils::chooseSwapPresentMode(swapChainSupport.presentModes); VkExtent2D extent = VulkanUtils::chooseSwapExtent(swapChainSupport.capabilities, gui->getWindowWidth(), gui->getWindowHeight()); 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; 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; QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface); 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.queueFamilyIndexCount = 0; createInfo.pQueueFamilyIndices = nullptr; } 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(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw runtime_error("failed to create swap chain!"); } vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); swapChainImageFormat = surfaceFormat.format; swapChainExtent = extent; } void VulkanGame::createImageViews() { swapChainImageViews.resize(swapChainImages.size()); for (size_t i = 0; i < swapChainImages.size(); i++) { swapChainImageViews[i] = VulkanUtils::createImageView(device, swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); } } void VulkanGame::createRenderPass() { VkAttachmentDescription colorAttachment = {}; colorAttachment.format = swapChainImageFormat; 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; VkAttachmentDescription depthAttachment = {}; depthAttachment.format = findDepthFormat(); depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkAttachmentReference depthAttachmentRef = {}; depthAttachmentRef.attachment = 1; depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; subpass.pDepthStencilAttachment = &depthAttachmentRef; 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_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; array attachments = { colorAttachment, depthAttachment }; VkRenderPassCreateInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = static_cast(attachments.size()); renderPassInfo.pAttachments = attachments.data(); renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw runtime_error("failed to create render pass!"); } } VkFormat VulkanGame::findDepthFormat() { return VulkanUtils::findSupportedFormat( physicalDevice, { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT ); } void VulkanGame::createCommandPool() { QueueFamilyIndices queueFamilyIndices = VulkanUtils::findQueueFamilies(physicalDevice, surface);; VkCommandPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); poolInfo.flags = 0; if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw runtime_error("failed to create graphics command pool!"); } } void VulkanGame::createImageResources() { VulkanUtils::createDepthImage(device, physicalDevice, commandPool, findDepthFormat(), swapChainExtent, depthImage, graphicsQueue); createTextureSampler(); VulkanUtils::createVulkanImageFromFile(device, physicalDevice, commandPool, "textures/texture.jpg", floorTextureImage, graphicsQueue); floorTextureImageDescriptor = {}; floorTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; floorTextureImageDescriptor.imageView = floorTextureImage.imageView; floorTextureImageDescriptor.sampler = textureSampler; VulkanUtils::createVulkanImageFromSDLTexture(device, physicalDevice, uiOverlay, sdlOverlayImage); sdlOverlayImageDescriptor = {}; sdlOverlayImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; sdlOverlayImageDescriptor.imageView = sdlOverlayImage.imageView; sdlOverlayImageDescriptor.sampler = textureSampler; } void VulkanGame::createTextureSampler() { VkSamplerCreateInfo samplerInfo = {}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR; samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.anisotropyEnable = VK_TRUE; samplerInfo.maxAnisotropy = 16; samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; samplerInfo.unnormalizedCoordinates = VK_FALSE; samplerInfo.compareEnable = VK_FALSE; samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.mipLodBias = 0.0f; samplerInfo.minLod = 0.0f; samplerInfo.maxLod = 0.0f; if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { throw runtime_error("failed to create texture sampler!"); } } void VulkanGame::createFramebuffers() { swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { array attachments = { swapChainImageViews[i], depthImage.imageView }; VkFramebufferCreateInfo framebufferInfo = {}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = static_cast(attachments.size()); framebufferInfo.pAttachments = attachments.data(); framebufferInfo.width = swapChainExtent.width; framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw runtime_error("failed to create framebuffer!"); } } } void VulkanGame::createUniformBuffers() { VkDeviceSize bufferSize = sizeof(UniformBufferObject); uniformBuffers.resize(swapChainImages.size()); uniformBuffersMemory.resize(swapChainImages.size()); uniformBufferInfoList.resize(swapChainImages.size()); for (size_t i = 0; i < swapChainImages.size(); i++) { VulkanUtils::createBuffer(device, physicalDevice, bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]); uniformBufferInfoList[i].buffer = uniformBuffers[i]; uniformBufferInfoList[i].offset = 0; uniformBufferInfoList[i].range = sizeof(UniformBufferObject); } } void VulkanGame::createCommandBuffers() { commandBuffers.resize(swapChainImages.size()); 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(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { throw runtime_error("failed to allocate command buffers!"); } for (size_t i = 0; i < commandBuffers.size(); i++) { VkCommandBufferBeginInfo beginInfo = {}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; beginInfo.pInheritanceInfo = nullptr; if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { throw runtime_error("failed to begin recording command buffer!"); } VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassInfo.renderPass = renderPass; renderPassInfo.framebuffer = swapChainFramebuffers[i]; renderPassInfo.renderArea.offset = { 0, 0 }; renderPassInfo.renderArea.extent = swapChainExtent; array clearValues = {}; clearValues[0].color = {{ 0.0f, 0.0f, 0.0f, 1.0f }}; clearValues[1].depthStencil = { 1.0f, 0 }; renderPassInfo.clearValueCount = static_cast(clearValues.size()); renderPassInfo.pClearValues = clearValues.data(); vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); for (GraphicsPipeline_Vulkan pipeline : graphicsPipelines) { pipeline.createRenderCommands(commandBuffers[i], i); } vkCmdEndRenderPass(commandBuffers[i]); if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { throw runtime_error("failed to record command buffer!"); } } } void VulkanGame::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(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { throw runtime_error("failed to create synchronization objects for a frame!"); } } } void VulkanGame::recreateSwapChain() { cout << "Recreating swap chain" << endl; gui->refreshWindowSize(); while (gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0 || (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) != 0) { SDL_WaitEvent(nullptr); gui->refreshWindowSize(); } vkDeviceWaitIdle(device); //cleanupSwapChain(); } void VulkanGame::cleanupSwapChain() { VulkanUtils::destroyVulkanImage(device, depthImage); for (VkFramebuffer framebuffer : swapChainFramebuffers) { vkDestroyFramebuffer(device, framebuffer, nullptr); } vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); for (GraphicsPipeline_Vulkan pipeline : graphicsPipelines) { pipeline.cleanup(); } vkDestroyRenderPass(device, renderPass, nullptr); for (VkImageView imageView : swapChainImageViews) { vkDestroyImageView(device, imageView, nullptr); } vkDestroySwapchainKHR(device, swapChain, nullptr); for (size_t i = 0; i < uniformBuffers.size(); i++) { vkDestroyBuffer(device, uniformBuffers[i], nullptr); vkFreeMemory(device, uniformBuffersMemory[i], nullptr); } }