source: opengl-game/vulkan-game.cpp@ 6486ba8

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

Rewrite some parts of SDLGame and VulkanGame to store per-object buffer object
data contiguously and copied to the GPU in one call

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