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

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

In VulkanGame, add a normal varying attribute to ModelVertex

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