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

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

Update the build instructions and makefile to work with OSX again

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