source: opengl-game/sdl-game.cpp@ 90880fb

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

Start using the VulkanBuffer class for the non-per-object uniforms (this means the view and projection matrices for all pipelines, as well as the current time for the explosion pipeline)

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