source: opengl-game/vulkan-game.cpp@ 90880fb

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

Start using the VulkanBuffer class for the non-per-object uniforms (this means the view and projection matrices for all pipelines, as well as the current time for the explosion pipeline)

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