source: opengl-game/vulkan-game.cpp@ 567fa88

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

Change createRenderCommands() to support dynamic offsets

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