source: opengl-game/sdl-game.cpp@ 5081b9a

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

Add prevTime and elapsedTime to SDLGame and refactor the event-handling code a little bit

  • Property mode set to 100644
File size: 43.0 KB
Line 
1#include "sdl-game.hpp"
2
3#include <array>
4#include <iostream>
5#include <set>
6
7#include "IMGUI/imgui_impl_sdl.h"
8
9#include "logger.hpp"
10
11#include "gui/imgui/button-imgui.hpp"
12
13using namespace std;
14
15#define IMGUI_UNLIMITED_FRAME_RATE
16
17static void check_imgui_vk_result(VkResult res) {
18 if (res == VK_SUCCESS) {
19 return;
20 }
21
22 ostringstream oss;
23 oss << "[imgui] Vulkan error! VkResult is \"" << VulkanUtils::resultString(res) << "\"" << __LINE__;
24 if (res < 0) {
25 throw runtime_error("Fatal: " + oss.str());
26 } else {
27 cerr << oss.str();
28 }
29}
30
31VKAPI_ATTR VkBool32 VKAPI_CALL VulkanGame::debugCallback(
32 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
33 VkDebugUtilsMessageTypeFlagsEXT messageType,
34 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
35 void* pUserData) {
36 cerr << "validation layer: " << pCallbackData->pMessage << endl;
37
38 // TODO: Figure out what the return value means and if it should always be VK_FALSE
39 return VK_FALSE;
40}
41
42VulkanGame::VulkanGame()
43 : swapChainImageCount(0)
44 , swapChainMinImageCount(0)
45 , swapChainSurfaceFormat({})
46 , swapChainPresentMode(VK_PRESENT_MODE_MAX_ENUM_KHR)
47 , swapChainExtent{ 0, 0 }
48 , swapChain(VK_NULL_HANDLE)
49 , vulkanSurface(VK_NULL_HANDLE)
50 , sdlVersion({ 0, 0, 0 })
51 , instance(VK_NULL_HANDLE)
52 , physicalDevice(VK_NULL_HANDLE)
53 , device(VK_NULL_HANDLE)
54 , debugMessenger(VK_NULL_HANDLE)
55 , resourceCommandPool(VK_NULL_HANDLE)
56 , renderPass(VK_NULL_HANDLE)
57 , graphicsQueue(VK_NULL_HANDLE)
58 , presentQueue(VK_NULL_HANDLE)
59 , depthImage({})
60 , shouldRecreateSwapChain(false)
61 , frameCount(0)
62 , currentFrame(0)
63 , imageIndex(0)
64 , fpsStartTime(0.0f)
65 , curTime(0.0f)
66 , done(false)
67 , currentRenderScreenFn(nullptr)
68 , imguiDescriptorPool(VK_NULL_HANDLE)
69 , gui(nullptr)
70 , window(nullptr)
71 , score(0)
72 , fps(0.0f) {
73}
74
75VulkanGame::~VulkanGame() {
76}
77
78void VulkanGame::run(int width, int height, unsigned char guiFlags) {
79 cout << "DEBUGGING IS " << (ENABLE_VALIDATION_LAYERS ? "ON" : "OFF") << endl;
80
81 cout << "Vulkan Game" << endl;
82
83 if (initUI(width, height, guiFlags) == RTWO_ERROR) {
84 return;
85 }
86
87 initVulkan();
88
89 initImGuiOverlay();
90
91 currentRenderScreenFn = &VulkanGame::renderMainScreen;
92
93 ImGuiIO& io = ImGui::GetIO();
94
95 initGuiValueLists(valueLists);
96
97 valueLists["stats value list"].push_back(UIValue(UIVALUE_INT, "Score", &score));
98 valueLists["stats value list"].push_back(UIValue(UIVALUE_DOUBLE, "FPS", &fps));
99 valueLists["stats value list"].push_back(UIValue(UIVALUE_DOUBLE, "IMGUI FPS", &io.Framerate));
100
101 renderLoop();
102 cleanup();
103
104 close_log();
105}
106
107bool VulkanGame::initUI(int width, int height, unsigned char guiFlags) {
108 // TODO: Create a game-gui function to get the gui version and retrieve it that way
109
110 SDL_VERSION(&sdlVersion); // This gets the compile-time version
111 SDL_GetVersion(&sdlVersion); // This gets the runtime version
112
113 cout << "SDL " <<
114 to_string(sdlVersion.major) << "." <<
115 to_string(sdlVersion.minor) << "." <<
116 to_string(sdlVersion.patch) << endl;
117
118 // TODO: Refactor the logger api to be more flexible,
119 // esp. since gl_log() and gl_log_err() have issues printing anything besides strings
120 restart_gl_log();
121 gl_log("starting SDL\n%s.%s.%s",
122 to_string(sdlVersion.major).c_str(),
123 to_string(sdlVersion.minor).c_str(),
124 to_string(sdlVersion.patch).c_str());
125
126 // TODO: Use open_Log() and related functions instead of gl_log ones
127 // TODO: In addition, delete the gl_log functions
128 open_log();
129 get_log() << "starting SDL" << endl;
130 get_log() <<
131 (int)sdlVersion.major << "." <<
132 (int)sdlVersion.minor << "." <<
133 (int)sdlVersion.patch << endl;
134
135 // TODO: Put all fonts, textures, and images in the assets folder
136 gui = new GameGui_SDL();
137
138 if (gui->init() == RTWO_ERROR) {
139 // TODO: Also print these sorts of errors to the log
140 cout << "UI library could not be initialized!" << endl;
141 cout << gui->getError() << endl;
142 // TODO: Rename RTWO_ERROR to something else
143 return RTWO_ERROR;
144 }
145
146 window = (SDL_Window*)gui->createWindow("Vulkan Game", width, height, guiFlags & GUI_FLAGS_WINDOW_FULLSCREEN);
147 if (window == nullptr) {
148 cout << "Window could not be created!" << endl;
149 cout << gui->getError() << endl;
150 return RTWO_ERROR;
151 }
152
153 cout << "Target window size: (" << width << ", " << height << ")" << endl;
154 cout << "Actual window size: (" << gui->getWindowWidth() << ", " << gui->getWindowHeight() << ")" << endl;
155
156 return RTWO_SUCCESS;
157}
158
159void VulkanGame::initVulkan() {
160 const vector<const char*> validationLayers = {
161 "VK_LAYER_KHRONOS_validation"
162 };
163 const vector<const char*> deviceExtensions = {
164 VK_KHR_SWAPCHAIN_EXTENSION_NAME
165 };
166
167 createVulkanInstance(validationLayers);
168 setupDebugMessenger();
169 createVulkanSurface();
170 pickPhysicalDevice(deviceExtensions);
171 createLogicalDevice(validationLayers, deviceExtensions);
172 chooseSwapChainProperties();
173 createSwapChain();
174 createImageViews();
175
176 createResourceCommandPool();
177 createImageResources();
178
179 createRenderPass();
180 createCommandPools();
181 createFramebuffers();
182 createCommandBuffers();
183 createSyncObjects();
184}
185
186void VulkanGame::renderLoop() {
187 startTime = steady_clock::now();
188 curTime = duration<float, seconds::period>(steady_clock::now() - startTime).count();
189
190 fpsStartTime = curTime;
191 frameCount = 0;
192
193 ImGuiIO& io = ImGui::GetIO();
194
195 done = false;
196 while (!done) {
197
198 prevTime = curTime;
199 curTime = duration<float, seconds::period>(steady_clock::now() - startTime).count();
200 elapsedTime = curTime - prevTime;
201
202 if (curTime - fpsStartTime >= 1.0f) {
203 fps = (float)frameCount / (curTime - fpsStartTime);
204
205 frameCount = 0;
206 fpsStartTime = curTime;
207 }
208
209 frameCount++;
210
211 gui->processEvents();
212
213 UIEvent uiEvent;
214 while (gui->pollEvent(&uiEvent)) {
215 GameEvent& e = uiEvent.event;
216 SDL_Event sdlEvent = uiEvent.rawEvent.sdl;
217
218 ImGui_ImplSDL2_ProcessEvent(&sdlEvent);
219 if ((e.type == UI_EVENT_MOUSEBUTTONDOWN || e.type == UI_EVENT_MOUSEBUTTONUP || e.type == UI_EVENT_UNKNOWN) &&
220 io.WantCaptureMouse) {
221 if (sdlEvent.type == SDL_MOUSEWHEEL || sdlEvent.type == SDL_MOUSEBUTTONDOWN ||
222 sdlEvent.type == SDL_MOUSEBUTTONUP) {
223 continue;
224 }
225 }
226 if ((e.type == UI_EVENT_KEYDOWN || e.type == UI_EVENT_KEYUP) && io.WantCaptureKeyboard) {
227 if (sdlEvent.type == SDL_KEYDOWN || sdlEvent.type == SDL_KEYUP) {
228 continue;
229 }
230 }
231 if (io.WantTextInput) {
232 // show onscreen keyboard if on mobile
233 }
234
235 switch (e.type) {
236 case UI_EVENT_QUIT:
237 cout << "Quit event detected" << endl;
238 done = true;
239 break;
240 case UI_EVENT_WINDOWRESIZE:
241 cout << "Window resize event detected" << endl;
242 shouldRecreateSwapChain = true;
243 break;
244 case UI_EVENT_KEYUP:
245 // START UNREVIEWED SECTION
246 // END UNREVIEWED SECTION
247 break;
248 case UI_EVENT_WINDOW:
249 case UI_EVENT_MOUSEBUTTONDOWN:
250 case UI_EVENT_MOUSEBUTTONUP:
251 case UI_EVENT_MOUSEMOTION:
252 break;
253 case UI_EVENT_UNHANDLED:
254 cout << "Unhandled event type: 0x" << hex << sdlEvent.type << dec << endl;
255 break;
256 case UI_EVENT_UNKNOWN:
257 default:
258 cout << "Unknown event type: 0x" << hex << sdlEvent.type << dec << endl;
259 break;
260 }
261 }
262
263 if (shouldRecreateSwapChain) {
264 gui->refreshWindowSize();
265 const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
266
267 if (!isMinimized) {
268 // TODO: This should be used if the min image count changes, presumably because a new surface was created
269 // with a different image count or something like that. Maybe I want to add code to query for a new min image count
270 // during swapchain recreation to take advantage of this
271 ImGui_ImplVulkan_SetMinImageCount(swapChainMinImageCount);
272
273 recreateSwapChain();
274
275 shouldRecreateSwapChain = false;
276 }
277 }
278
279 // TODO: Move this into a renderImGuiOverlay() function
280 ImGui_ImplVulkan_NewFrame();
281 ImGui_ImplSDL2_NewFrame(window);
282 ImGui::NewFrame();
283
284 (this->*currentRenderScreenFn)(gui->getWindowWidth(), gui->getWindowHeight());
285
286 ImGui::Render();
287
288 gui->refreshWindowSize();
289 const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
290
291 if (!isMinimized) {
292 renderFrame(ImGui::GetDrawData());
293 presentFrame();
294 }
295 }
296}
297
298void VulkanGame::cleanup() {
299 // FIXME: We could wait on the Queue if we had the queue in wd-> (otherwise VulkanH functions can't use globals)
300 //vkQueueWaitIdle(g_Queue);
301 VKUTIL_CHECK_RESULT(vkDeviceWaitIdle(device), "failed to wait for device!");
302
303 cleanupImGuiOverlay();
304
305 cleanupSwapChain();
306
307 vkDestroyCommandPool(device, resourceCommandPool, nullptr);
308
309 vkDestroyDevice(device, nullptr);
310 vkDestroySurfaceKHR(instance, vulkanSurface, nullptr);
311
312 if (ENABLE_VALIDATION_LAYERS) {
313 VulkanUtils::destroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
314 }
315
316 vkDestroyInstance(instance, nullptr);
317
318 gui->destroyWindow();
319 gui->shutdown();
320 delete gui;
321}
322
323void VulkanGame::createVulkanInstance(const vector<const char*>& validationLayers) {
324 if (ENABLE_VALIDATION_LAYERS && !VulkanUtils::checkValidationLayerSupport(validationLayers)) {
325 throw runtime_error("validation layers requested, but not available!");
326 }
327
328 VkApplicationInfo appInfo = {};
329 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
330 appInfo.pApplicationName = "Vulkan Game";
331 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
332 appInfo.pEngineName = "No Engine";
333 appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
334 appInfo.apiVersion = VK_API_VERSION_1_0;
335
336 VkInstanceCreateInfo createInfo = {};
337 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
338 createInfo.pApplicationInfo = &appInfo;
339
340 vector<const char*> extensions = gui->getRequiredExtensions();
341 if (ENABLE_VALIDATION_LAYERS) {
342 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
343 }
344
345 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
346 createInfo.ppEnabledExtensionNames = extensions.data();
347
348 cout << endl << "Extensions:" << endl;
349 for (const char* extensionName : extensions) {
350 cout << extensionName << endl;
351 }
352 cout << endl;
353
354 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
355 if (ENABLE_VALIDATION_LAYERS) {
356 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
357 createInfo.ppEnabledLayerNames = validationLayers.data();
358
359 populateDebugMessengerCreateInfo(debugCreateInfo);
360 createInfo.pNext = &debugCreateInfo;
361 }
362 else {
363 createInfo.enabledLayerCount = 0;
364
365 createInfo.pNext = nullptr;
366 }
367
368 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
369 throw runtime_error("failed to create instance!");
370 }
371}
372
373void VulkanGame::setupDebugMessenger() {
374 if (!ENABLE_VALIDATION_LAYERS) {
375 return;
376 }
377
378 VkDebugUtilsMessengerCreateInfoEXT createInfo;
379 populateDebugMessengerCreateInfo(createInfo);
380
381 if (VulkanUtils::createDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
382 throw runtime_error("failed to set up debug messenger!");
383 }
384}
385
386void VulkanGame::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
387 createInfo = {};
388 createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
389 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;
390 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;
391 createInfo.pfnUserCallback = debugCallback;
392}
393
394void VulkanGame::createVulkanSurface() {
395 if (gui->createVulkanSurface(instance, &vulkanSurface) == RTWO_ERROR) {
396 throw runtime_error("failed to create window surface!");
397 }
398}
399
400void VulkanGame::pickPhysicalDevice(const vector<const char*>& deviceExtensions) {
401 uint32_t deviceCount = 0;
402 // TODO: Check VkResult
403 vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
404
405 if (deviceCount == 0) {
406 throw runtime_error("failed to find GPUs with Vulkan support!");
407 }
408
409 vector<VkPhysicalDevice> devices(deviceCount);
410 // TODO: Check VkResult
411 vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
412
413 cout << endl << "Graphics cards:" << endl;
414 for (const VkPhysicalDevice& device : devices) {
415 if (isDeviceSuitable(device, deviceExtensions)) {
416 physicalDevice = device;
417 break;
418 }
419 }
420 cout << endl;
421
422 if (physicalDevice == VK_NULL_HANDLE) {
423 throw runtime_error("failed to find a suitable GPU!");
424 }
425}
426
427bool VulkanGame::isDeviceSuitable(VkPhysicalDevice physicalDevice, const vector<const char*>& deviceExtensions) {
428 VkPhysicalDeviceProperties deviceProperties;
429 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
430
431 cout << "Device: " << deviceProperties.deviceName << endl;
432
433 // TODO: Eventually, maybe let the user pick out of a set of GPUs in case the user does want to use
434 // an integrated GPU. On my laptop, this function returns TRUE for the integrated GPU, but crashes
435 // when trying to use it to render. Maybe I just need to figure out which other extensions and features
436 // to check.
437 if (deviceProperties.deviceType != VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
438 return false;
439 }
440
441 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
442 bool extensionsSupported = VulkanUtils::checkDeviceExtensionSupport(physicalDevice, deviceExtensions);
443 bool swapChainAdequate = false;
444
445 if (extensionsSupported) {
446 vector<VkSurfaceFormatKHR> formats = VulkanUtils::querySwapChainFormats(physicalDevice, vulkanSurface);
447 vector<VkPresentModeKHR> presentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, vulkanSurface);
448
449 swapChainAdequate = !formats.empty() && !presentModes.empty();
450 }
451
452 VkPhysicalDeviceFeatures supportedFeatures;
453 vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
454
455 return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
456}
457
458void VulkanGame::createLogicalDevice(const vector<const char*>& validationLayers,
459 const vector<const char*>& deviceExtensions) {
460 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
461
462 if (!indices.isComplete()) {
463 throw runtime_error("failed to find required queue families!");
464 }
465
466 // TODO: Using separate graphics and present queues currently works, but I should verify that I'm
467 // using them correctly to get the most benefit out of separate queues
468
469 vector<VkDeviceQueueCreateInfo> queueCreateInfoList;
470 set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
471
472 float queuePriority = 1.0f;
473 for (uint32_t queueFamily : uniqueQueueFamilies) {
474 VkDeviceQueueCreateInfo queueCreateInfo = {};
475 queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
476 queueCreateInfo.queueCount = 1;
477 queueCreateInfo.queueFamilyIndex = queueFamily;
478 queueCreateInfo.pQueuePriorities = &queuePriority;
479
480 queueCreateInfoList.push_back(queueCreateInfo);
481 }
482
483 VkPhysicalDeviceFeatures deviceFeatures = {};
484 deviceFeatures.samplerAnisotropy = VK_TRUE;
485
486 VkDeviceCreateInfo createInfo = {};
487 createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
488
489 createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfoList.size());
490 createInfo.pQueueCreateInfos = queueCreateInfoList.data();
491
492 createInfo.pEnabledFeatures = &deviceFeatures;
493
494 createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
495 createInfo.ppEnabledExtensionNames = deviceExtensions.data();
496
497 // These fields are ignored by up-to-date Vulkan implementations,
498 // but it's a good idea to set them for backwards compatibility
499 if (ENABLE_VALIDATION_LAYERS) {
500 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
501 createInfo.ppEnabledLayerNames = validationLayers.data();
502 }
503 else {
504 createInfo.enabledLayerCount = 0;
505 }
506
507 if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
508 throw runtime_error("failed to create logical device!");
509 }
510
511 vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
512 vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
513}
514
515void VulkanGame::chooseSwapChainProperties() {
516 vector<VkSurfaceFormatKHR> availableFormats = VulkanUtils::querySwapChainFormats(physicalDevice, vulkanSurface);
517 vector<VkPresentModeKHR> availablePresentModes = VulkanUtils::querySwapChainPresentModes(physicalDevice, vulkanSurface);
518
519 // Per Spec Format and View Format are expected to be the same unless VK_IMAGE_CREATE_MUTABLE_BIT was set at image creation
520 // Assuming that the default behavior is without setting this bit, there is no need for separate Swapchain image and image view format
521 // Additionally several new color spaces were introduced with Vulkan Spec v1.0.40,
522 // hence we must make sure that a format with the mostly available color space, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, is found and used.
523 swapChainSurfaceFormat = VulkanUtils::chooseSwapSurfaceFormat(availableFormats,
524 { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM },
525 VK_COLOR_SPACE_SRGB_NONLINEAR_KHR);
526
527#ifdef IMGUI_UNLIMITED_FRAME_RATE
528 vector<VkPresentModeKHR> presentModes{
529 VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR
530 };
531#else
532 vector<VkPresentModeKHR> presentModes{ VK_PRESENT_MODE_FIFO_KHR };
533#endif
534
535 swapChainPresentMode = VulkanUtils::chooseSwapPresentMode(availablePresentModes, presentModes);
536
537 cout << "[vulkan] Selected PresentMode = " << swapChainPresentMode << endl;
538
539 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, vulkanSurface);
540
541 // If min image count was not specified, request different count of images dependent on selected present mode
542 if (swapChainMinImageCount == 0) {
543 if (swapChainPresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
544 swapChainMinImageCount = 3;
545 }
546 else if (swapChainPresentMode == VK_PRESENT_MODE_FIFO_KHR || swapChainPresentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) {
547 swapChainMinImageCount = 2;
548 }
549 else if (swapChainPresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
550 swapChainMinImageCount = 1;
551 }
552 else {
553 throw runtime_error("unexpected present mode!");
554 }
555 }
556
557 if (swapChainMinImageCount < capabilities.minImageCount) {
558 swapChainMinImageCount = capabilities.minImageCount;
559 }
560 else if (capabilities.maxImageCount != 0 && swapChainMinImageCount > capabilities.maxImageCount) {
561 swapChainMinImageCount = capabilities.maxImageCount;
562 }
563}
564
565void VulkanGame::createSwapChain() {
566 VkSurfaceCapabilitiesKHR capabilities = VulkanUtils::querySwapChainCapabilities(physicalDevice, vulkanSurface);
567
568 swapChainExtent = VulkanUtils::chooseSwapExtent(capabilities, gui->getWindowWidth(), gui->getWindowHeight());
569
570 VkSwapchainCreateInfoKHR createInfo = {};
571 createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
572 createInfo.surface = vulkanSurface;
573 createInfo.minImageCount = swapChainMinImageCount;
574 createInfo.imageFormat = swapChainSurfaceFormat.format;
575 createInfo.imageColorSpace = swapChainSurfaceFormat.colorSpace;
576 createInfo.imageExtent = swapChainExtent;
577 createInfo.imageArrayLayers = 1;
578 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
579
580 // TODO: Maybe save this result so I don't have to recalculate it every time
581 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
582 uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
583
584 if (indices.graphicsFamily != indices.presentFamily) {
585 createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
586 createInfo.queueFamilyIndexCount = 2;
587 createInfo.pQueueFamilyIndices = queueFamilyIndices;
588 }
589 else {
590 createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
591 createInfo.queueFamilyIndexCount = 0;
592 createInfo.pQueueFamilyIndices = nullptr;
593 }
594
595 createInfo.preTransform = capabilities.currentTransform;
596 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
597 createInfo.presentMode = swapChainPresentMode;
598 createInfo.clipped = VK_TRUE;
599 createInfo.oldSwapchain = VK_NULL_HANDLE;
600
601 if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
602 throw runtime_error("failed to create swap chain!");
603 }
604
605 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, nullptr) != VK_SUCCESS) {
606 throw runtime_error("failed to get swap chain image count!");
607 }
608
609 swapChainImages.resize(swapChainImageCount);
610 if (vkGetSwapchainImagesKHR(device, swapChain, &swapChainImageCount, swapChainImages.data()) != VK_SUCCESS) {
611 throw runtime_error("failed to get swap chain images!");
612 }
613}
614
615void VulkanGame::createImageViews() {
616 swapChainImageViews.resize(swapChainImageCount);
617
618 for (uint32_t i = 0; i < swapChainImageViews.size(); i++) {
619 swapChainImageViews[i] = VulkanUtils::createImageView(device, swapChainImages[i], swapChainSurfaceFormat.format,
620 VK_IMAGE_ASPECT_COLOR_BIT);
621 }
622}
623
624void VulkanGame::createResourceCommandPool() {
625 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
626
627 VkCommandPoolCreateInfo poolInfo = {};
628 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
629 poolInfo.queueFamilyIndex = indices.graphicsFamily.value();
630 poolInfo.flags = 0;
631
632 if (vkCreateCommandPool(device, &poolInfo, nullptr, &resourceCommandPool) != VK_SUCCESS) {
633 throw runtime_error("failed to create resource command pool!");
634 }
635}
636
637void VulkanGame::createImageResources() {
638 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
639 depthImage, graphicsQueue);
640}
641
642VkFormat VulkanGame::findDepthFormat() {
643 return VulkanUtils::findSupportedFormat(
644 physicalDevice,
645 { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT },
646 VK_IMAGE_TILING_OPTIMAL,
647 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
648 );
649}
650
651void VulkanGame::createRenderPass() {
652 VkAttachmentDescription colorAttachment = {};
653 colorAttachment.format = swapChainSurfaceFormat.format;
654 colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
655 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // Set to VK_ATTACHMENT_LOAD_OP_DONT_CARE to disable clearing
656 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
657 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
658 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
659 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
660 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
661
662 VkAttachmentReference colorAttachmentRef = {};
663 colorAttachmentRef.attachment = 0;
664 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
665
666 VkAttachmentDescription depthAttachment = {};
667 depthAttachment.format = findDepthFormat();
668 depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
669 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
670 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
671 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
672 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
673 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
674 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
675
676 VkAttachmentReference depthAttachmentRef = {};
677 depthAttachmentRef.attachment = 1;
678 depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
679
680 VkSubpassDescription subpass = {};
681 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
682 subpass.colorAttachmentCount = 1;
683 subpass.pColorAttachments = &colorAttachmentRef;
684 //subpass.pDepthStencilAttachment = &depthAttachmentRef;
685
686 VkSubpassDependency dependency = {};
687 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
688 dependency.dstSubpass = 0;
689 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
690 dependency.srcAccessMask = 0;
691 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
692 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
693
694 array<VkAttachmentDescription, 2> attachments = { colorAttachment, depthAttachment };
695 VkRenderPassCreateInfo renderPassInfo = {};
696 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
697 renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
698 renderPassInfo.pAttachments = attachments.data();
699 renderPassInfo.subpassCount = 1;
700 renderPassInfo.pSubpasses = &subpass;
701 renderPassInfo.dependencyCount = 1;
702 renderPassInfo.pDependencies = &dependency;
703
704 if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
705 throw runtime_error("failed to create render pass!");
706 }
707
708 // We do not create a pipeline by default as this is also used by examples' main.cpp,
709 // but secondary viewport in multi-viewport mode may want to create one with:
710 //ImGui_ImplVulkan_CreatePipeline(device, g_Allocator, VK_NULL_HANDLE, g_MainWindowData.RenderPass, VK_SAMPLE_COUNT_1_BIT, &g_MainWindowData.Pipeline);
711}
712
713void VulkanGame::createCommandPools() {
714 commandPools.resize(swapChainImageCount);
715
716 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
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 VKUTIL_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAcquiredSemaphores[i]),
785 "failed to create image acquired sempahore for a frame!");
786
787 VKUTIL_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderCompleteSemaphores[i]),
788 "failed to create render complete sempahore for a frame!");
789
790 VKUTIL_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]),
791 "failed to create fence for a frame!");
792 }
793}
794
795void VulkanGame::initImGuiOverlay() {
796 vector<VkDescriptorPoolSize> pool_sizes {
797 { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
798 { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
799 { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
800 { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 },
801 { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 },
802 { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 },
803 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 },
804 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 },
805 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 },
806 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 },
807 { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 }
808 };
809
810 VkDescriptorPoolCreateInfo pool_info = {};
811 pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
812 pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
813 pool_info.maxSets = 1000 * pool_sizes.size();
814 pool_info.poolSizeCount = static_cast<uint32_t>(pool_sizes.size());
815 pool_info.pPoolSizes = pool_sizes.data();
816
817 VKUTIL_CHECK_RESULT(vkCreateDescriptorPool(device, &pool_info, nullptr, &imguiDescriptorPool),
818 "failed to create IMGUI descriptor pool!");
819
820 // TODO: Do this in one place and save it instead of redoing it every time I need a queue family index
821 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, vulkanSurface);
822
823 // Setup Dear ImGui context
824 IMGUI_CHECKVERSION();
825 ImGui::CreateContext();
826 ImGuiIO& io = ImGui::GetIO();
827 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
828 //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
829
830 // Setup Dear ImGui style
831 ImGui::StyleColorsDark();
832 //ImGui::StyleColorsClassic();
833
834 // Setup Platform/Renderer bindings
835 ImGui_ImplSDL2_InitForVulkan(window);
836 ImGui_ImplVulkan_InitInfo init_info = {};
837 init_info.Instance = instance;
838 init_info.PhysicalDevice = physicalDevice;
839 init_info.Device = device;
840 init_info.QueueFamily = indices.graphicsFamily.value();
841 init_info.Queue = graphicsQueue;
842 init_info.DescriptorPool = imguiDescriptorPool;
843 init_info.Allocator = nullptr;
844 init_info.MinImageCount = swapChainMinImageCount;
845 init_info.ImageCount = swapChainImageCount;
846 init_info.CheckVkResultFn = check_imgui_vk_result;
847 ImGui_ImplVulkan_Init(&init_info, renderPass);
848
849 // Load Fonts
850 // - 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.
851 // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
852 // - 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).
853 // - 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.
854 // - Read 'docs/FONTS.md' for more instructions and details.
855 // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
856 //io.Fonts->AddFontDefault();
857 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
858 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
859 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
860 //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f);
861 //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
862 //assert(font != NULL);
863
864 // Upload Fonts
865
866 VkCommandBuffer commandBuffer = VulkanUtils::beginSingleTimeCommands(device, resourceCommandPool);
867
868 ImGui_ImplVulkan_CreateFontsTexture(commandBuffer);
869
870 VulkanUtils::endSingleTimeCommands(device, resourceCommandPool, commandBuffer, graphicsQueue);
871
872 ImGui_ImplVulkan_DestroyFontUploadObjects();
873}
874
875void VulkanGame::cleanupImGuiOverlay() {
876 ImGui_ImplVulkan_Shutdown();
877 ImGui_ImplSDL2_Shutdown();
878 ImGui::DestroyContext();
879
880 vkDestroyDescriptorPool(device, imguiDescriptorPool, nullptr);
881}
882
883void VulkanGame::renderFrame(ImDrawData* draw_data) {
884 VkResult result = vkAcquireNextImageKHR(device, swapChain, numeric_limits<uint64_t>::max(),
885 imageAcquiredSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
886
887 if (result == VK_SUBOPTIMAL_KHR) {
888 shouldRecreateSwapChain = true;
889 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
890 shouldRecreateSwapChain = true;
891 return;
892 } else {
893 VKUTIL_CHECK_RESULT(result, "failed to acquire swap chain image!");
894 }
895
896 VKUTIL_CHECK_RESULT(
897 vkWaitForFences(device, 1, &inFlightFences[imageIndex], VK_TRUE, numeric_limits<uint64_t>::max()),
898 "failed waiting for fence!");
899
900 VKUTIL_CHECK_RESULT(vkResetFences(device, 1, &inFlightFences[imageIndex]),
901 "failed to reset fence!");
902
903 VKUTIL_CHECK_RESULT(vkResetCommandPool(device, commandPools[imageIndex], 0),
904 "failed to reset command pool!");
905
906 VkCommandBufferBeginInfo beginInfo = {};
907 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
908 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
909
910 VKUTIL_CHECK_RESULT(vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo),
911 "failed to begin recording command buffer!");
912
913 VkRenderPassBeginInfo renderPassInfo = {};
914 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
915 renderPassInfo.renderPass = renderPass;
916 renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
917 renderPassInfo.renderArea.offset = { 0, 0 };
918 renderPassInfo.renderArea.extent = swapChainExtent;
919
920 array<VkClearValue, 2> clearValues = {};
921 clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
922 clearValues[1].depthStencil = { 1.0f, 0 };
923
924 renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
925 renderPassInfo.pClearValues = clearValues.data();
926
927 vkCmdBeginRenderPass(commandBuffers[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
928
929 ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffers[imageIndex]);
930
931 vkCmdEndRenderPass(commandBuffers[imageIndex]);
932
933 VKUTIL_CHECK_RESULT(vkEndCommandBuffer(commandBuffers[imageIndex]),
934 "failed to record command buffer!");
935
936 VkSemaphore waitSemaphores[] = { imageAcquiredSemaphores[currentFrame] };
937 VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
938 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
939
940 VkSubmitInfo submitInfo = {};
941 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
942 submitInfo.waitSemaphoreCount = 1;
943 submitInfo.pWaitSemaphores = waitSemaphores;
944 submitInfo.pWaitDstStageMask = waitStages;
945 submitInfo.commandBufferCount = 1;
946 submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
947 submitInfo.signalSemaphoreCount = 1;
948 submitInfo.pSignalSemaphores = signalSemaphores;
949
950 VKUTIL_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[imageIndex]),
951 "failed to submit draw command buffer!");
952}
953
954void VulkanGame::presentFrame() {
955 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
956
957 VkPresentInfoKHR presentInfo = {};
958 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
959 presentInfo.waitSemaphoreCount = 1;
960 presentInfo.pWaitSemaphores = signalSemaphores;
961 presentInfo.swapchainCount = 1;
962 presentInfo.pSwapchains = &swapChain;
963 presentInfo.pImageIndices = &imageIndex;
964 presentInfo.pResults = nullptr;
965
966 VkResult result = vkQueuePresentKHR(presentQueue, &presentInfo);
967
968 if (result == VK_SUBOPTIMAL_KHR) {
969 shouldRecreateSwapChain = true;
970 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
971 shouldRecreateSwapChain = true;
972 return;
973 } else {
974 VKUTIL_CHECK_RESULT(result, "failed to present swap chain image!");
975 }
976
977 currentFrame = (currentFrame + 1) % swapChainImageCount;
978}
979
980void VulkanGame::recreateSwapChain() {
981 if (vkDeviceWaitIdle(device) != VK_SUCCESS) {
982 throw runtime_error("failed to wait for device!");
983 }
984
985 cleanupSwapChain();
986
987 createSwapChain();
988 createImageViews();
989
990 // The depth buffer does need to be recreated with the swap chain since its dimensions depend on the window size
991 // and resizing the window is a common reason to recreate the swapchain
992 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
993 depthImage, graphicsQueue);
994
995 createRenderPass();
996 createCommandPools();
997 createFramebuffers();
998 createCommandBuffers();
999 createSyncObjects();
1000
1001 // TODO: Update pipelines here
1002
1003 imageIndex = 0;
1004}
1005
1006void VulkanGame::cleanupSwapChain() {
1007 VulkanUtils::destroyVulkanImage(device, depthImage);
1008
1009 for (VkFramebuffer framebuffer : swapChainFramebuffers) {
1010 vkDestroyFramebuffer(device, framebuffer, nullptr);
1011 }
1012
1013 for (uint32_t i = 0; i < swapChainImageCount; i++) {
1014 vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
1015 vkDestroyCommandPool(device, commandPools[i], nullptr);
1016 }
1017
1018 for (uint32_t i = 0; i < swapChainImageCount; i++) {
1019 vkDestroySemaphore(device, imageAcquiredSemaphores[i], nullptr);
1020 vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr);
1021 vkDestroyFence(device, inFlightFences[i], nullptr);
1022 }
1023
1024 vkDestroyRenderPass(device, renderPass, nullptr);
1025
1026 for (VkImageView imageView : swapChainImageViews) {
1027 vkDestroyImageView(device, imageView, nullptr);
1028 }
1029
1030 vkDestroySwapchainKHR(device, swapChain, nullptr);
1031}
1032
1033void VulkanGame::renderMainScreen(int width, int height) {
1034 {
1035 int padding = 4;
1036 ImGui::SetNextWindowPos(vec2(-padding, -padding), ImGuiCond_Once);
1037 ImGui::SetNextWindowSize(vec2(width + 2 * padding, height + 2 * padding), ImGuiCond_Always);
1038 ImGui::Begin("WndMain", nullptr,
1039 ImGuiWindowFlags_NoTitleBar |
1040 ImGuiWindowFlags_NoResize |
1041 ImGuiWindowFlags_NoMove);
1042
1043 ButtonImGui btn("New Game");
1044
1045 ImGui::InvisibleButton("", vec2(10, height / 6));
1046 if (btn.draw((width - btn.getWidth()) / 2)) {
1047 goToScreen(&VulkanGame::renderGameScreen);
1048 }
1049
1050 ButtonImGui btn2("Quit");
1051
1052 ImGui::InvisibleButton("", vec2(10, 15));
1053 if (btn2.draw((width - btn2.getWidth()) / 2)) {
1054 quitGame();
1055 }
1056
1057 ImGui::End();
1058 }
1059}
1060
1061void VulkanGame::renderGameScreen(int width, int height) {
1062 {
1063 ImGui::SetNextWindowSize(vec2(130, 65), ImGuiCond_Once);
1064 ImGui::SetNextWindowPos(vec2(10, 50), ImGuiCond_Once);
1065 ImGui::Begin("WndStats", nullptr,
1066 ImGuiWindowFlags_NoTitleBar |
1067 ImGuiWindowFlags_NoResize |
1068 ImGuiWindowFlags_NoMove);
1069
1070 //ImGui::Text(ImGui::GetIO().Framerate);
1071 renderGuiValueList(valueLists["stats value list"]);
1072
1073 ImGui::End();
1074 }
1075
1076 {
1077 ImGui::SetNextWindowSize(vec2(250, 35), ImGuiCond_Once);
1078 ImGui::SetNextWindowPos(vec2(width - 260, 10), ImGuiCond_Always);
1079 ImGui::Begin("WndMenubar", nullptr,
1080 ImGuiWindowFlags_NoTitleBar |
1081 ImGuiWindowFlags_NoResize |
1082 ImGuiWindowFlags_NoMove);
1083 ImGui::InvisibleButton("", vec2(155, 18));
1084 ImGui::SameLine();
1085 if (ImGui::Button("Main Menu")) {
1086 goToScreen(&VulkanGame::renderMainScreen);
1087 }
1088 ImGui::End();
1089 }
1090
1091 {
1092 ImGui::SetNextWindowSize(vec2(200, 200), ImGuiCond_Once);
1093 ImGui::SetNextWindowPos(vec2(width - 210, 60), ImGuiCond_Always);
1094 ImGui::Begin("WndDebug", nullptr,
1095 ImGuiWindowFlags_NoTitleBar |
1096 ImGuiWindowFlags_NoResize |
1097 ImGuiWindowFlags_NoMove);
1098
1099 renderGuiValueList(valueLists["debug value list"]);
1100
1101 ImGui::End();
1102 }
1103}
1104
1105void VulkanGame::initGuiValueLists(map<string, vector<UIValue>>& valueLists) {
1106 valueLists["stats value list"] = vector<UIValue>();
1107 valueLists["debug value list"] = vector<UIValue>();
1108}
1109
1110// TODO: Probably turn this into a UI widget class
1111void VulkanGame::renderGuiValueList(vector<UIValue>& values) {
1112 float maxWidth = 0.0f;
1113 float cursorStartPos = ImGui::GetCursorPosX();
1114
1115 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
1116 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
1117
1118 if (maxWidth < textWidth)
1119 maxWidth = textWidth;
1120 }
1121
1122 stringstream ss;
1123
1124 // TODO: Possibly implement this based on gui/ui-value.hpp instead and use templates
1125 // to keep track of the type. This should make it a bit easier to use and maintain
1126 // Also, implement this in a way that's agnostic to the UI renderer.
1127 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
1128 ss.str("");
1129 ss.clear();
1130
1131 switch (it->type) {
1132 case UIVALUE_INT:
1133 ss << it->label << ": " << *(unsigned int*)it->value;
1134 break;
1135 case UIVALUE_DOUBLE:
1136 ss << it->label << ": " << *(double*)it->value;
1137 break;
1138 }
1139
1140 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
1141
1142 ImGui::SetCursorPosX(cursorStartPos + maxWidth - textWidth);
1143 //ImGui::Text("%s", ss.str().c_str());
1144 ImGui::Text("%s: %.1f", it->label.c_str(), *(float*)it->value);
1145 }
1146}
1147
1148void VulkanGame::goToScreen(void (VulkanGame::* renderScreenFn)(int width, int height)) {
1149 currentRenderScreenFn = renderScreenFn;
1150
1151 // TODO: Maybe just set shouldRecreateSwapChain to true instead. Check this render loop logic
1152 // to make sure there'd be no issues
1153 //recreateSwapChain();
1154}
1155
1156void VulkanGame::quitGame() {
1157 done = true;
1158}
Note: See TracBrowser for help on using the repository browser.