source: opengl-game/vulkan-game.cpp@ 9c0a614

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

Switch to using one command pool per swap chain image in VulkanGame

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