source: opengl-game/vulkan-game.cpp@ 67527a5

feature/imgui-sdl
Last change on this file since 67527a5 was 67527a5, checked in by Dmitry Portnoy <dportnoy@…>, 14 months ago

Switch all per-object buffers to be dynamic uniform buffers instead of shader storage buffers

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