source: opengl-game/sdl-game.cpp@ 85b5fec

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

Use the new UI system in SDLGame as well

  • Property mode set to 100644
File size: 41.7 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
11#include "gui/imgui/button-imgui.hpp"
12
13using namespace std;
14
15#define IMGUI_UNLIMITED_FRAME_RATE
16
17static void check_imgui_vk_result(VkResult res) {
18 if (res == VK_SUCCESS) {
19 return;
20 }
21
22 ostringstream oss;
23 oss << "[imgui] Vulkan error! VkResult is \"" << VulkanUtils::resultString(res) << "\"" << __LINE__;
24 if (res < 0) {
25 throw runtime_error("Fatal: " + oss.str());
26 } else {
27 cerr << oss.str();
28 }
29}
30
31VKAPI_ATTR VkBool32 VKAPI_CALL VulkanGame::debugCallback(
32 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
33 VkDebugUtilsMessageTypeFlagsEXT messageType,
34 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
35 void* pUserData) {
36 cerr << "validation layer: " << pCallbackData->pMessage << endl;
37
38 // TODO: Figure out what the return value means and if it should always be VK_FALSE
39 return VK_FALSE;
40}
41
42VulkanGame::VulkanGame() {
43 // TODO: Double-check whether initialization should happen in the header, where the variables are declared, or here
44 // Also, decide whether to use this-> for all instance variables, or only when necessary
45
46 debugMessenger = VK_NULL_HANDLE;
47
48 gui = nullptr;
49 window = nullptr;
50
51 swapChainPresentMode = VK_PRESENT_MODE_MAX_ENUM_KHR;
52 swapChainMinImageCount = 0;
53 currentFrame = 0;
54 imageIndex = 0;
55 shouldRecreateSwapChain = false;
56
57 score = 0;
58 fps = 0.0f;
59}
60
61VulkanGame::~VulkanGame() {
62}
63
64void VulkanGame::run(int width, int height, unsigned char guiFlags) {
65 // TODO: Maybe call the init code in the constructor instead of in run()
66 // Research this
67 cout << "DEBUGGING IS " << (ENABLE_VALIDATION_LAYERS ? "ON" : "OFF") << endl;
68
69 cout << "Vulkan Game" << endl;
70
71 // TODO: Move IMGUI initialization in here
72 if (initUI(width, height, guiFlags) == RTWO_ERROR) {
73 return;
74 }
75
76 initVulkan();
77
78 // Create Descriptor Pool
79 {
80 VkDescriptorPoolSize pool_sizes[] =
81 {
82 { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
83 { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
84 { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
85 { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 },
86 { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 },
87 { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 },
88 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 },
89 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 },
90 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 },
91 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 },
92 { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 }
93 };
94 VkDescriptorPoolCreateInfo pool_info = {};
95 pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
96 pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
97 pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes);
98 pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes);
99 pool_info.pPoolSizes = pool_sizes;
100
101 VKUTIL_CHECK_RESULT(vkCreateDescriptorPool(device, &pool_info, nullptr, &descriptorPool),
102 "failed to create descriptor pool");
103 }
104
105 // TODO: Do this in one place and save it instead of redoing it every time I need a queue family index
106 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
107
108 // Setup Dear ImGui context
109 IMGUI_CHECKVERSION();
110 ImGui::CreateContext();
111 ImGuiIO& io = ImGui::GetIO();
112 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
113 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
114
115 // Setup Dear ImGui style
116 ImGui::StyleColorsDark();
117 //ImGui::StyleColorsClassic();
118
119 // Setup Platform/Renderer bindings
120 ImGui_ImplSDL2_InitForVulkan(window);
121 ImGui_ImplVulkan_InitInfo init_info = {};
122 init_info.Instance = instance;
123 init_info.PhysicalDevice = physicalDevice;
124 init_info.Device = device;
125 init_info.QueueFamily = indices.graphicsFamily.value();
126 init_info.Queue = graphicsQueue;
127 init_info.DescriptorPool = descriptorPool;
128 init_info.Allocator = nullptr;
129 init_info.MinImageCount = swapChainMinImageCount;
130 init_info.ImageCount = swapChainImageCount;
131 init_info.CheckVkResultFn = check_imgui_vk_result;
132 ImGui_ImplVulkan_Init(&init_info, renderPass);
133
134 // Load Fonts
135 // - 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.
136 // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
137 // - 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).
138 // - 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.
139 // - Read 'docs/FONTS.md' for more instructions and details.
140 // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
141 //io.Fonts->AddFontDefault();
142 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
143 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
144 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
145 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f);
146 //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
147 //assert(font != NULL);
148
149 // Upload Fonts
150 {
151 VkCommandBuffer commandBuffer = VulkanUtils::beginSingleTimeCommands(device, resourceCommandPool);
152
153 ImGui_ImplVulkan_CreateFontsTexture(commandBuffer);
154
155 VulkanUtils::endSingleTimeCommands(device, resourceCommandPool, commandBuffer, graphicsQueue);
156
157 ImGui_ImplVulkan_DestroyFontUploadObjects();
158 }
159
160 currentRenderScreenFn = &VulkanGame::renderMainScreen;
161
162 initGuiValueLists(valueLists);
163
164 valueLists["stats value list"].push_back(UIValue(UIVALUE_INT, "Score", &score));
165 valueLists["stats value list"].push_back(UIValue(UIVALUE_DOUBLE, "FPS", &fps));
166 valueLists["stats value list"].push_back(UIValue(UIVALUE_DOUBLE, "IMGUI FPS", &io.Framerate));
167
168 renderLoop();
169 cleanup();
170
171 close_log();
172}
173
174bool VulkanGame::initUI(int width, int height, unsigned char guiFlags) {
175 // TODO: Create a game-gui function to get the gui version and retrieve it that way
176
177 SDL_VERSION(&sdlVersion); // This gets the compile-time version
178 SDL_GetVersion(&sdlVersion); // This gets the runtime version
179
180 cout << "SDL " <<
181 to_string(sdlVersion.major) << "." <<
182 to_string(sdlVersion.minor) << "." <<
183 to_string(sdlVersion.patch) << endl;
184
185 // TODO: Refactor the logger api to be more flexible,
186 // esp. since gl_log() and gl_log_err() have issues printing anything besides strings
187 restart_gl_log();
188 gl_log("starting SDL\n%s.%s.%s",
189 to_string(sdlVersion.major).c_str(),
190 to_string(sdlVersion.minor).c_str(),
191 to_string(sdlVersion.patch).c_str());
192
193 // TODO: Use open_Log() and related functions instead of gl_log ones
194 // TODO: In addition, delete the gl_log functions
195 open_log();
196 get_log() << "starting SDL" << endl;
197 get_log() <<
198 (int)sdlVersion.major << "." <<
199 (int)sdlVersion.minor << "." <<
200 (int)sdlVersion.patch << endl;
201
202 // TODO: Put all fonts, textures, and images in the assets folder
203 gui = new GameGui_SDL();
204
205 if (gui->init() == RTWO_ERROR) {
206 // TODO: Also print these sorts of errors to the log
207 cout << "UI library could not be initialized!" << endl;
208 cout << gui->getError() << endl;
209 return RTWO_ERROR;
210 }
211
212 window = (SDL_Window*)gui->createWindow("Vulkan Game", width, height, guiFlags & GUI_FLAGS_WINDOW_FULLSCREEN);
213 if (window == nullptr) {
214 cout << "Window could not be created!" << endl;
215 cout << gui->getError() << endl;
216 return RTWO_ERROR;
217 }
218
219 cout << "Target window size: (" << width << ", " << height << ")" << endl;
220 cout << "Actual window size: (" << gui->getWindowWidth() << ", " << gui->getWindowHeight() << ")" << endl;
221
222 return RTWO_SUCCESS;
223}
224
225void VulkanGame::initVulkan() {
226 const vector<const char*> validationLayers = {
227 "VK_LAYER_KHRONOS_validation"
228 };
229 const vector<const char*> deviceExtensions = {
230 VK_KHR_SWAPCHAIN_EXTENSION_NAME
231 };
232
233 createVulkanInstance(validationLayers);
234 setupDebugMessenger();
235 createVulkanSurface();
236 pickPhysicalDevice(deviceExtensions);
237 createLogicalDevice(validationLayers, deviceExtensions);
238 chooseSwapChainProperties();
239 createSwapChain();
240 createImageViews();
241 createRenderPass();
242
243 createResourceCommandPool();
244 createCommandPools();
245
246 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
247 depthImage, graphicsQueue);
248
249 createFramebuffers();
250
251 // TODO: Initialize pipelines here
252
253 createCommandBuffers();
254
255 createSyncObjects();
256}
257
258void VulkanGame::renderLoop() {
259 startTime = steady_clock::now();
260 curTime = duration<float, seconds::period>(steady_clock::now() - startTime).count();
261
262 fpsStartTime = curTime;
263 frameCount = 0;
264
265 ImGuiIO& io = ImGui::GetIO();
266
267 done = false;
268 while (!done) {
269
270 curTime = duration<float, seconds::period>(steady_clock::now() - startTime).count();
271
272 if (curTime - fpsStartTime >= 1.0f) {
273 fps = (float)frameCount / (curTime - fpsStartTime);
274
275 frameCount = 0;
276 fpsStartTime = curTime;
277 }
278
279 frameCount++;
280
281 gui->processEvents();
282
283 UIEvent uiEvent;
284 while (gui->pollEvent(&uiEvent)) {
285 GameEvent& e = uiEvent.event;
286 SDL_Event sdlEvent = uiEvent.rawEvent.sdl;
287
288 ImGui_ImplSDL2_ProcessEvent(&sdlEvent);
289 if (io.WantCaptureMouse &&
290 (e.type == UI_EVENT_MOUSEBUTTONDOWN || e.type == UI_EVENT_MOUSEBUTTONUP || e.type == UI_EVENT_UNKNOWN)) {
291 if (sdlEvent.type == SDL_MOUSEWHEEL || sdlEvent.type == SDL_MOUSEBUTTONDOWN || sdlEvent.type == SDL_MOUSEBUTTONUP) {
292 continue;
293 }
294 }
295 if (io.WantCaptureKeyboard &&
296 (e.type == UI_EVENT_KEYDOWN || e.type == UI_EVENT_KEYUP)) {
297 if (sdlEvent.type == SDL_KEYDOWN || sdlEvent.type == SDL_KEYUP) {
298 continue;
299 }
300 }
301 if (io.WantTextInput) {
302 // show onscreen keyboard if on mobile
303 }
304
305 switch (e.type) {
306 case UI_EVENT_MOUSEMOTION:
307 // Currently unused
308 break;
309 case UI_EVENT_WINDOW:
310 // Currently unused
311 break;
312 case UI_EVENT_QUIT:
313 cout << "Quit event detected" << endl;
314 done = true;
315 break;
316 case UI_EVENT_UNHANDLED:
317 cout << "Unhandled event type: 0x" << hex << sdlEvent.type << dec << endl;
318 break;
319 case UI_EVENT_UNKNOWN:
320 default:
321 cout << "Unknown event type: 0x" << hex << sdlEvent.type << dec << endl;
322 break;
323 }
324 }
325
326 if (shouldRecreateSwapChain) {
327 gui->refreshWindowSize();
328 const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
329
330 if (!isMinimized) {
331 // TODO: This should be used if the min image count changes, presumably because a new surface was created
332 // with a different image count or something like that. Maybe I want to add code to query for a new min image count
333 // during swapchain recreation to take advantage of this
334 ImGui_ImplVulkan_SetMinImageCount(swapChainMinImageCount);
335
336 recreateSwapChain();
337
338 shouldRecreateSwapChain = false;
339 }
340 }
341
342 // Start the Dear ImGui frame
343 ImGui_ImplVulkan_NewFrame();
344 ImGui_ImplSDL2_NewFrame(window);
345 ImGui::NewFrame();
346
347 (this->*currentRenderScreenFn)(gui->getWindowWidth(), gui->getWindowHeight());
348
349 ImGui::Render();
350
351 gui->refreshWindowSize();
352 const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
353
354 if (!isMinimized) {
355 renderFrame(ImGui::GetDrawData());
356 presentFrame();
357 }
358 }
359}
360
361void VulkanGame::cleanup() {
362 // FIXME: We could wait on the Queue if we had the queue in wd-> (otherwise VulkanH functions can't use globals)
363 //vkQueueWaitIdle(g_Queue);
364 VKUTIL_CHECK_RESULT(vkDeviceWaitIdle(device), "failed to wait for device!");
365
366 ImGui_ImplVulkan_Shutdown();
367 ImGui_ImplSDL2_Shutdown();
368 ImGui::DestroyContext();
369
370 cleanupSwapChain();
371
372 // this would actually be destroyed in the pipeline class
373 vkDestroyDescriptorPool(device, descriptorPool, nullptr);
374
375 vkDestroyCommandPool(device, resourceCommandPool, nullptr);
376
377 vkDestroyDevice(device, nullptr);
378 vkDestroySurfaceKHR(instance, surface, nullptr);
379
380 if (ENABLE_VALIDATION_LAYERS) {
381 VulkanUtils::destroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
382 }
383
384 vkDestroyInstance(instance, nullptr);
385
386 gui->destroyWindow();
387 gui->shutdown();
388 delete gui;
389}
390
391void VulkanGame::createVulkanInstance(const vector<const char*>& validationLayers) {
392 if (ENABLE_VALIDATION_LAYERS && !VulkanUtils::checkValidationLayerSupport(validationLayers)) {
393 throw runtime_error("validation layers requested, but not available!");
394 }
395
396 VkApplicationInfo appInfo = {};
397 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
398 appInfo.pApplicationName = "Vulkan Game";
399 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
400 appInfo.pEngineName = "No Engine";
401 appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
402 appInfo.apiVersion = VK_API_VERSION_1_0;
403
404 VkInstanceCreateInfo createInfo = {};
405 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
406 createInfo.pApplicationInfo = &appInfo;
407
408 vector<const char*> extensions = gui->getRequiredExtensions();
409 if (ENABLE_VALIDATION_LAYERS) {
410 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
411 }
412
413 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
414 createInfo.ppEnabledExtensionNames = extensions.data();
415
416 cout << endl << "Extensions:" << endl;
417 for (const char* extensionName : extensions) {
418 cout << extensionName << endl;
419 }
420 cout << endl;
421
422 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
423 if (ENABLE_VALIDATION_LAYERS) {
424 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
425 createInfo.ppEnabledLayerNames = validationLayers.data();
426
427 populateDebugMessengerCreateInfo(debugCreateInfo);
428 createInfo.pNext = &debugCreateInfo;
429 }
430 else {
431 createInfo.enabledLayerCount = 0;
432
433 createInfo.pNext = nullptr;
434 }
435
436 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
437 throw runtime_error("failed to create instance!");
438 }
439}
440
441void VulkanGame::setupDebugMessenger() {
442 if (!ENABLE_VALIDATION_LAYERS) {
443 return;
444 }
445
446 VkDebugUtilsMessengerCreateInfoEXT createInfo;
447 populateDebugMessengerCreateInfo(createInfo);
448
449 if (VulkanUtils::createDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
450 throw runtime_error("failed to set up debug messenger!");
451 }
452}
453
454void VulkanGame::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
455 createInfo = {};
456 createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
457 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;
458 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;
459 createInfo.pfnUserCallback = debugCallback;
460}
461
462void VulkanGame::createVulkanSurface() {
463 if (gui->createVulkanSurface(instance, &surface) == RTWO_ERROR) {
464 throw runtime_error("failed to create window surface!");
465 }
466}
467
468void VulkanGame::pickPhysicalDevice(const vector<const char*>& deviceExtensions) {
469 uint32_t deviceCount = 0;
470 // TODO: Check VkResult
471 vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
472
473 if (deviceCount == 0) {
474 throw runtime_error("failed to find GPUs with Vulkan support!");
475 }
476
477 vector<VkPhysicalDevice> devices(deviceCount);
478 // TODO: Check VkResult
479 vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
480
481 cout << endl << "Graphics cards:" << endl;
482 for (const VkPhysicalDevice& device : devices) {
483 if (isDeviceSuitable(device, deviceExtensions)) {
484 physicalDevice = device;
485 break;
486 }
487 }
488 cout << endl;
489
490 if (physicalDevice == VK_NULL_HANDLE) {
491 throw runtime_error("failed to find a suitable GPU!");
492 }
493}
494
495bool VulkanGame::isDeviceSuitable(VkPhysicalDevice physicalDevice, const vector<const char*>& deviceExtensions) {
496 VkPhysicalDeviceProperties deviceProperties;
497 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
498
499 cout << "Device: " << deviceProperties.deviceName << endl;
500
501 // TODO: Eventually, maybe let the user pick out of a set of GPUs in case the user does want to use
502 // an integrated GPU. On my laptop, this function returns TRUE for the integrated GPU, but crashes
503 // when trying to use it to render. Maybe I just need to figure out which other extensions and features
504 // to check.
505 if (deviceProperties.deviceType != VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
506 return false;
507 }
508
509 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
510 bool extensionsSupported = VulkanUtils::checkDeviceExtensionSupport(physicalDevice, deviceExtensions);
511 bool swapChainAdequate = false;
512
513 if (extensionsSupported) {
514 vector<VkSurfaceFormatKHR> formats = VulkanUtils::querySwapChainFormats(physicalDevice, surface);
515 vector<VkPresentModeKHR> presentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, surface);
516
517 swapChainAdequate = !formats.empty() && !presentModes.empty();
518 }
519
520 VkPhysicalDeviceFeatures supportedFeatures;
521 vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
522
523 return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
524}
525
526void VulkanGame::createLogicalDevice(const vector<const char*>& validationLayers,
527 const vector<const char*>& deviceExtensions) {
528 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
529
530 if (!indices.isComplete()) {
531 throw runtime_error("failed to find required queue families!");
532 }
533
534 // TODO: Using separate graphics and present queues currently works, but I should verify that I'm
535 // using them correctly to get the most benefit out of separate queues
536
537 vector<VkDeviceQueueCreateInfo> queueCreateInfoList;
538 set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
539
540 float queuePriority = 1.0f;
541 for (uint32_t queueFamily : uniqueQueueFamilies) {
542 VkDeviceQueueCreateInfo queueCreateInfo = {};
543 queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
544 queueCreateInfo.queueCount = 1;
545 queueCreateInfo.queueFamilyIndex = queueFamily;
546 queueCreateInfo.pQueuePriorities = &queuePriority;
547
548 queueCreateInfoList.push_back(queueCreateInfo);
549 }
550
551 VkPhysicalDeviceFeatures deviceFeatures = {};
552 deviceFeatures.samplerAnisotropy = VK_TRUE;
553
554 VkDeviceCreateInfo createInfo = {};
555 createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
556
557 createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfoList.size());
558 createInfo.pQueueCreateInfos = queueCreateInfoList.data();
559
560 createInfo.pEnabledFeatures = &deviceFeatures;
561
562 createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
563 createInfo.ppEnabledExtensionNames = deviceExtensions.data();
564
565 // These fields are ignored by up-to-date Vulkan implementations,
566 // but it's a good idea to set them for backwards compatibility
567 if (ENABLE_VALIDATION_LAYERS) {
568 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
569 createInfo.ppEnabledLayerNames = validationLayers.data();
570 }
571 else {
572 createInfo.enabledLayerCount = 0;
573 }
574
575 if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
576 throw runtime_error("failed to create logical device!");
577 }
578
579 vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
580 vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
581}
582
583void VulkanGame::chooseSwapChainProperties() {
584 vector<VkSurfaceFormatKHR> availableFormats = VulkanUtils::querySwapChainFormats(physicalDevice, surface);
585 vector<VkPresentModeKHR> availablePresentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, surface);
586
587 // Per Spec Format and View Format are expected to be the same unless VK_IMAGE_CREATE_MUTABLE_BIT was set at image creation
588 // Assuming that the default behavior is without setting this bit, there is no need for separate Swapchain image and image view format
589 // Additionally several new color spaces were introduced with Vulkan Spec v1.0.40,
590 // hence we must make sure that a format with the mostly available color space, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, is found and used.
591 swapChainSurfaceFormat = VulkanUtils::chooseSwapSurfaceFormat(availableFormats,
592 { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM },
593 VK_COLOR_SPACE_SRGB_NONLINEAR_KHR);
594
595#ifdef IMGUI_UNLIMITED_FRAME_RATE
596 vector<VkPresentModeKHR> presentModes{
597 VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR
598 };
599#else
600 vector<VkPresentModeKHR> presentModes{ VK_PRESENT_MODE_FIFO_KHR };
601#endif
602
603 swapChainPresentMode = VulkanUtils::chooseSwapPresentMode(availablePresentModes, presentModes);
604
605 cout << "[vulkan] Selected PresentMode = " << swapChainPresentMode << endl;
606
607 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, surface);
608
609 // If min image count was not specified, request different count of images dependent on selected present mode
610 if (swapChainMinImageCount == 0) {
611 if (swapChainPresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
612 swapChainMinImageCount = 3;
613 }
614 else if (swapChainPresentMode == VK_PRESENT_MODE_FIFO_KHR || swapChainPresentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) {
615 swapChainMinImageCount = 2;
616 }
617 else if (swapChainPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
618 swapChainMinImageCount = 1;
619 }
620 else {
621 throw runtime_error("unexpected present mode!");
622 }
623 }
624
625 if (swapChainMinImageCount < capabilities.minImageCount) {
626 swapChainMinImageCount = capabilities.minImageCount;
627 }
628 else if (capabilities.maxImageCount != 0 && swapChainMinImageCount > capabilities.maxImageCount) {
629 swapChainMinImageCount = capabilities.maxImageCount;
630 }
631}
632
633void VulkanGame::createSwapChain() {
634 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, surface);
635
636 swapChainExtent = VulkanUtils::chooseSwapExtent(capabilities, gui->getWindowWidth(), gui->getWindowHeight());
637
638 VkSwapchainCreateInfoKHR createInfo = {};
639 createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
640 createInfo.surface = surface;
641 createInfo.minImageCount = swapChainMinImageCount;
642 createInfo.imageFormat = swapChainSurfaceFormat.format;
643 createInfo.imageColorSpace = swapChainSurfaceFormat.colorSpace;
644 createInfo.imageExtent = swapChainExtent;
645 createInfo.imageArrayLayers = 1;
646 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
647
648 // TODO: Maybe save this result so I don't have to recalculate it every time
649 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
650 uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
651
652 if (indices.graphicsFamily != indices.presentFamily) {
653 createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
654 createInfo.queueFamilyIndexCount = 2;
655 createInfo.pQueueFamilyIndices = queueFamilyIndices;
656 }
657 else {
658 createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
659 createInfo.queueFamilyIndexCount = 0;
660 createInfo.pQueueFamilyIndices = nullptr;
661 }
662
663 createInfo.preTransform = capabilities.currentTransform;
664 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
665 createInfo.presentMode = swapChainPresentMode;
666 createInfo.clipped = VK_TRUE;
667 createInfo.oldSwapchain = VK_NULL_HANDLE;
668
669 if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
670 throw runtime_error("failed to create swap chain!");
671 }
672
673 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, nullptr) != VK_SUCCESS) {
674 throw runtime_error("failed to get swap chain image count!");
675 }
676
677 swapChainImages.resize(swapChainImageCount);
678 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, swapChainImages.data()) != VK_SUCCESS) {
679 throw runtime_error("failed to get swap chain images!");
680 }
681}
682
683void VulkanGame::createImageViews() {
684 swapChainImageViews.resize(swapChainImageCount);
685
686 for (uint32_t i = 0; i < swapChainImageViews.size(); i++) {
687 swapChainImageViews[i] = VulkanUtils::createImageView(device, swapChainImages[i], swapChainSurfaceFormat.format,
688 VK_IMAGE_ASPECT_COLOR_BIT);
689 }
690}
691
692void VulkanGame::createRenderPass() {
693 VkAttachmentDescription colorAttachment = {};
694 colorAttachment.format = swapChainSurfaceFormat.format;
695 colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
696 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // Set to VK_ATTACHMENT_LOAD_OP_DONT_CARE to disable clearing
697 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
698 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
699 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
700 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
701 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
702
703 VkAttachmentReference colorAttachmentRef = {};
704 colorAttachmentRef.attachment = 0;
705 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
706
707 VkAttachmentDescription depthAttachment = {};
708 depthAttachment.format = findDepthFormat();
709 depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
710 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
711 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
712 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
713 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
714 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
715 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
716
717 VkAttachmentReference depthAttachmentRef = {};
718 depthAttachmentRef.attachment = 1;
719 depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
720
721 VkSubpassDescription subpass = {};
722 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
723 subpass.colorAttachmentCount = 1;
724 subpass.pColorAttachments = &colorAttachmentRef;
725 //subpass.pDepthStencilAttachment = &depthAttachmentRef;
726
727 VkSubpassDependency dependency = {};
728 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
729 dependency.dstSubpass = 0;
730 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
731 dependency.srcAccessMask = 0;
732 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
733 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
734
735 array<VkAttachmentDescription, 2> attachments = { colorAttachment, depthAttachment };
736 VkRenderPassCreateInfo renderPassInfo = {};
737 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
738 renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
739 renderPassInfo.pAttachments = attachments.data();
740 renderPassInfo.subpassCount = 1;
741 renderPassInfo.pSubpasses = &subpass;
742 renderPassInfo.dependencyCount = 1;
743 renderPassInfo.pDependencies = &dependency;
744
745 if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
746 throw runtime_error("failed to create render pass!");
747 }
748
749 // We do not create a pipeline by default as this is also used by examples' main.cpp,
750 // but secondary viewport in multi-viewport mode may want to create one with:
751 //ImGui_ImplVulkan_CreatePipeline(device, g_Allocator, VK_NULL_HANDLE, g_MainWindowData.RenderPass, VK_SAMPLE_COUNT_1_BIT, &g_MainWindowData.Pipeline);
752}
753
754VkFormat VulkanGame::findDepthFormat() {
755 return VulkanUtils::findSupportedFormat(
756 physicalDevice,
757 { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT },
758 VK_IMAGE_TILING_OPTIMAL,
759 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
760 );
761}
762
763void VulkanGame::createResourceCommandPool() {
764 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
765
766 VkCommandPoolCreateInfo poolInfo = {};
767 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
768 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
769 poolInfo.flags = 0;
770
771 if (vkCreateCommandPool(device, &poolInfo, nullptr, &resourceCommandPool) != VK_SUCCESS) {
772 throw runtime_error("failed to create resource command pool!");
773 }
774}
775
776void VulkanGame::createCommandPools() {
777 commandPools.resize(swapChainImageCount);
778
779 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
780
781 for (size_t i = 0; i < swapChainImageCount; i++) {
782 VkCommandPoolCreateInfo poolInfo = {};
783 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
784 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
785 poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
786
787 if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPools[i]) != VK_SUCCESS) {
788 throw runtime_error("failed to create graphics command pool!");
789 }
790 }
791}
792
793void VulkanGame::createFramebuffers() {
794 swapChainFramebuffers.resize(swapChainImageCount);
795
796 VkFramebufferCreateInfo framebufferInfo = {};
797 framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
798 framebufferInfo.renderPass = renderPass;
799 framebufferInfo.width = swapChainExtent.width;
800 framebufferInfo.height = swapChainExtent.height;
801 framebufferInfo.layers = 1;
802
803 for (size_t i = 0; i < swapChainImageCount; i++) {
804 array<VkImageView, 2> attachments = {
805 swapChainImageViews[i],
806 depthImage.imageView
807 };
808
809 framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
810 framebufferInfo.pAttachments = attachments.data();
811
812 if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
813 throw runtime_error("failed to create framebuffer!");
814 }
815 }
816}
817
818void VulkanGame::createCommandBuffers() {
819 commandBuffers.resize(swapChainImageCount);
820
821 for (size_t i = 0; i < swapChainImageCount; i++) {
822 VkCommandBufferAllocateInfo allocInfo = {};
823 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
824 allocInfo.commandPool = commandPools[i];
825 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
826 allocInfo.commandBufferCount = 1;
827
828 if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffers[i]) != VK_SUCCESS) {
829 throw runtime_error("failed to allocate command buffer!");
830 }
831 }
832}
833
834void VulkanGame::createSyncObjects() {
835 imageAcquiredSemaphores.resize(swapChainImageCount);
836 renderCompleteSemaphores.resize(swapChainImageCount);
837 inFlightFences.resize(swapChainImageCount);
838
839 VkSemaphoreCreateInfo semaphoreInfo = {};
840 semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
841
842 VkFenceCreateInfo fenceInfo = {};
843 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
844 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
845
846 for (size_t i = 0; i < swapChainImageCount; i++) {
847 if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAcquiredSemaphores[i]) != VK_SUCCESS) {
848 throw runtime_error("failed to create image acquired sempahore for a frame!");
849 }
850
851 if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderCompleteSemaphores[i]) != VK_SUCCESS) {
852 throw runtime_error("failed to create render complete sempahore for a frame!");
853 }
854
855 if (vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) {
856 throw runtime_error("failed to create fence for a frame!");
857 }
858 }
859}
860
861void VulkanGame::renderFrame(ImDrawData* draw_data) {
862 VkResult result = vkAcquireNextImageKHR(device, swapChain, numeric_limits<uint64_t>::max(),
863 imageAcquiredSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
864
865 if (result == VK_SUBOPTIMAL_KHR) {
866 shouldRecreateSwapChain = true;
867 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
868 shouldRecreateSwapChain = true;
869 return;
870 } else {
871 VKUTIL_CHECK_RESULT(result, "failed to acquire swap chain image!");
872 }
873
874 VKUTIL_CHECK_RESULT(
875 vkWaitForFences(device, 1, &inFlightFences[imageIndex], VK_TRUE, numeric_limits<uint64_t>::max()),
876 "failed waiting for fence!");
877
878 VKUTIL_CHECK_RESULT(vkResetFences(device, 1, &inFlightFences[imageIndex]),
879 "failed to reset fence!");
880
881 VKUTIL_CHECK_RESULT(vkResetCommandPool(device, commandPools[imageIndex], 0),
882 "failed to reset command pool!");
883
884 VkCommandBufferBeginInfo beginInfo = {};
885 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
886 beginInfo.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
887
888 VKUTIL_CHECK_RESULT(vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo),
889 "failed to begin recording command buffer!");
890
891 VkRenderPassBeginInfo renderPassInfo = {};
892 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
893 renderPassInfo.renderPass = renderPass;
894 renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
895 renderPassInfo.renderArea.extent = swapChainExtent;
896
897 array<VkClearValue, 2> clearValues = {};
898 clearValues[0].color = { { 0.45f, 0.55f, 0.60f, 1.00f } };
899 clearValues[1].depthStencil = { 1.0f, 0 };
900
901 renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
902 renderPassInfo.pClearValues = clearValues.data();
903
904 vkCmdBeginRenderPass(commandBuffers[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
905
906 ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffers[imageIndex]);
907
908 vkCmdEndRenderPass(commandBuffers[imageIndex]);
909
910 VKUTIL_CHECK_RESULT(vkEndCommandBuffer(commandBuffers[imageIndex]),
911 "failed to record command buffer!");
912
913 VkSemaphore waitSemaphores[] = { imageAcquiredSemaphores[currentFrame] };
914 VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
915 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
916
917 VkSubmitInfo submitInfo = {};
918 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
919 submitInfo.waitSemaphoreCount = 1;
920 submitInfo.pWaitSemaphores = waitSemaphores;
921 submitInfo.pWaitDstStageMask = waitStages;
922 submitInfo.commandBufferCount = 1;
923 submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
924 submitInfo.signalSemaphoreCount = 1;
925 submitInfo.pSignalSemaphores = signalSemaphores;
926
927 VKUTIL_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[imageIndex]),
928 "failed to submit draw command buffer!");
929}
930
931void VulkanGame::presentFrame() {
932 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
933
934 VkPresentInfoKHR presentInfo = {};
935 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
936 presentInfo.waitSemaphoreCount = 1;
937 presentInfo.pWaitSemaphores = signalSemaphores;
938 presentInfo.swapchainCount = 1;
939 presentInfo.pSwapchains = &swapChain;
940 presentInfo.pImageIndices = &imageIndex;
941 presentInfo.pResults = nullptr;
942
943 VkResult result = vkQueuePresentKHR(presentQueue, &presentInfo);
944
945 if (result == VK_SUBOPTIMAL_KHR) {
946 shouldRecreateSwapChain = true;
947 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
948 shouldRecreateSwapChain = true;
949 return;
950 } else {
951 VKUTIL_CHECK_RESULT(result, "failed to present swap chain image!");
952 }
953
954 currentFrame = (currentFrame + 1) % swapChainImageCount;
955}
956
957void VulkanGame::recreateSwapChain() {
958 if (vkDeviceWaitIdle(device) != VK_SUCCESS) {
959 throw runtime_error("failed to wait for device!");
960 }
961
962 cleanupSwapChain();
963
964 createSwapChain();
965 createImageViews();
966 createRenderPass();
967
968 createCommandPools();
969
970 // The depth buffer does need to be recreated with the swap chain since its dimensions depend on the window size
971 // and resizing the window is a common reason to recreate the swapchain
972 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
973 depthImage, graphicsQueue);
974
975 createFramebuffers();
976
977 // TODO: Update pipelines here
978
979 createCommandBuffers();
980
981 createSyncObjects();
982
983 imageIndex = 0;
984}
985
986void VulkanGame::cleanupSwapChain() {
987 VulkanUtils::destroyVulkanImage(device, depthImage);
988
989 for (VkFramebuffer framebuffer : swapChainFramebuffers) {
990 vkDestroyFramebuffer(device, framebuffer, nullptr);
991 }
992
993 for (uint32_t i = 0; i < swapChainImageCount; i++) {
994 vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
995 vkDestroyCommandPool(device, commandPools[i], nullptr);
996 }
997
998 for (uint32_t i = 0; i < swapChainImageCount; i++) {
999 vkDestroySemaphore(device, imageAcquiredSemaphores[i], nullptr);
1000 vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr);
1001 vkDestroyFence(device, inFlightFences[i], nullptr);
1002 }
1003
1004 vkDestroyRenderPass(device, renderPass, nullptr);
1005
1006 for (VkImageView imageView : swapChainImageViews) {
1007 vkDestroyImageView(device, imageView, nullptr);
1008 }
1009
1010 vkDestroySwapchainKHR(device, swapChain, nullptr);
1011}
1012
1013void VulkanGame::renderMainScreen(int width, int height) {
1014 {
1015 int padding = 4;
1016 ImGui::SetNextWindowPos(vec2(-padding, -padding), ImGuiCond_Once);
1017 ImGui::SetNextWindowSize(vec2(width + 2 * padding, height + 2 * padding), ImGuiCond_Always);
1018 ImGui::Begin("WndMain", nullptr,
1019 ImGuiWindowFlags_NoTitleBar |
1020 ImGuiWindowFlags_NoResize |
1021 ImGuiWindowFlags_NoMove);
1022
1023 ButtonImGui btn("New Game");
1024
1025 ImGui::InvisibleButton("", vec2(10, height / 6));
1026 if (btn.draw((width - btn.getWidth()) / 2)) {
1027 goToScreen(&VulkanGame::renderGameScreen);
1028 }
1029
1030 ButtonImGui btn2("Quit");
1031
1032 ImGui::InvisibleButton("", vec2(10, 15));
1033 if (btn2.draw((width - btn2.getWidth()) / 2)) {
1034 quitGame();
1035 }
1036
1037 ImGui::End();
1038 }
1039}
1040
1041void VulkanGame::renderGameScreen(int width, int height) {
1042 {
1043 ImGui::SetNextWindowSize(vec2(130, 65), ImGuiCond_Once);
1044 ImGui::SetNextWindowPos(vec2(10, 50), ImGuiCond_Once);
1045 ImGui::Begin("WndStats", nullptr,
1046 ImGuiWindowFlags_NoTitleBar |
1047 ImGuiWindowFlags_NoResize |
1048 ImGuiWindowFlags_NoMove);
1049
1050 //ImGui::Text(ImGui::GetIO().Framerate);
1051 renderGuiValueList(valueLists["stats value list"]);
1052
1053 ImGui::End();
1054 }
1055
1056 {
1057 ImGui::SetNextWindowSize(vec2(250, 35), ImGuiCond_Once);
1058 ImGui::SetNextWindowPos(vec2(width - 260, 10), ImGuiCond_Always);
1059 ImGui::Begin("WndMenubar", nullptr,
1060 ImGuiWindowFlags_NoTitleBar |
1061 ImGuiWindowFlags_NoResize |
1062 ImGuiWindowFlags_NoMove);
1063 ImGui::InvisibleButton("", vec2(155, 18));
1064 ImGui::SameLine();
1065 if (ImGui::Button("Main Menu")) {
1066 goToScreen(&VulkanGame::renderMainScreen);
1067 }
1068 ImGui::End();
1069 }
1070
1071 {
1072 ImGui::SetNextWindowSize(vec2(200, 200), ImGuiCond_Once);
1073 ImGui::SetNextWindowPos(vec2(width - 210, 60), ImGuiCond_Always);
1074 ImGui::Begin("WndDebug", nullptr,
1075 ImGuiWindowFlags_NoTitleBar |
1076 ImGuiWindowFlags_NoResize |
1077 ImGuiWindowFlags_NoMove);
1078
1079 renderGuiValueList(valueLists["debug value list"]);
1080
1081 ImGui::End();
1082 }
1083}
1084
1085void VulkanGame::initGuiValueLists(map<string, vector<UIValue>>& valueLists) {
1086 valueLists["stats value list"] = vector<UIValue>();
1087 valueLists["debug value list"] = vector<UIValue>();
1088}
1089
1090// TODO: Probably turn this into a UI widget class
1091void VulkanGame::renderGuiValueList(vector<UIValue>& values) {
1092 float maxWidth = 0.0f;
1093 float cursorStartPos = ImGui::GetCursorPosX();
1094
1095 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
1096 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
1097
1098 if (maxWidth < textWidth)
1099 maxWidth = textWidth;
1100 }
1101
1102 stringstream ss;
1103
1104 // TODO: Possibly implement this based on gui/ui-value.hpp instead and use templates
1105 // to keep track of the type. This should make it a bit easier to use and maintain
1106 // Also, implement this in a way that's agnostic to the UI renderer.
1107 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
1108 ss.str("");
1109 ss.clear();
1110
1111 switch (it->type) {
1112 case UIVALUE_INT:
1113 ss << it->label << ": " << *(unsigned int*)it->value;
1114 break;
1115 case UIVALUE_DOUBLE:
1116 ss << it->label << ": " << *(double*)it->value;
1117 break;
1118 }
1119
1120 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
1121
1122 ImGui::SetCursorPosX(cursorStartPos + maxWidth - textWidth);
1123 //ImGui::Text("%s", ss.str().c_str());
1124 ImGui::Text("%s: %.1f", it->label.c_str(), *(float*)it->value);
1125 }
1126}
1127
1128void VulkanGame::goToScreen(void (VulkanGame::* renderScreenFn)(int width, int height)) {
1129 currentRenderScreenFn = renderScreenFn;
1130
1131 // TODO: Maybe just set shouldRecreateSwapChain to true instead. Check this render loop logic
1132 // to make sure there'd be no issues
1133 //recreateSwapChain();
1134}
1135
1136void VulkanGame::quitGame() {
1137 done = true;
1138}
Note: See TracBrowser for help on using the repository browser.