source: opengl-game/vulkan-game.cpp@ 9d21aac

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

Remove the SSBOType template parameter from GraphicsPipeline_Vulkan

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