source: opengl-game/sdl-game.cpp@ 880cfc2

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

Remove some of the example windows from SDLGame and cleanup some code

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