source: opengl-game/sdl-game.cpp@ 484334e

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

In VulkanGame, correctly recreate the swap chain during the render loop, and in SDLGame, use the GameGui class to get the window dimensions during swap chain recreation

  • Property mode set to 100644
File size: 36.4 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 // Poll and handle events (inputs, window resize, etc.)
157 // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
158 // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application.
159 // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application.
160 // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
161 SDL_Event event;
162 while (SDL_PollEvent(&event)) {
163 ImGui_ImplSDL2_ProcessEvent(&event);
164 if (event.type == SDL_QUIT)
165 done = true;
166 if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window))
167 done = true;
168 }
169
170 if (shouldRecreateSwapChain) {
171 gui->refreshWindowSize();
172
173 int width = gui->getWindowWidth();
174 int height = gui->getWindowHeight();
175 if (width > 0 && height > 0) {
176 // TODO: This should be used if the min image count changes, presumably because a new surface was created
177 // with a different image count or something like that. Maybe I want to add code to query for a new min image count
178 // during swapchain recreation to take advantage of this
179 ImGui_ImplVulkan_SetMinImageCount(swapChainMinImageCount);
180
181 recreateSwapChain();
182
183 imageIndex = 0;
184 shouldRecreateSwapChain = false;
185 }
186 }
187
188 // Start the Dear ImGui frame
189 ImGui_ImplVulkan_NewFrame();
190 ImGui_ImplSDL2_NewFrame(window);
191 ImGui::NewFrame();
192
193 // 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window.
194 {
195 static int counter = 0;
196
197 ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it.
198
199 ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too)
200
201 if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated)
202 counter++;
203 ImGui::SameLine();
204 ImGui::Text("counter = %d", counter);
205
206 ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
207 ImGui::End();
208 }
209
210 ImGui::Render();
211 ImDrawData* draw_data = ImGui::GetDrawData();
212 const bool is_minimized = (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f);
213 if (!is_minimized) {
214 renderFrame(draw_data);
215 presentFrame();
216 }
217 }
218
219 cleanup();
220
221 close_log();
222}
223
224bool VulkanGame::initUI(int width, int height, unsigned char guiFlags) {
225 // TODO: Create a game-gui function to get the gui version and retrieve it that way
226
227 SDL_VERSION(&sdlVersion); // This gets the compile-time version
228 SDL_GetVersion(&sdlVersion); // This gets the runtime version
229
230 cout << "SDL " <<
231 to_string(sdlVersion.major) << "." <<
232 to_string(sdlVersion.minor) << "." <<
233 to_string(sdlVersion.patch) << endl;
234
235 // TODO: Refactor the logger api to be more flexible,
236 // esp. since gl_log() and gl_log_err() have issues printing anything besides strings
237 restart_gl_log();
238 gl_log("starting SDL\n%s.%s.%s",
239 to_string(sdlVersion.major).c_str(),
240 to_string(sdlVersion.minor).c_str(),
241 to_string(sdlVersion.patch).c_str());
242
243 // TODO: Use open_Log() and related functions instead of gl_log ones
244 // TODO: In addition, delete the gl_log functions
245 open_log();
246 get_log() << "starting SDL" << endl;
247 get_log() <<
248 (int)sdlVersion.major << "." <<
249 (int)sdlVersion.minor << "." <<
250 (int)sdlVersion.patch << endl;
251
252 // TODO: Put all fonts, textures, and images in the assets folder
253 gui = new GameGui_SDL();
254
255 if (gui->init() == RTWO_ERROR) {
256 // TODO: Also print these sorts of errors to the log
257 cout << "UI library could not be initialized!" << endl;
258 cout << gui->getError() << endl;
259 return RTWO_ERROR;
260 }
261
262 window = (SDL_Window*)gui->createWindow("Vulkan Game", width, height, guiFlags & GUI_FLAGS_WINDOW_FULLSCREEN);
263 if (window == nullptr) {
264 cout << "Window could not be created!" << endl;
265 cout << gui->getError() << endl;
266 return RTWO_ERROR;
267 }
268
269 cout << "Target window size: (" << width << ", " << height << ")" << endl;
270 cout << "Actual window size: (" << gui->getWindowWidth() << ", " << gui->getWindowHeight() << ")" << endl;
271
272 return RTWO_SUCCESS;
273}
274
275void VulkanGame::initVulkan() {
276 const vector<const char*> validationLayers = {
277 "VK_LAYER_KHRONOS_validation"
278 };
279 const vector<const char*> deviceExtensions = {
280 VK_KHR_SWAPCHAIN_EXTENSION_NAME
281 };
282
283 createVulkanInstance(validationLayers);
284 setupDebugMessenger();
285 createVulkanSurface();
286 pickPhysicalDevice(deviceExtensions);
287 createLogicalDevice(validationLayers, deviceExtensions);
288 chooseSwapChainProperties();
289 createSwapChain();
290 createImageViews();
291 createRenderPass();
292
293 createResourceCommandPool();
294 createCommandPools();
295
296 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
297 depthImage, graphicsQueue);
298
299 createFramebuffers();
300
301 // TODO: Initialize pipelines here
302
303 createCommandBuffers();
304
305 createSyncObjects();
306}
307
308void VulkanGame::cleanup() {
309 // FIXME: We could wait on the Queue if we had the queue in wd-> (otherwise VulkanH functions can't use globals)
310 //vkQueueWaitIdle(g_Queue);
311 VKUTIL_CHECK_RESULT(vkDeviceWaitIdle(device), "failed to wait for device!");
312
313 ImGui_ImplVulkan_Shutdown();
314 ImGui_ImplSDL2_Shutdown();
315 ImGui::DestroyContext();
316
317 cleanupSwapChain();
318
319 // this would actually be destroyed in the pipeline class
320 vkDestroyDescriptorPool(device, descriptorPool, nullptr);
321
322 vkDestroyCommandPool(device, resourceCommandPool, nullptr);
323
324 vkDestroyDevice(device, nullptr);
325 vkDestroySurfaceKHR(instance, surface, nullptr);
326
327 if (ENABLE_VALIDATION_LAYERS) {
328 VulkanUtils::destroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
329 }
330
331 vkDestroyInstance(instance, nullptr);
332
333 gui->destroyWindow();
334 gui->shutdown();
335 delete gui;
336}
337
338void VulkanGame::createVulkanInstance(const vector<const char*>& validationLayers) {
339 if (ENABLE_VALIDATION_LAYERS && !VulkanUtils::checkValidationLayerSupport(validationLayers)) {
340 throw runtime_error("validation layers requested, but not available!");
341 }
342
343 VkApplicationInfo appInfo = {};
344 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
345 appInfo.pApplicationName = "Vulkan Game";
346 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
347 appInfo.pEngineName = "No Engine";
348 appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
349 appInfo.apiVersion = VK_API_VERSION_1_0;
350
351 VkInstanceCreateInfo createInfo = {};
352 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
353 createInfo.pApplicationInfo = &appInfo;
354
355 vector<const char*> extensions = gui->getRequiredExtensions();
356 if (ENABLE_VALIDATION_LAYERS) {
357 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
358 }
359
360 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
361 createInfo.ppEnabledExtensionNames = extensions.data();
362
363 cout << endl << "Extensions:" << endl;
364 for (const char* extensionName : extensions) {
365 cout << extensionName << endl;
366 }
367 cout << endl;
368
369 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
370 if (ENABLE_VALIDATION_LAYERS) {
371 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
372 createInfo.ppEnabledLayerNames = validationLayers.data();
373
374 populateDebugMessengerCreateInfo(debugCreateInfo);
375 createInfo.pNext = &debugCreateInfo;
376 }
377 else {
378 createInfo.enabledLayerCount = 0;
379
380 createInfo.pNext = nullptr;
381 }
382
383 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
384 throw runtime_error("failed to create instance!");
385 }
386}
387
388void VulkanGame::setupDebugMessenger() {
389 if (!ENABLE_VALIDATION_LAYERS) {
390 return;
391 }
392
393 VkDebugUtilsMessengerCreateInfoEXT createInfo;
394 populateDebugMessengerCreateInfo(createInfo);
395
396 if (VulkanUtils::createDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
397 throw runtime_error("failed to set up debug messenger!");
398 }
399}
400
401void VulkanGame::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
402 createInfo = {};
403 createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
404 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;
405 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;
406 createInfo.pfnUserCallback = debugCallback;
407}
408
409void VulkanGame::createVulkanSurface() {
410 if (gui->createVulkanSurface(instance, &surface) == RTWO_ERROR) {
411 throw runtime_error("failed to create window surface!");
412 }
413}
414
415void VulkanGame::pickPhysicalDevice(const vector<const char*>& deviceExtensions) {
416 uint32_t deviceCount = 0;
417 // TODO: Check VkResult
418 vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
419
420 if (deviceCount == 0) {
421 throw runtime_error("failed to find GPUs with Vulkan support!");
422 }
423
424 vector<VkPhysicalDevice> devices(deviceCount);
425 // TODO: Check VkResult
426 vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
427
428 cout << endl << "Graphics cards:" << endl;
429 for (const VkPhysicalDevice& device : devices) {
430 if (isDeviceSuitable(device, deviceExtensions)) {
431 physicalDevice = device;
432 break;
433 }
434 }
435 cout << endl;
436
437 if (physicalDevice == VK_NULL_HANDLE) {
438 throw runtime_error("failed to find a suitable GPU!");
439 }
440}
441
442bool VulkanGame::isDeviceSuitable(VkPhysicalDevice physicalDevice, const vector<const char*>& deviceExtensions) {
443 VkPhysicalDeviceProperties deviceProperties;
444 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
445
446 cout << "Device: " << deviceProperties.deviceName << endl;
447
448 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
449 bool extensionsSupported = VulkanUtils::checkDeviceExtensionSupport(physicalDevice, deviceExtensions);
450 bool swapChainAdequate = false;
451
452 if (extensionsSupported) {
453 vector<VkSurfaceFormatKHR> formats = VulkanUtils::querySwapChainFormats(physicalDevice, surface);
454 vector<VkPresentModeKHR> presentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, surface);
455
456 swapChainAdequate = !formats.empty() && !presentModes.empty();
457 }
458
459 VkPhysicalDeviceFeatures supportedFeatures;
460 vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
461
462 return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
463}
464
465void VulkanGame::createLogicalDevice(const vector<const char*>& validationLayers,
466 const vector<const char*>& deviceExtensions) {
467 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
468
469 if (!indices.isComplete()) {
470 throw runtime_error("failed to find required queue families!");
471 }
472
473 // TODO: Using separate graphics and present queues currently works, but I should verify that I'm
474 // using them correctly to get the most benefit out of separate queues
475
476 vector<VkDeviceQueueCreateInfo> queueCreateInfoList;
477 set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
478
479 float queuePriority = 1.0f;
480 for (uint32_t queueFamily : uniqueQueueFamilies) {
481 VkDeviceQueueCreateInfo queueCreateInfo = {};
482 queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
483 queueCreateInfo.queueCount = 1;
484 queueCreateInfo.queueFamilyIndex = queueFamily;
485 queueCreateInfo.pQueuePriorities = &queuePriority;
486
487 queueCreateInfoList.push_back(queueCreateInfo);
488 }
489
490 VkPhysicalDeviceFeatures deviceFeatures = {};
491 deviceFeatures.samplerAnisotropy = VK_TRUE;
492
493 VkDeviceCreateInfo createInfo = {};
494 createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
495
496 createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfoList.size());
497 createInfo.pQueueCreateInfos = queueCreateInfoList.data();
498
499 createInfo.pEnabledFeatures = &deviceFeatures;
500
501 createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
502 createInfo.ppEnabledExtensionNames = deviceExtensions.data();
503
504 // These fields are ignored by up-to-date Vulkan implementations,
505 // but it's a good idea to set them for backwards compatibility
506 if (ENABLE_VALIDATION_LAYERS) {
507 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
508 createInfo.ppEnabledLayerNames = validationLayers.data();
509 }
510 else {
511 createInfo.enabledLayerCount = 0;
512 }
513
514 if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
515 throw runtime_error("failed to create logical device!");
516 }
517
518 vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
519 vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
520}
521
522void VulkanGame::chooseSwapChainProperties() {
523 vector<VkSurfaceFormatKHR> availableFormats = VulkanUtils::querySwapChainFormats(physicalDevice, surface);
524 vector<VkPresentModeKHR> availablePresentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, surface);
525
526 // Per Spec Format and View Format are expected to be the same unless VK_IMAGE_CREATE_MUTABLE_BIT was set at image creation
527 // Assuming that the default behavior is without setting this bit, there is no need for separate Swapchain image and image view format
528 // Additionally several new color spaces were introduced with Vulkan Spec v1.0.40,
529 // hence we must make sure that a format with the mostly available color space, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, is found and used.
530 swapChainSurfaceFormat = VulkanUtils::chooseSwapSurfaceFormat(availableFormats,
531 { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM },
532 VK_COLOR_SPACE_SRGB_NONLINEAR_KHR);
533
534#ifdef IMGUI_UNLIMITED_FRAME_RATE
535 vector<VkPresentModeKHR> presentModes{
536 VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR
537 };
538#else
539 vector<VkPresentModeKHR> presentModes{ VK_PRESENT_MODE_FIFO_KHR };
540#endif
541
542 swapChainPresentMode = VulkanUtils::chooseSwapPresentMode(availablePresentModes, presentModes);
543
544 cout << "[vulkan] Selected PresentMode = " << swapChainPresentMode << endl;
545
546 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, surface);
547
548 // If min image count was not specified, request different count of images dependent on selected present mode
549 if (swapChainMinImageCount == 0) {
550 if (swapChainPresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
551 swapChainMinImageCount = 3;
552 }
553 else if (swapChainPresentMode == VK_PRESENT_MODE_FIFO_KHR || swapChainPresentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) {
554 swapChainMinImageCount = 2;
555 }
556 else if (swapChainPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
557 swapChainMinImageCount = 1;
558 }
559 else {
560 throw runtime_error("unexpected present mode!");
561 }
562 }
563
564 if (swapChainMinImageCount < capabilities.minImageCount) {
565 swapChainMinImageCount = capabilities.minImageCount;
566 }
567 else if (capabilities.maxImageCount != 0 && swapChainMinImageCount > capabilities.maxImageCount) {
568 swapChainMinImageCount = capabilities.maxImageCount;
569 }
570}
571
572void VulkanGame::createSwapChain() {
573 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, surface);
574
575 swapChainExtent = VulkanUtils::chooseSwapExtent(capabilities, gui->getWindowWidth(), gui->getWindowHeight());
576
577 VkSwapchainCreateInfoKHR createInfo = {};
578 createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
579 createInfo.surface = surface;
580 createInfo.minImageCount = swapChainMinImageCount;
581 createInfo.imageFormat = swapChainSurfaceFormat.format;
582 createInfo.imageColorSpace = swapChainSurfaceFormat.colorSpace;
583 createInfo.imageExtent = swapChainExtent;
584 createInfo.imageArrayLayers = 1;
585 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
586
587 // TODO: Maybe save this result so I don't have to recalculate it every time
588 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
589 uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
590
591 if (indices.graphicsFamily != indices.presentFamily) {
592 createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
593 createInfo.queueFamilyIndexCount = 2;
594 createInfo.pQueueFamilyIndices = queueFamilyIndices;
595 }
596 else {
597 createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
598 createInfo.queueFamilyIndexCount = 0;
599 createInfo.pQueueFamilyIndices = nullptr;
600 }
601
602 createInfo.preTransform = capabilities.currentTransform;
603 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
604 createInfo.presentMode = swapChainPresentMode;
605 createInfo.clipped = VK_TRUE;
606 createInfo.oldSwapchain = VK_NULL_HANDLE;
607
608 if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
609 throw runtime_error("failed to create swap chain!");
610 }
611
612 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, nullptr) != VK_SUCCESS) {
613 throw runtime_error("failed to get swap chain image count!");
614 }
615
616 swapChainImages.resize(swapChainImageCount);
617 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, swapChainImages.data()) != VK_SUCCESS) {
618 throw runtime_error("failed to get swap chain images!");
619 }
620}
621
622void VulkanGame::createImageViews() {
623 swapChainImageViews.resize(swapChainImageCount);
624
625 for (uint32_t i = 0; i < swapChainImageViews.size(); i++) {
626 swapChainImageViews[i] = VulkanUtils::createImageView(device, swapChainImages[i], swapChainSurfaceFormat.format,
627 VK_IMAGE_ASPECT_COLOR_BIT);
628 }
629}
630
631void VulkanGame::createRenderPass() {
632 VkAttachmentDescription colorAttachment = {};
633 colorAttachment.format = swapChainSurfaceFormat.format;
634 colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
635 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // Set to VK_ATTACHMENT_LOAD_OP_DONT_CARE to disable clearing
636 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
637 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
638 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
639 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
640 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
641
642 VkAttachmentReference colorAttachmentRef = {};
643 colorAttachmentRef.attachment = 0;
644 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
645
646 VkAttachmentDescription depthAttachment = {};
647 depthAttachment.format = findDepthFormat();
648 depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
649 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
650 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
651 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
652 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
653 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
654 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
655
656 VkAttachmentReference depthAttachmentRef = {};
657 depthAttachmentRef.attachment = 1;
658 depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
659
660 VkSubpassDescription subpass = {};
661 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
662 subpass.colorAttachmentCount = 1;
663 subpass.pColorAttachments = &colorAttachmentRef;
664 //subpass.pDepthStencilAttachment = &depthAttachmentRef;
665
666 VkSubpassDependency dependency = {};
667 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
668 dependency.dstSubpass = 0;
669 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
670 dependency.srcAccessMask = 0;
671 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
672 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
673
674 array<VkAttachmentDescription, 2> attachments = { colorAttachment, depthAttachment };
675 VkRenderPassCreateInfo renderPassInfo = {};
676 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
677 renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
678 renderPassInfo.pAttachments = attachments.data();
679 renderPassInfo.subpassCount = 1;
680 renderPassInfo.pSubpasses = &subpass;
681 renderPassInfo.dependencyCount = 1;
682 renderPassInfo.pDependencies = &dependency;
683
684 if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
685 throw runtime_error("failed to create render pass!");
686 }
687
688 // We do not create a pipeline by default as this is also used by examples' main.cpp,
689 // but secondary viewport in multi-viewport mode may want to create one with:
690 //ImGui_ImplVulkan_CreatePipeline(device, g_Allocator, VK_NULL_HANDLE, g_MainWindowData.RenderPass, VK_SAMPLE_COUNT_1_BIT, &g_MainWindowData.Pipeline);
691}
692
693VkFormat VulkanGame::findDepthFormat() {
694 return VulkanUtils::findSupportedFormat(
695 physicalDevice,
696 { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT },
697 VK_IMAGE_TILING_OPTIMAL,
698 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
699 );
700}
701
702void VulkanGame::createResourceCommandPool() {
703 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
704
705 VkCommandPoolCreateInfo poolInfo = {};
706 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
707 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
708 poolInfo.flags = 0;
709
710 if (vkCreateCommandPool(device, &poolInfo, nullptr, &resourceCommandPool) != VK_SUCCESS) {
711 throw runtime_error("failed to create resource command pool!");
712 }
713}
714
715void VulkanGame::createCommandPools() {
716 commandPools.resize(swapChainImageCount);
717
718 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
719
720 for (size_t i = 0; i < swapChainImageCount; i++) {
721 VkCommandPoolCreateInfo poolInfo = {};
722 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
723 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
724 poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
725
726 if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPools[i]) != VK_SUCCESS) {
727 throw runtime_error("failed to create graphics command pool!");
728 }
729 }
730}
731
732void VulkanGame::createFramebuffers() {
733 swapChainFramebuffers.resize(swapChainImageCount);
734
735 VkFramebufferCreateInfo framebufferInfo = {};
736 framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
737 framebufferInfo.renderPass = renderPass;
738 framebufferInfo.width = swapChainExtent.width;
739 framebufferInfo.height = swapChainExtent.height;
740 framebufferInfo.layers = 1;
741
742 for (size_t i = 0; i < swapChainImageCount; i++) {
743 array<VkImageView, 2> attachments = {
744 swapChainImageViews[i],
745 depthImage.imageView
746 };
747
748 framebufferInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
749 framebufferInfo.pAttachments = attachments.data();
750
751 if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
752 throw runtime_error("failed to create framebuffer!");
753 }
754 }
755}
756
757void VulkanGame::createCommandBuffers() {
758 commandBuffers.resize(swapChainImageCount);
759
760 for (size_t i = 0; i < swapChainImageCount; i++) {
761 VkCommandBufferAllocateInfo allocInfo = {};
762 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
763 allocInfo.commandPool = commandPools[i];
764 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
765 allocInfo.commandBufferCount = 1;
766
767 if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffers[i]) != VK_SUCCESS) {
768 throw runtime_error("failed to allocate command buffer!");
769 }
770 }
771}
772
773void VulkanGame::createSyncObjects() {
774 imageAcquiredSemaphores.resize(swapChainImageCount);
775 renderCompleteSemaphores.resize(swapChainImageCount);
776 inFlightFences.resize(swapChainImageCount);
777
778 VkSemaphoreCreateInfo semaphoreInfo = {};
779 semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
780
781 VkFenceCreateInfo fenceInfo = {};
782 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
783 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
784
785 for (size_t i = 0; i < swapChainImageCount; i++) {
786 if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAcquiredSemaphores[i]) != VK_SUCCESS) {
787 throw runtime_error("failed to create image acquired sempahore for a frame!");
788 }
789
790 if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderCompleteSemaphores[i]) != VK_SUCCESS) {
791 throw runtime_error("failed to create render complete sempahore for a frame!");
792 }
793
794 if (vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) {
795 throw runtime_error("failed to create fence for a frame!");
796 }
797 }
798}
799
800void VulkanGame::renderFrame(ImDrawData* draw_data) {
801 VkResult result = vkAcquireNextImageKHR(device, swapChain, numeric_limits<uint64_t>::max(),
802 imageAcquiredSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
803
804 if (result == VK_SUBOPTIMAL_KHR) {
805 shouldRecreateSwapChain = true;
806 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
807 shouldRecreateSwapChain = true;
808 return;
809 } else {
810 VKUTIL_CHECK_RESULT(result, "failed to acquire swap chain image!");
811 }
812
813 VKUTIL_CHECK_RESULT(
814 vkWaitForFences(device, 1, &inFlightFences[imageIndex], VK_TRUE, numeric_limits<uint64_t>::max()),
815 "failed waiting for fence!");
816
817 VKUTIL_CHECK_RESULT(vkResetFences(device, 1, &inFlightFences[imageIndex]),
818 "failed to reset fence!");
819
820 VKUTIL_CHECK_RESULT(vkResetCommandPool(device, commandPools[imageIndex], 0),
821 "failed to reset command pool!");
822
823 VkCommandBufferBeginInfo beginInfo = {};
824 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
825 beginInfo.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
826
827 VKUTIL_CHECK_RESULT(vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo),
828 "failed to begin recording command buffer!");
829
830 VkRenderPassBeginInfo renderPassInfo = {};
831 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
832 renderPassInfo.renderPass = renderPass;
833 renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
834 renderPassInfo.renderArea.extent = swapChainExtent;
835
836 array<VkClearValue, 2> clearValues = {};
837 clearValues[0].color = { { 0.45f, 0.55f, 0.60f, 1.00f } };
838 clearValues[1].depthStencil = { 1.0f, 0 };
839
840 renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
841 renderPassInfo.pClearValues = clearValues.data();
842
843 vkCmdBeginRenderPass(commandBuffers[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
844
845 ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffers[imageIndex]);
846
847 vkCmdEndRenderPass(commandBuffers[imageIndex]);
848
849 VKUTIL_CHECK_RESULT(vkEndCommandBuffer(commandBuffers[imageIndex]),
850 "failed to record command buffer!");
851
852 VkSemaphore waitSemaphores[] = { imageAcquiredSemaphores[currentFrame] };
853 VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
854 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
855
856 VkSubmitInfo submitInfo = {};
857 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
858 submitInfo.waitSemaphoreCount = 1;
859 submitInfo.pWaitSemaphores = waitSemaphores;
860 submitInfo.pWaitDstStageMask = waitStages;
861 submitInfo.commandBufferCount = 1;
862 submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
863 submitInfo.signalSemaphoreCount = 1;
864 submitInfo.pSignalSemaphores = signalSemaphores;
865
866 VKUTIL_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[imageIndex]),
867 "failed to submit draw command buffer!");
868}
869
870void VulkanGame::presentFrame() {
871 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
872
873 VkPresentInfoKHR presentInfo = {};
874 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
875 presentInfo.waitSemaphoreCount = 1;
876 presentInfo.pWaitSemaphores = signalSemaphores;
877 presentInfo.swapchainCount = 1;
878 presentInfo.pSwapchains = &swapChain;
879 presentInfo.pImageIndices = &imageIndex;
880 presentInfo.pResults = nullptr;
881
882 VkResult result = vkQueuePresentKHR(presentQueue, &presentInfo);
883
884 if (result == VK_SUBOPTIMAL_KHR) {
885 shouldRecreateSwapChain = true;
886 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
887 shouldRecreateSwapChain = true;
888 return;
889 } else {
890 VKUTIL_CHECK_RESULT(result, "failed to present swap chain image!");
891 }
892
893 currentFrame = (currentFrame + 1) % swapChainImageCount;
894}
895
896void VulkanGame::recreateSwapChain() {
897 if (vkDeviceWaitIdle(device) != VK_SUCCESS) {
898 throw runtime_error("failed to wait for device!");
899 }
900
901 cleanupSwapChain();
902
903 createSwapChain();
904 createImageViews();
905 createRenderPass();
906
907 createCommandPools();
908
909 // The depth buffer does need to be recreated with the swap chain since its dimensions depend on the window size
910 // and resizing the window is a common reason to recreate the swapchain
911 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
912 depthImage, graphicsQueue);
913
914 createFramebuffers();
915
916 // TODO: Update pipelines here
917
918 createCommandBuffers();
919
920 createSyncObjects();
921}
922
923void VulkanGame::cleanupSwapChain() {
924 VulkanUtils::destroyVulkanImage(device, depthImage);
925
926 for (VkFramebuffer framebuffer : swapChainFramebuffers) {
927 vkDestroyFramebuffer(device, framebuffer, nullptr);
928 }
929
930 for (uint32_t i = 0; i < swapChainImageCount; i++) {
931 vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
932 vkDestroyCommandPool(device, commandPools[i], nullptr);
933 }
934
935 for (uint32_t i = 0; i < swapChainImageCount; i++) {
936 vkDestroySemaphore(device, imageAcquiredSemaphores[i], nullptr);
937 vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr);
938 vkDestroyFence(device, inFlightFences[i], nullptr);
939 }
940
941 vkDestroyRenderPass(device, renderPass, nullptr);
942
943 for (VkImageView imageView : swapChainImageViews) {
944 vkDestroyImageView(device, imageView, nullptr);
945 }
946
947 vkDestroySwapchainKHR(device, swapChain, nullptr);
948}
Note: See TracBrowser for help on using the repository browser.