#include "vulkan-game.hpp" #include #include #include #include #include #include "IMGUI/imgui_impl_sdl.h" #include "IMGUI/imgui_internal.h" // For CalcItemSize #include "logger.hpp" #include "utils.hpp" #include "gui/imgui/button-imgui.hpp" using namespace std; // TODO: Update all instances of the "... != VK_SUCCESS" check to something similar to // the agp error checking function, which prints an appropriate error message based on the error code // TODO: Update all occurances of instance variables to use this-> (Actually, not sure if I really want to do this) /* TODO: Try doing the following tasks based on the Vulkan implementation of IMGUI (Also maybe looks at Sascha Willems' code to see how he does these things) * * - When recreating the swapchain, pass the old one in and destroy the old one after the new one is created * - Recreate semaphores when recreating the swapchain * - imgui uses one image acquired and one render complete sem and once fence per frame\ * - IMGUI creates one command pool per framebuffer */ /* NOTES WHEN ADDING IMGUI * * Possibly cleanup the imgui pipeline in cleanupSwapchain or call some imgui function that does this for me * call ImGui_ImplVulkan_RenderDrawData, without passing in a pipeline, to do the rendering */ static void check_imgui_vk_result(VkResult res) { if (res == VK_SUCCESS) { return; } ostringstream oss; oss << "[imgui] Vulkan error! VkResult is \"" << VulkanUtils::resultString(res) << "\"" << __LINE__; if (res < 0) { throw runtime_error("Fatal: " + oss.str()); } else { cerr << oss.str(); } } VulkanGame::VulkanGame() : swapChainImageCount(0) , swapChainMinImageCount(0) , swapChainSurfaceFormat({}) , swapChainPresentMode(VK_PRESENT_MODE_MAX_ENUM_KHR) , swapChainExtent{ 0, 0 } , swapChain(VK_NULL_HANDLE) , vulkanSurface(VK_NULL_HANDLE) , sdlVersion({ 0, 0, 0 }) , instance(VK_NULL_HANDLE) , physicalDevice(VK_NULL_HANDLE) , device(VK_NULL_HANDLE) , debugMessenger(VK_NULL_HANDLE) , resourceCommandPool(VK_NULL_HANDLE) , renderPass(VK_NULL_HANDLE) , graphicsQueue(VK_NULL_HANDLE) , presentQueue(VK_NULL_HANDLE) , depthImage({}) , shouldRecreateSwapChain(false) , frameCount(0) , currentFrame(0) , imageIndex(0) , fpsStartTime(0.0f) , curTime(0.0f) , done(false) , currentRenderScreenFn(nullptr) , gui(nullptr) , window(nullptr) , score(0) , fps(0.0f) { object_VP_mats = {}; ship_VP_mats = {}; asteroid_VP_mats = {}; laser_VP_mats = {}; 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(); initImGuiOverlay(); // 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(); initMatrices(); cout << "INITIALIZING OBJECTS" << endl; 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/model-vert.spv", "shaders/model-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); currentRenderScreenFn = &VulkanGame::renderMainScreen; ImGuiIO& io = ImGui::GetIO(); initGuiValueLists(valueLists); valueLists["stats value list"].push_back(UIValue(UIVALUE_INT, "Score", &score)); valueLists["stats value list"].push_back(UIValue(UIVALUE_DOUBLE, "FPS", &fps)); valueLists["stats value list"].push_back(UIValue(UIVALUE_DOUBLE, "IMGUI FPS", &io.Framerate)); renderLoop(); 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 strings 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 // TODO: In addition, delete the gl_log functions 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; // TODO: Rename RTWO_ERROR to something else 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; 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); chooseSwapChainProperties(); createSwapChain(); createImageViews(); createResourceCommandPool(); createImageResources(); createRenderPass(); createCommandPools(); createFramebuffers(); createCommandBuffers(); createSyncObjects(); } void VulkanGame::initGraphicsPipelines() { 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() { 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; laser_VP_mats.view = viewMat; laser_VP_mats.proj = projMat; explosion_UBO.view = viewMat; explosion_UBO.proj = projMat; } void VulkanGame::renderLoop() { startTime = steady_clock::now(); curTime = duration(steady_clock::now() - startTime).count(); fpsStartTime = curTime; frameCount = 0; lastSpawn_asteroid = curTime; ImGuiIO& io = ImGui::GetIO(); done = false; while (!done) { prevTime = curTime; curTime = duration(steady_clock::now() - startTime).count(); elapsedTime = curTime - prevTime; if (curTime - fpsStartTime >= 1.0f) { fps = (float)frameCount / (curTime - fpsStartTime); frameCount = 0; fpsStartTime = curTime; } frameCount++; gui->processEvents(); UIEvent uiEvent; while (gui->pollEvent(&uiEvent)) { GameEvent& e = uiEvent.event; SDL_Event sdlEvent = uiEvent.rawEvent.sdl; ImGui_ImplSDL2_ProcessEvent(&sdlEvent); if ((e.type == UI_EVENT_MOUSEBUTTONDOWN || e.type == UI_EVENT_MOUSEBUTTONUP || e.type == UI_EVENT_UNKNOWN) && io.WantCaptureMouse) { if (sdlEvent.type == SDL_MOUSEWHEEL || sdlEvent.type == SDL_MOUSEBUTTONDOWN || sdlEvent.type == SDL_MOUSEBUTTONUP) { continue; } } if ((e.type == UI_EVENT_KEYDOWN || e.type == UI_EVENT_KEYUP) && io.WantCaptureKeyboard) { if (sdlEvent.type == SDL_KEYDOWN || sdlEvent.type == SDL_KEYUP) { continue; } } if (io.WantTextInput) { // show onscreen keyboard if on mobile } switch (e.type) { case UI_EVENT_QUIT: cout << "Quit event detected" << endl; done = true; break; case UI_EVENT_WINDOWRESIZE: cout << "Window resize event detected" << endl; shouldRecreateSwapChain = true; break; case UI_EVENT_KEYDOWN: if (e.key.repeat) { break; } if (e.key.keycode == SDL_SCANCODE_ESCAPE) { done = 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_WINDOW: case UI_EVENT_MOUSEBUTTONDOWN: case UI_EVENT_MOUSEBUTTONUP: case UI_EVENT_MOUSEMOTION: break; case UI_EVENT_UNHANDLED: cout << "Unhandled event type: 0x" << hex << sdlEvent.type << dec << endl; break; case UI_EVENT_UNKNOWN: default: cout << "Unknown event type: 0x" << hex << sdlEvent.type << dec << endl; break; } // This was left ovedr from the previous SDL UI implementation. // Might need something like this again when I start processing screen-specific UI events not related // to the IMGUI ui, such as arrow keys for movement and other buttons for shotting or something // currentScreen->handleEvent(e); } // 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)); } } if (shouldRecreateSwapChain) { gui->refreshWindowSize(); const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0; if (!isMinimized) { // TODO: This should be used if the min image count changes, presumably because a new surface was created // with a different image count or something like that. Maybe I want to add code to query for a new min image count // during swapchain recreation to take advantage of this ImGui_ImplVulkan_SetMinImageCount(swapChainMinImageCount); recreateSwapChain(); shouldRecreateSwapChain = false; } } updateScene(); // TODO: Move this into a renderImGuiOverlay() function ImGui_ImplVulkan_NewFrame(); ImGui_ImplSDL2_NewFrame(window); ImGui::NewFrame(); (this->*currentRenderScreenFn)(gui->getWindowWidth(), gui->getWindowHeight()); ImGui::Render(); gui->refreshWindowSize(); const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0; if (!isMinimized) { renderFrame(ImGui::GetDrawData()); presentFrame(); } } } // 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() { 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(curTime); 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); this->score++; } 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[imageIndex], 0, object_VP_mats); VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_shipPipeline[imageIndex], 0, ship_VP_mats); VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_asteroidPipeline[imageIndex], 0, asteroid_VP_mats); VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_laserPipeline[imageIndex], 0, laser_VP_mats); VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_explosionPipeline[imageIndex], 0, explosion_UBO); } void VulkanGame::cleanup() { // FIXME: We could wait on the Queue if we had the queue in wd-> (otherwise VulkanH functions can't use globals) //vkQueueWaitIdle(g_Queue); VKUTIL_CHECK_RESULT(vkDeviceWaitIdle(device), "failed to wait for device!"); cleanupImGuiOverlay(); cleanupSwapChain(); VulkanUtils::destroyVulkanImage(device, floorTextureImage); VulkanUtils::destroyVulkanImage(device, laserTextureImage); vkDestroySampler(device, textureSampler, nullptr); modelPipeline.cleanupBuffers(); shipPipeline.cleanupBuffers(); asteroidPipeline.cleanupBuffers(); laserPipeline.cleanupBuffers(); explosionPipeline.cleanupBuffers(); vkDestroyCommandPool(device, resourceCommandPool, nullptr); vkDestroyDevice(device, nullptr); vkDestroySurfaceKHR(instance, vulkanSurface, nullptr); if (ENABLE_VALIDATION_LAYERS) { VulkanUtils::destroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); } vkDestroyInstance(instance, 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; // TODO: Figure out what the return value means and if it should always be VK_FALSE return VK_FALSE; } void VulkanGame::createVulkanSurface() { if (gui->createVulkanSurface(instance, &vulkanSurface) == RTWO_ERROR) { throw runtime_error("failed to create window surface!"); } } void VulkanGame::pickPhysicalDevice(const vector& deviceExtensions) { uint32_t deviceCount = 0; // TODO: Check VkResult vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); if (deviceCount == 0) { throw runtime_error("failed to find GPUs with Vulkan support!"); } vector devices(deviceCount); // TODO: Check VkResult 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; // TODO: Eventually, maybe let the user pick out of a set of GPUs in case the user does want to use // an integrated GPU. On my laptop, this function returns TRUE for the integrated GPU, but crashes // when trying to use it to render. Maybe I just need to figure out which other extensions and features // to check. if (deviceProperties.deviceType != VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { return false; } QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface); bool extensionsSupported = VulkanUtils::checkDeviceExtensionSupport(physicalDevice, deviceExtensions); bool swapChainAdequate = false; if (extensionsSupported) { vector formats = VulkanUtils::querySwapChainFormats(physicalDevice, vulkanSurface); vector presentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, vulkanSurface); swapChainAdequate = !formats.empty() && !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, vulkanSurface); if (!indices.isComplete()) { throw runtime_error("failed to find required queue families!"); } // TODO: Using separate graphics and present queues currently works, but I should verify that I'm // using them correctly to get the most benefit out of separate queues 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.queueCount = 1; queueCreateInfo.queueFamilyIndex = queueFamily; 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::chooseSwapChainProperties() { vector availableFormats = VulkanUtils::querySwapChainFormats(physicalDevice, vulkanSurface); vector availablePresentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, vulkanSurface); swapChainSurfaceFormat = VulkanUtils::chooseSwapSurfaceFormat(availableFormats, { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM }, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR); vector presentModes{ VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR }; //vector presentModes{ VK_PRESENT_MODE_FIFO_KHR }; swapChainPresentMode = VulkanUtils::chooseSwapPresentMode(availablePresentModes, presentModes); cout << "[vulkan] Selected PresentMode = " << swapChainPresentMode << endl; VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, vulkanSurface); if (swapChainPresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { swapChainMinImageCount = 3; } else if (swapChainPresentMode == VK_PRESENT_MODE_FIFO_KHR || swapChainPresentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) { swapChainMinImageCount = 2; } else if (swapChainPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) { swapChainMinImageCount = 1; } else { throw runtime_error("unexpected present mode!"); } if (swapChainMinImageCount < capabilities.minImageCount) { swapChainMinImageCount = capabilities.minImageCount; } else if (capabilities.maxImageCount != 0 && swapChainMinImageCount > capabilities.maxImageCount) { swapChainMinImageCount = capabilities.maxImageCount; } } void VulkanGame::createSwapChain() { VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, vulkanSurface); swapChainExtent = VulkanUtils::chooseSwapExtent(capabilities, gui->getWindowWidth(), gui->getWindowHeight()); VkSwapchainCreateInfoKHR createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = vulkanSurface; createInfo.minImageCount = swapChainMinImageCount; createInfo.imageFormat = swapChainSurfaceFormat.format; createInfo.imageColorSpace = swapChainSurfaceFormat.colorSpace; createInfo.imageExtent = swapChainExtent; createInfo.imageArrayLayers = 1; createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; // TODO: Maybe save this result so I don't have to recalculate it every time QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface); 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 = capabilities.currentTransform; createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; createInfo.presentMode = swapChainPresentMode; 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!"); } if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, nullptr) != VK_SUCCESS) { throw runtime_error("failed to get swap chain image count!"); } swapChainImages.resize(swapChainImageCount); if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, swapChainImages.data()) != VK_SUCCESS) { throw runtime_error("failed to get swap chain images!"); } } void VulkanGame::createImageViews() { swapChainImageViews.resize(swapChainImageCount); for (size_t i = 0; i < swapChainImageCount; i++) { swapChainImageViews[i] = VulkanUtils::createImageView(device, swapChainImages[i], swapChainSurfaceFormat.format, VK_IMAGE_ASPECT_COLOR_BIT); } } void VulkanGame::createResourceCommandPool() { QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface); VkCommandPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = indices.graphicsFamily.value(); poolInfo.flags = 0; if (vkCreateCommandPool(device, &poolInfo, nullptr, &resourceCommandPool) != VK_SUCCESS) { throw runtime_error("failed to create resource command pool!"); } } void VulkanGame::createImageResources() { VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent, depthImage, graphicsQueue); createTextureSampler(); // TODO: Move all images/textures somewhere into the assets folder VulkanUtils::createVulkanImageFromFile(device, physicalDevice, resourceCommandPool, "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, resourceCommandPool, "textures/laser.png", laserTextureImage, graphicsQueue); laserTextureImageDescriptor = {}; laserTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; laserTextureImageDescriptor.imageView = laserTextureImage.imageView; laserTextureImageDescriptor.sampler = textureSampler; } 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::createRenderPass() { VkAttachmentDescription colorAttachment = {}; colorAttachment.format = swapChainSurfaceFormat.format; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; VkAttachmentReference colorAttachmentRef = {}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; 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!"); } } void VulkanGame::createCommandPools() { commandPools.resize(swapChainImageCount); QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface); for (size_t i = 0; i < swapChainImageCount; i++) { VkCommandPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = indices.graphicsFamily.value(); poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; VKUTIL_CHECK_RESULT(vkCreateCommandPool(device, &poolInfo, nullptr, &commandPools[i]), "failed to create graphics command pool!"); } } 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; VKUTIL_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler), "failed to create texture sampler!"); } void VulkanGame::createFramebuffers() { swapChainFramebuffers.resize(swapChainImageCount); VkFramebufferCreateInfo framebufferInfo = {}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.width = swapChainExtent.width; framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; for (uint32_t i = 0; i < swapChainImageCount; i++) { array attachments = { swapChainImageViews[i], depthImage.imageView }; framebufferInfo.attachmentCount = static_cast(attachments.size()); framebufferInfo.pAttachments = attachments.data(); if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw runtime_error("failed to create framebuffer!"); } } } void VulkanGame::createCommandBuffers() { commandBuffers.resize(swapChainImageCount); for (size_t i = 0; i < swapChainImageCount; i++) { VkCommandBufferAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPools[i]; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandBufferCount = 1; if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffers[i]) != VK_SUCCESS) { throw runtime_error("failed to allocate command buffer!"); } } } void VulkanGame::createSyncObjects() { imageAcquiredSemaphores.resize(swapChainImageCount); renderCompleteSemaphores.resize(swapChainImageCount); inFlightFences.resize(swapChainImageCount); 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 < swapChainImageCount; i++) { VKUTIL_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAcquiredSemaphores[i]), "failed to create image acquired sempahore for a frame!"); VKUTIL_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderCompleteSemaphores[i]), "failed to create render complete sempahore for a frame!"); VKUTIL_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]), "failed to create fence for a frame!"); } } void VulkanGame::renderFrame(ImDrawData* draw_data) { VkResult result = vkAcquireNextImageKHR(device, swapChain, numeric_limits::max(), imageAcquiredSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_SUBOPTIMAL_KHR) { shouldRecreateSwapChain = true; } else if (result == VK_ERROR_OUT_OF_DATE_KHR) { shouldRecreateSwapChain = true; return; } else { VKUTIL_CHECK_RESULT(result, "failed to acquire swap chain image!"); } VKUTIL_CHECK_RESULT( vkWaitForFences(device, 1, &inFlightFences[imageIndex], VK_TRUE, numeric_limits::max()), "failed waiting for fence!"); VKUTIL_CHECK_RESULT(vkResetFences(device, 1, &inFlightFences[imageIndex]), "failed to reset fence!"); VKUTIL_CHECK_RESULT(vkResetCommandPool(device, commandPools[imageIndex], 0), "failed to reset command pool!"); VkCommandBufferBeginInfo beginInfo = {}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; VKUTIL_CHECK_RESULT(vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo), "failed to begin recording command buffer!"); VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassInfo.renderPass = renderPass; renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; 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[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); // TODO: Find a more elegant, per-screen solution for this if (currentRenderScreenFn == &VulkanGame::renderGameScreen) { modelPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex); shipPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex); asteroidPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex); laserPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex); explosionPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex); } ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffers[imageIndex]); vkCmdEndRenderPass(commandBuffers[imageIndex]); VKUTIL_CHECK_RESULT(vkEndCommandBuffer(commandBuffers[imageIndex]), "failed to record command buffer!"); VkSemaphore waitSemaphores[] = { imageAcquiredSemaphores[currentFrame] }; VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] }; VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; VKUTIL_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[imageIndex]), "failed to submit draw command buffer!"); } void VulkanGame::presentFrame() { VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] }; VkPresentInfoKHR presentInfo = {}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; presentInfo.pWaitSemaphores = signalSemaphores; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = &swapChain; presentInfo.pImageIndices = &imageIndex; presentInfo.pResults = nullptr; VkResult result = vkQueuePresentKHR(presentQueue, &presentInfo); if (result == VK_SUBOPTIMAL_KHR) { shouldRecreateSwapChain = true; } else if (result == VK_ERROR_OUT_OF_DATE_KHR) { shouldRecreateSwapChain = true; return; } else { VKUTIL_CHECK_RESULT(result, "failed to present swap chain image!"); } currentFrame = (currentFrame + 1) % swapChainImageCount; } void VulkanGame::initImGuiOverlay() { vector pool_sizes{ { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 }, { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 }, { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 }, { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 }, { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 } }; VkDescriptorPoolCreateInfo pool_info = {}; pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; pool_info.maxSets = 1000 * pool_sizes.size(); pool_info.poolSizeCount = static_cast(pool_sizes.size()); pool_info.pPoolSizes = pool_sizes.data(); VKUTIL_CHECK_RESULT(vkCreateDescriptorPool(device, &pool_info, nullptr, &imguiDescriptorPool), "failed to create IMGUI descriptor pool!"); // TODO: Do this in one place and save it instead of redoing it every time I need a queue family index QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface); // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls // Setup Dear ImGui style ImGui::StyleColorsDark(); //ImGui::StyleColorsClassic(); // Setup Platform/Renderer bindings ImGui_ImplSDL2_InitForVulkan(window); ImGui_ImplVulkan_InitInfo init_info = {}; init_info.Instance = instance; init_info.PhysicalDevice = physicalDevice; init_info.Device = device; init_info.QueueFamily = indices.graphicsFamily.value(); init_info.Queue = graphicsQueue; init_info.DescriptorPool = imguiDescriptorPool; init_info.Allocator = nullptr; init_info.MinImageCount = swapChainMinImageCount; init_info.ImageCount = swapChainImageCount; init_info.CheckVkResultFn = check_imgui_vk_result; ImGui_ImplVulkan_Init(&init_info, renderPass); // Load Fonts // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! //io.Fonts->AddFontDefault(); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f); //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese()); //assert(font != NULL); // Upload Fonts { VkCommandBuffer commandBuffer = VulkanUtils::beginSingleTimeCommands(device, resourceCommandPool); ImGui_ImplVulkan_CreateFontsTexture(commandBuffer); VulkanUtils::endSingleTimeCommands(device, resourceCommandPool, commandBuffer, graphicsQueue); ImGui_ImplVulkan_DestroyFontUploadObjects(); } } void VulkanGame::cleanupImGuiOverlay() { ImGui_ImplVulkan_Shutdown(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); vkDestroyDescriptorPool(device, imguiDescriptorPool, nullptr); } void VulkanGame::createBufferSet(VkDeviceSize bufferSize, VkBufferUsageFlags flags, vector& buffers, vector& buffersMemory, vector& bufferInfoList) { buffers.resize(swapChainImageCount); buffersMemory.resize(swapChainImageCount); bufferInfoList.resize(swapChainImageCount); for (size_t i = 0; i < swapChainImageCount; 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::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), curTime, -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 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::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; } void VulkanGame::recreateSwapChain() { if (vkDeviceWaitIdle(device) != VK_SUCCESS) { throw runtime_error("failed to wait for device!"); } cleanupSwapChain(); createSwapChain(); createImageViews(); // The depth buffer does need to be recreated with the swap chain since its dimensions depend on the window size // and resizing the window is a common reason to recreate the swapchain VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent, depthImage, graphicsQueue); createRenderPass(); createCommandPools(); createFramebuffers(); createCommandBuffers(); createSyncObjects(); // TODO: Move UBO creation/management into GraphicsPipeline_Vulkan, like I did with SSBOs // TODO: Check if the shader stages and maybe some other properties of the pipeline can be re-used // instead of recreated every time createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, uniformBuffers_modelPipeline, uniformBuffersMemory_modelPipeline, uniformBufferInfoList_modelPipeline); modelPipeline.updateRenderPass(renderPass); modelPipeline.createPipeline("shaders/model-vert.spv", "shaders/model-frag.spv"); modelPipeline.createDescriptorPool(swapChainImages); modelPipeline.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); imageIndex = 0; } void VulkanGame::cleanupSwapChain() { VulkanUtils::destroyVulkanImage(device, depthImage); for (VkFramebuffer framebuffer : swapChainFramebuffers) { vkDestroyFramebuffer(device, framebuffer, nullptr); } for (uint32_t i = 0; i < swapChainImageCount; i++) { vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]); vkDestroyCommandPool(device, commandPools[i], nullptr); } 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); } for (size_t i = 0; i < swapChainImageCount; i++) { vkDestroySemaphore(device, imageAcquiredSemaphores[i], nullptr); vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr); vkDestroyFence(device, inFlightFences[i], nullptr); } vkDestroyRenderPass(device, renderPass, nullptr); for (VkImageView imageView : swapChainImageViews) { vkDestroyImageView(device, imageView, nullptr); } vkDestroySwapchainKHR(device, swapChain, nullptr); } void VulkanGame::renderMainScreen(int width, int height) { { int padding = 4; ImGui::SetNextWindowPos(vec2(-padding, -padding), ImGuiCond_Once); ImGui::SetNextWindowSize(vec2(width + 2 * padding, height + 2 * padding), ImGuiCond_Always); ImGui::Begin("WndMain", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); ButtonImGui btn("New Game"); ImGui::InvisibleButton("", vec2(10, height / 6)); if (btn.draw((width - btn.getWidth()) / 2)) { goToScreen(&VulkanGame::renderGameScreen); } ButtonImGui btn2("Quit"); ImGui::InvisibleButton("", vec2(10, 15)); if (btn2.draw((width - btn2.getWidth()) / 2)) { quitGame(); } ImGui::End(); } } void VulkanGame::renderGameScreen(int width, int height) { { ImGui::SetNextWindowSize(vec2(130, 65), ImGuiCond_Once); ImGui::SetNextWindowPos(vec2(10, 50), ImGuiCond_Once); ImGui::Begin("WndStats", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); //ImGui::Text(ImGui::GetIO().Framerate); renderGuiValueList(valueLists["stats value list"]); ImGui::End(); } { ImGui::SetNextWindowSize(vec2(250, 35), ImGuiCond_Once); ImGui::SetNextWindowPos(vec2(width - 260, 10), ImGuiCond_Always); ImGui::Begin("WndMenubar", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); ImGui::InvisibleButton("", vec2(155, 18)); ImGui::SameLine(); if (ImGui::Button("Main Menu")) { goToScreen(&VulkanGame::renderMainScreen); } ImGui::End(); } { ImGui::SetNextWindowSize(vec2(200, 200), ImGuiCond_Once); ImGui::SetNextWindowPos(vec2(width - 210, 60), ImGuiCond_Always); ImGui::Begin("WndDebug", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); renderGuiValueList(valueLists["debug value list"]); ImGui::End(); } } void VulkanGame::initGuiValueLists(map>& valueLists) { valueLists["stats value list"] = vector(); valueLists["debug value list"] = vector(); } // TODO: Probably turn this into a UI widget class void VulkanGame::renderGuiValueList(vector& values) { float maxWidth = 0.0f; float cursorStartPos = ImGui::GetCursorPosX(); for (vector::iterator it = values.begin(); it != values.end(); it++) { float textWidth = ImGui::CalcTextSize(it->label.c_str()).x; if (maxWidth < textWidth) maxWidth = textWidth; } stringstream ss; // TODO: Possibly implement this based on gui/ui-value.hpp instead and use templates // to keep track of the type. This should make it a bit easier to use and maintain // Also, implement this in a way that's agnostic to the UI renderer. for (vector::iterator it = values.begin(); it != values.end(); it++) { ss.str(""); ss.clear(); switch (it->type) { case UIVALUE_INT: ss << it->label << ": " << *(unsigned int*)it->value; break; case UIVALUE_DOUBLE: ss << it->label << ": " << *(double*)it->value; break; } float textWidth = ImGui::CalcTextSize(it->label.c_str()).x; ImGui::SetCursorPosX(cursorStartPos + maxWidth - textWidth); //ImGui::Text("%s", ss.str().c_str()); ImGui::Text("%s: %.1f", it->label.c_str(), *(float*)it->value); } } void VulkanGame::goToScreen(void (VulkanGame::* renderScreenFn)(int width, int height)) { currentRenderScreenFn = renderScreenFn; // TODO: Maybe just set shouldRecreateSwapChain to true instead. Check this render loop logic // to make sure there'd be no issues //recreateSwapChain(); } void VulkanGame::quitGame() { done = true; }