source: opengl-game/sdl-game.cpp@ 6486ba8

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

Rewrite some parts of SDLGame and VulkanGame to store per-object buffer object
data contiguously and copied to the GPU in one call

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