source: opengl-game/sdl-game.cpp@ 6bac215

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

Rewrite a large portion of the VulkanBuffer class, start using it more
to resize buffers, and simplify resizeBufferSet() since the additions to
VulkanBuffer can now do some of what that function used to do

  • Property mode set to 100644
File size: 54.9 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);
114
115 modelPipeline.addDescriptorInfo(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
116 VK_SHADER_STAGE_VERTEX_BIT, &uniformBuffers_modelPipeline.infoSet);
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 {
137 0, 1, 2, 3, 4, 5
138 }, objects_modelPipeline, {
139 mat4(1.0f)
140 });
141
142 texturedSquare->model_base =
143 translate(mat4(1.0f), vec3(0.0f, 0.0f, -2.0f));
144 texturedSquare->modified = true;
145
146 texturedSquare = &addObject(modelObjects, modelPipeline,
147 addObjectIndex<ModelVertex>(modelObjects.size(),
148 addVertexNormals<ModelVertex>({
149 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}},
150 {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}},
151 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.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}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}},
154 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}}
155 })), {
156 0, 1, 2, 3, 4, 5
157 }, objects_modelPipeline, {
158 mat4(1.0f)
159 });
160
161 texturedSquare->model_base =
162 translate(mat4(1.0f), vec3(0.0f, 0.0f, -1.5f));
163 texturedSquare->modified = true;
164
165 modelPipeline.createDescriptorSetLayout();
166 modelPipeline.createPipeline("shaders/model-vert.spv", "shaders/model-frag.spv");
167 modelPipeline.createDescriptorPool(swapChainImages.size());
168 modelPipeline.createDescriptorSets(swapChainImages.size());
169
170 currentRenderScreenFn = &VulkanGame::renderMainScreen;
171
172 ImGuiIO& io = ImGui::GetIO();
173
174 initGuiValueLists(valueLists);
175
176 valueLists["stats value list"].push_back(UIValue(UIVALUE_INT, "Score", &score));
177 valueLists["stats value list"].push_back(UIValue(UIVALUE_DOUBLE, "FPS", &fps));
178 valueLists["stats value list"].push_back(UIValue(UIVALUE_DOUBLE, "IMGUI FPS", &io.Framerate));
179
180 renderLoop();
181 cleanup();
182
183 close_log();
184}
185
186bool VulkanGame::initUI(int width, int height, unsigned char guiFlags) {
187 // TODO: Create a game-gui function to get the gui version and retrieve it that way
188
189 SDL_VERSION(&sdlVersion); // This gets the compile-time version
190 SDL_GetVersion(&sdlVersion); // This gets the runtime version
191
192 cout << "SDL " <<
193 to_string(sdlVersion.major) << "." <<
194 to_string(sdlVersion.minor) << "." <<
195 to_string(sdlVersion.patch) << endl;
196
197 // TODO: Refactor the logger api to be more flexible,
198 // esp. since gl_log() and gl_log_err() have issues printing anything besides strings
199 restart_gl_log();
200 gl_log("starting SDL\n%s.%s.%s",
201 to_string(sdlVersion.major).c_str(),
202 to_string(sdlVersion.minor).c_str(),
203 to_string(sdlVersion.patch).c_str());
204
205 // TODO: Use open_Log() and related functions instead of gl_log ones
206 // TODO: In addition, delete the gl_log functions
207 open_log();
208 get_log() << "starting SDL" << endl;
209 get_log() <<
210 (int)sdlVersion.major << "." <<
211 (int)sdlVersion.minor << "." <<
212 (int)sdlVersion.patch << endl;
213
214 // TODO: Put all fonts, textures, and images in the assets folder
215 gui = new GameGui_SDL();
216
217 if (gui->init() == RTWO_ERROR) {
218 // TODO: Also print these sorts of errors to the log
219 cout << "UI library could not be initialized!" << endl;
220 cout << gui->getError() << endl;
221 // TODO: Rename RTWO_ERROR to something else
222 return RTWO_ERROR;
223 }
224
225 window = (SDL_Window*)gui->createWindow("Vulkan Game", width, height, guiFlags & GUI_FLAGS_WINDOW_FULLSCREEN);
226 if (window == nullptr) {
227 cout << "Window could not be created!" << endl;
228 cout << gui->getError() << endl;
229 return RTWO_ERROR;
230 }
231
232 cout << "Target window size: (" << width << ", " << height << ")" << endl;
233 cout << "Actual window size: (" << gui->getWindowWidth() << ", " << gui->getWindowHeight() << ")" << endl;
234
235 return RTWO_SUCCESS;
236}
237
238void VulkanGame::initVulkan() {
239 const vector<const char*> validationLayers = {
240 "VK_LAYER_KHRONOS_validation"
241 };
242 const vector<const char*> deviceExtensions = {
243 VK_KHR_SWAPCHAIN_EXTENSION_NAME
244 };
245
246 createVulkanInstance(validationLayers);
247 setupDebugMessenger();
248 createVulkanSurface();
249 pickPhysicalDevice(deviceExtensions);
250 createLogicalDevice(validationLayers, deviceExtensions);
251 chooseSwapChainProperties();
252 createSwapChain();
253 createImageViews();
254
255 createResourceCommandPool();
256 createImageResources();
257
258 createRenderPass();
259 createCommandPools();
260 createFramebuffers();
261 createCommandBuffers();
262 createSyncObjects();
263}
264
265void VulkanGame::initGraphicsPipelines() {
266 modelPipeline = GraphicsPipeline_Vulkan<ModelVertex>(
267 VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, physicalDevice, device, renderPass,
268 { 0, 0, (int)swapChainExtent.width, (int)swapChainExtent.height }, 16, 24);
269
270 createBufferSet(objects_modelPipeline.capacity * sizeof(SSBO_ModelObject),
271 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT
272 | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
273 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
274 storageBuffers_modelPipeline);
275}
276
277// TODO: Maybe change the name to initScene() or something similar
278void VulkanGame::initMatrices() {
279 cam_pos = vec3(0.0f, 0.0f, 2.0f);
280
281 float cam_yaw = 0.0f;
282 float cam_pitch = -50.0f;
283
284 mat4 yaw_mat = rotate(mat4(1.0f), radians(-cam_yaw), vec3(0.0f, 1.0f, 0.0f));
285 mat4 pitch_mat = rotate(mat4(1.0f), radians(-cam_pitch), vec3(1.0f, 0.0f, 0.0f));
286
287 mat4 R_view = pitch_mat * yaw_mat;
288 mat4 T_view = translate(mat4(1.0f), vec3(-cam_pos.x, -cam_pos.y, -cam_pos.z));
289 viewMat = R_view * T_view;
290
291 projMat = perspective(radians(FOV_ANGLE), (float)swapChainExtent.width / (float)swapChainExtent.height, NEAR_CLIP, FAR_CLIP);
292 projMat[1][1] *= -1; // flip the y-axis so that +y is up
293
294 object_VP_mats.view = viewMat;
295 object_VP_mats.proj = projMat;
296}
297
298void VulkanGame::renderLoop() {
299 startTime = steady_clock::now();
300 curTime = duration<float, seconds::period>(steady_clock::now() - startTime).count();
301
302 fpsStartTime = curTime;
303 frameCount = 0;
304
305 ImGuiIO& io = ImGui::GetIO();
306
307 done = false;
308 while (!done) {
309
310 prevTime = curTime;
311 curTime = duration<float, seconds::period>(steady_clock::now() - startTime).count();
312 elapsedTime = curTime - prevTime;
313
314 if (curTime - fpsStartTime >= 1.0f) {
315 fps = (float)frameCount / (curTime - fpsStartTime);
316
317 frameCount = 0;
318 fpsStartTime = curTime;
319 }
320
321 frameCount++;
322
323 gui->processEvents();
324
325 UIEvent uiEvent;
326 while (gui->pollEvent(&uiEvent)) {
327 GameEvent& e = uiEvent.event;
328 SDL_Event sdlEvent = uiEvent.rawEvent.sdl;
329
330 ImGui_ImplSDL2_ProcessEvent(&sdlEvent);
331 if ((e.type == UI_EVENT_MOUSEBUTTONDOWN || e.type == UI_EVENT_MOUSEBUTTONUP || e.type == UI_EVENT_UNKNOWN) &&
332 io.WantCaptureMouse) {
333 if (sdlEvent.type == SDL_MOUSEWHEEL || sdlEvent.type == SDL_MOUSEBUTTONDOWN ||
334 sdlEvent.type == SDL_MOUSEBUTTONUP) {
335 continue;
336 }
337 }
338 if ((e.type == UI_EVENT_KEYDOWN || e.type == UI_EVENT_KEYUP) && io.WantCaptureKeyboard) {
339 if (sdlEvent.type == SDL_KEYDOWN || sdlEvent.type == SDL_KEYUP) {
340 continue;
341 }
342 }
343 if (io.WantTextInput) {
344 // show onscreen keyboard if on mobile
345 }
346
347 switch (e.type) {
348 case UI_EVENT_QUIT:
349 cout << "Quit event detected" << endl;
350 done = true;
351 break;
352 case UI_EVENT_WINDOWRESIZE:
353 cout << "Window resize event detected" << endl;
354 shouldRecreateSwapChain = true;
355 break;
356 case UI_EVENT_KEYDOWN:
357 if (e.key.repeat) {
358 break;
359 }
360
361 if (e.key.keycode == SDL_SCANCODE_ESCAPE) {
362 done = true;
363 } else if (e.key.keycode == SDL_SCANCODE_SPACE) {
364 cout << "Adding a plane" << endl;
365 float zOffset = -2.0f + (0.5f * modelObjects.size());
366
367 SceneObject<ModelVertex, SSBO_ModelObject>& texturedSquare =
368 addObject(modelObjects, modelPipeline,
369 addObjectIndex<ModelVertex>(modelObjects.size(),
370 addVertexNormals<ModelVertex>({
371 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, 0},
372 {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, 0},
373 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, 0},
374 {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, 0},
375 {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, 0},
376 {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, 0}
377 })),
378 {
379 0, 1, 2, 3, 4, 5
380 }, objects_modelPipeline, {
381 mat4(1.0f)
382 });
383
384 texturedSquare.model_base =
385 translate(mat4(1.0f), vec3(0.0f, 0.0f, zOffset));
386 texturedSquare.modified = true;
387 // START UNREVIEWED SECTION
388 // END UNREVIEWED SECTION
389 } else {
390 cout << "Key event detected" << endl;
391 }
392 break;
393 case UI_EVENT_KEYUP:
394 // START UNREVIEWED SECTION
395 // END UNREVIEWED SECTION
396 break;
397 case UI_EVENT_WINDOW:
398 case UI_EVENT_MOUSEBUTTONDOWN:
399 case UI_EVENT_MOUSEBUTTONUP:
400 case UI_EVENT_MOUSEMOTION:
401 break;
402 case UI_EVENT_UNHANDLED:
403 cout << "Unhandled event type: 0x" << hex << sdlEvent.type << dec << endl;
404 break;
405 case UI_EVENT_UNKNOWN:
406 default:
407 cout << "Unknown event type: 0x" << hex << sdlEvent.type << dec << endl;
408 break;
409 }
410 }
411
412 if (shouldRecreateSwapChain) {
413 gui->refreshWindowSize();
414 const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
415
416 if (!isMinimized) {
417 // TODO: This should be used if the min image count changes, presumably because a new surface was created
418 // with a different image count or something like that. Maybe I want to add code to query for a new min image count
419 // during swapchain recreation to take advantage of this
420 ImGui_ImplVulkan_SetMinImageCount(swapChainMinImageCount);
421
422 recreateSwapChain();
423
424 shouldRecreateSwapChain = false;
425 }
426 }
427
428 updateScene();
429
430 // TODO: Move this into a renderImGuiOverlay() function
431 ImGui_ImplVulkan_NewFrame();
432 ImGui_ImplSDL2_NewFrame(window);
433 ImGui::NewFrame();
434
435 (this->*currentRenderScreenFn)(gui->getWindowWidth(), gui->getWindowHeight());
436
437 ImGui::Render();
438
439 gui->refreshWindowSize();
440 const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
441
442 if (!isMinimized) {
443 renderFrame(ImGui::GetDrawData());
444 presentFrame();
445 }
446 }
447}
448
449void VulkanGame::updateScene() {
450 // Rotate the textured squares
451 for (SceneObject<ModelVertex, SSBO_ModelObject>& model : this->modelObjects) {
452 model.model_transform =
453 translate(mat4(1.0f), vec3(0.0f, -2.0f, -0.0f)) *
454 rotate(mat4(1.0f), curTime * radians(90.0f), vec3(0.0f, 0.0f, 1.0f));
455 model.modified = true;
456 }
457
458 // TODO: Probably move the resizing to the VulkanBuffer class
459 if (objects_modelPipeline.resized) {
460 // TODO: Also resize the dynamic ubo
461 resizeBufferSet(storageBuffers_modelPipeline, objects_modelPipeline, modelPipeline, resourceCommandPool,
462 graphicsQueue);
463
464 objects_modelPipeline.resize();
465 }
466
467 for (size_t i = 0; i < modelObjects.size(); i++) {
468 if (modelObjects[i].modified) {
469 updateObject(modelObjects[i]);
470 updateBufferSet(storageBuffers_modelPipeline, i, modelObjects[i].ssbo);
471 }
472 }
473
474 VulkanUtils::copyDataToMemory(device, &object_VP_mats, uniformBuffers_modelPipeline.memory[imageIndex], 0,
475 sizeof(object_VP_mats), false);
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 usages, VkMemoryPropertyFlags properties,
1115 BufferSet& set) {
1116 set.usages = usages;
1117 set.properties = properties;
1118
1119 set.buffers.resize(swapChainImageCount);
1120 set.memory.resize(swapChainImageCount);
1121 set.infoSet.resize(swapChainImageCount);
1122
1123 for (size_t i = 0; i < swapChainImageCount; i++) {
1124 VulkanUtils::createBuffer(device, physicalDevice, bufferSize, usages, properties, set.buffers[i], set.memory[i]);
1125
1126 set.infoSet[i].buffer = set.buffers[i];
1127 set.infoSet[i].offset = 0; // This is the offset from the start of the buffer, so always 0 for now
1128 set.infoSet[i].range = bufferSize; // Size of the update starting from offset, or VK_WHOLE_SIZE
1129 }
1130}
1131
1132void VulkanGame::renderFrame(ImDrawData* draw_data) {
1133 VkResult result = vkAcquireNextImageKHR(device, swapChain, numeric_limits<uint64_t>::max(),
1134 imageAcquiredSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
1135
1136 if (result == VK_SUBOPTIMAL_KHR) {
1137 shouldRecreateSwapChain = true;
1138 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1139 shouldRecreateSwapChain = true;
1140 return;
1141 } else {
1142 VKUTIL_CHECK_RESULT(result, "failed to acquire swap chain image!");
1143 }
1144
1145 VKUTIL_CHECK_RESULT(
1146 vkWaitForFences(device, 1, &inFlightFences[imageIndex], VK_TRUE, numeric_limits<uint64_t>::max()),
1147 "failed waiting for fence!");
1148
1149 VKUTIL_CHECK_RESULT(vkResetFences(device, 1, &inFlightFences[imageIndex]),
1150 "failed to reset fence!");
1151
1152 VKUTIL_CHECK_RESULT(vkResetCommandPool(device, commandPools[imageIndex], 0),
1153 "failed to reset command pool!");
1154
1155 VkCommandBufferBeginInfo beginInfo = {};
1156 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1157 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1158
1159 VKUTIL_CHECK_RESULT(vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo),
1160 "failed to begin recording command buffer!");
1161
1162 VkRenderPassBeginInfo renderPassInfo = {};
1163 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
1164 renderPassInfo.renderPass = renderPass;
1165 renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
1166 renderPassInfo.renderArea.offset = { 0, 0 };
1167 renderPassInfo.renderArea.extent = swapChainExtent;
1168
1169 array<VkClearValue, 2> clearValues = {};
1170 clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
1171 clearValues[1].depthStencil = { 1.0f, 0 };
1172
1173 renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
1174 renderPassInfo.pClearValues = clearValues.data();
1175
1176 vkCmdBeginRenderPass(commandBuffers[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
1177
1178 // TODO: Find a more elegant, per-screen solution for this
1179 if (currentRenderScreenFn == &VulkanGame::renderGameScreen) {
1180 modelPipeline.createRenderCommands(commandBuffers[imageIndex], imageIndex, {});
1181
1182
1183
1184
1185 }
1186
1187 ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffers[imageIndex]);
1188
1189 vkCmdEndRenderPass(commandBuffers[imageIndex]);
1190
1191 VKUTIL_CHECK_RESULT(vkEndCommandBuffer(commandBuffers[imageIndex]),
1192 "failed to record command buffer!");
1193
1194 VkSemaphore waitSemaphores[] = { imageAcquiredSemaphores[currentFrame] };
1195 VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
1196 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
1197
1198 VkSubmitInfo submitInfo = {};
1199 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1200 submitInfo.waitSemaphoreCount = 1;
1201 submitInfo.pWaitSemaphores = waitSemaphores;
1202 submitInfo.pWaitDstStageMask = waitStages;
1203 submitInfo.commandBufferCount = 1;
1204 submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
1205 submitInfo.signalSemaphoreCount = 1;
1206 submitInfo.pSignalSemaphores = signalSemaphores;
1207
1208 VKUTIL_CHECK_RESULT(vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[imageIndex]),
1209 "failed to submit draw command buffer!");
1210}
1211
1212void VulkanGame::presentFrame() {
1213 VkSemaphore signalSemaphores[] = { renderCompleteSemaphores[currentFrame] };
1214
1215 VkPresentInfoKHR presentInfo = {};
1216 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
1217 presentInfo.waitSemaphoreCount = 1;
1218 presentInfo.pWaitSemaphores = signalSemaphores;
1219 presentInfo.swapchainCount = 1;
1220 presentInfo.pSwapchains = &swapChain;
1221 presentInfo.pImageIndices = &imageIndex;
1222 presentInfo.pResults = nullptr;
1223
1224 VkResult result = vkQueuePresentKHR(presentQueue, &presentInfo);
1225
1226 if (result == VK_SUBOPTIMAL_KHR) {
1227 shouldRecreateSwapChain = true;
1228 } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1229 shouldRecreateSwapChain = true;
1230 return;
1231 } else {
1232 VKUTIL_CHECK_RESULT(result, "failed to present swap chain image!");
1233 }
1234
1235 currentFrame = (currentFrame + 1) % swapChainImageCount;
1236}
1237
1238void VulkanGame::recreateSwapChain() {
1239 if (vkDeviceWaitIdle(device) != VK_SUCCESS) {
1240 throw runtime_error("failed to wait for device!");
1241 }
1242
1243 cleanupSwapChain();
1244
1245 createSwapChain();
1246 createImageViews();
1247
1248 // The depth buffer does need to be recreated with the swap chain since its dimensions depend on the window size
1249 // and resizing the window is a common reason to recreate the swapchain
1250 VulkanUtils::createDepthImage(device, physicalDevice, resourceCommandPool, findDepthFormat(), swapChainExtent,
1251 depthImage, graphicsQueue);
1252
1253 createRenderPass();
1254 createCommandPools();
1255 createFramebuffers();
1256 createCommandBuffers();
1257 createSyncObjects();
1258
1259 // TODO: Move UBO creation/management into GraphicsPipeline_Vulkan, like I did with SSBOs
1260 // TODO: Check if the shader stages and maybe some other properties of the pipeline can be re-used
1261 // instead of recreated every time
1262
1263 createBufferSet(sizeof(UBO_VP_mats),
1264 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1265 uniformBuffers_modelPipeline);
1266
1267 modelPipeline.updateRenderPass(renderPass);
1268 modelPipeline.createPipeline("shaders/model-vert.spv", "shaders/model-frag.spv");
1269 modelPipeline.createDescriptorPool(swapChainImages.size());
1270 modelPipeline.createDescriptorSets(swapChainImages.size());
1271
1272 imageIndex = 0;
1273}
1274
1275void VulkanGame::cleanupSwapChain() {
1276 VulkanUtils::destroyVulkanImage(device, depthImage);
1277
1278 for (VkFramebuffer framebuffer : swapChainFramebuffers) {
1279 vkDestroyFramebuffer(device, framebuffer, nullptr);
1280 }
1281
1282 for (uint32_t i = 0; i < swapChainImageCount; i++) {
1283 vkFreeCommandBuffers(device, commandPools[i], 1, &commandBuffers[i]);
1284 vkDestroyCommandPool(device, commandPools[i], nullptr);
1285 }
1286
1287 modelPipeline.cleanup();
1288
1289 for (size_t i = 0; i < uniformBuffers_modelPipeline.buffers.size(); i++) {
1290 vkDestroyBuffer(device, uniformBuffers_modelPipeline.buffers[i], nullptr);
1291 vkFreeMemory(device, uniformBuffers_modelPipeline.memory[i], nullptr);
1292 }
1293
1294 for (uint32_t i = 0; i < swapChainImageCount; i++) {
1295 vkDestroySemaphore(device, imageAcquiredSemaphores[i], nullptr);
1296 vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr);
1297 vkDestroyFence(device, inFlightFences[i], nullptr);
1298 }
1299
1300 vkDestroyRenderPass(device, renderPass, nullptr);
1301
1302 for (VkImageView imageView : swapChainImageViews) {
1303 vkDestroyImageView(device, imageView, nullptr);
1304 }
1305
1306 vkDestroySwapchainKHR(device, swapChain, nullptr);
1307}
1308
1309void VulkanGame::renderMainScreen(int width, int height) {
1310 {
1311 int padding = 4;
1312 ImGui::SetNextWindowPos(vec2(-padding, -padding), ImGuiCond_Once);
1313 ImGui::SetNextWindowSize(vec2(width + 2 * padding, height + 2 * padding), ImGuiCond_Always);
1314 ImGui::Begin("WndMain", nullptr,
1315 ImGuiWindowFlags_NoTitleBar |
1316 ImGuiWindowFlags_NoResize |
1317 ImGuiWindowFlags_NoMove);
1318
1319 ButtonImGui btn("New Game");
1320
1321 ImGui::InvisibleButton("", vec2(10, height / 6));
1322 if (btn.draw((width - btn.getWidth()) / 2)) {
1323 goToScreen(&VulkanGame::renderGameScreen);
1324 }
1325
1326 ButtonImGui btn2("Quit");
1327
1328 ImGui::InvisibleButton("", vec2(10, 15));
1329 if (btn2.draw((width - btn2.getWidth()) / 2)) {
1330 quitGame();
1331 }
1332
1333 ImGui::End();
1334 }
1335}
1336
1337void VulkanGame::renderGameScreen(int width, int height) {
1338 {
1339 ImGui::SetNextWindowSize(vec2(130, 65), ImGuiCond_Once);
1340 ImGui::SetNextWindowPos(vec2(10, 50), ImGuiCond_Once);
1341 ImGui::Begin("WndStats", nullptr,
1342 ImGuiWindowFlags_NoTitleBar |
1343 ImGuiWindowFlags_NoResize |
1344 ImGuiWindowFlags_NoMove);
1345
1346 //ImGui::Text(ImGui::GetIO().Framerate);
1347 renderGuiValueList(valueLists["stats value list"]);
1348
1349 ImGui::End();
1350 }
1351
1352 {
1353 ImGui::SetNextWindowSize(vec2(250, 35), ImGuiCond_Once);
1354 ImGui::SetNextWindowPos(vec2(width - 260, 10), ImGuiCond_Always);
1355 ImGui::Begin("WndMenubar", nullptr,
1356 ImGuiWindowFlags_NoTitleBar |
1357 ImGuiWindowFlags_NoResize |
1358 ImGuiWindowFlags_NoMove);
1359 ImGui::InvisibleButton("", vec2(155, 18));
1360 ImGui::SameLine();
1361 if (ImGui::Button("Main Menu")) {
1362 goToScreen(&VulkanGame::renderMainScreen);
1363 }
1364 ImGui::End();
1365 }
1366
1367 {
1368 ImGui::SetNextWindowSize(vec2(200, 200), ImGuiCond_Once);
1369 ImGui::SetNextWindowPos(vec2(width - 210, 60), ImGuiCond_Always);
1370 ImGui::Begin("WndDebug", nullptr,
1371 ImGuiWindowFlags_NoTitleBar |
1372 ImGuiWindowFlags_NoResize |
1373 ImGuiWindowFlags_NoMove);
1374
1375 renderGuiValueList(valueLists["debug value list"]);
1376
1377 ImGui::End();
1378 }
1379}
1380
1381void VulkanGame::initGuiValueLists(map<string, vector<UIValue>>& valueLists) {
1382 valueLists["stats value list"] = vector<UIValue>();
1383 valueLists["debug value list"] = vector<UIValue>();
1384}
1385
1386// TODO: Probably turn this into a UI widget class
1387void VulkanGame::renderGuiValueList(vector<UIValue>& values) {
1388 float maxWidth = 0.0f;
1389 float cursorStartPos = ImGui::GetCursorPosX();
1390
1391 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
1392 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
1393
1394 if (maxWidth < textWidth)
1395 maxWidth = textWidth;
1396 }
1397
1398 stringstream ss;
1399
1400 // TODO: Possibly implement this based on gui/ui-value.hpp instead and use templates
1401 // to keep track of the type. This should make it a bit easier to use and maintain
1402 // Also, implement this in a way that's agnostic to the UI renderer.
1403 for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
1404 ss.str("");
1405 ss.clear();
1406
1407 switch (it->type) {
1408 case UIVALUE_INT:
1409 ss << it->label << ": " << *(unsigned int*)it->value;
1410 break;
1411 case UIVALUE_DOUBLE:
1412 ss << it->label << ": " << *(double*)it->value;
1413 break;
1414 }
1415
1416 float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
1417
1418 ImGui::SetCursorPosX(cursorStartPos + maxWidth - textWidth);
1419 //ImGui::Text("%s", ss.str().c_str());
1420 ImGui::Text("%s: %.1f", it->label.c_str(), *(float*)it->value);
1421 }
1422}
1423
1424void VulkanGame::goToScreen(void (VulkanGame::* renderScreenFn)(int width, int height)) {
1425 currentRenderScreenFn = renderScreenFn;
1426
1427 // TODO: Maybe just set shouldRecreateSwapChain to true instead. Check this render loop logic
1428 // to make sure there'd be no issues
1429 //recreateSwapChain();
1430}
1431
1432void VulkanGame::quitGame() {
1433 done = true;
1434}
Note: See TracBrowser for help on using the repository browser.