source: opengl-game/vulkan-game.cpp@ 0e09340

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

In vulkangame, detect when the framebuffer is resized

  • Property mode set to 100644
File size: 17.8 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}
107
108void VulkanGame::mainLoop() {
109 UIEvent e;
110 bool quit = false;
111
112 SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
113
114 while (!quit) {
115 gui->processEvents();
116
117 while (gui->pollEvent(&e)) {
118 switch(e.type) {
119 case UI_EVENT_QUIT:
120 cout << "Quit event detected" << endl;
121 quit = true;
122 break;
123 case UI_EVENT_WINDOW:
124 cout << "Window event detected" << endl;
125 // Currently unused
126 break;
127 case UI_EVENT_WINDOWRESIZE:
128 cout << "Window resize event detected" << endl;
129 framebufferResized = true;
130 break;
131 case UI_EVENT_KEY:
132 if (e.key.keycode == SDL_SCANCODE_ESCAPE) {
133 quit = true;
134 } else {
135 cout << "Key event detected" << endl;
136 }
137 break;
138 case UI_EVENT_MOUSEBUTTONDOWN:
139 cout << "Mouse button down event detected" << endl;
140 break;
141 case UI_EVENT_MOUSEBUTTONUP:
142 cout << "Mouse button up event detected" << endl;
143 break;
144 case UI_EVENT_MOUSEMOTION:
145 break;
146 default:
147 cout << "Unhandled UI event: " << e.type << endl;
148 }
149 }
150
151 renderUI();
152 renderScene();
153 }
154
155 vkDeviceWaitIdle(device);
156}
157
158void VulkanGame::renderUI() {
159 SDL_RenderClear(renderer);
160 SDL_RenderPresent(renderer);
161}
162
163void VulkanGame::renderScene() {
164}
165
166void VulkanGame::cleanup() {
167 cleanupSwapChain();
168
169 vkDestroyDevice(device, nullptr);
170 vkDestroySurfaceKHR(instance, surface, nullptr);
171
172 if (ENABLE_VALIDATION_LAYERS) {
173 VulkanUtils::destroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
174 }
175
176 vkDestroyInstance(instance, nullptr);
177
178 SDL_DestroyRenderer(renderer);
179 renderer = nullptr;
180
181 gui->destroyWindow();
182 gui->shutdown();
183 delete gui;
184}
185
186void VulkanGame::createVulkanInstance(const vector<const char*> &validationLayers) {
187 if (ENABLE_VALIDATION_LAYERS && !VulkanUtils::checkValidationLayerSupport(validationLayers)) {
188 throw runtime_error("validation layers requested, but not available!");
189 }
190
191 VkApplicationInfo appInfo = {};
192 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
193 appInfo.pApplicationName = "Vulkan Game";
194 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
195 appInfo.pEngineName = "No Engine";
196 appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
197 appInfo.apiVersion = VK_API_VERSION_1_0;
198
199 VkInstanceCreateInfo createInfo = {};
200 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
201 createInfo.pApplicationInfo = &appInfo;
202
203 vector<const char*> extensions = gui->getRequiredExtensions();
204 if (ENABLE_VALIDATION_LAYERS) {
205 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
206 }
207
208 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
209 createInfo.ppEnabledExtensionNames = extensions.data();
210
211 cout << endl << "Extensions:" << endl;
212 for (const char* extensionName : extensions) {
213 cout << extensionName << endl;
214 }
215 cout << endl;
216
217 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
218 if (ENABLE_VALIDATION_LAYERS) {
219 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
220 createInfo.ppEnabledLayerNames = validationLayers.data();
221
222 populateDebugMessengerCreateInfo(debugCreateInfo);
223 createInfo.pNext = &debugCreateInfo;
224 } else {
225 createInfo.enabledLayerCount = 0;
226
227 createInfo.pNext = nullptr;
228 }
229
230 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
231 throw runtime_error("failed to create instance!");
232 }
233}
234
235void VulkanGame::setupDebugMessenger() {
236 if (!ENABLE_VALIDATION_LAYERS) return;
237
238 VkDebugUtilsMessengerCreateInfoEXT createInfo;
239 populateDebugMessengerCreateInfo(createInfo);
240
241 if (VulkanUtils::createDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
242 throw runtime_error("failed to set up debug messenger!");
243 }
244}
245
246void VulkanGame::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
247 createInfo = {};
248 createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
249 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;
250 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;
251 createInfo.pfnUserCallback = debugCallback;
252}
253
254VKAPI_ATTR VkBool32 VKAPI_CALL VulkanGame::debugCallback(
255 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
256 VkDebugUtilsMessageTypeFlagsEXT messageType,
257 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
258 void* pUserData) {
259 cerr << "validation layer: " << pCallbackData->pMessage << endl;
260
261 return VK_FALSE;
262}
263
264void VulkanGame::createVulkanSurface() {
265 if (gui->createVulkanSurface(instance, &surface) == RTWO_ERROR) {
266 throw runtime_error("failed to create window surface!");
267 }
268}
269
270void VulkanGame::pickPhysicalDevice(const vector<const char*>& deviceExtensions) {
271 uint32_t deviceCount = 0;
272 vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
273
274 if (deviceCount == 0) {
275 throw runtime_error("failed to find GPUs with Vulkan support!");
276 }
277
278 vector<VkPhysicalDevice> devices(deviceCount);
279 vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
280
281 cout << endl << "Graphics cards:" << endl;
282 for (const VkPhysicalDevice& device : devices) {
283 if (isDeviceSuitable(device, deviceExtensions)) {
284 physicalDevice = device;
285 break;
286 }
287 }
288 cout << endl;
289
290 if (physicalDevice == VK_NULL_HANDLE) {
291 throw runtime_error("failed to find a suitable GPU!");
292 }
293}
294
295bool VulkanGame::isDeviceSuitable(VkPhysicalDevice device, const vector<const char*>& deviceExtensions) {
296 VkPhysicalDeviceProperties deviceProperties;
297 vkGetPhysicalDeviceProperties(device, &deviceProperties);
298
299 cout << "Device: " << deviceProperties.deviceName << endl;
300
301 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(device, surface);
302 bool extensionsSupported = VulkanUtils::checkDeviceExtensionSupport(device, deviceExtensions);
303 bool swapChainAdequate = false;
304
305 if (extensionsSupported) {
306 SwapChainSupportDetails swapChainSupport = VulkanUtils::querySwapChainSupport(device, surface);
307 swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
308 }
309
310 VkPhysicalDeviceFeatures supportedFeatures;
311 vkGetPhysicalDeviceFeatures(device, &supportedFeatures);
312
313 return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
314}
315
316void VulkanGame::createLogicalDevice(
317 const vector<const char*> validationLayers,
318 const vector<const char*>& deviceExtensions) {
319 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
320
321 vector<VkDeviceQueueCreateInfo> queueCreateInfos;
322 set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
323
324 float queuePriority = 1.0f;
325 for (uint32_t queueFamily : uniqueQueueFamilies) {
326 VkDeviceQueueCreateInfo queueCreateInfo = {};
327 queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
328 queueCreateInfo.queueFamilyIndex = queueFamily;
329 queueCreateInfo.queueCount = 1;
330 queueCreateInfo.pQueuePriorities = &queuePriority;
331
332 queueCreateInfos.push_back(queueCreateInfo);
333 }
334
335 VkPhysicalDeviceFeatures deviceFeatures = {};
336 deviceFeatures.samplerAnisotropy = VK_TRUE;
337
338 VkDeviceCreateInfo createInfo = {};
339 createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
340 createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
341 createInfo.pQueueCreateInfos = queueCreateInfos.data();
342
343 createInfo.pEnabledFeatures = &deviceFeatures;
344
345 createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
346 createInfo.ppEnabledExtensionNames = deviceExtensions.data();
347
348 // These fields are ignored by up-to-date Vulkan implementations,
349 // but it's a good idea to set them for backwards compatibility
350 if (ENABLE_VALIDATION_LAYERS) {
351 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
352 createInfo.ppEnabledLayerNames = validationLayers.data();
353 } else {
354 createInfo.enabledLayerCount = 0;
355 }
356
357 if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
358 throw runtime_error("failed to create logical device!");
359 }
360
361 vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
362 vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
363}
364
365void VulkanGame::createSwapChain() {
366 SwapChainSupportDetails swapChainSupport = VulkanUtils::querySwapChainSupport(physicalDevice, surface);
367
368 VkSurfaceFormatKHR surfaceFormat = VulkanUtils::chooseSwapSurfaceFormat(swapChainSupport.formats);
369 VkPresentModeKHR presentMode = VulkanUtils::chooseSwapPresentMode(swapChainSupport.presentModes);
370 VkExtent2D extent = VulkanUtils::chooseSwapExtent(swapChainSupport.capabilities, gui->getWindowWidth(), gui->getWindowHeight());
371
372 uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
373 if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
374 imageCount = swapChainSupport.capabilities.maxImageCount;
375 }
376
377 VkSwapchainCreateInfoKHR createInfo = {};
378 createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
379 createInfo.surface = surface;
380 createInfo.minImageCount = imageCount;
381 createInfo.imageFormat = surfaceFormat.format;
382 createInfo.imageColorSpace = surfaceFormat.colorSpace;
383 createInfo.imageExtent = extent;
384 createInfo.imageArrayLayers = 1;
385 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
386
387 QueueFamilyIndices indices = VulkanUtils::findQueueFamilies(physicalDevice, surface);
388 uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
389
390 if (indices.graphicsFamily != indices.presentFamily) {
391 createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
392 createInfo.queueFamilyIndexCount = 2;
393 createInfo.pQueueFamilyIndices = queueFamilyIndices;
394 } else {
395 createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
396 createInfo.queueFamilyIndexCount = 0;
397 createInfo.pQueueFamilyIndices = nullptr;
398 }
399
400 createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
401 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
402 createInfo.presentMode = presentMode;
403 createInfo.clipped = VK_TRUE;
404 createInfo.oldSwapchain = VK_NULL_HANDLE;
405
406 if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
407 throw runtime_error("failed to create swap chain!");
408 }
409
410 vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
411 swapChainImages.resize(imageCount);
412 vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
413
414 swapChainImageFormat = surfaceFormat.format;
415 swapChainExtent = extent;
416}
417
418void VulkanGame::createImageViews() {
419 swapChainImageViews.resize(swapChainImages.size());
420
421 for (size_t i = 0; i < swapChainImages.size(); i++) {
422 swapChainImageViews[i] = VulkanUtils::createImageView(device, swapChainImages[i], swapChainImageFormat,
423 VK_IMAGE_ASPECT_COLOR_BIT);
424 }
425}
426
427void VulkanGame::createRenderPass() {
428 VkAttachmentDescription colorAttachment = {};
429 colorAttachment.format = swapChainImageFormat;
430 colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
431 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
432 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
433 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
434 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
435 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
436 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
437
438 VkAttachmentReference colorAttachmentRef = {};
439 colorAttachmentRef.attachment = 0;
440 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
441
442 VkAttachmentDescription depthAttachment = {};
443 depthAttachment.format = findDepthFormat();
444 depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
445 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
446 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
447 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
448 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
449 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
450 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
451
452 VkAttachmentReference depthAttachmentRef = {};
453 depthAttachmentRef.attachment = 1;
454 depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
455
456 VkSubpassDescription subpass = {};
457 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
458 subpass.colorAttachmentCount = 1;
459 subpass.pColorAttachments = &colorAttachmentRef;
460 subpass.pDepthStencilAttachment = &depthAttachmentRef;
461
462 VkSubpassDependency dependency = {};
463 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
464 dependency.dstSubpass = 0;
465 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
466 dependency.srcAccessMask = 0;
467 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
468 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
469
470 array<VkAttachmentDescription, 2> attachments = { colorAttachment, depthAttachment };
471 VkRenderPassCreateInfo renderPassInfo = {};
472 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
473 renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
474 renderPassInfo.pAttachments = attachments.data();
475 renderPassInfo.subpassCount = 1;
476 renderPassInfo.pSubpasses = &subpass;
477 renderPassInfo.dependencyCount = 1;
478 renderPassInfo.pDependencies = &dependency;
479
480 if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
481 throw runtime_error("failed to create render pass!");
482 }
483}
484
485VkFormat VulkanGame::findDepthFormat() {
486 return VulkanUtils::findSupportedFormat(
487 physicalDevice,
488 { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT },
489 VK_IMAGE_TILING_OPTIMAL,
490 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
491 );
492}
493
494void VulkanGame::cleanupSwapChain() {
495 vkDestroyRenderPass(device, renderPass, nullptr);
496
497 for (auto imageView : swapChainImageViews) {
498 vkDestroyImageView(device, imageView, nullptr);
499 }
500
501 vkDestroySwapchainKHR(device, swapChain, nullptr);
502}
Note: See TracBrowser for help on using the repository browser.