source: opengl-game/sdl-game.cpp@ 81869ef

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

Avoid rendering frames while the window is minimized

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