source: opengl-game/vulkan-game.cpp@ 996dd3e

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

Completely remove storage buffers from the GraphicsPipeline_Vulkan class and start moving storage buffer operations out of the addObject() and updateObject() functions

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