source: opengl-game/vulkan-game.cpp@ c6f0793

feature/imgui-sdl
Last change on this file since c6f0793 was c6f0793, checked in by Dmitry Portnoy <dportnoy@…>, 4 years ago

Use done instead of quit as the game loop flag and change it to an instance variable in SDLGame

  • Property mode set to 100644
File size: 88.1 KB
Line 
1#include "vulkan-game.hpp"
2
3#include <array>
4#include <iostream>
5#include <numeric>
6#include <set>
7#include <stdexcept>
8
9#include "IMGUI/imgui_impl_sdl.h"
10#include "IMGUI/imgui_impl_vulkan.h"
11
12#include "logger.hpp"
13
14#include "utils.hpp"
15
16#include "gui/main-screen.hpp"
17#include "gui/game-screen.hpp"
18
19using namespace std;
20
21// TODO: Update all instances of the "... != VK_SUCCESS" check to something similar to
22// the agp error checking function, which prints an appropriate error message based on the error code
23
24// TODO: Update all occurances of instance variables to use this-> (Actually, not sure if I really want to do this)
25
26/* 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)
27 *
28 * - When recreating the swapchain, pass the old one in and destroy the old one after the new one is created
29 * - Recreate semaphores when recreating the swapchain
30 * - imgui uses one image acquired and one render complete sem and once fence per frame\
31 * - IMGUI creates one command pool per framebuffer
32 */
33
34/* NOTES WHEN ADDING IMGUI
35 *
36 * Possibly cleanup the imgui pipeline in cleanupSwapchain or call some imgui function that does this for me
37 * call ImGui_ImplVulkan_RenderDrawData, without passing in a pipeline, to do the rendering
38 */
39
40static void check_imgui_vk_result(VkResult res) {
41 if (res == VK_SUCCESS) {
42 return;
43 }
44
45 ostringstream oss;
46 oss << "[imgui] Vulkan error! VkResult is \"" << VulkanUtils::resultString(res) << "\"" << __LINE__;
47 if (res < 0) {
48 throw runtime_error("Fatal: " + oss.str());
49 } else {
50 cerr << oss.str();
51 }
52}
53
54VulkanGame::VulkanGame() {
55 // TODO: Double-check whether initialization should happen in the header, where the variables are declared, or here
56 // Also, decide whether to use this-> for all instance variables, or only when necessary
57
58 this->debugMessenger = VK_NULL_HANDLE;
59
60 this->gui = nullptr;
61 this->window = nullptr;
62
63 this->swapChainPresentMode = VK_PRESENT_MODE_MAX_ENUM_KHR;
64 this->swapChainMinImageCount = 0;
65
66 this->currentFrame = 0;
67 this->framebufferResized = false;
68
69 this->object_VP_mats = {};
70 this->ship_VP_mats = {};
71 this->asteroid_VP_mats = {};
72 this->laser_VP_mats = {};
73 this->explosion_UBO = {};
74}
75
76VulkanGame::~VulkanGame() {
77}
78
79void VulkanGame::run(int width, int height, unsigned char guiFlags) {
80 seedRandomNums();
81
82 cout << "DEBUGGING IS " << (ENABLE_VALIDATION_LAYERS ? "ON" : "OFF") << endl;
83
84 cout << "Vulkan Game" << endl;
85
86 this->score = 0;
87
88 if (initUI(width, height, guiFlags) == RTWO_ERROR) {
89 return;
90 }
91
92 // TODO: Maybe make a struct of properties to share with each screen instead of passing
93 // in all of VulkanGame
94 screens[SCREEN_MAIN] = new MainScreen(*renderer, *this);
95 screens[SCREEN_GAME] = new GameScreen(*renderer, *this);
96
97 currentScreen = screens[SCREEN_MAIN];
98
99 initVulkan();
100 mainLoop();
101 cleanup();
102
103 close_log();
104}
105
106void VulkanGame::goToScreen(Screen* screen) {
107 currentScreen = screen;
108 currentScreen->init();
109
110 recreateSwapChain();
111}
112
113void VulkanGame::quitGame() {
114 done = true;
115}
116
117bool VulkanGame::initUI(int width, int height, unsigned char guiFlags) {
118 // TODO: Create a game-gui function to get the gui version and retrieve it that way
119
120 SDL_VERSION(&sdlVersion); // This gets the compile-time version
121 SDL_GetVersion(&sdlVersion); // This gets the runtime version
122
123 cout << "SDL "<<
124 to_string(sdlVersion.major) << "." <<
125 to_string(sdlVersion.minor) << "." <<
126 to_string(sdlVersion.patch) << endl;
127
128 // TODO: Refactor the logger api to be more flexible,
129 // esp. since gl_log() and gl_log_err() have issues printing anything besides strings
130 restart_gl_log();
131 gl_log("starting SDL\n%s.%s.%s",
132 to_string(sdlVersion.major).c_str(),
133 to_string(sdlVersion.minor).c_str(),
134 to_string(sdlVersion.patch).c_str());
135
136 // TODO: Use open_Log() and related functions instead of gl_log ones
137 // TODO: In addition, delete the gl_log functions
138 open_log();
139 get_log() << "starting SDL" << endl;
140 get_log() <<
141 (int)sdlVersion.major << "." <<
142 (int)sdlVersion.minor << "." <<
143 (int)sdlVersion.patch << endl;
144
145 // TODO: Put all fonts, textures, and images in the assets folder
146 gui = new GameGui_SDL();
147
148 if (gui->init() == RTWO_ERROR) {
149 // TODO: Also print these sorts of errors to the log
150 cout << "UI library could not be initialized!" << endl;
151 cout << gui->getError() << endl;
152 return RTWO_ERROR;
153 }
154
155 window = (SDL_Window*) gui->createWindow("Vulkan Game", width, height, guiFlags & GUI_FLAGS_WINDOW_FULLSCREEN);
156 if (window == nullptr) {
157 cout << "Window could not be created!" << endl;
158 cout << gui->getError() << endl;
159 return RTWO_ERROR;
160 }
161
162 cout << "Target window size: (" << width << ", " << height << ")" << endl;
163 cout << "Actual window size: (" << gui->getWindowWidth() << ", " << gui->getWindowHeight() << ")" << endl;
164
165 renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
166 if (renderer == nullptr) {
167 cout << "Renderer could not be created!" << endl;
168 cout << gui->getError() << endl;
169 return RTWO_ERROR;
170 }
171
172 uiOverlay = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET,
173 gui->getWindowWidth(), gui->getWindowHeight());
174
175 if (uiOverlay == nullptr) {
176 cout << "Unable to create blank texture! SDL Error: " << SDL_GetError() << endl;
177 return RTWO_ERROR;
178 }
179 if (SDL_SetTextureBlendMode(uiOverlay, SDL_BLENDMODE_BLEND) != 0) {
180 cout << "Unable to set texture blend mode! SDL Error: " << SDL_GetError() << endl;
181 return RTWO_ERROR;
182 }
183
184 SDL_SetRenderTarget(renderer, uiOverlay);
185
186 // TODO: Print the filename of the font in the error message
187
188 lazyFont = TTF_OpenFont("assets/fonts/lazy.ttf", 28);
189 if (lazyFont == nullptr) {
190 cout << "Failed to load lazy font! SDL_ttf Error: " << TTF_GetError() << endl;
191 return RTWO_ERROR;
192 }
193
194 proggyFont = TTF_OpenFont("assets/fonts/ProggyClean.ttf", 16);
195 if (proggyFont == nullptr) {
196 cout << "Failed to load proggy font! SDL_ttf Error: " << TTF_GetError() << endl;
197 return RTWO_ERROR;
198 }
199
200 return RTWO_SUCCESS;
201}
202
203void VulkanGame::initVulkan() {
204 const vector<const char*> validationLayers = {
205 "VK_LAYER_KHRONOS_validation"
206 };
207 const vector<const char*> deviceExtensions = {
208 VK_KHR_SWAPCHAIN_EXTENSION_NAME
209 };
210
211 createVulkanInstance(validationLayers);
212 setupDebugMessenger();
213 createVulkanSurface();
214 pickPhysicalDevice(deviceExtensions);
215 createLogicalDevice(validationLayers, deviceExtensions);
216 chooseSwapChainProperties();
217 createSwapChain();
218 createImageViews();
219 createRenderPass();
220 createResourceCommandPool();
221 createCommandPool();
222
223 createImageResources();
224 createFramebuffers();
225
226 // TODO: I think I can start setting up IMGUI here
227 // ImGui_ImplVulkan_Init will create the Vulkan pipeline for ImGui for me
228 // imgui_impl_vulkan keeps track of the imgui pipeline internally
229 // TODO: Check how the example recreates the pipeline and what code I need
230 // to copy over to do that
231
232 createImguiDescriptorPool();
233
234 IMGUI_CHECKVERSION();
235 ImGui::CreateContext();
236 ImGuiIO& io = ImGui::GetIO(); (void)io;
237 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
238 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
239
240 // Setup Dear ImGui style
241 ImGui::StyleColorsDark();
242 //ImGui::StyleColorsClassic();
243
244 // TODO: Maybe call this once and save the results since it's also called when creating the logical device
245 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
246
247 ImGui_ImplSDL2_InitForVulkan(window);
248 ImGui_ImplVulkan_InitInfo init_info = {};
249 init_info.Instance = this->instance;
250 init_info.PhysicalDevice = this->physicalDevice;
251 init_info.Device = this->device;
252 init_info.QueueFamily = indices.graphicsFamily.value();
253 init_info.Queue = graphicsQueue;
254 init_info.DescriptorPool = this->imguiDescriptorPool; // TODO: Create a descriptor pool for IMGUI
255 init_info.Allocator = nullptr;
256 init_info.MinImageCount = this->swapChainMinImageCount;
257 init_info.ImageCount = this->swapChainImageCount;
258 init_info.CheckVkResultFn = check_imgui_vk_result;
259 ImGui_ImplVulkan_Init(&init_info, this->renderPass);
260
261 cout << "Got here" << endl;
262
263 // TODO: I think I have code in VkUtil for creating VkImages, which uses command buffers
264 // Maybe check how that code works
265
266 // Upload Fonts
267 {
268 VkCommandBuffer command_buffer;
269
270 // Create the command buffer to load
271 VkCommandBufferAllocateInfo info = {};
272 info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
273 info.commandPool = resourceCommandPool;
274 info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
275 info.commandBufferCount = 1;
276
277 VKUTIL_CHECK_RESULT(vkAllocateCommandBuffers(this->device, &info, &command_buffer),
278 "failed to allocate command buffers!");
279
280 //err = vkResetCommandPool(this->device, command_pool, 0); // Probably not really needed here since the command pool is never used before this
281 //check_vk_result(err);
282 VkCommandBufferBeginInfo begin_info = {};
283 begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
284 begin_info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
285 VKUTIL_CHECK_RESULT(vkBeginCommandBuffer(command_buffer, &begin_info),
286 "failed to begin recording command buffer!");
287
288 ImGui_ImplVulkan_CreateFontsTexture(command_buffer);
289
290 VkSubmitInfo end_info = {};
291 end_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
292 end_info.commandBufferCount = 1;
293 end_info.pCommandBuffers = &command_buffer;
294
295 VKUTIL_CHECK_RESULT(vkEndCommandBuffer(command_buffer),
296 "failed to record command buffer!");
297
298 VKUTIL_CHECK_RESULT(vkQueueSubmit(this->graphicsQueue, 1, &end_info, VK_NULL_HANDLE),
299 "failed to submit draw command buffer!");
300
301 if (vkDeviceWaitIdle(this->device) != VK_SUCCESS) {
302 throw runtime_error("failed to wait for device!");
303 }
304
305 ImGui_ImplVulkan_DestroyFontUploadObjects();
306
307 // This should make the command pool reusable for later
308 VKUTIL_CHECK_RESULT(vkResetCommandPool(this->device, resourceCommandPool, 0),
309 "failed to reset command pool!");
310 }
311
312 cout << "And now here" << endl;
313
314 initMatrices();
315
316 // TODO: Figure out how much of ubo creation and associated variables should be in the pipeline class
317 // Maybe combine the ubo-related objects into a new class
318
319 initGraphicsPipelines();
320
321 overlayPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&OverlayVertex::pos));
322 overlayPipeline.addAttribute(VK_FORMAT_R32G32_SFLOAT, offset_of(&OverlayVertex::texCoord));
323
324 overlayPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
325 VK_SHADER_STAGE_FRAGMENT_BIT, &sdlOverlayImageDescriptor);
326
327 addObject(overlayObjects, overlayPipeline,
328 {
329 {{-1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}},
330 {{ 1.0f, 1.0f, 0.0f}, {1.0f, 1.0f}},
331 {{ 1.0f, -1.0f, 0.0f}, {1.0f, 0.0f}},
332 {{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f}}
333 }, {
334 0, 1, 2, 2, 3, 0
335 }, {}, false);
336
337 overlayPipeline.createDescriptorSetLayout();
338 overlayPipeline.createPipeline("shaders/overlay-vert.spv", "shaders/overlay-frag.spv");
339 overlayPipeline.createDescriptorPool(swapChainImages);
340 overlayPipeline.createDescriptorSets(swapChainImages);
341
342 modelPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::pos));
343 modelPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::color));
344 modelPipeline.addAttribute(VK_FORMAT_R32G32_SFLOAT, offset_of(&ModelVertex::texCoord));
345 modelPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&ModelVertex::objIndex));
346
347 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
348 uniformBuffers_modelPipeline, uniformBuffersMemory_modelPipeline, uniformBufferInfoList_modelPipeline);
349
350 modelPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
351 VK_SHADER_STAGE_VERTEX_BIT, &uniformBufferInfoList_modelPipeline);
352 modelPipeline.addStorageDescriptor(VK_SHADER_STAGE_VERTEX_BIT);
353 modelPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
354 VK_SHADER_STAGE_FRAGMENT_BIT, &floorTextureImageDescriptor);
355
356 SceneObject<ModelVertex, SSBO_ModelObject>* texturedSquare = nullptr;
357
358 texturedSquare = &addObject(modelObjects, modelPipeline,
359 addObjectIndex<ModelVertex>(modelObjects.size(), {
360 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}},
361 {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}},
362 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
363 {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}
364 }), {
365 0, 1, 2, 2, 3, 0
366 }, {
367 mat4(1.0f)
368 }, false);
369
370 texturedSquare->model_base =
371 translate(mat4(1.0f), vec3(0.0f, 0.0f, -2.0f));
372 texturedSquare->modified = true;
373
374 texturedSquare = &addObject(modelObjects, modelPipeline,
375 addObjectIndex<ModelVertex>(modelObjects.size(), {
376 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}},
377 {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}},
378 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
379 {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}
380 }), {
381 0, 1, 2, 2, 3, 0
382 }, {
383 mat4(1.0f)
384 }, false);
385
386 texturedSquare->model_base =
387 translate(mat4(1.0f), vec3(0.0f, 0.0f, -1.5f));
388 texturedSquare->modified = true;
389
390 modelPipeline.createDescriptorSetLayout();
391 modelPipeline.createPipeline("shaders/scene-vert.spv", "shaders/scene-frag.spv");
392 modelPipeline.createDescriptorPool(swapChainImages);
393 modelPipeline.createDescriptorSets(swapChainImages);
394
395 shipPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ShipVertex::pos));
396 shipPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ShipVertex::color));
397 shipPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ShipVertex::normal));
398 shipPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&ShipVertex::objIndex));
399
400 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
401 uniformBuffers_shipPipeline, uniformBuffersMemory_shipPipeline, uniformBufferInfoList_shipPipeline);
402
403 shipPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
404 VK_SHADER_STAGE_VERTEX_BIT, &uniformBufferInfoList_shipPipeline);
405 shipPipeline.addStorageDescriptor(VK_SHADER_STAGE_VERTEX_BIT);
406
407 // TODO: With the normals, indexing basically becomes pointless since no vertices will have exactly
408 // the same data. Add an option to make some pipelines not use indexing
409 SceneObject<ShipVertex, SSBO_ModelObject>& ship = addObject(shipObjects, shipPipeline,
410 addObjectIndex<ShipVertex>(shipObjects.size(),
411 addVertexNormals<ShipVertex>({
412
413 //back
414 {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
415 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
416 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
417 {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
418 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
419 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
420
421 // left back
422 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
423 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
424 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
425 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
426 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
427 {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
428
429 // right back
430 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
431 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
432 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
433 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
434 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
435 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
436
437 // left mid
438 {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}},
439 {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}},
440 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
441 {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}},
442 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
443 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
444
445 // right mid
446 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
447 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
448 {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}},
449 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
450 {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}},
451 {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}},
452
453 // left front
454 {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 1.0f}},
455 {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}},
456 {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}},
457
458 // right front
459 {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}},
460 {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}},
461 {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 1.0f}},
462
463 // top back
464 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
465 {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 1.0f}},
466 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 1.0f}},
467 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
468 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 1.0f}},
469 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
470
471 // bottom back
472 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
473 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
474 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
475 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
476 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
477 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
478
479 // top mid
480 {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}},
481 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
482 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
483 {{ -0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}},
484 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
485 {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}},
486
487 // bottom mid
488 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
489 {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}},
490 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
491 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
492 {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}},
493 {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}},
494
495 // top front
496 {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}},
497 {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}},
498 {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 0.3f}},
499
500 // bottom front
501 {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}},
502 {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}},
503 {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 0.3f}},
504
505 // left wing start back
506 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
507 {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
508 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
509 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
510 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
511 {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
512
513 // left wing start top
514 {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
515 {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
516 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
517 {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
518 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
519 {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
520
521 // left wing start front
522 {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
523 {{ -0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
524 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
525 {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
526 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
527 {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
528
529 // left wing start bottom
530 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
531 {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
532 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
533 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
534 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
535 {{ -0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
536
537 // left wing end outside
538 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
539 {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
540 {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
541
542 // left wing end top
543 {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
544 {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
545 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
546
547 // left wing end front
548 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
549 {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
550 {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
551
552 // left wing end bottom
553 {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
554 {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
555 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
556
557 // right wing start back
558 {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
559 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
560 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
561 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
562 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
563 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
564
565 // right wing start top
566 {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
567 {{ 0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
568 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
569 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
570 {{ 0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
571 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
572
573 // right wing start front
574 {{ 0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
575 {{ 0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
576 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
577 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
578 {{ 0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
579 {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
580
581 // right wing start bottom
582 {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
583 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
584 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
585 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
586 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
587 {{ 0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
588
589 // right wing end outside
590 {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
591 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
592 {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
593
594 // right wing end top
595 {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
596 {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
597 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
598
599 // right wing end front
600 {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
601 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
602 {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
603
604 // right wing end bottom
605 {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
606 {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
607 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
608 })), {
609 0, 1, 2, 3, 4, 5,
610 6, 7, 8, 9, 10, 11,
611 12, 13, 14, 15, 16, 17,
612 18, 19, 20, 21, 22, 23,
613 24, 25, 26, 27, 28, 29,
614 30, 31, 32,
615 33, 34, 35,
616 36, 37, 38, 39, 40, 41,
617 42, 43, 44, 45, 46, 47,
618 48, 49, 50, 51, 52, 53,
619 54, 55, 56, 57, 58, 59,
620 60, 61, 62,
621 63, 64, 65,
622 66, 67, 68, 69, 70, 71,
623 72, 73, 74, 75, 76, 77,
624 78, 79, 80, 81, 82, 83,
625 84, 85, 86, 87, 88, 89,
626 90, 91, 92,
627 93, 94, 95,
628 96, 97, 98,
629 99, 100, 101,
630 102, 103, 104, 105, 106, 107,
631 108, 109, 110, 111, 112, 113,
632 114, 115, 116, 117, 118, 119,
633 120, 121, 122, 123, 124, 125,
634 126, 127, 128,
635 129, 130, 131,
636 132, 133, 134,
637 135, 136, 137,
638 }, {
639 mat4(1.0f)
640 }, false);
641
642 ship.model_base =
643 translate(mat4(1.0f), vec3(0.0f, -1.2f, 1.65f)) *
644 scale(mat4(1.0f), vec3(0.1f, 0.1f, 0.1f));
645 ship.modified = true;
646
647 shipPipeline.createDescriptorSetLayout();
648 shipPipeline.createPipeline("shaders/ship-vert.spv", "shaders/ship-frag.spv");
649 shipPipeline.createDescriptorPool(swapChainImages);
650 shipPipeline.createDescriptorSets(swapChainImages);
651
652 asteroidPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&AsteroidVertex::pos));
653 asteroidPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&AsteroidVertex::color));
654 asteroidPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&AsteroidVertex::normal));
655 asteroidPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&AsteroidVertex::objIndex));
656
657 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
658 uniformBuffers_asteroidPipeline, uniformBuffersMemory_asteroidPipeline, uniformBufferInfoList_asteroidPipeline);
659
660 asteroidPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
661 VK_SHADER_STAGE_VERTEX_BIT, &uniformBufferInfoList_asteroidPipeline);
662 asteroidPipeline.addStorageDescriptor(VK_SHADER_STAGE_VERTEX_BIT);
663
664 asteroidPipeline.createDescriptorSetLayout();
665 asteroidPipeline.createPipeline("shaders/asteroid-vert.spv", "shaders/asteroid-frag.spv");
666 asteroidPipeline.createDescriptorPool(swapChainImages);
667 asteroidPipeline.createDescriptorSets(swapChainImages);
668
669 laserPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&LaserVertex::pos));
670 laserPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&LaserVertex::texCoord));
671 laserPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&LaserVertex::objIndex));
672
673 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
674 uniformBuffers_laserPipeline, uniformBuffersMemory_laserPipeline, uniformBufferInfoList_laserPipeline);
675
676 laserPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
677 VK_SHADER_STAGE_VERTEX_BIT, &uniformBufferInfoList_laserPipeline);
678 laserPipeline.addStorageDescriptor(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
679 laserPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
680 VK_SHADER_STAGE_FRAGMENT_BIT, &laserTextureImageDescriptor);
681
682 laserPipeline.createDescriptorSetLayout();
683 laserPipeline.createPipeline("shaders/laser-vert.spv", "shaders/laser-frag.spv");
684 laserPipeline.createDescriptorPool(swapChainImages);
685 laserPipeline.createDescriptorSets(swapChainImages);
686
687 explosionPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ExplosionVertex::particleStartVelocity));
688 explosionPipeline.addAttribute(VK_FORMAT_R32_SFLOAT, offset_of(&ExplosionVertex::particleStartTime));
689 explosionPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&ExplosionVertex::objIndex));
690
691 createBufferSet(sizeof(UBO_Explosion), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
692 uniformBuffers_explosionPipeline, uniformBuffersMemory_explosionPipeline, uniformBufferInfoList_explosionPipeline);
693
694 explosionPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
695 VK_SHADER_STAGE_VERTEX_BIT, &uniformBufferInfoList_explosionPipeline);
696 explosionPipeline.addStorageDescriptor(VK_SHADER_STAGE_VERTEX_BIT);
697
698 explosionPipeline.createDescriptorSetLayout();
699 explosionPipeline.createPipeline("shaders/explosion-vert.spv", "shaders/explosion-frag.spv");
700 explosionPipeline.createDescriptorPool(swapChainImages);
701 explosionPipeline.createDescriptorSets(swapChainImages);
702
703 cout << "Created all the graphics pipelines" << endl;
704
705 createCommandBuffers();
706
707 createSyncObjects();
708
709 cout << "Finished init function" << endl;
710}
711
712void VulkanGame::initGraphicsPipelines() {
713 overlayPipeline = GraphicsPipeline_Vulkan<OverlayVertex, void*>(
714 VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
715 { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 4, 6, 0);
716
717 modelPipeline = GraphicsPipeline_Vulkan<ModelVertex, SSBO_ModelObject>(
718 VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
719 { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 16, 24, 10);
720
721 shipPipeline = GraphicsPipeline_Vulkan<ShipVertex, SSBO_ModelObject>(
722 VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
723 { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 138, 138, 10);
724
725 asteroidPipeline = GraphicsPipeline_Vulkan<AsteroidVertex, SSBO_Asteroid>(
726 VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
727 { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 24, 36, 10);
728
729 laserPipeline = GraphicsPipeline_Vulkan<LaserVertex, SSBO_Laser>(
730 VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
731 { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, swapChainImages, 8, 18, 2);
732
733 explosionPipeline = GraphicsPipeline_Vulkan<ExplosionVertex, SSBO_Explosion>(
734 VK_PRIMITIVE_TOPOLOGY_POINT_LIST, physicalDevice, device, renderPass,
735 { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height },
736 swapChainImages, EXPLOSION_PARTICLE_COUNT, EXPLOSION_PARTICLE_COUNT, 2);
737}
738
739// TODO: Maybe changes the name to initScene() or something similar
740void VulkanGame::initMatrices() {
741 this->cam_pos = vec3(0.0f, 0.0f, 2.0f);
742
743 float cam_yaw = 0.0f;
744 float cam_pitch = -50.0f;
745
746 mat4 yaw_mat = rotate(mat4(1.0f), radians(-cam_yaw), vec3(0.0f, 1.0f, 0.0f));
747 mat4 pitch_mat = rotate(mat4(1.0f), radians(-cam_pitch), vec3(1.0f, 0.0f, 0.0f));
748
749 mat4 R_view = pitch_mat * yaw_mat;
750 mat4 T_view = translate(mat4(1.0f), vec3(-this->cam_pos.x, -this->cam_pos.y, -this->cam_pos.z));
751 viewMat = R_view * T_view;
752
753 projMat = perspective(radians(FOV_ANGLE), (float)swapChainExtent.width / (float)swapChainExtent.height, NEAR_CLIP, FAR_CLIP);
754 projMat[1][1] *= -1; // flip the y-axis so that +y is up
755
756 object_VP_mats.view = viewMat;
757 object_VP_mats.proj = projMat;
758
759 ship_VP_mats.view = viewMat;
760 ship_VP_mats.proj = projMat;
761
762 asteroid_VP_mats.view = viewMat;
763 asteroid_VP_mats.proj = projMat;
764
765 laser_VP_mats.view = viewMat;
766 laser_VP_mats.proj = projMat;
767
768 explosion_UBO.view = viewMat;
769 explosion_UBO.proj = projMat;
770}
771
772void VulkanGame::mainLoop() {
773 this->startTime = high_resolution_clock::now();
774 curTime = duration<float, seconds::period>(high_resolution_clock::now() - this->startTime).count();
775
776 this->fpsStartTime = curTime;
777 this->frameCount = 0;
778
779 lastSpawn_asteroid = curTime;
780
781 done = false;
782 while (!done) {
783
784 this->prevTime = curTime;
785 curTime = duration<float, seconds::period>(high_resolution_clock::now() - this->startTime).count();
786 this->elapsedTime = curTime - this->prevTime;
787
788 if (curTime - this->fpsStartTime >= 1.0f) {
789 this->fps = (float)frameCount / (curTime - this->fpsStartTime);
790
791 this->frameCount = 0;
792 this->fpsStartTime = curTime;
793 }
794
795 this->frameCount++;
796
797 gui->processEvents();
798
799 UIEvent uiEvent;
800 while (gui->pollEvent(&uiEvent)) {
801 GameEvent& e = uiEvent.event;
802
803 switch(e.type) {
804 case UI_EVENT_QUIT:
805 cout << "Quit event detected" << endl;
806 done = true;
807 break;
808 case UI_EVENT_WINDOW:
809 cout << "Window event detected" << endl;
810 // Currently unused
811 break;
812 case UI_EVENT_WINDOWRESIZE:
813 cout << "Window resize event detected" << endl;
814 framebufferResized = true;
815 break;
816 case UI_EVENT_KEYDOWN:
817 if (e.key.repeat) {
818 break;
819 }
820
821 if (e.key.keycode == SDL_SCANCODE_ESCAPE) {
822 done = true;
823 } else if (e.key.keycode == SDL_SCANCODE_SPACE) {
824 cout << "Adding a plane" << endl;
825 float zOffset = -2.0f + (0.5f * modelObjects.size());
826
827 SceneObject<ModelVertex, SSBO_ModelObject>& texturedSquare =
828 addObject(modelObjects, modelPipeline,
829 addObjectIndex<ModelVertex>(modelObjects.size(), {
830 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}},
831 {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}},
832 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
833 {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}
834 }), {
835 0, 1, 2, 2, 3, 0
836 }, {
837 mat4(1.0f)
838 }, true);
839
840 texturedSquare.model_base =
841 translate(mat4(1.0f), vec3(0.0f, 0.0f, zOffset));
842 texturedSquare.modified = true;
843 } else if (e.key.keycode == SDL_SCANCODE_Z && leftLaserIdx == -1) {
844 // TODO: When I start actually removing objects from the object vectors,
845 // I will need to update the indices since they might become incorrect
846 // or invalid as objects get moved around
847
848 vec3 offset(shipObjects[0].model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
849
850 addLaser(
851 vec3(-0.21f, -1.19f, 1.76f) + offset,
852 vec3(-0.21f, -1.19f, -3.0f) + offset,
853 LASER_COLOR, 0.03f);
854
855 leftLaserIdx = laserObjects.size() - 1;
856 } else if (e.key.keycode == SDL_SCANCODE_X && rightLaserIdx == -1) {
857 vec3 offset(shipObjects[0].model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
858
859 addLaser(
860 vec3(0.21f, -1.19f, 1.76f) + offset,
861 vec3(0.21f, -1.19f, -3.0f) + offset,
862 LASER_COLOR, 0.03f);
863
864 rightLaserIdx = laserObjects.size() - 1;
865 } else {
866 cout << "Key event detected" << endl;
867 }
868 break;
869 case UI_EVENT_KEYUP:
870 if (e.key.keycode == SDL_SCANCODE_Z && leftLaserIdx != -1) {
871 laserObjects[leftLaserIdx].ssbo.deleted = true;
872 laserObjects[leftLaserIdx].modified = true;
873 leftLaserIdx = -1;
874
875 if (leftLaserEffect != nullptr) {
876 leftLaserEffect->deleted = true;
877 leftLaserEffect = nullptr;
878 }
879 } else if (e.key.keycode == SDL_SCANCODE_X && rightLaserIdx != -1) {
880 laserObjects[rightLaserIdx].ssbo.deleted = true;
881 laserObjects[rightLaserIdx].modified = true;
882 rightLaserIdx = -1;
883
884 if (rightLaserEffect != nullptr) {
885 rightLaserEffect->deleted = true;
886 rightLaserEffect = nullptr;
887 }
888 }
889 break;
890 case UI_EVENT_MOUSEBUTTONDOWN:
891 case UI_EVENT_MOUSEBUTTONUP:
892 case UI_EVENT_MOUSEMOTION:
893 break;
894 case UI_EVENT_UNKNOWN:
895 //cout << "Unknown event type: 0x" << hex << e.unknown.eventType << dec << endl;
896 break;
897 default:
898 cout << "Unhandled UI event: " << e.type << endl;
899 }
900
901 currentScreen->handleEvent(e);
902 }
903
904 // Check which keys are held down
905
906 SceneObject<ShipVertex, SSBO_ModelObject>& ship = shipObjects[0];
907
908 if (gui->keyPressed(SDL_SCANCODE_LEFT)) {
909 float distance = -this->shipSpeed * this->elapsedTime;
910
911 ship.model_transform = translate(mat4(1.0f), vec3(distance, 0.0f, 0.0f))
912 * shipObjects[0].model_transform;
913 ship.modified = true;
914
915 if (leftLaserIdx != -1) {
916 translateLaser(leftLaserIdx, vec3(distance, 0.0f, 0.0f));
917 }
918 if (rightLaserIdx != -1) {
919 translateLaser(rightLaserIdx, vec3(distance, 0.0f, 0.0f));
920 }
921 } else if (gui->keyPressed(SDL_SCANCODE_RIGHT)) {
922 float distance = this->shipSpeed * this->elapsedTime;
923
924 ship.model_transform = translate(mat4(1.0f), vec3(distance, 0.0f, 0.0f))
925 * shipObjects[0].model_transform;
926 ship.modified = true;
927
928 if (leftLaserIdx != -1) {
929 translateLaser(leftLaserIdx, vec3(distance, 0.0f, 0.0f));
930 }
931 if (rightLaserIdx != -1) {
932 translateLaser(rightLaserIdx, vec3(distance, 0.0f, 0.0f));
933 }
934 }
935
936 currentScreen->renderUI();
937
938 // Copy the UI image to a vulkan texture
939 // TODO: I'm pretty sure this severely slows down the pipeline since this functions waits for the copy to be
940 // complete before continuing. See if I can find a more efficient method.
941 VulkanUtils::populateVulkanImageFromSDLTexture(device, physicalDevice, commandPool, uiOverlay, renderer,
942 sdlOverlayImage, graphicsQueue);
943
944 renderScene();
945 }
946}
947
948// TODO: The only updates that need to happen once per Vulkan image are the SSBO ones,
949// which are already handled by updateObject(). Move this code to a different place,
950// where it will run just once per frame
951void VulkanGame::updateScene() {
952 for (SceneObject<ModelVertex, SSBO_ModelObject>& model : this->modelObjects) {
953 model.model_transform =
954 translate(mat4(1.0f), vec3(0.0f, -2.0f, -0.0f)) *
955 rotate(mat4(1.0f), curTime * radians(90.0f), vec3(0.0f, 0.0f, 1.0f));
956 model.modified = true;
957 }
958
959 if (leftLaserIdx != -1) {
960 updateLaserTarget(leftLaserIdx);
961 }
962 if (rightLaserIdx != -1) {
963 updateLaserTarget(rightLaserIdx);
964 }
965
966 for (vector<BaseEffectOverTime*>::iterator it = effects.begin(); it != effects.end(); ) {
967 if ((*it)->deleted) {
968 delete *it;
969 it = effects.erase(it);
970 } else {
971 BaseEffectOverTime* eot = *it;
972
973 eot->applyEffect();
974
975 it++;
976 }
977 }
978
979 for (SceneObject<AsteroidVertex, SSBO_Asteroid>& asteroid : this->asteroidObjects) {
980 if (!asteroid.ssbo.deleted) {
981 vec3 objCenter = vec3(viewMat * vec4(asteroid.center, 1.0f));
982
983 if (asteroid.ssbo.hp <= 0.0f) {
984 asteroid.ssbo.deleted = true;
985
986 // TODO: Optimize this so I don't recalculate the camera rotation every time
987 // TODO: Also, avoid re-declaring cam_pitch
988 float cam_pitch = -50.0f;
989 mat4 pitch_mat = rotate(mat4(1.0f), radians(cam_pitch), vec3(1.0f, 0.0f, 0.0f));
990 mat4 model_mat = translate(mat4(1.0f), asteroid.center) * pitch_mat;
991
992 addExplosion(model_mat, 0.5f, curTime);
993
994 this->score++;
995 } else if ((objCenter.z - asteroid.radius) > -NEAR_CLIP) {
996 asteroid.ssbo.deleted = true;
997 } else {
998 asteroid.model_transform =
999 translate(mat4(1.0f), vec3(0.0f, 0.0f, this->asteroidSpeed * this->elapsedTime)) *
1000 asteroid.model_transform;
1001 }
1002
1003 asteroid.modified = true;
1004 }
1005 }
1006
1007 if (curTime - this->lastSpawn_asteroid > this->spawnRate_asteroid) {
1008 this->lastSpawn_asteroid = curTime;
1009
1010 SceneObject<AsteroidVertex, SSBO_Asteroid>& asteroid = addObject(
1011 asteroidObjects, asteroidPipeline,
1012 addObjectIndex<AsteroidVertex>(asteroidObjects.size(),
1013 addVertexNormals<AsteroidVertex>({
1014
1015 // front
1016 {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1017 {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1018 {{-1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1019 {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1020 {{-1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1021 {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1022
1023 // top
1024 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1025 {{-1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1026 {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1027 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1028 {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1029 {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1030
1031 // bottom
1032 {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1033 {{-1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1034 {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1035 {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1036 {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1037 {{ 1.0f, -1.0f, -1.0}, {0.4f, 0.4f, 0.4f}},
1038
1039 // back
1040 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1041 {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1042 {{-1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1043 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1044 {{ 1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1045 {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1046
1047 // right
1048 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1049 {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1050 {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1051 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1052 {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1053 {{ 1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1054
1055 // left
1056 {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1057 {{-1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1058 {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1059 {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1060 {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1061 {{-1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1062 })), {
1063 0, 1, 2, 3, 4, 5,
1064 6, 7, 8, 9, 10, 11,
1065 12, 13, 14, 15, 16, 17,
1066 18, 19, 20, 21, 22, 23,
1067 24, 25, 26, 27, 28, 29,
1068 30, 31, 32, 33, 34, 35,
1069 }, {
1070 mat4(1.0f),
1071 10.0f,
1072 false
1073 }, true);
1074
1075 // This accounts for the scaling in model_base.
1076 // Dividing by 8 instead of 10 since the bounding radius algorithm
1077 // under-calculates the true value.
1078 // TODO: Figure out the best way to take scaling into account when calculating the radius
1079 // Keep in mind that the main complicating factor is the currently poor radius calculation
1080 asteroid.radius /= 8.0f;
1081
1082 asteroid.model_base =
1083 translate(mat4(1.0f), vec3(getRandomNum(-1.3f, 1.3f), -1.2f, getRandomNum(-5.5f, -4.5f))) *
1084 rotate(mat4(1.0f), radians(60.0f), vec3(1.0f, 1.0f, -1.0f)) *
1085 scale(mat4(1.0f), vec3(0.1f, 0.1f, 0.1f));
1086 asteroid.modified = true;
1087 }
1088
1089 for (SceneObject<ExplosionVertex, SSBO_Explosion>& explosion : this->explosionObjects) {
1090 if (!explosion.ssbo.deleted) {
1091 if (curTime > (explosion.ssbo.explosionStartTime + explosion.ssbo.explosionDuration)) {
1092 explosion.ssbo.deleted = true;
1093 explosion.modified = true;
1094 }
1095 }
1096 }
1097
1098 for (size_t i = 0; i < shipObjects.size(); i++) {
1099 if (shipObjects[i].modified) {
1100 updateObject(shipObjects, shipPipeline, i);
1101 }
1102 }
1103
1104 for (size_t i = 0; i < modelObjects.size(); i++) {
1105 if (modelObjects[i].modified) {
1106 updateObject(modelObjects, modelPipeline, i);
1107 }
1108 }
1109
1110 for (size_t i = 0; i < asteroidObjects.size(); i++) {
1111 if (asteroidObjects[i].modified) {
1112 updateObject(asteroidObjects, asteroidPipeline, i);
1113 }
1114 }
1115
1116 for (size_t i = 0; i < laserObjects.size(); i++) {
1117 if (laserObjects[i].modified) {
1118 updateObject(laserObjects, laserPipeline, i);
1119 }
1120 }
1121
1122 for (size_t i = 0; i < explosionObjects.size(); i++) {
1123 if (explosionObjects[i].modified) {
1124 updateObject(explosionObjects, explosionPipeline, i);
1125 }
1126 }
1127
1128 explosion_UBO.cur_time = curTime;
1129
1130 VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_modelPipeline[imageIndex], 0, object_VP_mats);
1131
1132 VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_shipPipeline[imageIndex], 0, ship_VP_mats);
1133
1134 VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_asteroidPipeline[imageIndex], 0, asteroid_VP_mats);
1135
1136 VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_laserPipeline[imageIndex], 0, laser_VP_mats);
1137
1138 VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_explosionPipeline[imageIndex], 0, explosion_UBO);
1139}
1140
1141// TODO: Maybe move all/most of this to the base Screen class
1142void VulkanGame::renderScene() {
1143 // TODO: Recreate the swap chain here if the user went to a new screen
1144
1145 VkResult result = vkAcquireNextImageKHR(device, swapChain, numeric_limits<uint64_t>::max(),
1146 imageAcquiredSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
1147
1148 if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1149 recreateSwapChain();
1150 return;
1151 } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
1152 throw runtime_error("failed to acquire swap chain image!");
1153 }
1154
1155 if (vkWaitForFences(device, 1, &inFlightFences[imageIndex], VK_TRUE, numeric_limits<uint64_t>::max()) != VK_SUCCESS) {
1156 throw runtime_error("failed waiting for fence!");
1157 }
1158 if (vkResetFences(device, 1, &inFlightFences[imageIndex]) != VK_SUCCESS) {
1159 throw runtime_error("failed to reset fence!");
1160 }
1161
1162 updateScene();
1163
1164 VkSemaphore waitSemaphores[] = { imageAcquiredSemaphores[currentFrame] };
1165 VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
1166 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
1167
1168 VkSubmitInfo submitInfo = {};
1169 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1170 submitInfo.waitSemaphoreCount = 1;
1171 submitInfo.pWaitSemaphores = waitSemaphores;
1172 submitInfo.pWaitDstStageMask = waitStages;
1173 submitInfo.commandBufferCount = 1;
1174 submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
1175 submitInfo.signalSemaphoreCount = 1;
1176 submitInfo.pSignalSemaphores = signalSemaphores;
1177
1178 if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[imageIndex]) != VK_SUCCESS) {
1179 throw runtime_error("failed to submit draw command buffer!");
1180 }
1181
1182 VkPresentInfoKHR presentInfo = {};
1183 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
1184 presentInfo.waitSemaphoreCount = 1;
1185 presentInfo.pWaitSemaphores = signalSemaphores;
1186 presentInfo.swapchainCount = 1;
1187 presentInfo.pSwapchains = &swapChain;
1188 presentInfo.pImageIndices = &imageIndex;
1189 presentInfo.pResults = nullptr;
1190
1191 result = vkQueuePresentKHR(presentQueue, &presentInfo);
1192
1193 if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) {
1194 framebufferResized = false;
1195 recreateSwapChain();
1196 } else if (result != VK_SUCCESS) {
1197 throw runtime_error("failed to present swap chain image!");
1198 }
1199
1200 currentFrame = (currentFrame + 1) % swapChainImageCount;
1201}
1202
1203void VulkanGame::cleanup() {
1204 if (vkDeviceWaitIdle(device) != VK_SUCCESS) {
1205 throw runtime_error("failed to wait for device!");
1206 }
1207
1208 ImGui_ImplVulkan_Shutdown();
1209 ImGui_ImplSDL2_Shutdown();
1210 ImGui::DestroyContext();
1211
1212 // TODO: Probably move this into cleanupSwapChain once I finish the integration
1213 destroyImguiDescriptorPool();
1214
1215 cleanupSwapChain();
1216
1217 VulkanUtils::destroyVulkanImage(device, sdlOverlayImage);
1218 VulkanUtils::destroyVulkanImage(device, floorTextureImage);
1219 VulkanUtils::destroyVulkanImage(device, laserTextureImage);
1220
1221 vkDestroySampler(device, textureSampler, nullptr);
1222
1223 modelPipeline.cleanupBuffers();
1224 overlayPipeline.cleanupBuffers();
1225 shipPipeline.cleanupBuffers();
1226 asteroidPipeline.cleanupBuffers();
1227 laserPipeline.cleanupBuffers();
1228 explosionPipeline.cleanupBuffers();
1229
1230 vkDestroyCommandPool(device, resourceCommandPool, nullptr);
1231 vkDestroyCommandPool(device, commandPool, nullptr);
1232
1233 vkDestroyDevice(device, nullptr);
1234 vkDestroySurfaceKHR(instance, surface, nullptr);
1235
1236 if (ENABLE_VALIDATION_LAYERS) {
1237 VulkanUtils::destroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
1238 }
1239
1240 vkDestroyInstance(instance, nullptr);
1241
1242 delete screens[SCREEN_MAIN];
1243 delete screens[SCREEN_GAME];
1244
1245 if (lazyFont != nullptr) {
1246 TTF_CloseFont(lazyFont);
1247 lazyFont = nullptr;
1248 }
1249
1250 if (proggyFont != nullptr) {
1251 TTF_CloseFont(proggyFont);
1252 proggyFont = nullptr;
1253 }
1254
1255 if (uiOverlay != nullptr) {
1256 SDL_DestroyTexture(uiOverlay);
1257 uiOverlay = nullptr;
1258 }
1259
1260 SDL_DestroyRenderer(renderer);
1261 renderer = nullptr;
1262
1263 gui->destroyWindow();
1264 gui->shutdown();
1265 delete gui;
1266}
1267
1268void VulkanGame::createVulkanInstance(const vector<const char*>& validationLayers) {
1269 if (ENABLE_VALIDATION_LAYERS && !VulkanUtils::checkValidationLayerSupport(validationLayers)) {
1270 throw runtime_error("validation layers requested, but not available!");
1271 }
1272
1273 VkApplicationInfo appInfo = {};
1274 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
1275 appInfo.pApplicationName = "Vulkan Game";
1276 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
1277 appInfo.pEngineName = "No Engine";
1278 appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
1279 appInfo.apiVersion = VK_API_VERSION_1_0;
1280
1281 VkInstanceCreateInfo createInfo = {};
1282 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
1283 createInfo.pApplicationInfo = &appInfo;
1284
1285 vector<const char*> extensions = gui->getRequiredExtensions();
1286 if (ENABLE_VALIDATION_LAYERS) {
1287 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
1288 }
1289
1290 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
1291 createInfo.ppEnabledExtensionNames = extensions.data();
1292
1293 cout << endl << "Extensions:" << endl;
1294 for (const char* extensionName : extensions) {
1295 cout << extensionName << endl;
1296 }
1297 cout << endl;
1298
1299 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
1300 if (ENABLE_VALIDATION_LAYERS) {
1301 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
1302 createInfo.ppEnabledLayerNames = validationLayers.data();
1303
1304 populateDebugMessengerCreateInfo(debugCreateInfo);
1305 createInfo.pNext = &debugCreateInfo;
1306 } else {
1307 createInfo.enabledLayerCount = 0;
1308
1309 createInfo.pNext = nullptr;
1310 }
1311
1312 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
1313 throw runtime_error("failed to create instance!");
1314 }
1315}
1316
1317void VulkanGame::setupDebugMessenger() {
1318 if (!ENABLE_VALIDATION_LAYERS) {
1319 return;
1320 }
1321
1322 VkDebugUtilsMessengerCreateInfoEXT createInfo;
1323 populateDebugMessengerCreateInfo(createInfo);
1324
1325 if (VulkanUtils::createDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
1326 throw runtime_error("failed to set up debug messenger!");
1327 }
1328}
1329
1330void VulkanGame::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
1331 createInfo = {};
1332 createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
1333 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;
1334 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;
1335 createInfo.pfnUserCallback = debugCallback;
1336}
1337
1338VKAPI_ATTR VkBool32 VKAPI_CALL VulkanGame::debugCallback(
1339 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
1340 VkDebugUtilsMessageTypeFlagsEXT messageType,
1341 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
1342 void* pUserData) {
1343 cerr << "validation layer: " << pCallbackData->pMessage << endl;
1344
1345 return VK_FALSE;
1346}
1347
1348void VulkanGame::createVulkanSurface() {
1349 if (gui->createVulkanSurface(instance, &surface) == RTWO_ERROR) {
1350 throw runtime_error("failed to create window surface!");
1351 }
1352}
1353
1354void VulkanGame::pickPhysicalDevice(const vector<const char*>& deviceExtensions) {
1355 uint32_t deviceCount = 0;
1356 // TODO: Check VkResult
1357 vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
1358
1359 if (deviceCount == 0) {
1360 throw runtime_error("failed to find GPUs with Vulkan support!");
1361 }
1362
1363 vector<VkPhysicalDevice> devices(deviceCount);
1364 // TODO: Check VkResult
1365 vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
1366
1367 cout << endl << "Graphics cards:" << endl;
1368 for (const VkPhysicalDevice& device : devices) {
1369 if (isDeviceSuitable(device, deviceExtensions)) {
1370 physicalDevice = device;
1371 break;
1372 }
1373 }
1374 cout << endl;
1375
1376 if (physicalDevice == VK_NULL_HANDLE) {
1377 throw runtime_error("failed to find a suitable GPU!");
1378 }
1379}
1380
1381bool VulkanGame::isDeviceSuitable(VkPhysicalDevice physicalDevice, const vector<const char*>& deviceExtensions) {
1382 VkPhysicalDeviceProperties deviceProperties;
1383 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
1384
1385 cout << "Device: " << deviceProperties.deviceName << endl;
1386
1387 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
1388 bool extensionsSupported = VulkanUtils::checkDeviceExtensionSupport(physicalDevice, deviceExtensions);
1389 bool swapChainAdequate = false;
1390
1391 if (extensionsSupported) {
1392 vector<VkSurfaceFormatKHR> formats = VulkanUtils::querySwapChainFormats(physicalDevice, surface);
1393 vector<VkPresentModeKHR> presentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, surface);
1394
1395 swapChainAdequate = !formats.empty() && !presentModes.empty();
1396 }
1397
1398 VkPhysicalDeviceFeatures supportedFeatures;
1399 vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
1400
1401 return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
1402}
1403
1404void VulkanGame::createLogicalDevice(const vector<const char*>& validationLayers,
1405 const vector<const char*>& deviceExtensions) {
1406 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
1407
1408 if (!indices.isComplete()) {
1409 throw runtime_error("failed to find required queue families!");
1410 }
1411
1412 // TODO: Using separate graphics and present queues currently works, but I should verify that I'm
1413 // using them correctly to get the most benefit out of separate queues
1414
1415 vector<VkDeviceQueueCreateInfo> queueCreateInfoList;
1416 set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
1417
1418 float queuePriority = 1.0f;
1419 for (uint32_t queueFamily : uniqueQueueFamilies) {
1420 VkDeviceQueueCreateInfo queueCreateInfo = {};
1421 queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
1422 queueCreateInfo.queueCount = 1;
1423 queueCreateInfo.queueFamilyIndex = queueFamily;
1424 queueCreateInfo.pQueuePriorities = &queuePriority;
1425
1426 queueCreateInfoList.push_back(queueCreateInfo);
1427 }
1428
1429 VkPhysicalDeviceFeatures deviceFeatures = {};
1430 deviceFeatures.samplerAnisotropy = VK_TRUE;
1431
1432 VkDeviceCreateInfo createInfo = {};
1433 createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
1434
1435 createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfoList.size());
1436 createInfo.pQueueCreateInfos = queueCreateInfoList.data();
1437
1438 createInfo.pEnabledFeatures = &deviceFeatures;
1439
1440 createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
1441 createInfo.ppEnabledExtensionNames = deviceExtensions.data();
1442
1443 // These fields are ignored by up-to-date Vulkan implementations,
1444 // but it's a good idea to set them for backwards compatibility
1445 if (ENABLE_VALIDATION_LAYERS) {
1446 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
1447 createInfo.ppEnabledLayerNames = validationLayers.data();
1448 } else {
1449 createInfo.enabledLayerCount = 0;
1450 }
1451
1452 if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
1453 throw runtime_error("failed to create logical device!");
1454 }
1455
1456 vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
1457 vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
1458}
1459
1460void VulkanGame::chooseSwapChainProperties() {
1461 vector<VkSurfaceFormatKHR> availableFormats = VulkanUtils::querySwapChainFormats(physicalDevice, surface);
1462 vector<VkPresentModeKHR> availablePresentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, surface);
1463
1464 swapChainSurfaceFormat = VulkanUtils::chooseSwapSurfaceFormat(availableFormats,
1465 { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM },
1466 VK_COLOR_SPACE_SRGB_NONLINEAR_KHR);
1467
1468 vector<VkPresentModeKHR> presentModes{
1469 VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR
1470 };
1471 //vector<VkPresentModeKHR> presentModes{ VK_PRESENT_MODE_FIFO_KHR };
1472
1473 swapChainPresentMode = VulkanUtils::chooseSwapPresentMode(availablePresentModes, presentModes);
1474
1475 cout << "[vulkan] Selected PresentMode = " << swapChainPresentMode << endl;
1476
1477 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, surface);
1478
1479 if (swapChainPresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
1480 swapChainMinImageCount = 3;
1481 } else if (swapChainPresentMode == VK_PRESENT_MODE_FIFO_KHR || swapChainPresentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) {
1482 swapChainMinImageCount = 2;
1483 } else if (swapChainPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
1484 swapChainMinImageCount = 1;
1485 } else {
1486 throw runtime_error("unexpected present mode!");
1487 }
1488
1489 if (swapChainMinImageCount < capabilities.minImageCount) {
1490 swapChainMinImageCount = capabilities.minImageCount;
1491 } else if (capabilities.maxImageCount != 0 && swapChainMinImageCount > capabilities.maxImageCount) {
1492 swapChainMinImageCount = capabilities.maxImageCount;
1493 }
1494}
1495
1496void VulkanGame::createSwapChain() {
1497 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, surface);
1498
1499 swapChainExtent = VulkanUtils::chooseSwapExtent(capabilities, gui->getWindowWidth(), gui->getWindowHeight());
1500
1501 VkSwapchainCreateInfoKHR createInfo = {};
1502 createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
1503 createInfo.surface = surface;
1504 createInfo.minImageCount = swapChainMinImageCount;
1505 createInfo.imageFormat = swapChainSurfaceFormat.format;
1506 createInfo.imageColorSpace = swapChainSurfaceFormat.colorSpace;
1507 createInfo.imageExtent = swapChainExtent;
1508 createInfo.imageArrayLayers = 1;
1509 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
1510
1511 // TODO: Maybe save this result so I don't have to recalculate it every time
1512 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
1513 uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
1514
1515 if (indices.graphicsFamily != indices.presentFamily) {
1516 createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
1517 createInfo.queueFamilyIndexCount = 2;
1518 createInfo.pQueueFamilyIndices = queueFamilyIndices;
1519 } else {
1520 createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
1521 createInfo.queueFamilyIndexCount = 0;
1522 createInfo.pQueueFamilyIndices = nullptr;
1523 }
1524
1525 createInfo.preTransform = capabilities.currentTransform;
1526 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
1527 createInfo.presentMode = swapChainPresentMode;
1528 createInfo.clipped = VK_TRUE;
1529 createInfo.oldSwapchain = VK_NULL_HANDLE;
1530
1531 if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
1532 throw runtime_error("failed to create swap chain!");
1533 }
1534
1535 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, nullptr) != VK_SUCCESS) {
1536 throw runtime_error("failed to get swap chain image count!");
1537 }
1538
1539 swapChainImages.resize(swapChainImageCount);
1540 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, swapChainImages.data()) != VK_SUCCESS) {
1541 throw runtime_error("failed to get swap chain images!");
1542 }
1543}
1544
1545void VulkanGame::createImageViews() {
1546 swapChainImageViews.resize(swapChainImageCount);
1547
1548 for (size_t i = 0; i < swapChainImageCount; i++) {
1549 swapChainImageViews[i] = VulkanUtils::createImageView(device, swapChainImages[i], swapChainSurfaceFormat.format,
1550 VK_IMAGE_ASPECT_COLOR_BIT);
1551 }
1552}
1553
1554void VulkanGame::createRenderPass() {
1555 VkAttachmentDescription colorAttachment = {};
1556 colorAttachment.format = swapChainSurfaceFormat.format;
1557 colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
1558 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1559 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
1560 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1561 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1562 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1563 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
1564
1565 VkAttachmentReference colorAttachmentRef = {};
1566 colorAttachmentRef.attachment = 0;
1567 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
1568
1569 VkAttachmentDescription depthAttachment = {};
1570 depthAttachment.format = findDepthFormat();
1571 depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
1572 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1573 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1574 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1575 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1576 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1577 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1578
1579 VkAttachmentReference depthAttachmentRef = {};
1580 depthAttachmentRef.attachment = 1;
1581 depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1582
1583 VkSubpassDescription subpass = {};
1584 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
1585 subpass.colorAttachmentCount = 1;
1586 subpass.pColorAttachments = &colorAttachmentRef;
1587 subpass.pDepthStencilAttachment = &depthAttachmentRef;
1588
1589 VkSubpassDependency dependency = {};
1590 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
1591 dependency.dstSubpass = 0;
1592 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1593 dependency.srcAccessMask = 0;
1594 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1595 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
1596
1597 array<VkAttachmentDescription, 2> attachments = { colorAttachment, depthAttachment };
1598 VkRenderPassCreateInfo renderPassInfo = {};
1599 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
1600 renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
1601 renderPassInfo.pAttachments = attachments.data();
1602 renderPassInfo.subpassCount = 1;
1603 renderPassInfo.pSubpasses = &subpass;
1604 renderPassInfo.dependencyCount = 1;
1605 renderPassInfo.pDependencies = &dependency;
1606
1607 if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
1608 throw runtime_error("failed to create render pass!");
1609 }
1610}
1611
1612VkFormat VulkanGame::findDepthFormat() {
1613 return VulkanUtils::findSupportedFormat(
1614 physicalDevice,
1615 { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT },
1616 VK_IMAGE_TILING_OPTIMAL,
1617 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
1618 );
1619}
1620
1621void VulkanGame::createResourceCommandPool() {
1622 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
1623
1624 VkCommandPoolCreateInfo poolInfo = {};
1625 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
1626 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
1627 poolInfo.flags = 0;
1628
1629 if (vkCreateCommandPool(device, &poolInfo, nullptr, &resourceCommandPool) != VK_SUCCESS) {
1630 throw runtime_error("failed to create resource command pool!");
1631 }
1632}
1633
1634void VulkanGame::createCommandPool() {
1635 QueueFamilyIndices queueFamilyIndices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
1636
1637 VkCommandPoolCreateInfo poolInfo = {};
1638 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
1639 poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();
1640 poolInfo.flags = 0;
1641
1642 if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
1643 throw runtime_error("failed to create graphics command pool!");
1644 }
1645}
1646
1647void VulkanGame::createImageResources() {
1648 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
1649 depthImage, graphicsQueue);
1650
1651 createTextureSampler();
1652
1653 // TODO: Move all images/textures somewhere into the assets folder
1654
1655 VulkanUtils::createVulkanImageFromSDLTexture(device, physicalDevice, uiOverlay, sdlOverlayImage);
1656
1657 sdlOverlayImageDescriptor = {};
1658 sdlOverlayImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1659 sdlOverlayImageDescriptor.imageView = sdlOverlayImage.imageView;
1660 sdlOverlayImageDescriptor.sampler = textureSampler;
1661
1662 VulkanUtils::createVulkanImageFromFile(device, physicalDevice, resourceCommandPool, "textures/texture.jpg",
1663 floorTextureImage, graphicsQueue);
1664
1665 floorTextureImageDescriptor = {};
1666 floorTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1667 floorTextureImageDescriptor.imageView = floorTextureImage.imageView;
1668 floorTextureImageDescriptor.sampler = textureSampler;
1669
1670 VulkanUtils::createVulkanImageFromFile(device, physicalDevice, resourceCommandPool, "textures/laser.png",
1671 laserTextureImage, graphicsQueue);
1672
1673 laserTextureImageDescriptor = {};
1674 laserTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1675 laserTextureImageDescriptor.imageView = laserTextureImage.imageView;
1676 laserTextureImageDescriptor.sampler = textureSampler;
1677}
1678
1679void VulkanGame::createTextureSampler() {
1680 VkSamplerCreateInfo samplerInfo = {};
1681 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1682 samplerInfo.magFilter = VK_FILTER_LINEAR;
1683 samplerInfo.minFilter = VK_FILTER_LINEAR;
1684
1685 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1686 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1687 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1688
1689 samplerInfo.anisotropyEnable = VK_TRUE;
1690 samplerInfo.maxAnisotropy = 16;
1691 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
1692 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1693 samplerInfo.compareEnable = VK_FALSE;
1694 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1695 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
1696 samplerInfo.mipLodBias = 0.0f;
1697 samplerInfo.minLod = 0.0f;
1698 samplerInfo.maxLod = 0.0f;
1699
1700 if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) {
1701 throw runtime_error("failed to create texture sampler!");
1702 }
1703}
1704
1705void VulkanGame::createFramebuffers() {
1706 swapChainFramebuffers.resize(swapChainImageCount);
1707
1708 VkFramebufferCreateInfo framebufferInfo = {};
1709 framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
1710 framebufferInfo.renderPass = renderPass;
1711 framebufferInfo.width = swapChainExtent.width;
1712 framebufferInfo.height = swapChainExtent.height;
1713 framebufferInfo.layers = 1;
1714
1715 for (uint32_t i = 0; i < swapChainImageCount; i++) {
1716 array<VkImageView, 2> attachments = {
1717 swapChainImageViews[i],
1718 depthImage.imageView
1719 };
1720
1721 framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
1722 framebufferInfo.pAttachments = attachments.data();
1723
1724 if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
1725 throw runtime_error("failed to create framebuffer!");
1726 }
1727 }
1728}
1729
1730void VulkanGame::createCommandBuffers() {
1731 commandBuffers.resize(swapChainImageCount);
1732
1733 VkCommandBufferAllocateInfo allocInfo = {};
1734 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1735 allocInfo.commandPool = commandPool;
1736 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1737 allocInfo.commandBufferCount = (uint32_t) commandBuffers.size();
1738
1739 if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) {
1740 throw runtime_error("failed to allocate command buffers!");
1741 }
1742
1743 for (size_t i = 0; i < commandBuffers.size(); i++) {
1744 VkCommandBufferBeginInfo beginInfo = {};
1745 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1746 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
1747 beginInfo.pInheritanceInfo = nullptr;
1748
1749 if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) {
1750 throw runtime_error("failed to begin recording command buffer!");
1751 }
1752
1753 VkRenderPassBeginInfo renderPassInfo = {};
1754 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
1755 renderPassInfo.renderPass = renderPass;
1756 renderPassInfo.framebuffer = swapChainFramebuffers[i];
1757 renderPassInfo.renderArea.offset = { 0, 0 };
1758 renderPassInfo.renderArea.extent = swapChainExtent;
1759
1760 array<VkClearValue, 2> clearValues = {};
1761 clearValues[0].color = {{ 0.0f, 0.0f, 0.0f, 1.0f }};
1762 clearValues[1].depthStencil = { 1.0f, 0 };
1763
1764 renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
1765 renderPassInfo.pClearValues = clearValues.data();
1766
1767 vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
1768
1769 currentScreen->createRenderCommands(commandBuffers[i], i);
1770
1771 /**********************************************************/
1772
1773 ImGui_ImplVulkan_NewFrame();
1774 ImGui_ImplSDL2_NewFrame(this->window);
1775 ImGui::NewFrame();
1776
1777 {
1778 ImGui::SetNextWindowSize(ImVec2(250, 35), ImGuiCond_Once);
1779 ImGui::SetNextWindowPos(ImVec2(380, 10), ImGuiCond_Once);
1780 ImGui::Begin("WndMenubar", NULL,
1781 ImGuiWindowFlags_NoTitleBar |
1782 ImGuiWindowFlags_NoResize |
1783 ImGuiWindowFlags_NoMove);
1784 ImGui::InvisibleButton("", ImVec2(155, 18));
1785 ImGui::SameLine();
1786 if (ImGui::Button("Main Menu")) {
1787 cout << "Clicked on the main button" << endl;
1788 //events.push(Event::GO_TO_MAIN_MENU);
1789 }
1790 ImGui::End();
1791 }
1792
1793 ImGui::Render();
1794 ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commandBuffers[i]);
1795
1796 /**********************************************************/
1797
1798 vkCmdEndRenderPass(commandBuffers[i]);
1799
1800 if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) {
1801 throw runtime_error("failed to record command buffer!");
1802 }
1803 }
1804}
1805
1806void VulkanGame::createSyncObjects() {
1807 imageAcquiredSemaphores.resize(swapChainImageCount);
1808 renderCompleteSemaphores.resize(swapChainImageCount);
1809 inFlightFences.resize(swapChainImageCount);
1810
1811 VkSemaphoreCreateInfo semaphoreInfo = {};
1812 semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
1813
1814 VkFenceCreateInfo fenceInfo = {};
1815 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
1816 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
1817
1818 for (size_t i = 0; i < swapChainImageCount; i++) {
1819 if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAcquiredSemaphores[i]) != VK_SUCCESS) {
1820 throw runtime_error("failed to create image acquired sempahore for a frame!");
1821 }
1822
1823 if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderCompleteSemaphores[i]) != VK_SUCCESS) {
1824 throw runtime_error("failed to create render complete sempahore for a frame!");
1825 }
1826
1827 if (vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) {
1828 throw runtime_error("failed to create fence for a frame!");
1829 }
1830 }
1831}
1832
1833void VulkanGame::createImguiDescriptorPool() {
1834 vector<VkDescriptorPoolSize> pool_sizes{
1835 { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
1836 { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
1837 { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
1838 { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 },
1839 { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 },
1840 { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 },
1841 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 },
1842 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 },
1843 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 },
1844 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 },
1845 { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 }
1846 };
1847
1848 VkDescriptorPoolCreateInfo pool_info = {};
1849 pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1850 pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
1851 pool_info.maxSets = 1000 * pool_sizes.size();
1852 pool_info.poolSizeCount = static_cast<uint32_t>(pool_sizes.size());
1853 pool_info.pPoolSizes = pool_sizes.data();
1854 if (vkCreateDescriptorPool(device, &pool_info, nullptr, &imguiDescriptorPool) != VK_SUCCESS) {
1855 throw runtime_error("failed to create IMGUI descriptor pool!");
1856 }
1857}
1858
1859void VulkanGame::destroyImguiDescriptorPool() {
1860 vkDestroyDescriptorPool(device, imguiDescriptorPool, nullptr);
1861}
1862
1863void VulkanGame::addLaser(vec3 start, vec3 end, vec3 color, float width) {
1864 vec3 ray = end - start;
1865 float length = glm::length(ray);
1866
1867 SceneObject<LaserVertex, SSBO_Laser>& laser = addObject(
1868 laserObjects, laserPipeline,
1869 addObjectIndex<LaserVertex>(laserObjects.size(), {
1870 {{ width / 2, 0.0f, -width / 2 }, {1.0f, 0.5f }},
1871 {{-width / 2, 0.0f, -width / 2 }, {0.0f, 0.5f }},
1872 {{-width / 2, 0.0f, 0.0f }, {0.0f, 0.0f }},
1873 {{ width / 2, 0.0f, 0.0f }, {1.0f, 0.0f }},
1874 {{ width / 2, 0.0f, -length + width / 2}, {1.0f, 0.51f}},
1875 {{-width / 2, 0.0f, -length + width / 2}, {0.0f, 0.51f}},
1876 {{ width / 2, 0.0f, -length, }, {1.0f, 1.0f }},
1877 {{-width / 2, 0.0f, -length }, {0.0f, 1.0f }}
1878 }), {
1879 0, 1, 2, 0, 2, 3,
1880 4, 5, 1, 4, 1, 0,
1881 6, 7, 5, 6, 5, 4
1882 }, {
1883 mat4(1.0f),
1884 color,
1885 false
1886 }, true);
1887
1888 float xAxisRotation = asin(ray.y / length);
1889 float yAxisRotation = atan2(-ray.x, -ray.z);
1890
1891 vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
1892 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) *
1893 vec4(0.0f, 1.0f, 0.0f, 1.0f));
1894
1895 // To project point P onto line AB:
1896 // projection = A + dot(AP,AB) / dot(AB,AB) * AB
1897 vec3 projOnLaser = start + glm::dot(this->cam_pos - start, ray) / (length * length) * ray;
1898 vec3 laserToCam = this->cam_pos - projOnLaser;
1899
1900 float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam));
1901
1902 laser.targetAsteroid = nullptr;
1903
1904 laser.model_base =
1905 rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f));
1906
1907 laser.model_transform =
1908 translate(mat4(1.0f), start) *
1909 rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
1910 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f));
1911
1912 laser.modified = true;
1913}
1914
1915void VulkanGame::translateLaser(size_t index, const vec3& translation) {
1916 SceneObject<LaserVertex, SSBO_Laser>& laser = this->laserObjects[index];
1917
1918 // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created,
1919 // and then re-used here
1920
1921 vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1922 vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f));
1923
1924 vec3 ray = end - start;
1925 float length = glm::length(ray);
1926
1927 float xAxisRotation = asin(ray.y / length);
1928 float yAxisRotation = atan2(-ray.x, -ray.z);
1929
1930 vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
1931 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) *
1932 vec4(0.0f, 1.0f, 0.0f, 1.0f));
1933
1934 // To project point P onto line AB:
1935 // projection = A + dot(AP,AB) / dot(AB,AB) * AB
1936 vec3 projOnLaser = start + glm::dot(cam_pos - start, ray) / (length*length) * ray;
1937 vec3 laserToCam = cam_pos - projOnLaser;
1938
1939 float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam));
1940
1941 laser.model_base = rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f));
1942 laser.model_transform = translate(mat4(1.0f), translation) * laser.model_transform;
1943
1944 laser.modified = true;
1945}
1946
1947void VulkanGame::updateLaserTarget(size_t index) {
1948 SceneObject<LaserVertex, SSBO_Laser>& laser = this->laserObjects[index];
1949
1950 // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created,
1951 // and then re-used here
1952
1953 vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1954 vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f));
1955
1956 vec3 intersection(0.0f), closestIntersection(0.0f);
1957 SceneObject<AsteroidVertex, SSBO_Asteroid>* closestAsteroid = nullptr;
1958 unsigned int closestAsteroidIndex = -1;
1959
1960 for (int i = 0; i < this->asteroidObjects.size(); i++) {
1961 if (!this->asteroidObjects[i].ssbo.deleted &&
1962 this->getLaserAndAsteroidIntersection(this->asteroidObjects[i], start, end, intersection)) {
1963 // TODO: Implement a more generic algorithm for testing the closest object by getting the distance between the points
1964 // TODO: Also check which intersection is close to the start of the laser. This would make the algorithm work
1965 // regardless of which way -Z is pointing
1966 if (closestAsteroid == nullptr || intersection.z > closestIntersection.z) {
1967 // TODO: At this point, find the real intersection of the laser with one of the asteroid's sides
1968 closestAsteroid = &asteroidObjects[i];
1969 closestIntersection = intersection;
1970 closestAsteroidIndex = i;
1971 }
1972 }
1973 }
1974
1975 float width = laser.vertices[0].pos.x - laser.vertices[1].pos.x;
1976
1977 if (laser.targetAsteroid != closestAsteroid) {
1978 if (laser.targetAsteroid != nullptr) {
1979 if (index == leftLaserIdx && leftLaserEffect != nullptr) {
1980 leftLaserEffect->deleted = true;
1981 } else if (index == rightLaserIdx && rightLaserEffect != nullptr) {
1982 rightLaserEffect->deleted = true;
1983 }
1984 }
1985
1986 EffectOverTime<AsteroidVertex, SSBO_Asteroid>* eot = nullptr;
1987
1988 if (closestAsteroid != nullptr) {
1989 // TODO: Use some sort of smart pointer instead
1990 eot = new EffectOverTime<AsteroidVertex, SSBO_Asteroid>(asteroidPipeline, asteroidObjects, closestAsteroidIndex,
1991 offset_of(&SSBO_Asteroid::hp), -20.0f);
1992 effects.push_back(eot);
1993 }
1994
1995 if (index == leftLaserIdx) {
1996 leftLaserEffect = eot;
1997 } else if (index == rightLaserIdx) {
1998 rightLaserEffect = eot;
1999 }
2000
2001 laser.targetAsteroid = closestAsteroid;
2002 }
2003
2004 // Make the laser go past the end of the screen if it doesn't hit anything
2005 float length = closestAsteroid == nullptr ? 5.24f : glm::length(closestIntersection - start);
2006
2007 laser.vertices[4].pos.z = -length + width / 2;
2008 laser.vertices[5].pos.z = -length + width / 2;
2009 laser.vertices[6].pos.z = -length;
2010 laser.vertices[7].pos.z = -length;
2011
2012 // TODO: Consider if I want to set a flag and do this update in in updateScene() instead
2013 updateObjectVertices(this->laserPipeline, laser, index);
2014}
2015
2016// TODO: Determine if I should pass start and end by reference or value since they don't get changed
2017// Probably use const reference
2018bool VulkanGame::getLaserAndAsteroidIntersection(SceneObject<AsteroidVertex, SSBO_Asteroid>& asteroid,
2019 vec3& start, vec3& end, vec3& intersection) {
2020 /*
2021 ### LINE EQUATIONS ###
2022 x = x1 + u * (x2 - x1)
2023 y = y1 + u * (y2 - y1)
2024 z = z1 + u * (z2 - z1)
2025
2026 ### SPHERE EQUATION ###
2027 (x - x3)^2 + (y - y3)^2 + (z - z3)^2 = r^2
2028
2029 ### QUADRATIC EQUATION TO SOLVE ###
2030 a*u^2 + b*u + c = 0
2031 WHERE THE CONSTANTS ARE
2032 a = (x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2
2033 b = 2*( (x2 - x1)*(x1 - x3) + (y2 - y1)*(y1 - y3) + (z2 - z1)*(z1 - z3) )
2034 c = x3^2 + y3^2 + z3^2 + x1^2 + y1^2 + z1^2 - 2(x3*x1 + y3*y1 + z3*z1) - r^2
2035
2036 u = (-b +- sqrt(b^2 - 4*a*c)) / 2a
2037
2038 If the value under the root is >= 0, we got an intersection
2039 If the value > 0, there are two solutions. Take the one closer to 0, since that's the
2040 one closer to the laser start point
2041 */
2042
2043 vec3& center = asteroid.center;
2044
2045 float a = pow(end.x - start.x, 2) + pow(end.y - start.y, 2) + pow(end.z - start.z, 2);
2046 float b = 2 * ((start.x - end.x) * (start.x - center.x) + (end.y - start.y) * (start.y - center.y) +
2047 (end.z - start.z) * (start.z - center.z));
2048 float c = pow(center.x, 2) + pow(center.y, 2) + pow(center.z, 2) + pow(start.x, 2) + pow(start.y, 2) +
2049 pow(start.z, 2) - 2 * (center.x * start.x + center.y * start.y + center.z * start.z) -
2050 pow(asteroid.radius, 2);
2051 float discriminant = pow(b, 2) - 4 * a * c;
2052
2053 if (discriminant >= 0.0f) {
2054 // In this case, the negative root will always give the point closer to the laser start point
2055 float u = (-b - sqrt(discriminant)) / (2 * a);
2056
2057 // Check that the intersection is within the line segment corresponding to the laser
2058 if (0.0f <= u && u <= 1.0f) {
2059 intersection = start + u * (end - start);
2060 return true;
2061 }
2062 }
2063
2064 return false;
2065}
2066
2067void VulkanGame::createBufferSet(VkDeviceSize bufferSize, VkBufferUsageFlags flags,
2068 vector<VkBuffer>& buffers, vector<VkDeviceMemory>& buffersMemory, vector<VkDescriptorBufferInfo>& bufferInfoList) {
2069 buffers.resize(swapChainImageCount);
2070 buffersMemory.resize(swapChainImageCount);
2071 bufferInfoList.resize(swapChainImageCount);
2072
2073 for (size_t i = 0; i < swapChainImageCount; i++) {
2074 VulkanUtils::createBuffer(device, physicalDevice, bufferSize, flags,
2075 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2076 buffers[i], buffersMemory[i]);
2077
2078 bufferInfoList[i].buffer = buffers[i];
2079 bufferInfoList[i].offset = 0; // This is the offset from the start of the buffer, so always 0 for now
2080 bufferInfoList[i].range = bufferSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
2081 }
2082}
2083
2084void VulkanGame::addExplosion(mat4 model_mat, float duration, float cur_time) {
2085 vector<ExplosionVertex> vertices;
2086 vertices.reserve(EXPLOSION_PARTICLE_COUNT);
2087
2088 float particle_start_time = 0.0f;
2089
2090 for (int i = 0; i < EXPLOSION_PARTICLE_COUNT; i++) {
2091 float randx = ((float)rand() / (float)RAND_MAX) - 0.5f;
2092 float randy = ((float)rand() / (float)RAND_MAX) - 0.5f;
2093
2094 vertices.push_back({ vec3(randx, randy, 0.0f), particle_start_time});
2095
2096 particle_start_time += .01f;
2097 // TODO: Get this working
2098 // particle_start_time += 1.0f * EXPLOSION_PARTICLE_COUNT / duration
2099 }
2100
2101 // Fill the indices with the the first EXPLOSION_PARTICLE_COUNT ints
2102 vector<uint16_t> indices(EXPLOSION_PARTICLE_COUNT);
2103 iota(indices.begin(), indices.end(), 0);
2104
2105 SceneObject<ExplosionVertex, SSBO_Explosion>& explosion = addObject(
2106 explosionObjects, explosionPipeline,
2107 addObjectIndex(explosionObjects.size(), vertices),
2108 indices, {
2109 mat4(1.0f),
2110 cur_time,
2111 duration,
2112 false
2113 }, true);
2114
2115 explosion.model_base = model_mat;
2116 explosion.model_transform = mat4(1.0f);
2117
2118 explosion.modified = true;
2119}
2120
2121// TODO: Fix the crash that happens when alt-tabbing
2122void VulkanGame::recreateSwapChain() {
2123 cout << "Recreating swap chain" << endl;
2124 gui->refreshWindowSize();
2125
2126 while (gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0 ||
2127 (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) != 0) {
2128 SDL_WaitEvent(nullptr);
2129 gui->refreshWindowSize();
2130 }
2131
2132 if (vkDeviceWaitIdle(device) != VK_SUCCESS) {
2133 throw runtime_error("failed to wait for device!");
2134 }
2135
2136 cleanupSwapChain();
2137
2138 createSwapChain();
2139 createImageViews();
2140 createRenderPass();
2141
2142 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
2143 depthImage, graphicsQueue);
2144 createFramebuffers();
2145
2146 // TODO: Move UBO creation/management into GraphicsPipeline_Vulkan, like I did with SSBOs
2147
2148 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2149 uniformBuffers_modelPipeline, uniformBuffersMemory_modelPipeline, uniformBufferInfoList_modelPipeline);
2150
2151 modelPipeline.updateRenderPass(renderPass);
2152 modelPipeline.createPipeline("shaders/scene-vert.spv", "shaders/scene-frag.spv");
2153 modelPipeline.createDescriptorPool(swapChainImages);
2154 modelPipeline.createDescriptorSets(swapChainImages);
2155
2156 overlayPipeline.updateRenderPass(renderPass);
2157 overlayPipeline.createPipeline("shaders/overlay-vert.spv", "shaders/overlay-frag.spv");
2158 overlayPipeline.createDescriptorPool(swapChainImages);
2159 overlayPipeline.createDescriptorSets(swapChainImages);
2160
2161 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2162 uniformBuffers_shipPipeline, uniformBuffersMemory_shipPipeline, uniformBufferInfoList_shipPipeline);
2163
2164 shipPipeline.updateRenderPass(renderPass);
2165 shipPipeline.createPipeline("shaders/ship-vert.spv", "shaders/ship-frag.spv");
2166 shipPipeline.createDescriptorPool(swapChainImages);
2167 shipPipeline.createDescriptorSets(swapChainImages);
2168
2169 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2170 uniformBuffers_asteroidPipeline, uniformBuffersMemory_asteroidPipeline, uniformBufferInfoList_asteroidPipeline);
2171
2172 asteroidPipeline.updateRenderPass(renderPass);
2173 asteroidPipeline.createPipeline("shaders/asteroid-vert.spv", "shaders/asteroid-frag.spv");
2174 asteroidPipeline.createDescriptorPool(swapChainImages);
2175 asteroidPipeline.createDescriptorSets(swapChainImages);
2176
2177 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2178 uniformBuffers_laserPipeline, uniformBuffersMemory_laserPipeline, uniformBufferInfoList_laserPipeline);
2179
2180 laserPipeline.updateRenderPass(renderPass);
2181 laserPipeline.createPipeline("shaders/laser-vert.spv", "shaders/laser-frag.spv");
2182 laserPipeline.createDescriptorPool(swapChainImages);
2183 laserPipeline.createDescriptorSets(swapChainImages);
2184
2185 createBufferSet(sizeof(UBO_Explosion), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2186 uniformBuffers_explosionPipeline, uniformBuffersMemory_explosionPipeline, uniformBufferInfoList_explosionPipeline);
2187
2188 explosionPipeline.updateRenderPass(renderPass);
2189 explosionPipeline.createPipeline("shaders/explosion-vert.spv", "shaders/explosion-frag.spv");
2190 explosionPipeline.createDescriptorPool(swapChainImages);
2191 explosionPipeline.createDescriptorSets(swapChainImages);
2192
2193 createCommandBuffers();
2194
2195 createSyncObjects();
2196
2197 imageIndex = 0;
2198}
2199
2200void VulkanGame::cleanupSwapChain() {
2201 VulkanUtils::destroyVulkanImage(device, depthImage);
2202
2203 for (VkFramebuffer framebuffer : swapChainFramebuffers) {
2204 vkDestroyFramebuffer(device, framebuffer, nullptr);
2205 }
2206
2207 vkFreeCommandBuffers(device, commandPool, static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
2208
2209 overlayPipeline.cleanup();
2210 modelPipeline.cleanup();
2211 shipPipeline.cleanup();
2212 asteroidPipeline.cleanup();
2213 laserPipeline.cleanup();
2214 explosionPipeline.cleanup();
2215
2216 for (size_t i = 0; i < uniformBuffers_modelPipeline.size(); i++) {
2217 vkDestroyBuffer(device, uniformBuffers_modelPipeline[i], nullptr);
2218 vkFreeMemory(device, uniformBuffersMemory_modelPipeline[i], nullptr);
2219 }
2220
2221 for (size_t i = 0; i < uniformBuffers_shipPipeline.size(); i++) {
2222 vkDestroyBuffer(device, uniformBuffers_shipPipeline[i], nullptr);
2223 vkFreeMemory(device, uniformBuffersMemory_shipPipeline[i], nullptr);
2224 }
2225
2226 for (size_t i = 0; i < uniformBuffers_asteroidPipeline.size(); i++) {
2227 vkDestroyBuffer(device, uniformBuffers_asteroidPipeline[i], nullptr);
2228 vkFreeMemory(device, uniformBuffersMemory_asteroidPipeline[i], nullptr);
2229 }
2230
2231 for (size_t i = 0; i < uniformBuffers_laserPipeline.size(); i++) {
2232 vkDestroyBuffer(device, uniformBuffers_laserPipeline[i], nullptr);
2233 vkFreeMemory(device, uniformBuffersMemory_laserPipeline[i], nullptr);
2234 }
2235
2236 for (size_t i = 0; i < uniformBuffers_explosionPipeline.size(); i++) {
2237 vkDestroyBuffer(device, uniformBuffers_explosionPipeline[i], nullptr);
2238 vkFreeMemory(device, uniformBuffersMemory_explosionPipeline[i], nullptr);
2239 }
2240
2241 for (size_t i = 0; i < swapChainImageCount; i++) {
2242 vkDestroySemaphore(device, imageAcquiredSemaphores[i], nullptr);
2243 vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr);
2244 vkDestroyFence(device, inFlightFences[i], nullptr);
2245 }
2246
2247 vkDestroyRenderPass(device, renderPass, nullptr);
2248
2249 for (VkImageView imageView : swapChainImageViews) {
2250 vkDestroyImageView(device, imageView, nullptr);
2251 }
2252
2253 vkDestroySwapchainKHR(device, swapChain, nullptr);
2254}
Note: See TracBrowser for help on using the repository browser.