source: opengl-game/vulkan-game.cpp@ 3f32dfd

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

In VulkanGame, use the rewritten functions for querting swap chain capabilities, and use a separate command pool for setting up image resources

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