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

feature/imgui-sdl
Last change on this file since f809ae6 was f809ae6, checked in by Dmitry Portnoy <dmitry.portnoy@…>, 5 years ago

Show the score and frame rate on the game screen

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