source: opengl-game/sdl-game.cpp@ 9d21aac

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

Remove the SSBOType template parameter from GraphicsPipeline_Vulkan

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