From dfc8c1fac2d7836e632da60dba14815e83a24412 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 20 Nov 2025 10:39:16 +0100 Subject: [PATCH 1/6] Fix GPU BVH shader compilation for macOS/iOS --- Content/Shaders/SDF.flax | 4 +- Source/Shaders/MeshAccelerationStructure.hlsl | 37 +++++++++---------- Source/Shaders/SDF.shader | 10 +---- 3 files changed, 21 insertions(+), 30 deletions(-) diff --git a/Content/Shaders/SDF.flax b/Content/Shaders/SDF.flax index 5141c14dd..709cc20f2 100644 --- a/Content/Shaders/SDF.flax +++ b/Content/Shaders/SDF.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2b1dc1523cb2140db7ce5fed6e97b09d7fcebbe6cc19fca7708b5b882267040 -size 4175 +oid sha256:d3922811f0eb56cbb515c93cd53d80316740ea78219aa81118d2c9dee4a9d230 +size 4142 diff --git a/Source/Shaders/MeshAccelerationStructure.hlsl b/Source/Shaders/MeshAccelerationStructure.hlsl index d98d25404..0559e5f55 100644 --- a/Source/Shaders/MeshAccelerationStructure.hlsl +++ b/Source/Shaders/MeshAccelerationStructure.hlsl @@ -16,13 +16,10 @@ struct BVHNode int Count; // Negative for non-leaf nodes }; -struct BVHBuffers -{ - StructuredBuffer BVHBuffer; - ByteAddressBuffer VertexBuffer; - ByteAddressBuffer IndexBuffer; - uint VertexStride; -}; +// Pass all data via separate params (SPIR-V doesn't support buffers in structures) +#define BVHBuffers_Param StructuredBuffer BVHBuffer, ByteAddressBuffer VertexBuffer, ByteAddressBuffer IndexBuffer, uint VertexStride +#define BVHBuffers_Init(BVHBuffer, VertexBuffer, IndexBuffer, VertexStride) BVHBuffer, VertexBuffer, IndexBuffer, VertexStride +#define BVHBuffers_Pass BVHBuffers_Init(BVHBuffer, VertexBuffer, IndexBuffer, VertexStride) struct BVHHit { @@ -30,11 +27,11 @@ struct BVHHit bool IsBackface; }; -float3 LoadVertexBVH(BVHBuffers bvh, uint index) +float3 LoadVertexBVH(BVHBuffers_Param, uint index) { int addr = index << 2u; - uint vertexIndex = bvh.IndexBuffer.Load(addr); - return asfloat(bvh.VertexBuffer.Load3(vertexIndex * bvh.VertexStride)); + uint vertexIndex = IndexBuffer.Load(addr); + return asfloat(VertexBuffer.Load3(vertexIndex * VertexStride)); } // [https://tavianator.com/2011/ray_box.html] @@ -52,7 +49,7 @@ float RayTestBoxBVH(float3 rayPos, float3 rayDir, float3 boxMin, float3 boxMax) } // Performs raytracing against the BVH acceleration structure to find the closest intersection with a triangle. -bool RayCastBVH(BVHBuffers bvh, float3 rayPos, float3 rayDir, out BVHHit hit, float maxDistance = 1000000.0f) +bool RayCastBVH(BVHBuffers_Param, float3 rayPos, float3 rayDir, out BVHHit hit, float maxDistance = 1000000.0f) { hit = (BVHHit)0; hit.Distance = maxDistance; @@ -66,7 +63,7 @@ bool RayCastBVH(BVHBuffers bvh, float3 rayPos, float3 rayDir, out BVHHit hit, fl LOOP while (stackCount > 0) { - BVHNode node = bvh.BVHBuffer[stack[--stackCount]]; + BVHNode node = BVHBuffer[stack[--stackCount]]; // Raytrace bounds float boundsHit = RayTestBoxBVH(rayPos, rayDir, node.BoundsMin, node.BoundsMax); @@ -82,9 +79,9 @@ bool RayCastBVH(BVHBuffers bvh, float3 rayPos, float3 rayDir, out BVHHit hit, fl for (uint i = indexStart; i < indexEnd;) { // Load triangle - float3 v0 = LoadVertexBVH(bvh, i++); - float3 v1 = LoadVertexBVH(bvh, i++); - float3 v2 = LoadVertexBVH(bvh, i++); + float3 v0 = LoadVertexBVH(BVHBuffers_Pass, i++); + float3 v1 = LoadVertexBVH(BVHBuffers_Pass, i++); + float3 v2 = LoadVertexBVH(BVHBuffers_Pass, i++); // Raytrace triangle float distance; @@ -109,7 +106,7 @@ bool RayCastBVH(BVHBuffers bvh, float3 rayPos, float3 rayDir, out BVHHit hit, fl } // Performs a query against the BVH acceleration structure to find the closest distance to a triangle from a given point. -bool PointQueryBVH(BVHBuffers bvh, float3 pos, out BVHHit hit, float maxDistance = 1000000.0f) +bool PointQueryBVH(BVHBuffers_Param, float3 pos, out BVHHit hit, float maxDistance = 1000000.0f) { hit = (BVHHit)0; hit.Distance = maxDistance; @@ -123,7 +120,7 @@ bool PointQueryBVH(BVHBuffers bvh, float3 pos, out BVHHit hit, float maxDistance LOOP while (stackCount > 0) { - BVHNode node = bvh.BVHBuffer[stack[--stackCount]]; + BVHNode node = BVHBuffer[stack[--stackCount]]; // Skip too far nodes if (PointDistanceBox(node.BoundsMin, node.BoundsMax, pos) >= hit.Distance) @@ -138,9 +135,9 @@ bool PointQueryBVH(BVHBuffers bvh, float3 pos, out BVHHit hit, float maxDistance for (uint i = indexStart; i < indexEnd;) { // Load triangle - float3 v0 = LoadVertexBVH(bvh, i++); - float3 v1 = LoadVertexBVH(bvh, i++); - float3 v2 = LoadVertexBVH(bvh, i++); + float3 v0 = LoadVertexBVH(BVHBuffers_Pass, i++); + float3 v1 = LoadVertexBVH(BVHBuffers_Pass, i++); + float3 v2 = LoadVertexBVH(BVHBuffers_Pass, i++); // Check triangle float distance = sqrt(DistancePointToTriangle2(pos, v0, v1, v2)); diff --git a/Source/Shaders/SDF.shader b/Source/Shaders/SDF.shader index b2ae928f7..77e27d8f9 100644 --- a/Source/Shaders/SDF.shader +++ b/Source/Shaders/SDF.shader @@ -77,15 +77,9 @@ void CS_RasterizeTriangles(uint3 GroupId : SV_GroupID, uint3 GroupThreadID : SV_ int3 voxelCoord = GetVoxelCoord(voxelIndex); float3 voxelPos = GetVoxelPos(voxelCoord); - BVHBuffers bvh; - bvh.BVHBuffer = BVHBuffer; - bvh.VertexBuffer = VertexBuffer; - bvh.IndexBuffer = IndexBuffer; - bvh.VertexStride = VertexStride; - // Point query to find the distance to the closest surface BVHHit hit; - PointQueryBVH(bvh, voxelPos, hit, MaxDistance); + PointQueryBVH(BVHBuffers_Init(BVHBuffer, VertexBuffer, IndexBuffer, VertexStride), voxelPos, hit, MaxDistance); float sdf = hit.Distance; // Raycast triangles around voxel to count triangle backfaces hit @@ -104,7 +98,7 @@ void CS_RasterizeTriangles(uint3 GroupId : SV_GroupID, uint3 GroupThreadID : SV_ for (uint i = 0; i < CLOSEST_CACHE_SIZE; i++) { float3 rayDir = closestDirections[i]; - if (RayCastBVH(bvh, voxelPos, rayDir, hit, MaxDistance)) + if (RayCastBVH(BVHBuffers_Init(BVHBuffer, VertexBuffer, IndexBuffer, VertexStride), voxelPos, rayDir, hit, MaxDistance)) { sdf = min(sdf, hit.Distance); if (hit.IsBackface) From e46b6d5b06268f5204763fda1927325cf30e6770 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 20 Nov 2025 10:44:47 +0100 Subject: [PATCH 2/6] Restore Global Surface Atlas and DDGI on Apple platforms #3797 --- Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index 1c1b9e4e3..ce0ec1881 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -551,9 +551,6 @@ bool GlobalSurfaceAtlasPass::Init() // Check platform support const auto device = GPUDevice::Instance; _supported = device->GetFeatureLevel() >= FeatureLevel::SM5 && device->Limits.HasCompute && device->Limits.HasTypedUAVLoad; -#if PLATFORM_APPLE_FAMILY - _supported = false; // Vulkan over Metal has some issues in complex scenes with DDGI -#endif return false; } From a8db0086b193904a3760e66647ff2b7d43cb4217 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 20 Nov 2025 13:07:28 +0100 Subject: [PATCH 3/6] Fix shader compilation without HLSL 2021 on Vulkan --- Source/Engine/Renderer/DepthOfFieldPass.h | 9 ++++--- Source/Shaders/Common.hlsl | 31 +++-------------------- 2 files changed, 9 insertions(+), 31 deletions(-) diff --git a/Source/Engine/Renderer/DepthOfFieldPass.h b/Source/Engine/Renderer/DepthOfFieldPass.h index b7351c4b1..098c4061e 100644 --- a/Source/Engine/Renderer/DepthOfFieldPass.h +++ b/Source/Engine/Renderer/DepthOfFieldPass.h @@ -52,9 +52,12 @@ private: void OnShaderReloading(Asset* obj) { _psDofDepthBlurGeneration->ReleaseGPU(); - _psBokehGeneration->ReleaseGPU(); - _psBokeh->ReleaseGPU(); - _psBokehComposite->ReleaseGPU(); + if (_psBokehGeneration) + _psBokehGeneration->ReleaseGPU(); + if (_psBokeh) + _psBokeh->ReleaseGPU(); + if (_psBokehComposite) + _psBokehComposite->ReleaseGPU(); invalidateResources(); } #endif diff --git a/Source/Shaders/Common.hlsl b/Source/Shaders/Common.hlsl index 10b2855f4..66303546a 100644 --- a/Source/Shaders/Common.hlsl +++ b/Source/Shaders/Common.hlsl @@ -96,34 +96,9 @@ // Compiler support for HLSL 2021 that is stricter (need to use or/and/select for vector-based logical operators) #if !defined(__DXC_VERSION_MAJOR) || (__DXC_VERSION_MAJOR <= 1 && __DXC_VERSION_MINOR < 7) -bool InternalAnd(bool a, bool b) { return bool(a && b); } -bool2 InternalAnd(bool2 a, bool2 b) { return bool2(a.x && b.x, a.y && b.y); } -bool3 InternalAnd(bool3 a, bool3 b) { return bool3(a.x && b.x, a.y && b.y, a.z && b.z); } -bool4 InternalAnd(bool4 a, bool4 b) { return bool4(a.x && b.x, a.y && b.y, a.z && b.z, a.w && b.w); } - -bool InternalOr(bool a, bool b) { return bool(a || b); } -bool2 InternalOr(bool2 a, bool2 b) { return bool2(a.x || b.x, a.y || b.y); } -bool3 InternalOr(bool3 a, bool3 b) { return bool3(a.x || b.x, a.y || b.y, a.z || b.z); } -bool4 InternalOr(bool4 a, bool4 b) { return bool4(a.x || b.x, a.y || b.y, a.z || b.z, a.w || b.w); } - -#define SELECT_INTERNAL(type) \ - type InternalSelect(bool c, type a, type b) { return type (c ? a.x : b.x); } \ - type##2 InternalSelect(bool c, type##2 a, type##2 b) { return type##2(c ? a.x : b.x, c ? a.y : b.y); } \ - type##2 InternalSelect(bool2 c, type a, type b) { return type##2(c.x ? a : b, c.y ? a : b); } \ - type##2 InternalSelect(bool2 c, type##2 a, type##2 b) { return type##2(c.x ? a.x : b.x, c.y ? a.y : b.y); } \ - type##3 InternalSelect(bool c, type##3 a, type##3 b) { return type##3(c ? a.x : b.x, c ? a.y : b.y, c ? a.z : b.z); } \ - type##3 InternalSelect(bool3 c, type a, type b) { return type##3(c.x ? a : b, c.y ? a : b, c.z ? a : b); } \ - type##3 InternalSelect(bool3 c, type##3 a, type##3 b) { return type##3(c.x ? a.x : b.x, c.y ? a.y : b.y, c.z ? a.z : b.z); } \ - type##4 InternalSelect(bool c, type##4 a, type##4 b) { return type##4(c ? a.x : b.x, c ? a.y : b.y, c ? a.z : b.z, c ? a.w : b.w); } \ - type##4 InternalSelect(bool4 c, type a, type b) { return type##4(c.x ? a : b, c.y ? a : b, c.z ? a : b, c.w ? a : b); } \ - type##4 InternalSelect(bool4 c, type##4 a, type##4 b) { return type##4(c.x ? a.x : b.x, c.y ? a.y : b.y, c.z ? a.z : b.z, c.w ? a.w : b.w); } -SELECT_INTERNAL(uint) -SELECT_INTERNAL(float) -#undef SELECT_INTERNAL - -#define and(a, b) InternalAnd(a, b) -#define or(a, b) InternalOr(a, b) -#define select(c, a, b) InternalSelect(c, a, b) +#define and(a, b) (a) && (b) +#define or(a, b) (a) || (b) +#define select(c, a, b) (c) ? (a) : (b) #endif From dacb3b089158fa8b70d7eb869c05f88e927c8002 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 25 Nov 2025 10:30:53 +0100 Subject: [PATCH 4/6] Fix rendering on Intel-based macOS to use integrated GPU primarly --- Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index e96f7b3b3..bc33d9d86 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -1187,6 +1187,10 @@ GPUDevice* GPUDeviceVulkan::Create() return nullptr; } uint32 vendorId = 0; +#if PLATFORM_MAC && PLATFORM_ARCH_X64 + // Intel-based macs have artifacts on MoltenVK when using dedicated AMD GPU so fallback to integrated Intel GPU + vendorId = GPU_VENDOR_ID_INTEL; +#endif if (CommandLine::Options.NVIDIA.IsTrue()) vendorId = GPU_VENDOR_ID_NVIDIA; else if (CommandLine::Options.AMD.IsTrue()) From b07d74d28d4d34513a95b9afa5e340eff268e4c6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 25 Nov 2025 10:39:15 +0100 Subject: [PATCH 5/6] Add option to skip loading Model SDFs in game if disabled in Graphics Settings --- Source/Engine/Content/Assets/Model.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 66ca0aa2e..d7172e091 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -71,13 +71,23 @@ public: REGISTER_BINARY_ASSET_WITH_UPGRADER(Model, "FlaxEngine.Model", ModelAssetUpgrader, true); static byte EnableModelSDF = 0; +#if !USE_EDITOR +#include "Engine/Core/Config/GraphicsSettings.h" +#endif Model::Model(const SpawnParams& params, const AssetInfo* info) : ModelBase(params, info, StreamingGroups::Instance()->Models()) { if (EnableModelSDF == 0 && GPUDevice::Instance) { - const bool enable = GPUDevice::Instance->GetFeatureLevel() >= FeatureLevel::SM5; + bool enable = GPUDevice::Instance->GetFeatureLevel() >= FeatureLevel::SM5; +#if !USE_EDITOR + enable &= GraphicsSettings::Get()->EnableGlobalSDF; +#if !BUILD_RELEASE + if (!enable) + LOG(Info, "Not using Model SDFs"); +#endif +#endif EnableModelSDF = enable ? 1 : 2; } } @@ -684,7 +694,10 @@ void Model::unload(bool isReloading) AssetChunksFlag Model::getChunksToPreload() const { // Note: we don't preload any LODs here because it's done by the Streaming Manager - return GET_CHUNK_FLAG(0) | GET_CHUNK_FLAG(15); + AssetChunksFlag result = GET_CHUNK_FLAG(0); + if (EnableModelSDF == 1) + result |= GET_CHUNK_FLAG(15); + return result; } bool ModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, Mesh** mesh) From 394860656ae809899ddecd7334f9fa3b277a1c03 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 27 Nov 2025 10:55:02 +0100 Subject: [PATCH 6/6] Add FPS limit and pause option when game is unfocused --- Source/Engine/Core/Config/TimeSettings.h | 12 ++++ Source/Engine/Engine/Engine.cpp | 21 +------ Source/Engine/Engine/Time.cpp | 55 ++++++++++++++++--- .../Platform/Linux/LinuxPlatformSettings.h | 2 + .../Engine/Platform/Mac/MacPlatformSettings.h | 2 + .../Windows/WindowsPlatformSettings.h | 2 + 6 files changed, 65 insertions(+), 29 deletions(-) diff --git a/Source/Engine/Core/Config/TimeSettings.h b/Source/Engine/Core/Config/TimeSettings.h index 991f505ab..4e308a32e 100644 --- a/Source/Engine/Core/Config/TimeSettings.h +++ b/Source/Engine/Core/Config/TimeSettings.h @@ -43,6 +43,18 @@ public: API_FIELD(Attributes="EditorOrder(20), Limit(0.1f, 1000.0f, 0.01f), EditorDisplay(\"General\")") float MaxUpdateDeltaTime = 0.1f; + /// + /// Limits maximum game framerate when application window loses focus. Use 0 to disable this feature. + /// + API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Unfocused\")") + float UnfocusedMaxFPS = 30; + + /// + /// Enables pausing game when application window loses focus. + /// + API_FIELD(Attributes="EditorOrder(110), EditorDisplay(\"Unfocused\")") + bool UnfocusedPause = false; + public: /// /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 5e8224b4e..c0f1e06aa 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -40,15 +40,11 @@ #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MMethod.h" #include "Engine/Scripting/ManagedCLR/MException.h" -#include "Engine/Core/Config/PlatformSettings.h" #endif namespace EngineImpl { bool IsReady = false; -#if !USE_EDITOR - bool RunInBackground = false; -#endif String CommandLine = nullptr; int32 Fps = 0, FpsAccumulatedFrames = 0; double FpsAccumulated = 0.0; @@ -168,9 +164,6 @@ int32 Engine::Main(const Char* cmdLine) Platform::BeforeRun(); EngineImpl::InitMainWindow(); Application::BeforeRun(); -#if !USE_EDITOR && (PLATFORM_WINDOWS || PLATFORM_LINUX || PLATFORM_MAC) - EngineImpl::RunInBackground = PlatformSettings::Get()->RunInBackground; -#endif LOG_FLOOR(); LOG_FLUSH(); Time::Synchronize(); @@ -353,20 +346,8 @@ void Engine::OnUpdate() UpdateCount++; - const auto mainWindow = MainWindow; - -#if !USE_EDITOR - // Pause game if window lost focus and cannot run in a background - bool isGameRunning = true; - if (mainWindow && !mainWindow->IsFocused()) - { - isGameRunning = EngineImpl::RunInBackground; - } - Time::SetGamePaused(!isGameRunning); -#endif - // Determine if application has focus (flag used by the other parts of the engine) - HasFocus = (mainWindow && mainWindow->IsFocused()) || Platform::GetHasFocus(); + HasFocus = (MainWindow && MainWindow->IsFocused()) || Platform::GetHasFocus(); // Simulate lags //Platform::Sleep(100); diff --git a/Source/Engine/Engine/Time.cpp b/Source/Engine/Engine/Time.cpp index e55e76971..7d268cf73 100644 --- a/Source/Engine/Engine/Time.cpp +++ b/Source/Engine/Engine/Time.cpp @@ -6,12 +6,29 @@ #include "Engine/Platform/Platform.h" #include "Engine/Core/Config/TimeSettings.h" #include "Engine/Serialization/Serialization.h" +#if !USE_EDITOR +#include "Engine/Engine/Engine.h" +#endif namespace { bool FixedDeltaTimeEnable; float FixedDeltaTimeValue; float MaxUpdateDeltaTime = 0.1f; +#if USE_EDITOR + constexpr bool _gamePausedUnfocsed = false; + #define GetFps(fps) fps +#else + bool _gamePausedUnfocsed = false; + float UnfocusedMaxFPS = 0.0f; + bool UnfocusedPause = false; + float GetFps(float fps) + { + if (UnfocusedMaxFPS > 0 && !Engine::HasFocus && fps > UnfocusedMaxFPS) + fps = UnfocusedMaxFPS; + return fps; + } +#endif } bool Time::_gamePaused = false; @@ -52,15 +69,16 @@ void TimeSettings::Apply() Time::DrawFPS = DrawFPS; Time::TimeScale = TimeScale; ::MaxUpdateDeltaTime = MaxUpdateDeltaTime; +#if !USE_EDITOR + ::UnfocusedMaxFPS = UnfocusedMaxFPS; + ::UnfocusedPause = UnfocusedPause; +#endif } void Time::TickData::Synchronize(float targetFps, double currentTime) { + OnReset(targetFps, currentTime); Time = UnscaledTime = TimeSpan::Zero(); - DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero(); - LastLength = static_cast(DeltaTime.Ticks) / TimeSpan::TicksPerSecond; - LastBegin = currentTime - LastLength; - LastEnd = currentTime; NextBegin = targetFps > ZeroTolerance ? LastBegin + (1.0f / targetFps) : 0.0; } @@ -115,7 +133,7 @@ void Time::TickData::OnTickEnd() void Time::TickData::Advance(double time, double deltaTime) { float timeScale = TimeScale; - if (_gamePaused) + if (_gamePaused || _gamePausedUnfocsed) timeScale = 0.0f; LastBegin = time; UnscaledDeltaTime = TimeSpan::FromSeconds(deltaTime); @@ -194,9 +212,11 @@ void Time::SetGamePaused(bool value) { if (_gamePaused == value) return; - _gamePaused = value; + if (_gamePausedUnfocsed) + return; + // Reset ticking const double time = Platform::GetTimeSeconds(); Update.OnReset(UpdateFPS, time); Physics.OnReset(PhysicsFPS, time); @@ -249,7 +269,24 @@ void Time::Synchronize() bool Time::OnBeginUpdate(double time) { - if (Update.OnTickBegin(time, UpdateFPS, MaxUpdateDeltaTime)) +#if !USE_EDITOR + // Pause game if window lost focus (based on game settings) + bool gamePausedUnfocsed = !Engine::HasFocus && UnfocusedPause; + if (gamePausedUnfocsed != _gamePausedUnfocsed) + { + _gamePausedUnfocsed = gamePausedUnfocsed; + if (!gamePausedUnfocsed) + { + // Reset ticking + const double time = Platform::GetTimeSeconds(); + Update.OnReset(UpdateFPS, time); + Physics.OnReset(PhysicsFPS, time); + Draw.OnReset(DrawFPS, time); + } + } +#endif + + if (Update.OnTickBegin(time, GetFps(UpdateFPS), MaxUpdateDeltaTime)) { Current = &Update; return true; @@ -259,7 +296,7 @@ bool Time::OnBeginUpdate(double time) bool Time::OnBeginPhysics(double time) { - if (Physics.OnTickBegin(time, PhysicsFPS, _physicsMaxDeltaTime)) + if (Physics.OnTickBegin(time, GetFps(PhysicsFPS), _physicsMaxDeltaTime)) { Current = &Physics; return true; @@ -269,7 +306,7 @@ bool Time::OnBeginPhysics(double time) bool Time::OnBeginDraw(double time) { - if (Draw.OnTickBegin(time, DrawFPS, 1.0f)) + if (Draw.OnTickBegin(time, GetFps(DrawFPS), 1.0f)) { Current = &Draw; return true; diff --git a/Source/Engine/Platform/Linux/LinuxPlatformSettings.h b/Source/Engine/Platform/Linux/LinuxPlatformSettings.h index 6e9200cc6..dd63c0d78 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatformSettings.h +++ b/Source/Engine/Platform/Linux/LinuxPlatformSettings.h @@ -43,8 +43,10 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API /// /// Enables game running when application window loses focus. + /// [Deprecated in v1.12] /// API_FIELD(Attributes="EditorOrder(1010), DefaultValue(false), EditorDisplay(\"Other\", \"Run In Background\")") + DEPRECATED("Use UnfocusedPause from TimeSettings.") bool RunInBackground = false; /// diff --git a/Source/Engine/Platform/Mac/MacPlatformSettings.h b/Source/Engine/Platform/Mac/MacPlatformSettings.h index f78b12167..9f6807fba 100644 --- a/Source/Engine/Platform/Mac/MacPlatformSettings.h +++ b/Source/Engine/Platform/Mac/MacPlatformSettings.h @@ -40,8 +40,10 @@ API_CLASS(Sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API /// /// Enables game running when application window loses focus. + /// [Deprecated in v1.12] /// API_FIELD(Attributes="EditorOrder(1010), EditorDisplay(\"Other\", \"Run In Background\")") + DEPRECATED("Use UnfocusedPause from TimeSettings.") bool RunInBackground = false; /// diff --git a/Source/Engine/Platform/Windows/WindowsPlatformSettings.h b/Source/Engine/Platform/Windows/WindowsPlatformSettings.h index 9745b61c1..40e6bef9e 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatformSettings.h +++ b/Source/Engine/Platform/Windows/WindowsPlatformSettings.h @@ -43,8 +43,10 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API /// /// Enables game running when application window loses focus. + /// [Deprecated in v1.12] /// API_FIELD(Attributes="EditorOrder(1010), DefaultValue(false), EditorDisplay(\"Other\", \"Run In Background\")") + DEPRECATED("Use UnfocusedPause from TimeSettings.") bool RunInBackground = false; ///