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

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

In VulkanGame, change the asteroid pipeline to use ModelVertex

  • Property mode set to 100644
File size: 92.5 KB
Line 
1#include "vulkan-game.hpp"
2
3#include <array>
4#include <iostream>
5#include <numeric>
6#include <set>
7#include <stdexcept>
8
9#include "IMGUI/imgui_impl_sdl.h"
10#include "IMGUI/imgui_internal.h" // For CalcItemSize
11
12#include "logger.hpp"
13
14#include "gui/imgui/button-imgui.hpp"
15
16using namespace std;
17
18// TODO: Update all instances of the "... != VK_SUCCESS" check to something similar to
19// the agp error checking function, which prints an appropriate error message based on the error code
20
21// TODO: Update all occurances of instance variables to use this-> (Actually, not sure if I really want to do this)
22
23/* TODO: Try doing the following tasks based on the Vulkan implementation of IMGUI (Also maybe looks at Sascha Willems' code to see how he does these things)
24 *
25 * - When recreating the swapchain, pass the old one in and destroy the old one after the new one is created
26 * - Recreate semaphores when recreating the swapchain
27 * - imgui uses one image acquired and one render complete sem and once fence per frame\
28 * - IMGUI creates one command pool per framebuffer
29 */
30
31/* NOTES WHEN ADDING IMGUI
32 *
33 * Possibly cleanup the imgui pipeline in cleanupSwapchain or call some imgui function that does this for me
34 * call ImGui_ImplVulkan_RenderDrawData, without passing in a pipeline, to do the rendering
35 */
36
37static void check_imgui_vk_result(VkResult res) {
38 if (res == VK_SUCCESS) {
39 return;
40 }
41
42 ostringstream oss;
43 oss << "[imgui] Vulkan error! VkResult is \"" << VulkanUtils::resultString(res) << "\"" << __LINE__;
44 if (res < 0) {
45 throw runtime_error("Fatal: " + oss.str());
46 } else {
47 cerr << oss.str();
48 }
49}
50
51VulkanGame::VulkanGame()
52 : swapChainImageCount(0)
53 , swapChainMinImageCount(0)
54 , swapChainSurfaceFormat({})
55 , swapChainPresentMode(VK_PRESENT_MODE_MAX_ENUM_KHR)
56 , swapChainExtent{ 0, 0 }
57 , swapChain(VK_NULL_HANDLE)
58 , vulkanSurface(VK_NULL_HANDLE)
59 , sdlVersion({ 0, 0, 0 })
60 , instance(VK_NULL_HANDLE)
61 , physicalDevice(VK_NULL_HANDLE)
62 , device(VK_NULL_HANDLE)
63 , debugMessenger(VK_NULL_HANDLE)
64 , resourceCommandPool(VK_NULL_HANDLE)
65 , renderPass(VK_NULL_HANDLE)
66 , graphicsQueue(VK_NULL_HANDLE)
67 , presentQueue(VK_NULL_HANDLE)
68 , depthImage({})
69 , shouldRecreateSwapChain(false)
70 , frameCount(0)
71 , currentFrame(0)
72 , imageIndex(0)
73 , fpsStartTime(0.0f)
74 , curTime(0.0f)
75 , done(false)
76 , currentRenderScreenFn(nullptr)
77 , gui(nullptr)
78 , window(nullptr)
79 , score(0)
80 , fps(0.0f) {
81 object_VP_mats = {};
82 ship_VP_mats = {};
83 asteroid_VP_mats = {};
84 laser_VP_mats = {};
85 explosion_UBO = {};
86}
87
88VulkanGame::~VulkanGame() {
89}
90
91void VulkanGame::run(int width, int height, unsigned char guiFlags) {
92 seedRandomNums();
93
94 cout << "DEBUGGING IS " << (ENABLE_VALIDATION_LAYERS ? "ON" : "OFF") << endl;
95
96 cout << "Vulkan Game" << endl;
97
98 if (initUI(width, height, guiFlags) == RTWO_ERROR) {
99 return;
100 }
101
102 initVulkan();
103
104 initImGuiOverlay();
105
106 // TODO: Figure out how much of ubo creation and associated variables should be in the pipeline class
107 // Maybe combine the ubo-related objects into a new class
108
109 initGraphicsPipelines();
110
111 initMatrices();
112
113 cout << "INITIALIZING OBJECTS" << endl;
114
115 modelPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::pos));
116 modelPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::color));
117 modelPipeline.addAttribute(VK_FORMAT_R32G32_SFLOAT, offset_of(&ModelVertex::texCoord));
118 modelPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::normal));
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 addVertexNormals<ModelVertex>({
135 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}},
136 {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}},
137 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.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}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}},
140 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}}
141 })), {
142 0, 1, 2, 3, 4, 5
143 }, {
144 mat4(1.0f)
145 }, false);
146
147 texturedSquare->model_base =
148 translate(mat4(1.0f), vec3(0.0f, 0.0f, -2.0f));
149 texturedSquare->modified = true;
150
151 texturedSquare = &addObject(modelObjects, modelPipeline,
152 addObjectIndex<ModelVertex>(modelObjects.size(),
153 addVertexNormals<ModelVertex>({
154 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}},
155 {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}},
156 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
157 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
158 {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}},
159 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}}
160 })), {
161 0, 1, 2, 3, 4, 5
162 }, {
163 mat4(1.0f)
164 }, false);
165
166 texturedSquare->model_base =
167 translate(mat4(1.0f), vec3(0.0f, 0.0f, -1.5f));
168 texturedSquare->modified = true;
169
170 modelPipeline.createDescriptorSetLayout();
171 modelPipeline.createPipeline("shaders/model-vert.spv", "shaders/model-frag.spv");
172 modelPipeline.createDescriptorPool(swapChainImages);
173 modelPipeline.createDescriptorSets(swapChainImages);
174
175 // START UNREVIEWED SECTION
176 shipPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::pos));
177 shipPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::color));
178 shipPipeline.addAttribute(VK_FORMAT_R32G32_SFLOAT, offset_of(&ModelVertex::texCoord));
179 shipPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::normal));
180 shipPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&ModelVertex::objIndex));
181
182 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
183 uniformBuffers_shipPipeline, uniformBuffersMemory_shipPipeline, uniformBufferInfoList_shipPipeline);
184
185 shipPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
186 VK_SHADER_STAGE_VERTEX_BIT, &uniformBufferInfoList_shipPipeline);
187 shipPipeline.addStorageDescriptor(VK_SHADER_STAGE_VERTEX_BIT);
188
189 // TODO: With the normals, indexing basically becomes pointless since no vertices will have exactly
190 // the same data. Add an option to make some pipelines not use indexing
191 SceneObject<ModelVertex, SSBO_ModelObject>& ship = addObject(shipObjects, shipPipeline,
192 addObjectIndex<ModelVertex>(shipObjects.size(),
193 addVertexNormals<ModelVertex>({
194
195 //back
196 {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
197 {{ -0.5f, 0.0f, 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.3f, 0.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 // left back
204 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
205 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
206 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
207 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
208 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
209 {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
210
211 // right back
212 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
213 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
214 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
215 {{ 0.5f, 0.3f, 0.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 // left mid
220 {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}},
221 {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}},
222 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
223 {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}},
224 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
225 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
226
227 // right mid
228 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
229 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 0.3f}},
230 {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}},
231 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 0.3f}},
232 {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}},
233 {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}},
234
235 // left front
236 {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 1.0f}},
237 {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}},
238 {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}},
239
240 // right front
241 {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}},
242 {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}},
243 {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 1.0f}},
244
245 // top back
246 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
247 {{ -0.5f, 0.3f, 0.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, -2.0f}, {0.0f, 0.0f, 1.0f}},
250 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 1.0f}},
251 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
252
253 // bottom back
254 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
255 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
256 {{ 0.5f, 0.0f, 0.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, -2.0f}, {0.0f, 0.0f, 1.0f}},
259 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
260
261 // top mid
262 {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}},
263 {{ -0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
264 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
265 {{ -0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}},
266 {{ 0.5f, 0.3f, -2.0f}, {0.0f, 0.0f, 1.0f}},
267 {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 1.0f}},
268
269 // bottom mid
270 {{ -0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
271 {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}},
272 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
273 {{ 0.5f, 0.0f, -2.0f}, {0.0f, 0.0f, 1.0f}},
274 {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}},
275 {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 1.0f}},
276
277 // top front
278 {{-0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}},
279 {{ 0.25f, 0.3f, -3.0f}, {0.0f, 0.0f, 0.3f}},
280 {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 0.3f}},
281
282 // bottom front
283 {{ 0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}},
284 {{-0.25f, 0.0f, -3.0f}, {0.0f, 0.0f, 0.3f}},
285 {{ 0.0f, 0.0f, -3.5f}, {0.0f, 0.0f, 0.3f}},
286
287 // left wing start back
288 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
289 {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
290 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
291 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
292 {{ -0.5f, 0.0f, 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 top
296 {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
297 {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
298 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
299 {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
300 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
301 {{ -0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
302
303 // left wing start front
304 {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
305 {{ -0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
306 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
307 {{ -0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
308 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
309 {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
310
311 // left wing start bottom
312 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
313 {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
314 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
315 {{ -0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
316 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
317 {{ -0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
318
319 // left wing end outside
320 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
321 {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
322 {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
323
324 // left wing end top
325 {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
326 {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
327 {{ -1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
328
329 // left wing end front
330 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
331 {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
332 {{ -1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
333
334 // left wing end bottom
335 {{ -1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
336 {{ -2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
337 {{ -1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
338
339 // right wing start back
340 {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
341 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
342 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
343 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
344 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
345 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
346
347 // right wing start top
348 {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
349 {{ 0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
350 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
351 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
352 {{ 0.5f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
353 {{ 0.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
354
355 // right wing start front
356 {{ 0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
357 {{ 0.5f, 0.3f, -0.3f}, {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.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
361 {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
362
363 // right wing start bottom
364 {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
365 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
366 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
367 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
368 {{ 0.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
369 {{ 0.5f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
370
371 // right wing end outside
372 {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
373 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
374 {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
375
376 // right wing end top
377 {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
378 {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
379 {{ 1.5f, 0.3f, 0.0f}, {0.0f, 0.0f, 0.3f}},
380
381 // right wing end front
382 {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
383 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
384 {{ 1.3f, 0.3f, -0.3f}, {0.0f, 0.0f, 0.3f}},
385
386 // right wing end bottom
387 {{ 2.2f, 0.15f, -0.8f}, {0.0f, 0.0f, 0.3f}},
388 {{ 1.5f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.3f}},
389 {{ 1.3f, 0.0f, -0.3f}, {0.0f, 0.0f, 0.3f}},
390 })), {
391 0, 1, 2, 3, 4, 5,
392 6, 7, 8, 9, 10, 11,
393 12, 13, 14, 15, 16, 17,
394 18, 19, 20, 21, 22, 23,
395 24, 25, 26, 27, 28, 29,
396 30, 31, 32,
397 33, 34, 35,
398 36, 37, 38, 39, 40, 41,
399 42, 43, 44, 45, 46, 47,
400 48, 49, 50, 51, 52, 53,
401 54, 55, 56, 57, 58, 59,
402 60, 61, 62,
403 63, 64, 65,
404 66, 67, 68, 69, 70, 71,
405 72, 73, 74, 75, 76, 77,
406 78, 79, 80, 81, 82, 83,
407 84, 85, 86, 87, 88, 89,
408 90, 91, 92,
409 93, 94, 95,
410 96, 97, 98,
411 99, 100, 101,
412 102, 103, 104, 105, 106, 107,
413 108, 109, 110, 111, 112, 113,
414 114, 115, 116, 117, 118, 119,
415 120, 121, 122, 123, 124, 125,
416 126, 127, 128,
417 129, 130, 131,
418 132, 133, 134,
419 135, 136, 137,
420 }, {
421 mat4(1.0f)
422 }, false);
423
424 ship.model_base =
425 translate(mat4(1.0f), vec3(0.0f, -1.2f, 1.65f)) *
426 scale(mat4(1.0f), vec3(0.1f, 0.1f, 0.1f));
427 ship.modified = true;
428
429 shipPipeline.createDescriptorSetLayout();
430 shipPipeline.createPipeline("shaders/ship-vert.spv", "shaders/ship-frag.spv");
431 shipPipeline.createDescriptorPool(swapChainImages);
432 shipPipeline.createDescriptorSets(swapChainImages);
433
434 asteroidPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::pos));
435 asteroidPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::color));
436 asteroidPipeline.addAttribute(VK_FORMAT_R32G32_SFLOAT, offset_of(&ModelVertex::texCoord));
437 asteroidPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::normal));
438 asteroidPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&ModelVertex::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<ModelVertex, 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<ModelVertex, 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<ModelVertex, SSBO_Asteroid>& asteroid =
932 addObject(asteroidObjects, asteroidPipeline,
933 addObjectIndex<ModelVertex>(asteroidObjects.size(),
934 addVertexNormals<ModelVertex>({
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 // START UNREVIEWED SECTION
1073 VulkanUtils::destroyVulkanImage(device, laserTextureImage);
1074
1075 vkDestroySampler(device, textureSampler, nullptr);
1076
1077 modelPipeline.cleanupBuffers();
1078 shipPipeline.cleanupBuffers();
1079 asteroidPipeline.cleanupBuffers();
1080 laserPipeline.cleanupBuffers();
1081 explosionPipeline.cleanupBuffers();
1082
1083 // END UNREVIEWED SECTION
1084
1085 vkDestroyCommandPool(device, resourceCommandPool, nullptr);
1086
1087 vkDestroyDevice(device, nullptr);
1088 vkDestroySurfaceKHR(instance, vulkanSurface, nullptr);
1089
1090 if (ENABLE_VALIDATION_LAYERS) {
1091 VulkanUtils::destroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
1092 }
1093
1094 vkDestroyInstance(instance, nullptr);
1095
1096 gui->destroyWindow();
1097 gui->shutdown();
1098 delete gui;
1099}
1100
1101void VulkanGame::createVulkanInstance(const vector<const char*>& validationLayers) {
1102 if (ENABLE_VALIDATION_LAYERS && !VulkanUtils::checkValidationLayerSupport(validationLayers)) {
1103 throw runtime_error("validation layers requested, but not available!");
1104 }
1105
1106 VkApplicationInfo appInfo = {};
1107 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
1108 appInfo.pApplicationName = "Vulkan Game";
1109 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
1110 appInfo.pEngineName = "No Engine";
1111 appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
1112 appInfo.apiVersion = VK_API_VERSION_1_0;
1113
1114 VkInstanceCreateInfo createInfo = {};
1115 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
1116 createInfo.pApplicationInfo = &appInfo;
1117
1118 vector<const char*> extensions = gui->getRequiredExtensions();
1119 if (ENABLE_VALIDATION_LAYERS) {
1120 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
1121 }
1122
1123 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
1124 createInfo.ppEnabledExtensionNames = extensions.data();
1125
1126 cout << endl << "Extensions:" << endl;
1127 for (const char* extensionName : extensions) {
1128 cout << extensionName << endl;
1129 }
1130 cout << endl;
1131
1132 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
1133 if (ENABLE_VALIDATION_LAYERS) {
1134 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
1135 createInfo.ppEnabledLayerNames = validationLayers.data();
1136
1137 populateDebugMessengerCreateInfo(debugCreateInfo);
1138 createInfo.pNext = &debugCreateInfo;
1139 } else {
1140 createInfo.enabledLayerCount = 0;
1141
1142 createInfo.pNext = nullptr;
1143 }
1144
1145 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
1146 throw runtime_error("failed to create instance!");
1147 }
1148}
1149
1150void VulkanGame::setupDebugMessenger() {
1151 if (!ENABLE_VALIDATION_LAYERS) {
1152 return;
1153 }
1154
1155 VkDebugUtilsMessengerCreateInfoEXT createInfo;
1156 populateDebugMessengerCreateInfo(createInfo);
1157
1158 if (VulkanUtils::createDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
1159 throw runtime_error("failed to set up debug messenger!");
1160 }
1161}
1162
1163void VulkanGame::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
1164 createInfo = {};
1165 createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
1166 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;
1167 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;
1168 createInfo.pfnUserCallback = debugCallback;
1169}
1170
1171VKAPI_ATTR VkBool32 VKAPI_CALL VulkanGame::debugCallback(
1172 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
1173 VkDebugUtilsMessageTypeFlagsEXT messageType,
1174 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
1175 void* pUserData) {
1176 cerr << "validation layer: " << pCallbackData->pMessage << endl;
1177
1178 // TODO: Figure out what the return value means and if it should always be VK_FALSE
1179 return VK_FALSE;
1180}
1181
1182void VulkanGame::createVulkanSurface() {
1183 if (gui->createVulkanSurface(instance, &vulkanSurface) == RTWO_ERROR) {
1184 throw runtime_error("failed to create window surface!");
1185 }
1186}
1187
1188void VulkanGame::pickPhysicalDevice(const vector<const char*>& deviceExtensions) {
1189 uint32_t deviceCount = 0;
1190 // TODO: Check VkResult
1191 vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
1192
1193 if (deviceCount == 0) {
1194 throw runtime_error("failed to find GPUs with Vulkan support!");
1195 }
1196
1197 vector<VkPhysicalDevice> devices(deviceCount);
1198 // TODO: Check VkResult
1199 vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
1200
1201 cout << endl << "Graphics cards:" << endl;
1202 for (const VkPhysicalDevice& device : devices) {
1203 if (isDeviceSuitable(device, deviceExtensions)) {
1204 physicalDevice = device;
1205 break;
1206 }
1207 }
1208 cout << endl;
1209
1210 if (physicalDevice == VK_NULL_HANDLE) {
1211 throw runtime_error("failed to find a suitable GPU!");
1212 }
1213}
1214
1215bool VulkanGame::isDeviceSuitable(VkPhysicalDevice physicalDevice, const vector<const char*>& deviceExtensions) {
1216 VkPhysicalDeviceProperties deviceProperties;
1217 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
1218
1219 cout << "Device: " << deviceProperties.deviceName << endl;
1220
1221 // TODO: Eventually, maybe let the user pick out of a set of GPUs in case the user does want to use
1222 // an integrated GPU. On my laptop, this function returns TRUE for the integrated GPU, but crashes
1223 // when trying to use it to render. Maybe I just need to figure out which other extensions and features
1224 // to check.
1225 if (deviceProperties.deviceType != VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
1226 return false;
1227 }
1228
1229 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1230 bool extensionsSupported = VulkanUtils::checkDeviceExtensionSupport(physicalDevice, deviceExtensions);
1231 bool swapChainAdequate = false;
1232
1233 if (extensionsSupported) {
1234 vector<VkSurfaceFormatKHR> formats = VulkanUtils::querySwapChainFormats(physicalDevice, vulkanSurface);
1235 vector<VkPresentModeKHR> presentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, vulkanSurface);
1236
1237 swapChainAdequate = !formats.empty() && !presentModes.empty();
1238 }
1239
1240 VkPhysicalDeviceFeatures supportedFeatures;
1241 vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
1242
1243 return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
1244}
1245
1246void VulkanGame::createLogicalDevice(const vector<const char*>& validationLayers,
1247 const vector<const char*>& deviceExtensions) {
1248 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1249
1250 if (!indices.isComplete()) {
1251 throw runtime_error("failed to find required queue families!");
1252 }
1253
1254 // TODO: Using separate graphics and present queues currently works, but I should verify that I'm
1255 // using them correctly to get the most benefit out of separate queues
1256
1257 vector<VkDeviceQueueCreateInfo> queueCreateInfoList;
1258 set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
1259
1260 float queuePriority = 1.0f;
1261 for (uint32_t queueFamily : uniqueQueueFamilies) {
1262 VkDeviceQueueCreateInfo queueCreateInfo = {};
1263 queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
1264 queueCreateInfo.queueCount = 1;
1265 queueCreateInfo.queueFamilyIndex = queueFamily;
1266 queueCreateInfo.pQueuePriorities = &queuePriority;
1267
1268 queueCreateInfoList.push_back(queueCreateInfo);
1269 }
1270
1271 VkPhysicalDeviceFeatures deviceFeatures = {};
1272 deviceFeatures.samplerAnisotropy = VK_TRUE;
1273
1274 VkDeviceCreateInfo createInfo = {};
1275 createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
1276
1277 createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfoList.size());
1278 createInfo.pQueueCreateInfos = queueCreateInfoList.data();
1279
1280 createInfo.pEnabledFeatures = &deviceFeatures;
1281
1282 createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
1283 createInfo.ppEnabledExtensionNames = deviceExtensions.data();
1284
1285 // These fields are ignored by up-to-date Vulkan implementations,
1286 // but it's a good idea to set them for backwards compatibility
1287 if (ENABLE_VALIDATION_LAYERS) {
1288 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
1289 createInfo.ppEnabledLayerNames = validationLayers.data();
1290 } else {
1291 createInfo.enabledLayerCount = 0;
1292 }
1293
1294 if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
1295 throw runtime_error("failed to create logical device!");
1296 }
1297
1298 vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
1299 vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
1300}
1301
1302void VulkanGame::chooseSwapChainProperties() {
1303 vector<VkSurfaceFormatKHR> availableFormats = VulkanUtils::querySwapChainFormats(physicalDevice, vulkanSurface);
1304 vector<VkPresentModeKHR> availablePresentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, vulkanSurface);
1305
1306 swapChainSurfaceFormat = VulkanUtils::chooseSwapSurfaceFormat(availableFormats,
1307 { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM },
1308 VK_COLOR_SPACE_SRGB_NONLINEAR_KHR);
1309
1310 vector<VkPresentModeKHR> presentModes{
1311 VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR
1312 };
1313 //vector<VkPresentModeKHR> presentModes{ VK_PRESENT_MODE_FIFO_KHR };
1314
1315 swapChainPresentMode = VulkanUtils::chooseSwapPresentMode(availablePresentModes, presentModes);
1316
1317 cout << "[vulkan] Selected PresentMode = " << swapChainPresentMode << endl;
1318
1319 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, vulkanSurface);
1320
1321 if (swapChainPresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
1322 swapChainMinImageCount = 3;
1323 } else if (swapChainPresentMode == VK_PRESENT_MODE_FIFO_KHR || swapChainPresentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) {
1324 swapChainMinImageCount = 2;
1325 } else if (swapChainPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
1326 swapChainMinImageCount = 1;
1327 } else {
1328 throw runtime_error("unexpected present mode!");
1329 }
1330
1331 if (swapChainMinImageCount < capabilities.minImageCount) {
1332 swapChainMinImageCount = capabilities.minImageCount;
1333 } else if (capabilities.maxImageCount != 0 && swapChainMinImageCount > capabilities.maxImageCount) {
1334 swapChainMinImageCount = capabilities.maxImageCount;
1335 }
1336}
1337
1338void VulkanGame::createSwapChain() {
1339 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, vulkanSurface);
1340
1341 swapChainExtent = VulkanUtils::chooseSwapExtent(capabilities, gui->getWindowWidth(), gui->getWindowHeight());
1342
1343 VkSwapchainCreateInfoKHR createInfo = {};
1344 createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
1345 createInfo.surface = vulkanSurface;
1346 createInfo.minImageCount = swapChainMinImageCount;
1347 createInfo.imageFormat = swapChainSurfaceFormat.format;
1348 createInfo.imageColorSpace = swapChainSurfaceFormat.colorSpace;
1349 createInfo.imageExtent = swapChainExtent;
1350 createInfo.imageArrayLayers = 1;
1351 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
1352
1353 // TODO: Maybe save this result so I don't have to recalculate it every time
1354 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1355 uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
1356
1357 if (indices.graphicsFamily != indices.presentFamily) {
1358 createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
1359 createInfo.queueFamilyIndexCount = 2;
1360 createInfo.pQueueFamilyIndices = queueFamilyIndices;
1361 } else {
1362 createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
1363 createInfo.queueFamilyIndexCount = 0;
1364 createInfo.pQueueFamilyIndices = nullptr;
1365 }
1366
1367 createInfo.preTransform = capabilities.currentTransform;
1368 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
1369 createInfo.presentMode = swapChainPresentMode;
1370 createInfo.clipped = VK_TRUE;
1371 createInfo.oldSwapchain = VK_NULL_HANDLE;
1372
1373 if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
1374 throw runtime_error("failed to create swap chain!");
1375 }
1376
1377 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, nullptr) != VK_SUCCESS) {
1378 throw runtime_error("failed to get swap chain image count!");
1379 }
1380
1381 swapChainImages.resize(swapChainImageCount);
1382 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, swapChainImages.data()) != VK_SUCCESS) {
1383 throw runtime_error("failed to get swap chain images!");
1384 }
1385}
1386
1387void VulkanGame::createImageViews() {
1388 swapChainImageViews.resize(swapChainImageCount);
1389
1390 for (size_t i = 0; i < swapChainImageCount; i++) {
1391 swapChainImageViews[i] = VulkanUtils::createImageView(device, swapChainImages[i], swapChainSurfaceFormat.format,
1392 VK_IMAGE_ASPECT_COLOR_BIT);
1393 }
1394}
1395
1396void VulkanGame::createResourceCommandPool() {
1397 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1398
1399 VkCommandPoolCreateInfo poolInfo = {};
1400 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
1401 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
1402 poolInfo.flags = 0;
1403
1404 if (vkCreateCommandPool(device, &poolInfo, nullptr, &resourceCommandPool) != VK_SUCCESS) {
1405 throw runtime_error("failed to create resource command pool!");
1406 }
1407}
1408
1409void VulkanGame::createImageResources() {
1410 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
1411 depthImage, graphicsQueue);
1412
1413 createTextureSampler();
1414
1415 // TODO: Move all images/textures somewhere into the assets folder
1416
1417 VulkanUtils::createVulkanImageFromFile(device, physicalDevice, resourceCommandPool, "textures/texture.jpg",
1418 floorTextureImage, graphicsQueue);
1419
1420 floorTextureImageDescriptor = {};
1421 floorTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1422 floorTextureImageDescriptor.imageView = floorTextureImage.imageView;
1423 floorTextureImageDescriptor.sampler = textureSampler;
1424
1425 VulkanUtils::createVulkanImageFromFile(device, physicalDevice, resourceCommandPool, "textures/laser.png",
1426 laserTextureImage, graphicsQueue);
1427
1428 laserTextureImageDescriptor = {};
1429 laserTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1430 laserTextureImageDescriptor.imageView = laserTextureImage.imageView;
1431 laserTextureImageDescriptor.sampler = textureSampler;
1432}
1433
1434VkFormat VulkanGame::findDepthFormat() {
1435 return VulkanUtils::findSupportedFormat(
1436 physicalDevice,
1437 { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT },
1438 VK_IMAGE_TILING_OPTIMAL,
1439 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
1440 );
1441}
1442
1443void VulkanGame::createRenderPass() {
1444 VkAttachmentDescription colorAttachment = {};
1445 colorAttachment.format = swapChainSurfaceFormat.format;
1446 colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
1447 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1448 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
1449 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1450 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1451 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1452 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
1453
1454 VkAttachmentReference colorAttachmentRef = {};
1455 colorAttachmentRef.attachment = 0;
1456 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
1457
1458 VkAttachmentDescription depthAttachment = {};
1459 depthAttachment.format = findDepthFormat();
1460 depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
1461 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1462 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1463 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1464 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1465 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1466 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1467
1468 VkAttachmentReference depthAttachmentRef = {};
1469 depthAttachmentRef.attachment = 1;
1470 depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1471
1472 VkSubpassDescription subpass = {};
1473 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
1474 subpass.colorAttachmentCount = 1;
1475 subpass.pColorAttachments = &colorAttachmentRef;
1476 subpass.pDepthStencilAttachment = &depthAttachmentRef;
1477
1478 VkSubpassDependency dependency = {};
1479 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
1480 dependency.dstSubpass = 0;
1481 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1482 dependency.srcAccessMask = 0;
1483 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1484 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
1485
1486 array<VkAttachmentDescription, 2> attachments = { colorAttachment, depthAttachment };
1487 VkRenderPassCreateInfo renderPassInfo = {};
1488 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
1489 renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
1490 renderPassInfo.pAttachments = attachments.data();
1491 renderPassInfo.subpassCount = 1;
1492 renderPassInfo.pSubpasses = &subpass;
1493 renderPassInfo.dependencyCount = 1;
1494 renderPassInfo.pDependencies = &dependency;
1495
1496 if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
1497 throw runtime_error("failed to create render pass!");
1498 }
1499}
1500
1501void VulkanGame::createCommandPools() {
1502 commandPools.resize(swapChainImageCount);
1503
1504 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1505
1506 for (size_t i = 0; i < swapChainImageCount; i++) {
1507 VkCommandPoolCreateInfo poolInfo = {};
1508 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
1509 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
1510 poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
1511
1512 VKUTIL_CHECK_RESULT(vkCreateCommandPool(device, &poolInfo, nullptr, &commandPools[i]),
1513 "failed to create graphics command pool!");
1514 }
1515}
1516
1517void VulkanGame::createTextureSampler() {
1518 VkSamplerCreateInfo samplerInfo = {};
1519 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1520 samplerInfo.magFilter = VK_FILTER_LINEAR;
1521 samplerInfo.minFilter = VK_FILTER_LINEAR;
1522
1523 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1524 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1525 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1526
1527 samplerInfo.anisotropyEnable = VK_TRUE;
1528 samplerInfo.maxAnisotropy = 16;
1529 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
1530 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1531 samplerInfo.compareEnable = VK_FALSE;
1532 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1533 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
1534 samplerInfo.mipLodBias = 0.0f;
1535 samplerInfo.minLod = 0.0f;
1536 samplerInfo.maxLod = 0.0f;
1537
1538 VKUTIL_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler),
1539 "failed to create texture sampler!");
1540}
1541
1542void VulkanGame::createFramebuffers() {
1543 swapChainFramebuffers.resize(swapChainImageCount);
1544
1545 VkFramebufferCreateInfo framebufferInfo = {};
1546 framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
1547 framebufferInfo.renderPass = renderPass;
1548 framebufferInfo.width = swapChainExtent.width;
1549 framebufferInfo.height = swapChainExtent.height;
1550 framebufferInfo.layers = 1;
1551
1552 for (uint32_t i = 0; i < swapChainImageCount; i++) {
1553 array<VkImageView, 2> attachments = {
1554 swapChainImageViews[i],
1555 depthImage.imageView
1556 };
1557
1558 framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
1559 framebufferInfo.pAttachments = attachments.data();
1560
1561 if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
1562 throw runtime_error("failed to create framebuffer!");
1563 }
1564 }
1565}
1566
1567void VulkanGame::createCommandBuffers() {
1568 commandBuffers.resize(swapChainImageCount);
1569
1570 for (size_t i = 0; i < swapChainImageCount; i++) {
1571 VkCommandBufferAllocateInfo allocInfo = {};
1572 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1573 allocInfo.commandPool = commandPools[i];
1574 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1575 allocInfo.commandBufferCount = 1;
1576
1577 if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffers[i]) != VK_SUCCESS) {
1578 throw runtime_error("failed to allocate command buffer!");
1579 }
1580 }
1581}
1582
1583void VulkanGame::createSyncObjects() {
1584 imageAcquiredSemaphores.resize(swapChainImageCount);
1585 renderCompleteSemaphores.resize(swapChainImageCount);
1586 inFlightFences.resize(swapChainImageCount);
1587
1588 VkSemaphoreCreateInfo semaphoreInfo = {};
1589 semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
1590
1591 VkFenceCreateInfo fenceInfo = {};
1592 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
1593 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
1594
1595 for (size_t i = 0; i < swapChainImageCount; i++) {
1596 VKUTIL_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAcquiredSemaphores[i]),
1597 "failed to create image acquired sempahore for a frame!");
1598
1599 VKUTIL_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderCompleteSemaphores[i]),
1600 "failed to create render complete sempahore for a frame!");
1601
1602 VKUTIL_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]),
1603 "failed to create fence for a frame!");
1604 }
1605}
1606
1607void VulkanGame::renderFrame(ImDrawData* draw_data) {
1608 VkResult result = vkAcquireNextImageKHR(device, swapChain, numeric_limits<uint64_t>::max(),
1609 imageAcquiredSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
1610
1611 if (result == VK_SUBOPTIMAL_KHR) {
1612 shouldRecreateSwapChain = true;
1613 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1614 shouldRecreateSwapChain = true;
1615 return;
1616 } else {
1617 VKUTIL_CHECK_RESULT(result, "failed to acquire swap chain image!");
1618 }
1619
1620 VKUTIL_CHECK_RESULT(
1621 vkWaitForFences(device, 1, &inFlightFences[imageIndex], VK_TRUE, numeric_limits<uint64_t>::max()),
1622 "failed waiting for fence!");
1623
1624 VKUTIL_CHECK_RESULT(vkResetFences(device, 1, &inFlightFences[imageIndex]),
1625 "failed to reset fence!");
1626
1627 VKUTIL_CHECK_RESULT(vkResetCommandPool(device, commandPools[imageIndex], 0),
1628 "failed to reset command pool!");
1629
1630 VkCommandBufferBeginInfo beginInfo = {};
1631 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1632 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1633
1634 VKUTIL_CHECK_RESULT(vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo),
1635 "failed to begin recording command buffer!");
1636
1637 VkRenderPassBeginInfo renderPassInfo = {};
1638 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
1639 renderPassInfo.renderPass = renderPass;
1640 renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
1641 renderPassInfo.renderArea.offset = { 0, 0 };
1642 renderPassInfo.renderArea.extent = swapChainExtent;
1643
1644 array<VkClearValue, 2> clearValues = {};
1645 clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
1646 clearValues[1].depthStencil = { 1.0f, 0 };
1647
1648 renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
1649 renderPassInfo.pClearValues = clearValues.data();
1650
1651 vkCmdBeginRenderPass(commandBuffers[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
1652
1653 // TODO: Find a more elegant, per-screen solution for this
1654 if (currentRenderScreenFn == &VulkanGame::renderGameScreen) {
1655 modelPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex);
1656 shipPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex);
1657 asteroidPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex);
1658 laserPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex);
1659 explosionPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex);
1660 }
1661
1662 ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffers[imageIndex]);
1663
1664 vkCmdEndRenderPass(commandBuffers[imageIndex]);
1665
1666 VKUTIL_CHECK_RESULT(vkEndCommandBuffer(commandBuffers[imageIndex]),
1667 "failed to record command buffer!");
1668
1669 VkSemaphore waitSemaphores[] = { imageAcquiredSemaphores[currentFrame] };
1670 VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
1671 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
1672
1673 VkSubmitInfo submitInfo = {};
1674 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1675 submitInfo.waitSemaphoreCount = 1;
1676 submitInfo.pWaitSemaphores = waitSemaphores;
1677 submitInfo.pWaitDstStageMask = waitStages;
1678 submitInfo.commandBufferCount = 1;
1679 submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
1680 submitInfo.signalSemaphoreCount = 1;
1681 submitInfo.pSignalSemaphores = signalSemaphores;
1682
1683 VKUTIL_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[imageIndex]),
1684 "failed to submit draw command buffer!");
1685}
1686
1687void VulkanGame::presentFrame() {
1688 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
1689
1690 VkPresentInfoKHR presentInfo = {};
1691 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
1692 presentInfo.waitSemaphoreCount = 1;
1693 presentInfo.pWaitSemaphores = signalSemaphores;
1694 presentInfo.swapchainCount = 1;
1695 presentInfo.pSwapchains = &swapChain;
1696 presentInfo.pImageIndices = &imageIndex;
1697 presentInfo.pResults = nullptr;
1698
1699 VkResult result = vkQueuePresentKHR(presentQueue, &presentInfo);
1700
1701 if (result == VK_SUBOPTIMAL_KHR) {
1702 shouldRecreateSwapChain = true;
1703 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1704 shouldRecreateSwapChain = true;
1705 return;
1706 } else {
1707 VKUTIL_CHECK_RESULT(result, "failed to present swap chain image!");
1708 }
1709
1710 currentFrame = (currentFrame + 1) % swapChainImageCount;
1711}
1712
1713void VulkanGame::initImGuiOverlay() {
1714 vector<VkDescriptorPoolSize> pool_sizes{
1715 { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
1716 { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
1717 { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
1718 { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 },
1719 { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 },
1720 { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 },
1721 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 },
1722 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 },
1723 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 },
1724 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 },
1725 { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 }
1726 };
1727
1728 VkDescriptorPoolCreateInfo pool_info = {};
1729 pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1730 pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
1731 pool_info.maxSets = 1000 * pool_sizes.size();
1732 pool_info.poolSizeCount = static_cast<uint32_t>(pool_sizes.size());
1733 pool_info.pPoolSizes = pool_sizes.data();
1734
1735 VKUTIL_CHECK_RESULT(vkCreateDescriptorPool(device, &pool_info, nullptr, &imguiDescriptorPool),
1736 "failed to create IMGUI descriptor pool!");
1737
1738 // TODO: Do this in one place and save it instead of redoing it every time I need a queue family index
1739 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1740
1741 // Setup Dear ImGui context
1742 IMGUI_CHECKVERSION();
1743 ImGui::CreateContext();
1744 ImGuiIO& io = ImGui::GetIO();
1745 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
1746 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
1747
1748 // Setup Dear ImGui style
1749 ImGui::StyleColorsDark();
1750 //ImGui::StyleColorsClassic();
1751
1752 // Setup Platform/Renderer bindings
1753 ImGui_ImplSDL2_InitForVulkan(window);
1754 ImGui_ImplVulkan_InitInfo init_info = {};
1755 init_info.Instance = instance;
1756 init_info.PhysicalDevice = physicalDevice;
1757 init_info.Device = device;
1758 init_info.QueueFamily = indices.graphicsFamily.value();
1759 init_info.Queue = graphicsQueue;
1760 init_info.DescriptorPool = imguiDescriptorPool;
1761 init_info.Allocator = nullptr;
1762 init_info.MinImageCount = swapChainMinImageCount;
1763 init_info.ImageCount = swapChainImageCount;
1764 init_info.CheckVkResultFn = check_imgui_vk_result;
1765 ImGui_ImplVulkan_Init(&init_info, renderPass);
1766
1767 // Load Fonts
1768 // - 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.
1769 // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
1770 // - 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).
1771 // - 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.
1772 // - Read 'docs/FONTS.md' for more instructions and details.
1773 // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
1774 //io.Fonts->AddFontDefault();
1775 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
1776 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
1777 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
1778 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f);
1779 //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
1780 //assert(font != NULL);
1781
1782 // Upload Fonts
1783 {
1784 VkCommandBuffer commandBuffer = VulkanUtils::beginSingleTimeCommands(device, resourceCommandPool);
1785
1786 ImGui_ImplVulkan_CreateFontsTexture(commandBuffer);
1787
1788 VulkanUtils::endSingleTimeCommands(device, resourceCommandPool, commandBuffer, graphicsQueue);
1789
1790 ImGui_ImplVulkan_DestroyFontUploadObjects();
1791 }
1792}
1793
1794void VulkanGame::cleanupImGuiOverlay() {
1795 ImGui_ImplVulkan_Shutdown();
1796 ImGui_ImplSDL2_Shutdown();
1797 ImGui::DestroyContext();
1798
1799 vkDestroyDescriptorPool(device, imguiDescriptorPool, nullptr);
1800}
1801
1802void VulkanGame::createBufferSet(VkDeviceSize bufferSize, VkBufferUsageFlags flags,
1803 vector<VkBuffer>& buffers, vector<VkDeviceMemory>& buffersMemory, vector<VkDescriptorBufferInfo>& bufferInfoList) {
1804 buffers.resize(swapChainImageCount);
1805 buffersMemory.resize(swapChainImageCount);
1806 bufferInfoList.resize(swapChainImageCount);
1807
1808 for (size_t i = 0; i < swapChainImageCount; i++) {
1809 VulkanUtils::createBuffer(device, physicalDevice, bufferSize, flags,
1810 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1811 buffers[i], buffersMemory[i]);
1812
1813 bufferInfoList[i].buffer = buffers[i];
1814 bufferInfoList[i].offset = 0; // This is the offset from the start of the buffer, so always 0 for now
1815 bufferInfoList[i].range = bufferSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
1816 }
1817}
1818
1819void VulkanGame::addLaser(vec3 start, vec3 end, vec3 color, float width) {
1820 vec3 ray = end - start;
1821 float length = glm::length(ray);
1822
1823 SceneObject<LaserVertex, SSBO_Laser>& laser = addObject(
1824 laserObjects, laserPipeline,
1825 addObjectIndex<LaserVertex>(laserObjects.size(), {
1826 {{ width / 2, 0.0f, -width / 2 }, {1.0f, 0.5f }},
1827 {{-width / 2, 0.0f, -width / 2 }, {0.0f, 0.5f }},
1828 {{-width / 2, 0.0f, 0.0f }, {0.0f, 0.0f }},
1829 {{ width / 2, 0.0f, 0.0f }, {1.0f, 0.0f }},
1830 {{ width / 2, 0.0f, -length + width / 2}, {1.0f, 0.51f}},
1831 {{-width / 2, 0.0f, -length + width / 2}, {0.0f, 0.51f}},
1832 {{ width / 2, 0.0f, -length, }, {1.0f, 1.0f }},
1833 {{-width / 2, 0.0f, -length }, {0.0f, 1.0f }}
1834 }), {
1835 0, 1, 2, 0, 2, 3,
1836 4, 5, 1, 4, 1, 0,
1837 6, 7, 5, 6, 5, 4
1838 }, {
1839 mat4(1.0f),
1840 color,
1841 false
1842 }, true);
1843
1844 float xAxisRotation = asin(ray.y / length);
1845 float yAxisRotation = atan2(-ray.x, -ray.z);
1846
1847 vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
1848 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) *
1849 vec4(0.0f, 1.0f, 0.0f, 1.0f));
1850
1851 // To project point P onto line AB:
1852 // projection = A + dot(AP,AB) / dot(AB,AB) * AB
1853 vec3 projOnLaser = start + glm::dot(this->cam_pos - start, ray) / (length * length) * ray;
1854 vec3 laserToCam = this->cam_pos - projOnLaser;
1855
1856 float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam));
1857
1858 laser.targetAsteroid = nullptr;
1859
1860 laser.model_base =
1861 rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f));
1862
1863 laser.model_transform =
1864 translate(mat4(1.0f), start) *
1865 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
1868 laser.modified = true;
1869}
1870
1871void VulkanGame::translateLaser(size_t index, const vec3& translation) {
1872 SceneObject<LaserVertex, SSBO_Laser>& laser = this->laserObjects[index];
1873
1874 // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created,
1875 // and then re-used here
1876
1877 vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1878 vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f));
1879
1880 vec3 ray = end - start;
1881 float length = glm::length(ray);
1882
1883 float xAxisRotation = asin(ray.y / length);
1884 float yAxisRotation = atan2(-ray.x, -ray.z);
1885
1886 vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
1887 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) *
1888 vec4(0.0f, 1.0f, 0.0f, 1.0f));
1889
1890 // To project point P onto line AB:
1891 // projection = A + dot(AP,AB) / dot(AB,AB) * AB
1892 vec3 projOnLaser = start + glm::dot(cam_pos - start, ray) / (length*length) * ray;
1893 vec3 laserToCam = cam_pos - projOnLaser;
1894
1895 float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam));
1896
1897 laser.model_base = rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f));
1898 laser.model_transform = translate(mat4(1.0f), translation) * laser.model_transform;
1899
1900 laser.modified = true;
1901}
1902
1903void VulkanGame::updateLaserTarget(size_t index) {
1904 SceneObject<LaserVertex, SSBO_Laser>& laser = this->laserObjects[index];
1905
1906 // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created,
1907 // and then re-used here
1908
1909 vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1910 vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f));
1911
1912 vec3 intersection(0.0f), closestIntersection(0.0f);
1913 SceneObject<ModelVertex, SSBO_Asteroid>* closestAsteroid = nullptr;
1914 unsigned int closestAsteroidIndex = -1;
1915
1916 for (int i = 0; i < this->asteroidObjects.size(); i++) {
1917 if (!this->asteroidObjects[i].ssbo.deleted &&
1918 this->getLaserAndAsteroidIntersection(this->asteroidObjects[i], start, end, intersection)) {
1919 // TODO: Implement a more generic algorithm for testing the closest object by getting the distance between the points
1920 // TODO: Also check which intersection is close to the start of the laser. This would make the algorithm work
1921 // regardless of which way -Z is pointing
1922 if (closestAsteroid == nullptr || intersection.z > closestIntersection.z) {
1923 // TODO: At this point, find the real intersection of the laser with one of the asteroid's sides
1924 closestAsteroid = &asteroidObjects[i];
1925 closestIntersection = intersection;
1926 closestAsteroidIndex = i;
1927 }
1928 }
1929 }
1930
1931 float width = laser.vertices[0].pos.x - laser.vertices[1].pos.x;
1932
1933 if (laser.targetAsteroid != closestAsteroid) {
1934 if (laser.targetAsteroid != nullptr) {
1935 if (index == leftLaserIdx && leftLaserEffect != nullptr) {
1936 leftLaserEffect->deleted = true;
1937 } else if (index == rightLaserIdx && rightLaserEffect != nullptr) {
1938 rightLaserEffect->deleted = true;
1939 }
1940 }
1941
1942 EffectOverTime<ModelVertex, SSBO_Asteroid>* eot = nullptr;
1943
1944 if (closestAsteroid != nullptr) {
1945 // TODO: Use some sort of smart pointer instead
1946 eot = new EffectOverTime<ModelVertex, SSBO_Asteroid>(asteroidPipeline, asteroidObjects, closestAsteroidIndex,
1947 offset_of(&SSBO_Asteroid::hp), curTime, -20.0f);
1948 effects.push_back(eot);
1949 }
1950
1951 if (index == leftLaserIdx) {
1952 leftLaserEffect = eot;
1953 } else if (index == rightLaserIdx) {
1954 rightLaserEffect = eot;
1955 }
1956
1957 laser.targetAsteroid = closestAsteroid;
1958 }
1959
1960 // Make the laser go past the end of the screen if it doesn't hit anything
1961 float length = closestAsteroid == nullptr ? 5.24f : glm::length(closestIntersection - start);
1962
1963 laser.vertices[4].pos.z = -length + width / 2;
1964 laser.vertices[5].pos.z = -length + width / 2;
1965 laser.vertices[6].pos.z = -length;
1966 laser.vertices[7].pos.z = -length;
1967
1968 // TODO: Consider if I want to set a flag and do this update in updateScene() instead
1969 updateObjectVertices(this->laserPipeline, laser, index);
1970}
1971
1972// TODO: Determine if I should pass start and end by reference or value since they don't get changed
1973// Probably use const reference
1974bool VulkanGame::getLaserAndAsteroidIntersection(SceneObject<ModelVertex, SSBO_Asteroid>& asteroid,
1975 vec3& start, vec3& end, vec3& intersection) {
1976 /*
1977 ### LINE EQUATIONS ###
1978 x = x1 + u * (x2 - x1)
1979 y = y1 + u * (y2 - y1)
1980 z = z1 + u * (z2 - z1)
1981
1982 ### SPHERE EQUATION ###
1983 (x - x3)^2 + (y - y3)^2 + (z - z3)^2 = r^2
1984
1985 ### QUADRATIC EQUATION TO SOLVE ###
1986 a*u^2 + b*u + c = 0
1987 WHERE THE CONSTANTS ARE
1988 a = (x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2
1989 b = 2*( (x2 - x1)*(x1 - x3) + (y2 - y1)*(y1 - y3) + (z2 - z1)*(z1 - z3) )
1990 c = x3^2 + y3^2 + z3^2 + x1^2 + y1^2 + z1^2 - 2(x3*x1 + y3*y1 + z3*z1) - r^2
1991
1992 u = (-b +- sqrt(b^2 - 4*a*c)) / 2a
1993
1994 If the value under the root is >= 0, we got an intersection
1995 If the value > 0, there are two solutions. Take the one closer to 0, since that's the
1996 one closer to the laser start point
1997 */
1998
1999 vec3& center = asteroid.center;
2000
2001 float a = pow(end.x - start.x, 2) + pow(end.y - start.y, 2) + pow(end.z - start.z, 2);
2002 float b = 2 * ((start.x - end.x) * (start.x - center.x) + (end.y - start.y) * (start.y - center.y) +
2003 (end.z - start.z) * (start.z - center.z));
2004 float c = pow(center.x, 2) + pow(center.y, 2) + pow(center.z, 2) + pow(start.x, 2) + pow(start.y, 2) +
2005 pow(start.z, 2) - 2 * (center.x * start.x + center.y * start.y + center.z * start.z) -
2006 pow(asteroid.radius, 2);
2007 float discriminant = pow(b, 2) - 4 * a * c;
2008
2009 if (discriminant >= 0.0f) {
2010 // In this case, the negative root will always give the point closer to the laser start point
2011 float u = (-b - sqrt(discriminant)) / (2 * a);
2012
2013 // Check that the intersection is within the line segment corresponding to the laser
2014 if (0.0f <= u && u <= 1.0f) {
2015 intersection = start + u * (end - start);
2016 return true;
2017 }
2018 }
2019
2020 return false;
2021}
2022
2023void VulkanGame::addExplosion(mat4 model_mat, float duration, float cur_time) {
2024 vector<ExplosionVertex> vertices;
2025 vertices.reserve(EXPLOSION_PARTICLE_COUNT);
2026
2027 float particle_start_time = 0.0f;
2028
2029 for (int i = 0; i < EXPLOSION_PARTICLE_COUNT; i++) {
2030 float randx = ((float)rand() / (float)RAND_MAX) - 0.5f;
2031 float randy = ((float)rand() / (float)RAND_MAX) - 0.5f;
2032
2033 vertices.push_back({ vec3(randx, randy, 0.0f), particle_start_time});
2034
2035 particle_start_time += .01f;
2036 // TODO: Get this working
2037 // particle_start_time += 1.0f * EXPLOSION_PARTICLE_COUNT / duration
2038 }
2039
2040 // Fill the indices with the the first EXPLOSION_PARTICLE_COUNT ints
2041 vector<uint16_t> indices(EXPLOSION_PARTICLE_COUNT);
2042 iota(indices.begin(), indices.end(), 0);
2043
2044 SceneObject<ExplosionVertex, SSBO_Explosion>& explosion = addObject(
2045 explosionObjects, explosionPipeline,
2046 addObjectIndex(explosionObjects.size(), vertices),
2047 indices, {
2048 mat4(1.0f),
2049 cur_time,
2050 duration,
2051 false
2052 }, true);
2053
2054 explosion.model_base = model_mat;
2055 explosion.model_transform = mat4(1.0f);
2056
2057 explosion.modified = true;
2058}
2059
2060void VulkanGame::recreateSwapChain() {
2061 if (vkDeviceWaitIdle(device) != VK_SUCCESS) {
2062 throw runtime_error("failed to wait for device!");
2063 }
2064
2065 cleanupSwapChain();
2066
2067 createSwapChain();
2068 createImageViews();
2069
2070 // The depth buffer does need to be recreated with the swap chain since its dimensions depend on the window size
2071 // and resizing the window is a common reason to recreate the swapchain
2072 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
2073 depthImage, graphicsQueue);
2074
2075 createRenderPass();
2076 createCommandPools();
2077 createFramebuffers();
2078 createCommandBuffers();
2079 createSyncObjects();
2080
2081 // TODO: Move UBO creation/management into GraphicsPipeline_Vulkan, like I did with SSBOs
2082 // TODO: Check if the shader stages and maybe some other properties of the pipeline can be re-used
2083 // instead of recreated every time
2084
2085 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2086 uniformBuffers_modelPipeline, uniformBuffersMemory_modelPipeline, uniformBufferInfoList_modelPipeline);
2087
2088 modelPipeline.updateRenderPass(renderPass);
2089 modelPipeline.createPipeline("shaders/model-vert.spv", "shaders/model-frag.spv");
2090 modelPipeline.createDescriptorPool(swapChainImages);
2091 modelPipeline.createDescriptorSets(swapChainImages);
2092
2093 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2094 uniformBuffers_shipPipeline, uniformBuffersMemory_shipPipeline, uniformBufferInfoList_shipPipeline);
2095
2096 shipPipeline.updateRenderPass(renderPass);
2097 shipPipeline.createPipeline("shaders/ship-vert.spv", "shaders/ship-frag.spv");
2098 shipPipeline.createDescriptorPool(swapChainImages);
2099 shipPipeline.createDescriptorSets(swapChainImages);
2100
2101 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2102 uniformBuffers_asteroidPipeline, uniformBuffersMemory_asteroidPipeline, uniformBufferInfoList_asteroidPipeline);
2103
2104 asteroidPipeline.updateRenderPass(renderPass);
2105 asteroidPipeline.createPipeline("shaders/asteroid-vert.spv", "shaders/asteroid-frag.spv");
2106 asteroidPipeline.createDescriptorPool(swapChainImages);
2107 asteroidPipeline.createDescriptorSets(swapChainImages);
2108
2109 createBufferSet(sizeof(UBO_VP_mats), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2110 uniformBuffers_laserPipeline, uniformBuffersMemory_laserPipeline, uniformBufferInfoList_laserPipeline);
2111
2112 laserPipeline.updateRenderPass(renderPass);
2113 laserPipeline.createPipeline("shaders/laser-vert.spv", "shaders/laser-frag.spv");
2114 laserPipeline.createDescriptorPool(swapChainImages);
2115 laserPipeline.createDescriptorSets(swapChainImages);
2116
2117 createBufferSet(sizeof(UBO_Explosion), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2118 uniformBuffers_explosionPipeline, uniformBuffersMemory_explosionPipeline, uniformBufferInfoList_explosionPipeline);
2119
2120 explosionPipeline.updateRenderPass(renderPass);
2121 explosionPipeline.createPipeline("shaders/explosion-vert.spv", "shaders/explosion-frag.spv");
2122 explosionPipeline.createDescriptorPool(swapChainImages);
2123 explosionPipeline.createDescriptorSets(swapChainImages);
2124
2125 imageIndex = 0;
2126}
2127
2128void VulkanGame::cleanupSwapChain() {
2129 VulkanUtils::destroyVulkanImage(device, depthImage);
2130
2131 for (VkFramebuffer framebuffer : swapChainFramebuffers) {
2132 vkDestroyFramebuffer(device, framebuffer, nullptr);
2133 }
2134
2135 for (uint32_t i = 0; i < swapChainImageCount; i++) {
2136 vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
2137 vkDestroyCommandPool(device, commandPools[i], nullptr);
2138 }
2139
2140 modelPipeline.cleanup();
2141 shipPipeline.cleanup();
2142 asteroidPipeline.cleanup();
2143 laserPipeline.cleanup();
2144 explosionPipeline.cleanup();
2145
2146 for (size_t i = 0; i < uniformBuffers_modelPipeline.size(); i++) {
2147 vkDestroyBuffer(device, uniformBuffers_modelPipeline[i], nullptr);
2148 vkFreeMemory(device, uniformBuffersMemory_modelPipeline[i], nullptr);
2149 }
2150
2151 for (size_t i = 0; i < uniformBuffers_shipPipeline.size(); i++) {
2152 vkDestroyBuffer(device, uniformBuffers_shipPipeline[i], nullptr);
2153 vkFreeMemory(device, uniformBuffersMemory_shipPipeline[i], nullptr);
2154 }
2155
2156 for (size_t i = 0; i < uniformBuffers_asteroidPipeline.size(); i++) {
2157 vkDestroyBuffer(device, uniformBuffers_asteroidPipeline[i], nullptr);
2158 vkFreeMemory(device, uniformBuffersMemory_asteroidPipeline[i], nullptr);
2159 }
2160
2161 for (size_t i = 0; i < uniformBuffers_laserPipeline.size(); i++) {
2162 vkDestroyBuffer(device, uniformBuffers_laserPipeline[i], nullptr);
2163 vkFreeMemory(device, uniformBuffersMemory_laserPipeline[i], nullptr);
2164 }
2165
2166 for (size_t i = 0; i < uniformBuffers_explosionPipeline.size(); i++) {
2167 vkDestroyBuffer(device, uniformBuffers_explosionPipeline[i], nullptr);
2168 vkFreeMemory(device, uniformBuffersMemory_explosionPipeline[i], nullptr);
2169 }
2170
2171 for (size_t i = 0; i < swapChainImageCount; i++) {
2172 vkDestroySemaphore(device, imageAcquiredSemaphores[i], nullptr);
2173 vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr);
2174 vkDestroyFence(device, inFlightFences[i], nullptr);
2175 }
2176
2177 vkDestroyRenderPass(device, renderPass, nullptr);
2178
2179 for (VkImageView imageView : swapChainImageViews) {
2180 vkDestroyImageView(device, imageView, nullptr);
2181 }
2182
2183 vkDestroySwapchainKHR(device, swapChain, nullptr);
2184}
2185
2186void VulkanGame::renderMainScreen(int width, int height) {
2187 {
2188 int padding = 4;
2189 ImGui::SetNextWindowPos(vec2(-padding, -padding), ImGuiCond_Once);
2190 ImGui::SetNextWindowSize(vec2(width + 2 * padding, height + 2 * padding), ImGuiCond_Always);
2191 ImGui::Begin("WndMain", nullptr,
2192 ImGuiWindowFlags_NoTitleBar |
2193 ImGuiWindowFlags_NoResize |
2194 ImGuiWindowFlags_NoMove);
2195
2196 ButtonImGui btn("New Game");
2197
2198 ImGui::InvisibleButton("", vec2(10, height / 6));
2199 if (btn.draw((width - btn.getWidth()) / 2)) {
2200 goToScreen(&VulkanGame::renderGameScreen);
2201 }
2202
2203 ButtonImGui btn2("Quit");
2204
2205 ImGui::InvisibleButton("", vec2(10, 15));
2206 if (btn2.draw((width - btn2.getWidth()) / 2)) {
2207 quitGame();
2208 }
2209
2210 ImGui::End();
2211 }
2212}
2213
2214void VulkanGame::renderGameScreen(int width, int height) {
2215 {
2216 ImGui::SetNextWindowSize(vec2(130, 65), ImGuiCond_Once);
2217 ImGui::SetNextWindowPos(vec2(10, 50), ImGuiCond_Once);
2218 ImGui::Begin("WndStats", nullptr,
2219 ImGuiWindowFlags_NoTitleBar |
2220 ImGuiWindowFlags_NoResize |
2221 ImGuiWindowFlags_NoMove);
2222
2223 //ImGui::Text(ImGui::GetIO().Framerate);
2224 renderGuiValueList(valueLists["stats value list"]);
2225
2226 ImGui::End();
2227 }
2228
2229 {
2230 ImGui::SetNextWindowSize(vec2(250, 35), ImGuiCond_Once);
2231 ImGui::SetNextWindowPos(vec2(width - 260, 10), ImGuiCond_Always);
2232 ImGui::Begin("WndMenubar", nullptr,
2233 ImGuiWindowFlags_NoTitleBar |
2234 ImGuiWindowFlags_NoResize |
2235 ImGuiWindowFlags_NoMove);
2236 ImGui::InvisibleButton("", vec2(155, 18));
2237 ImGui::SameLine();
2238 if (ImGui::Button("Main Menu")) {
2239 goToScreen(&VulkanGame::renderMainScreen);
2240 }
2241 ImGui::End();
2242 }
2243
2244 {
2245 ImGui::SetNextWindowSize(vec2(200, 200), ImGuiCond_Once);
2246 ImGui::SetNextWindowPos(vec2(width - 210, 60), ImGuiCond_Always);
2247 ImGui::Begin("WndDebug", nullptr,
2248 ImGuiWindowFlags_NoTitleBar |
2249 ImGuiWindowFlags_NoResize |
2250 ImGuiWindowFlags_NoMove);
2251
2252 renderGuiValueList(valueLists["debug value list"]);
2253
2254 ImGui::End();
2255 }
2256}
2257
2258void VulkanGame::initGuiValueLists(map<string, vector<UIValue>>& valueLists) {
2259 valueLists["stats value list"] = vector<UIValue>();
2260 valueLists["debug value list"] = vector<UIValue>();
2261}
2262
2263// TODO: Probably turn this into a UI widget class
2264void VulkanGame::renderGuiValueList(vector<UIValue>& values) {
2265 float maxWidth = 0.0f;
2266 float cursorStartPos = ImGui::GetCursorPosX();
2267
2268 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
2269 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
2270
2271 if (maxWidth < textWidth)
2272 maxWidth = textWidth;
2273 }
2274
2275 stringstream ss;
2276
2277 // TODO: Possibly implement this based on gui/ui-value.hpp instead and use templates
2278 // to keep track of the type. This should make it a bit easier to use and maintain
2279 // Also, implement this in a way that's agnostic to the UI renderer.
2280 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
2281 ss.str("");
2282 ss.clear();
2283
2284 switch (it->type) {
2285 case UIVALUE_INT:
2286 ss << it->label << ": " << *(unsigned int*)it->value;
2287 break;
2288 case UIVALUE_DOUBLE:
2289 ss << it->label << ": " << *(double*)it->value;
2290 break;
2291 }
2292
2293 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
2294
2295 ImGui::SetCursorPosX(cursorStartPos + maxWidth - textWidth);
2296 //ImGui::Text("%s", ss.str().c_str());
2297 ImGui::Text("%s: %.1f", it->label.c_str(), *(float*)it->value);
2298 }
2299}
2300
2301void VulkanGame::goToScreen(void (VulkanGame::* renderScreenFn)(int width, int height)) {
2302 currentRenderScreenFn = renderScreenFn;
2303
2304 // TODO: Maybe just set shouldRecreateSwapChain to true instead. Check this render loop logic
2305 // to make sure there'd be no issues
2306 //recreateSwapChain();
2307}
2308
2309void VulkanGame::quitGame() {
2310 done = true;
2311}
Note: See TracBrowser for help on using the repository browser.