source: opengl-game/vulkan-game.cpp@ a0da009

feature/imgui-sdl points-test
Last change on this file since a0da009 was a0da009, checked in by Dmitry Portnoy <dmitry.portnoy@…>, 5 years ago

Add a window resize callback in gamegui and add an unknown event type for events that aren't currently handeld

  • Property mode set to 100644
File size: 18.5 KB
Line 
1#include "vulkan-game.hpp"
2
3#include <array>
4#include <iostream>
5#include <set>
6
7#include "consts.hpp"
8#include "logger.hpp"
9
10#include "vulkan-utils.hpp"
11
12using namespace std;
13
14VulkanGame::VulkanGame() {
15 gui = nullptr;
16 window = nullptr;
17}
18
19VulkanGame::~VulkanGame() {
20}
21
22void VulkanGame::run(int width, int height, unsigned char guiFlags) {
23 cout << "DEBUGGING IS " << (ENABLE_VALIDATION_LAYERS ? "ON" : "OFF") << endl;
24
25 cout << "Vulkan Game" << endl;
26
27 // This gets the runtime version, use SDL_VERSION() for the comppile-time version
28 // TODO: Create a game-gui function to get the gui version and retrieve it that way
29 SDL_GetVersion(&sdlVersion);
30
31 // TODO: Refactor the logger api to be more flexible,
32 // esp. since gl_log() and gl_log_err() have issues printing anything besides stirngs
33 restart_gl_log();
34 gl_log("starting SDL\n%s.%s.%s",
35 to_string(sdlVersion.major).c_str(),
36 to_string(sdlVersion.minor).c_str(),
37 to_string(sdlVersion.patch).c_str());
38
39 open_log();
40 get_log() << "starting SDL" << endl;
41 get_log() <<
42 (int)sdlVersion.major << "." <<
43 (int)sdlVersion.minor << "." <<
44 (int)sdlVersion.patch << endl;
45
46 if (initWindow(width, height, guiFlags) == RTWO_ERROR) {
47 return;
48 }
49
50 initVulkan();
51 mainLoop();
52 cleanup();
53
54 close_log();
55}
56
57// TODO: Make some more initi functions, or call this initUI if the
58// amount of things initialized here keeps growing
59bool VulkanGame::initWindow(int width, int height, unsigned char guiFlags) {
60 // TODO: Put all fonts, textures, and images in the assets folder
61 gui = new GameGui_SDL();
62
63 if (gui->init() == RTWO_ERROR) {
64 // TODO: Also print these sorts of errors to the log
65 cout << "UI library could not be initialized!" << endl;
66 cout << gui->getError() << endl;
67 return RTWO_ERROR;
68 }
69
70 window = (SDL_Window*) gui->createWindow("Vulkan Game", width, height, guiFlags & GUI_FLAGS_WINDOW_FULLSCREEN);
71 if (window == nullptr) {
72 cout << "Window could not be created!" << endl;
73 cout << gui->getError() << endl;
74 return RTWO_ERROR;
75 }
76
77 cout << "Target window size: (" << width << ", " << height << ")" << endl;
78 cout << "Actual window size: (" << gui->getWindowWidth() << ", " << gui->getWindowHeight() << ")" << endl;
79
80 renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
81 if (renderer == nullptr) {
82 cout << "Renderer could not be created!" << endl;
83 cout << gui->getError() << endl;
84 return RTWO_ERROR;
85 }
86
87 return RTWO_SUCCESS;
88}
89
90void VulkanGame::initVulkan() {
91 const vector<const char*> validationLayers = {
92 "VK_LAYER_KHRONOS_validation"
93 };
94 const vector<const char*> deviceExtensions = {
95 VK_KHR_SWAPCHAIN_EXTENSION_NAME
96 };
97
98 createVulkanInstance(validationLayers);
99 setupDebugMessenger();
100 createVulkanSurface();
101 pickPhysicalDevice(deviceExtensions);
102 createLogicalDevice(validationLayers, deviceExtensions);
103 createSwapChain();
104 createImageViews();
105 createRenderPass();
106 createCommandPool();
107}
108
109void VulkanGame::mainLoop() {
110 UIEvent e;
111 bool quit = false;
112
113 SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
114
115 while (!quit) {
116 gui->processEvents();
117
118 while (gui->pollEvent(&e)) {
119 switch(e.type) {
120 case UI_EVENT_QUIT:
121 cout << "Quit event detected" << endl;
122 quit = true;
123 break;
124 case UI_EVENT_WINDOW:
125 cout << "Window event detected" << endl;
126 // Currently unused
127 break;
128 case UI_EVENT_WINDOWRESIZE:
129 cout << "Window resize event detected" << endl;
130 framebufferResized = true;
131 break;
132 case UI_EVENT_KEY:
133 if (e.key.keycode == SDL_SCANCODE_ESCAPE) {
134 quit = true;
135 } else {
136 cout << "Key event detected" << endl;
137 }
138 break;
139 case UI_EVENT_MOUSEBUTTONDOWN:
140 cout << "Mouse button down event detected" << endl;
141 break;
142 case UI_EVENT_MOUSEBUTTONUP:
143 cout << "Mouse button up event detected" << endl;
144 break;
145 case UI_EVENT_MOUSEMOTION:
146 break;
147 case UI_EVENT_UNKNOWN:
148 cout << "Unknown event type: 0x" << hex << e.unknown.eventType << dec << endl;
149 break;
150 default:
151 cout << "Unhandled UI event: " << e.type << endl;
152 }
153 }
154
155 renderUI();
156 renderScene();
157 }
158
159 vkDeviceWaitIdle(device);
160}
161
162void VulkanGame::renderUI() {
163 SDL_RenderClear(renderer);
164 SDL_RenderPresent(renderer);
165}
166
167void VulkanGame::renderScene() {
168}
169
170void VulkanGame::cleanup() {
171 cleanupSwapChain();
172
173 vkDestroyCommandPool(device, commandPool, nullptr);
174 vkDestroyDevice(device, nullptr);
175 vkDestroySurfaceKHR(instance, surface, nullptr);
176
177 if (ENABLE_VALIDATION_LAYERS) {
178 VulkanUtils::destroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
179 }
180
181 vkDestroyInstance(instance, nullptr);
182
183 SDL_DestroyRenderer(renderer);
184 renderer = nullptr;
185
186 gui->destroyWindow();
187 gui->shutdown();
188 delete gui;
189}
190
191void VulkanGame::createVulkanInstance(const vector<const char*> &validationLayers) {
192 if (ENABLE_VALIDATION_LAYERS && !VulkanUtils::checkValidationLayerSupport(validationLayers)) {
193 throw runtime_error("validation layers requested, but not available!");
194 }
195
196 VkApplicationInfo appInfo = {};
197 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
198 appInfo.pApplicationName = "Vulkan Game";
199 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
200 appInfo.pEngineName = "No Engine";
201 appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
202 appInfo.apiVersion = VK_API_VERSION_1_0;
203
204 VkInstanceCreateInfo createInfo = {};
205 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
206 createInfo.pApplicationInfo = &appInfo;
207
208 vector<const char*> extensions = gui->getRequiredExtensions();
209 if (ENABLE_VALIDATION_LAYERS) {
210 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
211 }
212
213 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
214 createInfo.ppEnabledExtensionNames = extensions.data();
215
216 cout << endl << "Extensions:" << endl;
217 for (const char* extensionName : extensions) {
218 cout << extensionName << endl;
219 }
220 cout << endl;
221
222 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
223 if (ENABLE_VALIDATION_LAYERS) {
224 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
225 createInfo.ppEnabledLayerNames = validationLayers.data();
226
227 populateDebugMessengerCreateInfo(debugCreateInfo);
228 createInfo.pNext = &debugCreateInfo;
229 } else {
230 createInfo.enabledLayerCount = 0;
231
232 createInfo.pNext = nullptr;
233 }
234
235 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
236 throw runtime_error("failed to create instance!");
237 }
238}
239
240void VulkanGame::setupDebugMessenger() {
241 if (!ENABLE_VALIDATION_LAYERS) return;
242
243 VkDebugUtilsMessengerCreateInfoEXT createInfo;
244 populateDebugMessengerCreateInfo(createInfo);
245
246 if (VulkanUtils::createDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
247 throw runtime_error("failed to set up debug messenger!");
248 }
249}
250
251void VulkanGame::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
252 createInfo = {};
253 createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
254 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;
255 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;
256 createInfo.pfnUserCallback = debugCallback;
257}
258
259VKAPI_ATTR VkBool32 VKAPI_CALL VulkanGame::debugCallback(
260 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
261 VkDebugUtilsMessageTypeFlagsEXT messageType,
262 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
263 void* pUserData) {
264 cerr << "validation layer: " << pCallbackData->pMessage << endl;
265
266 return VK_FALSE;
267}
268
269void VulkanGame::createVulkanSurface() {
270 if (gui->createVulkanSurface(instance, &surface) == RTWO_ERROR) {
271 throw runtime_error("failed to create window surface!");
272 }
273}
274
275void VulkanGame::pickPhysicalDevice(const vector<const char*>& deviceExtensions) {
276 uint32_t deviceCount = 0;
277 vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
278
279 if (deviceCount == 0) {
280 throw runtime_error("failed to find GPUs with Vulkan support!");
281 }
282
283 vector<VkPhysicalDevice> devices(deviceCount);
284 vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
285
286 cout << endl << "Graphics cards:" << endl;
287 for (const VkPhysicalDevice& device : devices) {
288 if (isDeviceSuitable(device, deviceExtensions)) {
289 physicalDevice = device;
290 break;
291 }
292 }
293 cout << endl;
294
295 if (physicalDevice == VK_NULL_HANDLE) {
296 throw runtime_error("failed to find a suitable GPU!");
297 }
298}
299
300bool VulkanGame::isDeviceSuitable(VkPhysicalDevice physicalDevice,
301 const vector<const char*>& deviceExtensions) {
302 VkPhysicalDeviceProperties deviceProperties;
303 vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
304
305 cout << "Device: " << deviceProperties.deviceName << endl;
306
307 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
308 bool extensionsSupported = VulkanUtils::checkDeviceExtensionSupport(physicalDevice, deviceExtensions);
309 bool swapChainAdequate = false;
310
311 if (extensionsSupported) {
312 SwapChainSupportDetails swapChainSupport = VulkanUtils::querySwapChainSupport(physicalDevice, surface);
313 swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
314 }
315
316 VkPhysicalDeviceFeatures supportedFeatures;
317 vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
318
319 return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
320}
321
322void VulkanGame::createLogicalDevice(
323 const vector<const char*> validationLayers,
324 const vector<const char*>& deviceExtensions) {
325 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
326
327 vector<VkDeviceQueueCreateInfo> queueCreateInfos;
328 set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
329
330 float queuePriority = 1.0f;
331 for (uint32_t queueFamily : uniqueQueueFamilies) {
332 VkDeviceQueueCreateInfo queueCreateInfo = {};
333 queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
334 queueCreateInfo.queueFamilyIndex = queueFamily;
335 queueCreateInfo.queueCount = 1;
336 queueCreateInfo.pQueuePriorities = &queuePriority;
337
338 queueCreateInfos.push_back(queueCreateInfo);
339 }
340
341 VkPhysicalDeviceFeatures deviceFeatures = {};
342 deviceFeatures.samplerAnisotropy = VK_TRUE;
343
344 VkDeviceCreateInfo createInfo = {};
345 createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
346 createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
347 createInfo.pQueueCreateInfos = queueCreateInfos.data();
348
349 createInfo.pEnabledFeatures = &deviceFeatures;
350
351 createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
352 createInfo.ppEnabledExtensionNames = deviceExtensions.data();
353
354 // These fields are ignored by up-to-date Vulkan implementations,
355 // but it's a good idea to set them for backwards compatibility
356 if (ENABLE_VALIDATION_LAYERS) {
357 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
358 createInfo.ppEnabledLayerNames = validationLayers.data();
359 } else {
360 createInfo.enabledLayerCount = 0;
361 }
362
363 if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
364 throw runtime_error("failed to create logical device!");
365 }
366
367 vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
368 vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
369}
370
371void VulkanGame::createSwapChain() {
372 SwapChainSupportDetails swapChainSupport = VulkanUtils::querySwapChainSupport(physicalDevice, surface);
373
374 VkSurfaceFormatKHR surfaceFormat = VulkanUtils::chooseSwapSurfaceFormat(swapChainSupport.formats);
375 VkPresentModeKHR presentMode = VulkanUtils::chooseSwapPresentMode(swapChainSupport.presentModes);
376 VkExtent2D extent = VulkanUtils::chooseSwapExtent(swapChainSupport.capabilities, gui->getWindowWidth(), gui->getWindowHeight());
377
378 uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
379 if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
380 imageCount = swapChainSupport.capabilities.maxImageCount;
381 }
382
383 VkSwapchainCreateInfoKHR createInfo = {};
384 createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
385 createInfo.surface = surface;
386 createInfo.minImageCount = imageCount;
387 createInfo.imageFormat = surfaceFormat.format;
388 createInfo.imageColorSpace = surfaceFormat.colorSpace;
389 createInfo.imageExtent = extent;
390 createInfo.imageArrayLayers = 1;
391 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
392
393 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
394 uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
395
396 if (indices.graphicsFamily != indices.presentFamily) {
397 createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
398 createInfo.queueFamilyIndexCount = 2;
399 createInfo.pQueueFamilyIndices = queueFamilyIndices;
400 } else {
401 createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
402 createInfo.queueFamilyIndexCount = 0;
403 createInfo.pQueueFamilyIndices = nullptr;
404 }
405
406 createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
407 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
408 createInfo.presentMode = presentMode;
409 createInfo.clipped = VK_TRUE;
410 createInfo.oldSwapchain = VK_NULL_HANDLE;
411
412 if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
413 throw runtime_error("failed to create swap chain!");
414 }
415
416 vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
417 swapChainImages.resize(imageCount);
418 vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
419
420 swapChainImageFormat = surfaceFormat.format;
421 swapChainExtent = extent;
422}
423
424void VulkanGame::createImageViews() {
425 swapChainImageViews.resize(swapChainImages.size());
426
427 for (size_t i = 0; i < swapChainImages.size(); i++) {
428 swapChainImageViews[i] = VulkanUtils::createImageView(device, swapChainImages[i], swapChainImageFormat,
429 VK_IMAGE_ASPECT_COLOR_BIT);
430 }
431}
432
433void VulkanGame::createRenderPass() {
434 VkAttachmentDescription colorAttachment = {};
435 colorAttachment.format = swapChainImageFormat;
436 colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
437 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
438 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
439 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
440 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
441 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
442 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
443
444 VkAttachmentReference colorAttachmentRef = {};
445 colorAttachmentRef.attachment = 0;
446 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
447
448 VkAttachmentDescription depthAttachment = {};
449 depthAttachment.format = findDepthFormat();
450 depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
451 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
452 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
453 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
454 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
455 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
456 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
457
458 VkAttachmentReference depthAttachmentRef = {};
459 depthAttachmentRef.attachment = 1;
460 depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
461
462 VkSubpassDescription subpass = {};
463 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
464 subpass.colorAttachmentCount = 1;
465 subpass.pColorAttachments = &colorAttachmentRef;
466 subpass.pDepthStencilAttachment = &depthAttachmentRef;
467
468 VkSubpassDependency dependency = {};
469 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
470 dependency.dstSubpass = 0;
471 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
472 dependency.srcAccessMask = 0;
473 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
474 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
475
476 array<VkAttachmentDescription, 2> attachments = { colorAttachment, depthAttachment };
477 VkRenderPassCreateInfo renderPassInfo = {};
478 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
479 renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
480 renderPassInfo.pAttachments = attachments.data();
481 renderPassInfo.subpassCount = 1;
482 renderPassInfo.pSubpasses = &subpass;
483 renderPassInfo.dependencyCount = 1;
484 renderPassInfo.pDependencies = &dependency;
485
486 if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
487 throw runtime_error("failed to create render pass!");
488 }
489}
490
491VkFormat VulkanGame::findDepthFormat() {
492 return VulkanUtils::findSupportedFormat(
493 physicalDevice,
494 { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT },
495 VK_IMAGE_TILING_OPTIMAL,
496 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
497 );
498}
499
500void VulkanGame::createCommandPool() {
501 QueueFamilyIndices queueFamilyIndices = VulkanUtils::findQueueFamilies(physicalDevice, surface);;
502
503 VkCommandPoolCreateInfo poolInfo = {};
504 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
505 poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();
506 poolInfo.flags = 0;
507
508 if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
509 throw runtime_error("failed to create graphics command pool!");
510 }
511}
512
513void VulkanGame::cleanupSwapChain() {
514 vkDestroyRenderPass(device, renderPass, nullptr);
515
516 for (auto imageView : swapChainImageViews) {
517 vkDestroyImageView(device, imageView, nullptr);
518 }
519
520 vkDestroySwapchainKHR(device, swapChain, nullptr);
521}
Note: See TracBrowser for help on using the repository browser.