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

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

Rename createImguiDescriptorPool and createImguiDescriptorPool to
initImGuiOverlay and cleanupImGuiOverlay respectively and move all
ImGui-related init and cleanup code into them

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