source: opengl-game/vulkan-game.cpp@ 5ea0a37

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

Add a function to VulkanBuffer to return a pointer to the buffer memory, and replace the use of updateBufferSet with copyDataToMemory to update all the data for a single pipeline in one call

  • Property mode set to 100644
File size: 103.5 KB
Line 
1#include "vulkan-game.hpp"
2
3#include <array>
4#include <iostream>
5#include <numeric>
6#include <set>
7#include <stdexcept>
8
9#include "IMGUI/imgui_impl_sdl.h"
10#include "IMGUI/imgui_internal.h" // For CalcItemSize
11
12#include "logger.hpp"
13
14#include "gui/imgui/button-imgui.hpp"
15
16using namespace std;
17
18// TODO: Update all instances of the "... != VK_SUCCESS" check to something similar to
19// the agp error checking function, which prints an appropriate error message based on the error code
20
21// TODO: Update all occurances of instance variables to use this-> (Actually, not sure if I really want to do this)
22
23/* TODO: Try doing the following tasks based on the Vulkan implementation of IMGUI (Also maybe looks at Sascha Willems' code to see how he does these things)
24 *
25 * - When recreating the swapchain, pass the old one in and destroy the old one after the new one is created
26 * - Recreate semaphores when recreating the swapchain
27 * - imgui uses one image acquired and one render complete sem and once fence per frame\
28 * - IMGUI creates one command pool per framebuffer
29 */
30
31/* NOTES WHEN ADDING IMGUI
32 *
33 * Possibly cleanup the imgui pipeline in cleanupSwapchain or call some imgui function that does this for me
34 * call ImGui_ImplVulkan_RenderDrawData, without passing in a pipeline, to do the rendering
35 */
36
37static void check_imgui_vk_result(VkResult res) {
38 if (res == VK_SUCCESS) {
39 return;
40 }
41
42 ostringstream oss;
43 oss << "[imgui] Vulkan error! VkResult is \"" << VulkanUtils::resultString(res) << "\"" << __LINE__;
44 if (res < 0) {
45 throw runtime_error("Fatal: " + oss.str());
46 } else {
47 cerr << oss.str();
48 }
49}
50
51VulkanGame::VulkanGame()
52 : swapChainImageCount(0)
53 , swapChainMinImageCount(0)
54 , swapChainSurfaceFormat({})
55 , swapChainPresentMode(VK_PRESENT_MODE_MAX_ENUM_KHR)
56 , swapChainExtent{ 0, 0 }
57 , swapChain(VK_NULL_HANDLE)
58 , vulkanSurface(VK_NULL_HANDLE)
59 , sdlVersion({ 0, 0, 0 })
60 , instance(VK_NULL_HANDLE)
61 , physicalDevice(VK_NULL_HANDLE)
62 , device(VK_NULL_HANDLE)
63 , debugMessenger(VK_NULL_HANDLE)
64 , resourceCommandPool(VK_NULL_HANDLE)
65 , renderPass(VK_NULL_HANDLE)
66 , graphicsQueue(VK_NULL_HANDLE)
67 , presentQueue(VK_NULL_HANDLE)
68 , depthImage({})
69 , shouldRecreateSwapChain(false)
70 , frameCount(0)
71 , currentFrame(0)
72 , imageIndex(0)
73 , fpsStartTime(0.0f)
74 , curTime(0.0f)
75 , done(false)
76 , currentRenderScreenFn(nullptr)
77 , gui(nullptr)
78 , window(nullptr)
79 , objects_modelPipeline()
80 , objects_shipPipeline()
81 , objects_asteroidPipeline()
82 , objects_laserPipeline()
83 , objects_explosionPipeline()
84 , score(0)
85 , fps(0.0f) {
86 object_VP_mats = {};
87 ship_VP_mats = {};
88 asteroid_VP_mats = {};
89 laser_VP_mats = {};
90 explosion_UBO = {};
91}
92
93VulkanGame::~VulkanGame() {
94}
95
96void VulkanGame::run(int width, int height, unsigned char guiFlags) {
97 seedRandomNums();
98
99 cout << "Vulkan Game" << endl;
100
101 cout << "DEBUGGING IS " << (ENABLE_VALIDATION_LAYERS ? "ON" : "OFF") << endl;
102
103 if (initUI(width, height, guiFlags) == RTWO_ERROR) {
104 return;
105 }
106
107 initVulkan();
108
109 VkPhysicalDeviceProperties deviceProperties;
110 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
111
112 objects_modelPipeline = VulkanBuffer<SSBO_ModelObject>(10, deviceProperties.limits.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 // TODO: Figure out a way to make updateDescriptorInfo easier to use, maybe store the binding index in the buffer set
1025
1026 if (objects_modelPipeline.resized) {
1027 resizeBufferSet(storageBuffers_modelPipeline, objects_modelPipeline.memorySize(), resourceCommandPool,
1028 graphicsQueue, true);
1029
1030 objects_modelPipeline.resize();
1031
1032 modelPipeline.updateDescriptorInfo(1, &storageBuffers_modelPipeline.infoSet, swapChainImages.size());
1033 }
1034
1035 for (size_t i = 0; i < modelObjects.size(); i++) {
1036 SceneObject<ModelVertex>& obj = modelObjects[i];
1037 SSBO_ModelObject& objData = objects_modelPipeline.get(i);
1038
1039 // Rotate the textured squares
1040 obj.model_transform =
1041 translate(mat4(1.0f), vec3(0.0f, -2.0f, -0.0f)) *
1042 rotate(mat4(1.0f), curTime * radians(90.0f), vec3(0.0f, 0.0f, 1.0f));
1043
1044 objData.model = obj.model_transform * obj.model_base;
1045 obj.center = vec3(objData.model * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1046 }
1047
1048 VulkanUtils::copyDataToMemory(device, objects_modelPipeline.data(), storageBuffers_modelPipeline.memory[imageIndex],
1049 0, objects_modelPipeline.memorySize(), false);
1050
1051 if (objects_shipPipeline.resized) {
1052 resizeBufferSet(storageBuffers_shipPipeline, objects_shipPipeline.memorySize(), resourceCommandPool,
1053 graphicsQueue, true);
1054
1055 objects_shipPipeline.resize();
1056
1057 shipPipeline.updateDescriptorInfo(1, &storageBuffers_shipPipeline.infoSet, swapChainImages.size());
1058 }
1059
1060 // TODO: Move ship position updates from the ui event handling code into this function
1061 for (size_t i = 0; i < shipObjects.size(); i++) {
1062 SceneObject<ModelVertex>& obj = shipObjects[i];
1063 SSBO_ModelObject& objData = objects_shipPipeline.get(i);
1064
1065 objData.model = obj.model_transform * obj.model_base;
1066 obj.center = vec3(objData.model * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1067 }
1068
1069 VulkanUtils::copyDataToMemory(device, objects_shipPipeline.data(), storageBuffers_shipPipeline.memory[imageIndex],
1070 0, objects_shipPipeline.memorySize(), false);
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 }
1112
1113 VulkanUtils::copyDataToMemory(device, objects_asteroidPipeline.data(), storageBuffers_asteroidPipeline.memory[imageIndex],
1114 0, objects_asteroidPipeline.memorySize(), false);
1115
1116 if (objects_laserPipeline.resized) {
1117 resizeBufferSet(storageBuffers_laserPipeline, objects_laserPipeline.memorySize(), resourceCommandPool,
1118 graphicsQueue, true);
1119
1120 objects_laserPipeline.resize();
1121
1122 laserPipeline.updateDescriptorInfo(1, &storageBuffers_laserPipeline.infoSet, swapChainImages.size());
1123 }
1124
1125 if (leftLaserIdx != -1) {
1126 updateLaserTarget(leftLaserIdx);
1127 }
1128 if (rightLaserIdx != -1) {
1129 updateLaserTarget(rightLaserIdx);
1130 }
1131
1132 for (size_t i = 0; i < laserObjects.size(); i++) {
1133 SceneObject<LaserVertex>& obj = laserObjects[i];
1134 SSBO_Laser& objData = objects_laserPipeline.get(i);
1135
1136 objData.model = obj.model_transform * obj.model_base;
1137 obj.center = vec3(objData.model * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1138 }
1139
1140 VulkanUtils::copyDataToMemory(device, objects_laserPipeline.data(), storageBuffers_laserPipeline.memory[imageIndex],
1141 0, objects_laserPipeline.memorySize(), false);
1142
1143 if (objects_explosionPipeline.resized) {
1144 resizeBufferSet(storageBuffers_explosionPipeline, objects_explosionPipeline.memorySize(), resourceCommandPool,
1145 graphicsQueue, true);
1146
1147 objects_explosionPipeline.resize();
1148
1149 explosionPipeline.updateDescriptorInfo(1, &storageBuffers_explosionPipeline.infoSet, swapChainImages.size());
1150 }
1151
1152 for (size_t i = 0; i < explosionObjects.size(); i++) {
1153 SceneObject<ExplosionVertex>& obj = explosionObjects[i];
1154 SSBO_Explosion& objData = objects_explosionPipeline.get(i);
1155
1156 if (!objData.deleted) {
1157 if (curTime > (objData.explosionStartTime + objData.explosionDuration)) {
1158 objData.deleted = true;
1159 }
1160 }
1161
1162 objData.model = obj.model_transform * obj.model_base;
1163 obj.center = vec3(objData.model * vec4(0.0f, 0.0f, 0.0f, 1.0f));
1164 }
1165
1166 VulkanUtils::copyDataToMemory(device, objects_explosionPipeline.data(), storageBuffers_explosionPipeline.memory[imageIndex],
1167 0, objects_explosionPipeline.memorySize(), false);
1168
1169 explosion_UBO.cur_time = curTime;
1170
1171 for (vector<BaseEffectOverTime*>::iterator it = effects.begin(); it != effects.end(); ) {
1172 if ((*it)->deleted) {
1173 delete *it;
1174 it = effects.erase(it);
1175 } else {
1176 BaseEffectOverTime* eot = *it;
1177
1178 eot->applyEffect(curTime);
1179
1180 it++;
1181 }
1182 }
1183
1184 VulkanUtils::copyDataToMemory(device, &object_VP_mats, uniformBuffers_modelPipeline.memory[imageIndex], 0,
1185 sizeof(object_VP_mats), false);
1186
1187 VulkanUtils::copyDataToMemory(device, &ship_VP_mats, uniformBuffers_shipPipeline.memory[imageIndex], 0,
1188 sizeof(ship_VP_mats), false);
1189
1190 VulkanUtils::copyDataToMemory(device, &asteroid_VP_mats, uniformBuffers_asteroidPipeline.memory[imageIndex], 0,
1191 sizeof(asteroid_VP_mats), false);
1192
1193 VulkanUtils::copyDataToMemory(device, &laser_VP_mats, uniformBuffers_laserPipeline.memory[imageIndex], 0,
1194 sizeof(laser_VP_mats), false);
1195
1196 VulkanUtils::copyDataToMemory(device, &explosion_UBO, uniformBuffers_explosionPipeline.memory[imageIndex], 0,
1197 sizeof(explosion_UBO), false);
1198}
1199
1200void VulkanGame::cleanup() {
1201 // FIXME: We could wait on the Queue if we had the queue in wd-> (otherwise VulkanH functions can't use globals)
1202 //vkQueueWaitIdle(g_Queue);
1203 VKUTIL_CHECK_RESULT(vkDeviceWaitIdle(device), "failed to wait for device!");
1204
1205 cleanupImGuiOverlay();
1206
1207 cleanupSwapChain();
1208
1209 VulkanUtils::destroyVulkanImage(device, floorTextureImage);
1210 // START UNREVIEWED SECTION
1211 VulkanUtils::destroyVulkanImage(device, laserTextureImage);
1212
1213 vkDestroySampler(device, textureSampler, nullptr);
1214
1215 modelPipeline.cleanupBuffers();
1216 shipPipeline.cleanupBuffers();
1217 asteroidPipeline.cleanupBuffers();
1218 laserPipeline.cleanupBuffers();
1219 explosionPipeline.cleanupBuffers();
1220
1221 // END UNREVIEWED SECTION
1222
1223 vkDestroyCommandPool(device, resourceCommandPool, nullptr);
1224
1225 vkDestroyDevice(device, nullptr);
1226 vkDestroySurfaceKHR(instance, vulkanSurface, nullptr);
1227
1228 if (ENABLE_VALIDATION_LAYERS) {
1229 VulkanUtils::destroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
1230 }
1231
1232 vkDestroyInstance(instance, nullptr);
1233
1234 gui->destroyWindow();
1235 gui->shutdown();
1236 delete gui;
1237}
1238
1239void VulkanGame::createVulkanInstance(const vector<const char*>& validationLayers) {
1240 if (ENABLE_VALIDATION_LAYERS && !VulkanUtils::checkValidationLayerSupport(validationLayers)) {
1241 throw runtime_error("validation layers requested, but not available!");
1242 }
1243
1244 VkApplicationInfo appInfo = {};
1245 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
1246 appInfo.pApplicationName = "Vulkan Game";
1247 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
1248 appInfo.pEngineName = "No Engine";
1249 appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
1250 appInfo.apiVersion = VK_API_VERSION_1_0;
1251
1252 VkInstanceCreateInfo createInfo = {};
1253 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
1254 createInfo.pApplicationInfo = &appInfo;
1255
1256 vector<const char*> extensions = gui->getRequiredExtensions();
1257 if (ENABLE_VALIDATION_LAYERS) {
1258 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
1259 }
1260
1261 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
1262 createInfo.ppEnabledExtensionNames = extensions.data();
1263
1264 cout << endl << "Extensions:" << endl;
1265 for (const char* extensionName : extensions) {
1266 cout << extensionName << endl;
1267 }
1268 cout << endl;
1269
1270 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
1271 if (ENABLE_VALIDATION_LAYERS) {
1272 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
1273 createInfo.ppEnabledLayerNames = validationLayers.data();
1274
1275 populateDebugMessengerCreateInfo(debugCreateInfo);
1276 createInfo.pNext = &debugCreateInfo;
1277 } else {
1278 createInfo.enabledLayerCount = 0;
1279
1280 createInfo.pNext = nullptr;
1281 }
1282
1283 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
1284 throw runtime_error("failed to create instance!");
1285 }
1286}
1287
1288void VulkanGame::setupDebugMessenger() {
1289 if (!ENABLE_VALIDATION_LAYERS) {
1290 return;
1291 }
1292
1293 VkDebugUtilsMessengerCreateInfoEXT createInfo;
1294 populateDebugMessengerCreateInfo(createInfo);
1295
1296 if (VulkanUtils::createDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
1297 throw runtime_error("failed to set up debug messenger!");
1298 }
1299}
1300
1301void VulkanGame::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
1302 createInfo = {};
1303 createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
1304 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;
1305 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;
1306 createInfo.pfnUserCallback = debugCallback;
1307}
1308
1309VKAPI_ATTR VkBool32 VKAPI_CALL VulkanGame::debugCallback(
1310 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
1311 VkDebugUtilsMessageTypeFlagsEXT messageType,
1312 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
1313 void* pUserData) {
1314 cerr << "validation layer: " << pCallbackData->pMessage << endl;
1315
1316 // TODO: Figure out what the return value means and if it should always be VK_FALSE
1317 return VK_FALSE;
1318}
1319
1320void VulkanGame::createVulkanSurface() {
1321 if (gui->createVulkanSurface(instance, &vulkanSurface) == RTWO_ERROR) {
1322 throw runtime_error("failed to create window surface!");
1323 }
1324}
1325
1326void VulkanGame::pickPhysicalDevice(const vector<const char*>& deviceExtensions) {
1327 uint32_t deviceCount = 0;
1328 // TODO: Check VkResult
1329 vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
1330
1331 if (deviceCount == 0) {
1332 throw runtime_error("failed to find GPUs with Vulkan support!");
1333 }
1334
1335 vector<VkPhysicalDevice> devices(deviceCount);
1336 // TODO: Check VkResult
1337 vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
1338
1339 cout << endl << "Graphics cards:" << endl;
1340 for (const VkPhysicalDevice& device : devices) {
1341 if (isDeviceSuitable(device, deviceExtensions)) {
1342 physicalDevice = device;
1343 break;
1344 }
1345 }
1346 cout << endl;
1347
1348 if (physicalDevice == VK_NULL_HANDLE) {
1349 throw runtime_error("failed to find a suitable GPU!");
1350 }
1351}
1352
1353bool VulkanGame::isDeviceSuitable(VkPhysicalDevice physicalDevice, const vector<const char*>& deviceExtensions) {
1354 VkPhysicalDeviceProperties deviceProperties;
1355 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
1356
1357 cout << "Device: " << deviceProperties.deviceName << endl;
1358
1359 // TODO: Eventually, maybe let the user pick out of a set of GPUs in case the user does want to use
1360 // an integrated GPU. On my laptop, this function returns TRUE for the integrated GPU, but crashes
1361 // when trying to use it to render. Maybe I just need to figure out which other extensions and features
1362 // to check.
1363 if (deviceProperties.deviceType != VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
1364 return false;
1365 }
1366
1367 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1368 bool extensionsSupported = VulkanUtils::checkDeviceExtensionSupport(physicalDevice, deviceExtensions);
1369 bool swapChainAdequate = false;
1370
1371 if (extensionsSupported) {
1372 vector<VkSurfaceFormatKHR> formats = VulkanUtils::querySwapChainFormats(physicalDevice, vulkanSurface);
1373 vector<VkPresentModeKHR> presentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, vulkanSurface);
1374
1375 swapChainAdequate = !formats.empty() && !presentModes.empty();
1376 }
1377
1378 VkPhysicalDeviceFeatures supportedFeatures;
1379 vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
1380
1381 return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
1382}
1383
1384void VulkanGame::createLogicalDevice(const vector<const char*>& validationLayers,
1385 const vector<const char*>& deviceExtensions) {
1386 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1387
1388 if (!indices.isComplete()) {
1389 throw runtime_error("failed to find required queue families!");
1390 }
1391
1392 // TODO: Using separate graphics and present queues currently works, but I should verify that I'm
1393 // using them correctly to get the most benefit out of separate queues
1394
1395 vector<VkDeviceQueueCreateInfo> queueCreateInfoList;
1396 set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
1397
1398 float queuePriority = 1.0f;
1399 for (uint32_t queueFamily : uniqueQueueFamilies) {
1400 VkDeviceQueueCreateInfo queueCreateInfo = {};
1401 queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
1402 queueCreateInfo.queueCount = 1;
1403 queueCreateInfo.queueFamilyIndex = queueFamily;
1404 queueCreateInfo.pQueuePriorities = &queuePriority;
1405
1406 queueCreateInfoList.push_back(queueCreateInfo);
1407 }
1408
1409 VkPhysicalDeviceFeatures deviceFeatures = {};
1410 deviceFeatures.samplerAnisotropy = VK_TRUE;
1411
1412 VkDeviceCreateInfo createInfo = {};
1413 createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
1414
1415 createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfoList.size());
1416 createInfo.pQueueCreateInfos = queueCreateInfoList.data();
1417
1418 createInfo.pEnabledFeatures = &deviceFeatures;
1419
1420 createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
1421 createInfo.ppEnabledExtensionNames = deviceExtensions.data();
1422
1423 // These fields are ignored by up-to-date Vulkan implementations,
1424 // but it's a good idea to set them for backwards compatibility
1425 if (ENABLE_VALIDATION_LAYERS) {
1426 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
1427 createInfo.ppEnabledLayerNames = validationLayers.data();
1428 } else {
1429 createInfo.enabledLayerCount = 0;
1430 }
1431
1432 if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
1433 throw runtime_error("failed to create logical device!");
1434 }
1435
1436 vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
1437 vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
1438}
1439
1440void VulkanGame::chooseSwapChainProperties() {
1441 vector<VkSurfaceFormatKHR> availableFormats = VulkanUtils::querySwapChainFormats(physicalDevice, vulkanSurface);
1442 vector<VkPresentModeKHR> availablePresentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, vulkanSurface);
1443
1444 swapChainSurfaceFormat = VulkanUtils::chooseSwapSurfaceFormat(availableFormats,
1445 { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM },
1446 VK_COLOR_SPACE_SRGB_NONLINEAR_KHR);
1447
1448 vector<VkPresentModeKHR> presentModes{
1449 VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR
1450 };
1451 //vector<VkPresentModeKHR> presentModes{ VK_PRESENT_MODE_FIFO_KHR };
1452
1453 swapChainPresentMode = VulkanUtils::chooseSwapPresentMode(availablePresentModes, presentModes);
1454
1455 cout << "[vulkan] Selected PresentMode = " << swapChainPresentMode << endl;
1456
1457 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, vulkanSurface);
1458
1459 if (swapChainPresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
1460 swapChainMinImageCount = 3;
1461 } else if (swapChainPresentMode == VK_PRESENT_MODE_FIFO_KHR || swapChainPresentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) {
1462 swapChainMinImageCount = 2;
1463 } else if (swapChainPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
1464 swapChainMinImageCount = 1;
1465 } else {
1466 throw runtime_error("unexpected present mode!");
1467 }
1468
1469 if (swapChainMinImageCount < capabilities.minImageCount) {
1470 swapChainMinImageCount = capabilities.minImageCount;
1471 } else if (capabilities.maxImageCount != 0 && swapChainMinImageCount > capabilities.maxImageCount) {
1472 swapChainMinImageCount = capabilities.maxImageCount;
1473 }
1474}
1475
1476void VulkanGame::createSwapChain() {
1477 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, vulkanSurface);
1478
1479 swapChainExtent = VulkanUtils::chooseSwapExtent(capabilities, gui->getWindowWidth(), gui->getWindowHeight());
1480
1481 VkSwapchainCreateInfoKHR createInfo = {};
1482 createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
1483 createInfo.surface = vulkanSurface;
1484 createInfo.minImageCount = swapChainMinImageCount;
1485 createInfo.imageFormat = swapChainSurfaceFormat.format;
1486 createInfo.imageColorSpace = swapChainSurfaceFormat.colorSpace;
1487 createInfo.imageExtent = swapChainExtent;
1488 createInfo.imageArrayLayers = 1;
1489 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
1490
1491 // TODO: Maybe save this result so I don't have to recalculate it every time
1492 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1493 uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
1494
1495 if (indices.graphicsFamily != indices.presentFamily) {
1496 createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
1497 createInfo.queueFamilyIndexCount = 2;
1498 createInfo.pQueueFamilyIndices = queueFamilyIndices;
1499 } else {
1500 createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
1501 createInfo.queueFamilyIndexCount = 0;
1502 createInfo.pQueueFamilyIndices = nullptr;
1503 }
1504
1505 createInfo.preTransform = capabilities.currentTransform;
1506 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
1507 createInfo.presentMode = swapChainPresentMode;
1508 createInfo.clipped = VK_TRUE;
1509 createInfo.oldSwapchain = VK_NULL_HANDLE;
1510
1511 if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
1512 throw runtime_error("failed to create swap chain!");
1513 }
1514
1515 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, nullptr) != VK_SUCCESS) {
1516 throw runtime_error("failed to get swap chain image count!");
1517 }
1518
1519 swapChainImages.resize(swapChainImageCount);
1520 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, swapChainImages.data()) != VK_SUCCESS) {
1521 throw runtime_error("failed to get swap chain images!");
1522 }
1523}
1524
1525void VulkanGame::createImageViews() {
1526 swapChainImageViews.resize(swapChainImageCount);
1527
1528 for (size_t i = 0; i < swapChainImageCount; i++) {
1529 swapChainImageViews[i] = VulkanUtils::createImageView(device, swapChainImages[i], swapChainSurfaceFormat.format,
1530 VK_IMAGE_ASPECT_COLOR_BIT);
1531 }
1532}
1533
1534void VulkanGame::createResourceCommandPool() {
1535 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1536
1537 VkCommandPoolCreateInfo poolInfo = {};
1538 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
1539 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
1540 poolInfo.flags = 0;
1541
1542 if (vkCreateCommandPool(device, &poolInfo, nullptr, &resourceCommandPool) != VK_SUCCESS) {
1543 throw runtime_error("failed to create resource command pool!");
1544 }
1545}
1546
1547void VulkanGame::createImageResources() {
1548 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
1549 depthImage, graphicsQueue);
1550
1551 createTextureSampler();
1552
1553 // TODO: Move all images/textures somewhere into the assets folder
1554
1555 VulkanUtils::createVulkanImageFromFile(device, physicalDevice, resourceCommandPool, "textures/texture.jpg",
1556 floorTextureImage, graphicsQueue);
1557
1558 floorTextureImageDescriptor = {};
1559 floorTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1560 floorTextureImageDescriptor.imageView = floorTextureImage.imageView;
1561 floorTextureImageDescriptor.sampler = textureSampler;
1562
1563 VulkanUtils::createVulkanImageFromFile(device, physicalDevice, resourceCommandPool, "textures/laser.png",
1564 laserTextureImage, graphicsQueue);
1565
1566 laserTextureImageDescriptor = {};
1567 laserTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1568 laserTextureImageDescriptor.imageView = laserTextureImage.imageView;
1569 laserTextureImageDescriptor.sampler = textureSampler;
1570}
1571
1572VkFormat VulkanGame::findDepthFormat() {
1573 return VulkanUtils::findSupportedFormat(
1574 physicalDevice,
1575 { VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D32_SFLOAT, VK_FORMAT_D24_UNORM_S8_UINT },
1576 VK_IMAGE_TILING_OPTIMAL,
1577 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
1578 );
1579}
1580
1581void VulkanGame::createRenderPass() {
1582 VkAttachmentDescription colorAttachment = {};
1583 colorAttachment.format = swapChainSurfaceFormat.format;
1584 colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
1585 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1586 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
1587 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1588 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1589 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1590 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
1591
1592 VkAttachmentReference colorAttachmentRef = {};
1593 colorAttachmentRef.attachment = 0;
1594 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
1595
1596 VkAttachmentDescription depthAttachment = {};
1597 depthAttachment.format = findDepthFormat();
1598 depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
1599 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
1600 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1601 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
1602 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
1603 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1604 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1605
1606 VkAttachmentReference depthAttachmentRef = {};
1607 depthAttachmentRef.attachment = 1;
1608 depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1609
1610 VkSubpassDescription subpass = {};
1611 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
1612 subpass.colorAttachmentCount = 1;
1613 subpass.pColorAttachments = &colorAttachmentRef;
1614 subpass.pDepthStencilAttachment = &depthAttachmentRef;
1615
1616 VkSubpassDependency dependency = {};
1617 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
1618 dependency.dstSubpass = 0;
1619 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1620 dependency.srcAccessMask = 0;
1621 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
1622 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
1623
1624 array<VkAttachmentDescription, 2> attachments = { colorAttachment, depthAttachment };
1625 VkRenderPassCreateInfo renderPassInfo = {};
1626 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
1627 renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
1628 renderPassInfo.pAttachments = attachments.data();
1629 renderPassInfo.subpassCount = 1;
1630 renderPassInfo.pSubpasses = &subpass;
1631 renderPassInfo.dependencyCount = 1;
1632 renderPassInfo.pDependencies = &dependency;
1633
1634 if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
1635 throw runtime_error("failed to create render pass!");
1636 }
1637}
1638
1639void VulkanGame::createCommandPools() {
1640 commandPools.resize(swapChainImageCount);
1641
1642 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1643
1644 for (size_t i = 0; i < swapChainImageCount; i++) {
1645 VkCommandPoolCreateInfo poolInfo = {};
1646 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
1647 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
1648 poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
1649
1650 VKUTIL_CHECK_RESULT(vkCreateCommandPool(device, &poolInfo, nullptr, &commandPools[i]),
1651 "failed to create graphics command pool!");
1652 }
1653}
1654
1655void VulkanGame::createTextureSampler() {
1656 VkSamplerCreateInfo samplerInfo = {};
1657 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1658 samplerInfo.magFilter = VK_FILTER_LINEAR;
1659 samplerInfo.minFilter = VK_FILTER_LINEAR;
1660
1661 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1662 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1663 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1664
1665 samplerInfo.anisotropyEnable = VK_TRUE;
1666 samplerInfo.maxAnisotropy = 16;
1667 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
1668 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1669 samplerInfo.compareEnable = VK_FALSE;
1670 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1671 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
1672 samplerInfo.mipLodBias = 0.0f;
1673 samplerInfo.minLod = 0.0f;
1674 samplerInfo.maxLod = 0.0f;
1675
1676 VKUTIL_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler),
1677 "failed to create texture sampler!");
1678}
1679
1680void VulkanGame::createFramebuffers() {
1681 swapChainFramebuffers.resize(swapChainImageCount);
1682
1683 VkFramebufferCreateInfo framebufferInfo = {};
1684 framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
1685 framebufferInfo.renderPass = renderPass;
1686 framebufferInfo.width = swapChainExtent.width;
1687 framebufferInfo.height = swapChainExtent.height;
1688 framebufferInfo.layers = 1;
1689
1690 for (uint32_t i = 0; i < swapChainImageCount; i++) {
1691 array<VkImageView, 2> attachments = {
1692 swapChainImageViews[i],
1693 depthImage.imageView
1694 };
1695
1696 framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
1697 framebufferInfo.pAttachments = attachments.data();
1698
1699 if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
1700 throw runtime_error("failed to create framebuffer!");
1701 }
1702 }
1703}
1704
1705void VulkanGame::createCommandBuffers() {
1706 commandBuffers.resize(swapChainImageCount);
1707
1708 for (size_t i = 0; i < swapChainImageCount; i++) {
1709 VkCommandBufferAllocateInfo allocInfo = {};
1710 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1711 allocInfo.commandPool = commandPools[i];
1712 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1713 allocInfo.commandBufferCount = 1;
1714
1715 if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffers[i]) != VK_SUCCESS) {
1716 throw runtime_error("failed to allocate command buffer!");
1717 }
1718 }
1719}
1720
1721void VulkanGame::createSyncObjects() {
1722 imageAcquiredSemaphores.resize(swapChainImageCount);
1723 renderCompleteSemaphores.resize(swapChainImageCount);
1724 inFlightFences.resize(swapChainImageCount);
1725
1726 VkSemaphoreCreateInfo semaphoreInfo = {};
1727 semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
1728
1729 VkFenceCreateInfo fenceInfo = {};
1730 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
1731 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
1732
1733 for (size_t i = 0; i < swapChainImageCount; i++) {
1734 VKUTIL_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAcquiredSemaphores[i]),
1735 "failed to create image acquired sempahore for a frame!");
1736
1737 VKUTIL_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderCompleteSemaphores[i]),
1738 "failed to create render complete sempahore for a frame!");
1739
1740 VKUTIL_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]),
1741 "failed to create fence for a frame!");
1742 }
1743}
1744
1745void VulkanGame::renderFrame(ImDrawData* draw_data) {
1746 VkResult result = vkAcquireNextImageKHR(device, swapChain, numeric_limits<uint64_t>::max(),
1747 imageAcquiredSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
1748
1749 if (result == VK_SUBOPTIMAL_KHR) {
1750 shouldRecreateSwapChain = true;
1751 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1752 shouldRecreateSwapChain = true;
1753 return;
1754 } else {
1755 VKUTIL_CHECK_RESULT(result, "failed to acquire swap chain image!");
1756 }
1757
1758 VKUTIL_CHECK_RESULT(
1759 vkWaitForFences(device, 1, &inFlightFences[imageIndex], VK_TRUE, numeric_limits<uint64_t>::max()),
1760 "failed waiting for fence!");
1761
1762 VKUTIL_CHECK_RESULT(vkResetFences(device, 1, &inFlightFences[imageIndex]),
1763 "failed to reset fence!");
1764
1765 VKUTIL_CHECK_RESULT(vkResetCommandPool(device, commandPools[imageIndex], 0),
1766 "failed to reset command pool!");
1767
1768 VkCommandBufferBeginInfo beginInfo = {};
1769 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1770 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1771
1772 VKUTIL_CHECK_RESULT(vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo),
1773 "failed to begin recording command buffer!");
1774
1775 VkRenderPassBeginInfo renderPassInfo = {};
1776 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
1777 renderPassInfo.renderPass = renderPass;
1778 renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
1779 renderPassInfo.renderArea.offset = { 0, 0 };
1780 renderPassInfo.renderArea.extent = swapChainExtent;
1781
1782 array<VkClearValue, 2> clearValues = {};
1783 clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
1784 clearValues[1].depthStencil = { 1.0f, 0 };
1785
1786 renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
1787 renderPassInfo.pClearValues = clearValues.data();
1788
1789 vkCmdBeginRenderPass(commandBuffers[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
1790
1791 // TODO: Find a more elegant, per-screen solution for this
1792 if (currentRenderScreenFn == &VulkanGame::renderGameScreen) {
1793 modelPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex, {});
1794 shipPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex, {});
1795 asteroidPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex, {});
1796 laserPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex, {});
1797 explosionPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex, {});
1798 }
1799
1800 ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffers[imageIndex]);
1801
1802 vkCmdEndRenderPass(commandBuffers[imageIndex]);
1803
1804 VKUTIL_CHECK_RESULT(vkEndCommandBuffer(commandBuffers[imageIndex]),
1805 "failed to record command buffer!");
1806
1807 VkSemaphore waitSemaphores[] = { imageAcquiredSemaphores[currentFrame] };
1808 VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
1809 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
1810
1811 VkSubmitInfo submitInfo = {};
1812 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1813 submitInfo.waitSemaphoreCount = 1;
1814 submitInfo.pWaitSemaphores = waitSemaphores;
1815 submitInfo.pWaitDstStageMask = waitStages;
1816 submitInfo.commandBufferCount = 1;
1817 submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
1818 submitInfo.signalSemaphoreCount = 1;
1819 submitInfo.pSignalSemaphores = signalSemaphores;
1820
1821 VKUTIL_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[imageIndex]),
1822 "failed to submit draw command buffer!");
1823}
1824
1825void VulkanGame::presentFrame() {
1826 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
1827
1828 VkPresentInfoKHR presentInfo = {};
1829 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
1830 presentInfo.waitSemaphoreCount = 1;
1831 presentInfo.pWaitSemaphores = signalSemaphores;
1832 presentInfo.swapchainCount = 1;
1833 presentInfo.pSwapchains = &swapChain;
1834 presentInfo.pImageIndices = &imageIndex;
1835 presentInfo.pResults = nullptr;
1836
1837 VkResult result = vkQueuePresentKHR(presentQueue, &presentInfo);
1838
1839 if (result == VK_SUBOPTIMAL_KHR) {
1840 shouldRecreateSwapChain = true;
1841 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1842 shouldRecreateSwapChain = true;
1843 return;
1844 } else {
1845 VKUTIL_CHECK_RESULT(result, "failed to present swap chain image!");
1846 }
1847
1848 currentFrame = (currentFrame + 1) % swapChainImageCount;
1849}
1850
1851void VulkanGame::initImGuiOverlay() {
1852 vector<VkDescriptorPoolSize> pool_sizes{
1853 { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
1854 { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
1855 { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
1856 { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 },
1857 { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 },
1858 { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 },
1859 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 },
1860 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 },
1861 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 },
1862 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 },
1863 { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 }
1864 };
1865
1866 VkDescriptorPoolCreateInfo pool_info = {};
1867 pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1868 pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
1869 pool_info.maxSets = 1000 * pool_sizes.size();
1870 pool_info.poolSizeCount = static_cast<uint32_t>(pool_sizes.size());
1871 pool_info.pPoolSizes = pool_sizes.data();
1872
1873 VKUTIL_CHECK_RESULT(vkCreateDescriptorPool(device, &pool_info, nullptr, &imguiDescriptorPool),
1874 "failed to create IMGUI descriptor pool!");
1875
1876 // TODO: Do this in one place and save it instead of redoing it every time I need a queue family index
1877 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1878
1879 // Setup Dear ImGui context
1880 IMGUI_CHECKVERSION();
1881 ImGui::CreateContext();
1882 ImGuiIO& io = ImGui::GetIO();
1883 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
1884 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
1885
1886 // Setup Dear ImGui style
1887 ImGui::StyleColorsDark();
1888 //ImGui::StyleColorsClassic();
1889
1890 // Setup Platform/Renderer bindings
1891 ImGui_ImplSDL2_InitForVulkan(window);
1892 ImGui_ImplVulkan_InitInfo init_info = {};
1893 init_info.Instance = instance;
1894 init_info.PhysicalDevice = physicalDevice;
1895 init_info.Device = device;
1896 init_info.QueueFamily = indices.graphicsFamily.value();
1897 init_info.Queue = graphicsQueue;
1898 init_info.DescriptorPool = imguiDescriptorPool;
1899 init_info.Allocator = nullptr;
1900 init_info.MinImageCount = swapChainMinImageCount;
1901 init_info.ImageCount = swapChainImageCount;
1902 init_info.CheckVkResultFn = check_imgui_vk_result;
1903 ImGui_ImplVulkan_Init(&init_info, renderPass);
1904
1905 // Load Fonts
1906 // - 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.
1907 // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
1908 // - 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).
1909 // - 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.
1910 // - Read 'docs/FONTS.md' for more instructions and details.
1911 // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
1912 //io.Fonts->AddFontDefault();
1913 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
1914 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
1915 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
1916 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f);
1917 //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
1918 //assert(font != NULL);
1919
1920 // Upload Fonts
1921 {
1922 VkCommandBuffer commandBuffer = VulkanUtils::beginSingleTimeCommands(device, resourceCommandPool);
1923
1924 ImGui_ImplVulkan_CreateFontsTexture(commandBuffer);
1925
1926 VulkanUtils::endSingleTimeCommands(device, resourceCommandPool, commandBuffer, graphicsQueue);
1927
1928 ImGui_ImplVulkan_DestroyFontUploadObjects();
1929 }
1930}
1931
1932void VulkanGame::cleanupImGuiOverlay() {
1933 ImGui_ImplVulkan_Shutdown();
1934 ImGui_ImplSDL2_Shutdown();
1935 ImGui::DestroyContext();
1936
1937 vkDestroyDescriptorPool(device, imguiDescriptorPool, nullptr);
1938}
1939
1940void VulkanGame::createBufferSet(VkDeviceSize bufferSize, VkBufferUsageFlags usages, VkMemoryPropertyFlags properties,
1941 BufferSet& set) {
1942 set.usages = usages;
1943 set.properties = properties;
1944
1945 set.buffers.resize(swapChainImageCount);
1946 set.memory.resize(swapChainImageCount);
1947 set.infoSet.resize(swapChainImageCount);
1948
1949 for (size_t i = 0; i < swapChainImageCount; i++) {
1950 VulkanUtils::createBuffer(device, physicalDevice, bufferSize, usages, properties, set.buffers[i], set.memory[i]);
1951
1952 set.infoSet[i].buffer = set.buffers[i];
1953 set.infoSet[i].offset = 0; // This is the offset from the start of the buffer, so always 0 for now
1954 set.infoSet[i].range = bufferSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
1955 }
1956}
1957
1958void VulkanGame::resizeBufferSet(BufferSet& set, VkDeviceSize newSize, VkCommandPool commandPool,
1959 VkQueue graphicsQueue, bool copyData) {
1960 for (size_t i = 0; i < set.buffers.size(); i++) {
1961 VkBuffer newBuffer;
1962 VkDeviceMemory newMemory;
1963
1964 VulkanUtils::createBuffer(device, physicalDevice, newSize, set.usages, set.properties, newBuffer, newMemory);
1965
1966 if (copyData) {
1967 VulkanUtils::copyBuffer(device, commandPool, set.buffers[i], newBuffer, 0, 0, set.infoSet[i].range,
1968 graphicsQueue);
1969 }
1970
1971 vkDestroyBuffer(device, set.buffers[i], nullptr);
1972 vkFreeMemory(device, set.memory[i], nullptr);
1973
1974 set.buffers[i] = newBuffer;
1975 set.memory[i] = newMemory;
1976
1977 set.infoSet[i].buffer = set.buffers[i];
1978 set.infoSet[i].offset = 0; // This is the offset from the start of the buffer, so always 0 for now
1979 set.infoSet[i].range = newSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
1980 }
1981}
1982
1983void VulkanGame::addLaser(vec3 start, vec3 end, vec3 color, float width) {
1984 vec3 ray = end - start;
1985 float length = glm::length(ray);
1986
1987 SceneObject<LaserVertex>& laser = addObject(laserObjects, laserPipeline,
1988 addObjectIndex<LaserVertex>(laserObjects.size(), {
1989 {{ width / 2, 0.0f, -width / 2 }, {1.0f, 0.5f }},
1990 {{-width / 2, 0.0f, -width / 2 }, {0.0f, 0.5f }},
1991 {{-width / 2, 0.0f, 0.0f }, {0.0f, 0.0f }},
1992 {{ width / 2, 0.0f, 0.0f }, {1.0f, 0.0f }},
1993 {{ width / 2, 0.0f, -length + width / 2}, {1.0f, 0.51f}},
1994 {{-width / 2, 0.0f, -length + width / 2}, {0.0f, 0.51f}},
1995 {{ width / 2, 0.0f, -length, }, {1.0f, 1.0f }},
1996 {{-width / 2, 0.0f, -length }, {0.0f, 1.0f }}
1997 }), {
1998 0, 1, 2, 0, 2, 3,
1999 4, 5, 1, 4, 1, 0,
2000 6, 7, 5, 6, 5, 4
2001 }, objects_laserPipeline, {
2002 mat4(1.0f),
2003 color,
2004 false
2005 });
2006
2007 float xAxisRotation = asin(ray.y / length);
2008 float yAxisRotation = atan2(-ray.x, -ray.z);
2009
2010 vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
2011 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) *
2012 vec4(0.0f, 1.0f, 0.0f, 1.0f));
2013
2014 // To project point P onto line AB:
2015 // projection = A + dot(AP,AB) / dot(AB,AB) * AB
2016 vec3 projOnLaser = start + glm::dot(this->cam_pos - start, ray) / (length * length) * ray;
2017 vec3 laserToCam = this->cam_pos - projOnLaser;
2018
2019 float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam));
2020
2021 laser.targetAsteroid = nullptr;
2022
2023 laser.model_base =
2024 rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f));
2025
2026 laser.model_transform =
2027 translate(mat4(1.0f), start) *
2028 rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
2029 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f));
2030}
2031
2032void VulkanGame::translateLaser(size_t index, const vec3& translation) {
2033 SceneObject<LaserVertex>& laser = laserObjects[index];
2034
2035 // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created,
2036 // and then re-used here
2037
2038 vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
2039 vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f));
2040
2041 vec3 ray = end - start;
2042 float length = glm::length(ray);
2043
2044 float xAxisRotation = asin(ray.y / length);
2045 float yAxisRotation = atan2(-ray.x, -ray.z);
2046
2047 vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
2048 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) *
2049 vec4(0.0f, 1.0f, 0.0f, 1.0f));
2050
2051 // To project point P onto line AB:
2052 // projection = A + dot(AP,AB) / dot(AB,AB) * AB
2053 vec3 projOnLaser = start + glm::dot(cam_pos - start, ray) / (length*length) * ray;
2054 vec3 laserToCam = cam_pos - projOnLaser;
2055
2056 float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam));
2057
2058 laser.model_base = rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f));
2059 laser.model_transform = translate(mat4(1.0f), translation) * laser.model_transform;
2060}
2061
2062void VulkanGame::updateLaserTarget(size_t index) {
2063 SceneObject<LaserVertex>& laser = laserObjects[index];
2064
2065 // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created,
2066 // and then re-used here
2067
2068 vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
2069 vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f));
2070
2071 vec3 intersection(0.0f), closestIntersection(0.0f);
2072 SceneObject<ModelVertex>* closestAsteroid = nullptr;
2073 unsigned int closestAsteroidIndex = -1;
2074
2075 for (int i = 0; i < asteroidObjects.size(); i++) {
2076 if (!objects_asteroidPipeline.get(i).deleted &&
2077 getLaserAndAsteroidIntersection(asteroidObjects[i], start, end, intersection)) {
2078 // TODO: Implement a more generic algorithm for testing the closest object by getting the distance between the points
2079 // TODO: Also check which intersection is close to the start of the laser. This would make the algorithm work
2080 // regardless of which way -Z is pointing
2081 if (closestAsteroid == nullptr || intersection.z > closestIntersection.z) {
2082 // TODO: At this point, find the real intersection of the laser with one of the asteroid's sides
2083 closestAsteroid = &asteroidObjects[i];
2084 closestIntersection = intersection;
2085 closestAsteroidIndex = i;
2086 }
2087 }
2088 }
2089
2090 float width = laser.vertices[0].pos.x - laser.vertices[1].pos.x;
2091
2092 if (laser.targetAsteroid != closestAsteroid) {
2093 if (laser.targetAsteroid != nullptr) {
2094 if (index == leftLaserIdx && leftLaserEffect != nullptr) {
2095 leftLaserEffect->deleted = true;
2096 } else if (index == rightLaserIdx && rightLaserEffect != nullptr) {
2097 rightLaserEffect->deleted = true;
2098 }
2099 }
2100
2101 EffectOverTime<SSBO_Asteroid>* eot = nullptr;
2102
2103 if (closestAsteroid != nullptr) {
2104 // TODO: Figure out if I should use a smart pointer instead
2105 eot = new EffectOverTime(objects_asteroidPipeline, closestAsteroidIndex, offset_of(&SSBO_Asteroid::hp),
2106 curTime, -20.0f);
2107 effects.push_back(eot);
2108 }
2109
2110 if (index == leftLaserIdx) {
2111 leftLaserEffect = eot;
2112 } else if (index == rightLaserIdx) {
2113 rightLaserEffect = eot;
2114 }
2115
2116 laser.targetAsteroid = closestAsteroid;
2117 }
2118
2119 // Make the laser go past the end of the screen if it doesn't hit anything
2120 float length = closestAsteroid == nullptr ? 5.24f : glm::length(closestIntersection - start);
2121
2122 laser.vertices[4].pos.z = -length + width / 2;
2123 laser.vertices[5].pos.z = -length + width / 2;
2124 laser.vertices[6].pos.z = -length;
2125 laser.vertices[7].pos.z = -length;
2126
2127 // TODO: Consider if I want to set a flag and do this update in updateScene() instead
2128 updateObjectVertices(laserPipeline, laser, index);
2129}
2130
2131// TODO: Determine if I should pass start and end by reference or value since they don't get changed
2132// Probably use const reference
2133bool VulkanGame::getLaserAndAsteroidIntersection(SceneObject<ModelVertex>& asteroid, vec3& start, vec3& end,
2134 vec3& intersection) {
2135 /*
2136 ### LINE EQUATIONS ###
2137 x = x1 + u * (x2 - x1)
2138 y = y1 + u * (y2 - y1)
2139 z = z1 + u * (z2 - z1)
2140
2141 ### SPHERE EQUATION ###
2142 (x - x3)^2 + (y - y3)^2 + (z - z3)^2 = r^2
2143
2144 ### QUADRATIC EQUATION TO SOLVE ###
2145 a*u^2 + b*u + c = 0
2146 WHERE THE CONSTANTS ARE
2147 a = (x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2
2148 b = 2*( (x2 - x1)*(x1 - x3) + (y2 - y1)*(y1 - y3) + (z2 - z1)*(z1 - z3) )
2149 c = x3^2 + y3^2 + z3^2 + x1^2 + y1^2 + z1^2 - 2(x3*x1 + y3*y1 + z3*z1) - r^2
2150
2151 u = (-b +- sqrt(b^2 - 4*a*c)) / 2a
2152
2153 If the value under the root is >= 0, we got an intersection
2154 If the value > 0, there are two solutions. Take the one closer to 0, since that's the
2155 one closer to the laser start point
2156 */
2157
2158 vec3& center = asteroid.center;
2159
2160 float a = pow(end.x - start.x, 2) + pow(end.y - start.y, 2) + pow(end.z - start.z, 2);
2161 float b = 2 * ((start.x - end.x) * (start.x - center.x) + (end.y - start.y) * (start.y - center.y) +
2162 (end.z - start.z) * (start.z - center.z));
2163 float c = pow(center.x, 2) + pow(center.y, 2) + pow(center.z, 2) + pow(start.x, 2) + pow(start.y, 2) +
2164 pow(start.z, 2) - 2 * (center.x * start.x + center.y * start.y + center.z * start.z) -
2165 pow(asteroid.radius, 2);
2166 float discriminant = pow(b, 2) - 4 * a * c;
2167
2168 if (discriminant >= 0.0f) {
2169 // In this case, the negative root will always give the point closer to the laser start point
2170 float u = (-b - sqrt(discriminant)) / (2 * a);
2171
2172 // Check that the intersection is within the line segment corresponding to the laser
2173 if (0.0f <= u && u <= 1.0f) {
2174 intersection = start + u * (end - start);
2175 return true;
2176 }
2177 }
2178
2179 return false;
2180}
2181
2182void VulkanGame::addExplosion(mat4 model_mat, float duration, float cur_time) {
2183 vector<ExplosionVertex> vertices;
2184 vertices.reserve(EXPLOSION_PARTICLE_COUNT);
2185
2186 float particle_start_time = 0.0f;
2187
2188 for (int i = 0; i < EXPLOSION_PARTICLE_COUNT; i++) {
2189 float randx = ((float)rand() / (float)RAND_MAX) - 0.5f;
2190 float randy = ((float)rand() / (float)RAND_MAX) - 0.5f;
2191
2192 vertices.push_back({ vec3(randx, randy, 0.0f), particle_start_time});
2193
2194 particle_start_time += .01f;
2195 // TODO: Get this working
2196 // particle_start_time += 1.0f * EXPLOSION_PARTICLE_COUNT / duration
2197 }
2198
2199 // Fill the indices with the the first EXPLOSION_PARTICLE_COUNT ints
2200 vector<uint16_t> indices(EXPLOSION_PARTICLE_COUNT);
2201 iota(indices.begin(), indices.end(), 0);
2202
2203 SceneObject<ExplosionVertex>& explosion = addObject(explosionObjects, explosionPipeline,
2204 addObjectIndex(explosionObjects.size(), vertices), indices, objects_explosionPipeline, {
2205 mat4(1.0f),
2206 cur_time,
2207 duration,
2208 false
2209 });
2210
2211 explosion.model_base = model_mat;
2212 explosion.model_transform = mat4(1.0f);
2213}
2214
2215void VulkanGame::recreateSwapChain() {
2216 if (vkDeviceWaitIdle(device) != VK_SUCCESS) {
2217 throw runtime_error("failed to wait for device!");
2218 }
2219
2220 cleanupSwapChain();
2221
2222 createSwapChain();
2223 createImageViews();
2224
2225 // The depth buffer does need to be recreated with the swap chain since its dimensions depend on the window size
2226 // and resizing the window is a common reason to recreate the swapchain
2227 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
2228 depthImage, graphicsQueue);
2229
2230 createRenderPass();
2231 createCommandPools();
2232 createFramebuffers();
2233 createCommandBuffers();
2234 createSyncObjects();
2235
2236 createBufferSet(sizeof(UBO_VP_mats),
2237 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2238 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2239 uniformBuffers_modelPipeline);
2240
2241 createBufferSet(objects_modelPipeline.memorySize(),
2242 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT
2243 | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
2244 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2245 storageBuffers_modelPipeline);
2246
2247 modelPipeline.updateRenderPass(renderPass);
2248 modelPipeline.createPipeline("shaders/model-vert.spv", "shaders/model-frag.spv");
2249 modelPipeline.createDescriptorPool(swapChainImages.size());
2250 modelPipeline.createDescriptorSets(swapChainImages.size());
2251
2252 createBufferSet(sizeof(UBO_VP_mats),
2253 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2254 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2255 uniformBuffers_shipPipeline);
2256
2257 createBufferSet(objects_shipPipeline.memorySize(),
2258 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT
2259 | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
2260 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2261 storageBuffers_shipPipeline);
2262
2263 shipPipeline.updateRenderPass(renderPass);
2264 shipPipeline.createPipeline("shaders/ship-vert.spv", "shaders/ship-frag.spv");
2265 shipPipeline.createDescriptorPool(swapChainImages.size());
2266 shipPipeline.createDescriptorSets(swapChainImages.size());
2267
2268 createBufferSet(sizeof(UBO_VP_mats),
2269 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2270 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2271 uniformBuffers_asteroidPipeline);
2272
2273 createBufferSet(objects_asteroidPipeline.memorySize(),
2274 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT
2275 | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
2276 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2277 storageBuffers_asteroidPipeline);
2278
2279 asteroidPipeline.updateRenderPass(renderPass);
2280 asteroidPipeline.createPipeline("shaders/asteroid-vert.spv", "shaders/asteroid-frag.spv");
2281 asteroidPipeline.createDescriptorPool(swapChainImages.size());
2282 asteroidPipeline.createDescriptorSets(swapChainImages.size());
2283
2284 createBufferSet(sizeof(UBO_VP_mats),
2285 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2286 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2287 uniformBuffers_laserPipeline);
2288
2289 createBufferSet(objects_laserPipeline.memorySize(),
2290 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT
2291 | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
2292 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2293 storageBuffers_laserPipeline);
2294
2295 laserPipeline.updateRenderPass(renderPass);
2296 laserPipeline.createPipeline("shaders/laser-vert.spv", "shaders/laser-frag.spv");
2297 laserPipeline.createDescriptorPool(swapChainImages.size());
2298 laserPipeline.createDescriptorSets(swapChainImages.size());
2299
2300 createBufferSet(sizeof(UBO_Explosion),
2301 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
2302 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2303 uniformBuffers_explosionPipeline);
2304
2305 createBufferSet(objects_explosionPipeline.memorySize(),
2306 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT
2307 | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
2308 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2309 storageBuffers_explosionPipeline);
2310
2311 explosionPipeline.updateRenderPass(renderPass);
2312 explosionPipeline.createPipeline("shaders/explosion-vert.spv", "shaders/explosion-frag.spv");
2313 explosionPipeline.createDescriptorPool(swapChainImages.size());
2314 explosionPipeline.createDescriptorSets(swapChainImages.size());
2315
2316 imageIndex = 0;
2317}
2318
2319void VulkanGame::cleanupSwapChain() {
2320 VulkanUtils::destroyVulkanImage(device, depthImage);
2321
2322 for (VkFramebuffer framebuffer : swapChainFramebuffers) {
2323 vkDestroyFramebuffer(device, framebuffer, nullptr);
2324 }
2325
2326 for (uint32_t i = 0; i < swapChainImageCount; i++) {
2327 vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
2328 vkDestroyCommandPool(device, commandPools[i], nullptr);
2329 }
2330
2331 modelPipeline.cleanup();
2332 shipPipeline.cleanup();
2333 asteroidPipeline.cleanup();
2334 laserPipeline.cleanup();
2335 explosionPipeline.cleanup();
2336
2337 for (size_t i = 0; i < uniformBuffers_modelPipeline.buffers.size(); i++) {
2338 vkDestroyBuffer(device, uniformBuffers_modelPipeline.buffers[i], nullptr);
2339 vkFreeMemory(device, uniformBuffers_modelPipeline.memory[i], nullptr);
2340 }
2341
2342 for (size_t i = 0; i < storageBuffers_modelPipeline.buffers.size(); i++) {
2343 vkDestroyBuffer(device, storageBuffers_modelPipeline.buffers[i], nullptr);
2344 vkFreeMemory(device, storageBuffers_modelPipeline.memory[i], nullptr);
2345 }
2346
2347 for (size_t i = 0; i < uniformBuffers_shipPipeline.buffers.size(); i++) {
2348 vkDestroyBuffer(device, uniformBuffers_shipPipeline.buffers[i], nullptr);
2349 vkFreeMemory(device, uniformBuffers_shipPipeline.memory[i], nullptr);
2350 }
2351
2352 for (size_t i = 0; i < storageBuffers_shipPipeline.buffers.size(); i++) {
2353 vkDestroyBuffer(device, storageBuffers_shipPipeline.buffers[i], nullptr);
2354 vkFreeMemory(device, storageBuffers_shipPipeline.memory[i], nullptr);
2355 }
2356
2357 for (size_t i = 0; i < uniformBuffers_asteroidPipeline.buffers.size(); i++) {
2358 vkDestroyBuffer(device, uniformBuffers_asteroidPipeline.buffers[i], nullptr);
2359 vkFreeMemory(device, uniformBuffers_asteroidPipeline.memory[i], nullptr);
2360 }
2361
2362 for (size_t i = 0; i < storageBuffers_asteroidPipeline.buffers.size(); i++) {
2363 vkDestroyBuffer(device, storageBuffers_asteroidPipeline.buffers[i], nullptr);
2364 vkFreeMemory(device, storageBuffers_asteroidPipeline.memory[i], nullptr);
2365 }
2366
2367 for (size_t i = 0; i < uniformBuffers_laserPipeline.buffers.size(); i++) {
2368 vkDestroyBuffer(device, uniformBuffers_laserPipeline.buffers[i], nullptr);
2369 vkFreeMemory(device, uniformBuffers_laserPipeline.memory[i], nullptr);
2370 }
2371
2372 for (size_t i = 0; i < storageBuffers_laserPipeline.buffers.size(); i++) {
2373 vkDestroyBuffer(device, storageBuffers_laserPipeline.buffers[i], nullptr);
2374 vkFreeMemory(device, storageBuffers_laserPipeline.memory[i], nullptr);
2375 }
2376
2377 for (size_t i = 0; i < uniformBuffers_explosionPipeline.buffers.size(); i++) {
2378 vkDestroyBuffer(device, uniformBuffers_explosionPipeline.buffers[i], nullptr);
2379 vkFreeMemory(device, uniformBuffers_explosionPipeline.memory[i], nullptr);
2380 }
2381
2382 for (size_t i = 0; i < storageBuffers_explosionPipeline.buffers.size(); i++) {
2383 vkDestroyBuffer(device, storageBuffers_explosionPipeline.buffers[i], nullptr);
2384 vkFreeMemory(device, storageBuffers_explosionPipeline.memory[i], nullptr);
2385 }
2386
2387 for (size_t i = 0; i < swapChainImageCount; i++) {
2388 vkDestroySemaphore(device, imageAcquiredSemaphores[i], nullptr);
2389 vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr);
2390 vkDestroyFence(device, inFlightFences[i], nullptr);
2391 }
2392
2393 vkDestroyRenderPass(device, renderPass, nullptr);
2394
2395 for (VkImageView imageView : swapChainImageViews) {
2396 vkDestroyImageView(device, imageView, nullptr);
2397 }
2398
2399 vkDestroySwapchainKHR(device, swapChain, nullptr);
2400}
2401
2402void VulkanGame::renderMainScreen(int width, int height) {
2403 {
2404 int padding = 4;
2405 ImGui::SetNextWindowPos(vec2(-padding, -padding), ImGuiCond_Once);
2406 ImGui::SetNextWindowSize(vec2(width + 2 * padding, height + 2 * padding), ImGuiCond_Always);
2407 ImGui::Begin("WndMain", nullptr,
2408 ImGuiWindowFlags_NoTitleBar |
2409 ImGuiWindowFlags_NoResize |
2410 ImGuiWindowFlags_NoMove);
2411
2412 ButtonImGui btn("New Game");
2413
2414 ImGui::InvisibleButton("", vec2(10, height / 6));
2415 if (btn.draw((width - btn.getWidth()) / 2)) {
2416 goToScreen(&VulkanGame::renderGameScreen);
2417 }
2418
2419 ButtonImGui btn2("Quit");
2420
2421 ImGui::InvisibleButton("", vec2(10, 15));
2422 if (btn2.draw((width - btn2.getWidth()) / 2)) {
2423 quitGame();
2424 }
2425
2426 ImGui::End();
2427 }
2428}
2429
2430void VulkanGame::renderGameScreen(int width, int height) {
2431 {
2432 ImGui::SetNextWindowSize(vec2(130, 65), ImGuiCond_Once);
2433 ImGui::SetNextWindowPos(vec2(10, 50), ImGuiCond_Once);
2434 ImGui::Begin("WndStats", nullptr,
2435 ImGuiWindowFlags_NoTitleBar |
2436 ImGuiWindowFlags_NoResize |
2437 ImGuiWindowFlags_NoMove);
2438
2439 //ImGui::Text(ImGui::GetIO().Framerate);
2440 renderGuiValueList(valueLists["stats value list"]);
2441
2442 ImGui::End();
2443 }
2444
2445 {
2446 ImGui::SetNextWindowSize(vec2(250, 35), ImGuiCond_Once);
2447 ImGui::SetNextWindowPos(vec2(width - 260, 10), ImGuiCond_Always);
2448 ImGui::Begin("WndMenubar", nullptr,
2449 ImGuiWindowFlags_NoTitleBar |
2450 ImGuiWindowFlags_NoResize |
2451 ImGuiWindowFlags_NoMove);
2452 ImGui::InvisibleButton("", vec2(155, 18));
2453 ImGui::SameLine();
2454 if (ImGui::Button("Main Menu")) {
2455 goToScreen(&VulkanGame::renderMainScreen);
2456 }
2457 ImGui::End();
2458 }
2459
2460 {
2461 ImGui::SetNextWindowSize(vec2(200, 200), ImGuiCond_Once);
2462 ImGui::SetNextWindowPos(vec2(width - 210, 60), ImGuiCond_Always);
2463 ImGui::Begin("WndDebug", nullptr,
2464 ImGuiWindowFlags_NoTitleBar |
2465 ImGuiWindowFlags_NoResize |
2466 ImGuiWindowFlags_NoMove);
2467
2468 renderGuiValueList(valueLists["debug value list"]);
2469
2470 ImGui::End();
2471 }
2472}
2473
2474void VulkanGame::initGuiValueLists(map<string, vector<UIValue>>& valueLists) {
2475 valueLists["stats value list"] = vector<UIValue>();
2476 valueLists["debug value list"] = vector<UIValue>();
2477}
2478
2479// TODO: Probably turn this into a UI widget class
2480void VulkanGame::renderGuiValueList(vector<UIValue>& values) {
2481 float maxWidth = 0.0f;
2482 float cursorStartPos = ImGui::GetCursorPosX();
2483
2484 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
2485 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
2486
2487 if (maxWidth < textWidth)
2488 maxWidth = textWidth;
2489 }
2490
2491 stringstream ss;
2492
2493 // TODO: Possibly implement this based on gui/ui-value.hpp instead and use templates
2494 // to keep track of the type. This should make it a bit easier to use and maintain
2495 // Also, implement this in a way that's agnostic to the UI renderer.
2496 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
2497 ss.str("");
2498 ss.clear();
2499
2500 switch (it->type) {
2501 case UIVALUE_INT:
2502 ss << it->label << ": " << *(unsigned int*)it->value;
2503 break;
2504 case UIVALUE_DOUBLE:
2505 ss << it->label << ": " << *(double*)it->value;
2506 break;
2507 }
2508
2509 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
2510
2511 ImGui::SetCursorPosX(cursorStartPos + maxWidth - textWidth);
2512 //ImGui::Text("%s", ss.str().c_str());
2513 ImGui::Text("%s: %.1f", it->label.c_str(), *(float*)it->value);
2514 }
2515}
2516
2517void VulkanGame::goToScreen(void (VulkanGame::* renderScreenFn)(int width, int height)) {
2518 currentRenderScreenFn = renderScreenFn;
2519
2520 // TODO: Maybe just set shouldRecreateSwapChain to true instead. Check this render loop logic
2521 // to make sure there'd be no issues
2522 //recreateSwapChain();
2523}
2524
2525void VulkanGame::quitGame() {
2526 done = true;
2527}
Note: See TracBrowser for help on using the repository browser.