source: opengl-game/vulkan-game.cpp@ 914bb99

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

In VulkanGame, specify each vertex explicitly for the model pipeline instead of using the same index multiple times, in order to support the current approach to normal calculation.

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