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

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

Add a VkMemoryPropertyFlags parameter to SDLGame::createBufferSet
instead of hard-coding that value

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