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

feature/imgui-sdl
Last change on this file since c1ec4f6 was c1ec4f6, checked in by Dmitry Portnoy <dportnoy@…>, 3 years ago

Remove the modified field from the SceneObject object

  • Property mode set to 100644
File size: 101.5 KB
Line 
1#include "vulkan-game.hpp"
2
3#include <array>
4#include <iostream>
5#include <numeric>
6#include <set>
7#include <stdexcept>
8
9#include "IMGUI/imgui_impl_sdl.h"
10#include "IMGUI/imgui_internal.h" // For CalcItemSize
11
12#include "logger.hpp"
13
14#include "gui/imgui/button-imgui.hpp"
15
16using namespace std;
17
18// TODO: Update all instances of the "... != VK_SUCCESS" check to something similar to
19// the agp error checking function, which prints an appropriate error message based on the error code
20
21// TODO: Update all occurances of instance variables to use this-> (Actually, not sure if I really want to do this)
22
23/* TODO: Try doing the following tasks based on the Vulkan implementation of IMGUI (Also maybe looks at Sascha Willems' code to see how he does these things)
24 *
25 * - When recreating the swapchain, pass the old one in and destroy the old one after the new one is created
26 * - Recreate semaphores when recreating the swapchain
27 * - imgui uses one image acquired and one render complete sem and once fence per frame\
28 * - IMGUI creates one command pool per framebuffer
29 */
30
31/* NOTES WHEN ADDING IMGUI
32 *
33 * Possibly cleanup the imgui pipeline in cleanupSwapchain or call some imgui function that does this for me
34 * call ImGui_ImplVulkan_RenderDrawData, without passing in a pipeline, to do the rendering
35 */
36
37static void check_imgui_vk_result(VkResult res) {
38 if (res == VK_SUCCESS) {
39 return;
40 }
41
42 ostringstream oss;
43 oss << "[imgui] Vulkan error! VkResult is \"" << VulkanUtils::resultString(res) << "\"" << __LINE__;
44 if (res < 0) {
45 throw runtime_error("Fatal: " + oss.str());
46 } else {
47 cerr << oss.str();
48 }
49}
50
51VulkanGame::VulkanGame()
52 : swapChainImageCount(0)
53 , swapChainMinImageCount(0)
54 , swapChainSurfaceFormat({})
55 , swapChainPresentMode(VK_PRESENT_MODE_MAX_ENUM_KHR)
56 , swapChainExtent{ 0, 0 }
57 , swapChain(VK_NULL_HANDLE)
58 , vulkanSurface(VK_NULL_HANDLE)
59 , sdlVersion({ 0, 0, 0 })
60 , instance(VK_NULL_HANDLE)
61 , physicalDevice(VK_NULL_HANDLE)
62 , device(VK_NULL_HANDLE)
63 , debugMessenger(VK_NULL_HANDLE)
64 , resourceCommandPool(VK_NULL_HANDLE)
65 , renderPass(VK_NULL_HANDLE)
66 , graphicsQueue(VK_NULL_HANDLE)
67 , presentQueue(VK_NULL_HANDLE)
68 , depthImage({})
69 , shouldRecreateSwapChain(false)
70 , frameCount(0)
71 , currentFrame(0)
72 , imageIndex(0)
73 , fpsStartTime(0.0f)
74 , curTime(0.0f)
75 , done(false)
76 , currentRenderScreenFn(nullptr)
77 , gui(nullptr)
78 , window(nullptr)
79 , objects_modelPipeline()
80 , objects_shipPipeline()
81 , objects_asteroidPipeline()
82 , objects_laserPipeline()
83 , objects_explosionPipeline()
84 , score(0)
85 , fps(0.0f) {
86 object_VP_mats = {};
87 ship_VP_mats = {};
88 asteroid_VP_mats = {};
89 laser_VP_mats = {};
90 explosion_UBO = {};
91}
92
93VulkanGame::~VulkanGame() {
94}
95
96void VulkanGame::run(int width, int height, unsigned char guiFlags) {
97 seedRandomNums();
98
99 cout << "Vulkan Game" << endl;
100
101 cout << "DEBUGGING IS " << (ENABLE_VALIDATION_LAYERS ? "ON" : "OFF") << endl;
102
103 if (initUI(width, height, guiFlags) == RTWO_ERROR) {
104 return;
105 }
106
107 initVulkan();
108
109 VkPhysicalDeviceProperties deviceProperties;
110 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
111
112 objects_modelPipeline = VulkanBuffer<SSBO_ModelObject>(10, deviceProperties.limits.maxStorageBufferRange,
113 deviceProperties.limits.minStorageBufferOffsetAlignment);
114 objects_shipPipeline = VulkanBuffer<SSBO_ModelObject>(10, deviceProperties.limits.maxStorageBufferRange,
115 deviceProperties.limits.minStorageBufferOffsetAlignment);
116 objects_asteroidPipeline = VulkanBuffer<SSBO_Asteroid>(10, deviceProperties.limits.maxStorageBufferRange,
117 deviceProperties.limits.minStorageBufferOffsetAlignment);
118 objects_laserPipeline = VulkanBuffer<SSBO_Laser>(2, deviceProperties.limits.maxStorageBufferRange,
119 deviceProperties.limits.minStorageBufferOffsetAlignment);
120 objects_explosionPipeline = VulkanBuffer<SSBO_Explosion>(2, deviceProperties.limits.maxStorageBufferRange,
121 deviceProperties.limits.minStorageBufferOffsetAlignment);
122
123 initImGuiOverlay();
124
125 // TODO: Figure out how much of ubo creation and associated variables should be in the pipeline class
126 // Maybe combine the ubo-related objects into a new class
127
128 initGraphicsPipelines();
129
130 initMatrices();
131
132 cout << "INITIALIZING OBJECTS" << endl;
133
134 modelPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::pos));
135 modelPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::color));
136 modelPipeline.addAttribute(VK_FORMAT_R32G32_SFLOAT, offset_of(&ModelVertex::texCoord));
137 modelPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::normal));
138 modelPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&ModelVertex::objIndex));
139
140 createBufferSet(sizeof(UBO_VP_mats),
141 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
142 uniformBuffers_modelPipeline);
143
144 modelPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
145 VK_SHADER_STAGE_VERTEX_BIT, &uniformBuffers_modelPipeline.infoSet);
146 modelPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
147 VK_SHADER_STAGE_VERTEX_BIT, &storageBuffers_modelPipeline.infoSet);
148 modelPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
149 VK_SHADER_STAGE_FRAGMENT_BIT, &floorTextureImageDescriptor);
150
151 SceneObject<ModelVertex, SSBO_ModelObject>* texturedSquare = nullptr;
152
153 texturedSquare = &addObject(modelObjects, modelPipeline,
154 addObjectIndex<ModelVertex>(modelObjects.size(),
155 addVertexNormals<ModelVertex>({
156 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}},
157 {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}},
158 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
159 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
160 {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}},
161 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}}
162 })),
163 {
164 0, 1, 2, 3, 4, 5
165 }, objects_modelPipeline, {
166 mat4(1.0f)
167 });
168
169 texturedSquare->model_base =
170 translate(mat4(1.0f), vec3(0.0f, 0.0f, -2.0f));
171
172 texturedSquare = &addObject(modelObjects, modelPipeline,
173 addObjectIndex<ModelVertex>(modelObjects.size(),
174 addVertexNormals<ModelVertex>({
175 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}},
176 {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}},
177 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
178 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
179 {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}},
180 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}}
181 })),
182 {
183 0, 1, 2, 3, 4, 5
184 }, objects_modelPipeline, {
185 mat4(1.0f)
186 });
187
188 texturedSquare->model_base =
189 translate(mat4(1.0f), vec3(0.0f, 0.0f, -1.5f));
190
191 modelPipeline.createDescriptorSetLayout();
192 modelPipeline.createPipeline("shaders/model-vert.spv", "shaders/model-frag.spv");
193 modelPipeline.createDescriptorPool(swapChainImages.size());
194 modelPipeline.createDescriptorSets(swapChainImages.size());
195
196 // START UNREVIEWED SECTION
197 shipPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::pos));
198 shipPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::color));
199 shipPipeline.addAttribute(VK_FORMAT_R32G32_SFLOAT, offset_of(&ModelVertex::texCoord));
200 shipPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::normal));
201 shipPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&ModelVertex::objIndex));
202
203 createBufferSet(sizeof(UBO_VP_mats),
204 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
205 uniformBuffers_shipPipeline);
206
207 shipPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
208 VK_SHADER_STAGE_VERTEX_BIT, &uniformBuffers_shipPipeline.infoSet);
209 shipPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
210 VK_SHADER_STAGE_VERTEX_BIT, &storageBuffers_shipPipeline.infoSet);
211
212 // TODO: With the normals, indexing basically becomes pointless since no vertices will have exactly
213 // the same data. Add an option to make some pipelines not use indexing
214 SceneObject<ModelVertex, SSBO_ModelObject>& ship = addObject(shipObjects, shipPipeline,
215 addObjectIndex<ModelVertex>(shipObjects.size(),
216 addVertexNormals<ModelVertex>({
217
218 //back
219 {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
220 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
221 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
222 {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
223 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
224 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
225
226 // left back
227 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
228 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
229 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
230 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
231 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
232 {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
233
234 // right back
235 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
236 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
237 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
238 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
239 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
240 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
241
242 // left mid
243 {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}},
244 {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}},
245 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
246 {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}},
247 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
248 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
249
250 // right mid
251 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
252 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
253 {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}},
254 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
255 {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}},
256 {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}},
257
258 // left front
259 {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 1.0f}},
260 {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}},
261 {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}},
262
263 // right front
264 {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}},
265 {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}},
266 {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 1.0f}},
267
268 // top back
269 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
270 {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 1.0f}},
271 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 1.0f}},
272 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
273 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 1.0f}},
274 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
275
276 // bottom back
277 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
278 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
279 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
280 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
281 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
282 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
283
284 // top mid
285 {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}},
286 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
287 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
288 {{ -0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}},
289 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
290 {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}},
291
292 // bottom mid
293 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
294 {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}},
295 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
296 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
297 {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}},
298 {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}},
299
300 // top front
301 {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}},
302 {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}},
303 {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 0.3f}},
304
305 // bottom front
306 {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}},
307 {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}},
308 {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 0.3f}},
309
310 // left wing start back
311 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
312 {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
313 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
314 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
315 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
316 {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
317
318 // left wing start top
319 {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
320 {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
321 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
322 {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
323 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
324 {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
325
326 // left wing start front
327 {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
328 {{ -0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
329 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
330 {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
331 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
332 {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
333
334 // left wing start bottom
335 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
336 {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
337 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
338 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
339 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
340 {{ -0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
341
342 // left wing end outside
343 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
344 {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
345 {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
346
347 // left wing end top
348 {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
349 {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
350 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
351
352 // left wing end front
353 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
354 {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
355 {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
356
357 // left wing end bottom
358 {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
359 {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
360 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
361
362 // right wing start back
363 {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
364 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
365 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
366 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
367 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
368 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
369
370 // right wing start top
371 {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
372 {{ 0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
373 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
374 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
375 {{ 0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
376 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
377
378 // right wing start front
379 {{ 0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
380 {{ 0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
381 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
382 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
383 {{ 0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
384 {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
385
386 // right wing start bottom
387 {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
388 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
389 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
390 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
391 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
392 {{ 0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
393
394 // right wing end outside
395 {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
396 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
397 {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
398
399 // right wing end top
400 {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
401 {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
402 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
403
404 // right wing end front
405 {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
406 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
407 {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
408
409 // right wing end bottom
410 {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
411 {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
412 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
413 })),
414 {
415 0, 1, 2, 3, 4, 5,
416 6, 7, 8, 9, 10, 11,
417 12, 13, 14, 15, 16, 17,
418 18, 19, 20, 21, 22, 23,
419 24, 25, 26, 27, 28, 29,
420 30, 31, 32,
421 33, 34, 35,
422 36, 37, 38, 39, 40, 41,
423 42, 43, 44, 45, 46, 47,
424 48, 49, 50, 51, 52, 53,
425 54, 55, 56, 57, 58, 59,
426 60, 61, 62,
427 63, 64, 65,
428 66, 67, 68, 69, 70, 71,
429 72, 73, 74, 75, 76, 77,
430 78, 79, 80, 81, 82, 83,
431 84, 85, 86, 87, 88, 89,
432 90, 91, 92,
433 93, 94, 95,
434 96, 97, 98,
435 99, 100, 101,
436 102, 103, 104, 105, 106, 107,
437 108, 109, 110, 111, 112, 113,
438 114, 115, 116, 117, 118, 119,
439 120, 121, 122, 123, 124, 125,
440 126, 127, 128,
441 129, 130, 131,
442 132, 133, 134,
443 135, 136, 137,
444 }, objects_shipPipeline, {
445 mat4(1.0f)
446 });
447
448 ship.model_base =
449 translate(mat4(1.0f), vec3(0.0f, -1.2f, 1.65f)) *
450 scale(mat4(1.0f), vec3(0.1f, 0.1f, 0.1f));
451
452 shipPipeline.createDescriptorSetLayout();
453 shipPipeline.createPipeline("shaders/ship-vert.spv", "shaders/ship-frag.spv");
454 shipPipeline.createDescriptorPool(swapChainImages.size());
455 shipPipeline.createDescriptorSets(swapChainImages.size());
456
457 asteroidPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::pos));
458 asteroidPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::color));
459 asteroidPipeline.addAttribute(VK_FORMAT_R32G32_SFLOAT, offset_of(&ModelVertex::texCoord));
460 asteroidPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::normal));
461 asteroidPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&ModelVertex::objIndex));
462
463 createBufferSet(sizeof(UBO_VP_mats),
464 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
465 uniformBuffers_asteroidPipeline);
466
467 asteroidPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
468 VK_SHADER_STAGE_VERTEX_BIT, &uniformBuffers_asteroidPipeline.infoSet);
469 asteroidPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
470 VK_SHADER_STAGE_VERTEX_BIT, &storageBuffers_asteroidPipeline.infoSet);
471
472 asteroidPipeline.createDescriptorSetLayout();
473 asteroidPipeline.createPipeline("shaders/asteroid-vert.spv", "shaders/asteroid-frag.spv");
474 asteroidPipeline.createDescriptorPool(swapChainImages.size());
475 asteroidPipeline.createDescriptorSets(swapChainImages.size());
476
477 laserPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&LaserVertex::pos));
478 laserPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&LaserVertex::texCoord));
479 laserPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&LaserVertex::objIndex));
480
481 createBufferSet(sizeof(UBO_VP_mats),
482 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
483 uniformBuffers_laserPipeline);
484
485 laserPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
486 VK_SHADER_STAGE_VERTEX_BIT, &uniformBuffers_laserPipeline.infoSet);
487 laserPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
488 VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, &storageBuffers_laserPipeline.infoSet);
489 laserPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
490 VK_SHADER_STAGE_FRAGMENT_BIT, &laserTextureImageDescriptor);
491
492 laserPipeline.createDescriptorSetLayout();
493 laserPipeline.createPipeline("shaders/laser-vert.spv", "shaders/laser-frag.spv");
494 laserPipeline.createDescriptorPool(swapChainImages.size());
495 laserPipeline.createDescriptorSets(swapChainImages.size());
496
497 explosionPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ExplosionVertex::particleStartVelocity));
498 explosionPipeline.addAttribute(VK_FORMAT_R32_SFLOAT, offset_of(&ExplosionVertex::particleStartTime));
499 explosionPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&ExplosionVertex::objIndex));
500
501 createBufferSet(sizeof(UBO_Explosion),
502 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
503 uniformBuffers_explosionPipeline);
504
505 explosionPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
506 VK_SHADER_STAGE_VERTEX_BIT, &uniformBuffers_explosionPipeline.infoSet);
507 explosionPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
508 VK_SHADER_STAGE_VERTEX_BIT, &storageBuffers_explosionPipeline.infoSet);
509
510 explosionPipeline.createDescriptorSetLayout();
511 explosionPipeline.createPipeline("shaders/explosion-vert.spv", "shaders/explosion-frag.spv");
512 explosionPipeline.createDescriptorPool(swapChainImages.size());
513 explosionPipeline.createDescriptorSets(swapChainImages.size());
514
515 // END UNREVIEWED SECTION
516
517 currentRenderScreenFn = &VulkanGame::renderMainScreen;
518
519 ImGuiIO& io = ImGui::GetIO();
520
521 initGuiValueLists(valueLists);
522
523 valueLists["stats value list"].push_back(UIValue(UIVALUE_INT, "Score", &score));
524 valueLists["stats value list"].push_back(UIValue(UIVALUE_DOUBLE, "FPS", &fps));
525 valueLists["stats value list"].push_back(UIValue(UIVALUE_DOUBLE, "IMGUI FPS", &io.Framerate));
526
527 renderLoop();
528 cleanup();
529
530 close_log();
531}
532
533bool VulkanGame::initUI(int width, int height, unsigned char guiFlags) {
534 // TODO: Create a game-gui function to get the gui version and retrieve it that way
535
536 SDL_VERSION(&sdlVersion); // This gets the compile-time version
537 SDL_GetVersion(&sdlVersion); // This gets the runtime version
538
539 cout << "SDL "<<
540 to_string(sdlVersion.major) << "." <<
541 to_string(sdlVersion.minor) << "." <<
542 to_string(sdlVersion.patch) << endl;
543
544 // TODO: Refactor the logger api to be more flexible,
545 // esp. since gl_log() and gl_log_err() have issues printing anything besides strings
546 restart_gl_log();
547 gl_log("starting SDL\n%s.%s.%s",
548 to_string(sdlVersion.major).c_str(),
549 to_string(sdlVersion.minor).c_str(),
550 to_string(sdlVersion.patch).c_str());
551
552 // TODO: Use open_Log() and related functions instead of gl_log ones
553 // TODO: In addition, delete the gl_log functions
554 open_log();
555 get_log() << "starting SDL" << endl;
556 get_log() <<
557 (int)sdlVersion.major << "." <<
558 (int)sdlVersion.minor << "." <<
559 (int)sdlVersion.patch << endl;
560
561 // TODO: Put all fonts, textures, and images in the assets folder
562 gui = new GameGui_SDL();
563
564 if (gui->init() == RTWO_ERROR) {
565 // TODO: Also print these sorts of errors to the log
566 cout << "UI library could not be initialized!" << endl;
567 cout << gui->getError() << endl;
568 // TODO: Rename RTWO_ERROR to something else
569 return RTWO_ERROR;
570 }
571
572 window = (SDL_Window*)gui->createWindow("Vulkan Game", width, height, guiFlags & GUI_FLAGS_WINDOW_FULLSCREEN);
573 if (window == nullptr) {
574 cout << "Window could not be created!" << endl;
575 cout << gui->getError() << endl;
576 return RTWO_ERROR;
577 }
578
579 cout << "Target window size: (" << width << ", " << height << ")" << endl;
580 cout << "Actual window size: (" << gui->getWindowWidth() << ", " << gui->getWindowHeight() << ")" << endl;
581
582 return RTWO_SUCCESS;
583}
584
585void VulkanGame::initVulkan() {
586 const vector<const char*> validationLayers = {
587 "VK_LAYER_KHRONOS_validation"
588 };
589 const vector<const char*> deviceExtensions = {
590 VK_KHR_SWAPCHAIN_EXTENSION_NAME
591 };
592
593 createVulkanInstance(validationLayers);
594 setupDebugMessenger();
595 createVulkanSurface();
596 pickPhysicalDevice(deviceExtensions);
597 createLogicalDevice(validationLayers, deviceExtensions);
598 chooseSwapChainProperties();
599 createSwapChain();
600 createImageViews();
601
602 createResourceCommandPool();
603 createImageResources();
604
605 createRenderPass();
606 createCommandPools();
607 createFramebuffers();
608 createCommandBuffers();
609 createSyncObjects();
610}
611
612void VulkanGame::initGraphicsPipelines() {
613 modelPipeline = GraphicsPipeline_Vulkan<ModelVertex>(
614 VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
615 { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, 24, 24);
616
617 createBufferSet(objects_modelPipeline.capacity * sizeof(SSBO_ModelObject),
618 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT
619 | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
620 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
621 storageBuffers_modelPipeline);
622
623 shipPipeline = GraphicsPipeline_Vulkan<ModelVertex>(
624 VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
625 { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, 138, 138);
626
627 createBufferSet(objects_shipPipeline.capacity * sizeof(SSBO_ModelObject),
628 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT
629 | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
630 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
631 storageBuffers_shipPipeline);
632
633 asteroidPipeline = GraphicsPipeline_Vulkan<ModelVertex>(
634 VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
635 { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, 24, 36);
636
637 createBufferSet(objects_asteroidPipeline.capacity * sizeof(SSBO_Asteroid),
638 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT
639 | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
640 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
641 storageBuffers_asteroidPipeline);
642
643 laserPipeline = GraphicsPipeline_Vulkan<LaserVertex>(
644 VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
645 { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, 8, 18);
646
647 createBufferSet(objects_laserPipeline.capacity * sizeof(SSBO_Laser),
648 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT
649 | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
650 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
651 storageBuffers_laserPipeline);
652
653 explosionPipeline = GraphicsPipeline_Vulkan<ExplosionVertex>(
654 VK_PRIMITIVE_TOPOLOGY_POINT_LIST, physicalDevice, device, renderPass,
655 { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height },
656 EXPLOSION_PARTICLE_COUNT, EXPLOSION_PARTICLE_COUNT);
657
658 createBufferSet(objects_explosionPipeline.capacity * sizeof(SSBO_Explosion),
659 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT
660 | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
661 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
662 storageBuffers_explosionPipeline);
663}
664
665// TODO: Maybe changes the name to initScene() or something similar
666void VulkanGame::initMatrices() {
667 cam_pos = vec3(0.0f, 0.0f, 2.0f);
668
669 float cam_yaw = 0.0f;
670 float cam_pitch = -50.0f;
671
672 mat4 yaw_mat = rotate(mat4(1.0f), radians(-cam_yaw), vec3(0.0f, 1.0f, 0.0f));
673 mat4 pitch_mat = rotate(mat4(1.0f), radians(-cam_pitch), vec3(1.0f, 0.0f, 0.0f));
674
675 mat4 R_view = pitch_mat * yaw_mat;
676 mat4 T_view = translate(mat4(1.0f), vec3(-cam_pos.x, -cam_pos.y, -cam_pos.z));
677 viewMat = R_view * T_view;
678
679 projMat = perspective(radians(FOV_ANGLE), (float)swapChainExtent.width / (float)swapChainExtent.height, NEAR_CLIP, FAR_CLIP);
680 projMat[1][1] *= -1; // flip the y-axis so that +y is up
681
682 object_VP_mats.view = viewMat;
683 object_VP_mats.proj = projMat;
684
685 ship_VP_mats.view = viewMat;
686 ship_VP_mats.proj = projMat;
687
688 asteroid_VP_mats.view = viewMat;
689 asteroid_VP_mats.proj = projMat;
690
691 laser_VP_mats.view = viewMat;
692 laser_VP_mats.proj = projMat;
693
694 explosion_UBO.view = viewMat;
695 explosion_UBO.proj = projMat;
696}
697
698void VulkanGame::renderLoop() {
699 startTime = steady_clock::now();
700 curTime = duration<float, seconds::period>(steady_clock::now() - startTime).count();
701
702 fpsStartTime = curTime;
703 frameCount = 0;
704
705 lastSpawn_asteroid = curTime;
706
707 ImGuiIO& io = ImGui::GetIO();
708
709 done = false;
710 while (!done) {
711
712 prevTime = curTime;
713 curTime = duration<float, seconds::period>(steady_clock::now() - startTime).count();
714 elapsedTime = curTime - prevTime;
715
716 if (curTime - fpsStartTime >= 1.0f) {
717 fps = (float)frameCount / (curTime - fpsStartTime);
718
719 frameCount = 0;
720 fpsStartTime = curTime;
721 }
722
723 frameCount++;
724
725 gui->processEvents();
726
727 UIEvent uiEvent;
728 while (gui->pollEvent(&uiEvent)) {
729 GameEvent& e = uiEvent.event;
730 SDL_Event sdlEvent = uiEvent.rawEvent.sdl;
731
732 ImGui_ImplSDL2_ProcessEvent(&sdlEvent);
733 if ((e.type == UI_EVENT_MOUSEBUTTONDOWN || e.type == UI_EVENT_MOUSEBUTTONUP || e.type == UI_EVENT_UNKNOWN) &&
734 io.WantCaptureMouse) {
735 if (sdlEvent.type == SDL_MOUSEWHEEL || sdlEvent.type == SDL_MOUSEBUTTONDOWN ||
736 sdlEvent.type == SDL_MOUSEBUTTONUP) {
737 continue;
738 }
739 }
740 if ((e.type == UI_EVENT_KEYDOWN || e.type == UI_EVENT_KEYUP) && io.WantCaptureKeyboard) {
741 if (sdlEvent.type == SDL_KEYDOWN || sdlEvent.type == SDL_KEYUP) {
742 continue;
743 }
744 }
745 if (io.WantTextInput) {
746 // show onscreen keyboard if on mobile
747 }
748
749 switch (e.type) {
750 case UI_EVENT_QUIT:
751 cout << "Quit event detected" << endl;
752 done = true;
753 break;
754 case UI_EVENT_WINDOWRESIZE:
755 cout << "Window resize event detected" << endl;
756 shouldRecreateSwapChain = true;
757 break;
758 case UI_EVENT_KEYDOWN:
759 if (e.key.repeat) {
760 break;
761 }
762
763 if (e.key.keycode == SDL_SCANCODE_ESCAPE) {
764 done = true;
765 } else if (e.key.keycode == SDL_SCANCODE_SPACE) {
766 cout << "Adding a plane" << endl;
767 float zOffset = -2.0f + (0.5f * modelObjects.size());
768
769 SceneObject<ModelVertex, SSBO_ModelObject>& texturedSquare =
770 addObject(modelObjects, modelPipeline,
771 addObjectIndex<ModelVertex>(modelObjects.size(),
772 addVertexNormals<ModelVertex>({
773 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}},
774 {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}},
775 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
776 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
777 {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}},
778 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}}
779 })),
780 {
781 0, 1, 2, 3, 4, 5
782 }, objects_modelPipeline, {
783 mat4(1.0f)
784 });
785
786 texturedSquare.model_base =
787 translate(mat4(1.0f), vec3(0.0f, 0.0f, zOffset));
788 // START UNREVIEWED SECTION
789 } else if (e.key.keycode == SDL_SCANCODE_Z && leftLaserIdx == -1) {
790 // TODO: When I start actually removing objects from the object vectors,
791 // I will need to update the indices since they might become incorrect
792 // or invalid as objects get moved around
793
794 vec3 offset(shipObjects[0].model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
795
796 addLaser(
797 vec3(-0.21f, -1.19f, 1.76f) + offset,
798 vec3(-0.21f, -1.19f, -3.0f) + offset,
799 LASER_COLOR, 0.03f);
800
801 leftLaserIdx = laserObjects.size() - 1;
802 } else if (e.key.keycode == SDL_SCANCODE_X && rightLaserIdx == -1) {
803 vec3 offset(shipObjects[0].model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
804
805 addLaser(
806 vec3(0.21f, -1.19f, 1.76f) + offset,
807 vec3(0.21f, -1.19f, -3.0f) + offset,
808 LASER_COLOR, 0.03f);
809
810 rightLaserIdx = laserObjects.size() - 1;
811 // END UNREVIEWED SECTION
812 } else {
813 cout << "Key event detected" << endl;
814 }
815 break;
816 case UI_EVENT_KEYUP:
817 // START UNREVIEWED SECTION
818 if (e.key.keycode == SDL_SCANCODE_Z && leftLaserIdx != -1) {
819 laserObjects[leftLaserIdx].ssbo.deleted = true;
820 leftLaserIdx = -1;
821
822 if (leftLaserEffect != nullptr) {
823 leftLaserEffect->deleted = true;
824 leftLaserEffect = nullptr;
825 }
826 } else if (e.key.keycode == SDL_SCANCODE_X && rightLaserIdx != -1) {
827 laserObjects[rightLaserIdx].ssbo.deleted = true;
828 rightLaserIdx = -1;
829
830 if (rightLaserEffect != nullptr) {
831 rightLaserEffect->deleted = true;
832 rightLaserEffect = nullptr;
833 }
834 }
835 // END UNREVIEWED SECTION
836 break;
837 case UI_EVENT_WINDOW:
838 case UI_EVENT_MOUSEBUTTONDOWN:
839 case UI_EVENT_MOUSEBUTTONUP:
840 case UI_EVENT_MOUSEMOTION:
841 break;
842 case UI_EVENT_UNHANDLED:
843 cout << "Unhandled event type: 0x" << hex << sdlEvent.type << dec << endl;
844 break;
845 case UI_EVENT_UNKNOWN:
846 default:
847 cout << "Unknown event type: 0x" << hex << sdlEvent.type << dec << endl;
848 break;
849 }
850
851 // This was left ovedr from the previous SDL UI implementation.
852 // Might need something like this again when I start processing screen-specific UI events not related
853 // to the IMGUI ui, such as arrow keys for movement and other buttons for shotting or something
854 // currentScreen->handleEvent(e);
855 }
856
857 // Check which keys are held down
858
859 SceneObject<ModelVertex, SSBO_ModelObject>& ship = shipObjects[0];
860
861 if (gui->keyPressed(SDL_SCANCODE_LEFT)) {
862 float distance = -this->shipSpeed * this->elapsedTime;
863
864 ship.model_transform = translate(mat4(1.0f), vec3(distance, 0.0f, 0.0f))
865 * shipObjects[0].model_transform;
866
867 if (leftLaserIdx != -1) {
868 translateLaser(leftLaserIdx, vec3(distance, 0.0f, 0.0f));
869 }
870 if (rightLaserIdx != -1) {
871 translateLaser(rightLaserIdx, vec3(distance, 0.0f, 0.0f));
872 }
873 } else if (gui->keyPressed(SDL_SCANCODE_RIGHT)) {
874 float distance = this->shipSpeed * this->elapsedTime;
875
876 ship.model_transform = translate(mat4(1.0f), vec3(distance, 0.0f, 0.0f))
877 * shipObjects[0].model_transform;
878
879 if (leftLaserIdx != -1) {
880 translateLaser(leftLaserIdx, vec3(distance, 0.0f, 0.0f));
881 }
882 if (rightLaserIdx != -1) {
883 translateLaser(rightLaserIdx, vec3(distance, 0.0f, 0.0f));
884 }
885 }
886
887 if (shouldRecreateSwapChain) {
888 gui->refreshWindowSize();
889 const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
890
891 if (!isMinimized) {
892 // TODO: This should be used if the min image count changes, presumably because a new surface was created
893 // with a different image count or something like that. Maybe I want to add code to query for a new min image count
894 // during swapchain recreation to take advantage of this
895 ImGui_ImplVulkan_SetMinImageCount(swapChainMinImageCount);
896
897 recreateSwapChain();
898
899 shouldRecreateSwapChain = false;
900 }
901 }// REVIEWED TO THIS POINT
902
903 updateScene();
904
905 // TODO: Move this into a renderImGuiOverlay() function
906 ImGui_ImplVulkan_NewFrame();
907 ImGui_ImplSDL2_NewFrame(window);
908 ImGui::NewFrame();
909
910 int w, h;
911 SDL_GetWindowSize(((GameGui_SDL*)gui)->window, &w, &h);
912
913 // Probably a retina display
914 // TODO: Find a better fix for this. Maybe I should use SDL_Vulkan_GetWindowSize here instead
915 // of SDL_Vulkan_GetDrawableSize
916 if (w < gui->getWindowWidth() && h < gui->getWindowHeight()) {
917 (this->*currentRenderScreenFn)(w, h);
918 } else {
919 (this->*currentRenderScreenFn)(gui->getWindowWidth(), gui->getWindowHeight());
920 }
921
922 ImGui::Render();
923
924 gui->refreshWindowSize();
925 const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
926
927 if (!isMinimized) {
928 renderFrame(ImGui::GetDrawData());
929 presentFrame();
930 }
931 }
932}
933
934// TODO: The only updates that need to happen once per Vulkan image are the SSBO ones,
935// which are already handled by updateObject(). Move this code to a different place,
936// where it will run just once per frame
937void VulkanGame::updateScene() {
938 if (curTime - this->lastSpawn_asteroid > this->spawnRate_asteroid) {
939 this->lastSpawn_asteroid = curTime;
940
941 SceneObject<ModelVertex, SSBO_Asteroid>& asteroid =
942 addObject(asteroidObjects, asteroidPipeline,
943 addObjectIndex<ModelVertex>(asteroidObjects.size(),
944 addVertexNormals<ModelVertex>({
945
946 // front
947 {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
948 {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
949 {{-1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
950 {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
951 {{-1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
952 {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
953
954 // top
955 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
956 {{-1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
957 {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
958 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
959 {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
960 {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
961
962 // bottom
963 {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
964 {{-1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
965 {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
966 {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
967 {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
968 {{ 1.0f, -1.0f, -1.0}, {0.4f, 0.4f, 0.4f}},
969
970 // back
971 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
972 {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
973 {{-1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
974 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
975 {{ 1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
976 {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
977
978 // right
979 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
980 {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
981 {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
982 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
983 {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
984 {{ 1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
985
986 // left
987 {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
988 {{-1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
989 {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
990 {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
991 {{-1.0f, -1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
992 {{-1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
993 })),
994 {
995 0, 1, 2, 3, 4, 5,
996 6, 7, 8, 9, 10, 11,
997 12, 13, 14, 15, 16, 17,
998 18, 19, 20, 21, 22, 23,
999 24, 25, 26, 27, 28, 29,
1000 30, 31, 32, 33, 34, 35,
1001 }, objects_asteroidPipeline, {
1002 mat4(1.0f),
1003 10.0f,
1004 false
1005 });
1006
1007 // This accounts for the scaling in model_base.
1008 // Dividing by 8 instead of 10 since the bounding radius algorithm
1009 // under-calculates the true value.
1010 // TODO: Figure out the best way to take scaling into account when calculating the radius
1011 // Keep in mind that the main complicating factor is the currently poor radius calculation
1012 asteroid.radius /= 8.0f;
1013
1014 asteroid.model_base =
1015 translate(mat4(1.0f), vec3(getRandomNum(-1.3f, 1.3f), -1.2f, getRandomNum(-5.5f, -4.5f))) *
1016 rotate(mat4(1.0f), radians(60.0f), vec3(1.0f, 1.0f, -1.0f)) *
1017 scale(mat4(1.0f), vec3(0.1f, 0.1f, 0.1f));
1018 }
1019
1020 // TODO: Probably move the resizing to the VulkanBuffer class
1021
1022 // TODO: Replace updateBufferSet to one call to copyDataToMemory, using VulkanBuffer to provide the data source
1023 // TODO: Figure out a way to make updateDescriptorInfo easier to use, maybe store the binding index in the buffer set
1024
1025 if (objects_modelPipeline.resized) {
1026 resizeBufferSet(storageBuffers_modelPipeline, objects_modelPipeline.memorySize(), resourceCommandPool,
1027 graphicsQueue, true);
1028
1029 objects_modelPipeline.resize();
1030
1031 modelPipeline.updateDescriptorInfo(1, &storageBuffers_modelPipeline.infoSet, swapChainImages.size());
1032 }
1033
1034 for (size_t i = 0; i < modelObjects.size(); i++) {
1035 SceneObject<ModelVertex, SSBO_ModelObject>& obj = modelObjects[i];
1036 SSBO_ModelObject& objData = obj.ssbo;
1037
1038 // Rotate the textured squares
1039 obj.model_transform =
1040 translate(mat4(1.0f), vec3(0.0f, -2.0f, -0.0f)) *
1041 rotate(mat4(1.0f), curTime * radians(90.0f), vec3(0.0f, 0.0f, 1.0f));
1042
1043 objData.model = obj.model_transform * obj.model_base;
1044 obj.center = vec3(objData.model * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1045
1046 updateBufferSet(storageBuffers_modelPipeline, i, objData);
1047 }
1048
1049 if (objects_shipPipeline.resized) {
1050 resizeBufferSet(storageBuffers_shipPipeline, objects_shipPipeline.memorySize(), resourceCommandPool,
1051 graphicsQueue, true);
1052
1053 objects_shipPipeline.resize();
1054
1055 shipPipeline.updateDescriptorInfo(1, &storageBuffers_shipPipeline.infoSet, swapChainImages.size());
1056 }
1057
1058 // TODO: Move ship position updates from the ui event handling code into this function
1059 for (size_t i = 0; i < shipObjects.size(); i++) {
1060 SceneObject<ModelVertex, SSBO_ModelObject>& obj = shipObjects[i];
1061 SSBO_ModelObject& objData = obj.ssbo;
1062
1063 objData.model = obj.model_transform * obj.model_base;
1064 obj.center = vec3(objData.model * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1065
1066 updateBufferSet(storageBuffers_shipPipeline, i, objData);
1067 }
1068
1069 if (objects_asteroidPipeline.resized) {
1070 resizeBufferSet(storageBuffers_asteroidPipeline, objects_asteroidPipeline.memorySize(), resourceCommandPool,
1071 graphicsQueue, true);
1072
1073 objects_asteroidPipeline.resize();
1074
1075 asteroidPipeline.updateDescriptorInfo(1, &storageBuffers_asteroidPipeline.infoSet, swapChainImages.size());
1076 }
1077
1078 for (size_t i = 0; i < asteroidObjects.size(); i++) {
1079 SceneObject<ModelVertex, SSBO_Asteroid>& obj = asteroidObjects[i];
1080 SSBO_Asteroid& objData = obj.ssbo;
1081
1082 if (!objData.deleted) {
1083 vec3 objCenter = vec3(viewMat * vec4(obj.center, 1.0f));
1084
1085 if (objData.hp <= 0.0f) {
1086 objData.deleted = true;
1087
1088 // TODO: Optimize this so I don't recalculate the camera rotation every time
1089 // TODO: Also, avoid re-declaring cam_pitch
1090 float cam_pitch = -50.0f;
1091 mat4 pitch_mat = rotate(mat4(1.0f), radians(cam_pitch), vec3(1.0f, 0.0f, 0.0f));
1092 mat4 model_mat = translate(mat4(1.0f), obj.center) * pitch_mat;
1093
1094 addExplosion(model_mat, 0.5f, curTime);
1095
1096 this->score++;
1097 } else if ((objCenter.z - obj.radius) > -NEAR_CLIP) {
1098 objData.deleted = true;
1099 } else {
1100 obj.model_transform =
1101 translate(mat4(1.0f), vec3(0.0f, 0.0f, this->asteroidSpeed * this->elapsedTime)) *
1102 obj.model_transform;
1103 }
1104
1105 objData.model = obj.model_transform * obj.model_base;
1106 obj.center = vec3(objData.model * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1107
1108 updateBufferSet(storageBuffers_asteroidPipeline, i, objData);
1109 }
1110 }
1111
1112 if (objects_laserPipeline.resized) {
1113 resizeBufferSet(storageBuffers_laserPipeline, objects_laserPipeline.memorySize(), resourceCommandPool,
1114 graphicsQueue, true);
1115
1116 objects_laserPipeline.resize();
1117
1118 laserPipeline.updateDescriptorInfo(1, &storageBuffers_laserPipeline.infoSet, swapChainImages.size());
1119 }
1120
1121 if (leftLaserIdx != -1) {
1122 updateLaserTarget(leftLaserIdx);
1123 }
1124 if (rightLaserIdx != -1) {
1125 updateLaserTarget(rightLaserIdx);
1126 }
1127
1128 for (size_t i = 0; i < laserObjects.size(); i++) {
1129 SceneObject<LaserVertex, SSBO_Laser>& obj = laserObjects[i];
1130 SSBO_Laser& objData = obj.ssbo;
1131
1132 objData.model = obj.model_transform * obj.model_base;
1133 obj.center = vec3(objData.model * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1134
1135 updateBufferSet(storageBuffers_laserPipeline, i, objData);
1136 }
1137
1138 if (objects_explosionPipeline.resized) {
1139 resizeBufferSet(storageBuffers_explosionPipeline, objects_explosionPipeline.memorySize(), resourceCommandPool,
1140 graphicsQueue, true);
1141
1142 objects_explosionPipeline.resize();
1143
1144 explosionPipeline.updateDescriptorInfo(1, &storageBuffers_explosionPipeline.infoSet, swapChainImages.size());
1145 }
1146
1147 for (size_t i = 0; i < explosionObjects.size(); i++) {
1148 SceneObject<ExplosionVertex, SSBO_Explosion>& obj = explosionObjects[i];
1149 SSBO_Explosion& objData = obj.ssbo;
1150
1151 if (!objData.deleted) {
1152 if (curTime > (objData.explosionStartTime + objData.explosionDuration)) {
1153 objData.deleted = true;
1154 }
1155 }
1156
1157 objData.model = obj.model_transform * obj.model_base;
1158 obj.center = vec3(objData.model * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1159
1160 updateBufferSet(storageBuffers_explosionPipeline, i, objData);
1161 }
1162
1163 explosion_UBO.cur_time = curTime;
1164
1165 for (vector<BaseEffectOverTime*>::iterator it = effects.begin(); it != effects.end(); ) {
1166 if ((*it)->deleted) {
1167 delete *it;
1168 it = effects.erase(it);
1169 } else {
1170 BaseEffectOverTime* eot = *it;
1171
1172 eot->applyEffect(curTime);
1173
1174 it++;
1175 }
1176 }
1177
1178 VulkanUtils::copyDataToMemory(device, &object_VP_mats, uniformBuffers_modelPipeline.memory[imageIndex], 0,
1179 sizeof(object_VP_mats), false);
1180
1181 VulkanUtils::copyDataToMemory(device, &ship_VP_mats, uniformBuffers_shipPipeline.memory[imageIndex], 0,
1182 sizeof(ship_VP_mats), false);
1183
1184 VulkanUtils::copyDataToMemory(device, &asteroid_VP_mats, uniformBuffers_asteroidPipeline.memory[imageIndex], 0,
1185 sizeof(asteroid_VP_mats), false);
1186
1187 VulkanUtils::copyDataToMemory(device, &laser_VP_mats, uniformBuffers_laserPipeline.memory[imageIndex], 0,
1188 sizeof(laser_VP_mats), false);
1189
1190 VulkanUtils::copyDataToMemory(device, &explosion_UBO, uniformBuffers_explosionPipeline.memory[imageIndex], 0,
1191 sizeof(explosion_UBO), false);
1192}
1193
1194void VulkanGame::cleanup() {
1195 // FIXME: We could wait on the Queue if we had the queue in wd-> (otherwise VulkanH functions can't use globals)
1196 //vkQueueWaitIdle(g_Queue);
1197 VKUTIL_CHECK_RESULT(vkDeviceWaitIdle(device), "failed to wait for device!");
1198
1199 cleanupImGuiOverlay();
1200
1201 cleanupSwapChain();
1202
1203 VulkanUtils::destroyVulkanImage(device, floorTextureImage);
1204 // START UNREVIEWED SECTION
1205 VulkanUtils::destroyVulkanImage(device, laserTextureImage);
1206
1207 vkDestroySampler(device, textureSampler, nullptr);
1208
1209 modelPipeline.cleanupBuffers();
1210 shipPipeline.cleanupBuffers();
1211 asteroidPipeline.cleanupBuffers();
1212 laserPipeline.cleanupBuffers();
1213 explosionPipeline.cleanupBuffers();
1214
1215 for (size_t i = 0; i < storageBuffers_modelPipeline.buffers.size(); i++) {
1216 vkDestroyBuffer(device, storageBuffers_modelPipeline.buffers[i], nullptr);
1217 vkFreeMemory(device, storageBuffers_modelPipeline.memory[i], nullptr);
1218 }
1219
1220 for (size_t i = 0; i < storageBuffers_shipPipeline.buffers.size(); i++) {
1221 vkDestroyBuffer(device, storageBuffers_shipPipeline.buffers[i], nullptr);
1222 vkFreeMemory(device, storageBuffers_shipPipeline.memory[i], nullptr);
1223 }
1224
1225 for (size_t i = 0; i < storageBuffers_asteroidPipeline.buffers.size(); i++) {
1226 vkDestroyBuffer(device, storageBuffers_asteroidPipeline.buffers[i], nullptr);
1227 vkFreeMemory(device, storageBuffers_asteroidPipeline.memory[i], nullptr);
1228 }
1229
1230 for (size_t i = 0; i < storageBuffers_laserPipeline.buffers.size(); i++) {
1231 vkDestroyBuffer(device, storageBuffers_laserPipeline.buffers[i], nullptr);
1232 vkFreeMemory(device, storageBuffers_laserPipeline.memory[i], nullptr);
1233 }
1234
1235 for (size_t i = 0; i < storageBuffers_explosionPipeline.buffers.size(); i++) {
1236 vkDestroyBuffer(device, storageBuffers_explosionPipeline.buffers[i], nullptr);
1237 vkFreeMemory(device, storageBuffers_explosionPipeline.memory[i], nullptr);
1238 }
1239
1240 // END UNREVIEWED SECTION
1241
1242 vkDestroyCommandPool(device, resourceCommandPool, nullptr);
1243
1244 vkDestroyDevice(device, nullptr);
1245 vkDestroySurfaceKHR(instance, vulkanSurface, nullptr);
1246
1247 if (ENABLE_VALIDATION_LAYERS) {
1248 VulkanUtils::destroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
1249 }
1250
1251 vkDestroyInstance(instance, nullptr);
1252
1253 gui->destroyWindow();
1254 gui->shutdown();
1255 delete gui;
1256}
1257
1258void VulkanGame::createVulkanInstance(const vector<const char*>& validationLayers) {
1259 if (ENABLE_VALIDATION_LAYERS && !VulkanUtils::checkValidationLayerSupport(validationLayers)) {
1260 throw runtime_error("validation layers requested, but not available!");
1261 }
1262
1263 VkApplicationInfo appInfo = {};
1264 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
1265 appInfo.pApplicationName = "Vulkan Game";
1266 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
1267 appInfo.pEngineName = "No Engine";
1268 appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
1269 appInfo.apiVersion = VK_API_VERSION_1_0;
1270
1271 VkInstanceCreateInfo createInfo = {};
1272 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
1273 createInfo.pApplicationInfo = &appInfo;
1274
1275 vector<const char*> extensions = gui->getRequiredExtensions();
1276 if (ENABLE_VALIDATION_LAYERS) {
1277 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
1278 }
1279
1280 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
1281 createInfo.ppEnabledExtensionNames = extensions.data();
1282
1283 cout << endl << "Extensions:" << endl;
1284 for (const char* extensionName : extensions) {
1285 cout << extensionName << endl;
1286 }
1287 cout << endl;
1288
1289 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
1290 if (ENABLE_VALIDATION_LAYERS) {
1291 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
1292 createInfo.ppEnabledLayerNames = validationLayers.data();
1293
1294 populateDebugMessengerCreateInfo(debugCreateInfo);
1295 createInfo.pNext = &debugCreateInfo;
1296 } else {
1297 createInfo.enabledLayerCount = 0;
1298
1299 createInfo.pNext = nullptr;
1300 }
1301
1302 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
1303 throw runtime_error("failed to create instance!");
1304 }
1305}
1306
1307void VulkanGame::setupDebugMessenger() {
1308 if (!ENABLE_VALIDATION_LAYERS) {
1309 return;
1310 }
1311
1312 VkDebugUtilsMessengerCreateInfoEXT createInfo;
1313 populateDebugMessengerCreateInfo(createInfo);
1314
1315 if (VulkanUtils::createDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
1316 throw runtime_error("failed to set up debug messenger!");
1317 }
1318}
1319
1320void VulkanGame::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
1321 createInfo = {};
1322 createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
1323 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;
1324 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;
1325 createInfo.pfnUserCallback = debugCallback;
1326}
1327
1328VKAPI_ATTR VkBool32 VKAPI_CALL VulkanGame::debugCallback(
1329 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
1330 VkDebugUtilsMessageTypeFlagsEXT messageType,
1331 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
1332 void* pUserData) {
1333 cerr << "validation layer: " << pCallbackData->pMessage << endl;
1334
1335 // TODO: Figure out what the return value means and if it should always be VK_FALSE
1336 return VK_FALSE;
1337}
1338
1339void VulkanGame::createVulkanSurface() {
1340 if (gui->createVulkanSurface(instance, &vulkanSurface) == RTWO_ERROR) {
1341 throw runtime_error("failed to create window surface!");
1342 }
1343}
1344
1345void VulkanGame::pickPhysicalDevice(const vector<const char*>& deviceExtensions) {
1346 uint32_t deviceCount = 0;
1347 // TODO: Check VkResult
1348 vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
1349
1350 if (deviceCount == 0) {
1351 throw runtime_error("failed to find GPUs with Vulkan support!");
1352 }
1353
1354 vector<VkPhysicalDevice> devices(deviceCount);
1355 // TODO: Check VkResult
1356 vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
1357
1358 cout << endl << "Graphics cards:" << endl;
1359 for (const VkPhysicalDevice& device : devices) {
1360 if (isDeviceSuitable(device, deviceExtensions)) {
1361 physicalDevice = device;
1362 break;
1363 }
1364 }
1365 cout << endl;
1366
1367 if (physicalDevice == VK_NULL_HANDLE) {
1368 throw runtime_error("failed to find a suitable GPU!");
1369 }
1370}
1371
1372bool VulkanGame::isDeviceSuitable(VkPhysicalDevice physicalDevice, const vector<const char*>& deviceExtensions) {
1373 VkPhysicalDeviceProperties deviceProperties;
1374 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
1375
1376 cout << "Device: " << deviceProperties.deviceName << endl;
1377
1378 // TODO: Eventually, maybe let the user pick out of a set of GPUs in case the user does want to use
1379 // an integrated GPU. On my laptop, this function returns TRUE for the integrated GPU, but crashes
1380 // when trying to use it to render. Maybe I just need to figure out which other extensions and features
1381 // to check.
1382 if (deviceProperties.deviceType != VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
1383 return false;
1384 }
1385
1386 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1387 bool extensionsSupported = VulkanUtils::checkDeviceExtensionSupport(physicalDevice, deviceExtensions);
1388 bool swapChainAdequate = false;
1389
1390 if (extensionsSupported) {
1391 vector<VkSurfaceFormatKHR> formats = VulkanUtils::querySwapChainFormats(physicalDevice, vulkanSurface);
1392 vector<VkPresentModeKHR> presentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, vulkanSurface);
1393
1394 swapChainAdequate = !formats.empty() && !presentModes.empty();
1395 }
1396
1397 VkPhysicalDeviceFeatures supportedFeatures;
1398 vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
1399
1400 return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
1401}
1402
1403void VulkanGame::createLogicalDevice(const vector<const char*>& validationLayers,
1404 const vector<const char*>& deviceExtensions) {
1405 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1406
1407 if (!indices.isComplete()) {
1408 throw runtime_error("failed to find required queue families!");
1409 }
1410
1411 // TODO: Using separate graphics and present queues currently works, but I should verify that I'm
1412 // using them correctly to get the most benefit out of separate queues
1413
1414 vector<VkDeviceQueueCreateInfo> queueCreateInfoList;
1415 set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
1416
1417 float queuePriority = 1.0f;
1418 for (uint32_t queueFamily : uniqueQueueFamilies) {
1419 VkDeviceQueueCreateInfo queueCreateInfo = {};
1420 queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
1421 queueCreateInfo.queueCount = 1;
1422 queueCreateInfo.queueFamilyIndex = queueFamily;
1423 queueCreateInfo.pQueuePriorities = &queuePriority;
1424
1425 queueCreateInfoList.push_back(queueCreateInfo);
1426 }
1427
1428 VkPhysicalDeviceFeatures deviceFeatures = {};
1429 deviceFeatures.samplerAnisotropy = VK_TRUE;
1430
1431 VkDeviceCreateInfo createInfo = {};
1432 createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
1433
1434 createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfoList.size());
1435 createInfo.pQueueCreateInfos = queueCreateInfoList.data();
1436
1437 createInfo.pEnabledFeatures = &deviceFeatures;
1438
1439 createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
1440 createInfo.ppEnabledExtensionNames = deviceExtensions.data();
1441
1442 // These fields are ignored by up-to-date Vulkan implementations,
1443 // but it's a good idea to set them for backwards compatibility
1444 if (ENABLE_VALIDATION_LAYERS) {
1445 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
1446 createInfo.ppEnabledLayerNames = validationLayers.data();
1447 } else {
1448 createInfo.enabledLayerCount = 0;
1449 }
1450
1451 if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
1452 throw runtime_error("failed to create logical device!");
1453 }
1454
1455 vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
1456 vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
1457}
1458
1459void VulkanGame::chooseSwapChainProperties() {
1460 vector<VkSurfaceFormatKHR> availableFormats = VulkanUtils::querySwapChainFormats(physicalDevice, vulkanSurface);
1461 vector<VkPresentModeKHR> availablePresentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, vulkanSurface);
1462
1463 swapChainSurfaceFormat = VulkanUtils::chooseSwapSurfaceFormat(availableFormats,
1464 { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM },
1465 VK_COLOR_SPACE_SRGB_NONLINEAR_KHR);
1466
1467 vector<VkPresentModeKHR> presentModes{
1468 VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR
1469 };
1470 //vector<VkPresentModeKHR> presentModes{ VK_PRESENT_MODE_FIFO_KHR };
1471
1472 swapChainPresentMode = VulkanUtils::chooseSwapPresentMode(availablePresentModes, presentModes);
1473
1474 cout << "[vulkan] Selected PresentMode = " << swapChainPresentMode << endl;
1475
1476 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, vulkanSurface);
1477
1478 if (swapChainPresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
1479 swapChainMinImageCount = 3;
1480 } else if (swapChainPresentMode == VK_PRESENT_MODE_FIFO_KHR || swapChainPresentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) {
1481 swapChainMinImageCount = 2;
1482 } else if (swapChainPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
1483 swapChainMinImageCount = 1;
1484 } else {
1485 throw runtime_error("unexpected present mode!");
1486 }
1487
1488 if (swapChainMinImageCount < capabilities.minImageCount) {
1489 swapChainMinImageCount = capabilities.minImageCount;
1490 } else if (capabilities.maxImageCount != 0 && swapChainMinImageCount > capabilities.maxImageCount) {
1491 swapChainMinImageCount = capabilities.maxImageCount;
1492 }
1493}
1494
1495void VulkanGame::createSwapChain() {
1496 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, vulkanSurface);
1497
1498 swapChainExtent = VulkanUtils::chooseSwapExtent(capabilities, gui->getWindowWidth(), gui->getWindowHeight());
1499
1500 VkSwapchainCreateInfoKHR createInfo = {};
1501 createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
1502 createInfo.surface = vulkanSurface;
1503 createInfo.minImageCount = swapChainMinImageCount;
1504 createInfo.imageFormat = swapChainSurfaceFormat.format;
1505 createInfo.imageColorSpace = swapChainSurfaceFormat.colorSpace;
1506 createInfo.imageExtent = swapChainExtent;
1507 createInfo.imageArrayLayers = 1;
1508 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
1509
1510 // TODO: Maybe save this result so I don't have to recalculate it every time
1511 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1512 uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
1513
1514 if (indices.graphicsFamily != indices.presentFamily) {
1515 createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
1516 createInfo.queueFamilyIndexCount = 2;
1517 createInfo.pQueueFamilyIndices = queueFamilyIndices;
1518 } else {
1519 createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
1520 createInfo.queueFamilyIndexCount = 0;
1521 createInfo.pQueueFamilyIndices = nullptr;
1522 }
1523
1524 createInfo.preTransform = capabilities.currentTransform;
1525 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
1526 createInfo.presentMode = swapChainPresentMode;
1527 createInfo.clipped = VK_TRUE;
1528 createInfo.oldSwapchain = VK_NULL_HANDLE;
1529
1530 if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
1531 throw runtime_error("failed to create swap chain!");
1532 }
1533
1534 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, nullptr) != VK_SUCCESS) {
1535 throw runtime_error("failed to get swap chain image count!");
1536 }
1537
1538 swapChainImages.resize(swapChainImageCount);
1539 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, swapChainImages.data()) != VK_SUCCESS) {
1540 throw runtime_error("failed to get swap chain images!");
1541 }
1542}
1543
1544void VulkanGame::createImageViews() {
1545 swapChainImageViews.resize(swapChainImageCount);
1546
1547 for (size_t i = 0; i < swapChainImageCount; i++) {
1548 swapChainImageViews[i] = VulkanUtils::createImageView(device, swapChainImages[i], swapChainSurfaceFormat.format,
1549 VK_IMAGE_ASPECT_COLOR_BIT);
1550 }
1551}
1552
1553void VulkanGame::createResourceCommandPool() {
1554 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1555
1556 VkCommandPoolCreateInfo poolInfo = {};
1557 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
1558 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
1559 poolInfo.flags = 0;
1560
1561 if (vkCreateCommandPool(device, &poolInfo, nullptr, &resourceCommandPool) != VK_SUCCESS) {
1562 throw runtime_error("failed to create resource command pool!");
1563 }
1564}
1565
1566void VulkanGame::createImageResources() {
1567 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
1568 depthImage, graphicsQueue);
1569
1570 createTextureSampler();
1571
1572 // TODO: Move all images/textures somewhere into the assets folder
1573
1574 VulkanUtils::createVulkanImageFromFile(device, physicalDevice, resourceCommandPool, "textures/texture.jpg",
1575 floorTextureImage, graphicsQueue);
1576
1577 floorTextureImageDescriptor = {};
1578 floorTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1579 floorTextureImageDescriptor.imageView = floorTextureImage.imageView;
1580 floorTextureImageDescriptor.sampler = textureSampler;
1581
1582 VulkanUtils::createVulkanImageFromFile(device, physicalDevice, resourceCommandPool, "textures/laser.png",
1583 laserTextureImage, graphicsQueue);
1584
1585 laserTextureImageDescriptor = {};
1586 laserTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1587 laserTextureImageDescriptor.imageView = laserTextureImage.imageView;
1588 laserTextureImageDescriptor.sampler = textureSampler;
1589}
1590
1591VkFormat VulkanGame::findDepthFormat() {
1592 return VulkanUtils::findSupportedFormat(
1593 physicalDevice,
1594 { VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D32_SFLOAT, VK_FORMAT_D24_UNORM_S8_UINT },
1595 VK_IMAGE_TILING_OPTIMAL,
1596 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
1597 );
1598}
1599
1600void VulkanGame::createRenderPass() {
1601 VkAttachmentDescription colorAttachment = {};
1602 colorAttachment.format = swapChainSurfaceFormat.format;
1603 colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
1604 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1605 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
1606 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1607 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1608 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1609 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
1610
1611 VkAttachmentReference colorAttachmentRef = {};
1612 colorAttachmentRef.attachment = 0;
1613 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
1614
1615 VkAttachmentDescription depthAttachment = {};
1616 depthAttachment.format = findDepthFormat();
1617 depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
1618 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1619 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1620 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1621 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1622 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1623 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1624
1625 VkAttachmentReference depthAttachmentRef = {};
1626 depthAttachmentRef.attachment = 1;
1627 depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1628
1629 VkSubpassDescription subpass = {};
1630 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
1631 subpass.colorAttachmentCount = 1;
1632 subpass.pColorAttachments = &colorAttachmentRef;
1633 subpass.pDepthStencilAttachment = &depthAttachmentRef;
1634
1635 VkSubpassDependency dependency = {};
1636 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
1637 dependency.dstSubpass = 0;
1638 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1639 dependency.srcAccessMask = 0;
1640 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1641 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
1642
1643 array<VkAttachmentDescription, 2> attachments = { colorAttachment, depthAttachment };
1644 VkRenderPassCreateInfo renderPassInfo = {};
1645 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
1646 renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
1647 renderPassInfo.pAttachments = attachments.data();
1648 renderPassInfo.subpassCount = 1;
1649 renderPassInfo.pSubpasses = &subpass;
1650 renderPassInfo.dependencyCount = 1;
1651 renderPassInfo.pDependencies = &dependency;
1652
1653 if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
1654 throw runtime_error("failed to create render pass!");
1655 }
1656}
1657
1658void VulkanGame::createCommandPools() {
1659 commandPools.resize(swapChainImageCount);
1660
1661 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1662
1663 for (size_t i = 0; i < swapChainImageCount; i++) {
1664 VkCommandPoolCreateInfo poolInfo = {};
1665 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
1666 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
1667 poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
1668
1669 VKUTIL_CHECK_RESULT(vkCreateCommandPool(device, &poolInfo, nullptr, &commandPools[i]),
1670 "failed to create graphics command pool!");
1671 }
1672}
1673
1674void VulkanGame::createTextureSampler() {
1675 VkSamplerCreateInfo samplerInfo = {};
1676 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1677 samplerInfo.magFilter = VK_FILTER_LINEAR;
1678 samplerInfo.minFilter = VK_FILTER_LINEAR;
1679
1680 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1681 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1682 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1683
1684 samplerInfo.anisotropyEnable = VK_TRUE;
1685 samplerInfo.maxAnisotropy = 16;
1686 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
1687 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1688 samplerInfo.compareEnable = VK_FALSE;
1689 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1690 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
1691 samplerInfo.mipLodBias = 0.0f;
1692 samplerInfo.minLod = 0.0f;
1693 samplerInfo.maxLod = 0.0f;
1694
1695 VKUTIL_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler),
1696 "failed to create texture sampler!");
1697}
1698
1699void VulkanGame::createFramebuffers() {
1700 swapChainFramebuffers.resize(swapChainImageCount);
1701
1702 VkFramebufferCreateInfo framebufferInfo = {};
1703 framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
1704 framebufferInfo.renderPass = renderPass;
1705 framebufferInfo.width = swapChainExtent.width;
1706 framebufferInfo.height = swapChainExtent.height;
1707 framebufferInfo.layers = 1;
1708
1709 for (uint32_t i = 0; i < swapChainImageCount; i++) {
1710 array<VkImageView, 2> attachments = {
1711 swapChainImageViews[i],
1712 depthImage.imageView
1713 };
1714
1715 framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
1716 framebufferInfo.pAttachments = attachments.data();
1717
1718 if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
1719 throw runtime_error("failed to create framebuffer!");
1720 }
1721 }
1722}
1723
1724void VulkanGame::createCommandBuffers() {
1725 commandBuffers.resize(swapChainImageCount);
1726
1727 for (size_t i = 0; i < swapChainImageCount; i++) {
1728 VkCommandBufferAllocateInfo allocInfo = {};
1729 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1730 allocInfo.commandPool = commandPools[i];
1731 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1732 allocInfo.commandBufferCount = 1;
1733
1734 if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffers[i]) != VK_SUCCESS) {
1735 throw runtime_error("failed to allocate command buffer!");
1736 }
1737 }
1738}
1739
1740void VulkanGame::createSyncObjects() {
1741 imageAcquiredSemaphores.resize(swapChainImageCount);
1742 renderCompleteSemaphores.resize(swapChainImageCount);
1743 inFlightFences.resize(swapChainImageCount);
1744
1745 VkSemaphoreCreateInfo semaphoreInfo = {};
1746 semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
1747
1748 VkFenceCreateInfo fenceInfo = {};
1749 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
1750 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
1751
1752 for (size_t i = 0; i < swapChainImageCount; i++) {
1753 VKUTIL_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAcquiredSemaphores[i]),
1754 "failed to create image acquired sempahore for a frame!");
1755
1756 VKUTIL_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderCompleteSemaphores[i]),
1757 "failed to create render complete sempahore for a frame!");
1758
1759 VKUTIL_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]),
1760 "failed to create fence for a frame!");
1761 }
1762}
1763
1764void VulkanGame::renderFrame(ImDrawData* draw_data) {
1765 VkResult result = vkAcquireNextImageKHR(device, swapChain, numeric_limits<uint64_t>::max(),
1766 imageAcquiredSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
1767
1768 if (result == VK_SUBOPTIMAL_KHR) {
1769 shouldRecreateSwapChain = true;
1770 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1771 shouldRecreateSwapChain = true;
1772 return;
1773 } else {
1774 VKUTIL_CHECK_RESULT(result, "failed to acquire swap chain image!");
1775 }
1776
1777 VKUTIL_CHECK_RESULT(
1778 vkWaitForFences(device, 1, &inFlightFences[imageIndex], VK_TRUE, numeric_limits<uint64_t>::max()),
1779 "failed waiting for fence!");
1780
1781 VKUTIL_CHECK_RESULT(vkResetFences(device, 1, &inFlightFences[imageIndex]),
1782 "failed to reset fence!");
1783
1784 VKUTIL_CHECK_RESULT(vkResetCommandPool(device, commandPools[imageIndex], 0),
1785 "failed to reset command pool!");
1786
1787 VkCommandBufferBeginInfo beginInfo = {};
1788 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1789 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1790
1791 VKUTIL_CHECK_RESULT(vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo),
1792 "failed to begin recording command buffer!");
1793
1794 VkRenderPassBeginInfo renderPassInfo = {};
1795 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
1796 renderPassInfo.renderPass = renderPass;
1797 renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
1798 renderPassInfo.renderArea.offset = { 0, 0 };
1799 renderPassInfo.renderArea.extent = swapChainExtent;
1800
1801 array<VkClearValue, 2> clearValues = {};
1802 clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
1803 clearValues[1].depthStencil = { 1.0f, 0 };
1804
1805 renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
1806 renderPassInfo.pClearValues = clearValues.data();
1807
1808 vkCmdBeginRenderPass(commandBuffers[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
1809
1810 // TODO: Find a more elegant, per-screen solution for this
1811 if (currentRenderScreenFn == &VulkanGame::renderGameScreen) {
1812 modelPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex, {});
1813 shipPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex, {});
1814 asteroidPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex, {});
1815 laserPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex, {});
1816 explosionPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex, {});
1817 }
1818
1819 ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffers[imageIndex]);
1820
1821 vkCmdEndRenderPass(commandBuffers[imageIndex]);
1822
1823 VKUTIL_CHECK_RESULT(vkEndCommandBuffer(commandBuffers[imageIndex]),
1824 "failed to record command buffer!");
1825
1826 VkSemaphore waitSemaphores[] = { imageAcquiredSemaphores[currentFrame] };
1827 VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
1828 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
1829
1830 VkSubmitInfo submitInfo = {};
1831 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1832 submitInfo.waitSemaphoreCount = 1;
1833 submitInfo.pWaitSemaphores = waitSemaphores;
1834 submitInfo.pWaitDstStageMask = waitStages;
1835 submitInfo.commandBufferCount = 1;
1836 submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
1837 submitInfo.signalSemaphoreCount = 1;
1838 submitInfo.pSignalSemaphores = signalSemaphores;
1839
1840 VKUTIL_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[imageIndex]),
1841 "failed to submit draw command buffer!");
1842}
1843
1844void VulkanGame::presentFrame() {
1845 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
1846
1847 VkPresentInfoKHR presentInfo = {};
1848 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
1849 presentInfo.waitSemaphoreCount = 1;
1850 presentInfo.pWaitSemaphores = signalSemaphores;
1851 presentInfo.swapchainCount = 1;
1852 presentInfo.pSwapchains = &swapChain;
1853 presentInfo.pImageIndices = &imageIndex;
1854 presentInfo.pResults = nullptr;
1855
1856 VkResult result = vkQueuePresentKHR(presentQueue, &presentInfo);
1857
1858 if (result == VK_SUBOPTIMAL_KHR) {
1859 shouldRecreateSwapChain = true;
1860 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1861 shouldRecreateSwapChain = true;
1862 return;
1863 } else {
1864 VKUTIL_CHECK_RESULT(result, "failed to present swap chain image!");
1865 }
1866
1867 currentFrame = (currentFrame + 1) % swapChainImageCount;
1868}
1869
1870void VulkanGame::initImGuiOverlay() {
1871 vector<VkDescriptorPoolSize> pool_sizes{
1872 { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
1873 { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
1874 { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
1875 { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 },
1876 { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 },
1877 { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 },
1878 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 },
1879 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 },
1880 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 },
1881 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 },
1882 { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 }
1883 };
1884
1885 VkDescriptorPoolCreateInfo pool_info = {};
1886 pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1887 pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
1888 pool_info.maxSets = 1000 * pool_sizes.size();
1889 pool_info.poolSizeCount = static_cast<uint32_t>(pool_sizes.size());
1890 pool_info.pPoolSizes = pool_sizes.data();
1891
1892 VKUTIL_CHECK_RESULT(vkCreateDescriptorPool(device, &pool_info, nullptr, &imguiDescriptorPool),
1893 "failed to create IMGUI descriptor pool!");
1894
1895 // TODO: Do this in one place and save it instead of redoing it every time I need a queue family index
1896 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1897
1898 // Setup Dear ImGui context
1899 IMGUI_CHECKVERSION();
1900 ImGui::CreateContext();
1901 ImGuiIO& io = ImGui::GetIO();
1902 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
1903 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
1904
1905 // Setup Dear ImGui style
1906 ImGui::StyleColorsDark();
1907 //ImGui::StyleColorsClassic();
1908
1909 // Setup Platform/Renderer bindings
1910 ImGui_ImplSDL2_InitForVulkan(window);
1911 ImGui_ImplVulkan_InitInfo init_info = {};
1912 init_info.Instance = instance;
1913 init_info.PhysicalDevice = physicalDevice;
1914 init_info.Device = device;
1915 init_info.QueueFamily = indices.graphicsFamily.value();
1916 init_info.Queue = graphicsQueue;
1917 init_info.DescriptorPool = imguiDescriptorPool;
1918 init_info.Allocator = nullptr;
1919 init_info.MinImageCount = swapChainMinImageCount;
1920 init_info.ImageCount = swapChainImageCount;
1921 init_info.CheckVkResultFn = check_imgui_vk_result;
1922 ImGui_ImplVulkan_Init(&init_info, renderPass);
1923
1924 // Load Fonts
1925 // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
1926 // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
1927 // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
1928 // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call.
1929 // - Read 'docs/FONTS.md' for more instructions and details.
1930 // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
1931 //io.Fonts->AddFontDefault();
1932 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
1933 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
1934 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
1935 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f);
1936 //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
1937 //assert(font != NULL);
1938
1939 // Upload Fonts
1940 {
1941 VkCommandBuffer commandBuffer = VulkanUtils::beginSingleTimeCommands(device, resourceCommandPool);
1942
1943 ImGui_ImplVulkan_CreateFontsTexture(commandBuffer);
1944
1945 VulkanUtils::endSingleTimeCommands(device, resourceCommandPool, commandBuffer, graphicsQueue);
1946
1947 ImGui_ImplVulkan_DestroyFontUploadObjects();
1948 }
1949}
1950
1951void VulkanGame::cleanupImGuiOverlay() {
1952 ImGui_ImplVulkan_Shutdown();
1953 ImGui_ImplSDL2_Shutdown();
1954 ImGui::DestroyContext();
1955
1956 vkDestroyDescriptorPool(device, imguiDescriptorPool, nullptr);
1957}
1958
1959void VulkanGame::createBufferSet(VkDeviceSize bufferSize, VkBufferUsageFlags usages, VkMemoryPropertyFlags properties,
1960 BufferSet& set) {
1961 set.usages = usages;
1962 set.properties = properties;
1963
1964 set.buffers.resize(swapChainImageCount);
1965 set.memory.resize(swapChainImageCount);
1966 set.infoSet.resize(swapChainImageCount);
1967
1968 for (size_t i = 0; i < swapChainImageCount; i++) {
1969 VulkanUtils::createBuffer(device, physicalDevice, bufferSize, usages, properties, set.buffers[i], set.memory[i]);
1970
1971 set.infoSet[i].buffer = set.buffers[i];
1972 set.infoSet[i].offset = 0; // This is the offset from the start of the buffer, so always 0 for now
1973 set.infoSet[i].range = bufferSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
1974 }
1975}
1976
1977void VulkanGame::resizeBufferSet(BufferSet& set, VkDeviceSize newSize, VkCommandPool commandPool,
1978 VkQueue graphicsQueue, bool copyData) {
1979 for (size_t i = 0; i < set.buffers.size(); i++) {
1980 VkBuffer newBuffer;
1981 VkDeviceMemory newMemory;
1982
1983 VulkanUtils::createBuffer(device, physicalDevice, newSize, set.usages, set.properties, newBuffer, newMemory);
1984
1985 if (copyData) {
1986 VulkanUtils::copyBuffer(device, commandPool, set.buffers[i], newBuffer, 0, 0, set.infoSet[i].range,
1987 graphicsQueue);
1988 }
1989
1990 vkDestroyBuffer(device, set.buffers[i], nullptr);
1991 vkFreeMemory(device, set.memory[i], nullptr);
1992
1993 set.buffers[i] = newBuffer;
1994 set.memory[i] = newMemory;
1995
1996 set.infoSet[i].buffer = set.buffers[i];
1997 set.infoSet[i].offset = 0; // This is the offset from the start of the buffer, so always 0 for now
1998 set.infoSet[i].range = newSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
1999 }
2000}
2001
2002void VulkanGame::addLaser(vec3 start, vec3 end, vec3 color, float width) {
2003 vec3 ray = end - start;
2004 float length = glm::length(ray);
2005
2006 SceneObject<LaserVertex, SSBO_Laser>& laser = addObject(
2007 laserObjects, laserPipeline,
2008 addObjectIndex<LaserVertex>(laserObjects.size(), {
2009 {{ width / 2, 0.0f, -width / 2 }, {1.0f, 0.5f }},
2010 {{-width / 2, 0.0f, -width / 2 }, {0.0f, 0.5f }},
2011 {{-width / 2, 0.0f, 0.0f }, {0.0f, 0.0f }},
2012 {{ width / 2, 0.0f, 0.0f }, {1.0f, 0.0f }},
2013 {{ width / 2, 0.0f, -length + width / 2}, {1.0f, 0.51f}},
2014 {{-width / 2, 0.0f, -length + width / 2}, {0.0f, 0.51f}},
2015 {{ width / 2, 0.0f, -length, }, {1.0f, 1.0f }},
2016 {{-width / 2, 0.0f, -length }, {0.0f, 1.0f }}
2017 }), {
2018 0, 1, 2, 0, 2, 3,
2019 4, 5, 1, 4, 1, 0,
2020 6, 7, 5, 6, 5, 4
2021 }, objects_laserPipeline, {
2022 mat4(1.0f),
2023 color,
2024 false
2025 });
2026
2027 float xAxisRotation = asin(ray.y / length);
2028 float yAxisRotation = atan2(-ray.x, -ray.z);
2029
2030 vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
2031 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) *
2032 vec4(0.0f, 1.0f, 0.0f, 1.0f));
2033
2034 // To project point P onto line AB:
2035 // projection = A + dot(AP,AB) / dot(AB,AB) * AB
2036 vec3 projOnLaser = start + glm::dot(this->cam_pos - start, ray) / (length * length) * ray;
2037 vec3 laserToCam = this->cam_pos - projOnLaser;
2038
2039 float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam));
2040
2041 laser.targetAsteroid = nullptr;
2042
2043 laser.model_base =
2044 rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f));
2045
2046 laser.model_transform =
2047 translate(mat4(1.0f), start) *
2048 rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
2049 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f));
2050}
2051
2052void VulkanGame::translateLaser(size_t index, const vec3& translation) {
2053 SceneObject<LaserVertex, SSBO_Laser>& laser = this->laserObjects[index];
2054
2055 // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created,
2056 // and then re-used here
2057
2058 vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
2059 vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f));
2060
2061 vec3 ray = end - start;
2062 float length = glm::length(ray);
2063
2064 float xAxisRotation = asin(ray.y / length);
2065 float yAxisRotation = atan2(-ray.x, -ray.z);
2066
2067 vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
2068 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) *
2069 vec4(0.0f, 1.0f, 0.0f, 1.0f));
2070
2071 // To project point P onto line AB:
2072 // projection = A + dot(AP,AB) / dot(AB,AB) * AB
2073 vec3 projOnLaser = start + glm::dot(cam_pos - start, ray) / (length*length) * ray;
2074 vec3 laserToCam = cam_pos - projOnLaser;
2075
2076 float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam));
2077
2078 laser.model_base = rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f));
2079 laser.model_transform = translate(mat4(1.0f), translation) * laser.model_transform;
2080}
2081
2082void VulkanGame::updateLaserTarget(size_t index) {
2083 SceneObject<LaserVertex, SSBO_Laser>& laser = this->laserObjects[index];
2084
2085 // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created,
2086 // and then re-used here
2087
2088 vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
2089 vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f));
2090
2091 vec3 intersection(0.0f), closestIntersection(0.0f);
2092 SceneObject<ModelVertex, SSBO_Asteroid>* closestAsteroid = nullptr;
2093 unsigned int closestAsteroidIndex = -1;
2094
2095 for (int i = 0; i < this->asteroidObjects.size(); i++) {
2096 if (!this->asteroidObjects[i].ssbo.deleted &&
2097 this->getLaserAndAsteroidIntersection(this->asteroidObjects[i], start, end, intersection)) {
2098 // TODO: Implement a more generic algorithm for testing the closest object by getting the distance between the points
2099 // TODO: Also check which intersection is close to the start of the laser. This would make the algorithm work
2100 // regardless of which way -Z is pointing
2101 if (closestAsteroid == nullptr || intersection.z > closestIntersection.z) {
2102 // TODO: At this point, find the real intersection of the laser with one of the asteroid's sides
2103 closestAsteroid = &asteroidObjects[i];
2104 closestIntersection = intersection;
2105 closestAsteroidIndex = i;
2106 }
2107 }
2108 }
2109
2110 float width = laser.vertices[0].pos.x - laser.vertices[1].pos.x;
2111
2112 if (laser.targetAsteroid != closestAsteroid) {
2113 if (laser.targetAsteroid != nullptr) {
2114 if (index == leftLaserIdx && leftLaserEffect != nullptr) {
2115 leftLaserEffect->deleted = true;
2116 } else if (index == rightLaserIdx && rightLaserEffect != nullptr) {
2117 rightLaserEffect->deleted = true;
2118 }
2119 }
2120
2121 EffectOverTime<ModelVertex, SSBO_Asteroid>* eot = nullptr;
2122
2123 if (closestAsteroid != nullptr) {
2124 // TODO: Use some sort of smart pointer instead
2125 eot = new EffectOverTime<ModelVertex, SSBO_Asteroid>(asteroidPipeline, asteroidObjects, closestAsteroidIndex,
2126 offset_of(&SSBO_Asteroid::hp), curTime, -20.0f);
2127 effects.push_back(eot);
2128 }
2129
2130 if (index == leftLaserIdx) {
2131 leftLaserEffect = eot;
2132 } else if (index == rightLaserIdx) {
2133 rightLaserEffect = eot;
2134 }
2135
2136 laser.targetAsteroid = closestAsteroid;
2137 }
2138
2139 // Make the laser go past the end of the screen if it doesn't hit anything
2140 float length = closestAsteroid == nullptr ? 5.24f : glm::length(closestIntersection - start);
2141
2142 laser.vertices[4].pos.z = -length + width / 2;
2143 laser.vertices[5].pos.z = -length + width / 2;
2144 laser.vertices[6].pos.z = -length;
2145 laser.vertices[7].pos.z = -length;
2146
2147 // TODO: Consider if I want to set a flag and do this update in updateScene() instead
2148 updateObjectVertices(this->laserPipeline, laser, index);
2149}
2150
2151// TODO: Determine if I should pass start and end by reference or value since they don't get changed
2152// Probably use const reference
2153bool VulkanGame::getLaserAndAsteroidIntersection(SceneObject<ModelVertex, SSBO_Asteroid>& asteroid,
2154 vec3& start, vec3& end, vec3& intersection) {
2155 /*
2156 ### LINE EQUATIONS ###
2157 x = x1 + u * (x2 - x1)
2158 y = y1 + u * (y2 - y1)
2159 z = z1 + u * (z2 - z1)
2160
2161 ### SPHERE EQUATION ###
2162 (x - x3)^2 + (y - y3)^2 + (z - z3)^2 = r^2
2163
2164 ### QUADRATIC EQUATION TO SOLVE ###
2165 a*u^2 + b*u + c = 0
2166 WHERE THE CONSTANTS ARE
2167 a = (x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2
2168 b = 2*( (x2 - x1)*(x1 - x3) + (y2 - y1)*(y1 - y3) + (z2 - z1)*(z1 - z3) )
2169 c = x3^2 + y3^2 + z3^2 + x1^2 + y1^2 + z1^2 - 2(x3*x1 + y3*y1 + z3*z1) - r^2
2170
2171 u = (-b +- sqrt(b^2 - 4*a*c)) / 2a
2172
2173 If the value under the root is >= 0, we got an intersection
2174 If the value > 0, there are two solutions. Take the one closer to 0, since that's the
2175 one closer to the laser start point
2176 */
2177
2178 vec3& center = asteroid.center;
2179
2180 float a = pow(end.x - start.x, 2) + pow(end.y - start.y, 2) + pow(end.z - start.z, 2);
2181 float b = 2 * ((start.x - end.x) * (start.x - center.x) + (end.y - start.y) * (start.y - center.y) +
2182 (end.z - start.z) * (start.z - center.z));
2183 float c = pow(center.x, 2) + pow(center.y, 2) + pow(center.z, 2) + pow(start.x, 2) + pow(start.y, 2) +
2184 pow(start.z, 2) - 2 * (center.x * start.x + center.y * start.y + center.z * start.z) -
2185 pow(asteroid.radius, 2);
2186 float discriminant = pow(b, 2) - 4 * a * c;
2187
2188 if (discriminant >= 0.0f) {
2189 // In this case, the negative root will always give the point closer to the laser start point
2190 float u = (-b - sqrt(discriminant)) / (2 * a);
2191
2192 // Check that the intersection is within the line segment corresponding to the laser
2193 if (0.0f <= u && u <= 1.0f) {
2194 intersection = start + u * (end - start);
2195 return true;
2196 }
2197 }
2198
2199 return false;
2200}
2201
2202void VulkanGame::addExplosion(mat4 model_mat, float duration, float cur_time) {
2203 vector<ExplosionVertex> vertices;
2204 vertices.reserve(EXPLOSION_PARTICLE_COUNT);
2205
2206 float particle_start_time = 0.0f;
2207
2208 for (int i = 0; i < EXPLOSION_PARTICLE_COUNT; i++) {
2209 float randx = ((float)rand() / (float)RAND_MAX) - 0.5f;
2210 float randy = ((float)rand() / (float)RAND_MAX) - 0.5f;
2211
2212 vertices.push_back({ vec3(randx, randy, 0.0f), particle_start_time});
2213
2214 particle_start_time += .01f;
2215 // TODO: Get this working
2216 // particle_start_time += 1.0f * EXPLOSION_PARTICLE_COUNT / duration
2217 }
2218
2219 // Fill the indices with the the first EXPLOSION_PARTICLE_COUNT ints
2220 vector<uint16_t> indices(EXPLOSION_PARTICLE_COUNT);
2221 iota(indices.begin(), indices.end(), 0);
2222
2223 SceneObject<ExplosionVertex, SSBO_Explosion>& explosion = addObject(explosionObjects, explosionPipeline,
2224 addObjectIndex(explosionObjects.size(), vertices), indices, objects_explosionPipeline, {
2225 mat4(1.0f),
2226 cur_time,
2227 duration,
2228 false
2229 });
2230
2231 explosion.model_base = model_mat;
2232 explosion.model_transform = mat4(1.0f);
2233}
2234
2235void VulkanGame::recreateSwapChain() {
2236 if (vkDeviceWaitIdle(device) != VK_SUCCESS) {
2237 throw runtime_error("failed to wait for device!");
2238 }
2239
2240 cleanupSwapChain();
2241
2242 createSwapChain();
2243 createImageViews();
2244
2245 // The depth buffer does need to be recreated with the swap chain since its dimensions depend on the window size
2246 // and resizing the window is a common reason to recreate the swapchain
2247 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
2248 depthImage, graphicsQueue);
2249
2250 createRenderPass();
2251 createCommandPools();
2252 createFramebuffers();
2253 createCommandBuffers();
2254 createSyncObjects();
2255
2256 // TODO: Move UBO creation/management into GraphicsPipeline_Vulkan, like I did with SSBOs
2257 // TODO: Check if the shader stages and maybe some other properties of the pipeline can be re-used
2258 // instead of recreated every time
2259
2260 createBufferSet(sizeof(UBO_VP_mats),
2261 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2262 uniformBuffers_modelPipeline);
2263
2264 modelPipeline.updateRenderPass(renderPass);
2265 modelPipeline.createPipeline("shaders/model-vert.spv", "shaders/model-frag.spv");
2266 modelPipeline.createDescriptorPool(swapChainImages.size());
2267 modelPipeline.createDescriptorSets(swapChainImages.size());
2268
2269 createBufferSet(sizeof(UBO_VP_mats),
2270 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2271 uniformBuffers_shipPipeline);
2272
2273 shipPipeline.updateRenderPass(renderPass);
2274 shipPipeline.createPipeline("shaders/ship-vert.spv", "shaders/ship-frag.spv");
2275 shipPipeline.createDescriptorPool(swapChainImages.size());
2276 shipPipeline.createDescriptorSets(swapChainImages.size());
2277
2278 createBufferSet(sizeof(UBO_VP_mats),
2279 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2280 uniformBuffers_asteroidPipeline);
2281
2282 asteroidPipeline.updateRenderPass(renderPass);
2283 asteroidPipeline.createPipeline("shaders/asteroid-vert.spv", "shaders/asteroid-frag.spv");
2284 asteroidPipeline.createDescriptorPool(swapChainImages.size());
2285 asteroidPipeline.createDescriptorSets(swapChainImages.size());
2286
2287 createBufferSet(sizeof(UBO_VP_mats),
2288 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2289 uniformBuffers_laserPipeline);
2290
2291 laserPipeline.updateRenderPass(renderPass);
2292 laserPipeline.createPipeline("shaders/laser-vert.spv", "shaders/laser-frag.spv");
2293 laserPipeline.createDescriptorPool(swapChainImages.size());
2294 laserPipeline.createDescriptorSets(swapChainImages.size());
2295
2296 createBufferSet(sizeof(UBO_Explosion),
2297 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2298 uniformBuffers_explosionPipeline);
2299
2300 explosionPipeline.updateRenderPass(renderPass);
2301 explosionPipeline.createPipeline("shaders/explosion-vert.spv", "shaders/explosion-frag.spv");
2302 explosionPipeline.createDescriptorPool(swapChainImages.size());
2303 explosionPipeline.createDescriptorSets(swapChainImages.size());
2304
2305 imageIndex = 0;
2306}
2307
2308void VulkanGame::cleanupSwapChain() {
2309 VulkanUtils::destroyVulkanImage(device, depthImage);
2310
2311 for (VkFramebuffer framebuffer : swapChainFramebuffers) {
2312 vkDestroyFramebuffer(device, framebuffer, nullptr);
2313 }
2314
2315 for (uint32_t i = 0; i < swapChainImageCount; i++) {
2316 vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
2317 vkDestroyCommandPool(device, commandPools[i], nullptr);
2318 }
2319
2320 modelPipeline.cleanup();
2321 shipPipeline.cleanup();
2322 asteroidPipeline.cleanup();
2323 laserPipeline.cleanup();
2324 explosionPipeline.cleanup();
2325
2326 for (size_t i = 0; i < uniformBuffers_modelPipeline.buffers.size(); i++) {
2327 vkDestroyBuffer(device, uniformBuffers_modelPipeline.buffers[i], nullptr);
2328 vkFreeMemory(device, uniformBuffers_modelPipeline.memory[i], nullptr);
2329 }
2330
2331 for (size_t i = 0; i < uniformBuffers_shipPipeline.buffers.size(); i++) {
2332 vkDestroyBuffer(device, uniformBuffers_shipPipeline.buffers[i], nullptr);
2333 vkFreeMemory(device, uniformBuffers_shipPipeline.memory[i], nullptr);
2334 }
2335
2336 for (size_t i = 0; i < uniformBuffers_asteroidPipeline.buffers.size(); i++) {
2337 vkDestroyBuffer(device, uniformBuffers_asteroidPipeline.buffers[i], nullptr);
2338 vkFreeMemory(device, uniformBuffers_asteroidPipeline.memory[i], nullptr);
2339 }
2340
2341 for (size_t i = 0; i < uniformBuffers_laserPipeline.buffers.size(); i++) {
2342 vkDestroyBuffer(device, uniformBuffers_laserPipeline.buffers[i], nullptr);
2343 vkFreeMemory(device, uniformBuffers_laserPipeline.memory[i], nullptr);
2344 }
2345
2346 for (size_t i = 0; i < uniformBuffers_explosionPipeline.buffers.size(); i++) {
2347 vkDestroyBuffer(device, uniformBuffers_explosionPipeline.buffers[i], nullptr);
2348 vkFreeMemory(device, uniformBuffers_explosionPipeline.memory[i], nullptr);
2349 }
2350
2351 for (size_t i = 0; i < swapChainImageCount; i++) {
2352 vkDestroySemaphore(device, imageAcquiredSemaphores[i], nullptr);
2353 vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr);
2354 vkDestroyFence(device, inFlightFences[i], nullptr);
2355 }
2356
2357 vkDestroyRenderPass(device, renderPass, nullptr);
2358
2359 for (VkImageView imageView : swapChainImageViews) {
2360 vkDestroyImageView(device, imageView, nullptr);
2361 }
2362
2363 vkDestroySwapchainKHR(device, swapChain, nullptr);
2364}
2365
2366void VulkanGame::renderMainScreen(int width, int height) {
2367 {
2368 int padding = 4;
2369 ImGui::SetNextWindowPos(vec2(-padding, -padding), ImGuiCond_Once);
2370 ImGui::SetNextWindowSize(vec2(width + 2 * padding, height + 2 * padding), ImGuiCond_Always);
2371 ImGui::Begin("WndMain", nullptr,
2372 ImGuiWindowFlags_NoTitleBar |
2373 ImGuiWindowFlags_NoResize |
2374 ImGuiWindowFlags_NoMove);
2375
2376 ButtonImGui btn("New Game");
2377
2378 ImGui::InvisibleButton("", vec2(10, height / 6));
2379 if (btn.draw((width - btn.getWidth()) / 2)) {
2380 goToScreen(&VulkanGame::renderGameScreen);
2381 }
2382
2383 ButtonImGui btn2("Quit");
2384
2385 ImGui::InvisibleButton("", vec2(10, 15));
2386 if (btn2.draw((width - btn2.getWidth()) / 2)) {
2387 quitGame();
2388 }
2389
2390 ImGui::End();
2391 }
2392}
2393
2394void VulkanGame::renderGameScreen(int width, int height) {
2395 {
2396 ImGui::SetNextWindowSize(vec2(130, 65), ImGuiCond_Once);
2397 ImGui::SetNextWindowPos(vec2(10, 50), ImGuiCond_Once);
2398 ImGui::Begin("WndStats", nullptr,
2399 ImGuiWindowFlags_NoTitleBar |
2400 ImGuiWindowFlags_NoResize |
2401 ImGuiWindowFlags_NoMove);
2402
2403 //ImGui::Text(ImGui::GetIO().Framerate);
2404 renderGuiValueList(valueLists["stats value list"]);
2405
2406 ImGui::End();
2407 }
2408
2409 {
2410 ImGui::SetNextWindowSize(vec2(250, 35), ImGuiCond_Once);
2411 ImGui::SetNextWindowPos(vec2(width - 260, 10), ImGuiCond_Always);
2412 ImGui::Begin("WndMenubar", nullptr,
2413 ImGuiWindowFlags_NoTitleBar |
2414 ImGuiWindowFlags_NoResize |
2415 ImGuiWindowFlags_NoMove);
2416 ImGui::InvisibleButton("", vec2(155, 18));
2417 ImGui::SameLine();
2418 if (ImGui::Button("Main Menu")) {
2419 goToScreen(&VulkanGame::renderMainScreen);
2420 }
2421 ImGui::End();
2422 }
2423
2424 {
2425 ImGui::SetNextWindowSize(vec2(200, 200), ImGuiCond_Once);
2426 ImGui::SetNextWindowPos(vec2(width - 210, 60), ImGuiCond_Always);
2427 ImGui::Begin("WndDebug", nullptr,
2428 ImGuiWindowFlags_NoTitleBar |
2429 ImGuiWindowFlags_NoResize |
2430 ImGuiWindowFlags_NoMove);
2431
2432 renderGuiValueList(valueLists["debug value list"]);
2433
2434 ImGui::End();
2435 }
2436}
2437
2438void VulkanGame::initGuiValueLists(map<string, vector<UIValue>>& valueLists) {
2439 valueLists["stats value list"] = vector<UIValue>();
2440 valueLists["debug value list"] = vector<UIValue>();
2441}
2442
2443// TODO: Probably turn this into a UI widget class
2444void VulkanGame::renderGuiValueList(vector<UIValue>& values) {
2445 float maxWidth = 0.0f;
2446 float cursorStartPos = ImGui::GetCursorPosX();
2447
2448 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
2449 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
2450
2451 if (maxWidth < textWidth)
2452 maxWidth = textWidth;
2453 }
2454
2455 stringstream ss;
2456
2457 // TODO: Possibly implement this based on gui/ui-value.hpp instead and use templates
2458 // to keep track of the type. This should make it a bit easier to use and maintain
2459 // Also, implement this in a way that's agnostic to the UI renderer.
2460 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
2461 ss.str("");
2462 ss.clear();
2463
2464 switch (it->type) {
2465 case UIVALUE_INT:
2466 ss << it->label << ": " << *(unsigned int*)it->value;
2467 break;
2468 case UIVALUE_DOUBLE:
2469 ss << it->label << ": " << *(double*)it->value;
2470 break;
2471 }
2472
2473 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
2474
2475 ImGui::SetCursorPosX(cursorStartPos + maxWidth - textWidth);
2476 //ImGui::Text("%s", ss.str().c_str());
2477 ImGui::Text("%s: %.1f", it->label.c_str(), *(float*)it->value);
2478 }
2479}
2480
2481void VulkanGame::goToScreen(void (VulkanGame::* renderScreenFn)(int width, int height)) {
2482 currentRenderScreenFn = renderScreenFn;
2483
2484 // TODO: Maybe just set shouldRecreateSwapChain to true instead. Check this render loop logic
2485 // to make sure there'd be no issues
2486 //recreateSwapChain();
2487}
2488
2489void VulkanGame::quitGame() {
2490 done = true;
2491}
Note: See TracBrowser for help on using the repository browser.