source: opengl-game/vulkan-game.cpp@ 484334e

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

In VulkanGame, correctly recreate the swap chain during the render loop, and in SDLGame, use the GameGui class to get the window dimensions during swap chain recreation

  • Property mode set to 100644
File size: 88.8 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
941 int width = gui->getWindowWidth();
942 int height = gui->getWindowHeight();
943 if (width > 0 && height > 0) {
944 // TODO: This should be used if the min image count changes, presumably because a new surface was created
945 // with a different image count or something like that. Maybe I want to add code to query for a new min image count
946 // during swapchain recreation to take advantage of this
947 ImGui_ImplVulkan_SetMinImageCount(swapChainMinImageCount);
948
949 recreateSwapChain();
950
951 imageIndex = 0;
952 shouldRecreateSwapChain = false;
953 }
954 }
955
956 currentScreen->renderUI();
957
958 // Copy the UI image to a vulkan texture
959 // TODO: I'm pretty sure this severely slows down the pipeline since this functions waits for the copy to be
960 // complete before continuing. See if I can find a more efficient method.
961 VulkanUtils::populateVulkanImageFromSDLTexture(device, physicalDevice, resourceCommandPool, uiOverlay, renderer,
962 sdlOverlayImage, graphicsQueue);
963
964 updateScene();
965
966 ImGui_ImplVulkan_NewFrame();
967 ImGui_ImplSDL2_NewFrame(this->window);
968 ImGui::NewFrame();
969
970 {
971 ImGui::SetNextWindowSize(ImVec2(250, 35), ImGuiCond_Once);
972 ImGui::SetNextWindowPos(ImVec2(380, 10), ImGuiCond_Once);
973 ImGui::Begin("WndMenubar", NULL,
974 ImGuiWindowFlags_NoTitleBar |
975 ImGuiWindowFlags_NoResize |
976 ImGuiWindowFlags_NoMove);
977 ImGui::InvisibleButton("", ImVec2(155, 18));
978 ImGui::SameLine();
979 if (ImGui::Button("Main Menu")) {
980 cout << "Clicked on the main button" << endl;
981 //events.push(Event::GO_TO_MAIN_MENU);
982 }
983 ImGui::End();
984 }
985
986 ImGui::Render();
987
988 renderFrame(ImGui::GetDrawData());
989 presentFrame();
990 }
991}
992
993// TODO: The only updates that need to happen once per Vulkan image are the SSBO ones,
994// which are already handled by updateObject(). Move this code to a different place,
995// where it will run just once per frame
996void VulkanGame::updateScene() {
997 for (SceneObject<ModelVertex, SSBO_ModelObject>& model : this->modelObjects) {
998 model.model_transform =
999 translate(mat4(1.0f), vec3(0.0f, -2.0f, -0.0f)) *
1000 rotate(mat4(1.0f), curTime * radians(90.0f), vec3(0.0f, 0.0f, 1.0f));
1001 model.modified = true;
1002 }
1003
1004 if (leftLaserIdx != -1) {
1005 updateLaserTarget(leftLaserIdx);
1006 }
1007 if (rightLaserIdx != -1) {
1008 updateLaserTarget(rightLaserIdx);
1009 }
1010
1011 for (vector<BaseEffectOverTime*>::iterator it = effects.begin(); it != effects.end(); ) {
1012 if ((*it)->deleted) {
1013 delete *it;
1014 it = effects.erase(it);
1015 } else {
1016 BaseEffectOverTime* eot = *it;
1017
1018 eot->applyEffect();
1019
1020 it++;
1021 }
1022 }
1023
1024 for (SceneObject<AsteroidVertex, SSBO_Asteroid>& asteroid : this->asteroidObjects) {
1025 if (!asteroid.ssbo.deleted) {
1026 vec3 objCenter = vec3(viewMat * vec4(asteroid.center, 1.0f));
1027
1028 if (asteroid.ssbo.hp <= 0.0f) {
1029 asteroid.ssbo.deleted = true;
1030
1031 // TODO: Optimize this so I don't recalculate the camera rotation every time
1032 // TODO: Also, avoid re-declaring cam_pitch
1033 float cam_pitch = -50.0f;
1034 mat4 pitch_mat = rotate(mat4(1.0f), radians(cam_pitch), vec3(1.0f, 0.0f, 0.0f));
1035 mat4 model_mat = translate(mat4(1.0f), asteroid.center) * pitch_mat;
1036
1037 addExplosion(model_mat, 0.5f, curTime);
1038
1039 this->score++;
1040 } else if ((objCenter.z - asteroid.radius) > -NEAR_CLIP) {
1041 asteroid.ssbo.deleted = true;
1042 } else {
1043 asteroid.model_transform =
1044 translate(mat4(1.0f), vec3(0.0f, 0.0f, this->asteroidSpeed * this->elapsedTime)) *
1045 asteroid.model_transform;
1046 }
1047
1048 asteroid.modified = true;
1049 }
1050 }
1051
1052 if (curTime - this->lastSpawn_asteroid > this->spawnRate_asteroid) {
1053 this->lastSpawn_asteroid = curTime;
1054
1055 SceneObject<AsteroidVertex, SSBO_Asteroid>& asteroid = addObject(
1056 asteroidObjects, asteroidPipeline,
1057 addObjectIndex<AsteroidVertex>(asteroidObjects.size(),
1058 addVertexNormals<AsteroidVertex>({
1059
1060 // front
1061 {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1062 {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1063 {{-1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1064 {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1065 {{-1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1066 {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1067
1068 // top
1069 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1070 {{-1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1071 {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
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
1076 // bottom
1077 {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1078 {{-1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1079 {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
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.0}, {0.4f, 0.4f, 0.4f}},
1083
1084 // back
1085 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1086 {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1087 {{-1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
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
1092 // right
1093 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1094 {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1095 {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
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
1100 // left
1101 {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
1102 {{-1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
1103 {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
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 })), {
1108 0, 1, 2, 3, 4, 5,
1109 6, 7, 8, 9, 10, 11,
1110 12, 13, 14, 15, 16, 17,
1111 18, 19, 20, 21, 22, 23,
1112 24, 25, 26, 27, 28, 29,
1113 30, 31, 32, 33, 34, 35,
1114 }, {
1115 mat4(1.0f),
1116 10.0f,
1117 false
1118 }, true);
1119
1120 // This accounts for the scaling in model_base.
1121 // Dividing by 8 instead of 10 since the bounding radius algorithm
1122 // under-calculates the true value.
1123 // TODO: Figure out the best way to take scaling into account when calculating the radius
1124 // Keep in mind that the main complicating factor is the currently poor radius calculation
1125 asteroid.radius /= 8.0f;
1126
1127 asteroid.model_base =
1128 translate(mat4(1.0f), vec3(getRandomNum(-1.3f, 1.3f), -1.2f, getRandomNum(-5.5f, -4.5f))) *
1129 rotate(mat4(1.0f), radians(60.0f), vec3(1.0f, 1.0f, -1.0f)) *
1130 scale(mat4(1.0f), vec3(0.1f, 0.1f, 0.1f));
1131 asteroid.modified = true;
1132 }
1133
1134 for (SceneObject<ExplosionVertex, SSBO_Explosion>& explosion : this->explosionObjects) {
1135 if (!explosion.ssbo.deleted) {
1136 if (curTime > (explosion.ssbo.explosionStartTime + explosion.ssbo.explosionDuration)) {
1137 explosion.ssbo.deleted = true;
1138 explosion.modified = true;
1139 }
1140 }
1141 }
1142
1143 for (size_t i = 0; i < shipObjects.size(); i++) {
1144 if (shipObjects[i].modified) {
1145 updateObject(shipObjects, shipPipeline, i);
1146 }
1147 }
1148
1149 for (size_t i = 0; i < modelObjects.size(); i++) {
1150 if (modelObjects[i].modified) {
1151 updateObject(modelObjects, modelPipeline, i);
1152 }
1153 }
1154
1155 for (size_t i = 0; i < asteroidObjects.size(); i++) {
1156 if (asteroidObjects[i].modified) {
1157 updateObject(asteroidObjects, asteroidPipeline, i);
1158 }
1159 }
1160
1161 for (size_t i = 0; i < laserObjects.size(); i++) {
1162 if (laserObjects[i].modified) {
1163 updateObject(laserObjects, laserPipeline, i);
1164 }
1165 }
1166
1167 for (size_t i = 0; i < explosionObjects.size(); i++) {
1168 if (explosionObjects[i].modified) {
1169 updateObject(explosionObjects, explosionPipeline, i);
1170 }
1171 }
1172
1173 explosion_UBO.cur_time = curTime;
1174
1175 VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_modelPipeline[imageIndex], 0, object_VP_mats);
1176
1177 VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_shipPipeline[imageIndex], 0, ship_VP_mats);
1178
1179 VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_asteroidPipeline[imageIndex], 0, asteroid_VP_mats);
1180
1181 VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_laserPipeline[imageIndex], 0, laser_VP_mats);
1182
1183 VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_explosionPipeline[imageIndex], 0, explosion_UBO);
1184}
1185
1186void VulkanGame::cleanup() {
1187 VKUTIL_CHECK_RESULT(vkDeviceWaitIdle(device), "failed to wait for device!");
1188
1189 ImGui_ImplVulkan_Shutdown();
1190 ImGui_ImplSDL2_Shutdown();
1191 ImGui::DestroyContext();
1192
1193 // TODO: Probably move this into cleanupSwapChain once I finish the integration
1194 destroyImguiDescriptorPool();
1195
1196 cleanupSwapChain();
1197
1198 VulkanUtils::destroyVulkanImage(device, sdlOverlayImage);
1199 VulkanUtils::destroyVulkanImage(device, floorTextureImage);
1200 VulkanUtils::destroyVulkanImage(device, laserTextureImage);
1201
1202 vkDestroySampler(device, textureSampler, nullptr);
1203
1204 modelPipeline.cleanupBuffers();
1205 overlayPipeline.cleanupBuffers();
1206 shipPipeline.cleanupBuffers();
1207 asteroidPipeline.cleanupBuffers();
1208 laserPipeline.cleanupBuffers();
1209 explosionPipeline.cleanupBuffers();
1210
1211 vkDestroyCommandPool(device, resourceCommandPool, nullptr);
1212
1213 vkDestroyDevice(device, nullptr);
1214 vkDestroySurfaceKHR(instance, surface, nullptr);
1215
1216 if (ENABLE_VALIDATION_LAYERS) {
1217 VulkanUtils::destroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
1218 }
1219
1220 vkDestroyInstance(instance, nullptr);
1221
1222 delete screens[SCREEN_MAIN];
1223 delete screens[SCREEN_GAME];
1224
1225 if (lazyFont != nullptr) {
1226 TTF_CloseFont(lazyFont);
1227 lazyFont = nullptr;
1228 }
1229
1230 if (proggyFont != nullptr) {
1231 TTF_CloseFont(proggyFont);
1232 proggyFont = nullptr;
1233 }
1234
1235 if (uiOverlay != nullptr) {
1236 SDL_DestroyTexture(uiOverlay);
1237 uiOverlay = nullptr;
1238 }
1239
1240 SDL_DestroyRenderer(renderer);
1241 renderer = nullptr;
1242
1243 gui->destroyWindow();
1244 gui->shutdown();
1245 delete gui;
1246}
1247
1248void VulkanGame::createVulkanInstance(const vector<const char*>& validationLayers) {
1249 if (ENABLE_VALIDATION_LAYERS && !VulkanUtils::checkValidationLayerSupport(validationLayers)) {
1250 throw runtime_error("validation layers requested, but not available!");
1251 }
1252
1253 VkApplicationInfo appInfo = {};
1254 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
1255 appInfo.pApplicationName = "Vulkan Game";
1256 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
1257 appInfo.pEngineName = "No Engine";
1258 appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
1259 appInfo.apiVersion = VK_API_VERSION_1_0;
1260
1261 VkInstanceCreateInfo createInfo = {};
1262 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
1263 createInfo.pApplicationInfo = &appInfo;
1264
1265 vector<const char*> extensions = gui->getRequiredExtensions();
1266 if (ENABLE_VALIDATION_LAYERS) {
1267 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
1268 }
1269
1270 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
1271 createInfo.ppEnabledExtensionNames = extensions.data();
1272
1273 cout << endl << "Extensions:" << endl;
1274 for (const char* extensionName : extensions) {
1275 cout << extensionName << endl;
1276 }
1277 cout << endl;
1278
1279 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
1280 if (ENABLE_VALIDATION_LAYERS) {
1281 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
1282 createInfo.ppEnabledLayerNames = validationLayers.data();
1283
1284 populateDebugMessengerCreateInfo(debugCreateInfo);
1285 createInfo.pNext = &debugCreateInfo;
1286 } else {
1287 createInfo.enabledLayerCount = 0;
1288
1289 createInfo.pNext = nullptr;
1290 }
1291
1292 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
1293 throw runtime_error("failed to create instance!");
1294 }
1295}
1296
1297void VulkanGame::setupDebugMessenger() {
1298 if (!ENABLE_VALIDATION_LAYERS) {
1299 return;
1300 }
1301
1302 VkDebugUtilsMessengerCreateInfoEXT createInfo;
1303 populateDebugMessengerCreateInfo(createInfo);
1304
1305 if (VulkanUtils::createDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
1306 throw runtime_error("failed to set up debug messenger!");
1307 }
1308}
1309
1310void VulkanGame::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
1311 createInfo = {};
1312 createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
1313 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;
1314 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;
1315 createInfo.pfnUserCallback = debugCallback;
1316}
1317
1318VKAPI_ATTR VkBool32 VKAPI_CALL VulkanGame::debugCallback(
1319 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
1320 VkDebugUtilsMessageTypeFlagsEXT messageType,
1321 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
1322 void* pUserData) {
1323 cerr << "validation layer: " << pCallbackData->pMessage << endl;
1324
1325 // TODO: Figure out what the return value means and if it should always be VK_FALSE
1326 return VK_FALSE;
1327}
1328
1329void VulkanGame::createVulkanSurface() {
1330 if (gui->createVulkanSurface(instance, &surface) == RTWO_ERROR) {
1331 throw runtime_error("failed to create window surface!");
1332 }
1333}
1334
1335void VulkanGame::pickPhysicalDevice(const vector<const char*>& deviceExtensions) {
1336 uint32_t deviceCount = 0;
1337 // TODO: Check VkResult
1338 vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
1339
1340 if (deviceCount == 0) {
1341 throw runtime_error("failed to find GPUs with Vulkan support!");
1342 }
1343
1344 vector<VkPhysicalDevice> devices(deviceCount);
1345 // TODO: Check VkResult
1346 vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
1347
1348 cout << endl << "Graphics cards:" << endl;
1349 for (const VkPhysicalDevice& device : devices) {
1350 if (isDeviceSuitable(device, deviceExtensions)) {
1351 physicalDevice = device;
1352 break;
1353 }
1354 }
1355 cout << endl;
1356
1357 if (physicalDevice == VK_NULL_HANDLE) {
1358 throw runtime_error("failed to find a suitable GPU!");
1359 }
1360}
1361
1362bool VulkanGame::isDeviceSuitable(VkPhysicalDevice physicalDevice, const vector<const char*>& deviceExtensions) {
1363 VkPhysicalDeviceProperties deviceProperties;
1364 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
1365
1366 cout << "Device: " << deviceProperties.deviceName << endl;
1367
1368 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
1369 bool extensionsSupported = VulkanUtils::checkDeviceExtensionSupport(physicalDevice, deviceExtensions);
1370 bool swapChainAdequate = false;
1371
1372 if (extensionsSupported) {
1373 vector<VkSurfaceFormatKHR> formats = VulkanUtils::querySwapChainFormats(physicalDevice, surface);
1374 vector<VkPresentModeKHR> presentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, surface);
1375
1376 swapChainAdequate = !formats.empty() && !presentModes.empty();
1377 }
1378
1379 VkPhysicalDeviceFeatures supportedFeatures;
1380 vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
1381
1382 return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
1383}
1384
1385void VulkanGame::createLogicalDevice(const vector<const char*>& validationLayers,
1386 const vector<const char*>& deviceExtensions) {
1387 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
1388
1389 if (!indices.isComplete()) {
1390 throw runtime_error("failed to find required queue families!");
1391 }
1392
1393 // TODO: Using separate graphics and present queues currently works, but I should verify that I'm
1394 // using them correctly to get the most benefit out of separate queues
1395
1396 vector<VkDeviceQueueCreateInfo> queueCreateInfoList;
1397 set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
1398
1399 float queuePriority = 1.0f;
1400 for (uint32_t queueFamily : uniqueQueueFamilies) {
1401 VkDeviceQueueCreateInfo queueCreateInfo = {};
1402 queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
1403 queueCreateInfo.queueCount = 1;
1404 queueCreateInfo.queueFamilyIndex = queueFamily;
1405 queueCreateInfo.pQueuePriorities = &queuePriority;
1406
1407 queueCreateInfoList.push_back(queueCreateInfo);
1408 }
1409
1410 VkPhysicalDeviceFeatures deviceFeatures = {};
1411 deviceFeatures.samplerAnisotropy = VK_TRUE;
1412
1413 VkDeviceCreateInfo createInfo = {};
1414 createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
1415
1416 createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfoList.size());
1417 createInfo.pQueueCreateInfos = queueCreateInfoList.data();
1418
1419 createInfo.pEnabledFeatures = &deviceFeatures;
1420
1421 createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
1422 createInfo.ppEnabledExtensionNames = deviceExtensions.data();
1423
1424 // These fields are ignored by up-to-date Vulkan implementations,
1425 // but it's a good idea to set them for backwards compatibility
1426 if (ENABLE_VALIDATION_LAYERS) {
1427 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
1428 createInfo.ppEnabledLayerNames = validationLayers.data();
1429 } else {
1430 createInfo.enabledLayerCount = 0;
1431 }
1432
1433 if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
1434 throw runtime_error("failed to create logical device!");
1435 }
1436
1437 vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
1438 vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
1439}
1440
1441void VulkanGame::chooseSwapChainProperties() {
1442 vector<VkSurfaceFormatKHR> availableFormats = VulkanUtils::querySwapChainFormats(physicalDevice, surface);
1443 vector<VkPresentModeKHR> availablePresentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, surface);
1444
1445 swapChainSurfaceFormat = VulkanUtils::chooseSwapSurfaceFormat(availableFormats,
1446 { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM },
1447 VK_COLOR_SPACE_SRGB_NONLINEAR_KHR);
1448
1449 vector<VkPresentModeKHR> presentModes{
1450 VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR
1451 };
1452 //vector<VkPresentModeKHR> presentModes{ VK_PRESENT_MODE_FIFO_KHR };
1453
1454 swapChainPresentMode = VulkanUtils::chooseSwapPresentMode(availablePresentModes, presentModes);
1455
1456 cout << "[vulkan] Selected PresentMode = " << swapChainPresentMode << endl;
1457
1458 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, surface);
1459
1460 if (swapChainPresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
1461 swapChainMinImageCount = 3;
1462 } else if (swapChainPresentMode == VK_PRESENT_MODE_FIFO_KHR || swapChainPresentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) {
1463 swapChainMinImageCount = 2;
1464 } else if (swapChainPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
1465 swapChainMinImageCount = 1;
1466 } else {
1467 throw runtime_error("unexpected present mode!");
1468 }
1469
1470 if (swapChainMinImageCount < capabilities.minImageCount) {
1471 swapChainMinImageCount = capabilities.minImageCount;
1472 } else if (capabilities.maxImageCount != 0 && swapChainMinImageCount > capabilities.maxImageCount) {
1473 swapChainMinImageCount = capabilities.maxImageCount;
1474 }
1475}
1476
1477void VulkanGame::createSwapChain() {
1478 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, surface);
1479
1480 swapChainExtent = VulkanUtils::chooseSwapExtent(capabilities, gui->getWindowWidth(), gui->getWindowHeight());
1481
1482 VkSwapchainCreateInfoKHR createInfo = {};
1483 createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
1484 createInfo.surface = surface;
1485 createInfo.minImageCount = swapChainMinImageCount;
1486 createInfo.imageFormat = swapChainSurfaceFormat.format;
1487 createInfo.imageColorSpace = swapChainSurfaceFormat.colorSpace;
1488 createInfo.imageExtent = swapChainExtent;
1489 createInfo.imageArrayLayers = 1;
1490 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
1491
1492 // TODO: Maybe save this result so I don't have to recalculate it every time
1493 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
1494 uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
1495
1496 if (indices.graphicsFamily != indices.presentFamily) {
1497 createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
1498 createInfo.queueFamilyIndexCount = 2;
1499 createInfo.pQueueFamilyIndices = queueFamilyIndices;
1500 } else {
1501 createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
1502 createInfo.queueFamilyIndexCount = 0;
1503 createInfo.pQueueFamilyIndices = nullptr;
1504 }
1505
1506 createInfo.preTransform = capabilities.currentTransform;
1507 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
1508 createInfo.presentMode = swapChainPresentMode;
1509 createInfo.clipped = VK_TRUE;
1510 createInfo.oldSwapchain = VK_NULL_HANDLE;
1511
1512 if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
1513 throw runtime_error("failed to create swap chain!");
1514 }
1515
1516 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, nullptr) != VK_SUCCESS) {
1517 throw runtime_error("failed to get swap chain image count!");
1518 }
1519
1520 swapChainImages.resize(swapChainImageCount);
1521 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, swapChainImages.data()) != VK_SUCCESS) {
1522 throw runtime_error("failed to get swap chain images!");
1523 }
1524}
1525
1526void VulkanGame::createImageViews() {
1527 swapChainImageViews.resize(swapChainImageCount);
1528
1529 for (size_t i = 0; i < swapChainImageCount; i++) {
1530 swapChainImageViews[i] = VulkanUtils::createImageView(device, swapChainImages[i], swapChainSurfaceFormat.format,
1531 VK_IMAGE_ASPECT_COLOR_BIT);
1532 }
1533}
1534
1535void VulkanGame::createRenderPass() {
1536 VkAttachmentDescription colorAttachment = {};
1537 colorAttachment.format = swapChainSurfaceFormat.format;
1538 colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
1539 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1540 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
1541 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1542 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1543 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1544 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
1545
1546 VkAttachmentReference colorAttachmentRef = {};
1547 colorAttachmentRef.attachment = 0;
1548 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
1549
1550 VkAttachmentDescription depthAttachment = {};
1551 depthAttachment.format = findDepthFormat();
1552 depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
1553 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1554 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1555 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1556 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1557 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1558 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1559
1560 VkAttachmentReference depthAttachmentRef = {};
1561 depthAttachmentRef.attachment = 1;
1562 depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1563
1564 VkSubpassDescription subpass = {};
1565 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
1566 subpass.colorAttachmentCount = 1;
1567 subpass.pColorAttachments = &colorAttachmentRef;
1568 subpass.pDepthStencilAttachment = &depthAttachmentRef;
1569
1570 VkSubpassDependency dependency = {};
1571 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
1572 dependency.dstSubpass = 0;
1573 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1574 dependency.srcAccessMask = 0;
1575 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1576 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
1577
1578 array<VkAttachmentDescription, 2> attachments = { colorAttachment, depthAttachment };
1579 VkRenderPassCreateInfo renderPassInfo = {};
1580 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
1581 renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
1582 renderPassInfo.pAttachments = attachments.data();
1583 renderPassInfo.subpassCount = 1;
1584 renderPassInfo.pSubpasses = &subpass;
1585 renderPassInfo.dependencyCount = 1;
1586 renderPassInfo.pDependencies = &dependency;
1587
1588 if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
1589 throw runtime_error("failed to create render pass!");
1590 }
1591}
1592
1593VkFormat VulkanGame::findDepthFormat() {
1594 return VulkanUtils::findSupportedFormat(
1595 physicalDevice,
1596 { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT },
1597 VK_IMAGE_TILING_OPTIMAL,
1598 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
1599 );
1600}
1601
1602void VulkanGame::createResourceCommandPool() {
1603 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
1604
1605 VkCommandPoolCreateInfo poolInfo = {};
1606 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
1607 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
1608 poolInfo.flags = 0;
1609
1610 if (vkCreateCommandPool(device, &poolInfo, nullptr, &resourceCommandPool) != VK_SUCCESS) {
1611 throw runtime_error("failed to create resource command pool!");
1612 }
1613}
1614
1615void VulkanGame::createCommandPools() {
1616 commandPools.resize(swapChainImageCount);
1617
1618 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
1619
1620 for (size_t i = 0; i < swapChainImageCount; i++) {
1621 VkCommandPoolCreateInfo poolInfo = {};
1622 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
1623 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
1624 poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
1625
1626 VKUTIL_CHECK_RESULT(vkCreateCommandPool(device, &poolInfo, nullptr, &commandPools[i]),
1627 "failed to create graphics command pool!");
1628 }
1629}
1630
1631void VulkanGame::createImageResources() {
1632 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
1633 depthImage, graphicsQueue);
1634
1635 createTextureSampler();
1636
1637 // TODO: Move all images/textures somewhere into the assets folder
1638
1639 VulkanUtils::createVulkanImageFromSDLTexture(device, physicalDevice, uiOverlay, sdlOverlayImage);
1640
1641 sdlOverlayImageDescriptor = {};
1642 sdlOverlayImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1643 sdlOverlayImageDescriptor.imageView = sdlOverlayImage.imageView;
1644 sdlOverlayImageDescriptor.sampler = textureSampler;
1645
1646 VulkanUtils::createVulkanImageFromFile(device, physicalDevice, resourceCommandPool, "textures/texture.jpg",
1647 floorTextureImage, graphicsQueue);
1648
1649 floorTextureImageDescriptor = {};
1650 floorTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1651 floorTextureImageDescriptor.imageView = floorTextureImage.imageView;
1652 floorTextureImageDescriptor.sampler = textureSampler;
1653
1654 VulkanUtils::createVulkanImageFromFile(device, physicalDevice, resourceCommandPool, "textures/laser.png",
1655 laserTextureImage, graphicsQueue);
1656
1657 laserTextureImageDescriptor = {};
1658 laserTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1659 laserTextureImageDescriptor.imageView = laserTextureImage.imageView;
1660 laserTextureImageDescriptor.sampler = textureSampler;
1661}
1662
1663void VulkanGame::createTextureSampler() {
1664 VkSamplerCreateInfo samplerInfo = {};
1665 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1666 samplerInfo.magFilter = VK_FILTER_LINEAR;
1667 samplerInfo.minFilter = VK_FILTER_LINEAR;
1668
1669 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1670 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1671 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1672
1673 samplerInfo.anisotropyEnable = VK_TRUE;
1674 samplerInfo.maxAnisotropy = 16;
1675 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
1676 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1677 samplerInfo.compareEnable = VK_FALSE;
1678 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1679 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
1680 samplerInfo.mipLodBias = 0.0f;
1681 samplerInfo.minLod = 0.0f;
1682 samplerInfo.maxLod = 0.0f;
1683
1684 if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) {
1685 throw runtime_error("failed to create texture sampler!");
1686 }
1687}
1688
1689void VulkanGame::createFramebuffers() {
1690 swapChainFramebuffers.resize(swapChainImageCount);
1691
1692 VkFramebufferCreateInfo framebufferInfo = {};
1693 framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
1694 framebufferInfo.renderPass = renderPass;
1695 framebufferInfo.width = swapChainExtent.width;
1696 framebufferInfo.height = swapChainExtent.height;
1697 framebufferInfo.layers = 1;
1698
1699 for (uint32_t i = 0; i < swapChainImageCount; i++) {
1700 array<VkImageView, 2> attachments = {
1701 swapChainImageViews[i],
1702 depthImage.imageView
1703 };
1704
1705 framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
1706 framebufferInfo.pAttachments = attachments.data();
1707
1708 if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
1709 throw runtime_error("failed to create framebuffer!");
1710 }
1711 }
1712}
1713
1714void VulkanGame::createCommandBuffers() {
1715 commandBuffers.resize(swapChainImageCount);
1716
1717 for (size_t i = 0; i < swapChainImageCount; i++) {
1718 VkCommandBufferAllocateInfo allocInfo = {};
1719 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1720 allocInfo.commandPool = commandPools[i];
1721 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1722 allocInfo.commandBufferCount = 1;
1723
1724 if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffers[i]) != VK_SUCCESS) {
1725 throw runtime_error("failed to allocate command buffer!");
1726 }
1727 }
1728}
1729
1730void VulkanGame::renderFrame(ImDrawData* draw_data) {
1731 VkResult result = vkAcquireNextImageKHR(device, swapChain, numeric_limits<uint64_t>::max(),
1732 imageAcquiredSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
1733
1734 if (result == VK_SUBOPTIMAL_KHR) {
1735 shouldRecreateSwapChain = true;
1736 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1737 shouldRecreateSwapChain = true;
1738 return;
1739 } else {
1740 VKUTIL_CHECK_RESULT(result, "failed to acquire swap chain image!");
1741 }
1742
1743 VKUTIL_CHECK_RESULT(
1744 vkWaitForFences(device, 1, &inFlightFences[imageIndex], VK_TRUE, numeric_limits<uint64_t>::max()),
1745 "failed waiting for fence!");
1746
1747 VKUTIL_CHECK_RESULT(vkResetFences(device, 1, &inFlightFences[imageIndex]),
1748 "failed to reset fence!");
1749
1750 VKUTIL_CHECK_RESULT(vkResetCommandPool(device, commandPools[imageIndex], 0),
1751 "failed to reset command pool!");
1752
1753 VkCommandBufferBeginInfo beginInfo = {};
1754 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1755 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1756
1757 VKUTIL_CHECK_RESULT(vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo),
1758 "failed to begin recording command buffer!");
1759
1760 VkRenderPassBeginInfo renderPassInfo = {};
1761 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
1762 renderPassInfo.renderPass = renderPass;
1763 renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
1764 renderPassInfo.renderArea.offset = { 0, 0 };
1765 renderPassInfo.renderArea.extent = swapChainExtent;
1766
1767 array<VkClearValue, 2> clearValues = {};
1768 clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
1769 clearValues[1].depthStencil = { 1.0f, 0 };
1770
1771 renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
1772 renderPassInfo.pClearValues = clearValues.data();
1773
1774 vkCmdBeginRenderPass(commandBuffers[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
1775
1776 currentScreen->createRenderCommands(commandBuffers[imageIndex], imageIndex);
1777
1778 ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffers[imageIndex]);
1779
1780 vkCmdEndRenderPass(commandBuffers[imageIndex]);
1781
1782 VKUTIL_CHECK_RESULT(vkEndCommandBuffer(commandBuffers[imageIndex]),
1783 "failed to record command buffer!");
1784
1785 VkSemaphore waitSemaphores[] = { imageAcquiredSemaphores[currentFrame] };
1786 VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
1787 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
1788
1789 VkSubmitInfo submitInfo = {};
1790 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1791 submitInfo.waitSemaphoreCount = 1;
1792 submitInfo.pWaitSemaphores = waitSemaphores;
1793 submitInfo.pWaitDstStageMask = waitStages;
1794 submitInfo.commandBufferCount = 1;
1795 submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
1796 submitInfo.signalSemaphoreCount = 1;
1797 submitInfo.pSignalSemaphores = signalSemaphores;
1798
1799 VKUTIL_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[imageIndex]),
1800 "failed to submit draw command buffer!");
1801}
1802
1803void VulkanGame::presentFrame() {
1804 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
1805
1806 VkPresentInfoKHR presentInfo = {};
1807 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
1808 presentInfo.waitSemaphoreCount = 1;
1809 presentInfo.pWaitSemaphores = signalSemaphores;
1810 presentInfo.swapchainCount = 1;
1811 presentInfo.pSwapchains = &swapChain;
1812 presentInfo.pImageIndices = &imageIndex;
1813 presentInfo.pResults = nullptr;
1814
1815 VkResult result = vkQueuePresentKHR(presentQueue, &presentInfo);
1816
1817 if (result == VK_SUBOPTIMAL_KHR) {
1818 shouldRecreateSwapChain = true;
1819 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1820 shouldRecreateSwapChain = true;
1821 return;
1822 } else {
1823 VKUTIL_CHECK_RESULT(result, "failed to present swap chain image!");
1824 }
1825
1826 currentFrame = (currentFrame + 1) % swapChainImageCount;
1827}
1828
1829void VulkanGame::createSyncObjects() {
1830 imageAcquiredSemaphores.resize(swapChainImageCount);
1831 renderCompleteSemaphores.resize(swapChainImageCount);
1832 inFlightFences.resize(swapChainImageCount);
1833
1834 VkSemaphoreCreateInfo semaphoreInfo = {};
1835 semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
1836
1837 VkFenceCreateInfo fenceInfo = {};
1838 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
1839 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
1840
1841 for (size_t i = 0; i < swapChainImageCount; i++) {
1842 if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAcquiredSemaphores[i]) != VK_SUCCESS) {
1843 throw runtime_error("failed to create image acquired sempahore for a frame!");
1844 }
1845
1846 if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderCompleteSemaphores[i]) != VK_SUCCESS) {
1847 throw runtime_error("failed to create render complete sempahore for a frame!");
1848 }
1849
1850 if (vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) {
1851 throw runtime_error("failed to create fence for a frame!");
1852 }
1853 }
1854}
1855
1856void VulkanGame::createImguiDescriptorPool() {
1857 vector<VkDescriptorPoolSize> pool_sizes{
1858 { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
1859 { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
1860 { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
1861 { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 },
1862 { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 },
1863 { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 },
1864 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 },
1865 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 },
1866 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 },
1867 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 },
1868 { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 }
1869 };
1870
1871 VkDescriptorPoolCreateInfo pool_info = {};
1872 pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1873 pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
1874 pool_info.maxSets = 1000 * pool_sizes.size();
1875 pool_info.poolSizeCount = static_cast<uint32_t>(pool_sizes.size());
1876 pool_info.pPoolSizes = pool_sizes.data();
1877 if (vkCreateDescriptorPool(device, &pool_info, nullptr, &imguiDescriptorPool) != VK_SUCCESS) {
1878 throw runtime_error("failed to create IMGUI descriptor pool!");
1879 }
1880}
1881
1882void VulkanGame::destroyImguiDescriptorPool() {
1883 vkDestroyDescriptorPool(device, imguiDescriptorPool, nullptr);
1884}
1885
1886void VulkanGame::addLaser(vec3 start, vec3 end, vec3 color, float width) {
1887 vec3 ray = end - start;
1888 float length = glm::length(ray);
1889
1890 SceneObject<LaserVertex, SSBO_Laser>& laser = addObject(
1891 laserObjects, laserPipeline,
1892 addObjectIndex<LaserVertex>(laserObjects.size(), {
1893 {{ width / 2, 0.0f, -width / 2 }, {1.0f, 0.5f }},
1894 {{-width / 2, 0.0f, -width / 2 }, {0.0f, 0.5f }},
1895 {{-width / 2, 0.0f, 0.0f }, {0.0f, 0.0f }},
1896 {{ width / 2, 0.0f, 0.0f }, {1.0f, 0.0f }},
1897 {{ width / 2, 0.0f, -length + width / 2}, {1.0f, 0.51f}},
1898 {{-width / 2, 0.0f, -length + width / 2}, {0.0f, 0.51f}},
1899 {{ width / 2, 0.0f, -length, }, {1.0f, 1.0f }},
1900 {{-width / 2, 0.0f, -length }, {0.0f, 1.0f }}
1901 }), {
1902 0, 1, 2, 0, 2, 3,
1903 4, 5, 1, 4, 1, 0,
1904 6, 7, 5, 6, 5, 4
1905 }, {
1906 mat4(1.0f),
1907 color,
1908 false
1909 }, true);
1910
1911 float xAxisRotation = asin(ray.y / length);
1912 float yAxisRotation = atan2(-ray.x, -ray.z);
1913
1914 vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
1915 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) *
1916 vec4(0.0f, 1.0f, 0.0f, 1.0f));
1917
1918 // To project point P onto line AB:
1919 // projection = A + dot(AP,AB) / dot(AB,AB) * AB
1920 vec3 projOnLaser = start + glm::dot(this->cam_pos - start, ray) / (length * length) * ray;
1921 vec3 laserToCam = this->cam_pos - projOnLaser;
1922
1923 float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam));
1924
1925 laser.targetAsteroid = nullptr;
1926
1927 laser.model_base =
1928 rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f));
1929
1930 laser.model_transform =
1931 translate(mat4(1.0f), start) *
1932 rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
1933 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f));
1934
1935 laser.modified = true;
1936}
1937
1938void VulkanGame::translateLaser(size_t index, const vec3& translation) {
1939 SceneObject<LaserVertex, SSBO_Laser>& laser = this->laserObjects[index];
1940
1941 // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created,
1942 // and then re-used here
1943
1944 vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1945 vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f));
1946
1947 vec3 ray = end - start;
1948 float length = glm::length(ray);
1949
1950 float xAxisRotation = asin(ray.y / length);
1951 float yAxisRotation = atan2(-ray.x, -ray.z);
1952
1953 vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
1954 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) *
1955 vec4(0.0f, 1.0f, 0.0f, 1.0f));
1956
1957 // To project point P onto line AB:
1958 // projection = A + dot(AP,AB) / dot(AB,AB) * AB
1959 vec3 projOnLaser = start + glm::dot(cam_pos - start, ray) / (length*length) * ray;
1960 vec3 laserToCam = cam_pos - projOnLaser;
1961
1962 float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam));
1963
1964 laser.model_base = rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f));
1965 laser.model_transform = translate(mat4(1.0f), translation) * laser.model_transform;
1966
1967 laser.modified = true;
1968}
1969
1970void VulkanGame::updateLaserTarget(size_t index) {
1971 SceneObject<LaserVertex, SSBO_Laser>& laser = this->laserObjects[index];
1972
1973 // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created,
1974 // and then re-used here
1975
1976 vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1977 vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f));
1978
1979 vec3 intersection(0.0f), closestIntersection(0.0f);
1980 SceneObject<AsteroidVertex, SSBO_Asteroid>* closestAsteroid = nullptr;
1981 unsigned int closestAsteroidIndex = -1;
1982
1983 for (int i = 0; i < this->asteroidObjects.size(); i++) {
1984 if (!this->asteroidObjects[i].ssbo.deleted &&
1985 this->getLaserAndAsteroidIntersection(this->asteroidObjects[i], start, end, intersection)) {
1986 // TODO: Implement a more generic algorithm for testing the closest object by getting the distance between the points
1987 // TODO: Also check which intersection is close to the start of the laser. This would make the algorithm work
1988 // regardless of which way -Z is pointing
1989 if (closestAsteroid == nullptr || intersection.z > closestIntersection.z) {
1990 // TODO: At this point, find the real intersection of the laser with one of the asteroid's sides
1991 closestAsteroid = &asteroidObjects[i];
1992 closestIntersection = intersection;
1993 closestAsteroidIndex = i;
1994 }
1995 }
1996 }
1997
1998 float width = laser.vertices[0].pos.x - laser.vertices[1].pos.x;
1999
2000 if (laser.targetAsteroid != closestAsteroid) {
2001 if (laser.targetAsteroid != nullptr) {
2002 if (index == leftLaserIdx && leftLaserEffect != nullptr) {
2003 leftLaserEffect->deleted = true;
2004 } else if (index == rightLaserIdx && rightLaserEffect != nullptr) {
2005 rightLaserEffect->deleted = true;
2006 }
2007 }
2008
2009 EffectOverTime<AsteroidVertex, SSBO_Asteroid>* eot = nullptr;
2010
2011 if (closestAsteroid != nullptr) {
2012 // TODO: Use some sort of smart pointer instead
2013 eot = new EffectOverTime<AsteroidVertex, SSBO_Asteroid>(asteroidPipeline, asteroidObjects, closestAsteroidIndex,
2014 offset_of(&SSBO_Asteroid::hp), -20.0f);
2015 effects.push_back(eot);
2016 }
2017
2018 if (index == leftLaserIdx) {
2019 leftLaserEffect = eot;
2020 } else if (index == rightLaserIdx) {
2021 rightLaserEffect = eot;
2022 }
2023
2024 laser.targetAsteroid = closestAsteroid;
2025 }
2026
2027 // Make the laser go past the end of the screen if it doesn't hit anything
2028 float length = closestAsteroid == nullptr ? 5.24f : glm::length(closestIntersection - start);
2029
2030 laser.vertices[4].pos.z = -length + width / 2;
2031 laser.vertices[5].pos.z = -length + width / 2;
2032 laser.vertices[6].pos.z = -length;
2033 laser.vertices[7].pos.z = -length;
2034
2035 // TODO: Consider if I want to set a flag and do this update in in updateScene() instead
2036 updateObjectVertices(this->laserPipeline, laser, index);
2037}
2038
2039// TODO: Determine if I should pass start and end by reference or value since they don't get changed
2040// Probably use const reference
2041bool VulkanGame::getLaserAndAsteroidIntersection(SceneObject<AsteroidVertex, SSBO_Asteroid>& asteroid,
2042 vec3& start, vec3& end, vec3& intersection) {
2043 /*
2044 ### LINE EQUATIONS ###
2045 x = x1 + u * (x2 - x1)
2046 y = y1 + u * (y2 - y1)
2047 z = z1 + u * (z2 - z1)
2048
2049 ### SPHERE EQUATION ###
2050 (x - x3)^2 + (y - y3)^2 + (z - z3)^2 = r^2
2051
2052 ### QUADRATIC EQUATION TO SOLVE ###
2053 a*u^2 + b*u + c = 0
2054 WHERE THE CONSTANTS ARE
2055 a = (x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2
2056 b = 2*( (x2 - x1)*(x1 - x3) + (y2 - y1)*(y1 - y3) + (z2 - z1)*(z1 - z3) )
2057 c = x3^2 + y3^2 + z3^2 + x1^2 + y1^2 + z1^2 - 2(x3*x1 + y3*y1 + z3*z1) - r^2
2058
2059 u = (-b +- sqrt(b^2 - 4*a*c)) / 2a
2060
2061 If the value under the root is >= 0, we got an intersection
2062 If the value > 0, there are two solutions. Take the one closer to 0, since that's the
2063 one closer to the laser start point
2064 */
2065
2066 vec3& center = asteroid.center;
2067
2068 float a = pow(end.x - start.x, 2) + pow(end.y - start.y, 2) + pow(end.z - start.z, 2);
2069 float b = 2 * ((start.x - end.x) * (start.x - center.x) + (end.y - start.y) * (start.y - center.y) +
2070 (end.z - start.z) * (start.z - center.z));
2071 float c = pow(center.x, 2) + pow(center.y, 2) + pow(center.z, 2) + pow(start.x, 2) + pow(start.y, 2) +
2072 pow(start.z, 2) - 2 * (center.x * start.x + center.y * start.y + center.z * start.z) -
2073 pow(asteroid.radius, 2);
2074 float discriminant = pow(b, 2) - 4 * a * c;
2075
2076 if (discriminant >= 0.0f) {
2077 // In this case, the negative root will always give the point closer to the laser start point
2078 float u = (-b - sqrt(discriminant)) / (2 * a);
2079
2080 // Check that the intersection is within the line segment corresponding to the laser
2081 if (0.0f <= u && u <= 1.0f) {
2082 intersection = start + u * (end - start);
2083 return true;
2084 }
2085 }
2086
2087 return false;
2088}
2089
2090void VulkanGame::createBufferSet(VkDeviceSize bufferSize, VkBufferUsageFlags flags,
2091 vector<VkBuffer>& buffers, vector<VkDeviceMemory>& buffersMemory, vector<VkDescriptorBufferInfo>& bufferInfoList) {
2092 buffers.resize(swapChainImageCount);
2093 buffersMemory.resize(swapChainImageCount);
2094 bufferInfoList.resize(swapChainImageCount);
2095
2096 for (size_t i = 0; i < swapChainImageCount; i++) {
2097 VulkanUtils::createBuffer(device, physicalDevice, bufferSize, flags,
2098 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2099 buffers[i], buffersMemory[i]);
2100
2101 bufferInfoList[i].buffer = buffers[i];
2102 bufferInfoList[i].offset = 0; // This is the offset from the start of the buffer, so always 0 for now
2103 bufferInfoList[i].range = bufferSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
2104 }
2105}
2106
2107void VulkanGame::addExplosion(mat4 model_mat, float duration, float cur_time) {
2108 vector<ExplosionVertex> vertices;
2109 vertices.reserve(EXPLOSION_PARTICLE_COUNT);
2110
2111 float particle_start_time = 0.0f;
2112
2113 for (int i = 0; i < EXPLOSION_PARTICLE_COUNT; i++) {
2114 float randx = ((float)rand() / (float)RAND_MAX) - 0.5f;
2115 float randy = ((float)rand() / (float)RAND_MAX) - 0.5f;
2116
2117 vertices.push_back({ vec3(randx, randy, 0.0f), particle_start_time});
2118
2119 particle_start_time += .01f;
2120 // TODO: Get this working
2121 // particle_start_time += 1.0f * EXPLOSION_PARTICLE_COUNT / duration
2122 }
2123
2124 // Fill the indices with the the first EXPLOSION_PARTICLE_COUNT ints
2125 vector<uint16_t> indices(EXPLOSION_PARTICLE_COUNT);
2126 iota(indices.begin(), indices.end(), 0);
2127
2128 SceneObject<ExplosionVertex, SSBO_Explosion>& explosion = addObject(
2129 explosionObjects, explosionPipeline,
2130 addObjectIndex(explosionObjects.size(), vertices),
2131 indices, {
2132 mat4(1.0f),
2133 cur_time,
2134 duration,
2135 false
2136 }, true);
2137
2138 explosion.model_base = model_mat;
2139 explosion.model_transform = mat4(1.0f);
2140
2141 explosion.modified = true;
2142}
2143
2144void VulkanGame::recreateSwapChain() {
2145 if (vkDeviceWaitIdle(device) != VK_SUCCESS) {
2146 throw runtime_error("failed to wait for device!");
2147 }
2148
2149 cleanupSwapChain();
2150
2151 createSwapChain();
2152 createImageViews();
2153 createRenderPass();
2154
2155 createCommandPools();
2156
2157 // The depth buffer does need to be recreated with the swap chain since its dimensions depend on the window size
2158 // and resizing the window is a common reason to recreate the swapchain
2159 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
2160 depthImage, graphicsQueue);
2161 createFramebuffers();
2162
2163 // TODO: Move UBO creation/management into GraphicsPipeline_Vulkan, like I did with SSBOs
2164
2165 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2166 uniformBuffers_modelPipeline, uniformBuffersMemory_modelPipeline, uniformBufferInfoList_modelPipeline);
2167
2168 modelPipeline.updateRenderPass(renderPass);
2169 modelPipeline.createPipeline("shaders/scene-vert.spv", "shaders/scene-frag.spv");
2170 modelPipeline.createDescriptorPool(swapChainImages);
2171 modelPipeline.createDescriptorSets(swapChainImages);
2172
2173 overlayPipeline.updateRenderPass(renderPass);
2174 overlayPipeline.createPipeline("shaders/overlay-vert.spv", "shaders/overlay-frag.spv");
2175 overlayPipeline.createDescriptorPool(swapChainImages);
2176 overlayPipeline.createDescriptorSets(swapChainImages);
2177
2178 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2179 uniformBuffers_shipPipeline, uniformBuffersMemory_shipPipeline, uniformBufferInfoList_shipPipeline);
2180
2181 shipPipeline.updateRenderPass(renderPass);
2182 shipPipeline.createPipeline("shaders/ship-vert.spv", "shaders/ship-frag.spv");
2183 shipPipeline.createDescriptorPool(swapChainImages);
2184 shipPipeline.createDescriptorSets(swapChainImages);
2185
2186 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2187 uniformBuffers_asteroidPipeline, uniformBuffersMemory_asteroidPipeline, uniformBufferInfoList_asteroidPipeline);
2188
2189 asteroidPipeline.updateRenderPass(renderPass);
2190 asteroidPipeline.createPipeline("shaders/asteroid-vert.spv", "shaders/asteroid-frag.spv");
2191 asteroidPipeline.createDescriptorPool(swapChainImages);
2192 asteroidPipeline.createDescriptorSets(swapChainImages);
2193
2194 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2195 uniformBuffers_laserPipeline, uniformBuffersMemory_laserPipeline, uniformBufferInfoList_laserPipeline);
2196
2197 laserPipeline.updateRenderPass(renderPass);
2198 laserPipeline.createPipeline("shaders/laser-vert.spv", "shaders/laser-frag.spv");
2199 laserPipeline.createDescriptorPool(swapChainImages);
2200 laserPipeline.createDescriptorSets(swapChainImages);
2201
2202 createBufferSet(sizeof(UBO_Explosion), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2203 uniformBuffers_explosionPipeline, uniformBuffersMemory_explosionPipeline, uniformBufferInfoList_explosionPipeline);
2204
2205 explosionPipeline.updateRenderPass(renderPass);
2206 explosionPipeline.createPipeline("shaders/explosion-vert.spv", "shaders/explosion-frag.spv");
2207 explosionPipeline.createDescriptorPool(swapChainImages);
2208 explosionPipeline.createDescriptorSets(swapChainImages);
2209
2210 createCommandBuffers();
2211
2212 createSyncObjects();
2213
2214 imageIndex = 0;
2215}
2216
2217void VulkanGame::cleanupSwapChain() {
2218 VulkanUtils::destroyVulkanImage(device, depthImage);
2219
2220 for (VkFramebuffer framebuffer : swapChainFramebuffers) {
2221 vkDestroyFramebuffer(device, framebuffer, nullptr);
2222 }
2223
2224 for (uint32_t i = 0; i < swapChainImageCount; i++) {
2225 vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
2226 vkDestroyCommandPool(device, commandPools[i], nullptr);
2227 }
2228
2229 overlayPipeline.cleanup();
2230 modelPipeline.cleanup();
2231 shipPipeline.cleanup();
2232 asteroidPipeline.cleanup();
2233 laserPipeline.cleanup();
2234 explosionPipeline.cleanup();
2235
2236 for (size_t i = 0; i < uniformBuffers_modelPipeline.size(); i++) {
2237 vkDestroyBuffer(device, uniformBuffers_modelPipeline[i], nullptr);
2238 vkFreeMemory(device, uniformBuffersMemory_modelPipeline[i], nullptr);
2239 }
2240
2241 for (size_t i = 0; i < uniformBuffers_shipPipeline.size(); i++) {
2242 vkDestroyBuffer(device, uniformBuffers_shipPipeline[i], nullptr);
2243 vkFreeMemory(device, uniformBuffersMemory_shipPipeline[i], nullptr);
2244 }
2245
2246 for (size_t i = 0; i < uniformBuffers_asteroidPipeline.size(); i++) {
2247 vkDestroyBuffer(device, uniformBuffers_asteroidPipeline[i], nullptr);
2248 vkFreeMemory(device, uniformBuffersMemory_asteroidPipeline[i], nullptr);
2249 }
2250
2251 for (size_t i = 0; i < uniformBuffers_laserPipeline.size(); i++) {
2252 vkDestroyBuffer(device, uniformBuffers_laserPipeline[i], nullptr);
2253 vkFreeMemory(device, uniformBuffersMemory_laserPipeline[i], nullptr);
2254 }
2255
2256 for (size_t i = 0; i < uniformBuffers_explosionPipeline.size(); i++) {
2257 vkDestroyBuffer(device, uniformBuffers_explosionPipeline[i], nullptr);
2258 vkFreeMemory(device, uniformBuffersMemory_explosionPipeline[i], nullptr);
2259 }
2260
2261 for (size_t i = 0; i < swapChainImageCount; i++) {
2262 vkDestroySemaphore(device, imageAcquiredSemaphores[i], nullptr);
2263 vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr);
2264 vkDestroyFence(device, inFlightFences[i], nullptr);
2265 }
2266
2267 vkDestroyRenderPass(device, renderPass, nullptr);
2268
2269 for (VkImageView imageView : swapChainImageViews) {
2270 vkDestroyImageView(device, imageView, nullptr);
2271 }
2272
2273 vkDestroySwapchainKHR(device, swapChain, nullptr);
2274}
Note: See TracBrowser for help on using the repository browser.