source: opengl-game/vulkan-game.cpp@ 8b823e7

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

Create an error-checking macro to check Vulkan function results, which will print a custom message, the error code, and the line number for all results besides VK_SUCCESS, and update the imgui error-checking callback with similar functionality.

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