source: opengl-game/vulkan-game.cpp@ 301c90a

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

Implement the start of a generic UI system built on top of IMGUI, which can reposition elements when the screen is resized, and use it in VulkanGame

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