source: opengl-game/vulkan-game.cpp@ 4e705d6

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

Rename initWindow to initUI and move code for initializing the UI overlay into it

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