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

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

Modify SDLGame to use createImageResources, initImGuiOverlay, and cleanupImGuiOverlay

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