Search Unity

Game Jittering when less than 60fps even at 59

Discussion in 'Entity Component System' started by Opeth001, Dec 16, 2019.

  1. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,117
    Hello Everyone,

    i saw in unity documentation and on the internet that it's recommended to optimize the game for 60 fps and set target fps to 30 on low end devices.

    our game is stable in 60fps but when i put the fps to 30,40 even in 50 the visuals become Jittering.

    my first thought:
    it was a problem from the CameraFollowSystem or AnimatorSystem. so i disabled them in a way that only the character still moving but the game still Jittering

    my second thought:
    im using ECS and im setting the SimulationSystemGroup to run in fixedUpdate like Physics, PlayersMovementSystem, TransformSystemGroup... knowing that all these systems are running in fixedUpdate they should not be affected by the TargetFrameRate, then why the game is running flawlessly at 60 fps.

    Any suggestion is highly appreciated!!
     
  2. RuanJacobz

    RuanJacobz

    Joined:
    Jan 24, 2014
    Posts:
    59
    Have you tried doing a build?
    Sometimes playmode in the editor is hitchy even though everything would be running smoothly.
    This seems to be especially common with camera movement.

     
    Opeth001 likes this.
  3. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,117
    Yes I tried in 4 different devices and still laggy.
    I found where the problem is coming from.
    The character is moving in the fixedUpdate and with the default unity settings it’s running 52/s. Which means the characters is moving more than the screen is rendered.
    So I tried changing the FixedUpdate rate to 30/s but now it’s more laggy because the FixedUpdate is less frequent now and not stable at all. For a regular GO oriented project this can be fixed by simply setting the regidbody component to interpolate but in my case I’m using ECS so I’ll try to create the interpolation by my self, I think I’ll modify the TransformSystemGroup to interpolate movements.
     
    Last edited: Dec 17, 2019
  4. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    do you see any jitter if everything runs on Update() instead of FixedUpdate()?

    When things run on FixedUpdate, all it takes is a camera or an object that does any sort of movement on Update() and you'll see jitter
     
    axxessdenied likes this.
  5. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,117
    when everything is running in Update obviously it's not as smooth as in 60 fps but it's really acceptable as result.

    the problem im having now is how can i run the SimulationSystemGroup in the fixedUpdate and put the TransformSystemGroup in an UpdateLoop in the Entities 0.1 package
     
    Last edited: Dec 17, 2019
  6. Deleted User

    Deleted User

    Guest

  7. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    966
    The human brain is exceptional at picking up oddities in linear movement even at high frames.
    The article @wobes posted goes over it in detail. If you want smooth/linear movement you have to decouple the simulation from presentation and interpolate. No other way around it unless everything runs at stable framerate and in sequence which doesn't happen if you mix fixed and normal update.
     
    Opeth001 and Deleted User like this.
  8. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,117
    i discovred this website yesterday and it's really interesting ^_^
     
  9. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,117
    Hello guys,
    im not understanding why even the Update loop is unstable.

    i created a sample project to test and fix the Jittering problem, but i came out with a new and bigger problem. even a simple script rotating a gameobject in the Update Loop is Jittering.

    Code (CSharp):
    1.  
    2. public class Rotator : MonoBehaviour
    3. {
    4.  
    5.     public Transform toRotate;
    6.     public int rotateSpeed;
    7.     private void Update()
    8.     {
    9.         toRotate.Rotate(0, Time.deltaTime * rotateSpeed, 0, Space.World);
    10.     }
    11. }




    Im really lost now XD.
    any explanation is really appreciated!
    Thank you!
     
  10. patrickreece

    patrickreece

    Joined:
    Nov 14, 2019
    Posts:
    23
  11. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,117
  12. Deleted User

    Deleted User

    Guest

    What is your Time setting for physics? It is somewhere in Physics or Time settings. Default value is 0.02 which is 50fps
     
  13. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,117
    i set the FixedUpdate to 20/s ( 0.05s )

    [Edit] More Details:
    i added a screenshot to show how my systems are ordred.


    1) I set inputs to LocalPlayer
    2) i move players using their inputs and PhysicsVelocity CD in the FixedUpdate SimulationSystemGroup.
    3) UpdateLinkedEntityInterpolatorSystem Stores the transforms for an object after the two most recent fixed steps.
    Code:
    Code (CSharp):
    1. [UpdateInGroup(typeof(LateSimulationSystemGroup))]
    2.     public class UpdateLinkedEntityInterpolatorSystem : JobComponentSystem
    3.     {
    4.         [BurstCompile]
    5.         struct updateLinkedEntityInterpolatorJob : IJobForEach<LinkedEntityInterpolator, LinkedPrefabParent>
    6.         {
    7.             [ReadOnly] public ComponentDataFromEntity<Translation> TranslationFromEntity;
    8.             [ReadOnly] public ComponentDataFromEntity<Rotation> RotationFromEntity;
    9.            
    10.  
    11.             public void Execute(ref LinkedEntityInterpolator linkedEntityInterpolator, [ReadOnly] ref LinkedPrefabParent linkedPrefabParent)
    12.             {
    13.                 linkedEntityInterpolator.UpdateTransforms(TranslationFromEntity[linkedPrefabParent.Parent].Value, RotationFromEntity[linkedPrefabParent.Parent].Value);
    14.             }
    15.         }
    16.        
    17.  
    18.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    19.         {
    20.             var updateLinkedEntityInterpolatorJob = new updateLinkedEntityInterpolatorJob
    21.             {
    22.                 RotationFromEntity = GetComponentDataFromEntity<Rotation>(true),
    23.                 TranslationFromEntity = GetComponentDataFromEntity<Translation>(true),
    24.             };
    25.             return updateLinkedEntityInterpolatorJob.ScheduleSingle(this, inputDeps);
    26.         }
    27.  
    28.  
    29.     }
    4) InterpolateTransformToGameObjectSystem Interpolate between LinkedEntityInterpolator Component Values. (Latest 2 Transforms)

    Code (CSharp):
    1.  [UnityEngine.ExecuteAlways]
    2.     [UpdateInGroup(typeof(TransformSystemGroup))]
    3.     [UpdateAfter(typeof(EndFrameLocalToParentSystem))]
    4.     [UpdateBefore(typeof(CopyTransformToGameObjectSystem))]
    5.  
    6.     public class InterpolateTransformToGameObjectSystem : JobComponentSystem
    7.     {
    8.        
    9.         EntityQuery m_InterpolatedTransformGroup;
    10.  
    11.         protected override void OnCreate()
    12.         {
    13.             m_InterpolatedTransformGroup = GetEntityQuery(
    14.                 ComponentType.ReadOnly<LinkedEntityInterpolator>(),
    15.                 ComponentType.ReadOnly<LocalToWorld>(),
    16.                 typeof(UnityEngine.Transform));
    17.         }
    18.        
    19.         [BurstCompile]
    20.         struct InterpolateTransforms : IJobParallelForTransform
    21.         {
    22.             [DeallocateOnJobCompletion]
    23.             [ReadOnly] public NativeArray<Entity> Entities;
    24.             [ReadOnly] public float InterpolationFactor;
    25.  
    26.             [NativeDisableParallelForRestriction]
    27.             [ReadOnly] public ComponentDataFromEntity<LinkedEntityInterpolator> TransformInterpolatorFromEntity;
    28.  
    29.  
    30.             [NativeDisableParallelForRestriction]
    31.             public ComponentDataFromEntity<LocalToWorld> LocalToWorldFromEntity;
    32.  
    33.             public void Execute(int index, TransformAccess transform)
    34.             {
    35.                 var entity = Entities[index];
    36.  
    37.                 var transformInterpolator = TransformInterpolatorFromEntity[entity];
    38.                  
    39.                 transform.position = math.lerp(
    40.                                     transformInterpolator.OldPosition,
    41.                                     transformInterpolator.NewPosition,
    42.                                     InterpolationFactor);
    43.                 /*
    44.                 transform.localScale = math.Lerp(
    45.                                            
    46.                                             InterpolationController.InterpolationFactor);*/
    47.              
    48.                 transform.rotation = math.slerp(
    49.                                             transformInterpolator.OldRotation,
    50.                                             transformInterpolator.NewRotation,
    51.                                             InterpolationFactor);
    52.                
    53.  
    54.                 if (transformInterpolator.UpdateLocalToWorld)
    55.                 {
    56.                     var localToWorld = LocalToWorldFromEntity[entity];
    57.                     localToWorld.Value = float4x4.TRS(
    58.                         transform.position,
    59.                         transform.rotation,
    60.                         transform.localScale);
    61.                     LocalToWorldFromEntity[entity] = localToWorld;
    62.                 }
    63.             }
    64.         }
    65.  
    66.      
    67.  
    68.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    69.         {
    70.             if (m_InterpolatedTransformGroup.CalculateEntityCount() > 0)
    71.             {
    72.  
    73.                 //Debug.Log($"InterpolationFactor: {InterpolationController.InterpolationFactor}");
    74.  
    75.                 var interPolatedTransforms = m_InterpolatedTransformGroup.GetTransformAccessArray();
    76.                 var interpolateTransformsJob = new InterpolateTransforms
    77.                 {
    78.                     Entities = m_InterpolatedTransformGroup.ToEntityArray(Allocator.TempJob, out inputDeps),
    79.                     TransformInterpolatorFromEntity = GetComponentDataFromEntity<LinkedEntityInterpolator>(true),
    80.                     LocalToWorldFromEntity = GetComponentDataFromEntity<LocalToWorld>(false),
    81.                     InterpolationFactor = InterpolationController.InterpolationFactor,
    82.                 };
    83.  
    84.  
    85.                 return interpolateTransformsJob.Schedule(interPolatedTransforms, inputDeps);
    86.             }
    87.  
    88.             return inputDeps;
    89.         }
    90.        
    91.     }
    5) CameraFollowSystem is running in the Update Loop so it's normally interpolating using the Time.DeltaTime value and as you can see it's running after the InterpolateTransformToGameObjectSystem cause it's following the Interpolated Transform values and not the Latest FixedUpdate values of the Player.

    6) CopyTransformToGameObjectSystem is setting Transform values for all entities having the CopyTransformToGameObject CD (Like Camera GO) but not interpolated Entities (Like Players )

    And for the InterpolationFactor i just used the same Script from the link you posted.
     
    Last edited: Dec 31, 2019
  14. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,117
    it's really strange o_O
    cause it's running perfectly in the editor between 30 and 60 fps but the build works correctly only for 60fps when i drop the fps even to 59 the Game become unplayable.
    this means there is no GC w any performance issue it's more related to a bug or something not running correctly in the build.
     
  15. Deleted User

    Deleted User

    Guest

    It might be because of the update loop. Things are not ordered correctly.
     
  16. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,117
    thanks for the suggestion.
    but i added to all the concerned systems a Debug.Log with system name + Current Frame Count
    and the debug is showing the correct order from Editor and Device.
     
  17. elcionap

    elcionap

    Joined:
    Jan 11, 2016
    Posts:
    138
    IMHO you should keep a buffer with the data you want interpolate and lerp by time. Using only old/new and a factor it's not good enough so you should keep the timestamp:
    Code (CSharp):
    1.  
    2. public struct InterpolateTime : IComponentData {
    3.     public float Value;
    4. }
    5.  
    6. [InternalBufferCapacity(10)] // Just a example
    7. public struct TransformState : IBufferElementData {
    8.     public double Timestamp;
    9.     public float3 Position;
    10.     public quaternion Rotation;
    11. }
    12.  
    13. [UpdateAfter(typeof(TransformSystemGroup))]
    14. ... class TransformStateSnapshotSystem ... {
    15.     ... OnUpdate(...) {
    16.         var timestamp = Time.ElapsedTime; // Entities 0.2 and forward
    17.         Entities
    18.             .ForEach((DynamicBuffer<TransformState> transformStateBuffer, in LocalToWorld localToWorld) => {
    19.                 // You should "clamp" the length of the buffer to not explode your memory and preferentially keep from being moved to the heap.
    20.                 // Also could use Insert to make it easy to clamp, or treating like a ring buffer, etc
    21.                 transformStateBuffer.Add(new TransformState {
    22.                    Timestamp = timestamp,
    23.                    Position = localToWorld.Position,
    24.                    Rotation = localToWorld.Rotation
    25.                 });
    26.             })
    27.             ...;
    28.     }
    29. }
    30.  
    31. [UpdateInGroup(typeof(PresentationSytemGroup))]
    32. ... class InterpolateTransformStateSystem ... {
    33.     ... OnUpdate(...) {
    34.         var timestamp = Time.ElapsedTime; // Entities 0.2 and forward
    35.         Entities
    36.             .ForEach((in DynamicBuffer<TransformState> transformStateBuffer, in InterpolateTime interpolateTime) => {
    37.                     TransformState state, lhsState, rhsState;
    38.  
    39.                     state.Timestamp = math.max(0, timestamp - interpolateTime.Value);
    40.                     lhsState = ... // Find the closest state in transformStateBuffer that is lower than state.Timestamp  
    41.                     rhsState = ... // Find the closest state in transformStateBuffer that is higher than state.Timestamp
    42.                     var factor = (float)((state.Timestamp - lhsState.Timestamp) / (rhsState.Timestamp - lhsState.Timestamp));
    43.                     state.Position = math.lerp(lhsState.Position, rhsState.Position, factor);
    44.                     state.Rotation = math.slerp(lhsState.Rotation, rhsState.Rotation, factor);
    45.                     // Use state.Position and state.Rotation
    46.                     // Remember check for empty buffer, negative/infinite values, not having lhs and/or rhs etc.
    47.                 });
    48.             })
    49.             ...;
    50.     }
    51. }
    52.  
    This is more or less what you do when interpolating proxies in the client so you can look for examples in this area.
    If your simulation is running in the FixedUpdate you maybe need to use UnityEngine.Time.fixedTime instead of Time.ElapsedTime (Time in 0.2 is a new field in systems and the same as World.Time, also new in 0.2).

    []'s
     
    Opeth001 likes this.
  18. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,117
    you are right i was updating my camera Translation & Rotation after the EndFrameRTSToLocalToWorldSystem.
    i changed my interplationSystem to interpolate LinkedPrefabEntities using their Translation , Rotation before the TransformSystemGroup and modification are applied by the CopyTransformToGameObjectSystem.

    but im still having this weird Jittering when i set TargetFrameRate to <= 59. but run very flawlessly at 60fps. i tried everything these last 2 weeks and im beginning to think it's bug from Unity.
     
  19. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,117
    Thanks @elcionap! ill try it and let you know ;)
     
  20. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,117
    Hello Everyone,
    i found out where the problem is comming from and i have no idea how to fix it cause it's related to Unity.
    by Setting the Frame using application.targetframerate the game is running flawlessly in the editor with Constant DeltaTimes 60FPS (~0,0166667s) & 40FPS (~0,025s), the Player Build (Android) is Perfectly running at
    60FPS (~0.0165....s) but so laggy when limiting the FPS even at 59FPS.
    this is how deltaTimes looks like at 40FPS (0.03308875, 0.00001, 0.03309203, 0.03311411, 0.02692817, 0.006139117, 0.03307073, .......) the fact that the game is running flawlessly at 60FPS in the Player Build means that it's not related to GC or any resource problem but it's juste Unity killing the Frame Spacing by giving a high varience between them.

    Interpolated Positions at 40FPS in the Editor:


    Interpolated Positions at 40FPS in the Player Build:
     
  21. Deleted User

    Deleted User

    Guest

    Is this order respected with your ECS setup? Afaik you need to use Time.fixedDeltaTime for your simulation.

     
  22. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,117



    in my case i converted this logic to work with ECS.

    1) i didnt change the InterpolationController.

    2) i move the SimulationSystemGroup to Run in FixedUpdate so im collecting Transforms in LateSimulationSystemGroup.

    3) im interpolating between Collected Transforms in UpdateSystemGroup Running In Update.

    4) i moved the TransformsSystemsGroup to Run later in UpdateSystemGroup to apply changes on All GameObjects.
     
  23. Deleted User

    Deleted User

    Guest

    To answer the question on the other thread (now locked), @hippocoder is right about setting vSync count to 2; it's a lot more stable than using targetframerate:
    Code (CSharp):
    1.         if(Application.isMobilePlatform)
    2.             QualitySettings.vSyncCount = 2;
    will give you a steady 30 fps, at least that what it does on my pc. You can use 0 to 4, 0 disabling vSync (I think) and 4 setting vSyncCount to 15 fps.
     
    Opeth001 and hippocoder like this.
  24. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,117
    Thanks @APSchmidt for your answer, but i tested this approach on a low end device and setting QualitySettings.vSyncCount = 1 or 2 the device is not able to reach the 30fps anymore and by disabling it it goes to 50fps.
    so is there a way to limit the Frame Rate with Vsync set to 0 knowing that Application.targetFrameRate will give a High Variance between Frame as shown above.
     
  25. Deleted User

    Deleted User

    Guest

    The values you must use for vSync count must be comprised between 0 and 4, as I already explained:
    • 4 will give you 15 fps,
    • 3 will give you 20 fps,
    • 2 will give you 30 fps,
    • 1 will give you 60 fps,
    • 0 will disable vSync.
     
  26. DonVercotti

    DonVercotti

    Joined:
    Feb 1, 2019
    Posts:
    3
    @Opeth001 Have you managed to do something about this? I seem to have the exact same problem on Android. My game runs at a stable 30fps but frame pacing is jittery. The problem doesn't exist on iOS, it's pretty smooth there.
     
  27. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,117
    it turned out that when FPS is not stable in 30 or 60 will create some jitter do to the device screen linear sync i think.
    for my case i had 2 ways to fix it:
    1) set 60fps for high end devices and 30 fps for mid-low end devices so it's always stable.
    2) use smoothedDeltaTime with interpolation / extrapolation instead of deltatime, this makes my game perfectly smooth at any frameRate between 30-60 fps. but this doesn't work with things Particle Systems as it creates weird Effects.

    good luck!
     
    ipotonic likes this.