#include "vulkan-game.hpp" #include #include #include #include "consts.hpp" #include "logger.hpp" #include "utils.hpp" using namespace std; VulkanGame::VulkanGame(int maxFramesInFlight) : MAX_FRAMES_IN_FLIGHT(maxFramesInFlight) { gui = nullptr; window = nullptr; font = nullptr; fontSDLTexture = nullptr; imageSDLTexture = nullptr; currentFrame = 0; framebufferResized = false; object_VP_mats = {}; ship_VP_mats = {}; asteroid_VP_mats = {}; } VulkanGame::~VulkanGame() { } void VulkanGame::run(int width, int height, unsigned char guiFlags) { seedRandomNums(); 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 init 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); cout << "SDL "<< to_string(sdlVersion.major) << "." << to_string(sdlVersion.minor) << "." << to_string(sdlVersion.patch) << endl; font = TTF_OpenFont("assets/fonts/lazy.ttf", 28); if (font == nullptr) { cout << "Failed to load lazy font! SDL_ttf Error: " << TTF_GetError() << endl; return RTWO_ERROR; } SDL_Surface* fontSDLSurface = TTF_RenderText_Solid(font, "Great success!", { 255, 255, 255 }); if (fontSDLSurface == nullptr) { cout << "Unable to render text surface! SDL_ttf Error: " << TTF_GetError() << endl; return RTWO_ERROR; } fontSDLTexture = SDL_CreateTextureFromSurface(renderer, fontSDLSurface); if (fontSDLTexture == nullptr) { cout << "Unable to create texture from rendered text! SDL Error: " << SDL_GetError() << endl; SDL_FreeSurface(fontSDLSurface); return RTWO_ERROR; } SDL_FreeSurface(fontSDLSurface); // TODO: Load a PNG instead SDL_Surface* imageSDLSurface = SDL_LoadBMP("assets/images/spaceship.bmp"); if (imageSDLSurface == nullptr) { cout << "Unable to load image " << "spaceship.bmp" << "! SDL Error: " << SDL_GetError() << endl; return RTWO_ERROR; } imageSDLTexture = SDL_CreateTextureFromSurface(renderer, imageSDLSurface); if (imageSDLTexture == nullptr) { cout << "Unable to create texture from BMP surface! SDL Error: " << SDL_GetError() << endl; SDL_FreeSurface(imageSDLSurface); return RTWO_ERROR; } SDL_FreeSurface(imageSDLSurface); // 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; return RTWO_ERROR; } if (SDL_SetTextureBlendMode(uiOverlay, SDL_BLENDMODE_BLEND) != 0) { cout << "Unable to set texture blend mode! SDL Error: " << SDL_GetError() << endl; return RTWO_ERROR; } SDL_SetRenderTarget(renderer, uiOverlay); 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(); initMatrices(); // TODO: Figure out how much of ubo creation and associated variables should be in the pipeline class // Maybe combine the ubo-related objects into a new class initGraphicsPipelines(); overlayPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&OverlayVertex::pos)); overlayPipeline.addAttribute(VK_FORMAT_R32G32_SFLOAT, offset_of(&OverlayVertex::texCoord)); overlayPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, &sdlOverlayImageDescriptor); addObject(overlayObjects, overlayPipeline, { {{-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}} }, { 0, 1, 2, 2, 3, 0 }, {}, false); overlayPipeline.createDescriptorSetLayout(); overlayPipeline.createPipeline("shaders/overlay-vert.spv", "shaders/overlay-frag.spv"); overlayPipeline.createDescriptorPool(swapChainImages); overlayPipeline.createDescriptorSets(swapChainImages); modelPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::pos)); modelPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::color)); modelPipeline.addAttribute(VK_FORMAT_R32G32_SFLOAT, offset_of(&ModelVertex::texCoord)); modelPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&ModelVertex::objIndex)); createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, uniformBuffers_modelPipeline, uniformBuffersMemory_modelPipeline, uniformBufferInfoList_modelPipeline); modelPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, &uniformBufferInfoList_modelPipeline); modelPipeline.addStorageDescriptor(); modelPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, &floorTextureImageDescriptor); addObject(modelObjects, modelPipeline, addObjectIndex(modelObjects.size(), { {{-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}} }), { 0, 1, 2, 2, 3, 0 }, { mat4(1.0f) }, false); modelObjects.back().model_base = translate(mat4(1.0f), vec3(0.0f, 0.0f, -2.0f)); updateObject(modelObjects, modelPipeline, modelObjects.size() - 1); addObject(modelObjects, modelPipeline, addObjectIndex(modelObjects.size(), { {{-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}} }), { 0, 1, 2, 2, 3, 0 }, { mat4(1.0f) }, false); modelObjects.back().model_base = translate(mat4(1.0f), vec3(0.0f, 0.0f, -1.5f)); updateObject(modelObjects, modelPipeline, modelObjects.size() - 1); modelPipeline.createDescriptorSetLayout(); modelPipeline.createPipeline("shaders/scene-vert.spv", "shaders/scene-frag.spv"); modelPipeline.createDescriptorPool(swapChainImages); modelPipeline.createDescriptorSets(swapChainImages); shipPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ShipVertex::pos)); shipPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ShipVertex::color)); shipPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ShipVertex::normal)); shipPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&ShipVertex::objIndex)); createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, uniformBuffers_shipPipeline, uniformBuffersMemory_shipPipeline, uniformBufferInfoList_shipPipeline); shipPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, &uniformBufferInfoList_shipPipeline); shipPipeline.addStorageDescriptor(); // TODO: With the normals, indexing basically becomes pointless since no vertices will have exactly // the same data. Add an option to make some pipelines not use indexing addObject(shipObjects, shipPipeline, addObjectIndex(shipObjects.size(), addVertexNormals({ //back {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, // left back {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, // right back {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}}, // left mid {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}}, {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}}, {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}}, // right mid {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}}, // left front {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 1.0f}}, {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}}, {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}}, // right front {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}}, {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}}, {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 1.0f}}, // top back {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}}, {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 1.0f}}, {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 1.0f}}, {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}}, {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 1.0f}}, {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}}, // bottom back {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}}, {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}}, {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}}, // top mid {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}}, {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}}, {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}}, {{ -0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}}, {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}}, {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}}, // bottom mid {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}}, {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}}, {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}}, {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}}, {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}}, {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}}, // top front {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 0.3f}}, // bottom front {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}}, {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 0.3f}}, // left wing start back {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, // left wing start top {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, // left wing start front {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, // left wing start bottom {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ -0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, // left wing end outside {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}}, {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, // left wing end top {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}}, {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, // left wing end front {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}}, {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, // left wing end bottom {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}}, {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, // right wing start back {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, // right wing start top {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, // right wing start front {{ 0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, // right wing start bottom {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, // right wing end outside {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}}, {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, // right wing end top {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}}, {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}}, // right wing end front {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}}, {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}}, // right wing end bottom {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}}, {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}}, {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}}, })), { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, }, { mat4(1.0f) }, false); shipPipeline.createDescriptorSetLayout(); shipPipeline.createPipeline("shaders/ship-vert.spv", "shaders/ship-frag.spv"); shipPipeline.createDescriptorPool(swapChainImages); shipPipeline.createDescriptorSets(swapChainImages); asteroidPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&AsteroidVertex::pos)); asteroidPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&AsteroidVertex::color)); asteroidPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&AsteroidVertex::normal)); asteroidPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&AsteroidVertex::objIndex)); createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, uniformBuffers_asteroidPipeline, uniformBuffersMemory_asteroidPipeline, uniformBufferInfoList_asteroidPipeline); asteroidPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, &uniformBufferInfoList_asteroidPipeline); asteroidPipeline.addStorageDescriptor(); asteroidPipeline.createDescriptorSetLayout(); asteroidPipeline.createPipeline("shaders/asteroid-vert.spv", "shaders/asteroid-frag.spv"); asteroidPipeline.createDescriptorPool(swapChainImages); asteroidPipeline.createDescriptorSets(swapChainImages); cout << "Created all the graphics pipelines" << endl; createCommandBuffers(); createSyncObjects(); shipObjects[0].model_base = translate(mat4(1.0f), vec3(0.0f, -1.2f, 1.65f)) * scale(mat4(1.0f), vec3(0.1f, 0.1f, 0.1f)); updateObject(shipObjects, shipPipeline, 0); } void VulkanGame::initGraphicsPipelines() { overlayPipeline = GraphicsPipeline_Vulkan(physicalDevice, device, renderPass, { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 4, 6, 0); modelPipeline = GraphicsPipeline_Vulkan(physicalDevice, device, renderPass, { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 16, 24, 10); shipPipeline = GraphicsPipeline_Vulkan(physicalDevice, device, renderPass, { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 138, 138, 10); asteroidPipeline = GraphicsPipeline_Vulkan(physicalDevice, device, renderPass, { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 24, 36, 10); } // TODO: Maybe changes the name to initScene() or something similar void VulkanGame::initMatrices() { cam_pos = vec3(0.0f, 0.0f, 2.0f); float cam_yaw = 0.0f; float cam_pitch = -50.0f; mat4 yaw_mat = rotate(mat4(1.0f), radians(-cam_yaw), vec3(0.0f, 1.0f, 0.0f)); mat4 pitch_mat = rotate(mat4(1.0f), radians(-cam_pitch), vec3(1.0f, 0.0f, 0.0f)); mat4 R_view = pitch_mat * yaw_mat; mat4 T_view = translate(mat4(1.0f), vec3(-cam_pos.x, -cam_pos.y, -cam_pos.z)); viewMat = R_view * T_view; projMat = perspective(radians(FOV_ANGLE), (float)swapChainExtent.width / (float)swapChainExtent.height, NEAR_CLIP, FAR_CLIP); projMat[1][1] *= -1; // flip the y-axis so that +y is up object_VP_mats.view = viewMat; object_VP_mats.proj = projMat; ship_VP_mats.view = viewMat; ship_VP_mats.proj = projMat; asteroid_VP_mats.view = viewMat; asteroid_VP_mats.proj = projMat; } void VulkanGame::mainLoop() { UIEvent e; bool quit = false; this->startTime = high_resolution_clock::now(); this->curTime = duration(high_resolution_clock::now() - this->startTime).count(); lastSpawn_asteroid = this->curTime; while (!quit) { this->prevTime = this->curTime; this->curTime = duration(high_resolution_clock::now() - this->startTime).count(); this->elapsedTime = this->curTime - this->prevTime; 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_KEYDOWN: if (e.key.keycode == SDL_SCANCODE_ESCAPE) { quit = true; } else if (e.key.keycode == SDL_SCANCODE_SPACE) { cout << "Adding a plane" << endl; float zOffset = -2.0f + (0.5f * modelObjects.size()); addObject(modelObjects, modelPipeline, addObjectIndex(modelObjects.size(), { {{-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}} }), { 0, 1, 2, 2, 3, 0 }, { mat4(1.0f) }, true); modelObjects.back().model_base = translate(mat4(1.0f), vec3(0.0f, 0.0f, zOffset)); updateObject(modelObjects, modelPipeline, modelObjects.size() - 1); } else { cout << "Key event detected" << endl; } break; case UI_EVENT_KEYUP: 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; } } // Check which keys are held down if (gui->keyPressed(SDL_SCANCODE_LEFT)) { shipObjects[0].model_transform = translate(mat4(1.0f), vec3(-this->shipSpeed * this->elapsedTime, 0.0f, 0.0f)) * shipObjects[0].model_transform; updateObject(shipObjects, shipPipeline, 0); } else if (gui->keyPressed(SDL_SCANCODE_RIGHT)) { shipObjects[0].model_transform = translate(mat4(1.0f), vec3(this->shipSpeed * this->elapsedTime, 0.0f, 0.0f)) * shipObjects[0].model_transform; updateObject(shipObjects, shipPipeline, 0); } renderUI(); renderScene(); } vkDeviceWaitIdle(device); } // TODO: The only updates that need to happen once per Vulkan image are the SSBO ones, // which are already handled by updateObject(). Move this code to a different place, // where it will run just once per frame void VulkanGame::updateScene(uint32_t currentImage) { for (int i = 0; i < modelObjects.size(); i++) { modelObjects[i].model_transform = translate(mat4(1.0f), vec3(0.0f, -2.0f, -0.0f)) * rotate(mat4(1.0f), this->curTime * radians(90.0f), vec3(0.0f, 0.0f, 1.0f)); updateObject(modelObjects, modelPipeline, i); } for (int i = 0; i < this->asteroidObjects.size(); i++) { if (!this->asteroidObjects[i].ssbo.deleted) { vec3 objCenter = vec3(viewMat * vec4(this->asteroidObjects[i].center, 1.0f)); if ((objCenter.z - this->asteroidObjects[i].radius) > -NEAR_CLIP) { this->asteroidObjects[i].ssbo.deleted = true; } else { this->asteroidObjects[i].model_transform = translate(mat4(1.0f), vec3(0.0f, 0.0f, this->asteroidSpeed * this->elapsedTime)) * this->asteroidObjects[i].model_transform; } updateObject(asteroidObjects, asteroidPipeline, i); } } if (this->curTime - this->lastSpawn_asteroid > this->spawnRate_asteroid) { this->lastSpawn_asteroid = this->curTime; addObject(asteroidObjects, asteroidPipeline, addObjectIndex(asteroidObjects.size(), addVertexNormals({ // front {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, // top {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, // bottom {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, {{ 1.0f, -1.0f, -1.0}, {0.4f, 0.4f, 0.4f}}, // back {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, {{ 1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, // right {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, {{ 1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, // left {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}}, {{-1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}}, })), { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, }, { mat4(1.0f), 10.0f, 0 }, true); // This accounts for the scaling in model_base. // Dividing by 8 instead of 10 since the bounding radius algorithm // under-calculates the true value. // TODO: Figure out the best way to take scaling into account when calculating the radius // Keep in mind that the main complicating factor is the currently poor radius calculation asteroidObjects.back().radius /= 8.0f; asteroidObjects.back().model_base = translate(mat4(1.0f), vec3(getRandomNum(-1.3f, 1.3f), -1.2f, getRandomNum(-5.5f, -4.5f))) * rotate(mat4(1.0f), radians(60.0f), vec3(1.0f, 1.0f, -1.0f)) * scale(mat4(1.0f), vec3(0.1f, 0.1f, 0.1f)); updateObject(asteroidObjects, asteroidPipeline, asteroidObjects.size() - 1); } VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_modelPipeline[currentImage], 0, object_VP_mats); VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_shipPipeline[currentImage], 0, ship_VP_mats); VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_asteroidPipeline[currentImage], 0, asteroid_VP_mats); } void VulkanGame::renderUI() { 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); rect = {10, 10, 0, 0}; SDL_QueryTexture(fontSDLTexture, nullptr, nullptr, &(rect.w), &(rect.h)); SDL_RenderCopy(renderer, fontSDLTexture, nullptr, &rect); rect = {10, 80, 0, 0}; SDL_QueryTexture(imageSDLTexture, nullptr, nullptr, &(rect.w), &(rect.h)); SDL_RenderCopy(renderer, imageSDLTexture, nullptr, &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!"); } // TODO: Figure out a more elegant way to only do updates and render the UI once per scene render // Probably move some of the renderScene() code into a higher function that updates the UI, and renders // the UI and scene updateScene(imageIndex); 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); modelPipeline.cleanupBuffers(); overlayPipeline.cleanupBuffers(); shipPipeline.cleanupBuffers(); asteroidPipeline.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; } if (fontSDLTexture != nullptr) { SDL_DestroyTexture(fontSDLTexture); fontSDLTexture = nullptr; } if (imageSDLTexture != nullptr) { SDL_DestroyTexture(imageSDLTexture); imageSDLTexture = nullptr; } TTF_CloseFont(font); font = 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::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); modelPipeline.createRenderCommands(commandBuffers[i], i); shipPipeline.createRenderCommands(commandBuffers[i], i); asteroidPipeline.createRenderCommands(commandBuffers[i], i); // Always render this pipeline last overlayPipeline.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::createBufferSet(VkDeviceSize bufferSize, VkBufferUsageFlags flags, vector& buffers, vector& buffersMemory, vector& bufferInfoList) { buffers.resize(swapChainImages.size()); buffersMemory.resize(swapChainImages.size()); bufferInfoList.resize(swapChainImages.size()); for (size_t i = 0; i < swapChainImages.size(); i++) { VulkanUtils::createBuffer(device, physicalDevice, bufferSize, flags, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, buffers[i], buffersMemory[i]); bufferInfoList[i].buffer = buffers[i]; bufferInfoList[i].offset = 0; // This is the offset from the start of the buffer, so always 0 for now bufferInfoList[i].range = bufferSize; // Size of the update starting from offset, or VK_WHOLE_SIZE } } // TODO: Fix the crash that happens when alt-tabbing 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(); createSwapChain(); createImageViews(); createRenderPass(); VulkanUtils::createDepthImage(device, physicalDevice, commandPool, findDepthFormat(), swapChainExtent, depthImage, graphicsQueue); createFramebuffers(); // TODO: Move UBO creation/management into GraphicsPipeline_Vulkan, like I did with SSBOs createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, uniformBuffers_modelPipeline, uniformBuffersMemory_modelPipeline, uniformBufferInfoList_modelPipeline); modelPipeline.updateRenderPass(renderPass); modelPipeline.createPipeline("shaders/scene-vert.spv", "shaders/scene-frag.spv"); modelPipeline.createDescriptorPool(swapChainImages); modelPipeline.createDescriptorSets(swapChainImages); overlayPipeline.updateRenderPass(renderPass); overlayPipeline.createPipeline("shaders/overlay-vert.spv", "shaders/overlay-frag.spv"); overlayPipeline.createDescriptorPool(swapChainImages); overlayPipeline.createDescriptorSets(swapChainImages); createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, uniformBuffers_shipPipeline, uniformBuffersMemory_shipPipeline, uniformBufferInfoList_shipPipeline); shipPipeline.updateRenderPass(renderPass); shipPipeline.createPipeline("shaders/ship-vert.spv", "shaders/ship-frag.spv"); shipPipeline.createDescriptorPool(swapChainImages); shipPipeline.createDescriptorSets(swapChainImages); createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, uniformBuffers_asteroidPipeline, uniformBuffersMemory_asteroidPipeline, uniformBufferInfoList_asteroidPipeline); asteroidPipeline.updateRenderPass(renderPass); asteroidPipeline.createPipeline("shaders/asteroid-vert.spv", "shaders/asteroid-frag.spv"); asteroidPipeline.createDescriptorPool(swapChainImages); asteroidPipeline.createDescriptorSets(swapChainImages); createCommandBuffers(); } void VulkanGame::cleanupSwapChain() { VulkanUtils::destroyVulkanImage(device, depthImage); for (VkFramebuffer framebuffer : swapChainFramebuffers) { vkDestroyFramebuffer(device, framebuffer, nullptr); } vkFreeCommandBuffers(device, commandPool, static_cast(commandBuffers.size()), commandBuffers.data()); overlayPipeline.cleanup(); modelPipeline.cleanup(); shipPipeline.cleanup(); asteroidPipeline.cleanup(); for (size_t i = 0; i < uniformBuffers_modelPipeline.size(); i++) { vkDestroyBuffer(device, uniformBuffers_modelPipeline[i], nullptr); vkFreeMemory(device, uniformBuffersMemory_modelPipeline[i], nullptr); } for (size_t i = 0; i < uniformBuffers_shipPipeline.size(); i++) { vkDestroyBuffer(device, uniformBuffers_shipPipeline[i], nullptr); vkFreeMemory(device, uniformBuffersMemory_shipPipeline[i], nullptr); } for (size_t i = 0; i < uniformBuffers_asteroidPipeline.size(); i++) { vkDestroyBuffer(device, uniformBuffers_asteroidPipeline[i], nullptr); vkFreeMemory(device, uniformBuffersMemory_asteroidPipeline[i], nullptr); } vkDestroyRenderPass(device, renderPass, nullptr); for (VkImageView imageView : swapChainImageViews) { vkDestroyImageView(device, imageView, nullptr); } vkDestroySwapchainKHR(device, swapChain, nullptr); }