source: opengl-game/vulkan-game.cpp@ 81869ef

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

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