source: opengl-game/vulkan-game.cpp@ 699e83a

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

Add a GameScreen class to render the main gameplay

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