source: opengl-game/vulkan-game.cpp@ 670c09a

feature/imgui-sdl
Last change on this file since 670c09a was aa7707d, checked in by Dmitry Portnoy <dmp1488@…>, 4 years ago

Update the Vulkan SDK version and get the latest code compiling on Windows

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