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

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

Cleanup VulkanGame code

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