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

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

Rename all BufferSets named storageBuffers_* to objectBuffers_*

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