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

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

Modify the VulkanBuffer class to take a range and to align data based on that rather than the size of an individual data item. Also, reorganize the code in VulkanGae::updateScene() in a more logical fashion, and remove VulkanGame::updateObject() and inline its functionality.

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