18#include "spdlog/spdlog.h"
22 std::unordered_map<std::string, std::shared_ptr<Engine>> Engine::_engines;
25 return [app](
double timestamp,
void* xrFrame) {
26 if (!app || !app->_graphicsDevice) {
31 if (app->_frameRequestId) {
32 app->_frameRequestId =
nullptr;
35 app->_inFrameUpdate =
true;
37 double currentTime = app->processTimestamp(timestamp);
38 if (currentTime == 0) {
39 auto now = std::chrono::high_resolution_clock::now();
40 currentTime = std::chrono::duration<double, std::milli>(now.time_since_epoch()).count();
43 double ms = currentTime - (app->_time > 0 ? app->_time : currentTime);
44 float dt =
static_cast<float>(ms / 1000.0);
45 dt = std::clamp(dt, 0.0f, app->_maxDeltaTime);
46 dt *= app->_timeScale;
48 app->_time = currentTime;
51 if (app->_xr && app->_xr->active()) {
52 app->_frameRequestId = app->_xr->requestAnimationFrame(app->_tick);
55 app->_frameRequestId =
reinterpret_cast<void*
>(1);
58 if (app->_graphicsDevice->contextLost()) {
59 app->_inFrameUpdate =
false;
63 app->fillFrameStatsBasic(currentTime, dt,
static_cast<float>(ms));
64 app->fillFrameStats();
66 app->fire(
"frameupdate", ms);
68 bool skipUpdate =
false;
70 if (xrFrame && app->_xr) {
71 skipUpdate = !app->_xr->update(xrFrame);
77 app->fire(
"framerender");
79 if (app->_autoRender || app->_renderNextFrame) {
81 app->_renderNextFrame =
false;
84 app->fire(
"frameend");
87 app->_inFrameUpdate =
false;
89 if (app->_destroyRequested) {
102 if (_inFrameUpdate) {
103 _destroyRequested =
true;
131 _elementInput->detach();
132 _elementInput.reset();
146 for (
auto assetList = _assets->list();
auto& asset : assetList) {
179 _lightmapper.reset();
186 _entityIndex.clear();
197 if (_graphicsDevice) {
198 _graphicsDevice.reset();
210 if (!_graphicsDevice) {
211 throw std::runtime_error(
"The application cannot be created without a valid GraphicsDevice");
214 _root = std::make_shared<Entity>();
215 _root->setEngine(
this);
219 _root->setEnabledInHierarchy(
true);
223 initDefaultMaterial();
224 initProgramLibrary();
226 _stats = std::make_shared<ApplicationStats>(_graphicsDevice);
227 _scene = std::make_shared<Scene>(_graphicsDevice);
228 registerSceneImmediate(_scene);
230 _loader = std::make_shared<ResourceLoader>(shared_from_this());
233 _loader->addHandler(
AssetType::FONT, std::make_unique<FontResourceHandler>());
234 _assets = std::make_shared<AssetRegistry>(_loader);
235 _bundles = std::make_shared<BundleRegistry>(_assets);
236 _scenes = std::make_shared<SceneRegistry>(shared_from_this());
237 _scripts = std::make_shared<ScriptRegistry>(shared_from_this());
239 _systems = std::make_shared<ComponentSystemRegistry>();
242 _systems->add(componentSystem(
this));
245 _i18n = std::make_shared<I18n>(shared_from_this());
250 _defaultLayerWorld = std::make_shared<Layer>(
"World", 1);
251 _defaultLayerDepth = std::make_shared<Layer>(
"Depth", 2);
252 _defaultLayerSkybox = std::make_shared<Layer>(
"Skybox", 3);
253 _defaultLayerUi = std::make_shared<Layer>(
"UI", 4);
254 _defaultLayerImmediate = std::make_shared<Layer>(
"Immediate", 5);
257 auto defaultLayerComposition = std::make_shared<LayerComposition>(
"default");
258 defaultLayerComposition->pushOpaque(_defaultLayerWorld);
259 defaultLayerComposition->pushOpaque(_defaultLayerDepth);
260 defaultLayerComposition->pushOpaque(_defaultLayerSkybox);
261 defaultLayerComposition->pushTransparent(_defaultLayerWorld);
262 defaultLayerComposition->pushOpaque(_defaultLayerImmediate);
263 defaultLayerComposition->pushTransparent(_defaultLayerImmediate);
264 defaultLayerComposition->pushTransparent(_defaultLayerUi);
265 _scene->setLayers(defaultLayerComposition);
267 _renderer = std::make_shared<ForwardRenderer>(_graphicsDevice, _scene);
276 _batcher = std::make_shared<BatchManager>(_graphicsDevice.get());
280 _mouse = appOptions.
mouse;
281 _touch = appOptions.
touch;
285 _elementInput->setEngine(shared_from_this());
292 _tick =
makeTick(shared_from_this());
295 void Engine::initDefaultMaterial()
297 auto material = std::make_shared<StandardMaterial>();
298 material->setName(
"Default Material");
302 void Engine::initProgramLibrary()
304 auto library = std::make_shared<ProgramLibrary>(_graphicsDevice,
new StandardMaterial());
316 assert(_graphicsDevice &&
"Engine::render requires a valid graphics device");
317 _frameStartCalled =
false;
318 _renderCompositionCalled =
false;
319 _frameEndCalled =
false;
321 _graphicsDevice->frameStart();
322 _frameStartCalled =
true;
324 _renderCompositionCalled =
true;
327 if (_graphicsDevice->insideRenderPass()) {
328 spdlog::error(
"Frame parity violation: render() reached frameEnd while still inside a render pass");
329 assert(!_graphicsDevice->insideRenderPass() &&
"Unbalanced render pass before frameEnd");
332 _graphicsDevice->frameEnd();
333 _frameEndCalled =
true;
335 if (!(_frameStartCalled && _renderCompositionCalled && _frameEndCalled)) {
336 spdlog::error(
"Frame parity violation: expected frameStart -> render passes -> frameEnd sequence");
337 assert(
false &&
"Invalid frame lifecycle ordering");
344 auto now = std::chrono::high_resolution_clock::now();
345 const double ms = std::chrono::duration<double, std::milli>(now.time_since_epoch()).count();
350 void Engine::renderComposition()
352 if (!_frameStartCalled || _frameEndCalled) {
353 spdlog::error(
"Frame parity violation: renderComposition called outside active frame scope");
354 assert(
false &&
"renderComposition must run after frameStart and before frameEnd");
357 if (!_renderer || !_scene || !_graphicsDevice) {
361 const auto& layerComposition = _scene->layers();
362 if (!layerComposition) {
366 FrameGraph frameGraph;
367 _renderer->buildFrameGraph(&frameGraph, layerComposition.get());
368 frameGraph.render(_graphicsDevice.get());
371 void Engine::registerSceneImmediate(
const std::shared_ptr<Scene>& scene) {
372 if (_scene && _scene->immediate()) {
374 scene->immediate()->onPostRender();
379 void Engine::fillFrameStatsBasic(
double now,
float dt,
float ms)
382 auto& stats = _stats->frame();
385 if (now > stats.timeToCountFrames) {
386 stats.fps = stats.fpsAccum;
388 stats.timeToCountFrames = now + 1000;
393 _stats->drawCalls().total = _graphicsDevice->drawCallsPerFrame();
394 _graphicsDevice->resetDrawCallsPerFrame();
396 stats.gsplats = _renderer->_gsplatCount;
413 if (_xr && _xr->active()) {
418 auto windowSize = _graphicsDevice->size();
421 float r =
static_cast<float>(windowSize.first) / windowSize.second;
422 float winR =
static_cast<float>(windowSize.first) / windowSize.second;
425 width = windowSize.first;
426 height =
static_cast<int>(width / r);
428 height = windowSize.second;
429 width =
static_cast<int>(height * r);
432 width = windowSize.first;
433 height = windowSize.second;
439 return {width, height};
444 _resolutionMode = mode;
448 auto size = _graphicsDevice->size();
450 height = size.second;
453 _graphicsDevice->resizeCanvas(width, height);
456 void Engine::fillFrameStats()
458 auto& stats = _stats->frame();
461 stats.cameras = _renderer->_camerasRendered;
462 stats.materials = _renderer->_materialSwitches;
463 stats.shaders = _graphicsDevice->_shaderSwitchesPerFrame;
464 stats.shadowMapUpdates = _renderer->_shadowMapUpdates;
465 stats.shadowMapTime = _renderer->_shadowMapTime;
466 stats.depthMapTime = _renderer->_depthMapTime;
467 stats.forwardTime = _renderer->_forwardTime;
469 auto& prims = _graphicsDevice->_primsPerFrame;
477 stats.cullTime = _renderer->_cullTime;
478 stats.sortTime = _renderer->_sortTime;
479 stats.skinTime = _renderer->_skinTime;
480 stats.morphTime = _renderer->_morphTime;
481 stats.lightClusters = _renderer->_lightClusters;
482 stats.lightClustersTime = _renderer->_lightClustersTime;
483 stats.otherPrimitives = 0;
485 for (
int i = 0; i < prims.size(); i++) {
487 stats.otherPrimitives += prims[i];
492 _renderer->_camerasRendered = 0;
493 _renderer->_materialSwitches = 0;
494 _renderer->_shadowMapUpdates = 0;
495 _graphicsDevice->_shaderSwitchesPerFrame = 0;
496 _renderer->_cullTime = 0;
497 _renderer->_layerCompositionUpdateTime = 0;
498 _renderer->_lightClustersTime = 0;
499 _renderer->_sortTime = 0;
500 _renderer->_skinTime = 0;
501 _renderer->_morphTime = 0;
502 _renderer->_shadowMapTime = 0;
503 _renderer->_depthMapTime = 0;
504 _renderer->_forwardTime = 0;
507 auto& drawCallstats = _stats->drawCalls();
508 drawCallstats.forward = _renderer->_forwardDrawCalls;
509 drawCallstats.depth = 0;
510 drawCallstats.shadow = _renderer->_shadowDrawCalls;
511 drawCallstats.skinned = _renderer->_skinDrawCalls;
512 drawCallstats.immediate = 0;
513 drawCallstats.instanced = 0;
514 drawCallstats.removedByInstancing = 0;
515 drawCallstats.misc = drawCallstats.total - (drawCallstats.forward + drawCallstats.shadow);
517 _renderer->_shadowDrawCalls = 0;
518 _renderer->_forwardDrawCalls = 0;
519 _renderer->_numDrawCallsCulled = 0;
520 _renderer->_skinDrawCalls = 0;
521 _renderer->_instancedDrawCalls = 0;
523 _stats->misc().renderTargetCreationTime = _graphicsDevice->_renderTargetCreationTime;
525 auto& particleStats = _stats->particles();
526 particleStats.updatesPerFrame = particleStats._updatesPerFrame;
527 particleStats.frameTime = particleStats._frameTime;
528 particleStats._updatesPerFrame = 0;
529 particleStats._frameTime = 0;
535 _controller->update();
553 _stats->frame().fixedUpdateTime = 0.0;
556 _graphicsDevice->update();
562 _loader->processCompletions(1);
565 auto updateStart = std::chrono::high_resolution_clock::now();
566 bool hasScriptSystem =
false;
567 bool scriptUpdateCalled =
false;
568 bool scriptPostUpdateCalled =
false;
570 _systems->fire(_inTools ?
"toolsUpdate" :
"update", dt);
571 _systems->fire(
"animationUpdate", dt);
573 if (
auto* scriptSystemBase = _systems->getByComponentType<
ScriptComponent>()) {
575 hasScriptSystem =
true;
576 scriptSystem->update(dt);
577 scriptUpdateCalled =
true;
581 _systems->fire(
"postUpdate", dt);
583 if (
auto* scriptSystemBase = _systems->getByComponentType<
ScriptComponent>()) {
585 hasScriptSystem =
true;
586 if (!scriptUpdateCalled) {
587 spdlog::error(
"Script lifecycle parity violation: postUpdateScripts called before updateScripts");
588 assert(scriptUpdateCalled &&
"Script postUpdate invoked before script update");
590 scriptSystem->postUpdate(dt);
591 scriptPostUpdateCalled =
true;
595 if (hasScriptSystem && !(scriptUpdateCalled && scriptPostUpdateCalled)) {
596 spdlog::error(
"Script lifecycle parity violation: expected script update + postUpdate each frame");
597 assert(
false &&
"Incomplete script lifecycle in frame update");
604 _batcher->updateAll();
610 _fixedTimeAccumulator += dt;
612 while (_fixedTimeAccumulator >= _fixedDeltaTime
613 && substeps < _maxFixedSubSteps) {
615 _fixedTimeAccumulator -= _fixedDeltaTime;
619 if (_fixedTimeAccumulator > _fixedDeltaTime) {
620 _fixedTimeAccumulator = _fixedDeltaTime;
622 _fixedTimeAlpha = _fixedDeltaTime > 0.0f
623 ?
static_cast<float>(_fixedTimeAccumulator / _fixedDeltaTime)
628 auto updateEnd = std::chrono::high_resolution_clock::now();
629 auto updateTime = std::chrono::duration<float, std::milli>(updateEnd - updateStart).count();
632 _stats->frame().updateTime = updateTime;
638 auto fixedStart = std::chrono::high_resolution_clock::now();
640 _systems->fire(
"fixedUpdate", fixedDt);
642 if (
auto* scriptSystemBase = _systems->getByComponentType<
ScriptComponent>()) {
644 scriptSystem->fixedUpdate(fixedDt);
648 fire(
"fixedUpdate", fixedDt);
650 auto fixedEnd = std::chrono::high_resolution_clock::now();
652 _stats->frame().fixedUpdateTime +=
653 std::chrono::duration<double, std::milli>(fixedEnd - fixedStart).count();
660 if ((!_allowResize) || (_xr !=
nullptr && _xr->active())) {
667 SDL_GetWindowSizeInPixels(_window, &w, &h);
668 _graphicsDevice->resizeCanvas(w, h);
static void setDefaultGraphicsDevice(const std::shared_ptr< GraphicsDevice > &graphicsDevice)
void setCanvasResolution(ResolutionMode mode, int width=0, int height=0)
std::pair< int, int > resizeCanvas(int width=0, int height=0)
friend MakeTickCallback makeTick(const std::shared_ptr< Engine > &engine)
void init(const AppOptions &appOptions)
void setCanvasFillMode(FillMode mode, int width=0, int height=0)
const std::shared_ptr< Scene > & scene() const
void fixedUpdate(float fixedDt)
void inputUpdate(float dt)
EventHandler * fire(const std::string &name, Args &&... args)
EventHandle * on(const std::string &name, HandleEventCallback callback, void *scope=nullptr)
constexpr const char * CONTAINER
constexpr const char * FONT
constexpr const char * TEXTURE
void setProgramLibrary(const std::shared_ptr< GraphicsDevice > &device, const std::shared_ptr< ProgramLibrary > &library)
std::function< void(double, void *)> MakeTickCallback
MakeTickCallback makeTick(const std::shared_ptr< Engine > &app)
void setDefaultMaterial(const std::shared_ptr< GraphicsDevice > &device, const std::shared_ptr< Material > &material)
std::vector< ComponentSystemCreator > componentSystems
std::shared_ptr< ElementInput > elementInput
std::shared_ptr< XrManager > xr
std::shared_ptr< Mouse > mouse
std::shared_ptr< BatchManager > batchManager
std::vector< std::string > scriptsOrder
std::shared_ptr< GamePads > gamepads
std::shared_ptr< GraphicsDevice > graphicsDevice
std::shared_ptr< Keyboard > keyboard
std::shared_ptr< TouchDevice > touch
std::shared_ptr< Lightmapper > lightmapper