Getting Monitor Refresh Rate to eradicate jerkiness.

Since Xna 4 and the removal of the screen refresh rate from the Adapter info it seems there has been a problem lurking in Xna and now Monogame regarding smooth refresh rates on systems with higher than 60FPS screens.

My dev laptop has a 120Hz screen and doesn’t support 60Hz, even with fullscreen exclusive in DirectX. Besides, it’s always safer to go with what the user is currently running if possible.

Strictly speaking, all movement code should be time independent but practically, if you have different amounts of time between updates, you will get jerkiness (frame pacing should be consistent).

If you want to use a FixedUpdate time then the problem becomes, what rate should this be? If anything other than the Refresh rate of the screen then either tearing (no vsync) or jerkiness will result.

It seems to me, what you really want to do in most cases is to GET the screen refresh rate, then set your FixedUpdate time to the correct fraction of that…basically get as close to 1:1 between Update and Draw as you can is optimal.

Every time I do this I run into the same problem, default performance on my dev laptop is horrible and jerky…(it’s tempting to just go with Presentinterval.Two to mimick a 60Fps update) but I can’t GET the screen Refresh rate from Xna/Monogame, so I have to resort to other methods…Android from the Activity, and Windows from interop into user32.dll.

However, UWP…I can’t find anything, I just don’t know how to get the screen refresh in UWP from C#.

Does anyone know a decent solution for this?

You need to use ticks: Locked 30 FPS plays is jiggly, laggy - #4 by boot

It should remove jerkiness.

Hey Mateo,

If you mean like this:

        // Timing to 60fps...but with a screen refresh rate of 120fps.
        this.IsFixedTimeStep = true;
        this.graphics.SynchronizeWithVerticalRetrace = true;
        this.TargetElapsedTime = TimeSpan.FromTicks((long)(TimeSpan.TicksPerSecond / 60L));

Then unfortunately it just results in a similar, jerky result on a 120Hz screen.

If I do something like this instead:

        this.IsFixedTimeStep = true;
        this.graphics.SynchronizeWithVerticalRetrace = true;
        this.TargetElapsedTime = TimeSpan.FromTicks((long)(TimeSpan.TicksPerSecond / 120L));

then yes, I get the perfectly smooth motion desired…but that’s only because I’m matching Updates with Draws as 1:1 timing.

But I don’t “know” about the 120Hz screen refresh in order to divide up the Target elapsed time correctly (well I can on Android and Win32 but not on UWP).

So I’m still wondering how I GET the screen refresh Hz on UWP in order to set this value correctly?

Back in the day, a directX game would be expected to just SET the screen refresh and resolution as desired by the game but these days, especially in multiplatform like Android and UWP, you’re expected to run at whatever the resolution happens to be so this blindness as to what the hardware specs actually are, from within Xna 4/Monogame seems like a bad move if you wish to base movement code around elapsed timings.

The link to the Sean Hargreaves blogpost about why the refresh rate was removed from Xna 4 is no longer valid so I don’t know the reasoning for the removal in Xna 4 and so then monogame. This causes me, and maybe others, a problem now that we are required to match up with what is there, rather than setting it ourselves.

1 Like

Ok so updating this thread for future viewers, I’ve had what I think is perhaps the correct answer on Stackoverflow: Unfortunately it is NOT possible to get the screen refresh rate Hz in a UWP game (in a way that is compatible with the Microsoft Store).

(How do I GET the screen refresh Hz in C# UWP? - Stack Overflow)

Another issue arose out of this in that in UWP it is NOT possible to set the PresentationInterval to Two either…that is to say, “This is a fast monitor, lets only draw every second frame and achieve smoothness that way”. It’s just not supported in UWP.

So in UWP I can see only 3 options:

  1. Allow a Variable TimeStep. Time all your movement to ElapsedTime between Updates and hope you don’t have any hitches… with varying elapsed times, for a 2D game this is probably a complete non starter with horrible jerky movement but is perhaps OK for 3D games or if potentially running slowly on slow machines.

  2. Decide on a framerate (60) and use a FixedTimeStep TargetTime for that (1/60 seconds but in ticks). You can still base your movement on ElapsedTime but since using a FixedTimeStep you don’t actually have to if 60FPS is always going to be achievable. Resign yourself that for high refresh rate monitors, the game may appear jerky since the Update/Draw rate is not going to be 1:1.
    I’ve found that this strategy on Windows DirectX, even with a fullscreen exclusive window is beyond awful for 120Hz display (you’re much better off with PresentationInterval.Two if targeting 60FPS) but on Windows UWP it’s not that bad…perhaps because it’s not fullscreen exclusive…but you take a performance hit for that too.

  3. Bizarrely ask your user what refresh rate their monitor is and set your FixedTimeStep Target to that (1/X but in ticks). Only do this if your game CAN actually run at those high rates on their machine, obviously this may vary depending on their machine.

If I get any more info I’ll try to update this thread. If anyone else has any ideas or input on this I’d love to hear about it.

You can also use smaller FixedTimeStep for the highest refresh rate (1/144) and enable the V-Sync to cap the FPS at the monitor refresh rate.
Make sure FixedTimeStep isn’t way too small to cause problems with the physics.

This is exactly what I would recommend. It’s as simple as something like this:
position += moveVector * (float)(MOVE_SPEED_PER_SEC * gameTime.ElapsedGameTime.TotalSeconds);

Thank you both for your input.

I’ve tried both ideas. Here is my test code in full. It can be copy/pasted, there are no assets or external dependancies other than a Monogame UWP project.

Program.cs:

using Windows.Foundation;
using Windows.UI.ViewManagement;

namespace UWPRefreshRate
{
    /// <summary>
    /// The main class.
    /// </summary>
    public static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static void Main()
        {
            ApplicationView.PreferredLaunchViewSize = new Size(TestGame.Width, TestGame.Height);
            ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.FullScreen;

            var factory = new MonoGame.Framework.GameFrameworkViewSource<TestGame>();
            Windows.ApplicationModel.Core.CoreApplication.Run(factory);
        }
    }
}

TestGame.cs:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;

namespace UWPRefreshRate
{
    public class TestGame : Game
    {
        // Screen dimensions...
        public static int Width = 1920;
        public static int Height = 1080;

        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        /// <summary>
        /// Represents a Pixel Art Game Screen.
        /// </summary>
        RenderTarget2D _renderTargetPixelArt;
        /// <summary>
        /// Represents the player sprite texture
        /// </summary>
        RenderTarget2D _playerTexture;
        /// <summary>
        /// Represents the Player Size
        /// </summary>
        Rectangle _playerBounds;
        /// <summary>
        /// Represents the player position
        /// </summary>
        Vector2 _playerPosition = new Vector2(20, 20);

        /// <summary>
        /// Represents the bounds of the Pixel Art Screen
        /// </summary>
        Rectangle _screenBounds;

        /// <summary>
        /// Give uniform scaling to pixel art screen.
        /// </summary>
        const int SCALE_FACTOR = 4;

        /// <summary>
        /// 1x1 Pixel Texture to avoid Loading Assets in this example.
        /// </summary>
        Texture2D _pixelTexture;

        /// <summary>
        /// Set to true to ignore elapsedTime in movement code.
        /// </summary>
        bool _arcadeModeIgnoreElapsedTime = false;

        public TestGame()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            graphics.IsFullScreen = true;
            graphics.HardwareModeSwitch = true; // Makes no difference on UWP.

            // -----------------------------------------------------------------------------------------------------------------------------
            // Test 1: Use a Variable TimeStep.
            // Outcome: Horrible Stutter on 120Hz screen, at least "now and again".
            // Give it a minute and try some diagonal movement too, you'll see it skip.
            // Once the jitter starts it pretty much continues, as if the elapsed time is going "out of phase"
            //...the trouble is the elapsedTime value is changing which ruins consistent frame timing and smooth movement.
            IsFixedTimeStep = false;
            graphics.SynchronizeWithVerticalRetrace = true;
            _arcadeModeIgnoreElapsedTime = false;

            //// -----------------------------------------------------------------------------------------------------------------------------
            //// Test 2: Uncomment to use Variable Timestep but with the Arcade approach of ignoring ElapsedTime.
            //// Outcome: Fixes the stutter but movement speed is different on faster screens (not what we want).
            //IsFixedTimeStep = false;
            //graphics.SynchronizeWithVerticalRetrace = true;
            //_arcadeModeIgnoreElapsedTime = true;


            //// -----------------------------------------------------------------------------------------------------------------------------
            //// Test 3: Uncomment for 60Hz FixedTimeStep
            //// Outcome: "Better" frametiming at least although now "Ghosting" as moving at 2 pixel jumps on a 120Hz screen.
            //// This is represented by the Player rectangle looking more like an isomentric 3D "Box" as it moves diagonally.
            //IsFixedTimeStep = true;
            //TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 60);
            //graphics.SynchronizeWithVerticalRetrace = true;
            //_arcadeModeIgnoreElapsedTime = false;

            //// -----------------------------------------------------------------------------------------------------------------------------
            //// Test 4: Uncomment for 60Hz FixedTimeStep but with Arcade style Ignore ElapedTime.
            //// Outcome: No Ghosting but moving at "half speed" for everyone. 
            //IsFixedTimeStep = true;
            //TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 60);
            //graphics.SynchronizeWithVerticalRetrace = true;
            //_arcadeModeIgnoreElapsedTime = true;

            //// -----------------------------------------------------------------------------------------------------------------------------
            //// Test 5: Uncomment 120Hz FixedTimeStep
            //// Outcome: "Pretty much perfect" as moving approx 1 pixel every update on a 120Hz screen.
            //// The trouble is, we cannot "Know" about the 120Hz screen from the UWP API (or MonoGame) so cannot set this without user input.
            //IsFixedTimeStep = true;
            //TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 120);
            //graphics.SynchronizeWithVerticalRetrace = true;
            //_arcadeModeIgnoreElapsedTime = false;

            //// -----------------------------------------------------------------------------------------------------------------------------
            //// Test 6: Uncomment 120Hz FixedTimeStep updates that ignore GameTime.
            //// Outcome: Basically the same as test 5 excepy moving 1 pixel every update (guaranteed).
            //// This is "Absolute Perfection" on 120Hz screen.
            //// The trouble is, it's totally wrong for anyone NOT using a 120Hz screen!
            //IsFixedTimeStep = true;
            //TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 120);
            //graphics.SynchronizeWithVerticalRetrace = true;
            //_arcadeModeIgnoreElapsedTime = true;

            //// -----------------------------------------------------------------------------------------------------------------------------
            //// Test 7: Uncomment for "Smaller timestep for all". 
            //// Outcome: Not divisable by 120Hz or even 60, beyond horrible jerkiness pretty much ALL the time.
            //// Actually gives me a headache just looking at it.
            //IsFixedTimeStep = true;
            //TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 144);
            //graphics.SynchronizeWithVerticalRetrace = true;
            //_arcadeModeIgnoreElapsedTime = false;


            /// -----------------------------------------------------------------------------------------------------------------------------
            //// Test Bonus: Just for "fun", Uncomment to cause the game to crash on startup (At least Debug mode on Monogame 3.7.x)?
            //graphics.SynchronizeWithVerticalRetrace = false;
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            _renderTargetPixelArt = new RenderTarget2D(GraphicsDevice, 224, 248, false, SurfaceFormat.Color, DepthFormat.None);

            // Centered
            _screenBounds = new Rectangle(0, 0, _renderTargetPixelArt.Width * SCALE_FACTOR, _renderTargetPixelArt.Height * SCALE_FACTOR);
            _screenBounds.X += (GraphicsDevice.Viewport.Width - _screenBounds.Width) / 2;
            _screenBounds.Y += (GraphicsDevice.Viewport.Height - _screenBounds.Height) / 2;

            _playerTexture = new RenderTarget2D(GraphicsDevice, 15, 5, false, SurfaceFormat.Color, DepthFormat.None);
            _playerBounds = new Rectangle(0, 0, _playerTexture.Width, _playerTexture.Height);
            
            _pixelTexture = new Texture2D(GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
            _pixelTexture.SetData(new Color[] { Color.White });


            base.LoadContent();
        }

        protected override void Update(GameTime gameTime)
        {
            KeyboardState keyState = Keyboard.GetState();
            if (keyState.IsKeyDown(Keys.Escape))
                Exit();

            Vector2 moveVector = Vector2.Zero;
            if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D))
                moveVector.X = 1;
            if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A))
                moveVector.X = -1;
            if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S))
                moveVector.Y = 1;
            if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W))
                moveVector.Y = -1;

            

            // PC mode, try to move with elapsed time for best compatibility with a range of devices...
            if (!_arcadeModeIgnoreElapsedTime) 
            {
                const float speed = 60 * 2; // 2 pixels per frame @60 fps BUT a perfect 1 pixel per frame at 120Hz
                float elapsedSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;
                _playerPosition += moveVector * speed * elapsedSeconds;
            }

            // Arcade mode, we run at a fixed speed ALL the time, no need for time integration.
            // But can ONLY really work with a Fixed Time step that works for everyone (ie known hardware, hence Arcade/Console approach)
            if (_arcadeModeIgnoreElapsedTime) 
            {
                _playerPosition += moveVector; // 1 pixel per frame always but will move twice as fast @ 120Hz than @ 60Hz.
            }

            base.Update(gameTime);
        }


        protected override void Draw(GameTime gameTime)
        {
            // Create player "Sprite"
            GraphicsDevice.SetRenderTarget(_playerTexture);
            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp);
            spriteBatch.Draw(_pixelTexture, _playerBounds, Color.White);
            spriteBatch.End();

            // Draw Player Sprite into Pixel Art "screen".
            GraphicsDevice.SetRenderTarget(_renderTargetPixelArt);
            GraphicsDevice.Clear(Color.Black);
            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp);
            spriteBatch.Draw(_playerTexture, _playerPosition, Color.White);
            spriteBatch.End();

            // Draw Pixel Art to screen.
            GraphicsDevice.SetRenderTarget(null);
            GraphicsDevice.Clear(Color.CornflowerBlue);
            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp);
            spriteBatch.Draw(_renderTargetPixelArt, _screenBounds, Color.White);
            spriteBatch.End();

            base.Draw(gameTime);
        }

    }
}

In order to see what I see and get the outcomes I get of course you’ll need to be running on a 120Hz screen but I think my notes on this are sound?

Test 5 would be about the best you could possibly do if you wanted to support all screen Hz and would be virtually perfect for everyone…if only you knew what the Screen Hz actually was…but you don’t.

Unless you get user input to tell you that it’s 120 or X, then Test 3 is about the best you can do and although fine for the vast majority running a 60Hz screen, the high Hz people have to put up with ghosting and this is not ideal for a 2D arcade style game.

One final thought for now…perhaps Test 1 should indeed have worked the best as according to Shawn Hargreaves: His blog

If you disable fixed timesteps, XNA does nothing clever for you. Our algorithm in this mode is extremely simple:

Update
Draw
Rinse, lather, repeat
(that is actually a slight simplification, but the details are unimportant)

The trouble perhaps comes about because now ElapsedTime is varying quite alot and this causes the “phasing” observed.

This leads me to believe that a FixedTimeStep is actually the only real way, in UWP (no Hardware Exclusive Screen, no PresentationInterval.Two option) that smoothness can be achieved. The trouble is, as I say, there’s no way to know what the FixedTimeStep should actually be to be optimal.