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

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

Move SSBO resizing and pipeline recreation checks out of addObject() and into updateScene() so that those operations are only done at most once per pipeline per frame, using vkUpdateDescriptorSets() instead of recreating the whole graphics pipeline, and create a VulkanBuffer class for managing data related to uniform buffers and shader storage buffers, move objectCapacity and numObjects out of GraphicsPipeline_vulkan and use VulkanBuffer to manage them instead

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