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

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

Move SSBO resizing and pipeline recreation checks out of addObject() and into updateScene() so that those operations are only done at most once per pipeline per frame, using vkUpdateDescriptorSets() instead of recreating the whole graphics pipeline, and create a VulkanBuffer class for managing data related to uniform buffers and shader storage buffers, move objectCapacity and numObjects out of GraphicsPipeline_vulkan and use VulkanBuffer to manage them instead

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