source: opengl-game/vulkan-game.cpp@ 6a39266

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

In vulkan-game, throw an error if either a graphics or present queue could not be found

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