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

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

Change VulkanUtils::copyDataToMemory() to always require a size and to optionally flush the memory. Also add a VulkanUtils::copyDataToMappedMemory function that does the same thing, but assumes the data is already mapped.

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