RavEngine
Loading...
Searching...
No Matches
App_priv.hpp
1#include "App.hpp"
2#if !RVE_SERVER
3 #include "RenderEngine.hpp"
4 #include <SDL3/SDL_events.h>
5 #include "Texture.hpp"
6 #include <RmlUi/Core.h>
7 #include "GUI.hpp"
8 #include "Material.hpp"
9 #include "RMLFileInterface.hpp"
10 #include "InputManager.hpp"
11 #include "Skybox.hpp"
12 #include <SDL3/SDL.h>
13 #include "AudioPlayer.hpp"
14 #include "Window.hpp"
15 #include <RGL/Swapchain.hpp>
16 #include <RGL/CommandBuffer.hpp>
17 #include "OpenXRIntegration.hpp"
18 #include "BuiltinTonemap.hpp"
19#endif
20#include <algorithm>
21#include "MeshAsset.hpp"
22#include <physfs.h>
23#include <steam/steamnetworkingsockets.h>
24#include <steam/isteamnetworkingutils.h>
25#include <filesystem>
26#include "Function.hpp"
27#include "World.hpp"
28#include "GetApp.hpp"
29#include "Defines.hpp"
30#include "VirtualFileSystem.hpp"
31#include "MeshAssetSkinned.hpp"
32
33#include "CameraComponent.hpp"
34#include <csignal>
35#include "Debug.hpp"
36#include "Profile.hpp"
37
38#ifdef _WIN32
39 #include <Windows.h>
40 #include <winuser.h>
41 #undef min
42#endif
43
44#ifdef __APPLE__
45 #include "AppleUtilities.h"
46#endif
47
48using namespace std;
49using namespace RavEngine;
50using namespace std::chrono;
51
52// pointer to the current app instance
53static App* currentApp = nullptr;
54
55#if !RVE_SERVER
56void RGLFatalCallback(const std::string& msg, void* userData) {
57 Debug::Fatal(msg);
58};
59
60void RGLmsgCallback(RGL::MessageSeverity severity, const std::string& msg, void* userData) {
61 switch (severity) {
62 case RGL::MessageSeverity::Info:
63 Debug::Log(msg);
64 break;
65 case RGL::MessageSeverity::Warning:
66 Debug::Warning(msg);
67 break;
68 case RGL::MessageSeverity::Error:
69 Debug::Error(msg);
70 break;
71 }
72 // fatal errors are handled by the fatal callback
73};
74#endif
75
76// on crash, call this
77void crash_signal_handler(int signum) {
78 ::signal(signum, SIG_DFL);
79 Debug::PrintStacktraceHere();
80 ::raise(SIGABRT);
81}
82
86static void DebugOutput( ESteamNetworkingSocketsDebugOutputType eType, const char *pszMsg )
87{
89 {
90 Debug::Fatal("{}",pszMsg);
91 }
92 else{
93 Debug::Log("{}",pszMsg);
94 }
95}
96
97App::App()
98{
99 currentApp = this;
100 // crash signal handlers
101 ::signal(SIGSEGV, &crash_signal_handler);
102 ::signal(SIGABRT, &crash_signal_handler);
103
104 //initialize virtual file system library
105#if __ANDROID__
107 .jnienv = SDL_GetAndroidJNIEnv(),
108 .context = SDL_GetAndroidActivity()
109 };
110 if (PHYSFS_init(reinterpret_cast<const char*>(&androidInit)) == 0){
111 Debug::Fatal("PhysFS failed to init: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
112 }
113#else
114 PHYSFS_init("");
115#endif
116#if !RVE_SERVER
117 Resources = std::make_unique<VirtualFilesystem>();
118#endif
119}
120
121int App::run(int argc, char** argv) {
122#if !RVE_SERVER
123 // initialize SDL2
125 Debug::Fatal("Unable to initialize SDL: {}", SDL_GetError());
126 }
127 {
128 window = std::make_unique<Window>(960, 540, "RavEngine");
129
130 auto config = OnConfigure(argc, argv);
131
132 // initialize RGL and the global Device
133 RGL::API api = RGL::API::PlatformDefault;
134 {
135
136 auto envv = std::getenv("RGL_BACKEND");
137
138 if (envv == nullptr) {
139 goto cont;
140 }
141 auto backend = std::string_view(envv);
142
143 const std::unordered_map<std::string_view, RGL::API> apis{
144 {"metal", decltype(apis)::value_type::second_type::Metal},
145 { "d3d12", decltype(apis)::value_type::second_type::Direct3D12 },
146 { "vulkan", decltype(apis)::value_type::second_type::Vulkan },
147 };
148
149 auto it = apis.find(backend);
150 if (it != apis.end()) {
151 api = (*it).second;
152 }
153 else {
154 std::cerr << "No backend \"" << backend << "\", expected one of:\n";
155 for (const auto& api : apis) {
156 std::cout << "\t - " << RGL::APIToString(api.second) << "\n";
157 }
158 }
159 }
160 cont:
161
163 .api = api,
164 .callback = RGLmsgCallback,
165 .fatal_callback = RGLFatalCallback,
166 .engineName = "RavEngine",
167 };
168 RGL::Init(opt);
169
170 device = RGL::IDevice::CreateSystemDefaultDevice();
171
172 Renderer = std::make_unique<RenderEngine>(config, device);
173 Renderer->dummyTonemap = New<DummyTonemapInstance>(New<DummyTonemap>());
174
175
176 window->InitSwapchain(device, Renderer->mainCommandQueue);
177
178 auto size = window->GetSizeInPixels();
179 mainWindowView = { Renderer->CreateRenderTargetCollection({ static_cast<unsigned int>(size.width), static_cast<unsigned int>(size.height) }) };
180
181#ifdef RVE_XR_AVAILABLE
182 if (wantsXR) {
183 OpenXRIntegration::init_openxr({
184 .device = device,
185 .commandQueue = Renderer->mainCommandQueue
186 });
187 xrRenderViewCollections = OpenXRIntegration::CreateRenderTargetCollections();
188 }
189#endif
190 }
191
192 //setup GUI rendering
193 Rml::SetSystemInterface(&GetRenderEngine());
194 Rml::SetRenderInterface(&GetRenderEngine());
195 Rml::SetFileInterface(new VFSInterface());
196 Rml::Initialise();
197
198#ifndef NDEBUG
199 Renderer->InitDebugger();
200#endif
201
202#ifdef __APPLE__
203 enableSmoothScrolling();
204#endif
205
206 //load the built-in fonts
207 App::Resources->IterateDirectory("fonts", [](const std::string& filename) {
208 auto p = Filesystem::Path(filename);
209 if (p.extension() == ".ttf") {
210 GUIComponent::LoadFont(p.filename().string());
211 }
212 });
213
214 //setup Audio
215 if (NeedsAudio()) {
216 player = std::make_unique<AudioPlayer>();
217 player->Init();
218 }
219#endif
220 //setup networking
221 if (NeedsNetworking()) {
223 if (!GameNetworkingSockets_Init(nullptr, errMsg)) {
224 Debug::Fatal("Networking initialization failed: {}", errMsg);
225 }
226 SteamNetworkingUtils()->SetDebugOutputFunction(k_ESteamNetworkingSocketsDebugOutputType_Msg, DebugOutput);
227 }
228
229 // if built in non-UWP for Windows, need to manually set DPI awareness
230 // for some weird reason, it's not present on ARM
231#if defined _WIN32 && !defined(_M_ARM64)
233 //SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
234#endif
235#if !RVE_SERVER
236
237 {
238 //make the default texture white
239 uint8_t data[] = {0xFF,0xFF,0xFF,0xFF};
240 Texture::Manager::defaultTexture = make_shared<Texture>(1, 1, Texture::Config{
241 .mipLevels = 1,
242 .numLayers = 1,
243 .initialData = {{reinterpret_cast<std::byte*>(data),sizeof(data)}}
244 });
245
246 uint8_t normalData[] = {256/2,256/2, 0xFF,0xFF};
247 Texture::Manager::defaultNormalTexture = New<Texture>(1,1, Texture::Config{
248 .mipLevels = 1,
249 .numLayers = 1,
250 .initialData = {{reinterpret_cast<std::byte*>(normalData),sizeof(normalData)}}
251 });
252
253 uint8_t zeroData[] = { 0,0,0,0 };
254 Texture::Manager::zeroTexture = New<Texture>(1, 1, Texture::Config{
255 .mipLevels = 1,
256 .numLayers = 1,
257 .initialData = {{reinterpret_cast<std::byte*>(zeroData), sizeof(zeroData)}}
258 });
259 }
260#endif
261
262 //invoke startup hook
263 OnStartup(argc, argv);
264
265 lastFrameTime = clocktype::now();
266
267#if !RVE_SERVER
268 float windowScaleFactor = GetMainWindow()->GetDPIScale();
270#endif
271 bool exit = false;
272
273 while (!exit) {
274#if __APPLE__
276#endif
277
278 //setup framerate scaling for next frame
279 auto now = clocktype::now();
280 //will cause engine to run in slow motion if the frame rate is <= 1fps
281 deltaTimeMicroseconds = std::min(duration_cast<timeDiff>(now - lastFrameTime), maxTimeStep);
282 float deltaSeconds = std::chrono::duration<decltype(deltaSeconds)>(deltaTimeMicroseconds).count();
283 time += deltaSeconds;
284 currentScale = deltaSeconds * evalNormal;
285#if !RVE_SERVER
286 RVE_PROFILE_SECTION(events, "Process all Events");
287 auto windowflags = SDL_GetWindowFlags(window->window);
288 while (SDL_PollEvent(&event)) {
289 switch (event.type) {
290 case SDL_EVENT_QUIT:
291 exit = true;
292 break;
295 Renderer->mainCommandQueue->WaitUntilCompleted();
296 window->NotifySizeChanged(event.window.data1, event.window.data2);
297 windowScaleFactor = GetMainWindow()->GetDPIScale();
298 {
299 auto size = window->GetSizeInPixels();
300 Renderer->ResizeRenderTargetCollection(mainWindowView.collection, {uint32_t(size.width), uint32_t(size.height)});
301 }
302 break;
304 exit = true;
305 break;
306 }
307 //process others
308 if (inputManager) {
309 inputManager->ProcessInput(event,windowflags,currentScale, window->windowdims.width, window->windowdims.height, windowScaleFactor);
310#ifndef NDEBUG
311 RenderEngine::debuggerInput->ProcessInput(event,windowflags,currentScale, window->windowdims.width, window->windowdims.height, windowScaleFactor);
312#endif
313 }
314 }
315 RVE_PROFILE_SECTION_END(events);
316#endif // !RVE_SERVER
317 Tick();
318#if RVE_SERVER
319 //make up the difference
320 // because there's no vsync on server builds, we need to add delay
321 //can't just call sleep because sleep is not very accurate
322 clocktype::duration work_time;
323 do{
324 auto workEnd = clocktype::now();
325 work_time = workEnd - now;
326 auto delta = min_tick_time - work_time;
327 if (delta > std::chrono::duration<double, std::milli>(3)) {
328 auto dc = std::chrono::duration_cast<std::chrono::milliseconds>(delta);
329 std::this_thread::sleep_for(std::chrono::duration<double, std::milli>(dc.count()-1));
330 }
331 }while (work_time < min_tick_time);
332#endif
333 lastFrameTime = now;
334#if __APPLE__
335 } // end of @autoreleasepool
336#endif
337 }
338
339 return OnShutdown();
340}
341
342void App::Tick(){
343#if __APPLE__
345#endif
346
347#if !RVE_SERVER
348 RVE_PROFILE_SECTION(getSwapchain, "Acquire Swapchain Image");
350 window->QueueGetNextSwapchainImage(swapchainPresentConfig);
351#ifndef NDEBUG
352 RenderEngine::debuggerInput->TickAxes();
353#endif
354 if (inputManager) {
355 inputManager->TickAxes();
356 }
357#endif
358
359#if !RVE_SERVER
360 auto windowSize = window->GetSizeInPixels();
361 auto scale = window->GetDPIScale();
362#endif
363 RVE_PROFILE_SECTION(tickallworlds, "Tick All Worlds");
364 //tick all worlds
365 for (const auto world : loadedWorlds) {
366 world->Tick(currentScale);
367#if !RVE_SERVER
368 world->Filter([=](GUIComponent& gui) {
369 if (gui.Mode == GUIComponent::RenderMode::Screenspace) {
370 gui.SetDimensions(windowSize.width, windowSize.height);
371 gui.SetDPIScale(scale);
372 }
373 gui.Update();
374 });
375#endif
376 }
377
378 //process main thread tasks
379 {
380 Function<void(void)> front;
381 while (main_tasks.try_dequeue(front)) {
382 front();
383 }
384 }
385 RVE_PROFILE_SECTION_END(tickallworlds);
386#if !RVE_SERVER
387
388 // get the cameras to render
389 auto allCameras = renderWorld->GetAllComponentsOfType<CameraComponent>();
390
391 if (!allCameras)
392 {
393 Debug::Fatal("Cannot render: World does not have a camera!");
394 }
395 mainWindowView.camDatas.clear();
396
397 constexpr auto MakeCamData = [](const auto& camera, uint32_t width, uint32_t height){
398 auto projOnly = camera.GenerateProjectionMatrix(width, height);
399 auto viewOnly = camera.GenerateViewMatrix();
400 auto viewProj = projOnly * viewOnly;
401 auto camPos = camera.GetOwner().GetTransform().GetWorldPosition();
402
403 auto viewportOverride = camera.viewportOverride;
404
405 return RenderViewCollection::camData{ viewProj, projOnly, viewOnly, camPos,{camera.nearClip, camera.farClip} ,viewportOverride, camera.renderLayers, camera.FOV, width, height, &camera.postProcessingEffects, camera.tonemap.get(), camera.indirectLightingSettings};
406 };
407 std::vector<RenderViewCollection> allViews;
408 for(const auto& camera : *allCameras){
409 if (!camera.IsActive()) {
410 continue;
411 }
412 if (!camera.target){
413 continue; // only want render texture cameras
414 }
415
416 auto& collection = camera.target->GetCollection();
417 auto size = collection.depthStencil->GetSize();
418 allViews.push_back({collection, {MakeCamData(camera, size.width, size.height)}, {static_cast<int>(size.width), static_cast<int>(size.height)}});
419 }
420
421 for (const auto& camera : *allCameras) {
422 if (!camera.IsActive()) {
423 continue;
424 }
425 if (camera.target){
426 continue; // no render texture cameras
427 }
428
429 mainWindowView.camDatas.push_back(MakeCamData(camera, windowSize.width, windowSize.height));
430 }
431
432 mainWindowView.pixelDimensions = window->GetSizeInPixels();
433
434#ifdef RVE_XR_AVAILABLE
435 // update OpenXR data if it is requested
436 std::pair<std::vector<XrView>, XrFrameState> xrBeginData;
437 if (wantsXR) {
438 xrBeginData = OpenXRIntegration::BeginXRFrame();
439 OpenXRIntegration::UpdateXRTargetCollections(xrRenderViewCollections, xrBeginData.first);
440 allViews.insert(allViews.end(), xrRenderViewCollections.begin(), xrRenderViewCollections.end());
441 }
442#endif
443
444 auto nextTexture = window->BlockGetNextSwapchainImage(swapchainPresentConfig);
445 RVE_PROFILE_SECTION_END(getSwapchain);
446 mainWindowView.collection.finalFramebuffer = nextTexture.texture;
447 allViews.push_back(mainWindowView);
448 auto mainCommandBuffer = Renderer->Draw(renderWorld, allViews, scale);
449
450
451 // show the results to the user
453 .signalFence = window->swapchainFence,
454 };
455 mainCommandBuffer->Commit(commitconfig);
456
457 window->swapchain->Present(nextTexture.presentConfig);
458 Profile::EndTick();
459
460#ifdef RVE_XR_AVAILABLE
461 if (wantsXR) {
462 OpenXRIntegration::EndXRFrame(xrBeginData.second);
463 }
464#endif
465 if (GetAudioActive()) {
466 player->SetWorld(renderWorld);
467 }
469 ; // dummy statement for the label
470#endif
471
472#if __APPLE__
473 } // end of autoreleasepool
474#endif
475}
476
477float App::CurrentTPS() {
478 return App::evalNormal / currentScale;
479}
480
487 if (!loadedWorlds.contains(newWorld)) {
488 Debug::Fatal("Cannot render an inactive world");
489 }
490 if (renderWorld) {
491 renderWorld->OnDeactivate();
492 renderWorld->isRendering = false;
493 }
494 renderWorld = newWorld;
495 renderWorld->isRendering = true;
496 renderWorld->OnActivate();
497}
498
505 loadedWorlds.insert(world);
506 if (!renderWorld) {
507 SetRenderedWorld(world);
508 }
509
510 // synchronize network if necessary
511 if (networkManager.IsClient() && !networkManager.IsServer()) {
512 networkManager.client->SendSyncWorldRequest(world);
513 }
514}
515
522 loadedWorlds.erase(world);
523 if (renderWorld == world) {
524 renderWorld->OnDeactivate();
525 renderWorld.reset(); //this will cause nothing to render, so set a different world as rendered
526 }
527}
528
534 for (const auto& world : loadedWorlds) {
535 RemoveWorld(world);
536 }
537}
538
546 AddWorld(newWorld);
547 bool updateRender = renderWorld == oldWorld;
548 RemoveWorld(oldWorld);
549 if (updateRender) {
550 SetRenderedWorld(newWorld);
551 }
552}
553
555#if !RVE_SERVER
557 event.type = SDL_EVENT_QUIT;
559#else
560 Debug::Fatal("Quit is not implemented on the server (TODO)");
561#endif
562}
563
564App::~App(){
565 if (!PHYSFS_isInit()){ // unit tests do not initialize the vfs, so we don't want to procede here
566 return;
567 }
568
569#if !RVE_SERVER
570 // ensure the GPU is done doing work
571 window->BlockGetNextSwapchainImage({});
572#ifndef NDEBUG
573 Renderer->DeactivateDebugger();
574#endif
575#endif
578
579#if !RVE_SERVER
580
581 Texture::Manager::defaultTexture.reset();
582 Texture::Manager::defaultNormalTexture.reset();
583 Texture::Manager::zeroTexture.reset();
585 if (GetAudioActive()) {
586 player->Shutdown();
587 }
588#endif
589 networkManager.server.reset();
590 networkManager.client.reset();
591
592#if !RVE_SERVER
593 inputManager = nullptr;
594#endif
595 renderWorld = nullptr;
596 loadedWorlds.clear();
597
600#if !RVE_SERVER
601
602 auto fsi = Rml::GetFileInterface();
603 Rml::Shutdown();
604 Renderer.reset();
605 delete fsi;
606#endif
607
608 currentApp = nullptr;
609}
610
611#if !RVE_SERVER
612void App::SetWindowTitle(const char *title){
613 SDL_SetWindowTitle(window->window, title);
614}
615#endif
616
617std::optional<Ref<World>> RavEngine::App::GetWorldByName(const std::string& name) {
618 std::optional<Ref<World>> value;
619 for (const auto& world : loadedWorlds) {
620 // because std::string "world\0\0" != "world", we need to use strncmp
621 if (std::strncmp(world->worldID.data(), name.data(), World::id_size) == 0) {
622 value.emplace(world);
623 break;
624 }
625 }
626 return value;
627}
628
630 Debug::Warning("Dropped {} audio tasks.", nDropped);
631}
632
633bool RavEngine::App::GetAudioActive() const
634{
635#if !RVE_SERVER
636 return player.operator bool();
637#else
638 return false;
639#endif
640}
641
642
643App* RavEngine::GetApp()
644{
645 return currentApp;
646}
virtual void OnDropAudioWorklets(uint32_t ndropped)
Definition App_priv.hpp:629
void AddReplaceWorld(Ref< World > oldWorld, Ref< World > newWorld)
Definition App_priv.hpp:545
void RemoveWorld(Ref< World > world)
Definition App_priv.hpp:521
void SetRenderedWorld(Ref< World > newWorld)
Definition App_priv.hpp:486
void SetWindowTitle(const char *title)
Definition App_priv.hpp:612
void AddWorld(Ref< World > world)
Definition App_priv.hpp:504
void Quit()
Definition App_priv.hpp:554
void RemoveAllWorlds()
Definition App_priv.hpp:533
Definition CameraComponent.hpp:14
static void Fatal(const std::string_view message)
Definition Debug.hpp:106
static void Warning(const char *message)
Definition Debug.hpp:64
static void Log(const std::string_view message)
Definition Debug.hpp:46
static void Error(const std::string_view message)
Definition Debug.hpp:86
Definition GUI.hpp:23
static bool LoadFont(const std::string &filename)
Definition GUI.cpp:110
Definition RMLFileInterface.hpp:5
Definition concurrentqueue.h:747
uint32 uint32_t
Definition fwd.hpp:131
Definition Animation.hpp:6
Definition EditorCommon.h:232
Definition CommandBuffer.hpp:46
Definition Core.hpp:47
Definition Swapchain.hpp:8
Definition RenderTargetCollection.hpp:31
Definition Texture.hpp:18