Thread: A slow drawing problem

  1. #1
    Registered User gavra's Avatar
    Join Date
    Jun 2008
    Posts
    265

    A slow drawing problem

    Recently, I posted my snake game and have bee told to move all my drawing methods to the Form1_Paint method.
    Before I changed it, I draw only the parts of the snake that has changed.
    Now I draw all the snake and the maze, but as the maze has more parts, as the drawing is getting slower.
    Is it acceptable to move the level.Draw() out of the Form1_Paint or there is another solution?

    Thanks a lot.

    Code:
            private void FormGame_Paint(object sender, PaintEventArgs e)
            {
                if (onGame)
                {
                    snake.Draw();
                    food.Draw();
                    level.Draw();
                }
            }
    Last edited by gavra; 01-22-2011 at 03:21 PM.
    gavra.

  2. #2
    Registered User
    Join Date
    Mar 2009
    Location
    england
    Posts
    209
    You're not passing the PaintEventArgs object to any of the three draw methods so it makes me wonder how it is being painted at all. My guess would be Control.CreateGraphics() but that kinda defeats the point of having the PaintEventArgs already there and available to you.

    By the way, if you suffer with flickering when your graphics are being painted you should check that the control's DoubleBuffer property is set to true. It should eliminate that problem. I'm not sure if this is what you mean when you say "slow drawing".

  3. #3
    Registered User gavra's Avatar
    Join Date
    Jun 2008
    Posts
    265
    I've tried setting the DoubleBuffer property to true but it just made it worst.

    I don't know that is the PaintEventArgs object but as I construct the objects that I draw in the FormGame_Paint method, I pass a reference to the graphics object of form_Game that I created using CreateGraphics method.

    And yes, I mean that it's flickering.

    anyone?
    gavra.

  4. #4
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    Cant tell from the code you supplied, could be many reasons.

    If you want me to guess, try handling the WM_ERASEBACKGROUND / OnPaintBackground() msg.

    Otherwise post ALL the drawing code, including when and how you call for a paint msg.
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  5. #5
    Registered User gavra's Avatar
    Join Date
    Jun 2008
    Posts
    265
    Snake.cs:
    Code:
            public void Draw()
            { 
                foreach (Point organ in organs)
                {
                    graphics.FillRectangle(bodyBrush, new Rectangle(organ, Globals.defaultSize));
                }
                graphics.FillRectangle(headBrush, new Rectangle(organs[0], Globals.defaultSize));
            }
    FormGame.cs:
    Code:
            private void FormGame_Paint(object sender, PaintEventArgs e)
            {
                if (onGame)
                {
                    snake.Draw();
                    food.Draw();
                    lvl.Draw();
                }            
            }
    FormGame.Designer.cs:
    Code:
                this.Paint += new System.Windows.Forms.PaintEventHandler(this.FormGame_Paint);
    Actually, the level is the problem maker.
    Level.cs:
    Code:
            public void Draw()
            {
                foreach (Brick b in bricks)
                    graphics.FillRectangle(solidBrush, 
                                           new Rectangle(b.GetPosition(), Globals.defaultSize));
            }
    gavra.

  6. #6
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    Handling the WM_ERASEBACKGROUND may stop the flicker.

    How do you call for a paint message? How often does it happen? (you did not post this code)



    If not then you will need reduce the number of drawing operations when you get a paint message.

    When a game starts create an Image and draw the 'level', inc 'food' but not the snake (ie all items that do not move/change in a game).

    Store this Image and copy the whole Image to the screen graphics object as one draw (rather than constructing it each paint msg), then draw the new snake position etc.

    Normally you would do all the drawing (draw the entire screen, update the postions of the moving objects, etc) and then call for a paint message.
    The paint handler will only copy this 'buffer' to a limited section of the screen, using the rectangle from the paint msg.
    This means if your app gets partially covered by another app, it will still draw quickly and smoothly as only a limited area is redrawn and only one 'draw' is called (as opposed to your 100's of 'draws').
    Last edited by novacain; 01-25-2011 at 11:06 PM.
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  7. #7
    Registered User gavra's Avatar
    Join Date
    Jun 2008
    Posts
    265
    FormGame.cs:
    Code:
            private void timer1_Tick(object sender, EventArgs e)
            {
                if (key.Equals(Globals.upKey))
                    snake.MoveUp(steps);
                else if (key.Equals(Globals.downKey))
                    snake.MoveDown(steps);
                else if (key.Equals(Globals.leftKey))
                    snake.MoveLeft(steps);
                else if (key.Equals(Globals.rightKey))
                    snake.MoveRight(steps);
                else return;
    
                if (snake.GetHeadPosition().Equals(food.GetPosition()))
                {
                    if (Globals.enableSounds)
                        System.Media.SystemSounds.Exclamation.Play();
                    score += Globals.speed.value;
                    scoreLabel.Text = score.ToString();
                    food.Generate(snake.GetOrgans(), lvl.GetBricks(), steps);
                    eatenFoodCount++;
                }
                else snake.RemoveTail();
    
                if (snake.isCrashed(lvl.GetBricks()))
                {
                    onGame = false;
                    GameOver();
                }
    
                Invalidate();
            }
    When I create the Image, should I include the food or only the level? (since the food changes frequently and I would have to create a new image every time the snake eats it)
    gavra.

  8. #8
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    How fast are you running the timer?

    Does adding an 'Update' after the Invalidate help?

    Quote Originally Posted by gavra View Post
    When I create the Image, should I include the food or only the level? (since the food changes frequently and I would have to create a new image every time the snake eats it)
    The idea is to limit the number of draws each paint msg, preferably to one.

    This is how I do this sort of thing.....

    This design uses two images.
    Playingfield image has just the background (everything but the snake, score and food)
    Game image has what you want the user to see (everything).

    When you get a 'new game' event;
    clean up any old images.
    create a new Playingfield image
    create a blank Game image.
    [so creating the bricks only gets called once per game]

    When you get a timer event;
    process user input and update snake/food positions.
    copy Playingfield image over Game image (clear old screen)
    draw new food, snake, score, etc to Game image
    call for a paint msg
    [so all our drawing is done before we call a paint]

    When you get a paint event;
    draw Game image to the main screen using the paint event args
    [one draw only so quick]

    When the app closes;
    clean up any old images

    EDIT:
    If shearing/flicker continues;

    When you get an OnPaintBackground event;
    pretend we handled it so teh OS doesn't redraw the apps background
    [Check this! I think just creating an event handler is enough to stop teh OS calling the default handler]

    [I am not sure a timer event is the best way to do this, but I am much more conversant with what results can be achieved with WIN32 drawing than C#.

    I found C# drawing to be VERY dependent on the version of MSVC/.NET you use (2005 was very slow IME)

    I wonder if using Idle and KeyEventHandler events would not be a better design.

    Better to get a working version and then experiment, by shifting the already working code to see if performance improves.]
    Last edited by novacain; 01-28-2011 at 11:06 PM.
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  9. #9
    Gawking at stupidity
    Join Date
    Jul 2004
    Location
    Oregon, USA
    Posts
    3,218
    You're still not using e.Graphics to draw with from the OnPaint() method. Try this:
    Code:
            private void FormGame_Paint(object sender, PaintEventArgs e)
            {
                if (onGame)
                {
                    snake.Draw(e.Graphics);
                    food.Draw(e.Graphics);
                    level.Draw(e.Graphics);
                }
            }
    And then in each of the Draw() methods, use that for drawing. e.g.:
    Snake.cs
    Code:
            public void Draw(Graphics g)
            { 
                foreach (Point organ in organs)
                {
                    g.FillRectangle(bodyBrush, new Rectangle(organ, Globals.defaultSize));
                }
                g.FillRectangle(headBrush, new Rectangle(organs[0], Globals.defaultSize));
            }
    If you understand what you're doing, you're not learning anything.

  10. #10
    Registered User gavra's Avatar
    Join Date
    Jun 2008
    Posts
    265
    novacain,

    The Update is done in the tick event method.
    OK, I think I got your idea about the drawing but I don't know hot to create an Image, update it and so on.. I would be happy get some help with that.
    I tried handling the OnPaintBackground event but I just got a method that paints the background of the form.
    I use Microsoft Visual Studio 2010.

    itsme86,
    I've already tried doing this, but it didn't make any change.
    gavra.

  11. #11
    Registered User C_ntua's Avatar
    Join Date
    Jun 2008
    Posts
    1,853
    My guess is that the re-drawn is caused because the Form is invalidated more than once. So you can focus on stopping this.

    Honestly, I don't know how to stop automatic re-draw, thus giving you the control to re-draw when you want (i.e. when and only you can Invalidate()). If somebody knows this answer then this is the best you can do.

    To make a picture you will use a similar method you already use. You just have to use the graphics of a Bitmap, not the Form. Check this as an example.
    Then once you construct your Bitmap before you invalidate you draw the Bitmap on the screen. So in th end you will just have minimal changes, simply you will use a Bitmap as a buffer and use one DrawImage() rather than a bunch of FillRectangle().

    Make the above work, but let me give you a simpler approach:
    Code:
    //In class Snake
    Panel head, tail, food;
    ...
    void Move(...)
    {
        ...
        if (!crushed) 
        {
          head.Location = ...
          tail.Location = ....
          if (foodEaten)
            food.Location = ....
       }
    }
    This is a method so you won't bother with Graphics at all. You just move pieces (panel) and let the drawing completely to the system. When you move a Panel after some point the screen will re-draw, since you only move 2-3 Panel you can assume that there won't be any real flickering.

    As a final note, why use WinForms?? I have built a whole game in WinForms as a matter of fact (the on in my signature) and I simply got stuck because a feature wasn't working (transparent background on Panels). As an exercise it is fine, but if you want to go to graphics I really don't think you should bother too much. I solved my problem switching to WPF which has the feature I wanted and some more. But my requirements are low since it is a turn-based game, yours are low just because the game itself is simple, if for example you wanted the snake to be more nicely animated and all the "pieces" of its body shake a bit as it moves you might encounter a lot of limitations in WinForms and in the end if you are going to skip down to the basic why not use a real library for graphics??

  12. #12
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    How often does the timer code get called? What time period did you create the timer with? Can your system handle drawing that fast?

    Quote Originally Posted by gavra View Post
    The Update is done in the tick event method.
    In the timer handler you posted;

    Invalidate() is called, which sends a paint msg to the OS msg queue, then to the app msg queue, then to the form's callback, which calls your code (which takes time)

    If you change it to
    Invalidate()
    Update()

    Then the paint msg is sent directly to the form's callback (which is quicler)


    Quote Originally Posted by gavra View Post
    OK, I think I got your idea about the drawing but I don't know hot to create an Image, update it and so on.. I would be happy get some help with that.
    This is written off the top of my head, no complier, so will not actually work.....

    Code:
    //NOTE the 'PlayingField' and 'Game' are member variables of type Bitmap. They could be Graphics objects, if you find that easier.
    
    Graphics g = this.GetGraphics();//get the forms graphics object
    
    //free any old image from the member PlayingField
    PlayingField.Dispose();
    
    //create new image
    PlayingField= new Bitmap(g.ClipBounds.Width, g.ClipBounds.Height, g);//create an image the same size as the screen
    
    //draw background
    Graphics holder = Graphics.FromImage(PlayingField);//create a graphics object with PlayingField in it to draw on
    level.Draw(holder);//draw the bricks
    
    //do the same for the 'Game' image but also draw the starting snake and food etc 
    //clean up old image
    Game.Dispose();
    //copy PlayingField to Game 
    Game=PlayingField.Clone();
    
    //draw on snake etc


    Quote Originally Posted by gavra View Post
    I tried handling the OnPaintBackground event but I just got a method that paints the background of the form.
    The form/window has two 'layers', teh background and your image.

    Sometimes the form redraws the background (flash of grey) under your image, then your image on top.

    This can cause the screen to flicker.
    Last edited by novacain; 01-30-2011 at 11:18 PM.
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  13. #13
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    All of this and yet GDI is always going to be fairly slow. As some have suggested you should use a graphics library/SDK like OGL, Direct3D, SDL, OGRE, Irrlicht, etc. if you want to do any type of real-time graphics in Windows. I'm pretty sure you could have written a super simple renderer in C++, bridged it with C++/CLI or through raw P/Invoke's, and used the C# GUI controls to interact with it in less time than you have spent doing this in pure WinForms.

  14. #14
    Registered User gavra's Avatar
    Join Date
    Jun 2008
    Posts
    265
    It is still flickering.
    Think about this: Every time the snake moves I have to redraw it. To to that I must call Invalidate() method that its one of the FormGamePaint() method triggers. Invalidate() method clears the form so the level has also been cleaned ans that means that I now have to redraw the level too. In conclusion, I have to redraw the snake, food and the level every tick of the timer and the way I draw the level does not matter because, as I said, although I used Bitmap for drawing it is still flickering.

    I thought maybe it won't be that awful using the fist way: draw the whole snake, the food and the level just at the start of the game and every time the snake moves - erase its tail and draw its new head, every time the food is been eaten - erase it and draw a new one.

    Do you think it would be acceptable? It's just a school project.
    gavra.

  15. #15
    Gawking at stupidity
    Join Date
    Jul 2004
    Location
    Oregon, USA
    Posts
    3,218
    It shouldn't be flickering. I've made a snake game before using GDI+ in WinForms and there was no flicker. Just put your whole project up and I'll figure out what's wrong.
    If you understand what you're doing, you're not learning anything.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Which is the better way to draw?
    By g4j31a5 in forum Game Programming
    Replies: 16
    Last Post: 01-22-2007, 11:56 PM
  2. draw function HELP!!!
    By sunoflight77 in forum C++ Programming
    Replies: 1
    Last Post: 05-10-2005, 11:28 PM
  3. ray casting
    By lambs4 in forum Game Programming
    Replies: 62
    Last Post: 01-09-2003, 06:57 PM
  4. Draw Shapes.
    By Unregistered in forum C Programming
    Replies: 1
    Last Post: 08-19-2002, 09:22 AM
  5. Transparent Draw Question
    By GodLike in forum Windows Programming
    Replies: 5
    Last Post: 05-07-2002, 06:56 AM