Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drawing 2D Graphics To A Form Seems To Lag/Slow Down My Program

I just started learning .NET so chances are this is a big n00b mistake:

I'm trying to make a simple pong game, using a form and then the System::Drawing::Graphics class to draw the game to the form.

The significant bits of my code look like this:

(Main Game Loop):

void updateGame()
{
    //Update Game Elements
    (Update Paddle And Ball) (Ex. paddle.x += paddleDirection;)
    //Draw Game Elements
    //Double Buffer Image, Get Graphics
    Bitmap dbImage = new Bitmap(800, 600);
    Graphics g = Graphics::FromImage(dbImage);
    //Draw Background
    g.FillRectangle(Brushes::White, 0, 0, 800, 600);
    //Draw Paddle & Ball
    g.FillRectangle(Brushes::Black, paddle);
    g.FillRectangle(Brushes::Red, ball);
    //Dispose Graphics
    g.Dispose();
    //Draw Double Buffer Image To Form
    g = form.CreateGraphics();
    g.DrawImage(dbImage, 0, 0);
    g.Dispose();
    //Sleep
    Thread.sleep(15);
    //Continue Or Exit
    if(contineGame())
    {
        updateGame();
    } 
    else
    {
        exitGame();
    }
}

(Form Initialization Code)

void InitForm()
{
    form = new Form();
    form.Text = "Pong"
    form.Size = new Size(800, 600);
    form.FormBorderStyle = FormBorderStyle::Fixed3D;
    form.StartLocation = StartLocation::CenterScreen;
    Application::Run(form);
}

PS. This is not the exact code, I just wrote it from memory, so that's where any typos or wrong names, or some important lines of code to do with initializing the form would be coming from.

So that is the code. My issue is is that the game is certainly not updating every 15 milliseconds (about 60 fps), its going much slower, so what I have to do instead is move the paddle/ball larger amounts of distance each time to compensate for it not updating very quickly, and that looks very bad.

In a nutshell, something when it comes to drawing the graphics is slowing the game down a very large amount. I have a feeling it has something to do with me double buffering, but I cannot get rid of that, since that would create some yucky flickering. My question is, how do I get rid of this lag?

like image 223
Aaron Avatar asked Feb 18 '23 20:02

Aaron


2 Answers

There are several issues with this code that can heavily impact app performance:

  1. Creating a large Bitmap buffer every frame and not disposing it
  2. Implementing double-buffering while WinForms already have a great implementation of this behavior
  3. updateGame() is recursive but it doesn't have to be recursive
  4. Calling Thread.Sleep() in GUI thread

Code sample, which uses separate thread to count game ticks and WinForms built-in double-buffering:

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.Run(new GameWindow());
    }

    class GameWindow : Form
    {
        private Thread _gameThread;
        private ManualResetEvent _evExit;

        public GameWindow()
        {
            Text            = "Pong";
            Size            = new Size(800, 600);
            StartPosition   = FormStartPosition.CenterScreen;
            FormBorderStyle = FormBorderStyle.Fixed3D;
            DoubleBuffered  = true;

            SetStyle(
                ControlStyles.AllPaintingInWmPaint |
                ControlStyles.OptimizedDoubleBuffer |
                ControlStyles.UserPaint,
                true);
        }

        private void GameThreadProc()
        {
            IAsyncResult tick = null;
            while(!_evExit.WaitOne(15))
            {
                if(tick != null)
                {
                    if(!tick.AsyncWaitHandle.WaitOne(0))
                    {
                        // we are running too slow, maybe we can do something about it
                        if(WaitHandle.WaitAny(
                            new WaitHandle[]
                            {
                                _evExit,
                                tick.AsyncWaitHandle
                            }) == 0)
                        {
                            return;
                        }
                    }
                }
                tick = BeginInvoke(new MethodInvoker(OnGameTimerTick));
            }
        }

        private void OnGameTimerTick()
        {
            // perform game physics here
            // don't draw anything

            Invalidate();
        }

        private void ExitGame()
        {
            Close();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            var g = e.Graphics;
            g.Clear(Color.White);

            // do all painting here
            // don't do your own double-buffering here, it is working already
            // don't dispose g
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            _evExit = new ManualResetEvent(false);
            _gameThread = new Thread(GameThreadProc);
            _gameThread.Name = "Game Thread";
            _gameThread.Start();
        }

        protected override void OnClosed(EventArgs e)
        {
            _evExit.Set();
            _gameThread.Join();
            _evExit.Close();
            base.OnClosed(e);
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            // do nothing
        }
    }
}

Even more improvements can be made (for example, invalidating only parts of game screen).

like image 99
max Avatar answered Apr 27 '23 01:04

max


Don't create a new Bitmap every frame.

Don't use Thread.Sleep. Instead check out the Timer component (the one in the System.Windows.Forms namespace).

like image 41
Ben Voigt Avatar answered Apr 27 '23 00:04

Ben Voigt