#include "vulkan-game.hpp" #include #include #include #include #include "consts.hpp" #include "logger.hpp" #include "utils.hpp" using namespace std; // TODO: Update all occurances of instance variables to use this-> VulkanGame::VulkanGame(int maxFramesInFlight) : MAX_FRAMES_IN_FLIGHT(maxFramesInFlight) { this->gui = nullptr; this->window = nullptr; this->font = nullptr; this->fontSDLTexture = nullptr; this->imageSDLTexture = nullptr; this->currentFrame = 0; this->framebufferResized = false; this->object_VP_mats = {}; this->ship_VP_mats = {}; this->asteroid_VP_mats = {}; this->laser_VP_mats = {}; this->explosion_UBO = {}; } 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; if (initUI(width, height, guiFlags) == RTWO_ERROR) { return; } initVulkan(); mainLoop(); cleanup(); close_log(); } bool VulkanGame::initUI(int width, int height, unsigned char guiFlags) { // TODO: Create a game-gui function to get the gui version and retrieve it that way SDL_VERSION(&sdlVersion); // This gets the compile-time version SDL_GetVersion(&sdlVersion); // This gets the runtime version cout << "SDL "<< to_string(sdlVersion.major) << "." << to_string(sdlVersion.minor) << "." << to_string(sdlVersion.patch) << endl; // 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()); // TODO: Use open_Log() and related functions instead of gl_log ones open_log(); get_log() << "starting SDL" << endl; get_log() << (int)sdlVersion.major << "." << (int)sdlVersion.minor << "." << (int)sdlVersion.patch << endl; // 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; } 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); 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); 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(VK_SHADER_STAGE_VERTEX_BIT); modelPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, &floorTextureImageDescriptor); SceneObject* texturedSquare = nullptr; texturedSquare = &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); texturedSquare->model_base = translate(mat4(1.0f), vec3(0.0f, 0.0f, -2.0f)); texturedSquare->modified = true; texturedSquare = &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); texturedSquare->model_base = translate(mat4(1.0f), vec3(0.0f, 0.0f, -1.5f)); texturedSquare->modified = true; 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(VK_SHADER_STAGE_VERTEX_BIT); // 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 SceneObject& ship = 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); ship.model_base = translate(mat4(1.0f), vec3(0.0f, -1.2f, 1.65f)) * scale(mat4(1.0f), vec3(0.1f, 0.1f, 0.1f)); ship.modified = true; 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(VK_SHADER_STAGE_VERTEX_BIT); asteroidPipeline.createDescriptorSetLayout(); asteroidPipeline.createPipeline("shaders/asteroid-vert.spv", "shaders/asteroid-frag.spv"); asteroidPipeline.createDescriptorPool(swapChainImages); asteroidPipeline.createDescriptorSets(swapChainImages); laserPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&LaserVertex::pos)); laserPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&LaserVertex::texCoord)); laserPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&LaserVertex::objIndex)); createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, uniformBuffers_laserPipeline, uniformBuffersMemory_laserPipeline, uniformBufferInfoList_laserPipeline); laserPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, &uniformBufferInfoList_laserPipeline); laserPipeline.addStorageDescriptor(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); laserPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, &laserTextureImageDescriptor); laserPipeline.createDescriptorSetLayout(); laserPipeline.createPipeline("shaders/laser-vert.spv", "shaders/laser-frag.spv"); laserPipeline.createDescriptorPool(swapChainImages); laserPipeline.createDescriptorSets(swapChainImages); explosionPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ExplosionVertex::particleStartVelocity)); explosionPipeline.addAttribute(VK_FORMAT_R32_SFLOAT, offset_of(&ExplosionVertex::particleStartTime)); explosionPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&ExplosionVertex::objIndex)); createBufferSet(sizeof(UBO_Explosion), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, uniformBuffers_explosionPipeline, uniformBuffersMemory_explosionPipeline, uniformBufferInfoList_explosionPipeline); explosionPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, &uniformBufferInfoList_explosionPipeline); explosionPipeline.addStorageDescriptor(VK_SHADER_STAGE_VERTEX_BIT); explosionPipeline.createDescriptorSetLayout(); explosionPipeline.createPipeline("shaders/explosion-vert.spv", "shaders/explosion-frag.spv"); explosionPipeline.createDescriptorPool(swapChainImages); explosionPipeline.createDescriptorSets(swapChainImages); cout << "Created all the graphics pipelines" << endl; createCommandBuffers(); createSyncObjects(); } void VulkanGame::initGraphicsPipelines() { overlayPipeline = GraphicsPipeline_Vulkan( VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass, { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 4, 6, 0); modelPipeline = GraphicsPipeline_Vulkan( VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass, { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 16, 24, 10); shipPipeline = GraphicsPipeline_Vulkan( VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass, { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 138, 138, 10); asteroidPipeline = GraphicsPipeline_Vulkan( VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass, { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 24, 36, 10); laserPipeline = GraphicsPipeline_Vulkan( VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass, { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 8, 18, 2); explosionPipeline = GraphicsPipeline_Vulkan( VK_PRIMITIVE_TOPOLOGY_POINT_LIST, physicalDevice, device, renderPass, { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, EXPLOSION_PARTICLE_COUNT, EXPLOSION_PARTICLE_COUNT, 2); } // TODO: Maybe changes the name to initScene() or something similar void VulkanGame::initMatrices() { this->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(-this->cam_pos.x, -this->cam_pos.y, -this->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; laser_VP_mats.view = viewMat; laser_VP_mats.proj = projMat; explosion_UBO.view = viewMat; explosion_UBO.proj = projMat; } void VulkanGame::mainLoop() { UIEvent e; bool quit = false; this->startTime = high_resolution_clock::now(); curTime = duration(high_resolution_clock::now() - this->startTime).count(); lastSpawn_asteroid = curTime; while (!quit) { this->prevTime = curTime; curTime = duration(high_resolution_clock::now() - this->startTime).count(); this->elapsedTime = 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.repeat) { break; } 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()); SceneObject& texturedSquare = 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); texturedSquare.model_base = translate(mat4(1.0f), vec3(0.0f, 0.0f, zOffset)); texturedSquare.modified = true; } else if (e.key.keycode == SDL_SCANCODE_Z && leftLaserIdx == -1) { // TODO: When I start actually removing objects from the object vectors, // I will need to update the indices since they might become incorrect // or invalid as objects get moved around vec3 offset(shipObjects[0].model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f)); addLaser( vec3(-0.21f, -1.19f, 1.76f) + offset, vec3(-0.21f, -1.19f, -3.0f) + offset, LASER_COLOR, 0.03f); leftLaserIdx = laserObjects.size() - 1; } else if (e.key.keycode == SDL_SCANCODE_X && rightLaserIdx == -1) { vec3 offset(shipObjects[0].model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f)); addLaser( vec3(0.21f, -1.19f, 1.76f) + offset, vec3(0.21f, -1.19f, -3.0f) + offset, LASER_COLOR, 0.03f); rightLaserIdx = laserObjects.size() - 1; } else { cout << "Key event detected" << endl; } break; case UI_EVENT_KEYUP: if (e.key.keycode == SDL_SCANCODE_Z && leftLaserIdx != -1) { laserObjects[leftLaserIdx].ssbo.deleted = true; laserObjects[leftLaserIdx].modified = true; leftLaserIdx = -1; if (leftLaserEffect != nullptr) { leftLaserEffect->deleted = true; leftLaserEffect = nullptr; } } else if (e.key.keycode == SDL_SCANCODE_X && rightLaserIdx != -1) { laserObjects[rightLaserIdx].ssbo.deleted = true; laserObjects[rightLaserIdx].modified = true; rightLaserIdx = -1; if (rightLaserEffect != nullptr) { rightLaserEffect->deleted = true; rightLaserEffect = nullptr; } } break; case UI_EVENT_MOUSEBUTTONDOWN: case UI_EVENT_MOUSEBUTTONUP: 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 SceneObject& ship = shipObjects[0]; if (gui->keyPressed(SDL_SCANCODE_LEFT)) { float distance = -this->shipSpeed * this->elapsedTime; ship.model_transform = translate(mat4(1.0f), vec3(distance, 0.0f, 0.0f)) * shipObjects[0].model_transform; ship.modified = true; if (leftLaserIdx != -1) { translateLaser(leftLaserIdx, vec3(distance, 0.0f, 0.0f)); } if (rightLaserIdx != -1) { translateLaser(rightLaserIdx, vec3(distance, 0.0f, 0.0f)); } } else if (gui->keyPressed(SDL_SCANCODE_RIGHT)) { float distance = this->shipSpeed * this->elapsedTime; ship.model_transform = translate(mat4(1.0f), vec3(distance, 0.0f, 0.0f)) * shipObjects[0].model_transform; ship.modified = true; if (leftLaserIdx != -1) { translateLaser(leftLaserIdx, vec3(distance, 0.0f, 0.0f)); } if (rightLaserIdx != -1) { translateLaser(rightLaserIdx, vec3(distance, 0.0f, 0.0f)); } } 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 (SceneObject& model : this->modelObjects) { model.model_transform = translate(mat4(1.0f), vec3(0.0f, -2.0f, -0.0f)) * rotate(mat4(1.0f), curTime * radians(90.0f), vec3(0.0f, 0.0f, 1.0f)); model.modified = true; } if (leftLaserIdx != -1) { updateLaserTarget(leftLaserIdx); } if (rightLaserIdx != -1) { updateLaserTarget(rightLaserIdx); } for (vector::iterator it = effects.begin(); it != effects.end(); ) { if ((*it)->deleted) { delete *it; it = effects.erase(it); } else { BaseEffectOverTime* eot = *it; eot->applyEffect(); it++; } } for (SceneObject& asteroid : this->asteroidObjects) { if (!asteroid.ssbo.deleted) { vec3 objCenter = vec3(viewMat * vec4(asteroid.center, 1.0f)); if (asteroid.ssbo.hp <= 0.0f) { asteroid.ssbo.deleted = true; // TODO: Optimize this so I don't recalculate the camera rotation every time // TODO: Also, avoid re-declaring cam_pitch float cam_pitch = -50.0f; mat4 pitch_mat = rotate(mat4(1.0f), radians(cam_pitch), vec3(1.0f, 0.0f, 0.0f)); mat4 model_mat = translate(mat4(1.0f), asteroid.center) * pitch_mat; addExplosion(model_mat, 0.5f, curTime); // TODO: Increment player's score here } else if ((objCenter.z - asteroid.radius) > -NEAR_CLIP) { asteroid.ssbo.deleted = true; } else { asteroid.model_transform = translate(mat4(1.0f), vec3(0.0f, 0.0f, this->asteroidSpeed * this->elapsedTime)) * asteroid.model_transform; } asteroid.modified = true; } } if (curTime - this->lastSpawn_asteroid > this->spawnRate_asteroid) { this->lastSpawn_asteroid = curTime; SceneObject& asteroid = 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, false }, 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 asteroid.radius /= 8.0f; asteroid.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)); asteroid.modified = true; } for (SceneObject& explosion : this->explosionObjects) { if (!explosion.ssbo.deleted) { if (curTime > (explosion.ssbo.explosionStartTime + explosion.ssbo.explosionDuration)) { explosion.ssbo.deleted = true; explosion.modified = true; } } } for (size_t i = 0; i < shipObjects.size(); i++) { if (shipObjects[i].modified) { updateObject(shipObjects, shipPipeline, i); } } for (size_t i = 0; i < modelObjects.size(); i++) { if (modelObjects[i].modified) { updateObject(modelObjects, modelPipeline, i); } } for (size_t i = 0; i < asteroidObjects.size(); i++) { if (asteroidObjects[i].modified) { updateObject(asteroidObjects, asteroidPipeline, i); } } for (size_t i = 0; i < laserObjects.size(); i++) { if (laserObjects[i].modified) { updateObject(laserObjects, laserPipeline, i); } } for (size_t i = 0; i < explosionObjects.size(); i++) { if (explosionObjects[i].modified) { updateObject(explosionObjects, explosionPipeline, i); } } explosion_UBO.cur_time = curTime; 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); VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_laserPipeline[currentImage], 0, laser_VP_mats); VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_explosionPipeline[currentImage], 0, explosion_UBO); } 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; // TODO: Recreate the swap chain here if the user went to a new screen 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, sdlOverlayImage); VulkanUtils::destroyVulkanImage(device, floorTextureImage); VulkanUtils::destroyVulkanImage(device, laserTextureImage); vkDestroySampler(device, textureSampler, nullptr); modelPipeline.cleanupBuffers(); overlayPipeline.cleanupBuffers(); shipPipeline.cleanupBuffers(); asteroidPipeline.cleanupBuffers(); laserPipeline.cleanupBuffers(); explosionPipeline.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 (fontSDLTexture != nullptr) { SDL_DestroyTexture(fontSDLTexture); fontSDLTexture = nullptr; } if (imageSDLTexture != nullptr) { SDL_DestroyTexture(imageSDLTexture); imageSDLTexture = nullptr; } TTF_CloseFont(font); font = nullptr; 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(); // TODO: Move all images/textures somewhere into the assets folder VulkanUtils::createVulkanImageFromSDLTexture(device, physicalDevice, uiOverlay, sdlOverlayImage); sdlOverlayImageDescriptor = {}; sdlOverlayImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; sdlOverlayImageDescriptor.imageView = sdlOverlayImage.imageView; sdlOverlayImageDescriptor.sampler = textureSampler; 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::createVulkanImageFromFile(device, physicalDevice, commandPool, "textures/laser.png", laserTextureImage, graphicsQueue); laserTextureImageDescriptor = {}; laserTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; laserTextureImageDescriptor.imageView = laserTextureImage.imageView; laserTextureImageDescriptor.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); laserPipeline.createRenderCommands(commandBuffers[i], i); explosionPipeline.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::addLaser(vec3 start, vec3 end, vec3 color, float width) { vec3 ray = end - start; float length = glm::length(ray); SceneObject& laser = addObject( laserObjects, laserPipeline, addObjectIndex(laserObjects.size(), { {{ width / 2, 0.0f, -width / 2 }, {1.0f, 0.5f }}, {{-width / 2, 0.0f, -width / 2 }, {0.0f, 0.5f }}, {{-width / 2, 0.0f, 0.0f }, {0.0f, 0.0f }}, {{ width / 2, 0.0f, 0.0f }, {1.0f, 0.0f }}, {{ width / 2, 0.0f, -length + width / 2}, {1.0f, 0.51f}}, {{-width / 2, 0.0f, -length + width / 2}, {0.0f, 0.51f}}, {{ width / 2, 0.0f, -length, }, {1.0f, 1.0f }}, {{-width / 2, 0.0f, -length }, {0.0f, 1.0f }} }), { 0, 1, 2, 0, 2, 3, 4, 5, 1, 4, 1, 0, 6, 7, 5, 6, 5, 4 }, { mat4(1.0f), color, false }, true); float xAxisRotation = asin(ray.y / length); float yAxisRotation = atan2(-ray.x, -ray.z); vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) * rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) * vec4(0.0f, 1.0f, 0.0f, 1.0f)); // To project point P onto line AB: // projection = A + dot(AP,AB) / dot(AB,AB) * AB vec3 projOnLaser = start + glm::dot(this->cam_pos - start, ray) / (length * length) * ray; vec3 laserToCam = this->cam_pos - projOnLaser; float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam)); laser.targetAsteroid = nullptr; laser.model_base = rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f)); laser.model_transform = translate(mat4(1.0f), start) * rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) * rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)); laser.modified = true; } void VulkanGame::translateLaser(size_t index, const vec3& translation) { SceneObject& laser = this->laserObjects[index]; // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created, // and then re-used here vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f)); vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f)); vec3 ray = end - start; float length = glm::length(ray); float xAxisRotation = asin(ray.y / length); float yAxisRotation = atan2(-ray.x, -ray.z); vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) * rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) * vec4(0.0f, 1.0f, 0.0f, 1.0f)); // To project point P onto line AB: // projection = A + dot(AP,AB) / dot(AB,AB) * AB vec3 projOnLaser = start + glm::dot(cam_pos - start, ray) / (length*length) * ray; vec3 laserToCam = cam_pos - projOnLaser; float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam)); laser.model_base = rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f)); laser.model_transform = translate(mat4(1.0f), translation) * laser.model_transform; laser.modified = true; } void VulkanGame::updateLaserTarget(size_t index) { SceneObject& laser = this->laserObjects[index]; // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created, // and then re-used here vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f)); vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f)); vec3 intersection(0.0f), closestIntersection(0.0f); SceneObject* closestAsteroid = nullptr; unsigned int closestAsteroidIndex = -1; for (int i = 0; i < this->asteroidObjects.size(); i++) { if (!this->asteroidObjects[i].ssbo.deleted && this->getLaserAndAsteroidIntersection(this->asteroidObjects[i], start, end, intersection)) { // TODO: Implement a more generic algorithm for testing the closest object by getting the distance between the points // TODO: Also check which intersection is close to the start of the laser. This would make the algorithm work // regardless of which way -Z is pointing if (closestAsteroid == nullptr || intersection.z > closestIntersection.z) { // TODO: At this point, find the real intersection of the laser with one of the asteroid's sides closestAsteroid = &asteroidObjects[i]; closestIntersection = intersection; closestAsteroidIndex = i; } } } float width = laser.vertices[0].pos.x - laser.vertices[1].pos.x; if (laser.targetAsteroid != closestAsteroid) { if (laser.targetAsteroid != nullptr) { if (index == leftLaserIdx && leftLaserEffect != nullptr) { leftLaserEffect->deleted = true; } else if (index == rightLaserIdx && rightLaserEffect != nullptr) { rightLaserEffect->deleted = true; } } EffectOverTime* eot = nullptr; if (closestAsteroid != nullptr) { // TODO: Use some sort of smart pointer instead eot = new EffectOverTime(asteroidPipeline, asteroidObjects, closestAsteroidIndex, offset_of(&SSBO_Asteroid::hp), -20.0f); effects.push_back(eot); } if (index == leftLaserIdx) { leftLaserEffect = eot; } else if (index == rightLaserIdx) { rightLaserEffect = eot; } laser.targetAsteroid = closestAsteroid; } // Make the laser go past the end of the screen if it doesn't hit anything float length = closestAsteroid == nullptr ? 5.24f : glm::length(closestIntersection - start); laser.vertices[4].pos.z = -length + width / 2; laser.vertices[5].pos.z = -length + width / 2; laser.vertices[6].pos.z = -length; laser.vertices[7].pos.z = -length; // TODO: Consider if I want to set a flag and do this update in in updateScene() instead updateObjectVertices(this->laserPipeline, laser, index); } // TODO: Determine if I should pass start and end by reference or value since they don't get changed // Probably use const reference bool VulkanGame::getLaserAndAsteroidIntersection(SceneObject& asteroid, vec3& start, vec3& end, vec3& intersection) { /* ### LINE EQUATIONS ### x = x1 + u * (x2 - x1) y = y1 + u * (y2 - y1) z = z1 + u * (z2 - z1) ### SPHERE EQUATION ### (x - x3)^2 + (y - y3)^2 + (z - z3)^2 = r^2 ### QUADRATIC EQUATION TO SOLVE ### a*u^2 + b*u + c = 0 WHERE THE CONSTANTS ARE a = (x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2 b = 2*( (x2 - x1)*(x1 - x3) + (y2 - y1)*(y1 - y3) + (z2 - z1)*(z1 - z3) ) c = x3^2 + y3^2 + z3^2 + x1^2 + y1^2 + z1^2 - 2(x3*x1 + y3*y1 + z3*z1) - r^2 u = (-b +- sqrt(b^2 - 4*a*c)) / 2a If the value under the root is >= 0, we got an intersection If the value > 0, there are two solutions. Take the one closer to 0, since that's the one closer to the laser start point */ vec3& center = asteroid.center; float a = pow(end.x - start.x, 2) + pow(end.y - start.y, 2) + pow(end.z - start.z, 2); float b = 2 * ((start.x - end.x) * (start.x - center.x) + (end.y - start.y) * (start.y - center.y) + (end.z - start.z) * (start.z - center.z)); float c = pow(center.x, 2) + pow(center.y, 2) + pow(center.z, 2) + pow(start.x, 2) + pow(start.y, 2) + pow(start.z, 2) - 2 * (center.x * start.x + center.y * start.y + center.z * start.z) - pow(asteroid.radius, 2); float discriminant = pow(b, 2) - 4 * a * c; if (discriminant >= 0.0f) { // In this case, the negative root will always give the point closer to the laser start point float u = (-b - sqrt(discriminant)) / (2 * a); // Check that the intersection is within the line segment corresponding to the laser if (0.0f <= u && u <= 1.0f) { intersection = start + u * (end - start); return true; } } return false; } 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 } } void VulkanGame::addExplosion(mat4 model_mat, float duration, float cur_time) { vector vertices; vertices.reserve(EXPLOSION_PARTICLE_COUNT); float particle_start_time = 0.0f; for (int i = 0; i < EXPLOSION_PARTICLE_COUNT; i++) { float randx = ((float)rand() / (float)RAND_MAX) - 0.5f; float randy = ((float)rand() / (float)RAND_MAX) - 0.5f; vertices.push_back({ vec3(randx, randy, 0.0f), particle_start_time}); particle_start_time += .01f; // TODO: Get this working // particle_start_time += 1.0f * EXPLOSION_PARTICLE_COUNT / duration } // Fill the indices with the the first EXPLOSION_PARTICLE_COUNT ints vector indices(EXPLOSION_PARTICLE_COUNT); iota(indices.begin(), indices.end(), 0); SceneObject& explosion = addObject( explosionObjects, explosionPipeline, addObjectIndex(explosionObjects.size(), vertices), indices, { mat4(1.0f), cur_time, duration, false }, true); explosion.model_base = model_mat; explosion.model_transform = mat4(1.0f); explosion.modified = true; } // 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); createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, uniformBuffers_laserPipeline, uniformBuffersMemory_laserPipeline, uniformBufferInfoList_laserPipeline); laserPipeline.updateRenderPass(renderPass); laserPipeline.createPipeline("shaders/laser-vert.spv", "shaders/laser-frag.spv"); laserPipeline.createDescriptorPool(swapChainImages); laserPipeline.createDescriptorSets(swapChainImages); createBufferSet(sizeof(UBO_Explosion), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, uniformBuffers_explosionPipeline, uniformBuffersMemory_explosionPipeline, uniformBufferInfoList_explosionPipeline); explosionPipeline.updateRenderPass(renderPass); explosionPipeline.createPipeline("shaders/explosion-vert.spv", "shaders/explosion-frag.spv"); explosionPipeline.createDescriptorPool(swapChainImages); explosionPipeline.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(); laserPipeline.cleanup(); explosionPipeline.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); } for (size_t i = 0; i < uniformBuffers_laserPipeline.size(); i++) { vkDestroyBuffer(device, uniformBuffers_laserPipeline[i], nullptr); vkFreeMemory(device, uniformBuffersMemory_laserPipeline[i], nullptr); } for (size_t i = 0; i < uniformBuffers_explosionPipeline.size(); i++) { vkDestroyBuffer(device, uniformBuffers_explosionPipeline[i], nullptr); vkFreeMemory(device, uniformBuffersMemory_explosionPipeline[i], nullptr); } vkDestroyRenderPass(device, renderPass, nullptr); for (VkImageView imageView : swapChainImageViews) { vkDestroyImageView(device, imageView, nullptr); } vkDestroySwapchainKHR(device, swapChain, nullptr); }