Chronos 0.0
A advanced 2D rendering and animation system
Loading...
Searching...
No Matches
engine.cpp
Go to the documentation of this file.
1/*
2Copyright (c) 2024 Rahul Satish Vadhyar
3
4Permission is hereby granted, free of charge, to any person obtaining a copy
5of this software and associated documentation files (the "Software"), to deal
6in the Software without restriction, including without limitation the rights
7to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8copies of the Software, and to permit persons to whom the Software is
9furnished to do so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in all
12copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20SOFTWARE.
21*/
22
23#ifdef ENABLE_VULKAN_VALIDATION_LAYERS
24#include "validation.hpp"
25#endif
26#include "engine.hpp"
27#include "logging.hpp"
28#include "helper.hpp"
29
30static void framebuffer_size_callback(GLFWwindow* window, int width, int height)
31{
32 auto app = reinterpret_cast<Chronos::Engine::Engine*>(
33 glfwGetWindowUserPointer(window));
34 app->resizeFrameBuffer();
35 LOG(3, "Framebuffer resized")
36}
38{
39#ifdef ENABLE_EDITOR
41#endif
42 // initialize the window and vulkan
43 initWindow();
44 LOG(3, "GLFW initialized")
45 initVulkan();
46 LOG(3, "Vulkan initialized")
47}
48
50{
51 vkDeviceWaitIdle(device.device);
52 cleanup();
53 LOG(3, "Engine cleaned up")
54}
55
56void Chronos::Engine::Engine::resizeFrameBuffer() { framebufferResized = true; }
57
59{
60 // initialize glfw with a resizeable window
61 glfwInit();
62 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
63
64 window = glfwCreateWindow(width, height, GAME_NAME, nullptr, nullptr);
65 if (window == nullptr) {
66 LOG(1, "Failed to create GLFW window")
67 throw std::runtime_error("Failed to create GLFW window");
68 }
69 glfwMakeContextCurrent(window);
70 glfwSetWindowUserPointer(window, this);
71 glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
72}
73
75{
76 // create the basic objects
77 createInstance();
78
79#ifdef ENABLE_VULKAN_VALIDATION_LAYERS
80 setupDebugMessenger();
81#endif
82 createSurface();
83 device.init(instance, surface);
84 LOG(3, "Device initialized")
85 swapChain.init(&device, surface, window);
86 LOG(3, "Swapchain initialized")
87 commandPool = createCommandPool(device, swapChain.surface);
88 LOG(3, "Command pool initialized")
89
90 // initalize the object manager
91 objectManager.init(&device, &swapChain, commandPool);
92
93 textureManager.init(&device, commandPool);
94 LOG(3, "Texture manager initialized")
95
96 createSyncObjects();
97
98#ifdef ENABLE_EDITOR
99 gui.init(&device, window, &swapChain, instance, surface);
100 LOG(3, "Editor initialized")
101#endif
102}
103
105{
106 // after we are done, we need to cleanup all the resources we created
107 swapChain.cleanup();
108 LOG(3, "Swapchain cleaned up")
109 objectManager.destroy();
110 LOG(3, "Object manager cleaned up")
111 textureManager.destroy();
112 LOG(3, "Texture manager cleaned up")
113#ifdef ENABLE_EDITOR
114 gui.destroy();
115 LOG(3, "Editor cleaned up")
116#endif
117 for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
118 vkDestroySemaphore(device.device, renderFinishedSemaphores[i], nullptr);
119 vkDestroySemaphore(device.device, imageAvailableSemaphores[i], nullptr);
120 vkDestroyFence(device.device, inFlightFences[i], nullptr);
121 }
122 vkDestroyCommandPool(device.device, commandPool, nullptr);
123 LOG(3, "Command pool cleaned up")
124#ifdef ENABLE_VULKAN_VALIDATION_LAYERS
125 DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
126 LOG(3, "Debug messenger cleaned up")
127#endif
128 device.destroy(); // destroy the logical device
129 LOG(3, "Device cleaned up")
130 vkDestroySurfaceKHR(instance, surface, nullptr);
131 vkDestroyInstance(instance, nullptr);
132 glfwDestroyWindow(window);
133 glfwTerminate();
134 LOG(3, "GLFW cleaned up")
135}
136
138{
139 if (swapChain.changePresentMode) {
140 changePresentMode();
141 swapChain.changePresentMode = false;
142 LOG(3, "Present mode changed")
143 }
144 if (this->changeMSAAFlag) {
145 vkDeviceWaitIdle(device.device);
146 changeMSAASettings();
147 this->changeMSAAFlag = false;
148 LOG(3, "MSAA changed")
149 }
150 // wait for the previous frame to finish
151 glfwPollEvents();
152
153#ifdef CHRONOS_PROFILING
154 std::chrono::steady_clock::time_point start
155 = std::chrono::steady_clock::now();
156#endif
157
158 vkWaitForFences(
159 device.device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
160
161#ifdef CHRONOS_PROFILING
162 this->waitTime = std::chrono::duration_cast<std::chrono::nanoseconds>(
163 std::chrono::steady_clock::now() - start)
164 .count();
165#endif
166
167 // get the index of the next image to render to
168 uint32_t imageIndex;
169 VkResult result = vkAcquireNextImageKHR(device.device, swapChain.swapChain,
170 UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE,
171 &imageIndex);
172
173 // if window has been minimized, then recreate the swap chain and other
174 // things
175 if (result == VK_ERROR_OUT_OF_DATE_KHR) {
176 swapChain.recreate();
177 LOG(3, "Swapchain recreated")
178 objectManager.recreate();
179 LOG(3, "Object manager recreated")
180#ifdef ENABLE_EDITOR
181 gui.recreate();
182 LOG(3, "Editor recreated")
183#endif
184 return;
185 } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
186 LOG(1, "Failed to acquire swap chain image")
187 throw std::runtime_error("Failed to acquire swap chain image");
188 }
189#ifdef CHRONOS_PROFILING
190 auto startUpdate = std::chrono::steady_clock::now();
191#endif
192 // update the shapes and text
193 objectManager.update(currentFrame);
194
195#ifdef ENABLE_EDITOR
196 gui.update();
197#endif
198#ifdef CHRONOS_PROFILING
199 this->updateTime = std::chrono::duration_cast<std::chrono::nanoseconds>(
200 std::chrono::steady_clock::now() - startUpdate)
201 .count();
202#endif
203 LOG(4, "Managers updated")
204
205 // reset the fences
206 vkResetFences(device.device, 1, &inFlightFences[currentFrame]);
207
208 // record the command buffers
209 objectManager.render(currentFrame, imageIndex, bgColor);
210#ifdef ENABLE_EDITOR
211 gui.render(currentFrame, imageIndex, bgColor);
212#endif
213 LOG(4, "Managers command buffers recorded")
214
215 // configure the semaphores
216 VkSemaphore waitSemaphores[] = { imageAvailableSemaphores[currentFrame] };
217 VkPipelineStageFlags waitStages[]
218 = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
219 VkSemaphore signalSemaphores[] = { renderFinishedSemaphores[currentFrame] };
220
221 // submit the command buffers
222 std::vector<VkCommandBuffer> submitCommandBuffers
223 = { objectManager.commandBuffers[currentFrame] };
224
225#ifdef ENABLE_EDITOR
226 submitCommandBuffers.push_back(gui.commandBuffers[currentFrame]);
227#endif
228 VkSubmitInfo submitInfo {};
229 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
230 submitInfo.waitSemaphoreCount = 1;
231 submitInfo.pWaitSemaphores = waitSemaphores;
232 submitInfo.pWaitDstStageMask = waitStages;
233 submitInfo.commandBufferCount
234 = static_cast<uint32_t>(submitCommandBuffers.size());
235 submitInfo.pCommandBuffers = submitCommandBuffers.data();
236 submitInfo.signalSemaphoreCount = 1;
237 submitInfo.pSignalSemaphores = signalSemaphores;
238
239 if (vkQueueSubmit(
240 device.graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame])
241 != VK_SUCCESS) {
242 LOG(1, "Failed to submit draw command buffer")
243 throw std::runtime_error("failed to submit draw command buffer!");
244 }
245 LOG(4, "Command buffers submitted")
246
247#ifdef ENABLE_EDITOR
248 gui.renderAdditionalViewports();
249#endif
250
251#ifdef CHRONOS_PROFILING
252 this->cpuTime = std::chrono::duration_cast<std::chrono::nanoseconds>(
253 std::chrono::steady_clock::now() - start)
254 .count();
255 auto startPresent = std::chrono::steady_clock::now();
256#endif
257
258 // present the image
259 VkPresentInfoKHR presentInfo {};
260 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
261 presentInfo.waitSemaphoreCount = 1;
262 presentInfo.pWaitSemaphores = signalSemaphores;
263 VkSwapchainKHR swapChains[] = { swapChain.swapChain };
264 presentInfo.swapchainCount = 1;
265 presentInfo.pSwapchains = swapChains;
266 presentInfo.pImageIndices = &imageIndex;
267 presentInfo.pResults = nullptr;
268 result = vkQueuePresentKHR(device.presentQueue, &presentInfo);
269 if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR
270 || framebufferResized) {
271 framebufferResized = false;
272 swapChain.recreate();
273 LOG(3, "Swapchain recreated")
274 objectManager.recreate();
275 LOG(3, "Object Manager recreated")
276#ifdef ENABLE_EDITOR
277 gui.recreate();
278 LOG(3, "Editor recreated")
279#endif
280 } else if (result != VK_SUCCESS) {
281 LOG(1, "Failed to present swap chain image")
282 throw std::runtime_error("Failed to present swap chain image");
283 }
284#ifdef CHRONOS_PROFILING
285 this->presentTime = std::chrono::duration_cast<std::chrono::nanoseconds>(
286 std::chrono::steady_clock::now() - startPresent)
287 .count();
288 this->totalTime = std::chrono::duration_cast<std::chrono::nanoseconds>(
289 std::chrono::steady_clock::now() - start)
290 .count();
291#endif
292 // update the current frame
293 currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
294 LOG(4, "Current frame is " + std::to_string(currentFrame))
295}
296
305static inline std::vector<const char*> getRequiredExtensions()
306{
307 // here we set the needed extensions. For now it is only debug layer
308 uint32_t glfwExtensionCount = 0;
309 const char** glfwExtensions;
310 glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
311 std::vector<const char*> extensions(
312 glfwExtensions, glfwExtensions + glfwExtensionCount);
313#ifdef ENABLE_VULKAN_VALIDATION_LAYERS
314 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
315#endif
316 return extensions;
317}
318
320{
321 VkApplicationInfo appInfo {};
322 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
323 appInfo.pApplicationName = GAME_NAME;
324 appInfo.applicationVersion = VK_MAKE_VERSION(
325 GAME_VERSION_MAJOR, GAME_VERSION_MINOR, GAME_VERSION_PATCH);
326 appInfo.pEngineName = "Chronos";
327 appInfo.engineVersion = VK_MAKE_VERSION(
328 CHRONOS_VERSION_MAJOR, CHRONOS_VERSION_MINOR, CHRONOS_VERSION_PATCH);
329 // using vulkan 1.3 as we need shader printf support
330 appInfo.apiVersion = VK_API_VERSION_1_0;
331 LOG(3,
332 "engine version: " + std::to_string(CHRONOS_VERSION_MAJOR) + "."
333 + std::to_string(CHRONOS_VERSION_MINOR) + "."
334 + std::to_string(CHRONOS_VERSION_PATCH))
335 LOG(3,
336 "game version: " + std::to_string(GAME_VERSION_MAJOR) + "."
337 + std::to_string(GAME_VERSION_MINOR) + "."
338 + std::to_string(GAME_VERSION_PATCH))
339 LOG(3, "game name: " + std::string(GAME_NAME))
340
341 VkInstanceCreateInfo createInfo {};
342 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
343 createInfo.pApplicationInfo = &appInfo;
344
345 // uint32_t glfwExtensionCount = 0;
346 // const char** glfwExtensions;
347 // glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
348
349 auto extensions = getRequiredExtensions();
350 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
351 createInfo.ppEnabledExtensionNames = extensions.data();
352
353#ifdef ENABLE_VULKAN_VALIDATION_LAYERS
354 if (!checkValidationLayerSupport()) {
355 LOG(1, "Validation layers requested, but not available")
356 throw std::runtime_error(
357 "Validation layers requested, but not available");
358 }
359#endif
360
361 createInfo.enabledLayerCount = 0;
362 createInfo.pNext = nullptr;
363
364#ifdef ENABLE_VULKAN_VALIDATION_LAYERS
365 createInfo.enabledLayerCount
366 = static_cast<uint32_t>(validationLayers.size());
367 createInfo.ppEnabledLayerNames = validationLayers.data();
368 // uncomment below if u need fine details. It just creates extra verbose
369 // generally not needed
370 // VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo {};
371 // populateDebugMessengerCreateInfo(debugCreateInfo);
372 // createInfo.pNext =
373 // (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo;
374#endif
375 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
376 LOG(1, "Failed to create instance")
377 throw std::runtime_error("Failed to create instance");
378 }
379}
380
381#ifdef ENABLE_VULKAN_VALIDATION_LAYERS
382void Chronos::Engine::Engine::setupDebugMessenger()
383{
384 VkDebugUtilsMessengerCreateInfoEXT createInfo;
385 populateDebugMessengerCreateInfo(createInfo);
386 if (CreateDebugUtilsMessengerEXT(
387 instance, &createInfo, nullptr, &debugMessenger)
388 != VK_SUCCESS) {
389 LOG(1, "Failed to set up debug messenger")
390 throw std::runtime_error("Failed to set up debug messenger");
391 }
392}
393#endif
394
396{
397 if (glfwCreateWindowSurface(instance, window, nullptr, &surface)
398 != VK_SUCCESS) {
399 LOG(1, "Failed to create window surface")
400 throw std::runtime_error("Failed to create window surface");
401 }
402}
403
405{
406 imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
407 renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
408 inFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
409
410 VkSemaphoreCreateInfo semaphoreInfo {};
411 semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
412
413 VkFenceCreateInfo fenceInfo {};
414 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
415 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
416
417 for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
418 if (vkCreateSemaphore(device.device, &semaphoreInfo, nullptr,
419 &imageAvailableSemaphores[i])
420 != VK_SUCCESS
421 || vkCreateSemaphore(device.device, &semaphoreInfo, nullptr,
422 &renderFinishedSemaphores[i])
423 != VK_SUCCESS
424 || vkCreateFence(
425 device.device, &fenceInfo, nullptr, &inFlightFences[i])
426 != VK_SUCCESS) {
427 LOG(1, "Failed to create synchronization objects for a frame")
428 throw std::runtime_error(
429 "failed to create synchronization objects for a frame!");
430 }
431 }
432}
433
434#ifdef ENABLE_EDITOR
435void Chronos::Engine::Engine::setEditorAddElementsCallback(
436 void (*editorAddElements)())
437{
438 gui.addElements = editorAddElements;
439}
440#endif
441
443{
444 vkDeviceWaitIdle(device.device);
445 swapChain.recreate();
446 objectManager.recreate();
447#ifdef ENABLE_EDITOR
448 gui.recreate();
449#endif
450}
451
453{
454 if (mode == "immediate") {
455 this->swapChain.preferredPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
456 } else if (mode == "fifo") {
457 this->swapChain.preferredPresentMode = VK_PRESENT_MODE_FIFO_KHR;
458 } else if (mode == "fifo_relaxed") {
459 this->swapChain.preferredPresentMode = VK_PRESENT_MODE_FIFO_RELAXED_KHR;
460 } else if (mode == "mailbox") {
461 this->swapChain.preferredPresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
462 } else {
463 throw std::runtime_error("Invalid present mode");
464 }
465 this->swapChain.changePresentMode = true;
466 LOG(3, "Present mode set to " + mode)
467}
468
470{
471 VkSampleCountFlagBits maxSample = device.maxMsaaSamples;
472 std::vector<std::string> modes;
473 if (VK_SAMPLE_COUNT_64_BIT <= maxSample) {
474 modes.push_back("64");
475 }
476 if (VK_SAMPLE_COUNT_32_BIT <= maxSample) {
477 modes.push_back("32");
478 }
479 if (VK_SAMPLE_COUNT_16_BIT <= maxSample) {
480 modes.push_back("16");
481 }
482 if (VK_SAMPLE_COUNT_8_BIT <= maxSample) {
483 modes.push_back("8");
484 }
485 if (VK_SAMPLE_COUNT_4_BIT <= maxSample) {
486 modes.push_back("4");
487 }
488 if (VK_SAMPLE_COUNT_2_BIT <= maxSample) {
489 modes.push_back("2");
490 }
491 modes.push_back("1");
492 return modes;
493}
494
496{
497 if (mode == "64") {
498 this->newMSAAMode = VK_SAMPLE_COUNT_64_BIT;
499 } else if (mode == "32") {
500 this->newMSAAMode = VK_SAMPLE_COUNT_32_BIT;
501 } else if (mode == "16") {
502 this->newMSAAMode = VK_SAMPLE_COUNT_16_BIT;
503 } else if (mode == "8") {
504 this->newMSAAMode = VK_SAMPLE_COUNT_8_BIT;
505 } else if (mode == "4") {
506 this->newMSAAMode = VK_SAMPLE_COUNT_4_BIT;
507 } else if (mode == "2") {
508 this->newMSAAMode = VK_SAMPLE_COUNT_2_BIT;
509 } else if (mode == "1") {
510 this->newMSAAMode = VK_SAMPLE_COUNT_1_BIT;
511 } else {
512 throw std::runtime_error("Invalid MSAA mode");
513 }
514 if (this->newMSAAMode > device.maxMsaaSamples) {
515 throw std::runtime_error("Invalid MSAA mode");
516 }
517 this->changeMSAAFlag = true;
518 LOG(3, "MSAA to be changed to " + mode)
519}
520
522{
523 device.msaaSamples = this->newMSAAMode;
524 this->swapChain.changeMsaa();
525 this->objectManager.changeMsaa();
526#ifdef ENABLE_EDITOR
527 this->gui.changeMsaa();
528#endif
529 LOG(3, "MSAA changed to " + std::to_string(device.msaaSamples))
530}
void initVulkan()
The complicated process of initalizing the vulkan API is done here.
Definition engine.cpp:74
void resizeFrameBuffer()
Tells the engine that the window has to be resized. This is called by GLFW.
Definition engine.cpp:56
void createSurface()
Creates the surface to render to.
Definition engine.cpp:395
void drawFrame()
Main draw call. Its responsible for drawing frames and resizing the window. To be added to the main g...
Definition engine.cpp:137
void initWindow()
This function initializes the GLFW window.
Definition engine.cpp:58
void cleanup()
Cleans up vulkan objects.
Definition engine.cpp:104
void createSyncObjects()
Creates sync objects for rendering.
Definition engine.cpp:404
void createInstance()
Creates a vulkan instance.
Definition engine.cpp:319
~Engine()
This is the destructor of the engine.
Definition engine.cpp:49
Engine()
Initlizes the engine.
Definition engine.cpp:37
void setPresentMode(std::string mode)
Changes the present mode of the swapchain.
Definition engine.cpp:452
void changeMSAA(std::string)
Definition engine.cpp:495
std::vector< std::string > getAvailableMSAAModes()
Definition engine.cpp:469
static std::vector< const char * > getRequiredExtensions()
Gets the required extensions to be enabled.
Definition engine.cpp:305
static void framebuffer_size_callback(GLFWwindow *window, int width, int height)
Definition engine.cpp:30
Contains the Engine class.
Contains various common functions used by other classes in the Engine namespace.
#define LOG(LEVEL, MESSAGE)
Definition logging.hpp:60
VkCommandPool createCommandPool(Chronos::Engine::Device device, VkSurfaceKHR surface)
Creates a command pool for a given device and surface.
Definition helper.cpp:157
Contains the functions needed for initalization of vulkan validation layers.
#define MAX_FRAMES_IN_FLIGHT
The number of frames in flight.