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

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

Add some functionality to VulkanBuffer, and modify VulkanGame::addObject() to call VulkanBuffer::add()

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