source: opengl-game/sdl-game.cpp@ 1abebc1

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

Remove the storageBuffers parameter from addObject() since it is no longer used, rename StorageBufferSet, resizeStorageBufferSet(), and updateStorageuffer() to BufferSet, resizeBufferSet(), and updateBufferSet() respectively, and change updateObject() to just take a SceneObject reference.

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