source: opengl-game/vulkan-game.cpp@ 1abebc1

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

Remove the storageBuffers parameter from addObject() since it is no longer used, rename StorageBufferSet, resizeStorageBufferSet(), and updateStorageuffer() to BufferSet, resizeBufferSet(), and updateBufferSet() respectively, and change updateObject() to just take a SceneObject reference.

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