source: opengl-game/sdl-game.cpp@ 40eb092

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

In SDLGame, implement the game UI using ImGui

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