source: opengl-game/vulkan-game.cpp@ 1f81ecc

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

Add the ability for the ship to fire a laser from either of its wings

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