source: opengl-game/sdl-game.cpp@ 996dd3e

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

Completely remove storage buffers from the GraphicsPipeline_Vulkan class and start moving storage buffer operations out of the addObject() and updateObject() functions

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