gabriel
gabriel

Reputation: 147

Frame update speed on different frameworks

I implemented a very small program in Python and C#, in Pygame, Monogame, and Windows Forms. I wanted to see differences in the time taken to change the background color in ms.

These are the respective codes:

Pygame

import sys,time
from pygame.locals import *
import pygame

def g():
    table = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
    swap = -1
    counter = 0
    cl = pygame.time.Clock()
    sta = time.time()
    while counter<2000:
        for event in pygame.event.get():
            if event.type== QUIT or (event.type ==pygame.KEYDOWN  and event.key == pygame.K_ESCAPE):
                pygame.quit()
                sys.exit()

        if swap>0 :
            table.fill((255,0,0))
            swap = -1
        else:
            table.fill((0,0,255))
            swap = 1
        pygame.display.update()
        counter+=1
        cl.tick()
    print((time.time()-sta)*1000/counter)
   
g()

Monogame

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

namespace MonoGameProject;

public class Game1 : Game
{
    private GraphicsDeviceManager _graphics;
    private SpriteBatch _spriteBatch;
    Texture2D texture;
    int sw = 1;
    Stopwatch stp = new Stopwatch();
    double counter = 0d;
    Color color;

    public Game1()
    {
        _graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        IsMouseVisible = true;
    }

    protected override void Initialize()
    {
        _graphics.PreferredBackBufferWidth = 1200;
        _graphics.PreferredBackBufferHeight = 900;
        _graphics.SynchronizeWithVerticalRetrace = false;
        _graphics.ApplyChanges();
        stp.Start();
        base.Initialize();
    }

    protected override void LoadContent()
    {
        _spriteBatch = new SpriteBatch(GraphicsDevice);

        texture = new Texture2D(GraphicsDevice, 
                                GraphicsDevice.Viewport.Width, 
                                GraphicsDevice.Viewport.Height);

        Color[] colorData = new Color[texture.Width * texture.Height];
        for (int i = 0; i < colorData.Length; i++)
        {
            colorData[i] = Color.Red;
        }
        texture.SetData(colorData);
    }

    protected override void Update(GameTime gameTime)
    {
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == 
        ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
            Exit();

        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.Black);

        if (sw > 0) {
            color = Color.Red;
            sw = -1;
        } else {
            color = Color.Blue;
            sw = 1;
        }
       
        Color[] colorData = new Color[texture.Width * texture.Height];
        for (int i = 0; i < colorData.Length; i++)
        {
            colorData[i] = color;
        }
        texture.SetData(colorData);

        _spriteBatch.Begin();
        _spriteBatch.Draw(texture, Vector2.Zero, Color.White);
        _spriteBatch.End();

       
        base.Draw(gameTime);
        counter += 1;
        if (counter>2000)
        {
            Debug.WriteLine($"Ms per frame " +
                $"{stp.ElapsedMilliseconds / counter}");
            Thread.Sleep(5000);
            Environment.Exit(0);
        }
     }
}

Windows Forms

using System.Diagnostics;
using System.Globalization;

namespace reaction;

public partial class Form1 : Form {
    double timeDiff = 0;
    int switchIndex = 1;
    double acum = 0d;

    private Stopwatch _globalStopwatch = new Stopwatch();
    private System.Windows.Forms.Timer colorChangeTimer;
   
    public Form1()
    {
        colorChangeTimer = new System.Windows.Forms.Timer();
        colorChangeTimer.Interval = 1; 
        colorChangeTimer.Tick += new EventHandler(colorChangeTimer_Tick); 
        colorChangeTimer.Start(); 
        _globalStopwatch.Start();

    }
    
    private void colorChangeTimer_Tick(object sender, EventArgs e)
    {
        if (switchIndex > 0) {
            BackColor = Color.Red;
            switchIndex = -1;
        } else {
            BackColor = Color.Blue;
            switchIndex = 1;
        };
        acum += 1;
        if (acum > 2000) {
            Debug.WriteLine($"Ms per frame " +
                $"{_globalStopwatch.ElapsedMilliseconds / acum}");
            Thread.Sleep( 5000 );
            this.Close();
        }
    }

}

Pygame is reporting average around 1ms per frame while Windows Forms and Monogame 16ms. Is there any way to make Windows Forms or Monogame update faster? is the Pygame metric correct? what library/language/framework would you recommend if I wanted the fastest update frequency? and if I also want to programmatically make complex changes in the display through some operations, therefore code execution also has to be fast? I would also like high resolution timing monitoring like with the Stopwatch class in C#.

Upvotes: -1

Views: 136

Answers (2)

Jeremy Thompson
Jeremy Thompson

Reputation: 65692

The Windows Forms Timer component is single-threaded, and is limited to an accuracy of 55 milliseconds. If you require a multithreaded timer with greater accuracy, use the Timer class in the System.Timers namespace.

Ref: https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.timer?view=windowsdesktop-8.0

You could use the multithreaded Timer though using a StopWatch is much smaller and more precise than a Timer which is quite heavy and bloated in comparison.

When you get rid of the Timer you'll lose the Tick() Event, replace it with the Forms_Paint Event.

Upvotes: 0

Rafloka
Rafloka

Reputation: 366

I can only answer for Monogame: Usually, Monogame out of the box is set to a fixed framerate of 60 FPS. Thus, if you measure time like you did, it will always show 16ms.

To change this, use IsFixedTimeStep = false; in Initialize and Vsync must be off (DeviceManager.SynchronizeWithVerticalRetrace = false;). This way, you'd work with a variable time step and maximum possible FPS.

Edit: Second part of your question: You can definitely use Monogame for high performance applications. With an unrestricted framerate, I reached over 1000 FPS (OpenGL) while simulating thousands of entities. In my tests, the DirectX version was even a bit faster than OpenGL.

An important thing is to strictly split logic and drawing code (hence Update and Draw).

With a variable time step, you'd have maximum control but consequently, gameTime.ElapsedGameTime becomes variable, which you have to keep in mind when animating or simulating things.

Edit 2: Regarding your comment: In the Monogame version, you do something rather expensive in Draw, while the operation in Windows Forms is super cheap.

Color[] colorData = new Color[texture.Width * texture.Height];

Here, a new array object is allocated each frame, this array should already exist and be reused (would save you some ms). Then filling the whole array also takes some time. To have a more correct comparison, in Monogame use

GraphicsDevice.Clear(Color.Black);

as

if (sw > 0) {
     GraphicsDevice.Clear(Color.Red);
     sw = -1;
} else {
     GraphicsDevice.Clear(Color.Blue);
     sw = 1;
}

and leave out all the texture filling, then it'd be much more like what you're doing in Windows Forms.

Upvotes: 0

Related Questions