source: opengl-game/sdl-game.cpp@ bb76950

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

Change VulkanGame::resizeBufferSet() to take a buffer size instead of an entire
VulkanBuffer object, and to not update descriptor sets, which obviates the need
to pass in a GraphicsPipeline_Vulkan object. Descriptor set updates must
now be handled separetly.

  • Property mode set to 100644
File size: 56.1 KB
Line 
1#include "sdl-game.hpp"
2
3#include <array>
4#include <iostream>
5#include <set>
6
7#include "IMGUI/imgui_impl_sdl.h"
8
9#include "logger.hpp"
10#include "utils.hpp"
11
12#include "gui/imgui/button-imgui.hpp"
13
14using namespace std;
15
16#define IMGUI_UNLIMITED_FRAME_RATE
17
18static void check_imgui_vk_result(VkResult res) {
19 if (res == VK_SUCCESS) {
20 return;
21 }
22
23 ostringstream oss;
24 oss << "[imgui] Vulkan error! VkResult is \"" << VulkanUtils::resultString(res) << "\"" << __LINE__;
25 if (res < 0) {
26 throw runtime_error("Fatal: " + oss.str());
27 } else {
28 cerr << oss.str();
29 }
30}
31
32VKAPI_ATTR VkBool32 VKAPI_CALL VulkanGame::debugCallback(
33 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
34 VkDebugUtilsMessageTypeFlagsEXT messageType,
35 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
36 void* pUserData) {
37 cerr << "validation layer: " << pCallbackData->pMessage << endl;
38
39 // TODO: Figure out what the return value means and if it should always be VK_FALSE
40 return VK_FALSE;
41}
42
43VulkanGame::VulkanGame()
44 : swapChainImageCount(0)
45 , swapChainMinImageCount(0)
46 , swapChainSurfaceFormat({})
47 , swapChainPresentMode(VK_PRESENT_MODE_MAX_ENUM_KHR)
48 , swapChainExtent{ 0, 0 }
49 , swapChain(VK_NULL_HANDLE)
50 , vulkanSurface(VK_NULL_HANDLE)
51 , sdlVersion({ 0, 0, 0 })
52 , instance(VK_NULL_HANDLE)
53 , physicalDevice(VK_NULL_HANDLE)
54 , device(VK_NULL_HANDLE)
55 , debugMessenger(VK_NULL_HANDLE)
56 , resourceCommandPool(VK_NULL_HANDLE)
57 , renderPass(VK_NULL_HANDLE)
58 , graphicsQueue(VK_NULL_HANDLE)
59 , presentQueue(VK_NULL_HANDLE)
60 , depthImage({})
61 , shouldRecreateSwapChain(false)
62 , frameCount(0)
63 , currentFrame(0)
64 , imageIndex(0)
65 , fpsStartTime(0.0f)
66 , curTime(0.0f)
67 , done(false)
68 , currentRenderScreenFn(nullptr)
69 , imguiDescriptorPool(VK_NULL_HANDLE)
70 , gui(nullptr)
71 , window(nullptr)
72 , objects_modelPipeline()
73 , score(0)
74 , fps(0.0f) {
75}
76
77VulkanGame::~VulkanGame() {
78}
79
80void VulkanGame::run(int width, int height, unsigned char guiFlags) {
81 cout << "Vulkan Game" << endl;
82
83 cout << "DEBUGGING IS " << (ENABLE_VALIDATION_LAYERS ? "ON" : "OFF") << endl;
84
85 if (initUI(width, height, guiFlags) == RTWO_ERROR) {
86 return;
87 }
88
89 initVulkan();
90
91 VkPhysicalDeviceProperties deviceProperties;
92 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
93
94 objects_modelPipeline = VulkanBuffer<SSBO_ModelObject>(10, deviceProperties.limits.minUniformBufferOffsetAlignment);
95
96 initImGuiOverlay();
97
98 // TODO: Figure out how much of ubo creation and associated variables should be in the pipeline class
99 // Maybe combine the ubo-related objects into a new class
100
101 initGraphicsPipelines();
102
103 initMatrices();
104
105 modelPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::pos));
106 modelPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::color));
107 modelPipeline.addAttribute(VK_FORMAT_R32G32_SFLOAT, offset_of(&ModelVertex::texCoord));
108 modelPipeline.addAttribute(VK_FORMAT_R32G32B32_SFLOAT, offset_of(&ModelVertex::normal));
109 modelPipeline.addAttribute(VK_FORMAT_R32_UINT, offset_of(&ModelVertex::objIndex));
110
111 createBufferSet(sizeof(UBO_VP_mats),
112 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
113 uniformBuffers_modelPipeline);
114
115 modelPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
116 VK_SHADER_STAGE_VERTEX_BIT, &uniformBuffers_modelPipeline.infoSet);
117 modelPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
118 VK_SHADER_STAGE_VERTEX_BIT, &storageBuffers_modelPipeline.infoSet);
119 modelPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
120 VK_SHADER_STAGE_FRAGMENT_BIT, &floorTextureImageDescriptor);
121
122 SceneObject<ModelVertex, SSBO_ModelObject>* texturedSquare = nullptr;
123
124 // TODO: Ideally, avoid having to make the squares as modified upon creation
125
126 texturedSquare = &addObject(modelObjects, modelPipeline,
127 addObjectIndex<ModelVertex>(modelObjects.size(),
128 addVertexNormals<ModelVertex>({
129 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, 0},
130 {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, 0},
131 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, 0},
132 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, 0},
133 {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, 0},
134 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, 0}
135 })),
136 {
137 0, 1, 2, 3, 4, 5
138 }, objects_modelPipeline, {
139 mat4(1.0f)
140 });
141
142 texturedSquare->model_base =
143 translate(mat4(1.0f), vec3(0.0f, 0.0f, -2.0f));
144 texturedSquare->modified = true;
145
146 texturedSquare = &addObject(modelObjects, modelPipeline,
147 addObjectIndex<ModelVertex>(modelObjects.size(),
148 addVertexNormals<ModelVertex>({
149 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}},
150 {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}},
151 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
152 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
153 {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}},
154 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}}
155 })), {
156 0, 1, 2, 3, 4, 5
157 }, objects_modelPipeline, {
158 mat4(1.0f)
159 });
160
161 texturedSquare->model_base =
162 translate(mat4(1.0f), vec3(0.0f, 0.0f, -1.5f));
163 texturedSquare->modified = true;
164
165 modelPipeline.createDescriptorSetLayout();
166 modelPipeline.createPipeline("shaders/model-vert.spv", "shaders/model-frag.spv");
167 modelPipeline.createDescriptorPool(swapChainImages.size());
168 modelPipeline.createDescriptorSets(swapChainImages.size());
169
170 currentRenderScreenFn = &VulkanGame::renderMainScreen;
171
172 ImGuiIO& io = ImGui::GetIO();
173
174 initGuiValueLists(valueLists);
175
176 valueLists["stats value list"].push_back(UIValue(UIVALUE_INT, "Score", &score));
177 valueLists["stats value list"].push_back(UIValue(UIVALUE_DOUBLE, "FPS", &fps));
178 valueLists["stats value list"].push_back(UIValue(UIVALUE_DOUBLE, "IMGUI FPS", &io.Framerate));
179
180 renderLoop();
181 cleanup();
182
183 close_log();
184}
185
186bool VulkanGame::initUI(int width, int height, unsigned char guiFlags) {
187 // TODO: Create a game-gui function to get the gui version and retrieve it that way
188
189 SDL_VERSION(&sdlVersion); // This gets the compile-time version
190 SDL_GetVersion(&sdlVersion); // This gets the runtime version
191
192 cout << "SDL " <<
193 to_string(sdlVersion.major) << "." <<
194 to_string(sdlVersion.minor) << "." <<
195 to_string(sdlVersion.patch) << endl;
196
197 // TODO: Refactor the logger api to be more flexible,
198 // esp. since gl_log() and gl_log_err() have issues printing anything besides strings
199 restart_gl_log();
200 gl_log("starting SDL\n%s.%s.%s",
201 to_string(sdlVersion.major).c_str(),
202 to_string(sdlVersion.minor).c_str(),
203 to_string(sdlVersion.patch).c_str());
204
205 // TODO: Use open_Log() and related functions instead of gl_log ones
206 // TODO: In addition, delete the gl_log functions
207 open_log();
208 get_log() << "starting SDL" << endl;
209 get_log() <<
210 (int)sdlVersion.major << "." <<
211 (int)sdlVersion.minor << "." <<
212 (int)sdlVersion.patch << endl;
213
214 // TODO: Put all fonts, textures, and images in the assets folder
215 gui = new GameGui_SDL();
216
217 if (gui->init() == RTWO_ERROR) {
218 // TODO: Also print these sorts of errors to the log
219 cout << "UI library could not be initialized!" << endl;
220 cout << gui->getError() << endl;
221 // TODO: Rename RTWO_ERROR to something else
222 return RTWO_ERROR;
223 }
224
225 window = (SDL_Window*)gui->createWindow("Vulkan Game", width, height, guiFlags & GUI_FLAGS_WINDOW_FULLSCREEN);
226 if (window == nullptr) {
227 cout << "Window could not be created!" << endl;
228 cout << gui->getError() << endl;
229 return RTWO_ERROR;
230 }
231
232 cout << "Target window size: (" << width << ", " << height << ")" << endl;
233 cout << "Actual window size: (" << gui->getWindowWidth() << ", " << gui->getWindowHeight() << ")" << endl;
234
235 return RTWO_SUCCESS;
236}
237
238void VulkanGame::initVulkan() {
239 const vector<const char*> validationLayers = {
240 "VK_LAYER_KHRONOS_validation"
241 };
242 const vector<const char*> deviceExtensions = {
243 VK_KHR_SWAPCHAIN_EXTENSION_NAME
244 };
245
246 createVulkanInstance(validationLayers);
247 setupDebugMessenger();
248 createVulkanSurface();
249 pickPhysicalDevice(deviceExtensions);
250 createLogicalDevice(validationLayers, deviceExtensions);
251 chooseSwapChainProperties();
252 createSwapChain();
253 createImageViews();
254
255 createResourceCommandPool();
256 createImageResources();
257
258 createRenderPass();
259 createCommandPools();
260 createFramebuffers();
261 createCommandBuffers();
262 createSyncObjects();
263}
264
265void VulkanGame::initGraphicsPipelines() {
266 modelPipeline = GraphicsPipeline_Vulkan<ModelVertex>(
267 VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
268 { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, 16, 24);
269
270 createBufferSet(objects_modelPipeline.capacity * sizeof(SSBO_ModelObject),
271 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT
272 | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
273 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
274 storageBuffers_modelPipeline);
275}
276
277// TODO: Maybe change the name to initScene() or something similar
278void VulkanGame::initMatrices() {
279 cam_pos = vec3(0.0f, 0.0f, 2.0f);
280
281 float cam_yaw = 0.0f;
282 float cam_pitch = -50.0f;
283
284 mat4 yaw_mat = rotate(mat4(1.0f), radians(-cam_yaw), vec3(0.0f, 1.0f, 0.0f));
285 mat4 pitch_mat = rotate(mat4(1.0f), radians(-cam_pitch), vec3(1.0f, 0.0f, 0.0f));
286
287 mat4 R_view = pitch_mat * yaw_mat;
288 mat4 T_view = translate(mat4(1.0f), vec3(-cam_pos.x, -cam_pos.y, -cam_pos.z));
289 viewMat = R_view * T_view;
290
291 projMat = perspective(radians(FOV_ANGLE), (float)swapChainExtent.width / (float)swapChainExtent.height, NEAR_CLIP, FAR_CLIP);
292 projMat[1][1] *= -1; // flip the y-axis so that +y is up
293
294 object_VP_mats.view = viewMat;
295 object_VP_mats.proj = projMat;
296}
297
298void VulkanGame::renderLoop() {
299 startTime = steady_clock::now();
300 curTime = duration<float, seconds::period>(steady_clock::now() - startTime).count();
301
302 fpsStartTime = curTime;
303 frameCount = 0;
304
305 ImGuiIO& io = ImGui::GetIO();
306
307 done = false;
308 while (!done) {
309
310 prevTime = curTime;
311 curTime = duration<float, seconds::period>(steady_clock::now() - startTime).count();
312 elapsedTime = curTime - prevTime;
313
314 if (curTime - fpsStartTime >= 1.0f) {
315 fps = (float)frameCount / (curTime - fpsStartTime);
316
317 frameCount = 0;
318 fpsStartTime = curTime;
319 }
320
321 frameCount++;
322
323 gui->processEvents();
324
325 UIEvent uiEvent;
326 while (gui->pollEvent(&uiEvent)) {
327 GameEvent& e = uiEvent.event;
328 SDL_Event sdlEvent = uiEvent.rawEvent.sdl;
329
330 ImGui_ImplSDL2_ProcessEvent(&sdlEvent);
331 if ((e.type == UI_EVENT_MOUSEBUTTONDOWN || e.type == UI_EVENT_MOUSEBUTTONUP || e.type == UI_EVENT_UNKNOWN) &&
332 io.WantCaptureMouse) {
333 if (sdlEvent.type == SDL_MOUSEWHEEL || sdlEvent.type == SDL_MOUSEBUTTONDOWN ||
334 sdlEvent.type == SDL_MOUSEBUTTONUP) {
335 continue;
336 }
337 }
338 if ((e.type == UI_EVENT_KEYDOWN || e.type == UI_EVENT_KEYUP) && io.WantCaptureKeyboard) {
339 if (sdlEvent.type == SDL_KEYDOWN || sdlEvent.type == SDL_KEYUP) {
340 continue;
341 }
342 }
343 if (io.WantTextInput) {
344 // show onscreen keyboard if on mobile
345 }
346
347 switch (e.type) {
348 case UI_EVENT_QUIT:
349 cout << "Quit event detected" << endl;
350 done = true;
351 break;
352 case UI_EVENT_WINDOWRESIZE:
353 cout << "Window resize event detected" << endl;
354 shouldRecreateSwapChain = true;
355 break;
356 case UI_EVENT_KEYDOWN:
357 if (e.key.repeat) {
358 break;
359 }
360
361 if (e.key.keycode == SDL_SCANCODE_ESCAPE) {
362 done = true;
363 } else if (e.key.keycode == SDL_SCANCODE_SPACE) {
364 cout << "Adding a plane" << endl;
365 float zOffset = -2.0f + (0.5f * modelObjects.size());
366
367 SceneObject<ModelVertex, SSBO_ModelObject>& texturedSquare =
368 addObject(modelObjects, modelPipeline,
369 addObjectIndex<ModelVertex>(modelObjects.size(),
370 addVertexNormals<ModelVertex>({
371 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, 0},
372 {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, 0},
373 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, 0},
374 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, 0},
375 {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, 0},
376 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, 0}
377 })),
378 {
379 0, 1, 2, 3, 4, 5
380 }, objects_modelPipeline, {
381 mat4(1.0f)
382 });
383
384 texturedSquare.model_base =
385 translate(mat4(1.0f), vec3(0.0f, 0.0f, zOffset));
386 texturedSquare.modified = true;
387 // START UNREVIEWED SECTION
388 // END UNREVIEWED SECTION
389 } else {
390 cout << "Key event detected" << endl;
391 }
392 break;
393 case UI_EVENT_KEYUP:
394 // START UNREVIEWED SECTION
395 // END UNREVIEWED SECTION
396 break;
397 case UI_EVENT_WINDOW:
398 case UI_EVENT_MOUSEBUTTONDOWN:
399 case UI_EVENT_MOUSEBUTTONUP:
400 case UI_EVENT_MOUSEMOTION:
401 break;
402 case UI_EVENT_UNHANDLED:
403 cout << "Unhandled event type: 0x" << hex << sdlEvent.type << dec << endl;
404 break;
405 case UI_EVENT_UNKNOWN:
406 default:
407 cout << "Unknown event type: 0x" << hex << sdlEvent.type << dec << endl;
408 break;
409 }
410 }
411
412 if (shouldRecreateSwapChain) {
413 gui->refreshWindowSize();
414 const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
415
416 if (!isMinimized) {
417 // TODO: This should be used if the min image count changes, presumably because a new surface was created
418 // with a different image count or something like that. Maybe I want to add code to query for a new min image count
419 // during swapchain recreation to take advantage of this
420 ImGui_ImplVulkan_SetMinImageCount(swapChainMinImageCount);
421
422 recreateSwapChain();
423
424 shouldRecreateSwapChain = false;
425 }
426 }
427
428 updateScene();
429
430 // TODO: Move this into a renderImGuiOverlay() function
431 ImGui_ImplVulkan_NewFrame();
432 ImGui_ImplSDL2_NewFrame(window);
433 ImGui::NewFrame();
434
435 (this->*currentRenderScreenFn)(gui->getWindowWidth(), gui->getWindowHeight());
436
437 ImGui::Render();
438
439 gui->refreshWindowSize();
440 const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
441
442 if (!isMinimized) {
443 renderFrame(ImGui::GetDrawData());
444 presentFrame();
445 }
446 }
447}
448
449void VulkanGame::updateScene() {
450 // Rotate the textured squares
451 for (SceneObject<ModelVertex, SSBO_ModelObject>& model : this->modelObjects) {
452 model.model_transform =
453 translate(mat4(1.0f), vec3(0.0f, -2.0f, -0.0f)) *
454 rotate(mat4(1.0f), curTime * radians(90.0f), vec3(0.0f, 0.0f, 1.0f));
455 model.modified = true;
456 }
457
458 // TODO: Probably move the resizing to the VulkanBuffer class
459 if (objects_modelPipeline.resized) {
460 // TODO: Also resize the dynamic ubo
461 resizeBufferSet(storageBuffers_modelPipeline, objects_modelPipeline.capacity * sizeof(SSBO_ModelObject),
462 resourceCommandPool, graphicsQueue, true);
463
464 objects_modelPipeline.resize();
465
466 modelPipeline.updateDescriptorInfo(1, &storageBuffers_modelPipeline.infoSet, swapChainImages.size());
467 }
468
469 for (size_t i = 0; i < modelObjects.size(); i++) {
470 if (modelObjects[i].modified) {
471 updateObject(modelObjects[i]);
472 updateBufferSet(storageBuffers_modelPipeline, i, modelObjects[i].ssbo);
473 }
474 }
475
476 VulkanUtils::copyDataToMemory(device, &object_VP_mats, uniformBuffers_modelPipeline.memory[imageIndex], 0,
477 sizeof(object_VP_mats), false);
478}
479
480void VulkanGame::cleanup() {
481 // FIXME: We could wait on the Queue if we had the queue in wd-> (otherwise VulkanH functions can't use globals)
482 //vkQueueWaitIdle(g_Queue);
483 VKUTIL_CHECK_RESULT(vkDeviceWaitIdle(device), "failed to wait for device!");
484
485 cleanupImGuiOverlay();
486
487 cleanupSwapChain();
488
489 VulkanUtils::destroyVulkanImage(device, floorTextureImage);
490 // START UNREVIEWED SECTION
491
492 vkDestroySampler(device, textureSampler, nullptr);
493
494 modelPipeline.cleanupBuffers();
495
496 for (size_t i = 0; i < storageBuffers_modelPipeline.buffers.size(); i++) {
497 vkDestroyBuffer(device, storageBuffers_modelPipeline.buffers[i], nullptr);
498 vkFreeMemory(device, storageBuffers_modelPipeline.memory[i], nullptr);
499 }
500
501 // END UNREVIEWED SECTION
502
503 vkDestroyCommandPool(device, resourceCommandPool, nullptr);
504
505 vkDestroyDevice(device, nullptr);
506 vkDestroySurfaceKHR(instance, vulkanSurface, nullptr);
507
508 if (ENABLE_VALIDATION_LAYERS) {
509 VulkanUtils::destroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
510 }
511
512 vkDestroyInstance(instance, nullptr);
513
514 gui->destroyWindow();
515 gui->shutdown();
516 delete gui;
517}
518
519void VulkanGame::createVulkanInstance(const vector<const char*>& validationLayers) {
520 if (ENABLE_VALIDATION_LAYERS && !VulkanUtils::checkValidationLayerSupport(validationLayers)) {
521 throw runtime_error("validation layers requested, but not available!");
522 }
523
524 VkApplicationInfo appInfo = {};
525 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
526 appInfo.pApplicationName = "Vulkan Game";
527 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
528 appInfo.pEngineName = "No Engine";
529 appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
530 appInfo.apiVersion = VK_API_VERSION_1_0;
531
532 VkInstanceCreateInfo createInfo = {};
533 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
534 createInfo.pApplicationInfo = &appInfo;
535
536 vector<const char*> extensions = gui->getRequiredExtensions();
537 if (ENABLE_VALIDATION_LAYERS) {
538 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
539 }
540
541 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
542 createInfo.ppEnabledExtensionNames = extensions.data();
543
544 cout << endl << "Extensions:" << endl;
545 for (const char* extensionName : extensions) {
546 cout << extensionName << endl;
547 }
548 cout << endl;
549
550 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
551 if (ENABLE_VALIDATION_LAYERS) {
552 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
553 createInfo.ppEnabledLayerNames = validationLayers.data();
554
555 populateDebugMessengerCreateInfo(debugCreateInfo);
556 createInfo.pNext = &debugCreateInfo;
557 }
558 else {
559 createInfo.enabledLayerCount = 0;
560
561 createInfo.pNext = nullptr;
562 }
563
564 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
565 throw runtime_error("failed to create instance!");
566 }
567}
568
569void VulkanGame::setupDebugMessenger() {
570 if (!ENABLE_VALIDATION_LAYERS) {
571 return;
572 }
573
574 VkDebugUtilsMessengerCreateInfoEXT createInfo;
575 populateDebugMessengerCreateInfo(createInfo);
576
577 if (VulkanUtils::createDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
578 throw runtime_error("failed to set up debug messenger!");
579 }
580}
581
582void VulkanGame::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
583 createInfo = {};
584 createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
585 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;
586 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;
587 createInfo.pfnUserCallback = debugCallback;
588}
589
590void VulkanGame::createVulkanSurface() {
591 if (gui->createVulkanSurface(instance, &vulkanSurface) == RTWO_ERROR) {
592 throw runtime_error("failed to create window surface!");
593 }
594}
595
596void VulkanGame::pickPhysicalDevice(const vector<const char*>& deviceExtensions) {
597 uint32_t deviceCount = 0;
598 // TODO: Check VkResult
599 vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
600
601 if (deviceCount == 0) {
602 throw runtime_error("failed to find GPUs with Vulkan support!");
603 }
604
605 vector<VkPhysicalDevice> devices(deviceCount);
606 // TODO: Check VkResult
607 vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
608
609 cout << endl << "Graphics cards:" << endl;
610 for (const VkPhysicalDevice& device : devices) {
611 if (isDeviceSuitable(device, deviceExtensions)) {
612 physicalDevice = device;
613 break;
614 }
615 }
616 cout << endl;
617
618 if (physicalDevice == VK_NULL_HANDLE) {
619 throw runtime_error("failed to find a suitable GPU!");
620 }
621}
622
623bool VulkanGame::isDeviceSuitable(VkPhysicalDevice physicalDevice, const vector<const char*>& deviceExtensions) {
624 VkPhysicalDeviceProperties deviceProperties;
625 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
626
627 cout << "Device: " << deviceProperties.deviceName << endl;
628
629 // TODO: Eventually, maybe let the user pick out of a set of GPUs in case the user does want to use
630 // an integrated GPU. On my laptop, this function returns TRUE for the integrated GPU, but crashes
631 // when trying to use it to render. Maybe I just need to figure out which other extensions and features
632 // to check.
633 if (deviceProperties.deviceType != VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
634 return false;
635 }
636
637 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
638 bool extensionsSupported = VulkanUtils::checkDeviceExtensionSupport(physicalDevice, deviceExtensions);
639 bool swapChainAdequate = false;
640
641 if (extensionsSupported) {
642 vector<VkSurfaceFormatKHR> formats = VulkanUtils::querySwapChainFormats(physicalDevice, vulkanSurface);
643 vector<VkPresentModeKHR> presentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, vulkanSurface);
644
645 swapChainAdequate = !formats.empty() && !presentModes.empty();
646 }
647
648 VkPhysicalDeviceFeatures supportedFeatures;
649 vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
650
651 return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
652}
653
654void VulkanGame::createLogicalDevice(const vector<const char*>& validationLayers,
655 const vector<const char*>& deviceExtensions) {
656 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
657
658 if (!indices.isComplete()) {
659 throw runtime_error("failed to find required queue families!");
660 }
661
662 // TODO: Using separate graphics and present queues currently works, but I should verify that I'm
663 // using them correctly to get the most benefit out of separate queues
664
665 vector<VkDeviceQueueCreateInfo> queueCreateInfoList;
666 set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
667
668 float queuePriority = 1.0f;
669 for (uint32_t queueFamily : uniqueQueueFamilies) {
670 VkDeviceQueueCreateInfo queueCreateInfo = {};
671 queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
672 queueCreateInfo.queueCount = 1;
673 queueCreateInfo.queueFamilyIndex = queueFamily;
674 queueCreateInfo.pQueuePriorities = &queuePriority;
675
676 queueCreateInfoList.push_back(queueCreateInfo);
677 }
678
679 VkPhysicalDeviceFeatures deviceFeatures = {};
680 deviceFeatures.samplerAnisotropy = VK_TRUE;
681
682 VkDeviceCreateInfo createInfo = {};
683 createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
684
685 createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfoList.size());
686 createInfo.pQueueCreateInfos = queueCreateInfoList.data();
687
688 createInfo.pEnabledFeatures = &deviceFeatures;
689
690 createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
691 createInfo.ppEnabledExtensionNames = deviceExtensions.data();
692
693 // These fields are ignored by up-to-date Vulkan implementations,
694 // but it's a good idea to set them for backwards compatibility
695 if (ENABLE_VALIDATION_LAYERS) {
696 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
697 createInfo.ppEnabledLayerNames = validationLayers.data();
698 }
699 else {
700 createInfo.enabledLayerCount = 0;
701 }
702
703 if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
704 throw runtime_error("failed to create logical device!");
705 }
706
707 vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
708 vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
709}
710
711void VulkanGame::chooseSwapChainProperties() {
712 vector<VkSurfaceFormatKHR> availableFormats = VulkanUtils::querySwapChainFormats(physicalDevice, vulkanSurface);
713 vector<VkPresentModeKHR> availablePresentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, vulkanSurface);
714
715 // Per Spec Format and View Format are expected to be the same unless VK_IMAGE_CREATE_MUTABLE_BIT was set at image creation
716 // Assuming that the default behavior is without setting this bit, there is no need for separate Swapchain image and image view format
717 // Additionally several new color spaces were introduced with Vulkan Spec v1.0.40,
718 // hence we must make sure that a format with the mostly available color space, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, is found and used.
719 swapChainSurfaceFormat = VulkanUtils::chooseSwapSurfaceFormat(availableFormats,
720 { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM },
721 VK_COLOR_SPACE_SRGB_NONLINEAR_KHR);
722
723#ifdef IMGUI_UNLIMITED_FRAME_RATE
724 vector<VkPresentModeKHR> presentModes{
725 VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR
726 };
727#else
728 vector<VkPresentModeKHR> presentModes{ VK_PRESENT_MODE_FIFO_KHR };
729#endif
730
731 swapChainPresentMode = VulkanUtils::chooseSwapPresentMode(availablePresentModes, presentModes);
732
733 cout << "[vulkan] Selected PresentMode = " << swapChainPresentMode << endl;
734
735 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, vulkanSurface);
736
737 // If min image count was not specified, request different count of images dependent on selected present mode
738 if (swapChainMinImageCount == 0) {
739 if (swapChainPresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
740 swapChainMinImageCount = 3;
741 }
742 else if (swapChainPresentMode == VK_PRESENT_MODE_FIFO_KHR || swapChainPresentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) {
743 swapChainMinImageCount = 2;
744 }
745 else if (swapChainPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
746 swapChainMinImageCount = 1;
747 }
748 else {
749 throw runtime_error("unexpected present mode!");
750 }
751 }
752
753 if (swapChainMinImageCount < capabilities.minImageCount) {
754 swapChainMinImageCount = capabilities.minImageCount;
755 }
756 else if (capabilities.maxImageCount != 0 && swapChainMinImageCount > capabilities.maxImageCount) {
757 swapChainMinImageCount = capabilities.maxImageCount;
758 }
759}
760
761void VulkanGame::createSwapChain() {
762 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, vulkanSurface);
763
764 swapChainExtent = VulkanUtils::chooseSwapExtent(capabilities, gui->getWindowWidth(), gui->getWindowHeight());
765
766 VkSwapchainCreateInfoKHR createInfo = {};
767 createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
768 createInfo.surface = vulkanSurface;
769 createInfo.minImageCount = swapChainMinImageCount;
770 createInfo.imageFormat = swapChainSurfaceFormat.format;
771 createInfo.imageColorSpace = swapChainSurfaceFormat.colorSpace;
772 createInfo.imageExtent = swapChainExtent;
773 createInfo.imageArrayLayers = 1;
774 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
775
776 // TODO: Maybe save this result so I don't have to recalculate it every time
777 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
778 uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
779
780 if (indices.graphicsFamily != indices.presentFamily) {
781 createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
782 createInfo.queueFamilyIndexCount = 2;
783 createInfo.pQueueFamilyIndices = queueFamilyIndices;
784 }
785 else {
786 createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
787 createInfo.queueFamilyIndexCount = 0;
788 createInfo.pQueueFamilyIndices = nullptr;
789 }
790
791 createInfo.preTransform = capabilities.currentTransform;
792 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
793 createInfo.presentMode = swapChainPresentMode;
794 createInfo.clipped = VK_TRUE;
795 createInfo.oldSwapchain = VK_NULL_HANDLE;
796
797 if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
798 throw runtime_error("failed to create swap chain!");
799 }
800
801 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, nullptr) != VK_SUCCESS) {
802 throw runtime_error("failed to get swap chain image count!");
803 }
804
805 swapChainImages.resize(swapChainImageCount);
806 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, swapChainImages.data()) != VK_SUCCESS) {
807 throw runtime_error("failed to get swap chain images!");
808 }
809}
810
811void VulkanGame::createImageViews() {
812 swapChainImageViews.resize(swapChainImageCount);
813
814 for (uint32_t i = 0; i < swapChainImageViews.size(); i++) {
815 swapChainImageViews[i] = VulkanUtils::createImageView(device, swapChainImages[i], swapChainSurfaceFormat.format,
816 VK_IMAGE_ASPECT_COLOR_BIT);
817 }
818}
819
820void VulkanGame::createResourceCommandPool() {
821 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
822
823 VkCommandPoolCreateInfo poolInfo = {};
824 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
825 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
826 poolInfo.flags = 0;
827
828 if (vkCreateCommandPool(device, &poolInfo, nullptr, &resourceCommandPool) != VK_SUCCESS) {
829 throw runtime_error("failed to create resource command pool!");
830 }
831}
832
833void VulkanGame::createImageResources() {
834 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
835 depthImage, graphicsQueue);
836
837 createTextureSampler();
838
839 // TODO: Move all images/textures somewhere into the assets folder
840
841 VulkanUtils::createVulkanImageFromFile(device, physicalDevice, resourceCommandPool, "textures/texture.jpg",
842 floorTextureImage, graphicsQueue);
843
844 floorTextureImageDescriptor = {};
845 floorTextureImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
846 floorTextureImageDescriptor.imageView = floorTextureImage.imageView;
847 floorTextureImageDescriptor.sampler = textureSampler;
848}
849
850VkFormat VulkanGame::findDepthFormat() {
851 return VulkanUtils::findSupportedFormat(
852 physicalDevice,
853 { VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D32_SFLOAT, VK_FORMAT_D24_UNORM_S8_UINT },
854 VK_IMAGE_TILING_OPTIMAL,
855 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
856 );
857}
858
859void VulkanGame::createRenderPass() {
860 VkAttachmentDescription colorAttachment = {};
861 colorAttachment.format = swapChainSurfaceFormat.format;
862 colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
863 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // Set to VK_ATTACHMENT_LOAD_OP_DONT_CARE to disable clearing
864 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
865 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
866 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
867 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
868 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
869
870 VkAttachmentReference colorAttachmentRef = {};
871 colorAttachmentRef.attachment = 0;
872 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
873
874 VkAttachmentDescription depthAttachment = {};
875 depthAttachment.format = findDepthFormat();
876 depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
877 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
878 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
879 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
880 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
881 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
882 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
883
884 VkAttachmentReference depthAttachmentRef = {};
885 depthAttachmentRef.attachment = 1;
886 depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
887
888 VkSubpassDescription subpass = {};
889 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
890 subpass.colorAttachmentCount = 1;
891 subpass.pColorAttachments = &colorAttachmentRef;
892 //subpass.pDepthStencilAttachment = &depthAttachmentRef;
893
894 VkSubpassDependency dependency = {};
895 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
896 dependency.dstSubpass = 0;
897 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
898 dependency.srcAccessMask = 0;
899 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
900 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
901
902 array<VkAttachmentDescription, 2> attachments = { colorAttachment, depthAttachment };
903 VkRenderPassCreateInfo renderPassInfo = {};
904 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
905 renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
906 renderPassInfo.pAttachments = attachments.data();
907 renderPassInfo.subpassCount = 1;
908 renderPassInfo.pSubpasses = &subpass;
909 renderPassInfo.dependencyCount = 1;
910 renderPassInfo.pDependencies = &dependency;
911
912 if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
913 throw runtime_error("failed to create render pass!");
914 }
915
916 // We do not create a pipeline by default as this is also used by examples' main.cpp,
917 // but secondary viewport in multi-viewport mode may want to create one with:
918 //ImGui_ImplVulkan_CreatePipeline(device, g_Allocator, VK_NULL_HANDLE, g_MainWindowData.RenderPass, VK_SAMPLE_COUNT_1_BIT, &g_MainWindowData.Pipeline);
919}
920
921void VulkanGame::createCommandPools() {
922 commandPools.resize(swapChainImageCount);
923
924 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
925
926 for (size_t i = 0; i < swapChainImageCount; i++) {
927 VkCommandPoolCreateInfo poolInfo = {};
928 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
929 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
930 poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
931
932 if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPools[i]) != VK_SUCCESS) {
933 throw runtime_error("failed to create graphics command pool!");
934 }
935 }
936}
937
938void VulkanGame::createTextureSampler() {
939 VkSamplerCreateInfo samplerInfo = {};
940 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
941 samplerInfo.magFilter = VK_FILTER_LINEAR;
942 samplerInfo.minFilter = VK_FILTER_LINEAR;
943
944 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
945 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
946 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
947
948 samplerInfo.anisotropyEnable = VK_TRUE;
949 samplerInfo.maxAnisotropy = 16;
950 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
951 samplerInfo.unnormalizedCoordinates = VK_FALSE;
952 samplerInfo.compareEnable = VK_FALSE;
953 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
954 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
955 samplerInfo.mipLodBias = 0.0f;
956 samplerInfo.minLod = 0.0f;
957 samplerInfo.maxLod = 0.0f;
958
959 VKUTIL_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler),
960 "failed to create texture sampler!");
961}
962
963void VulkanGame::createFramebuffers() {
964 swapChainFramebuffers.resize(swapChainImageCount);
965
966 VkFramebufferCreateInfo framebufferInfo = {};
967 framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
968 framebufferInfo.renderPass = renderPass;
969 framebufferInfo.width = swapChainExtent.width;
970 framebufferInfo.height = swapChainExtent.height;
971 framebufferInfo.layers = 1;
972
973 for (size_t i = 0; i < swapChainImageCount; i++) {
974 array<VkImageView, 2> attachments = {
975 swapChainImageViews[i],
976 depthImage.imageView
977 };
978
979 framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
980 framebufferInfo.pAttachments = attachments.data();
981
982 if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
983 throw runtime_error("failed to create framebuffer!");
984 }
985 }
986}
987
988void VulkanGame::createCommandBuffers() {
989 commandBuffers.resize(swapChainImageCount);
990
991 for (size_t i = 0; i < swapChainImageCount; i++) {
992 VkCommandBufferAllocateInfo allocInfo = {};
993 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
994 allocInfo.commandPool = commandPools[i];
995 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
996 allocInfo.commandBufferCount = 1;
997
998 if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffers[i]) != VK_SUCCESS) {
999 throw runtime_error("failed to allocate command buffer!");
1000 }
1001 }
1002}
1003
1004void VulkanGame::createSyncObjects() {
1005 imageAcquiredSemaphores.resize(swapChainImageCount);
1006 renderCompleteSemaphores.resize(swapChainImageCount);
1007 inFlightFences.resize(swapChainImageCount);
1008
1009 VkSemaphoreCreateInfo semaphoreInfo = {};
1010 semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
1011
1012 VkFenceCreateInfo fenceInfo = {};
1013 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
1014 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
1015
1016 for (size_t i = 0; i < swapChainImageCount; i++) {
1017 VKUTIL_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAcquiredSemaphores[i]),
1018 "failed to create image acquired sempahore for a frame!");
1019
1020 VKUTIL_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderCompleteSemaphores[i]),
1021 "failed to create render complete sempahore for a frame!");
1022
1023 VKUTIL_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]),
1024 "failed to create fence for a frame!");
1025 }
1026}
1027
1028void VulkanGame::initImGuiOverlay() {
1029 vector<VkDescriptorPoolSize> pool_sizes {
1030 { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
1031 { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
1032 { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
1033 { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 },
1034 { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 },
1035 { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 },
1036 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 },
1037 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 },
1038 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 },
1039 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 },
1040 { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 }
1041 };
1042
1043 VkDescriptorPoolCreateInfo pool_info = {};
1044 pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1045 pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
1046 pool_info.maxSets = 1000 * pool_sizes.size();
1047 pool_info.poolSizeCount = static_cast<uint32_t>(pool_sizes.size());
1048 pool_info.pPoolSizes = pool_sizes.data();
1049
1050 VKUTIL_CHECK_RESULT(vkCreateDescriptorPool(device, &pool_info, nullptr, &imguiDescriptorPool),
1051 "failed to create IMGUI descriptor pool!");
1052
1053 // TODO: Do this in one place and save it instead of redoing it every time I need a queue family index
1054 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
1055
1056 // Setup Dear ImGui context
1057 IMGUI_CHECKVERSION();
1058 ImGui::CreateContext();
1059 ImGuiIO& io = ImGui::GetIO();
1060 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
1061 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
1062
1063 // Setup Dear ImGui style
1064 ImGui::StyleColorsDark();
1065 //ImGui::StyleColorsClassic();
1066
1067 // Setup Platform/Renderer bindings
1068 ImGui_ImplSDL2_InitForVulkan(window);
1069 ImGui_ImplVulkan_InitInfo init_info = {};
1070 init_info.Instance = instance;
1071 init_info.PhysicalDevice = physicalDevice;
1072 init_info.Device = device;
1073 init_info.QueueFamily = indices.graphicsFamily.value();
1074 init_info.Queue = graphicsQueue;
1075 init_info.DescriptorPool = imguiDescriptorPool;
1076 init_info.Allocator = nullptr;
1077 init_info.MinImageCount = swapChainMinImageCount;
1078 init_info.ImageCount = swapChainImageCount;
1079 init_info.CheckVkResultFn = check_imgui_vk_result;
1080 ImGui_ImplVulkan_Init(&init_info, renderPass);
1081
1082 // Load Fonts
1083 // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
1084 // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
1085 // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
1086 // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call.
1087 // - Read 'docs/FONTS.md' for more instructions and details.
1088 // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
1089 //io.Fonts->AddFontDefault();
1090 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
1091 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
1092 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
1093 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f);
1094 //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
1095 //assert(font != NULL);
1096
1097 // Upload Fonts
1098
1099 VkCommandBuffer commandBuffer = VulkanUtils::beginSingleTimeCommands(device, resourceCommandPool);
1100
1101 ImGui_ImplVulkan_CreateFontsTexture(commandBuffer);
1102
1103 VulkanUtils::endSingleTimeCommands(device, resourceCommandPool, commandBuffer, graphicsQueue);
1104
1105 ImGui_ImplVulkan_DestroyFontUploadObjects();
1106}
1107
1108void VulkanGame::cleanupImGuiOverlay() {
1109 ImGui_ImplVulkan_Shutdown();
1110 ImGui_ImplSDL2_Shutdown();
1111 ImGui::DestroyContext();
1112
1113 vkDestroyDescriptorPool(device, imguiDescriptorPool, nullptr);
1114}
1115
1116void VulkanGame::createBufferSet(VkDeviceSize bufferSize, VkBufferUsageFlags usages, VkMemoryPropertyFlags properties,
1117 BufferSet& set) {
1118 set.usages = usages;
1119 set.properties = properties;
1120
1121 set.buffers.resize(swapChainImageCount);
1122 set.memory.resize(swapChainImageCount);
1123 set.infoSet.resize(swapChainImageCount);
1124
1125 for (size_t i = 0; i < swapChainImageCount; i++) {
1126 VulkanUtils::createBuffer(device, physicalDevice, bufferSize, usages, properties, set.buffers[i], set.memory[i]);
1127
1128 set.infoSet[i].buffer = set.buffers[i];
1129 set.infoSet[i].offset = 0; // This is the offset from the start of the buffer, so always 0 for now
1130 set.infoSet[i].range = bufferSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
1131 }
1132}
1133
1134void VulkanGame::resizeBufferSet(BufferSet& set, VkDeviceSize newSize, VkCommandPool commandPool,
1135 VkQueue graphicsQueue, bool copyData) {
1136 for (size_t i = 0; i < set.buffers.size(); i++) {
1137 VkBuffer newBuffer;
1138 VkDeviceMemory newMemory;
1139
1140 VulkanUtils::createBuffer(device, physicalDevice, newSize, set.usages, set.properties, newBuffer, newMemory);
1141
1142 if (copyData) {
1143 VulkanUtils::copyBuffer(device, commandPool, set.buffers[i], newBuffer, 0, 0, set.infoSet[i].range,
1144 graphicsQueue);
1145 }
1146
1147 vkDestroyBuffer(device, set.buffers[i], nullptr);
1148 vkFreeMemory(device, set.memory[i], nullptr);
1149
1150 set.buffers[i] = newBuffer;
1151 set.memory[i] = newMemory;
1152
1153 set.infoSet[i].buffer = set.buffers[i];
1154 set.infoSet[i].offset = 0; // This is the offset from the start of the buffer, so always 0 for now
1155 set.infoSet[i].range = newSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
1156 }
1157}
1158
1159void VulkanGame::renderFrame(ImDrawData* draw_data) {
1160 VkResult result = vkAcquireNextImageKHR(device, swapChain, numeric_limits<uint64_t>::max(),
1161 imageAcquiredSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
1162
1163 if (result == VK_SUBOPTIMAL_KHR) {
1164 shouldRecreateSwapChain = true;
1165 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1166 shouldRecreateSwapChain = true;
1167 return;
1168 } else {
1169 VKUTIL_CHECK_RESULT(result, "failed to acquire swap chain image!");
1170 }
1171
1172 VKUTIL_CHECK_RESULT(
1173 vkWaitForFences(device, 1, &inFlightFences[imageIndex], VK_TRUE, numeric_limits<uint64_t>::max()),
1174 "failed waiting for fence!");
1175
1176 VKUTIL_CHECK_RESULT(vkResetFences(device, 1, &inFlightFences[imageIndex]),
1177 "failed to reset fence!");
1178
1179 VKUTIL_CHECK_RESULT(vkResetCommandPool(device, commandPools[imageIndex], 0),
1180 "failed to reset command pool!");
1181
1182 VkCommandBufferBeginInfo beginInfo = {};
1183 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1184 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1185
1186 VKUTIL_CHECK_RESULT(vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo),
1187 "failed to begin recording command buffer!");
1188
1189 VkRenderPassBeginInfo renderPassInfo = {};
1190 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
1191 renderPassInfo.renderPass = renderPass;
1192 renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
1193 renderPassInfo.renderArea.offset = { 0, 0 };
1194 renderPassInfo.renderArea.extent = swapChainExtent;
1195
1196 array<VkClearValue, 2> clearValues = {};
1197 clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
1198 clearValues[1].depthStencil = { 1.0f, 0 };
1199
1200 renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
1201 renderPassInfo.pClearValues = clearValues.data();
1202
1203 vkCmdBeginRenderPass(commandBuffers[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
1204
1205 // TODO: Find a more elegant, per-screen solution for this
1206 if (currentRenderScreenFn == &VulkanGame::renderGameScreen) {
1207 modelPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex, {});
1208
1209
1210
1211
1212 }
1213
1214 ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffers[imageIndex]);
1215
1216 vkCmdEndRenderPass(commandBuffers[imageIndex]);
1217
1218 VKUTIL_CHECK_RESULT(vkEndCommandBuffer(commandBuffers[imageIndex]),
1219 "failed to record command buffer!");
1220
1221 VkSemaphore waitSemaphores[] = { imageAcquiredSemaphores[currentFrame] };
1222 VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
1223 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
1224
1225 VkSubmitInfo submitInfo = {};
1226 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1227 submitInfo.waitSemaphoreCount = 1;
1228 submitInfo.pWaitSemaphores = waitSemaphores;
1229 submitInfo.pWaitDstStageMask = waitStages;
1230 submitInfo.commandBufferCount = 1;
1231 submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
1232 submitInfo.signalSemaphoreCount = 1;
1233 submitInfo.pSignalSemaphores = signalSemaphores;
1234
1235 VKUTIL_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[imageIndex]),
1236 "failed to submit draw command buffer!");
1237}
1238
1239void VulkanGame::presentFrame() {
1240 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
1241
1242 VkPresentInfoKHR presentInfo = {};
1243 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
1244 presentInfo.waitSemaphoreCount = 1;
1245 presentInfo.pWaitSemaphores = signalSemaphores;
1246 presentInfo.swapchainCount = 1;
1247 presentInfo.pSwapchains = &swapChain;
1248 presentInfo.pImageIndices = &imageIndex;
1249 presentInfo.pResults = nullptr;
1250
1251 VkResult result = vkQueuePresentKHR(presentQueue, &presentInfo);
1252
1253 if (result == VK_SUBOPTIMAL_KHR) {
1254 shouldRecreateSwapChain = true;
1255 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1256 shouldRecreateSwapChain = true;
1257 return;
1258 } else {
1259 VKUTIL_CHECK_RESULT(result, "failed to present swap chain image!");
1260 }
1261
1262 currentFrame = (currentFrame + 1) % swapChainImageCount;
1263}
1264
1265void VulkanGame::recreateSwapChain() {
1266 if (vkDeviceWaitIdle(device) != VK_SUCCESS) {
1267 throw runtime_error("failed to wait for device!");
1268 }
1269
1270 cleanupSwapChain();
1271
1272 createSwapChain();
1273 createImageViews();
1274
1275 // The depth buffer does need to be recreated with the swap chain since its dimensions depend on the window size
1276 // and resizing the window is a common reason to recreate the swapchain
1277 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
1278 depthImage, graphicsQueue);
1279
1280 createRenderPass();
1281 createCommandPools();
1282 createFramebuffers();
1283 createCommandBuffers();
1284 createSyncObjects();
1285
1286 // TODO: Move UBO creation/management into GraphicsPipeline_Vulkan, like I did with SSBOs
1287 // TODO: Check if the shader stages and maybe some other properties of the pipeline can be re-used
1288 // instead of recreated every time
1289
1290 createBufferSet(sizeof(UBO_VP_mats),
1291 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1292 uniformBuffers_modelPipeline);
1293
1294 modelPipeline.updateRenderPass(renderPass);
1295 modelPipeline.createPipeline("shaders/model-vert.spv", "shaders/model-frag.spv");
1296 modelPipeline.createDescriptorPool(swapChainImages.size());
1297 modelPipeline.createDescriptorSets(swapChainImages.size());
1298
1299 imageIndex = 0;
1300}
1301
1302void VulkanGame::cleanupSwapChain() {
1303 VulkanUtils::destroyVulkanImage(device, depthImage);
1304
1305 for (VkFramebuffer framebuffer : swapChainFramebuffers) {
1306 vkDestroyFramebuffer(device, framebuffer, nullptr);
1307 }
1308
1309 for (uint32_t i = 0; i < swapChainImageCount; i++) {
1310 vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
1311 vkDestroyCommandPool(device, commandPools[i], nullptr);
1312 }
1313
1314 modelPipeline.cleanup();
1315
1316 for (size_t i = 0; i < uniformBuffers_modelPipeline.buffers.size(); i++) {
1317 vkDestroyBuffer(device, uniformBuffers_modelPipeline.buffers[i], nullptr);
1318 vkFreeMemory(device, uniformBuffers_modelPipeline.memory[i], nullptr);
1319 }
1320
1321 for (uint32_t i = 0; i < swapChainImageCount; i++) {
1322 vkDestroySemaphore(device, imageAcquiredSemaphores[i], nullptr);
1323 vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr);
1324 vkDestroyFence(device, inFlightFences[i], nullptr);
1325 }
1326
1327 vkDestroyRenderPass(device, renderPass, nullptr);
1328
1329 for (VkImageView imageView : swapChainImageViews) {
1330 vkDestroyImageView(device, imageView, nullptr);
1331 }
1332
1333 vkDestroySwapchainKHR(device, swapChain, nullptr);
1334}
1335
1336void VulkanGame::renderMainScreen(int width, int height) {
1337 {
1338 int padding = 4;
1339 ImGui::SetNextWindowPos(vec2(-padding, -padding), ImGuiCond_Once);
1340 ImGui::SetNextWindowSize(vec2(width + 2 * padding, height + 2 * padding), ImGuiCond_Always);
1341 ImGui::Begin("WndMain", nullptr,
1342 ImGuiWindowFlags_NoTitleBar |
1343 ImGuiWindowFlags_NoResize |
1344 ImGuiWindowFlags_NoMove);
1345
1346 ButtonImGui btn("New Game");
1347
1348 ImGui::InvisibleButton("", vec2(10, height / 6));
1349 if (btn.draw((width - btn.getWidth()) / 2)) {
1350 goToScreen(&VulkanGame::renderGameScreen);
1351 }
1352
1353 ButtonImGui btn2("Quit");
1354
1355 ImGui::InvisibleButton("", vec2(10, 15));
1356 if (btn2.draw((width - btn2.getWidth()) / 2)) {
1357 quitGame();
1358 }
1359
1360 ImGui::End();
1361 }
1362}
1363
1364void VulkanGame::renderGameScreen(int width, int height) {
1365 {
1366 ImGui::SetNextWindowSize(vec2(130, 65), ImGuiCond_Once);
1367 ImGui::SetNextWindowPos(vec2(10, 50), ImGuiCond_Once);
1368 ImGui::Begin("WndStats", nullptr,
1369 ImGuiWindowFlags_NoTitleBar |
1370 ImGuiWindowFlags_NoResize |
1371 ImGuiWindowFlags_NoMove);
1372
1373 //ImGui::Text(ImGui::GetIO().Framerate);
1374 renderGuiValueList(valueLists["stats value list"]);
1375
1376 ImGui::End();
1377 }
1378
1379 {
1380 ImGui::SetNextWindowSize(vec2(250, 35), ImGuiCond_Once);
1381 ImGui::SetNextWindowPos(vec2(width - 260, 10), ImGuiCond_Always);
1382 ImGui::Begin("WndMenubar", nullptr,
1383 ImGuiWindowFlags_NoTitleBar |
1384 ImGuiWindowFlags_NoResize |
1385 ImGuiWindowFlags_NoMove);
1386 ImGui::InvisibleButton("", vec2(155, 18));
1387 ImGui::SameLine();
1388 if (ImGui::Button("Main Menu")) {
1389 goToScreen(&VulkanGame::renderMainScreen);
1390 }
1391 ImGui::End();
1392 }
1393
1394 {
1395 ImGui::SetNextWindowSize(vec2(200, 200), ImGuiCond_Once);
1396 ImGui::SetNextWindowPos(vec2(width - 210, 60), ImGuiCond_Always);
1397 ImGui::Begin("WndDebug", nullptr,
1398 ImGuiWindowFlags_NoTitleBar |
1399 ImGuiWindowFlags_NoResize |
1400 ImGuiWindowFlags_NoMove);
1401
1402 renderGuiValueList(valueLists["debug value list"]);
1403
1404 ImGui::End();
1405 }
1406}
1407
1408void VulkanGame::initGuiValueLists(map<string, vector<UIValue>>& valueLists) {
1409 valueLists["stats value list"] = vector<UIValue>();
1410 valueLists["debug value list"] = vector<UIValue>();
1411}
1412
1413// TODO: Probably turn this into a UI widget class
1414void VulkanGame::renderGuiValueList(vector<UIValue>& values) {
1415 float maxWidth = 0.0f;
1416 float cursorStartPos = ImGui::GetCursorPosX();
1417
1418 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
1419 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
1420
1421 if (maxWidth < textWidth)
1422 maxWidth = textWidth;
1423 }
1424
1425 stringstream ss;
1426
1427 // TODO: Possibly implement this based on gui/ui-value.hpp instead and use templates
1428 // to keep track of the type. This should make it a bit easier to use and maintain
1429 // Also, implement this in a way that's agnostic to the UI renderer.
1430 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
1431 ss.str("");
1432 ss.clear();
1433
1434 switch (it->type) {
1435 case UIVALUE_INT:
1436 ss << it->label << ": " << *(unsigned int*)it->value;
1437 break;
1438 case UIVALUE_DOUBLE:
1439 ss << it->label << ": " << *(double*)it->value;
1440 break;
1441 }
1442
1443 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
1444
1445 ImGui::SetCursorPosX(cursorStartPos + maxWidth - textWidth);
1446 //ImGui::Text("%s", ss.str().c_str());
1447 ImGui::Text("%s: %.1f", it->label.c_str(), *(float*)it->value);
1448 }
1449}
1450
1451void VulkanGame::goToScreen(void (VulkanGame::* renderScreenFn)(int width, int height)) {
1452 currentRenderScreenFn = renderScreenFn;
1453
1454 // TODO: Maybe just set shouldRecreateSwapChain to true instead. Check this render loop logic
1455 // to make sure there'd be no issues
1456 //recreateSwapChain();
1457}
1458
1459void VulkanGame::quitGame() {
1460 done = true;
1461}
Note: See TracBrowser for help on using the repository browser.