source: opengl-game/vulkan-game.cpp@ aa7e5f0

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

Rename the scene.* shaders to model.*

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