source: opengl-game/sdl-game.cpp@ 6053b24

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

Read events from GameGui in both VulkanGame and SDLGame and don't pass
events to the main application when IMGUI wants to capture them.

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