source: opengl-game/sdl-game.cpp@ 7865c5b

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

Rename surface to vulkanSurface and add an initializer list to the VulkanGame constructor

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