source: opengl-game/vulkan-game.cpp@ 85b5fec

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

Use the new UI system in SDLGame as well

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