source: opengl-game/vulkan-game.cpp@ d8cf709

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

Change UIEvent to also include the original event from the UI library the game gui is currently using, such as SDL or GLFW.

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