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

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

Modify the VulkanBuffer class to take a range and to align data based on that rather than the size of an individual data item. Also, reorganize the code in VulkanGae::updateScene() in a more logical fashion, and remove VulkanGame::updateObject() and inline its functionality.

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