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

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

Modify the parameter order of VulkanUtils::copyDataToMemory and add an overload
that takes the data size as a parameter instead of getting it from the template type

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