source: opengl-game/vulkan-game.cpp@ 8d92284

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

In VulkanGame, change the ship pipeline to use ModelVertex

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