Files
Destrum/destrum/src/App.cpp
T

404 lines
10 KiB
C++

#include <chrono>
#include <exception>
#include <SDL_vulkan.h>
#include <thread>
#include <stdexcept>
#include <destrum/App.h>
#include <destrum/FS/AssetFS.h>
#include <destrum/Scene/SceneManager.h>
#include <destrum/Util/DeltaTime.h>
#include "imgui.h"
#include "glm/gtx/transform.hpp"
#include "spdlog/spdlog.h"
#include <tracy/Tracy.hpp>
#include <common/TracySystem.hpp>
struct TracyFrameScope
{
~TracyFrameScope()
{
FrameMark;
}
};
App::App()
{
}
App::~App()
{
if (!cleanedUp) {
try {
cleanup();
} catch (...) {
// Destructors must not throw. Initialization failures are already
// reported by the caller, while cleanup is best effort here.
}
}
}
void App::init(const AppParams& params)
{
cleanedUp = false;
customInitStarted = false;
try {
m_params = params;
ZoneScopedN("App::init");
tracy::SetThreadName("Main Thread");
TracySetProgramName(params.appName.c_str());
AssetFS::GetInstance().Init(params.exeDir);
window = SDL_CreateWindow(
params.windowTitle.c_str(),
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
params.windowSize.x,
params.windowSize.y,
SDL_WINDOW_VULKAN);
if (!window) {
spdlog::error("Failed to create window. SDL Error: {}", SDL_GetError());
throw std::runtime_error(
"Failed to create window: " + std::string{SDL_GetError()});
}
SDL_SetWindowResizable(window, SDL_TRUE);
gfxDevice.init(window, params.appName, false);
imguiPass.init(window, gfxDevice);
InputManager::GetInstance().Init();
Time::GetInstance().Update();
customInitStarted = true;
customInit();
cleanedUp = false;
} catch (...) {
try {
cleanup();
} catch (...) {
}
throw;
}
}
void App::run()
{
ZoneScopedN("App::run");
Time::GetInstance().Update();
const float fixedDt = static_cast<float>(Time::GetInstance().FixedDeltaTime());
const int maxSteps = 5;
float accumulator = 0.0f;
isRunning = true;
while (isRunning)
{
TracyFrameScope tracyFrame;
ZoneScopedN("App::Frame");
Time::GetInstance().Update();
float dt = static_cast<float>(Time::GetInstance().DeltaTime());
if (dt > 0.25f) dt = 0.25f;
if (dt > 0.0f)
{
float newFPS = 1.0f / dt;
avgFPS = std::lerp(avgFPS, newFPS, 0.1f);
TracyPlot("FPS", avgFPS);
TracyPlot("Delta Time ms", dt * 1000.0f);
}
accumulator += dt;
{
ZoneScopedN("Input BeginFrame");
InputManager::GetInstance().BeginFrame();
}
{
ZoneScopedN("SDL Events");
SDL_Event event;
while (SDL_PollEvent(&event))
{
ZoneScopedN("SDL Event");
imguiPass.handleEvent(event);
if (event.type == SDL_QUIT)
{
isRunning = false;
break;
}
if (event.type == SDL_WINDOWEVENT)
{
switch (event.window.event)
{
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
resizePending = true;
lastResizeTime = std::chrono::steady_clock::now();
break;
}
}
const bool mouseEvent =
event.type == SDL_MOUSEBUTTONDOWN ||
event.type == SDL_MOUSEBUTTONUP ||
event.type == SDL_MOUSEMOTION ||
event.type == SDL_MOUSEWHEEL;
const bool keyboardEvent =
event.type == SDL_KEYDOWN ||
event.type == SDL_KEYUP ||
event.type == SDL_TEXTINPUT;
const bool capturedByImgui =
(mouseEvent && imguiPass.wantsMouse()) ||
(keyboardEvent && imguiPass.wantsKeyboard());
if (!capturedByImgui)
{
ZoneScopedN("Input ProcessEvent");
if (InputManager::GetInstance().ProcessEvent(event))
{
isRunning = false;
}
}
}
}
if (!isRunning) break;
// Consume SDL events before updating the base camera so held and
// newly pressed inputs are applied in the same frame.
camera.Update(dt);
{
ZoneScopedN("ImGui BeginFrame");
imguiPass.beginFrame();
}
{
ZoneScopedN("customUpdate");
customUpdate(dt);
}
{
ZoneScopedN("Debug ImGui");
ImGui::Begin("Debug");
ImGui::Text("FPS: %.2f", avgFPS);
ImGui::End();
drawPhysicsPanel(dt, fixedDt, accumulator);
TracyPlot("FPS", avgFPS);
TracyPlot("Frame Time ms", dt * 1000.0f);
TracyPlot("Accumulator ms", accumulator * 1000.0f);
}
{
ZoneScopedN("ImGui EndFrame");
imguiPass.endFrame();
}
{
ZoneScopedN("FixedUpdate");
int steps = 0;
if (m_PhysicsPaused)
{
// Important: prevent physics from building up a huge backlog while paused.
accumulator = 0.0f;
if (m_PhysicsStepOnce)
{
ZoneScopedN("Single Physics Step");
customFixedUpdate(fixedDt * m_PhysicsTimeScale);
steps = 1;
m_PhysicsStepOnce = false;
}
}
else
{
while (accumulator >= fixedDt && steps < maxSteps)
{
ZoneScopedN("Fixed Step");
customFixedUpdate(fixedDt * m_PhysicsTimeScale);
accumulator -= fixedDt;
steps++;
}
if (steps == maxSteps)
{
accumulator = 0.0f;
}
}
m_PhysicsStepsLastFrame = steps;
TracyPlot("Fixed Steps", static_cast<int64_t>(steps));
}
const float alpha = accumulator / fixedDt;
(void)alpha;
{
ZoneScopedN("Swapchain Resize Check");
if (gfxDevice.needsSwapchainRecreate() || resizePending)
{
auto now = std::chrono::steady_clock::now();
if (resizePending &&
now - lastResizeTime < std::chrono::milliseconds(100))
{
continue;
}
int w = 0;
int h = 0;
SDL_Vulkan_GetDrawableSize(window, &w, &h);
if (w == 0 || h == 0)
{
continue;
}
spdlog::info("Recreating swapchain to size: {}x{}", w, h);
{
ZoneScopedN("Recreate Swapchain");
gfxDevice.recreateSwapchain(w, h);
imguiPass.onSwapchainRecreated();
onWindowResize(w, h);
}
resizePending = false;
continue;
}
}
{
ZoneScopedN("customDraw");
customDraw();
}
if (frameLimit)
{
ZoneScopedN("Frame Limit Sleep");
auto sleepTime = Time::GetInstance().SleepDuration();
if (sleepTime.count() > 0)
{
std::this_thread::sleep_for(sleepTime);
}
}
}
gfxDevice.waitIdle();
}
void App::cleanup()
{
if (cleanedUp || cleaningUp) {
return;
}
cleaningUp = true;
spdlog::info("Cleaning up");
std::exception_ptr firstException;
const auto attempt = [&firstException](auto&& operation) {
try {
operation();
} catch (...) {
if (!firstException) {
firstException = std::current_exception();
}
}
};
if (customInitStarted) {
attempt([this] { customCleanup(); });
}
attempt([] { SceneManager::GetInstance().Destroy(); });
attempt([this] { renderer.cleanup(gfxDevice); });
attempt([this] { imguiPass.cleanup(); });
attempt([this] { resources.cleanup(gfxDevice); });
attempt([this] { gfxDevice.cleanup(); });
if (window) {
SDL_DestroyWindow(window);
window = nullptr;
}
AssetFS::GetInstance().Reset();
customInitStarted = false;
cleanedUp = true;
cleaningUp = false;
if (firstException) {
std::rethrow_exception(firstException);
}
}
void App::drawPhysicsPanel(float dt, float fixedDt, float accumulator)
{
ImGui::Begin("Physics");
ImGui::Text("Frame dt: %.3f ms", dt * 1000.0f);
ImGui::Text("Fixed dt: %.3f ms", fixedDt * 1000.0f);
ImGui::Text("Accumulator: %.3f ms", accumulator * 1000.0f);
ImGui::Text("Steps last frame: %d", m_PhysicsStepsLastFrame);
ImGui::Separator();
if (ImGui::Button(m_PhysicsPaused ? "Resume Physics" : "Pause Physics"))
{
m_PhysicsPaused = !m_PhysicsPaused;
}
ImGui::SameLine();
if (!m_PhysicsPaused)
{
ImGui::BeginDisabled();
ImGui::Button("Step Physics");
ImGui::EndDisabled();
}
else
{
if (ImGui::Button("Step Physics"))
{
m_PhysicsStepOnce = true;
}
}
ImGui::Checkbox("Paused", &m_PhysicsPaused);
ImGui::Separator();
ImGui::SliderFloat("Physics Time Scale", &m_PhysicsTimeScale, 0.0f, 2.0f, "%.2fx");
if (ImGui::Button("Reset Time Scale"))
{
m_PhysicsTimeScale = 1.0f;
}
ImGui::End();
}