source: opengl-game/vulkan-game.cpp@ 6bac215

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

Rewrite a large portion of the VulkanBuffer class, start using it more
to resize buffers, and simplify resizeBufferSet() since the additions to
VulkanBuffer can now do some of what that function used to do

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