source: opengl-game/sdl-game.cpp@ ce9dc9f

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

Remove all dependencies on VulkanH functions and structures from SDLGame

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