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

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

Change VulkanGame::resizeBufferSet() to take a buffer size instead of an entire
VulkanBuffer object, and to not update descriptor sets, which obviates the need
to pass in a GraphicsPipeline_Vulkan object. Descriptor set updates must
now be handled separetly.

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