source: opengl-game/sdl-game.cpp@ 737c26a

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

Perform minor reformatting in SDLGame

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