source: opengl-game/sdl-game.cpp@ 187b0f5

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

Change VulkanGame and SDLGame to only use discrete GPUs and switch the timer class to steady_clock

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