SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
Application.cpp
Go to the documentation of this file.
7#include <Core/Window.hpp>
8#include <algorithm>
9#include <cstring>
10#include <exception>
11#include <stdexcept>
13#include "Core/Logger.hpp"
14#include <Memory/ObjectPtr.hpp>
15
17#include <Runtime/MeshBatch.hpp>
18#include <Camera/Camera.hpp>
19
20#include <Core/GameObject.hpp>
21#include <Math/Quaternion.hpp>
22#include <Math/Random.hpp>
24#include <Core/ScopedTimer.hpp>
25#include <Math/Matrix.hpp>
26#include <UI/UI.hpp>
28#include <Runtime/MeshData.hpp>
33#include <Runtime/Skybox.hpp>
34#include <Runtime/Material.hpp>
36
37using namespace Sleak;
38using namespace Sleak::Math;
39
40int width = 1200;
41int height = 800;
42
43namespace Sleak {
44 Application* Application::Instance = nullptr;
45
46 Application::Application(const char* Name) :
48 {Name, Arguments(0, nullptr)}
49 )
50 {}
51
52 Application::Application(ApplicationDefaults Settings) : Specification(Settings) {
53 if (Instance) {
54 throw std::runtime_error("The Application is already running!");
55 }
56 Instance = this;
57
58 // Read all settings from CommandLine (parsed in main before Application)
59 {
60 const std::string wStr = CommandLine::GetValue("-w");
61 const std::string hStr = CommandLine::GetValue("-h");
62 if (!wStr.empty()) try { width = std::stoi(wStr); } catch (...) {}
63 if (!hStr.empty()) try { height = std::stoi(hStr); } catch (...) {}
64 }
65
66 {
67 std::string title = CommandLine::GetValue("-t");
68 if (!title.empty()) {
69 std::replace(title.begin(), title.end(), '_', ' ');
70 Specification.Name = title;
71 }
72 }
73
74 CoreWindow = new Window(width, height, Specification.Name);
75
76 try {
77 const std::string rendererArg = CommandLine::GetValue("-r");
78 if (!rendererArg.empty()) {
79 renderer = RenderEngine::RendererFactory::ParseArg(rendererArg, CoreWindow);
80 } else {
81 #ifdef PLATFORM_WIN
84 #else
87 #endif
88 }
89 }
90 catch (std::exception& e) {
91 SLEAK_ERROR(std::string(e.what()));
94 }
95
99
100 }
101
103 // Flush the GPU before tearing down any scene-owned resources (textures,
104 // meshes, materials) — the renderer's descriptor sets still reference
105 // samplers/image views owned by the game's scene objects. Without this
106 // wait, Texture destructors call vkDestroySampler while the descriptor
107 // set binding still has the sampler in use (VUID-vkDestroySampler-sampler-01082).
108 if (renderer) renderer->WaitIdle();
109
110 delete Game;
111 Sleak::UI::ShutdownTextureCache(); // frees cached VkImage/memory pre-device-teardown
113 delete m_benchmark;
114 m_benchmark = nullptr;
115 delete m_DebugOverlay;
116 m_DebugOverlay = nullptr;
117
118 if (renderer)
119 renderer->Cleanup();
120
121 delete renderer;
122 delete CoreWindow;
123 SLEAK_LOG("The application has been successfully closed, have a good day sir");
124 }
125
127 Game = game;
128
129 float lastTime = FrameTimer.Elapsed();
130
131 if(CoreWindow && CoreWindow->InitializeWindow()) {
132
133 if(renderer && renderer->Initialize()) {
134
135 renderer->CreateImGUI();
136 if (renderer->GetImGUIEnabled())
137 CoreWindow->SetImGuiReady(true);
138
139 m_DebugOverlay = new DebugOverlay();
140 m_DebugOverlay->Initialize(renderer, game);
141
142 m_benchmark = new Benchmark();
143 m_benchmark->Initialize(renderer);
144 {
145 auto& cfg = m_DebugOverlay->GetConfig();
146 cfg.ShowCameraPanel = false;
147 cfg.ShowPerformancePanel = false;
148 }
149
150 auto context = renderer->GetContext();
152
153 float lastTime = FrameTimer.Elapsed();
154 float accumulator = 0.0f;
155 const float fixedTimestep = 1.0f / 60.0f;
156
157 // Initialize and begin the game (and scene)
158 if (Game) {
159 if (!Game->Initialize()) {
160 SLEAK_FATAL("Game failed to initialize!");
161 return -1;
162 }
163
164 Game->Begin();
165
166 // Apply CLI graphics settings
167 {
168 if (CommandLine::HasFlag("--vsync")) renderer->SetVSync(true);
169 if (CommandLine::HasFlag("--no-vsync")) renderer->SetVSync(false);
170
171 const std::string msaaStr = CommandLine::GetValue("-msaa");
172 if (!msaaStr.empty()) {
173 try { renderer->SetMSAASampleCount(
174 static_cast<uint32_t>(std::stoi(msaaStr))); }
175 catch (...) {}
176 }
177
178 if (CommandLine::HasFlag("--fullscreen")) CoreWindow->ToggleFullScreen();
179 }
180
181 // Auto-start benchmark if --bench / --benchmark was passed
182 if ((CommandLine::HasFlag("--bench") || CommandLine::HasFlag("--benchmark"))
183 && m_benchmark)
184 m_benchmark->ToggleRecording();
185 }
186
187 while(!CoreWindow->ShouldClose()) {
188
189 float currentTime = FrameTimer.Elapsed();
190 DeltaTime = currentTime - lastTime;
191 lastTime = currentTime;
192
193 #if defined(_DEBUG) && defined(COUNT_FRAME)
194 ScopedTimer("Frame Timer");
195 #endif
196
197 CoreWindow->Update();
198
199 // Apply any pending resize (deferred from event handler to avoid GPU hang)
200 if (m_pendingResize) {
201 renderer->Resize(m_pendingResizeW, m_pendingResizeH);
202 width = static_cast<int>(m_pendingResizeW);
203 height = static_cast<int>(m_pendingResizeH);
204 m_pendingResize = false;
205
206 // Update active camera's projection matrix for the new aspect ratio.
207 // Vulkan renderer ignores the width/height args to Resize() and uses
208 // the Vulkan surface caps, so the camera must be notified separately.
209 if (Game && Game->GetActiveScene()) {
210 if (auto* cam = Game->GetActiveScene()->GetActiveCamera())
211 cam->OnResize(m_pendingResizeW, m_pendingResizeH);
212 }
213 }
214
215 renderer->BeginRender();
216
217 // Update active scene if present
218 if (Game && Game->GetActiveScene()) {
219 auto* activeScene = Game->GetActiveScene();
220
221 // Fixed timestep updates (physics, etc.)
222 accumulator += DeltaTime;
223 while (accumulator >= fixedTimestep) {
224 activeScene->FixedUpdate(fixedTimestep);
225 accumulator -= fixedTimestep;
226 }
227
228 // Per-frame update
229 activeScene->Update(DeltaTime);
230
231 // Late update (after all updates, e.g. camera follow)
232 activeScene->LateUpdate(DeltaTime);
233 }
234
235 // Per-frame game logic
236 if (Game)
237 Game->Loop(DeltaTime);
238 if (m_DebugOverlay)
239 m_DebugOverlay->Render(DeltaTime);
240
241 if (m_benchmark)
242 m_benchmark->Tick(DeltaTime);
243
244 renderer->FlushPendingTransfers();
245
246 if (queue && context)
247 queue->ExecuteCommands(context);
248
249 renderer->EndRender();
250 }
251 }
252 else{
253 SLEAK_FATAL("Unable to initialize graphics!");
254 return -1;
255 }
256
257 }
258 else {
259 SLEAK_FATAL("App cannot run without any window!");
260 return -2;
261 }
262
263 return 0;
264 }
265
267 // Store and defer — applying during event dispatch causes GPU hangs on rapid resize
268 m_pendingResizeW = e.GetWidth();
269 m_pendingResizeH = e.GetHeight();
270 m_pendingResize = true;
271 }
272
274 int w = Window::GetWidth();
275 int h = Window::GetHeight();
276 if (w > 0 && h > 0) {
277 m_pendingResizeW = static_cast<uint32_t>(w);
278 m_pendingResizeH = static_cast<uint32_t>(h);
279 m_pendingResize = true;
280 }
281 }
282
286
289
291
292 switch(e.GetKeyCode())
293 {
295 // Let the game handle ESC (e.g. return to menu)
296 // Only close window if no game is running
297 if (!Game)
298 GetWindow().Close();
299 break;
300
302 break;
303
305 break;
306
308 break;
309
311 {
312 if (Game && Game->GetActiveScene()) {
313 Camera* cam = Game->GetActiveScene()->GetActiveCamera();
314 if (cam) {
315 auto* fpc = cam->GetComponent<FirstPersonController>();
316 if (fpc) {
317 fpc->SetEnabled(!fpc->IsEnabled());
318 } else {
319 auto* ctrl = cam->GetComponent<FreeLookCameraController>();
320 if (ctrl)
321 ctrl->SetEnabled(!ctrl->IsEnabled());
322 }
323 }
324 }
325 }
326 break;
327
329 CoreWindow->ToggleFullScreen();
330 break;
331
333 if (m_benchmark)
334 m_benchmark->ToggleRecording();
335 break;
336
337 }
338
339 }
340
342 return *CoreWindow;
343 }
344
346 CoreWindow->Close();
347 }
348
350 if (renderer) renderer->WaitIdle();
351 }
352
353 void Application::SetCursorVisible(bool visible) {
354 if (visible)
355 SDL_ShowCursor();
356 else
357 SDL_HideCursor();
358 }
359
361 if (CoreWindow)
362 CoreWindow->SetRelativeMouseMode(enabled);
363 }
364
365 int Application::GetFPS() const { return renderer->GetFrameRate(); }
366 float Application::GetFrameTime() const { return renderer->GetFrameTime(); }
367 int Application::GetVertices() const { return renderer->GetVertices(); }
368 int Application::GetTriangles() const { return renderer->GetTriangles(); }
369 size_t Application::GetGPUMemoryUsed() const { return renderer->GetGPUMemoryUsed(); }
370 size_t Application::GetGPUMemoryBudget() const { return renderer->GetGPUMemoryBudget(); }
371 const char* Application::GetRendererTypeStr() const { return renderer->GetTypeStr(); }
372
373 void Application::GetRendererTypeColor(float& r, float& g, float& b) const {
374 switch (renderer->GetType()) {
375 case RenderEngine::RendererType::DirectX12: r = 0.0f; g = 0.5f; b = 1.0f; break;
376 case RenderEngine::RendererType::DirectX11: r = 0.2f; g = 0.6f; b = 0.8f; break;
377 case RenderEngine::RendererType::Vulkan: r = 0.8f; g = 0.2f; b = 0.0f; break;
378 case RenderEngine::RendererType::OpenGL: r = 0.0f; g = 0.8f; b = 0.2f; break;
379 default: r = 0.5f; g = 0.5f; b = 0.5f; break;
380 }
381 }
382
383 uint32_t Application::GetMSAASampleCount() const { return renderer->GetMSAASampleCount(); }
384 uint32_t Application::GetMaxMSAASampleCount() const { return renderer->GetMaxMSAASampleCount(); }
385 void Application::SetMSAASampleCount(uint32_t samples) { renderer->SetMSAASampleCount(samples); }
386
387 bool Application::GetVSync() const { return renderer->GetVSync(); }
388 void Application::SetVSync(bool enabled) { renderer->SetVSync(enabled); }
389
390 bool Application::IsSSAOEnabled() const { return renderer->IsSSAOEnabled(); }
391 void Application::SetSSAOEnabled(bool e) { renderer->SetSSAOEnabled(e); }
392 float Application::GetSSAORadius() const { return renderer->GetSSAORadius(); }
393 void Application::SetSSAORadius(float r) { renderer->SetSSAORadius(r); }
394 float Application::GetSSAOBias() const { return renderer->GetSSAOBias(); }
395 void Application::SetSSAOBias(float b) { renderer->SetSSAOBias(b); }
396 float Application::GetSSAOPower() const { return renderer->GetSSAOPower(); }
397 void Application::SetSSAOPower(float p) { renderer->SetSSAOPower(p); }
398
399 bool Application::IsIBLEnabled() const { return renderer->IsIBLEnabled(); }
400 void Application::SetIBLEnabled(bool e) { renderer->SetIBLEnabled(e); }
401 float Application::GetIBLIntensity() const { return renderer->GetIBLIntensity(); }
402 void Application::SetIBLIntensity(float i){ renderer->SetIBLIntensity(i); }
403
404 bool Application::IsSSREnabled() const { return renderer->IsSSREnabled(); }
405 void Application::SetSSREnabled(bool e) { renderer->SetSSREnabled(e); }
406
407 bool Application::IsTAAEnabled() const { return renderer->IsTAAEnabled(); }
408 void Application::SetTAAEnabled(bool e) { renderer->SetTAAEnabled(e); }
409
410 bool Application::IsBloomEnabled() const { return renderer->IsBloomEnabled(); }
411 void Application::SetBloomEnabled(bool e) { renderer->SetBloomEnabled(e); }
412
414 m_graphicsConfig = cfg;
415 renderer->SetSSAOEnabled(cfg.ssaoEnabled);
416 renderer->SetSSAORadius(cfg.ssaoRadius);
417 renderer->SetSSAOBias(cfg.ssaoBias);
418 renderer->SetSSAOPower(cfg.ssaoPower);
419 renderer->SetSSREnabled(cfg.ssrEnabled);
420 renderer->SetBloomEnabled(cfg.bloomEnabled);
421 renderer->SetIBLEnabled(cfg.iblEnabled);
422 renderer->SetIBLIntensity(cfg.iblIntensity);
423 renderer->SetTAAEnabled(cfg.taaEnabled);
424 renderer->SetShadowMapResolution(cfg.shadowMapResolution);
425 renderer->SetMSAASampleCount(cfg.msaaSamples);
426 renderer->SetTonemappingEnabled(cfg.tonemapEnabled);
427 renderer->SetExposure(cfg.exposure);
428 renderer->SetGamma(cfg.gamma);
429 renderer->SetPCSSEnabled(cfg.pcssEnabled);
430 }
431
433 return m_graphicsConfig;
434 }
435
437 return renderer->GetFeatureCaps();
438 }
439
440}
int width
int height
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
#define SLEAK_FATAL(...)
Definition Logger.hpp:23
#define SLEAK_INFO(...)
Definition Logger.hpp:20
#define SLEAK_LOG(...)
Definition Logger.hpp:19
bool GetVSync() const
Application(const char *ProjectName)
void SetVSync(bool enabled)
uint32_t GetMSAASampleCount() const
void OnWindowResize(const Sleak::Events::WindowResizeEvent &e)
Stashes the new size; the resize is applied at the start of the next frame.
void SetSSAOPower(float power)
void SetSSREnabled(bool enabled)
int GetFPS() const
Frames rendered in the last second.
void SetBloomEnabled(bool enabled)
void onMouseClick(const Sleak::Events::Input::MouseButtonPressedEvent &e)
void OnKeyPressed(const Sleak::Events::Input::KeyPressedEvent &e)
Handles engine-level hotkeys (F9 camera toggle, F11 fullscreen, F12 benchmark, Esc).
bool IsBloomEnabled() const
void SetIBLEnabled(bool enabled)
void SetIBLIntensity(float intensity)
const char * GetRendererTypeStr() const
Human-readable name of the active backend (e.g. "Vulkan").
float GetIBLIntensity() const
void OnWindowFullScreen(const Sleak::Events::WindowFullScreen &e)
Treats entering/leaving fullscreen as a resize to the current window size.
int GetVertices() const
Vertices submitted in the last frame.
void SetSSAORadius(float radius)
void SetMSAASampleCount(uint32_t samples)
size_t GetGPUMemoryBudget() const
void onMouseMove(const Sleak::Events::Input::MouseMovedEvent &e)
float GetFrameTime() const
Duration of the last frame, in seconds.
const GraphicsConfig & GetGraphicsConfig() const
Config last passed to ApplyGraphicsConfig().
void GetRendererTypeColor(float &r, float &g, float &b) const
UI accent color associated with the active backend.
bool IsSSAOEnabled() const
void WaitGPUIdle()
Blocks until the GPU finishes all in-flight work. Call before tearing down scene resources.
void ApplyGraphicsConfig(const GraphicsConfig &cfg)
Pushes every field of cfg onto the active renderer in one call.
float GetSSAORadius() const
void SetSSAOBias(float bias)
void SetMouseRelativeMode(bool enabled)
size_t GetGPUMemoryUsed() const
bool IsIBLEnabled() const
int GetTriangles() const
Triangles submitted in the last frame.
bool IsSSREnabled() const
int Run(GameBase *game)
Drives the game loop until the window closes; returns the process exit code.
float GetSSAOPower() const
void SetCursorVisible(bool visible)
uint32_t GetGraphicsCaps() const
float GetSSAOBias() const
uint32_t GetMaxMSAASampleCount() const
bool IsTAAEnabled() const
void SetTAAEnabled(bool enabled)
void SetSSAOEnabled(bool enabled)
static std::string GetValue(const std::string &flag, const std::string &defaultVal="")
Returns the raw string value stored for -flag, or defaultVal if it was not passed.
static bool HasFlag(const std::string &flag)
True if --flag was present on the command line.
static std::string RegisterEventHandler(T *instance, void(T::*memberFunction)(const EventT &))
Registers a member-function handler bound to instance, returning an ID for later unregistration.
Definition Event.hpp:149
std::string ToString() const override
void SetEnabled(bool enabled) override
Toggles relative mouse mode and, when re-enabling, resets velocity and re-syncs yaw/pitch.
T * GetComponent()
Finds the first attached component of type T, or nullptr.
static void Shutdown()
Release static resources before renderer cleanup.
static RenderCommandQueue * GetInstance()
Lazily creates and returns the process-wide singleton instance.
static Renderer * ParseArg(std::string arg, Window *window)
Resolves a CLI backend name (e.g. "-vulkan") to a Renderer instance.
static Renderer * CreateRenderer(RendererType type, Window *window)
Instantiates and returns the Renderer for the given backend, bound to a window.
RAII stopwatch: logs elapsed time on destruction under the given name.
static int GetHeight()
Definition Window.hpp:48
static int GetWidth()
Definition Window.hpp:47
void Close()
Marks the window for close; actual teardown happens in the destructor.
Definition Window.cpp:200
Vectors, matrices, quaternions, colors, AABBs, and random helpers.
ENGINE_API void ShutdownTextureCache()
Definition UI.cpp:273
Root namespace for everything the engine exposes.
Definition Camera.hpp:10
class ENGINE_API Window