source: opengl-game/vulkan-game.cpp@ 4e2c709

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

In VulkanGame, separate renderScene() into renderFrame() and presentFrame(), and in SDLGame, rename FrameRender() and FramePresent() to renderFrame() and presentFrame()

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