source: opengl-game/vulkan-game.cpp@ 8d96e95

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

Avoid recreating the swap chain when the window is minimized

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