source: opengl-game/vulkan-game.cpp@ 5081b9a

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

Add prevTime and elapsedTime to SDLGame and refactor the event-handling code a little bit

  • Property mode set to 100644
File size: 91.0 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 ((e.type == UI_EVENT_MOUSEBUTTONDOWN || e.type == UI_EVENT_MOUSEBUTTONUP || e.type == UI_EVENT_UNKNOWN) &&
664 io.WantCaptureMouse) {
665 if (sdlEvent.type == SDL_MOUSEWHEEL || sdlEvent.type == SDL_MOUSEBUTTONDOWN ||
666 sdlEvent.type == SDL_MOUSEBUTTONUP) {
667 continue;
668 }
669 }
670 if ((e.type == UI_EVENT_KEYDOWN || e.type == UI_EVENT_KEYUP) && io.WantCaptureKeyboard) {
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_WINDOWRESIZE:
685 cout << "Window resize event detected" << endl;
686 shouldRecreateSwapChain = true;
687 break;
688 case UI_EVENT_KEYDOWN:
689 if (e.key.repeat) {
690 break;
691 }
692
693 if (e.key.keycode == SDL_SCANCODE_ESCAPE) {
694 done = true;
695 } else if (e.key.keycode == SDL_SCANCODE_SPACE) {
696 cout << "Adding a plane" << endl;
697 float zOffset = -2.0f + (0.5f * modelObjects.size());
698
699 SceneObject<ModelVertex, SSBO_ModelObject>& texturedSquare =
700 addObject(modelObjects, modelPipeline,
701 addObjectIndex<ModelVertex>(modelObjects.size(), {
702 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}},
703 {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}},
704 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
705 {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}
706 }), {
707 0, 1, 2, 2, 3, 0
708 }, {
709 mat4(1.0f)
710 }, true);
711
712 texturedSquare.model_base =
713 translate(mat4(1.0f), vec3(0.0f, 0.0f, zOffset));
714 texturedSquare.modified = true;
715 } else if (e.key.keycode == SDL_SCANCODE_Z && leftLaserIdx == -1) {
716 // TODO: When I start actually removing objects from the object vectors,
717 // I will need to update the indices since they might become incorrect
718 // or invalid as objects get moved around
719
720 vec3 offset(shipObjects[0].model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
721
722 addLaser(
723 vec3(-0.21f, -1.19f, 1.76f) + offset,
724 vec3(-0.21f, -1.19f, -3.0f) + offset,
725 LASER_COLOR, 0.03f);
726
727 leftLaserIdx = laserObjects.size() - 1;
728 } else if (e.key.keycode == SDL_SCANCODE_X && rightLaserIdx == -1) {
729 vec3 offset(shipObjects[0].model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
730
731 addLaser(
732 vec3(0.21f, -1.19f, 1.76f) + offset,
733 vec3(0.21f, -1.19f, -3.0f) + offset,
734 LASER_COLOR, 0.03f);
735
736 rightLaserIdx = laserObjects.size() - 1;
737 } else {
738 cout << "Key event detected" << endl;
739 }
740 break;
741 case UI_EVENT_KEYUP:
742 if (e.key.keycode == SDL_SCANCODE_Z && leftLaserIdx != -1) {
743 laserObjects[leftLaserIdx].ssbo.deleted = true;
744 laserObjects[leftLaserIdx].modified = true;
745 leftLaserIdx = -1;
746
747 if (leftLaserEffect != nullptr) {
748 leftLaserEffect->deleted = true;
749 leftLaserEffect = nullptr;
750 }
751 } else if (e.key.keycode == SDL_SCANCODE_X && rightLaserIdx != -1) {
752 laserObjects[rightLaserIdx].ssbo.deleted = true;
753 laserObjects[rightLaserIdx].modified = true;
754 rightLaserIdx = -1;
755
756 if (rightLaserEffect != nullptr) {
757 rightLaserEffect->deleted = true;
758 rightLaserEffect = nullptr;
759 }
760 }
761 break;
762 case UI_EVENT_WINDOW:
763 case UI_EVENT_MOUSEBUTTONDOWN:
764 case UI_EVENT_MOUSEBUTTONUP:
765 case UI_EVENT_MOUSEMOTION:
766 break;
767 case UI_EVENT_UNHANDLED:
768 cout << "Unhandled event type: 0x" << hex << sdlEvent.type << dec << endl;
769 break;
770 case UI_EVENT_UNKNOWN:
771 default:
772 cout << "Unknown event type: 0x" << hex << sdlEvent.type << dec << endl;
773 break;
774 }
775
776 // This was left ovedr from the previous SDL UI implementation.
777 // Might need something like this again when I start processing screen-specific UI events not related
778 // to the IMGUI ui, such as arrow keys for movement and other buttons for shotting or something
779 // currentScreen->handleEvent(e);
780 }
781
782 // Check which keys are held down
783
784 SceneObject<ShipVertex, SSBO_ModelObject>& ship = shipObjects[0];
785
786 if (gui->keyPressed(SDL_SCANCODE_LEFT)) {
787 float distance = -this->shipSpeed * this->elapsedTime;
788
789 ship.model_transform = translate(mat4(1.0f), vec3(distance, 0.0f, 0.0f))
790 * shipObjects[0].model_transform;
791 ship.modified = true;
792
793 if (leftLaserIdx != -1) {
794 translateLaser(leftLaserIdx, vec3(distance, 0.0f, 0.0f));
795 }
796 if (rightLaserIdx != -1) {
797 translateLaser(rightLaserIdx, vec3(distance, 0.0f, 0.0f));
798 }
799 } else if (gui->keyPressed(SDL_SCANCODE_RIGHT)) {
800 float distance = this->shipSpeed * this->elapsedTime;
801
802 ship.model_transform = translate(mat4(1.0f), vec3(distance, 0.0f, 0.0f))
803 * shipObjects[0].model_transform;
804 ship.modified = true;
805
806 if (leftLaserIdx != -1) {
807 translateLaser(leftLaserIdx, vec3(distance, 0.0f, 0.0f));
808 }
809 if (rightLaserIdx != -1) {
810 translateLaser(rightLaserIdx, vec3(distance, 0.0f, 0.0f));
811 }
812 }
813
814 if (shouldRecreateSwapChain) {
815 gui->refreshWindowSize();
816 const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
817
818 if (!isMinimized) {
819 // TODO: This should be used if the min image count changes, presumably because a new surface was created
820 // with a different image count or something like that. Maybe I want to add code to query for a new min image count
821 // during swapchain recreation to take advantage of this
822 ImGui_ImplVulkan_SetMinImageCount(swapChainMinImageCount);
823
824 recreateSwapChain();
825
826 shouldRecreateSwapChain = false;
827 }
828 }
829
830 updateScene();
831
832 // TODO: Move this into a renderImGuiOverlay() function
833 ImGui_ImplVulkan_NewFrame();
834 ImGui_ImplSDL2_NewFrame(window);
835 ImGui::NewFrame();
836
837 (this->*currentRenderScreenFn)(gui->getWindowWidth(), gui->getWindowHeight());
838
839 ImGui::Render();
840
841 gui->refreshWindowSize();
842 const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
843
844 if (!isMinimized) {
845 renderFrame(ImGui::GetDrawData());
846 presentFrame();
847 }
848 }
849}
850
851// TODO: The only updates that need to happen once per Vulkan image are the SSBO ones,
852// which are already handled by updateObject(). Move this code to a different place,
853// where it will run just once per frame
854void VulkanGame::updateScene() {
855 for (SceneObject<ModelVertex, SSBO_ModelObject>& model : this->modelObjects) {
856 model.model_transform =
857 translate(mat4(1.0f), vec3(0.0f, -2.0f, -0.0f)) *
858 rotate(mat4(1.0f), curTime * radians(90.0f), vec3(0.0f, 0.0f, 1.0f));
859 model.modified = true;
860 }
861
862 if (leftLaserIdx != -1) {
863 updateLaserTarget(leftLaserIdx);
864 }
865 if (rightLaserIdx != -1) {
866 updateLaserTarget(rightLaserIdx);
867 }
868
869 for (vector<BaseEffectOverTime*>::iterator it = effects.begin(); it != effects.end(); ) {
870 if ((*it)->deleted) {
871 delete *it;
872 it = effects.erase(it);
873 } else {
874 BaseEffectOverTime* eot = *it;
875
876 eot->applyEffect(curTime);
877
878 it++;
879 }
880 }
881
882 for (SceneObject<AsteroidVertex, SSBO_Asteroid>& asteroid : this->asteroidObjects) {
883 if (!asteroid.ssbo.deleted) {
884 vec3 objCenter = vec3(viewMat * vec4(asteroid.center, 1.0f));
885
886 if (asteroid.ssbo.hp <= 0.0f) {
887 asteroid.ssbo.deleted = true;
888
889 // TODO: Optimize this so I don't recalculate the camera rotation every time
890 // TODO: Also, avoid re-declaring cam_pitch
891 float cam_pitch = -50.0f;
892 mat4 pitch_mat = rotate(mat4(1.0f), radians(cam_pitch), vec3(1.0f, 0.0f, 0.0f));
893 mat4 model_mat = translate(mat4(1.0f), asteroid.center) * pitch_mat;
894
895 addExplosion(model_mat, 0.5f, curTime);
896
897 this->score++;
898 } else if ((objCenter.z - asteroid.radius) > -NEAR_CLIP) {
899 asteroid.ssbo.deleted = true;
900 } else {
901 asteroid.model_transform =
902 translate(mat4(1.0f), vec3(0.0f, 0.0f, this->asteroidSpeed * this->elapsedTime)) *
903 asteroid.model_transform;
904 }
905
906 asteroid.modified = true;
907 }
908 }
909
910 if (curTime - this->lastSpawn_asteroid > this->spawnRate_asteroid) {
911 this->lastSpawn_asteroid = curTime;
912
913 SceneObject<AsteroidVertex, SSBO_Asteroid>& asteroid = addObject(
914 asteroidObjects, asteroidPipeline,
915 addObjectIndex<AsteroidVertex>(asteroidObjects.size(),
916 addVertexNormals<AsteroidVertex>({
917
918 // front
919 {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
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
926 // top
927 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
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
934 // bottom
935 {{ 1.0f, -1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
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.0}, {0.4f, 0.4f, 0.4f}},
941
942 // back
943 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
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
950 // right
951 {{ 1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
952 {{ 1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
953 {{ 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
958 // left
959 {{-1.0f, 1.0f, 1.0f}, {0.4f, 0.4f, 0.4f}},
960 {{-1.0f, 1.0f, -1.0f}, {0.4f, 0.4f, 0.4f}},
961 {{-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 })), {
966 0, 1, 2, 3, 4, 5,
967 6, 7, 8, 9, 10, 11,
968 12, 13, 14, 15, 16, 17,
969 18, 19, 20, 21, 22, 23,
970 24, 25, 26, 27, 28, 29,
971 30, 31, 32, 33, 34, 35,
972 }, {
973 mat4(1.0f),
974 10.0f,
975 false
976 }, true);
977
978 // This accounts for the scaling in model_base.
979 // Dividing by 8 instead of 10 since the bounding radius algorithm
980 // under-calculates the true value.
981 // TODO: Figure out the best way to take scaling into account when calculating the radius
982 // Keep in mind that the main complicating factor is the currently poor radius calculation
983 asteroid.radius /= 8.0f;
984
985 asteroid.model_base =
986 translate(mat4(1.0f), vec3(getRandomNum(-1.3f, 1.3f), -1.2f, getRandomNum(-5.5f, -4.5f))) *
987 rotate(mat4(1.0f), radians(60.0f), vec3(1.0f, 1.0f, -1.0f)) *
988 scale(mat4(1.0f), vec3(0.1f, 0.1f, 0.1f));
989 asteroid.modified = true;
990 }
991
992 for (SceneObject<ExplosionVertex, SSBO_Explosion>& explosion : this->explosionObjects) {
993 if (!explosion.ssbo.deleted) {
994 if (curTime > (explosion.ssbo.explosionStartTime + explosion.ssbo.explosionDuration)) {
995 explosion.ssbo.deleted = true;
996 explosion.modified = true;
997 }
998 }
999 }
1000
1001 for (size_t i = 0; i < shipObjects.size(); i++) {
1002 if (shipObjects[i].modified) {
1003 updateObject(shipObjects, shipPipeline, i);
1004 }
1005 }
1006
1007 for (size_t i = 0; i < modelObjects.size(); i++) {
1008 if (modelObjects[i].modified) {
1009 updateObject(modelObjects, modelPipeline, i);
1010 }
1011 }
1012
1013 for (size_t i = 0; i < asteroidObjects.size(); i++) {
1014 if (asteroidObjects[i].modified) {
1015 updateObject(asteroidObjects, asteroidPipeline, i);
1016 }
1017 }
1018
1019 for (size_t i = 0; i < laserObjects.size(); i++) {
1020 if (laserObjects[i].modified) {
1021 updateObject(laserObjects, laserPipeline, i);
1022 }
1023 }
1024
1025 for (size_t i = 0; i < explosionObjects.size(); i++) {
1026 if (explosionObjects[i].modified) {
1027 updateObject(explosionObjects, explosionPipeline, i);
1028 }
1029 }
1030
1031 explosion_UBO.cur_time = curTime;
1032
1033 VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_modelPipeline[imageIndex], 0, object_VP_mats);
1034
1035 VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_shipPipeline[imageIndex], 0, ship_VP_mats);
1036
1037 VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_asteroidPipeline[imageIndex], 0, asteroid_VP_mats);
1038
1039 VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_laserPipeline[imageIndex], 0, laser_VP_mats);
1040
1041 VulkanUtils::copyDataToMemory(device, uniformBuffersMemory_explosionPipeline[imageIndex], 0, explosion_UBO);
1042}
1043
1044void VulkanGame::cleanup() {
1045 // FIXME: We could wait on the Queue if we had the queue in wd-> (otherwise VulkanH functions can't use globals)
1046 //vkQueueWaitIdle(g_Queue);
1047 VKUTIL_CHECK_RESULT(vkDeviceWaitIdle(device), "failed to wait for device!");
1048
1049 cleanupImGuiOverlay();
1050
1051 cleanupSwapChain();
1052
1053 VulkanUtils::destroyVulkanImage(device, floorTextureImage);
1054 VulkanUtils::destroyVulkanImage(device, laserTextureImage);
1055
1056 vkDestroySampler(device, textureSampler, nullptr);
1057
1058 modelPipeline.cleanupBuffers();
1059 shipPipeline.cleanupBuffers();
1060 asteroidPipeline.cleanupBuffers();
1061 laserPipeline.cleanupBuffers();
1062 explosionPipeline.cleanupBuffers();
1063
1064 vkDestroyCommandPool(device, resourceCommandPool, nullptr);
1065
1066 vkDestroyDevice(device, nullptr);
1067 vkDestroySurfaceKHR(instance, vulkanSurface, nullptr);
1068
1069 if (ENABLE_VALIDATION_LAYERS) {
1070 VulkanUtils::destroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
1071 }
1072
1073 vkDestroyInstance(instance, nullptr);
1074
1075 gui->destroyWindow();
1076 gui->shutdown();
1077 delete gui;
1078}
1079
1080void VulkanGame::createVulkanInstance(const vector<const char*>& validationLayers) {
1081 if (ENABLE_VALIDATION_LAYERS && !VulkanUtils::checkValidationLayerSupport(validationLayers)) {
1082 throw runtime_error("validation layers requested, but not available!");
1083 }
1084
1085 VkApplicationInfo appInfo = {};
1086 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
1087 appInfo.pApplicationName = "Vulkan Game";
1088 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
1089 appInfo.pEngineName = "No Engine";
1090 appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
1091 appInfo.apiVersion = VK_API_VERSION_1_0;
1092
1093 VkInstanceCreateInfo createInfo = {};
1094 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
1095 createInfo.pApplicationInfo = &appInfo;
1096
1097 vector<const char*> extensions = gui->getRequiredExtensions();
1098 if (ENABLE_VALIDATION_LAYERS) {
1099 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
1100 }
1101
1102 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
1103 createInfo.ppEnabledExtensionNames = extensions.data();
1104
1105 cout << endl << "Extensions:" << endl;
1106 for (const char* extensionName : extensions) {
1107 cout << extensionName << endl;
1108 }
1109 cout << endl;
1110
1111 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
1112 if (ENABLE_VALIDATION_LAYERS) {
1113 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
1114 createInfo.ppEnabledLayerNames = validationLayers.data();
1115
1116 populateDebugMessengerCreateInfo(debugCreateInfo);
1117 createInfo.pNext = &debugCreateInfo;
1118 } else {
1119 createInfo.enabledLayerCount = 0;
1120
1121 createInfo.pNext = nullptr;
1122 }
1123
1124 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
1125 throw runtime_error("failed to create instance!");
1126 }
1127}
1128
1129void VulkanGame::setupDebugMessenger() {
1130 if (!ENABLE_VALIDATION_LAYERS) {
1131 return;
1132 }
1133
1134 VkDebugUtilsMessengerCreateInfoEXT createInfo;
1135 populateDebugMessengerCreateInfo(createInfo);
1136
1137 if (VulkanUtils::createDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
1138 throw runtime_error("failed to set up debug messenger!");
1139 }
1140}
1141
1142void VulkanGame::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
1143 createInfo = {};
1144 createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
1145 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;
1146 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;
1147 createInfo.pfnUserCallback = debugCallback;
1148}
1149
1150VKAPI_ATTR VkBool32 VKAPI_CALL VulkanGame::debugCallback(
1151 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
1152 VkDebugUtilsMessageTypeFlagsEXT messageType,
1153 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
1154 void* pUserData) {
1155 cerr << "validation layer: " << pCallbackData->pMessage << endl;
1156
1157 // TODO: Figure out what the return value means and if it should always be VK_FALSE
1158 return VK_FALSE;
1159}
1160
1161void VulkanGame::createVulkanSurface() {
1162 if (gui->createVulkanSurface(instance, &vulkanSurface) == RTWO_ERROR) {
1163 throw runtime_error("failed to create window surface!");
1164 }
1165}
1166
1167void VulkanGame::pickPhysicalDevice(const vector<const char*>& deviceExtensions) {
1168 uint32_t deviceCount = 0;
1169 // TODO: Check VkResult
1170 vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
1171
1172 if (deviceCount == 0) {
1173 throw runtime_error("failed to find GPUs with Vulkan support!");
1174 }
1175
1176 vector<VkPhysicalDevice> devices(deviceCount);
1177 // TODO: Check VkResult
1178 vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
1179
1180 cout << endl << "Graphics cards:" << endl;
1181 for (const VkPhysicalDevice& device : devices) {
1182 if (isDeviceSuitable(device, deviceExtensions)) {
1183 physicalDevice = device;
1184 break;
1185 }
1186 }
1187 cout << endl;
1188
1189 if (physicalDevice == VK_NULL_HANDLE) {
1190 throw runtime_error("failed to find a suitable GPU!");
1191 }
1192}
1193
1194bool VulkanGame::isDeviceSuitable(VkPhysicalDevice physicalDevice, const vector<const char*>& deviceExtensions) {
1195 VkPhysicalDeviceProperties deviceProperties;
1196 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
1197
1198 cout << "Device: " << deviceProperties.deviceName << endl;
1199
1200 // TODO: Eventually, maybe let the user pick out of a set of GPUs in case the user does want to use
1201 // an integrated GPU. On my laptop, this function returns TRUE for the integrated GPU, but crashes
1202 // when trying to use it to render. Maybe I just need to figure out which other extensions and features
1203 // to check.
1204 if (deviceProperties.deviceType != VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
1205 return false;
1206 }
1207
1208 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1209 bool extensionsSupported = VulkanUtils::checkDeviceExtensionSupport(physicalDevice, deviceExtensions);
1210 bool swapChainAdequate = false;
1211
1212 if (extensionsSupported) {
1213 vector<VkSurfaceFormatKHR> formats = VulkanUtils::querySwapChainFormats(physicalDevice, vulkanSurface);
1214 vector<VkPresentModeKHR> presentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, vulkanSurface);
1215
1216 swapChainAdequate = !formats.empty() && !presentModes.empty();
1217 }
1218
1219 VkPhysicalDeviceFeatures supportedFeatures;
1220 vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
1221
1222 return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
1223}
1224
1225void VulkanGame::createLogicalDevice(const vector<const char*>& validationLayers,
1226 const vector<const char*>& deviceExtensions) {
1227 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1228
1229 if (!indices.isComplete()) {
1230 throw runtime_error("failed to find required queue families!");
1231 }
1232
1233 // TODO: Using separate graphics and present queues currently works, but I should verify that I'm
1234 // using them correctly to get the most benefit out of separate queues
1235
1236 vector<VkDeviceQueueCreateInfo> queueCreateInfoList;
1237 set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
1238
1239 float queuePriority = 1.0f;
1240 for (uint32_t queueFamily : uniqueQueueFamilies) {
1241 VkDeviceQueueCreateInfo queueCreateInfo = {};
1242 queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
1243 queueCreateInfo.queueCount = 1;
1244 queueCreateInfo.queueFamilyIndex = queueFamily;
1245 queueCreateInfo.pQueuePriorities = &queuePriority;
1246
1247 queueCreateInfoList.push_back(queueCreateInfo);
1248 }
1249
1250 VkPhysicalDeviceFeatures deviceFeatures = {};
1251 deviceFeatures.samplerAnisotropy = VK_TRUE;
1252
1253 VkDeviceCreateInfo createInfo = {};
1254 createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
1255
1256 createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfoList.size());
1257 createInfo.pQueueCreateInfos = queueCreateInfoList.data();
1258
1259 createInfo.pEnabledFeatures = &deviceFeatures;
1260
1261 createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
1262 createInfo.ppEnabledExtensionNames = deviceExtensions.data();
1263
1264 // These fields are ignored by up-to-date Vulkan implementations,
1265 // but it's a good idea to set them for backwards compatibility
1266 if (ENABLE_VALIDATION_LAYERS) {
1267 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
1268 createInfo.ppEnabledLayerNames = validationLayers.data();
1269 } else {
1270 createInfo.enabledLayerCount = 0;
1271 }
1272
1273 if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
1274 throw runtime_error("failed to create logical device!");
1275 }
1276
1277 vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
1278 vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
1279}
1280
1281void VulkanGame::chooseSwapChainProperties() {
1282 vector<VkSurfaceFormatKHR> availableFormats = VulkanUtils::querySwapChainFormats(physicalDevice, vulkanSurface);
1283 vector<VkPresentModeKHR> availablePresentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, vulkanSurface);
1284
1285 swapChainSurfaceFormat = VulkanUtils::chooseSwapSurfaceFormat(availableFormats,
1286 { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM },
1287 VK_COLOR_SPACE_SRGB_NONLINEAR_KHR);
1288
1289 vector<VkPresentModeKHR> presentModes{
1290 VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR
1291 };
1292 //vector<VkPresentModeKHR> presentModes{ VK_PRESENT_MODE_FIFO_KHR };
1293
1294 swapChainPresentMode = VulkanUtils::chooseSwapPresentMode(availablePresentModes, presentModes);
1295
1296 cout << "[vulkan] Selected PresentMode = " << swapChainPresentMode << endl;
1297
1298 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, vulkanSurface);
1299
1300 if (swapChainPresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
1301 swapChainMinImageCount = 3;
1302 } else if (swapChainPresentMode == VK_PRESENT_MODE_FIFO_KHR || swapChainPresentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) {
1303 swapChainMinImageCount = 2;
1304 } else if (swapChainPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
1305 swapChainMinImageCount = 1;
1306 } else {
1307 throw runtime_error("unexpected present mode!");
1308 }
1309
1310 if (swapChainMinImageCount < capabilities.minImageCount) {
1311 swapChainMinImageCount = capabilities.minImageCount;
1312 } else if (capabilities.maxImageCount != 0 && swapChainMinImageCount > capabilities.maxImageCount) {
1313 swapChainMinImageCount = capabilities.maxImageCount;
1314 }
1315}
1316
1317void VulkanGame::createSwapChain() {
1318 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, vulkanSurface);
1319
1320 swapChainExtent = VulkanUtils::chooseSwapExtent(capabilities, gui->getWindowWidth(), gui->getWindowHeight());
1321
1322 VkSwapchainCreateInfoKHR createInfo = {};
1323 createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
1324 createInfo.surface = vulkanSurface;
1325 createInfo.minImageCount = swapChainMinImageCount;
1326 createInfo.imageFormat = swapChainSurfaceFormat.format;
1327 createInfo.imageColorSpace = swapChainSurfaceFormat.colorSpace;
1328 createInfo.imageExtent = swapChainExtent;
1329 createInfo.imageArrayLayers = 1;
1330 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
1331
1332 // TODO: Maybe save this result so I don't have to recalculate it every time
1333 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1334 uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
1335
1336 if (indices.graphicsFamily != indices.presentFamily) {
1337 createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
1338 createInfo.queueFamilyIndexCount = 2;
1339 createInfo.pQueueFamilyIndices = queueFamilyIndices;
1340 } else {
1341 createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
1342 createInfo.queueFamilyIndexCount = 0;
1343 createInfo.pQueueFamilyIndices = nullptr;
1344 }
1345
1346 createInfo.preTransform = capabilities.currentTransform;
1347 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
1348 createInfo.presentMode = swapChainPresentMode;
1349 createInfo.clipped = VK_TRUE;
1350 createInfo.oldSwapchain = VK_NULL_HANDLE;
1351
1352 if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
1353 throw runtime_error("failed to create swap chain!");
1354 }
1355
1356 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, nullptr) != VK_SUCCESS) {
1357 throw runtime_error("failed to get swap chain image count!");
1358 }
1359
1360 swapChainImages.resize(swapChainImageCount);
1361 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, swapChainImages.data()) != VK_SUCCESS) {
1362 throw runtime_error("failed to get swap chain images!");
1363 }
1364}
1365
1366void VulkanGame::createImageViews() {
1367 swapChainImageViews.resize(swapChainImageCount);
1368
1369 for (size_t i = 0; i < swapChainImageCount; i++) {
1370 swapChainImageViews[i] = VulkanUtils::createImageView(device, swapChainImages[i], swapChainSurfaceFormat.format,
1371 VK_IMAGE_ASPECT_COLOR_BIT);
1372 }
1373}
1374
1375void VulkanGame::createResourceCommandPool() {
1376 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1377
1378 VkCommandPoolCreateInfo poolInfo = {};
1379 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
1380 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
1381 poolInfo.flags = 0;
1382
1383 if (vkCreateCommandPool(device, &poolInfo, nullptr, &resourceCommandPool) != VK_SUCCESS) {
1384 throw runtime_error("failed to create resource command pool!");
1385 }
1386}
1387
1388void VulkanGame::createImageResources() {
1389 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
1390 depthImage, graphicsQueue);
1391
1392 createTextureSampler();
1393
1394 // TODO: Move all images/textures somewhere into the assets folder
1395
1396 VulkanUtils::createVulkanImageFromFile(device, physicalDevice, resourceCommandPool, "textures/texture.jpg",
1397 floorTextureImage, graphicsQueue);
1398
1399 floorTextureImageDescriptor = {};
1400 floorTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1401 floorTextureImageDescriptor.imageView = floorTextureImage.imageView;
1402 floorTextureImageDescriptor.sampler = textureSampler;
1403
1404 VulkanUtils::createVulkanImageFromFile(device, physicalDevice, resourceCommandPool, "textures/laser.png",
1405 laserTextureImage, graphicsQueue);
1406
1407 laserTextureImageDescriptor = {};
1408 laserTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1409 laserTextureImageDescriptor.imageView = laserTextureImage.imageView;
1410 laserTextureImageDescriptor.sampler = textureSampler;
1411}
1412
1413VkFormat VulkanGame::findDepthFormat() {
1414 return VulkanUtils::findSupportedFormat(
1415 physicalDevice,
1416 { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT },
1417 VK_IMAGE_TILING_OPTIMAL,
1418 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
1419 );
1420}
1421
1422void VulkanGame::createRenderPass() {
1423 VkAttachmentDescription colorAttachment = {};
1424 colorAttachment.format = swapChainSurfaceFormat.format;
1425 colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
1426 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1427 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
1428 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1429 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1430 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1431 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
1432
1433 VkAttachmentReference colorAttachmentRef = {};
1434 colorAttachmentRef.attachment = 0;
1435 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
1436
1437 VkAttachmentDescription depthAttachment = {};
1438 depthAttachment.format = findDepthFormat();
1439 depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
1440 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1441 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1442 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1443 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1444 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1445 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1446
1447 VkAttachmentReference depthAttachmentRef = {};
1448 depthAttachmentRef.attachment = 1;
1449 depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1450
1451 VkSubpassDescription subpass = {};
1452 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
1453 subpass.colorAttachmentCount = 1;
1454 subpass.pColorAttachments = &colorAttachmentRef;
1455 subpass.pDepthStencilAttachment = &depthAttachmentRef;
1456
1457 VkSubpassDependency dependency = {};
1458 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
1459 dependency.dstSubpass = 0;
1460 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1461 dependency.srcAccessMask = 0;
1462 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1463 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
1464
1465 array<VkAttachmentDescription, 2> attachments = { colorAttachment, depthAttachment };
1466 VkRenderPassCreateInfo renderPassInfo = {};
1467 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
1468 renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
1469 renderPassInfo.pAttachments = attachments.data();
1470 renderPassInfo.subpassCount = 1;
1471 renderPassInfo.pSubpasses = &subpass;
1472 renderPassInfo.dependencyCount = 1;
1473 renderPassInfo.pDependencies = &dependency;
1474
1475 if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
1476 throw runtime_error("failed to create render pass!");
1477 }
1478}
1479
1480void VulkanGame::createCommandPools() {
1481 commandPools.resize(swapChainImageCount);
1482
1483 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1484
1485 for (size_t i = 0; i < swapChainImageCount; i++) {
1486 VkCommandPoolCreateInfo poolInfo = {};
1487 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
1488 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
1489 poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
1490
1491 VKUTIL_CHECK_RESULT(vkCreateCommandPool(device, &poolInfo, nullptr, &commandPools[i]),
1492 "failed to create graphics command pool!");
1493 }
1494}
1495
1496void VulkanGame::createTextureSampler() {
1497 VkSamplerCreateInfo samplerInfo = {};
1498 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1499 samplerInfo.magFilter = VK_FILTER_LINEAR;
1500 samplerInfo.minFilter = VK_FILTER_LINEAR;
1501
1502 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1503 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1504 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1505
1506 samplerInfo.anisotropyEnable = VK_TRUE;
1507 samplerInfo.maxAnisotropy = 16;
1508 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
1509 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1510 samplerInfo.compareEnable = VK_FALSE;
1511 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1512 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
1513 samplerInfo.mipLodBias = 0.0f;
1514 samplerInfo.minLod = 0.0f;
1515 samplerInfo.maxLod = 0.0f;
1516
1517 VKUTIL_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler),
1518 "failed to create texture sampler!");
1519}
1520
1521void VulkanGame::createFramebuffers() {
1522 swapChainFramebuffers.resize(swapChainImageCount);
1523
1524 VkFramebufferCreateInfo framebufferInfo = {};
1525 framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
1526 framebufferInfo.renderPass = renderPass;
1527 framebufferInfo.width = swapChainExtent.width;
1528 framebufferInfo.height = swapChainExtent.height;
1529 framebufferInfo.layers = 1;
1530
1531 for (uint32_t i = 0; i < swapChainImageCount; i++) {
1532 array<VkImageView, 2> attachments = {
1533 swapChainImageViews[i],
1534 depthImage.imageView
1535 };
1536
1537 framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
1538 framebufferInfo.pAttachments = attachments.data();
1539
1540 if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
1541 throw runtime_error("failed to create framebuffer!");
1542 }
1543 }
1544}
1545
1546void VulkanGame::createCommandBuffers() {
1547 commandBuffers.resize(swapChainImageCount);
1548
1549 for (size_t i = 0; i < swapChainImageCount; i++) {
1550 VkCommandBufferAllocateInfo allocInfo = {};
1551 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1552 allocInfo.commandPool = commandPools[i];
1553 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1554 allocInfo.commandBufferCount = 1;
1555
1556 if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffers[i]) != VK_SUCCESS) {
1557 throw runtime_error("failed to allocate command buffer!");
1558 }
1559 }
1560}
1561
1562void VulkanGame::createSyncObjects() {
1563 imageAcquiredSemaphores.resize(swapChainImageCount);
1564 renderCompleteSemaphores.resize(swapChainImageCount);
1565 inFlightFences.resize(swapChainImageCount);
1566
1567 VkSemaphoreCreateInfo semaphoreInfo = {};
1568 semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
1569
1570 VkFenceCreateInfo fenceInfo = {};
1571 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
1572 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
1573
1574 for (size_t i = 0; i < swapChainImageCount; i++) {
1575 VKUTIL_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAcquiredSemaphores[i]),
1576 "failed to create image acquired sempahore for a frame!");
1577
1578 VKUTIL_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderCompleteSemaphores[i]),
1579 "failed to create render complete sempahore for a frame!");
1580
1581 VKUTIL_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]),
1582 "failed to create fence for a frame!");
1583 }
1584}
1585
1586void VulkanGame::renderFrame(ImDrawData* draw_data) {
1587 VkResult result = vkAcquireNextImageKHR(device, swapChain, numeric_limits<uint64_t>::max(),
1588 imageAcquiredSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
1589
1590 if (result == VK_SUBOPTIMAL_KHR) {
1591 shouldRecreateSwapChain = true;
1592 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1593 shouldRecreateSwapChain = true;
1594 return;
1595 } else {
1596 VKUTIL_CHECK_RESULT(result, "failed to acquire swap chain image!");
1597 }
1598
1599 VKUTIL_CHECK_RESULT(
1600 vkWaitForFences(device, 1, &inFlightFences[imageIndex], VK_TRUE, numeric_limits<uint64_t>::max()),
1601 "failed waiting for fence!");
1602
1603 VKUTIL_CHECK_RESULT(vkResetFences(device, 1, &inFlightFences[imageIndex]),
1604 "failed to reset fence!");
1605
1606 VKUTIL_CHECK_RESULT(vkResetCommandPool(device, commandPools[imageIndex], 0),
1607 "failed to reset command pool!");
1608
1609 VkCommandBufferBeginInfo beginInfo = {};
1610 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1611 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1612
1613 VKUTIL_CHECK_RESULT(vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo),
1614 "failed to begin recording command buffer!");
1615
1616 VkRenderPassBeginInfo renderPassInfo = {};
1617 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
1618 renderPassInfo.renderPass = renderPass;
1619 renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
1620 renderPassInfo.renderArea.offset = { 0, 0 };
1621 renderPassInfo.renderArea.extent = swapChainExtent;
1622
1623 array<VkClearValue, 2> clearValues = {};
1624 clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
1625 clearValues[1].depthStencil = { 1.0f, 0 };
1626
1627 renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
1628 renderPassInfo.pClearValues = clearValues.data();
1629
1630 vkCmdBeginRenderPass(commandBuffers[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
1631
1632 // TODO: Find a more elegant, per-screen solution for this
1633 if (currentRenderScreenFn == &VulkanGame::renderGameScreen) {
1634 modelPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex);
1635 shipPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex);
1636 asteroidPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex);
1637 laserPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex);
1638 explosionPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex);
1639 }
1640
1641 ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffers[imageIndex]);
1642
1643 vkCmdEndRenderPass(commandBuffers[imageIndex]);
1644
1645 VKUTIL_CHECK_RESULT(vkEndCommandBuffer(commandBuffers[imageIndex]),
1646 "failed to record command buffer!");
1647
1648 VkSemaphore waitSemaphores[] = { imageAcquiredSemaphores[currentFrame] };
1649 VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
1650 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
1651
1652 VkSubmitInfo submitInfo = {};
1653 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1654 submitInfo.waitSemaphoreCount = 1;
1655 submitInfo.pWaitSemaphores = waitSemaphores;
1656 submitInfo.pWaitDstStageMask = waitStages;
1657 submitInfo.commandBufferCount = 1;
1658 submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
1659 submitInfo.signalSemaphoreCount = 1;
1660 submitInfo.pSignalSemaphores = signalSemaphores;
1661
1662 VKUTIL_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[imageIndex]),
1663 "failed to submit draw command buffer!");
1664}
1665
1666void VulkanGame::presentFrame() {
1667 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
1668
1669 VkPresentInfoKHR presentInfo = {};
1670 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
1671 presentInfo.waitSemaphoreCount = 1;
1672 presentInfo.pWaitSemaphores = signalSemaphores;
1673 presentInfo.swapchainCount = 1;
1674 presentInfo.pSwapchains = &swapChain;
1675 presentInfo.pImageIndices = &imageIndex;
1676 presentInfo.pResults = nullptr;
1677
1678 VkResult result = vkQueuePresentKHR(presentQueue, &presentInfo);
1679
1680 if (result == VK_SUBOPTIMAL_KHR) {
1681 shouldRecreateSwapChain = true;
1682 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1683 shouldRecreateSwapChain = true;
1684 return;
1685 } else {
1686 VKUTIL_CHECK_RESULT(result, "failed to present swap chain image!");
1687 }
1688
1689 currentFrame = (currentFrame + 1) % swapChainImageCount;
1690}
1691
1692void VulkanGame::initImGuiOverlay() {
1693 vector<VkDescriptorPoolSize> pool_sizes{
1694 { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
1695 { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
1696 { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
1697 { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 },
1698 { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 },
1699 { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 },
1700 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 },
1701 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 },
1702 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 },
1703 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 },
1704 { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 }
1705 };
1706
1707 VkDescriptorPoolCreateInfo pool_info = {};
1708 pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1709 pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
1710 pool_info.maxSets = 1000 * pool_sizes.size();
1711 pool_info.poolSizeCount = static_cast<uint32_t>(pool_sizes.size());
1712 pool_info.pPoolSizes = pool_sizes.data();
1713
1714 VKUTIL_CHECK_RESULT(vkCreateDescriptorPool(device, &pool_info, nullptr, &imguiDescriptorPool),
1715 "failed to create IMGUI descriptor pool!");
1716
1717 // TODO: Do this in one place and save it instead of redoing it every time I need a queue family index
1718 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1719
1720 // Setup Dear ImGui context
1721 IMGUI_CHECKVERSION();
1722 ImGui::CreateContext();
1723 ImGuiIO& io = ImGui::GetIO();
1724 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
1725 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
1726
1727 // Setup Dear ImGui style
1728 ImGui::StyleColorsDark();
1729 //ImGui::StyleColorsClassic();
1730
1731 // Setup Platform/Renderer bindings
1732 ImGui_ImplSDL2_InitForVulkan(window);
1733 ImGui_ImplVulkan_InitInfo init_info = {};
1734 init_info.Instance = instance;
1735 init_info.PhysicalDevice = physicalDevice;
1736 init_info.Device = device;
1737 init_info.QueueFamily = indices.graphicsFamily.value();
1738 init_info.Queue = graphicsQueue;
1739 init_info.DescriptorPool = imguiDescriptorPool;
1740 init_info.Allocator = nullptr;
1741 init_info.MinImageCount = swapChainMinImageCount;
1742 init_info.ImageCount = swapChainImageCount;
1743 init_info.CheckVkResultFn = check_imgui_vk_result;
1744 ImGui_ImplVulkan_Init(&init_info, renderPass);
1745
1746 // Load Fonts
1747 // - 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.
1748 // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
1749 // - 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).
1750 // - 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.
1751 // - Read 'docs/FONTS.md' for more instructions and details.
1752 // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
1753 //io.Fonts->AddFontDefault();
1754 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
1755 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
1756 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
1757 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f);
1758 //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
1759 //assert(font != NULL);
1760
1761 // Upload Fonts
1762 {
1763 VkCommandBuffer commandBuffer = VulkanUtils::beginSingleTimeCommands(device, resourceCommandPool);
1764
1765 ImGui_ImplVulkan_CreateFontsTexture(commandBuffer);
1766
1767 VulkanUtils::endSingleTimeCommands(device, resourceCommandPool, commandBuffer, graphicsQueue);
1768
1769 ImGui_ImplVulkan_DestroyFontUploadObjects();
1770 }
1771}
1772
1773void VulkanGame::cleanupImGuiOverlay() {
1774 ImGui_ImplVulkan_Shutdown();
1775 ImGui_ImplSDL2_Shutdown();
1776 ImGui::DestroyContext();
1777
1778 vkDestroyDescriptorPool(device, imguiDescriptorPool, nullptr);
1779}
1780
1781void VulkanGame::createBufferSet(VkDeviceSize bufferSize, VkBufferUsageFlags flags,
1782 vector<VkBuffer>& buffers, vector<VkDeviceMemory>& buffersMemory, vector<VkDescriptorBufferInfo>& bufferInfoList) {
1783 buffers.resize(swapChainImageCount);
1784 buffersMemory.resize(swapChainImageCount);
1785 bufferInfoList.resize(swapChainImageCount);
1786
1787 for (size_t i = 0; i < swapChainImageCount; i++) {
1788 VulkanUtils::createBuffer(device, physicalDevice, bufferSize, flags,
1789 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1790 buffers[i], buffersMemory[i]);
1791
1792 bufferInfoList[i].buffer = buffers[i];
1793 bufferInfoList[i].offset = 0; // This is the offset from the start of the buffer, so always 0 for now
1794 bufferInfoList[i].range = bufferSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
1795 }
1796}
1797
1798void VulkanGame::addLaser(vec3 start, vec3 end, vec3 color, float width) {
1799 vec3 ray = end - start;
1800 float length = glm::length(ray);
1801
1802 SceneObject<LaserVertex, SSBO_Laser>& laser = addObject(
1803 laserObjects, laserPipeline,
1804 addObjectIndex<LaserVertex>(laserObjects.size(), {
1805 {{ width / 2, 0.0f, -width / 2 }, {1.0f, 0.5f }},
1806 {{-width / 2, 0.0f, -width / 2 }, {0.0f, 0.5f }},
1807 {{-width / 2, 0.0f, 0.0f }, {0.0f, 0.0f }},
1808 {{ width / 2, 0.0f, 0.0f }, {1.0f, 0.0f }},
1809 {{ width / 2, 0.0f, -length + width / 2}, {1.0f, 0.51f}},
1810 {{-width / 2, 0.0f, -length + width / 2}, {0.0f, 0.51f}},
1811 {{ width / 2, 0.0f, -length, }, {1.0f, 1.0f }},
1812 {{-width / 2, 0.0f, -length }, {0.0f, 1.0f }}
1813 }), {
1814 0, 1, 2, 0, 2, 3,
1815 4, 5, 1, 4, 1, 0,
1816 6, 7, 5, 6, 5, 4
1817 }, {
1818 mat4(1.0f),
1819 color,
1820 false
1821 }, true);
1822
1823 float xAxisRotation = asin(ray.y / length);
1824 float yAxisRotation = atan2(-ray.x, -ray.z);
1825
1826 vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
1827 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) *
1828 vec4(0.0f, 1.0f, 0.0f, 1.0f));
1829
1830 // To project point P onto line AB:
1831 // projection = A + dot(AP,AB) / dot(AB,AB) * AB
1832 vec3 projOnLaser = start + glm::dot(this->cam_pos - start, ray) / (length * length) * ray;
1833 vec3 laserToCam = this->cam_pos - projOnLaser;
1834
1835 float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam));
1836
1837 laser.targetAsteroid = nullptr;
1838
1839 laser.model_base =
1840 rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f));
1841
1842 laser.model_transform =
1843 translate(mat4(1.0f), start) *
1844 rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
1845 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f));
1846
1847 laser.modified = true;
1848}
1849
1850void VulkanGame::translateLaser(size_t index, const vec3& translation) {
1851 SceneObject<LaserVertex, SSBO_Laser>& laser = this->laserObjects[index];
1852
1853 // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created,
1854 // and then re-used here
1855
1856 vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1857 vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f));
1858
1859 vec3 ray = end - start;
1860 float length = glm::length(ray);
1861
1862 float xAxisRotation = asin(ray.y / length);
1863 float yAxisRotation = atan2(-ray.x, -ray.z);
1864
1865 vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
1866 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) *
1867 vec4(0.0f, 1.0f, 0.0f, 1.0f));
1868
1869 // To project point P onto line AB:
1870 // projection = A + dot(AP,AB) / dot(AB,AB) * AB
1871 vec3 projOnLaser = start + glm::dot(cam_pos - start, ray) / (length*length) * ray;
1872 vec3 laserToCam = cam_pos - projOnLaser;
1873
1874 float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam));
1875
1876 laser.model_base = rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f));
1877 laser.model_transform = translate(mat4(1.0f), translation) * laser.model_transform;
1878
1879 laser.modified = true;
1880}
1881
1882void VulkanGame::updateLaserTarget(size_t index) {
1883 SceneObject<LaserVertex, SSBO_Laser>& laser = this->laserObjects[index];
1884
1885 // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created,
1886 // and then re-used here
1887
1888 vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1889 vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f));
1890
1891 vec3 intersection(0.0f), closestIntersection(0.0f);
1892 SceneObject<AsteroidVertex, SSBO_Asteroid>* closestAsteroid = nullptr;
1893 unsigned int closestAsteroidIndex = -1;
1894
1895 for (int i = 0; i < this->asteroidObjects.size(); i++) {
1896 if (!this->asteroidObjects[i].ssbo.deleted &&
1897 this->getLaserAndAsteroidIntersection(this->asteroidObjects[i], start, end, intersection)) {
1898 // TODO: Implement a more generic algorithm for testing the closest object by getting the distance between the points
1899 // TODO: Also check which intersection is close to the start of the laser. This would make the algorithm work
1900 // regardless of which way -Z is pointing
1901 if (closestAsteroid == nullptr || intersection.z > closestIntersection.z) {
1902 // TODO: At this point, find the real intersection of the laser with one of the asteroid's sides
1903 closestAsteroid = &asteroidObjects[i];
1904 closestIntersection = intersection;
1905 closestAsteroidIndex = i;
1906 }
1907 }
1908 }
1909
1910 float width = laser.vertices[0].pos.x - laser.vertices[1].pos.x;
1911
1912 if (laser.targetAsteroid != closestAsteroid) {
1913 if (laser.targetAsteroid != nullptr) {
1914 if (index == leftLaserIdx && leftLaserEffect != nullptr) {
1915 leftLaserEffect->deleted = true;
1916 } else if (index == rightLaserIdx && rightLaserEffect != nullptr) {
1917 rightLaserEffect->deleted = true;
1918 }
1919 }
1920
1921 EffectOverTime<AsteroidVertex, SSBO_Asteroid>* eot = nullptr;
1922
1923 if (closestAsteroid != nullptr) {
1924 // TODO: Use some sort of smart pointer instead
1925 eot = new EffectOverTime<AsteroidVertex, SSBO_Asteroid>(asteroidPipeline, asteroidObjects, closestAsteroidIndex,
1926 offset_of(&SSBO_Asteroid::hp), curTime, -20.0f);
1927 effects.push_back(eot);
1928 }
1929
1930 if (index == leftLaserIdx) {
1931 leftLaserEffect = eot;
1932 } else if (index == rightLaserIdx) {
1933 rightLaserEffect = eot;
1934 }
1935
1936 laser.targetAsteroid = closestAsteroid;
1937 }
1938
1939 // Make the laser go past the end of the screen if it doesn't hit anything
1940 float length = closestAsteroid == nullptr ? 5.24f : glm::length(closestIntersection - start);
1941
1942 laser.vertices[4].pos.z = -length + width / 2;
1943 laser.vertices[5].pos.z = -length + width / 2;
1944 laser.vertices[6].pos.z = -length;
1945 laser.vertices[7].pos.z = -length;
1946
1947 // TODO: Consider if I want to set a flag and do this update in updateScene() instead
1948 updateObjectVertices(this->laserPipeline, laser, index);
1949}
1950
1951// TODO: Determine if I should pass start and end by reference or value since they don't get changed
1952// Probably use const reference
1953bool VulkanGame::getLaserAndAsteroidIntersection(SceneObject<AsteroidVertex, SSBO_Asteroid>& asteroid,
1954 vec3& start, vec3& end, vec3& intersection) {
1955 /*
1956 ### LINE EQUATIONS ###
1957 x = x1 + u * (x2 - x1)
1958 y = y1 + u * (y2 - y1)
1959 z = z1 + u * (z2 - z1)
1960
1961 ### SPHERE EQUATION ###
1962 (x - x3)^2 + (y - y3)^2 + (z - z3)^2 = r^2
1963
1964 ### QUADRATIC EQUATION TO SOLVE ###
1965 a*u^2 + b*u + c = 0
1966 WHERE THE CONSTANTS ARE
1967 a = (x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2
1968 b = 2*( (x2 - x1)*(x1 - x3) + (y2 - y1)*(y1 - y3) + (z2 - z1)*(z1 - z3) )
1969 c = x3^2 + y3^2 + z3^2 + x1^2 + y1^2 + z1^2 - 2(x3*x1 + y3*y1 + z3*z1) - r^2
1970
1971 u = (-b +- sqrt(b^2 - 4*a*c)) / 2a
1972
1973 If the value under the root is >= 0, we got an intersection
1974 If the value > 0, there are two solutions. Take the one closer to 0, since that's the
1975 one closer to the laser start point
1976 */
1977
1978 vec3& center = asteroid.center;
1979
1980 float a = pow(end.x - start.x, 2) + pow(end.y - start.y, 2) + pow(end.z - start.z, 2);
1981 float b = 2 * ((start.x - end.x) * (start.x - center.x) + (end.y - start.y) * (start.y - center.y) +
1982 (end.z - start.z) * (start.z - center.z));
1983 float c = pow(center.x, 2) + pow(center.y, 2) + pow(center.z, 2) + pow(start.x, 2) + pow(start.y, 2) +
1984 pow(start.z, 2) - 2 * (center.x * start.x + center.y * start.y + center.z * start.z) -
1985 pow(asteroid.radius, 2);
1986 float discriminant = pow(b, 2) - 4 * a * c;
1987
1988 if (discriminant >= 0.0f) {
1989 // In this case, the negative root will always give the point closer to the laser start point
1990 float u = (-b - sqrt(discriminant)) / (2 * a);
1991
1992 // Check that the intersection is within the line segment corresponding to the laser
1993 if (0.0f <= u && u <= 1.0f) {
1994 intersection = start + u * (end - start);
1995 return true;
1996 }
1997 }
1998
1999 return false;
2000}
2001
2002void VulkanGame::addExplosion(mat4 model_mat, float duration, float cur_time) {
2003 vector<ExplosionVertex> vertices;
2004 vertices.reserve(EXPLOSION_PARTICLE_COUNT);
2005
2006 float particle_start_time = 0.0f;
2007
2008 for (int i = 0; i < EXPLOSION_PARTICLE_COUNT; i++) {
2009 float randx = ((float)rand() / (float)RAND_MAX) - 0.5f;
2010 float randy = ((float)rand() / (float)RAND_MAX) - 0.5f;
2011
2012 vertices.push_back({ vec3(randx, randy, 0.0f), particle_start_time});
2013
2014 particle_start_time += .01f;
2015 // TODO: Get this working
2016 // particle_start_time += 1.0f * EXPLOSION_PARTICLE_COUNT / duration
2017 }
2018
2019 // Fill the indices with the the first EXPLOSION_PARTICLE_COUNT ints
2020 vector<uint16_t> indices(EXPLOSION_PARTICLE_COUNT);
2021 iota(indices.begin(), indices.end(), 0);
2022
2023 SceneObject<ExplosionVertex, SSBO_Explosion>& explosion = addObject(
2024 explosionObjects, explosionPipeline,
2025 addObjectIndex(explosionObjects.size(), vertices),
2026 indices, {
2027 mat4(1.0f),
2028 cur_time,
2029 duration,
2030 false
2031 }, true);
2032
2033 explosion.model_base = model_mat;
2034 explosion.model_transform = mat4(1.0f);
2035
2036 explosion.modified = true;
2037}
2038
2039void VulkanGame::recreateSwapChain() {
2040 if (vkDeviceWaitIdle(device) != VK_SUCCESS) {
2041 throw runtime_error("failed to wait for device!");
2042 }
2043
2044 cleanupSwapChain();
2045
2046 createSwapChain();
2047 createImageViews();
2048
2049 // The depth buffer does need to be recreated with the swap chain since its dimensions depend on the window size
2050 // and resizing the window is a common reason to recreate the swapchain
2051 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
2052 depthImage, graphicsQueue);
2053
2054 createRenderPass();
2055 createCommandPools();
2056 createFramebuffers();
2057 createCommandBuffers();
2058 createSyncObjects();
2059
2060 // TODO: Move UBO creation/management into GraphicsPipeline_Vulkan, like I did with SSBOs
2061 // TODO: Check if the shader stages and maybe some other properties of the pipeline can be re-used
2062 // instead of recreated every time
2063
2064 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2065 uniformBuffers_modelPipeline, uniformBuffersMemory_modelPipeline, uniformBufferInfoList_modelPipeline);
2066
2067 modelPipeline.updateRenderPass(renderPass);
2068 modelPipeline.createPipeline("shaders/model-vert.spv", "shaders/model-frag.spv");
2069 modelPipeline.createDescriptorPool(swapChainImages);
2070 modelPipeline.createDescriptorSets(swapChainImages);
2071
2072 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2073 uniformBuffers_shipPipeline, uniformBuffersMemory_shipPipeline, uniformBufferInfoList_shipPipeline);
2074
2075 shipPipeline.updateRenderPass(renderPass);
2076 shipPipeline.createPipeline("shaders/ship-vert.spv", "shaders/ship-frag.spv");
2077 shipPipeline.createDescriptorPool(swapChainImages);
2078 shipPipeline.createDescriptorSets(swapChainImages);
2079
2080 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2081 uniformBuffers_asteroidPipeline, uniformBuffersMemory_asteroidPipeline, uniformBufferInfoList_asteroidPipeline);
2082
2083 asteroidPipeline.updateRenderPass(renderPass);
2084 asteroidPipeline.createPipeline("shaders/asteroid-vert.spv", "shaders/asteroid-frag.spv");
2085 asteroidPipeline.createDescriptorPool(swapChainImages);
2086 asteroidPipeline.createDescriptorSets(swapChainImages);
2087
2088 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2089 uniformBuffers_laserPipeline, uniformBuffersMemory_laserPipeline, uniformBufferInfoList_laserPipeline);
2090
2091 laserPipeline.updateRenderPass(renderPass);
2092 laserPipeline.createPipeline("shaders/laser-vert.spv", "shaders/laser-frag.spv");
2093 laserPipeline.createDescriptorPool(swapChainImages);
2094 laserPipeline.createDescriptorSets(swapChainImages);
2095
2096 createBufferSet(sizeof(UBO_Explosion), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2097 uniformBuffers_explosionPipeline, uniformBuffersMemory_explosionPipeline, uniformBufferInfoList_explosionPipeline);
2098
2099 explosionPipeline.updateRenderPass(renderPass);
2100 explosionPipeline.createPipeline("shaders/explosion-vert.spv", "shaders/explosion-frag.spv");
2101 explosionPipeline.createDescriptorPool(swapChainImages);
2102 explosionPipeline.createDescriptorSets(swapChainImages);
2103
2104 imageIndex = 0;
2105}
2106
2107void VulkanGame::cleanupSwapChain() {
2108 VulkanUtils::destroyVulkanImage(device, depthImage);
2109
2110 for (VkFramebuffer framebuffer : swapChainFramebuffers) {
2111 vkDestroyFramebuffer(device, framebuffer, nullptr);
2112 }
2113
2114 for (uint32_t i = 0; i < swapChainImageCount; i++) {
2115 vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
2116 vkDestroyCommandPool(device, commandPools[i], nullptr);
2117 }
2118
2119 modelPipeline.cleanup();
2120 shipPipeline.cleanup();
2121 asteroidPipeline.cleanup();
2122 laserPipeline.cleanup();
2123 explosionPipeline.cleanup();
2124
2125 for (size_t i = 0; i < uniformBuffers_modelPipeline.size(); i++) {
2126 vkDestroyBuffer(device, uniformBuffers_modelPipeline[i], nullptr);
2127 vkFreeMemory(device, uniformBuffersMemory_modelPipeline[i], nullptr);
2128 }
2129
2130 for (size_t i = 0; i < uniformBuffers_shipPipeline.size(); i++) {
2131 vkDestroyBuffer(device, uniformBuffers_shipPipeline[i], nullptr);
2132 vkFreeMemory(device, uniformBuffersMemory_shipPipeline[i], nullptr);
2133 }
2134
2135 for (size_t i = 0; i < uniformBuffers_asteroidPipeline.size(); i++) {
2136 vkDestroyBuffer(device, uniformBuffers_asteroidPipeline[i], nullptr);
2137 vkFreeMemory(device, uniformBuffersMemory_asteroidPipeline[i], nullptr);
2138 }
2139
2140 for (size_t i = 0; i < uniformBuffers_laserPipeline.size(); i++) {
2141 vkDestroyBuffer(device, uniformBuffers_laserPipeline[i], nullptr);
2142 vkFreeMemory(device, uniformBuffersMemory_laserPipeline[i], nullptr);
2143 }
2144
2145 for (size_t i = 0; i < uniformBuffers_explosionPipeline.size(); i++) {
2146 vkDestroyBuffer(device, uniformBuffers_explosionPipeline[i], nullptr);
2147 vkFreeMemory(device, uniformBuffersMemory_explosionPipeline[i], nullptr);
2148 }
2149
2150 for (size_t i = 0; i < swapChainImageCount; i++) {
2151 vkDestroySemaphore(device, imageAcquiredSemaphores[i], nullptr);
2152 vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr);
2153 vkDestroyFence(device, inFlightFences[i], nullptr);
2154 }
2155
2156 vkDestroyRenderPass(device, renderPass, nullptr);
2157
2158 for (VkImageView imageView : swapChainImageViews) {
2159 vkDestroyImageView(device, imageView, nullptr);
2160 }
2161
2162 vkDestroySwapchainKHR(device, swapChain, nullptr);
2163}
2164
2165void VulkanGame::renderMainScreen(int width, int height) {
2166 {
2167 int padding = 4;
2168 ImGui::SetNextWindowPos(vec2(-padding, -padding), ImGuiCond_Once);
2169 ImGui::SetNextWindowSize(vec2(width + 2 * padding, height + 2 * padding), ImGuiCond_Always);
2170 ImGui::Begin("WndMain", nullptr,
2171 ImGuiWindowFlags_NoTitleBar |
2172 ImGuiWindowFlags_NoResize |
2173 ImGuiWindowFlags_NoMove);
2174
2175 ButtonImGui btn("New Game");
2176
2177 ImGui::InvisibleButton("", vec2(10, height / 6));
2178 if (btn.draw((width - btn.getWidth()) / 2)) {
2179 goToScreen(&VulkanGame::renderGameScreen);
2180 }
2181
2182 ButtonImGui btn2("Quit");
2183
2184 ImGui::InvisibleButton("", vec2(10, 15));
2185 if (btn2.draw((width - btn2.getWidth()) / 2)) {
2186 quitGame();
2187 }
2188
2189 ImGui::End();
2190 }
2191}
2192
2193void VulkanGame::renderGameScreen(int width, int height) {
2194 {
2195 ImGui::SetNextWindowSize(vec2(130, 65), ImGuiCond_Once);
2196 ImGui::SetNextWindowPos(vec2(10, 50), ImGuiCond_Once);
2197 ImGui::Begin("WndStats", nullptr,
2198 ImGuiWindowFlags_NoTitleBar |
2199 ImGuiWindowFlags_NoResize |
2200 ImGuiWindowFlags_NoMove);
2201
2202 //ImGui::Text(ImGui::GetIO().Framerate);
2203 renderGuiValueList(valueLists["stats value list"]);
2204
2205 ImGui::End();
2206 }
2207
2208 {
2209 ImGui::SetNextWindowSize(vec2(250, 35), ImGuiCond_Once);
2210 ImGui::SetNextWindowPos(vec2(width - 260, 10), ImGuiCond_Always);
2211 ImGui::Begin("WndMenubar", nullptr,
2212 ImGuiWindowFlags_NoTitleBar |
2213 ImGuiWindowFlags_NoResize |
2214 ImGuiWindowFlags_NoMove);
2215 ImGui::InvisibleButton("", vec2(155, 18));
2216 ImGui::SameLine();
2217 if (ImGui::Button("Main Menu")) {
2218 goToScreen(&VulkanGame::renderMainScreen);
2219 }
2220 ImGui::End();
2221 }
2222
2223 {
2224 ImGui::SetNextWindowSize(vec2(200, 200), ImGuiCond_Once);
2225 ImGui::SetNextWindowPos(vec2(width - 210, 60), ImGuiCond_Always);
2226 ImGui::Begin("WndDebug", nullptr,
2227 ImGuiWindowFlags_NoTitleBar |
2228 ImGuiWindowFlags_NoResize |
2229 ImGuiWindowFlags_NoMove);
2230
2231 renderGuiValueList(valueLists["debug value list"]);
2232
2233 ImGui::End();
2234 }
2235}
2236
2237void VulkanGame::initGuiValueLists(map<string, vector<UIValue>>& valueLists) {
2238 valueLists["stats value list"] = vector<UIValue>();
2239 valueLists["debug value list"] = vector<UIValue>();
2240}
2241
2242// TODO: Probably turn this into a UI widget class
2243void VulkanGame::renderGuiValueList(vector<UIValue>& values) {
2244 float maxWidth = 0.0f;
2245 float cursorStartPos = ImGui::GetCursorPosX();
2246
2247 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
2248 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
2249
2250 if (maxWidth < textWidth)
2251 maxWidth = textWidth;
2252 }
2253
2254 stringstream ss;
2255
2256 // TODO: Possibly implement this based on gui/ui-value.hpp instead and use templates
2257 // to keep track of the type. This should make it a bit easier to use and maintain
2258 // Also, implement this in a way that's agnostic to the UI renderer.
2259 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
2260 ss.str("");
2261 ss.clear();
2262
2263 switch (it->type) {
2264 case UIVALUE_INT:
2265 ss << it->label << ": " << *(unsigned int*)it->value;
2266 break;
2267 case UIVALUE_DOUBLE:
2268 ss << it->label << ": " << *(double*)it->value;
2269 break;
2270 }
2271
2272 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
2273
2274 ImGui::SetCursorPosX(cursorStartPos + maxWidth - textWidth);
2275 //ImGui::Text("%s", ss.str().c_str());
2276 ImGui::Text("%s: %.1f", it->label.c_str(), *(float*)it->value);
2277 }
2278}
2279
2280void VulkanGame::goToScreen(void (VulkanGame::* renderScreenFn)(int width, int height)) {
2281 currentRenderScreenFn = renderScreenFn;
2282
2283 // TODO: Maybe just set shouldRecreateSwapChain to true instead. Check this render loop logic
2284 // to make sure there'd be no issues
2285 //recreateSwapChain();
2286}
2287
2288void VulkanGame::quitGame() {
2289 done = true;
2290}
Note: See TracBrowser for help on using the repository browser.