source: opengl-game/sdl-game.cpp@ 67527a5

feature/imgui-sdl
Last change on this file since 67527a5 was 67527a5, checked in by Dmitry Portnoy <dportnoy@…>, 14 months ago

Switch all per-object buffers to be dynamic uniform buffers instead of shader storage buffers

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