source: opengl-game/vulkan-game.cpp@ 1cb64e6

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

In VulkanGame, recreate the command buffers every frame

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