source: opengl-game/vulkan-game.cpp@ 8aa4888

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

Add buffer usages and memory properties to BufferSet and save them (to help with future recreation) in createBufferSet()

  • 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 usages, VkMemoryPropertyFlags properties,
1945 BufferSet& set) {
1946 set.usages = usages;
1947 set.properties = properties;
1948
1949 set.buffers.resize(swapChainImageCount);
1950 set.memory.resize(swapChainImageCount);
1951 set.infoSet.resize(swapChainImageCount);
1952
1953 for (size_t i = 0; i < swapChainImageCount; i++) {
1954 VulkanUtils::createBuffer(device, physicalDevice, bufferSize, usages, properties, set.buffers[i], set.memory[i]);
1955
1956 set.infoSet[i].buffer = set.buffers[i];
1957 set.infoSet[i].offset = 0; // This is the offset from the start of the buffer, so always 0 for now
1958 set.infoSet[i].range = bufferSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
1959 }
1960}
1961
1962void VulkanGame::addLaser(vec3 start, vec3 end, vec3 color, float width) {
1963 vec3 ray = end - start;
1964 float length = glm::length(ray);
1965
1966 SceneObject<LaserVertex, SSBO_Laser>& laser = addObject(
1967 laserObjects, laserPipeline,
1968 addObjectIndex<LaserVertex>(laserObjects.size(), {
1969 {{ width / 2, 0.0f, -width / 2 }, {1.0f, 0.5f }},
1970 {{-width / 2, 0.0f, -width / 2 }, {0.0f, 0.5f }},
1971 {{-width / 2, 0.0f, 0.0f }, {0.0f, 0.0f }},
1972 {{ width / 2, 0.0f, 0.0f }, {1.0f, 0.0f }},
1973 {{ width / 2, 0.0f, -length + width / 2}, {1.0f, 0.51f}},
1974 {{-width / 2, 0.0f, -length + width / 2}, {0.0f, 0.51f}},
1975 {{ width / 2, 0.0f, -length, }, {1.0f, 1.0f }},
1976 {{-width / 2, 0.0f, -length }, {0.0f, 1.0f }}
1977 }), {
1978 0, 1, 2, 0, 2, 3,
1979 4, 5, 1, 4, 1, 0,
1980 6, 7, 5, 6, 5, 4
1981 }, {
1982 mat4(1.0f),
1983 color,
1984 false
1985 });
1986
1987 objects_laserPipeline.numObjects++;
1988
1989 float xAxisRotation = asin(ray.y / length);
1990 float yAxisRotation = atan2(-ray.x, -ray.z);
1991
1992 vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
1993 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) *
1994 vec4(0.0f, 1.0f, 0.0f, 1.0f));
1995
1996 // To project point P onto line AB:
1997 // projection = A + dot(AP,AB) / dot(AB,AB) * AB
1998 vec3 projOnLaser = start + glm::dot(this->cam_pos - start, ray) / (length * length) * ray;
1999 vec3 laserToCam = this->cam_pos - projOnLaser;
2000
2001 float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam));
2002
2003 laser.targetAsteroid = nullptr;
2004
2005 laser.model_base =
2006 rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f));
2007
2008 laser.model_transform =
2009 translate(mat4(1.0f), start) *
2010 rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
2011 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f));
2012
2013 laser.modified = true;
2014}
2015
2016void VulkanGame::translateLaser(size_t index, const vec3& translation) {
2017 SceneObject<LaserVertex, SSBO_Laser>& laser = this->laserObjects[index];
2018
2019 // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created,
2020 // and then re-used here
2021
2022 vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
2023 vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f));
2024
2025 vec3 ray = end - start;
2026 float length = glm::length(ray);
2027
2028 float xAxisRotation = asin(ray.y / length);
2029 float yAxisRotation = atan2(-ray.x, -ray.z);
2030
2031 vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) *
2032 rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) *
2033 vec4(0.0f, 1.0f, 0.0f, 1.0f));
2034
2035 // To project point P onto line AB:
2036 // projection = A + dot(AP,AB) / dot(AB,AB) * AB
2037 vec3 projOnLaser = start + glm::dot(cam_pos - start, ray) / (length*length) * ray;
2038 vec3 laserToCam = cam_pos - projOnLaser;
2039
2040 float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam));
2041
2042 laser.model_base = rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f));
2043 laser.model_transform = translate(mat4(1.0f), translation) * laser.model_transform;
2044
2045 laser.modified = true;
2046}
2047
2048void VulkanGame::updateLaserTarget(size_t index) {
2049 SceneObject<LaserVertex, SSBO_Laser>& laser = this->laserObjects[index];
2050
2051 // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created,
2052 // and then re-used here
2053
2054 vec3 start = vec3(laser.model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f));
2055 vec3 end = vec3(laser.model_transform * vec4(0.0f, 0.0f, laser.vertices[6].pos.z, 1.0f));
2056
2057 vec3 intersection(0.0f), closestIntersection(0.0f);
2058 SceneObject<ModelVertex, SSBO_Asteroid>* closestAsteroid = nullptr;
2059 unsigned int closestAsteroidIndex = -1;
2060
2061 for (int i = 0; i < this->asteroidObjects.size(); i++) {
2062 if (!this->asteroidObjects[i].ssbo.deleted &&
2063 this->getLaserAndAsteroidIntersection(this->asteroidObjects[i], start, end, intersection)) {
2064 // TODO: Implement a more generic algorithm for testing the closest object by getting the distance between the points
2065 // TODO: Also check which intersection is close to the start of the laser. This would make the algorithm work
2066 // regardless of which way -Z is pointing
2067 if (closestAsteroid == nullptr || intersection.z > closestIntersection.z) {
2068 // TODO: At this point, find the real intersection of the laser with one of the asteroid's sides
2069 closestAsteroid = &asteroidObjects[i];
2070 closestIntersection = intersection;
2071 closestAsteroidIndex = i;
2072 }
2073 }
2074 }
2075
2076 float width = laser.vertices[0].pos.x - laser.vertices[1].pos.x;
2077
2078 if (laser.targetAsteroid != closestAsteroid) {
2079 if (laser.targetAsteroid != nullptr) {
2080 if (index == leftLaserIdx && leftLaserEffect != nullptr) {
2081 leftLaserEffect->deleted = true;
2082 } else if (index == rightLaserIdx && rightLaserEffect != nullptr) {
2083 rightLaserEffect->deleted = true;
2084 }
2085 }
2086
2087 EffectOverTime<ModelVertex, SSBO_Asteroid>* eot = nullptr;
2088
2089 if (closestAsteroid != nullptr) {
2090 // TODO: Use some sort of smart pointer instead
2091 eot = new EffectOverTime<ModelVertex, SSBO_Asteroid>(asteroidPipeline, asteroidObjects, closestAsteroidIndex,
2092 offset_of(&SSBO_Asteroid::hp), curTime, -20.0f);
2093 effects.push_back(eot);
2094 }
2095
2096 if (index == leftLaserIdx) {
2097 leftLaserEffect = eot;
2098 } else if (index == rightLaserIdx) {
2099 rightLaserEffect = eot;
2100 }
2101
2102 laser.targetAsteroid = closestAsteroid;
2103 }
2104
2105 // Make the laser go past the end of the screen if it doesn't hit anything
2106 float length = closestAsteroid == nullptr ? 5.24f : glm::length(closestIntersection - start);
2107
2108 laser.vertices[4].pos.z = -length + width / 2;
2109 laser.vertices[5].pos.z = -length + width / 2;
2110 laser.vertices[6].pos.z = -length;
2111 laser.vertices[7].pos.z = -length;
2112
2113 // TODO: Consider if I want to set a flag and do this update in updateScene() instead
2114 updateObjectVertices(this->laserPipeline, laser, index);
2115}
2116
2117// TODO: Determine if I should pass start and end by reference or value since they don't get changed
2118// Probably use const reference
2119bool VulkanGame::getLaserAndAsteroidIntersection(SceneObject<ModelVertex, SSBO_Asteroid>& asteroid,
2120 vec3& start, vec3& end, vec3& intersection) {
2121 /*
2122 ### LINE EQUATIONS ###
2123 x = x1 + u * (x2 - x1)
2124 y = y1 + u * (y2 - y1)
2125 z = z1 + u * (z2 - z1)
2126
2127 ### SPHERE EQUATION ###
2128 (x - x3)^2 + (y - y3)^2 + (z - z3)^2 = r^2
2129
2130 ### QUADRATIC EQUATION TO SOLVE ###
2131 a*u^2 + b*u + c = 0
2132 WHERE THE CONSTANTS ARE
2133 a = (x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2
2134 b = 2*( (x2 - x1)*(x1 - x3) + (y2 - y1)*(y1 - y3) + (z2 - z1)*(z1 - z3) )
2135 c = x3^2 + y3^2 + z3^2 + x1^2 + y1^2 + z1^2 - 2(x3*x1 + y3*y1 + z3*z1) - r^2
2136
2137 u = (-b +- sqrt(b^2 - 4*a*c)) / 2a
2138
2139 If the value under the root is >= 0, we got an intersection
2140 If the value > 0, there are two solutions. Take the one closer to 0, since that's the
2141 one closer to the laser start point
2142 */
2143
2144 vec3& center = asteroid.center;
2145
2146 float a = pow(end.x - start.x, 2) + pow(end.y - start.y, 2) + pow(end.z - start.z, 2);
2147 float b = 2 * ((start.x - end.x) * (start.x - center.x) + (end.y - start.y) * (start.y - center.y) +
2148 (end.z - start.z) * (start.z - center.z));
2149 float c = pow(center.x, 2) + pow(center.y, 2) + pow(center.z, 2) + pow(start.x, 2) + pow(start.y, 2) +
2150 pow(start.z, 2) - 2 * (center.x * start.x + center.y * start.y + center.z * start.z) -
2151 pow(asteroid.radius, 2);
2152 float discriminant = pow(b, 2) - 4 * a * c;
2153
2154 if (discriminant >= 0.0f) {
2155 // In this case, the negative root will always give the point closer to the laser start point
2156 float u = (-b - sqrt(discriminant)) / (2 * a);
2157
2158 // Check that the intersection is within the line segment corresponding to the laser
2159 if (0.0f <= u && u <= 1.0f) {
2160 intersection = start + u * (end - start);
2161 return true;
2162 }
2163 }
2164
2165 return false;
2166}
2167
2168void VulkanGame::addExplosion(mat4 model_mat, float duration, float cur_time) {
2169 vector<ExplosionVertex> vertices;
2170 vertices.reserve(EXPLOSION_PARTICLE_COUNT);
2171
2172 float particle_start_time = 0.0f;
2173
2174 for (int i = 0; i < EXPLOSION_PARTICLE_COUNT; i++) {
2175 float randx = ((float)rand() / (float)RAND_MAX) - 0.5f;
2176 float randy = ((float)rand() / (float)RAND_MAX) - 0.5f;
2177
2178 vertices.push_back({ vec3(randx, randy, 0.0f), particle_start_time});
2179
2180 particle_start_time += .01f;
2181 // TODO: Get this working
2182 // particle_start_time += 1.0f * EXPLOSION_PARTICLE_COUNT / duration
2183 }
2184
2185 // Fill the indices with the the first EXPLOSION_PARTICLE_COUNT ints
2186 vector<uint16_t> indices(EXPLOSION_PARTICLE_COUNT);
2187 iota(indices.begin(), indices.end(), 0);
2188
2189 SceneObject<ExplosionVertex, SSBO_Explosion>& explosion = addObject(
2190 explosionObjects, explosionPipeline, addObjectIndex(explosionObjects.size(), vertices), indices, {
2191 mat4(1.0f),
2192 cur_time,
2193 duration,
2194 false
2195 });
2196
2197 objects_explosionPipeline.numObjects++;
2198
2199 explosion.model_base = model_mat;
2200 explosion.model_transform = mat4(1.0f);
2201
2202 explosion.modified = true;
2203}
2204
2205void VulkanGame::recreateSwapChain() {
2206 if (vkDeviceWaitIdle(device) != VK_SUCCESS) {
2207 throw runtime_error("failed to wait for device!");
2208 }
2209
2210 cleanupSwapChain();
2211
2212 createSwapChain();
2213 createImageViews();
2214
2215 // The depth buffer does need to be recreated with the swap chain since its dimensions depend on the window size
2216 // and resizing the window is a common reason to recreate the swapchain
2217 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
2218 depthImage, graphicsQueue);
2219
2220 createRenderPass();
2221 createCommandPools();
2222 createFramebuffers();
2223 createCommandBuffers();
2224 createSyncObjects();
2225
2226 // TODO: Move UBO creation/management into GraphicsPipeline_Vulkan, like I did with SSBOs
2227 // TODO: Check if the shader stages and maybe some other properties of the pipeline can be re-used
2228 // instead of recreated every time
2229
2230 createBufferSet(sizeof(UBO_VP_mats),
2231 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2232 uniformBuffers_modelPipeline);
2233
2234 modelPipeline.updateRenderPass(renderPass);
2235 modelPipeline.createPipeline("shaders/model-vert.spv", "shaders/model-frag.spv");
2236 modelPipeline.createDescriptorPool(swapChainImages.size());
2237 modelPipeline.createDescriptorSets(swapChainImages.size());
2238
2239 createBufferSet(sizeof(UBO_VP_mats),
2240 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2241 uniformBuffers_shipPipeline);
2242
2243 shipPipeline.updateRenderPass(renderPass);
2244 shipPipeline.createPipeline("shaders/ship-vert.spv", "shaders/ship-frag.spv");
2245 shipPipeline.createDescriptorPool(swapChainImages.size());
2246 shipPipeline.createDescriptorSets(swapChainImages.size());
2247
2248 createBufferSet(sizeof(UBO_VP_mats),
2249 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2250 uniformBuffers_asteroidPipeline);
2251
2252 asteroidPipeline.updateRenderPass(renderPass);
2253 asteroidPipeline.createPipeline("shaders/asteroid-vert.spv", "shaders/asteroid-frag.spv");
2254 asteroidPipeline.createDescriptorPool(swapChainImages.size());
2255 asteroidPipeline.createDescriptorSets(swapChainImages.size());
2256
2257 createBufferSet(sizeof(UBO_VP_mats),
2258 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2259 uniformBuffers_laserPipeline);
2260
2261 laserPipeline.updateRenderPass(renderPass);
2262 laserPipeline.createPipeline("shaders/laser-vert.spv", "shaders/laser-frag.spv");
2263 laserPipeline.createDescriptorPool(swapChainImages.size());
2264 laserPipeline.createDescriptorSets(swapChainImages.size());
2265
2266 createBufferSet(sizeof(UBO_Explosion),
2267 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
2268 uniformBuffers_explosionPipeline);
2269
2270 explosionPipeline.updateRenderPass(renderPass);
2271 explosionPipeline.createPipeline("shaders/explosion-vert.spv", "shaders/explosion-frag.spv");
2272 explosionPipeline.createDescriptorPool(swapChainImages.size());
2273 explosionPipeline.createDescriptorSets(swapChainImages.size());
2274
2275 imageIndex = 0;
2276}
2277
2278void VulkanGame::cleanupSwapChain() {
2279 VulkanUtils::destroyVulkanImage(device, depthImage);
2280
2281 for (VkFramebuffer framebuffer : swapChainFramebuffers) {
2282 vkDestroyFramebuffer(device, framebuffer, nullptr);
2283 }
2284
2285 for (uint32_t i = 0; i < swapChainImageCount; i++) {
2286 vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
2287 vkDestroyCommandPool(device, commandPools[i], nullptr);
2288 }
2289
2290 modelPipeline.cleanup();
2291 shipPipeline.cleanup();
2292 asteroidPipeline.cleanup();
2293 laserPipeline.cleanup();
2294 explosionPipeline.cleanup();
2295
2296 for (size_t i = 0; i < uniformBuffers_modelPipeline.buffers.size(); i++) {
2297 vkDestroyBuffer(device, uniformBuffers_modelPipeline.buffers[i], nullptr);
2298 vkFreeMemory(device, uniformBuffers_modelPipeline.memory[i], nullptr);
2299 }
2300
2301 for (size_t i = 0; i < uniformBuffers_shipPipeline.buffers.size(); i++) {
2302 vkDestroyBuffer(device, uniformBuffers_shipPipeline.buffers[i], nullptr);
2303 vkFreeMemory(device, uniformBuffers_shipPipeline.memory[i], nullptr);
2304 }
2305
2306 for (size_t i = 0; i < uniformBuffers_asteroidPipeline.buffers.size(); i++) {
2307 vkDestroyBuffer(device, uniformBuffers_asteroidPipeline.buffers[i], nullptr);
2308 vkFreeMemory(device, uniformBuffers_asteroidPipeline.memory[i], nullptr);
2309 }
2310
2311 for (size_t i = 0; i < uniformBuffers_laserPipeline.buffers.size(); i++) {
2312 vkDestroyBuffer(device, uniformBuffers_laserPipeline.buffers[i], nullptr);
2313 vkFreeMemory(device, uniformBuffers_laserPipeline.memory[i], nullptr);
2314 }
2315
2316 for (size_t i = 0; i < uniformBuffers_explosionPipeline.buffers.size(); i++) {
2317 vkDestroyBuffer(device, uniformBuffers_explosionPipeline.buffers[i], nullptr);
2318 vkFreeMemory(device, uniformBuffers_explosionPipeline.memory[i], nullptr);
2319 }
2320
2321 for (size_t i = 0; i < swapChainImageCount; i++) {
2322 vkDestroySemaphore(device, imageAcquiredSemaphores[i], nullptr);
2323 vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr);
2324 vkDestroyFence(device, inFlightFences[i], nullptr);
2325 }
2326
2327 vkDestroyRenderPass(device, renderPass, nullptr);
2328
2329 for (VkImageView imageView : swapChainImageViews) {
2330 vkDestroyImageView(device, imageView, nullptr);
2331 }
2332
2333 vkDestroySwapchainKHR(device, swapChain, nullptr);
2334}
2335
2336void VulkanGame::renderMainScreen(int width, int height) {
2337 {
2338 int padding = 4;
2339 ImGui::SetNextWindowPos(vec2(-padding, -padding), ImGuiCond_Once);
2340 ImGui::SetNextWindowSize(vec2(width + 2 * padding, height + 2 * padding), ImGuiCond_Always);
2341 ImGui::Begin("WndMain", nullptr,
2342 ImGuiWindowFlags_NoTitleBar |
2343 ImGuiWindowFlags_NoResize |
2344 ImGuiWindowFlags_NoMove);
2345
2346 ButtonImGui btn("New Game");
2347
2348 ImGui::InvisibleButton("", vec2(10, height / 6));
2349 if (btn.draw((width - btn.getWidth()) / 2)) {
2350 goToScreen(&VulkanGame::renderGameScreen);
2351 }
2352
2353 ButtonImGui btn2("Quit");
2354
2355 ImGui::InvisibleButton("", vec2(10, 15));
2356 if (btn2.draw((width - btn2.getWidth()) / 2)) {
2357 quitGame();
2358 }
2359
2360 ImGui::End();
2361 }
2362}
2363
2364void VulkanGame::renderGameScreen(int width, int height) {
2365 {
2366 ImGui::SetNextWindowSize(vec2(130, 65), ImGuiCond_Once);
2367 ImGui::SetNextWindowPos(vec2(10, 50), ImGuiCond_Once);
2368 ImGui::Begin("WndStats", nullptr,
2369 ImGuiWindowFlags_NoTitleBar |
2370 ImGuiWindowFlags_NoResize |
2371 ImGuiWindowFlags_NoMove);
2372
2373 //ImGui::Text(ImGui::GetIO().Framerate);
2374 renderGuiValueList(valueLists["stats value list"]);
2375
2376 ImGui::End();
2377 }
2378
2379 {
2380 ImGui::SetNextWindowSize(vec2(250, 35), ImGuiCond_Once);
2381 ImGui::SetNextWindowPos(vec2(width - 260, 10), ImGuiCond_Always);
2382 ImGui::Begin("WndMenubar", nullptr,
2383 ImGuiWindowFlags_NoTitleBar |
2384 ImGuiWindowFlags_NoResize |
2385 ImGuiWindowFlags_NoMove);
2386 ImGui::InvisibleButton("", vec2(155, 18));
2387 ImGui::SameLine();
2388 if (ImGui::Button("Main Menu")) {
2389 goToScreen(&VulkanGame::renderMainScreen);
2390 }
2391 ImGui::End();
2392 }
2393
2394 {
2395 ImGui::SetNextWindowSize(vec2(200, 200), ImGuiCond_Once);
2396 ImGui::SetNextWindowPos(vec2(width - 210, 60), ImGuiCond_Always);
2397 ImGui::Begin("WndDebug", nullptr,
2398 ImGuiWindowFlags_NoTitleBar |
2399 ImGuiWindowFlags_NoResize |
2400 ImGuiWindowFlags_NoMove);
2401
2402 renderGuiValueList(valueLists["debug value list"]);
2403
2404 ImGui::End();
2405 }
2406}
2407
2408void VulkanGame::initGuiValueLists(map<string, vector<UIValue>>& valueLists) {
2409 valueLists["stats value list"] = vector<UIValue>();
2410 valueLists["debug value list"] = vector<UIValue>();
2411}
2412
2413// TODO: Probably turn this into a UI widget class
2414void VulkanGame::renderGuiValueList(vector<UIValue>& values) {
2415 float maxWidth = 0.0f;
2416 float cursorStartPos = ImGui::GetCursorPosX();
2417
2418 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
2419 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
2420
2421 if (maxWidth < textWidth)
2422 maxWidth = textWidth;
2423 }
2424
2425 stringstream ss;
2426
2427 // TODO: Possibly implement this based on gui/ui-value.hpp instead and use templates
2428 // to keep track of the type. This should make it a bit easier to use and maintain
2429 // Also, implement this in a way that's agnostic to the UI renderer.
2430 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
2431 ss.str("");
2432 ss.clear();
2433
2434 switch (it->type) {
2435 case UIVALUE_INT:
2436 ss << it->label << ": " << *(unsigned int*)it->value;
2437 break;
2438 case UIVALUE_DOUBLE:
2439 ss << it->label << ": " << *(double*)it->value;
2440 break;
2441 }
2442
2443 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
2444
2445 ImGui::SetCursorPosX(cursorStartPos + maxWidth - textWidth);
2446 //ImGui::Text("%s", ss.str().c_str());
2447 ImGui::Text("%s: %.1f", it->label.c_str(), *(float*)it->value);
2448 }
2449}
2450
2451void VulkanGame::goToScreen(void (VulkanGame::* renderScreenFn)(int width, int height)) {
2452 currentRenderScreenFn = renderScreenFn;
2453
2454 // TODO: Maybe just set shouldRecreateSwapChain to true instead. Check this render loop logic
2455 // to make sure there'd be no issues
2456 //recreateSwapChain();
2457}
2458
2459void VulkanGame::quitGame() {
2460 done = true;
2461}
Note: See TracBrowser for help on using the repository browser.