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

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

Start implementing an ImGUI ui on top of SDL and Vulkan using some example code

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