BitBlt Slowdown with repeated calls

Maloric

Member
Joined
Mar 4, 2006
Messages
9
Im using GDI32 to TransparentBlt graphics to the screen about 10 times a second. But after about 10 seconds I start to suffer severe slowdown. This is the sprite.draw function I have created for my sprite class.

Code:
Public Sub Draw(ByVal targetGraphics As Graphics, ByVal ImageType As TileImage, ByVal Tile As Integer, _
        ByVal DestX As Integer, ByVal DestY As Integer)

        Dim X As Integer
        Dim Y As Integer

        Y = Math.Floor(Tile / ImageType.RowCount) * ImageType.TileHeight
        Math.DivRem((Tile * ImageType.TileWidth), (ImageType.TileWidth * ImageType.RowCount), X)
        Row and Column (X & Y position) of the tile to draw

        Dim offscreen As Bitmap = New Bitmap(Application.StartupPath & ImageType.Location)

        Get the hDC from targetGraphics
        Dim targethDC As IntPtr = targetGraphics.GetHdc()

        Create an offscreen dc and select our bitmap in to it
        Dim offscreenhDC As IntPtr = CreateCompatibleDC(targethDC)
        Dim oldObject As IntPtr
        oldObject = SelectObject(offscreenhDC, offscreen.GetHbitmap())

        Blt the image to the target
        TransparentBlt(targethDC, DestX, DestY, ImageType.TileWidth, ImageType.TileHeight, offscreenhDC, X, Y, ImageType.TileWidth, ImageType.TileHeight, ImageType.KeyColor)

        Equivalent BitBlt (minus transparency) commented out
        BitBlt(targethDC, DestX, DestY, ImageType.TileWidth, ImageType.TileHeight, offscreenhDC, X, Y, SRCCOPY)

        Select our bitmap out of the dc and delete it

        DeleteDC(SelectObject(offscreenhDC, oldObject))

        Release the target hDC
        targetGraphics.ReleaseHdc(targethDC)
        offscreen.Dispose()
    End Sub

I think the problem resides in this part of the code.
Code:
Dim offscreen As Bitmap = New Bitmap(Application.StartupPath & ImageType.Location)

Because when I changed the arguments of the Bitmap to 100,100 (width and height) it created a small rectangle instead, but didnt suffer from the slowdown. I used offscreen.dispose() but that doesnt seem to make any difference.

Any just before anyone suggests it - no, its not the dreaded TransparentBlt memory leak as Im using XP and BitBlt also has this problem (its commented out in there somewhere).

Any help would be greatly appreciated.

Jamie.
 
I think its also worth mentioning that I tried targetGraphics.Dispose() but it generated an exception after trying to call the function a second time. I also tried disposing the graphics object at the end of the loop calling the draw function but the same error occurred.

When I opened the task manager (CTRL + ALT + DEL) and opened the Performance tab, the "Total" figure in "Commit Charge" was creeping higher, up to the Limit and seemingly pushing the limit higher. I stopped the program at this point so my PC wouldnt explode.
 
There are memory leak issues associated with TransparentBlt, although whether or not these are specific to certain versions of Windows, Im not sure. It also looks like you will be creating a lot of Bitmap objects that could be recycled which make extra work for the GC as well as extra CPU for disposal.
 
marble_eater said:
There are memory leak issues associated with TransparentBlt, although whether or not these are specific to certain versions of Windows, Im not sure. It also looks like you will be creating a lot of Bitmap objects that could be recycled which make extra work for the GC as well as extra CPU for disposal.

I know Im creating a bitmap for every iteration of this function, but I dont know how else to do it. I cant create a new instance of the object without the startup path, which means it has to be inside the loop. I have tried putting "Dim offscreen as Bitmap" at the top of the class, and then using "offscreen = New Bitmap(Filename)" in the Draw function but this defeats the point as it is still creating a new Bitmap each time.

The only thing I can think of is that Im not disposing the bitmap properly but I dont know how else to do it.
 
Just out of interest what is the method for. Ive not seen anyone use blt methods since I programmed VB 6. Surely the same effect could be achieved using the Graphics object? or have I missed the point somewhere?
 
GDI tends to execute a little faster than GDI+. GDI+ 2.0 is faster than 1.x, but still not as fast as GDI. Im not sure if this applies to TransparentBlt, since this is actuall not part of GDI32.dll.

Also, Maloric, will the image be the same every time? Are you re-using the ImageType objects? If so, perhaps it would be best to move the Bitmap object into the ImageType object so that you may load it once when you create the ImageType, instead of re-loading it each time the image is drawn in the Draw sub.
 
marble_eater said:
GDI tends to execute a little faster than GDI+. GDI+ 2.0 is faster than 1.x, but still not as fast as GDI. Im not sure if this applies to TransparentBlt, since this is actuall not part of GDI32.dll.

Also, Maloric, will the image be the same every time? Are you re-using the ImageType objects? If so, perhaps it would be best to move the Bitmap object into the ImageType object so that you may load it once when you create the ImageType, instead of re-loading it each time the image is drawn in the Draw sub.

In response to Cags, the function is to draw sprites to the screen (well, to the backbuffer which is then blitted to the screen).

And unfortunately, no, the image isnt the same every time. I was planning to use the same function to draw multiple types of sprites. Each type of sprite has a different image. I might just write separate functions for each type (i.e. ShipDraw. ComponentDraw etc) if that solves the memory issue. Ill try that now and repost my results.
 
Well I tried delcaring the bitmap in the TileImage class but to no avail. Here is the code as it looks now:

Code:
Public CompImage As New TileImage
        Public Sub Draw(ByVal Target As BackBuffer, ByVal Tile As Integer, _
        ByVal DestX As Integer, ByVal DestY As Integer)

        Dim X As Integer
        Dim Y As Integer

        Dim offscreenhDC As IntPtr = Target.offscreenhDC
        Dim targethDC As IntPtr = Target.targethDC

        Y = Math.Floor(Tile / CompImage.RowCount) * CompImage.TileHeight
        Math.DivRem((Tile * CompImage.TileWidth), (CompImage.TileWidth * CompImage.RowCount), X)
        Row and Column (X & Y position) of the tile to draw

        Dim oldObject As IntPtr
        oldObject = SelectObject(offscreenhDC, CompImage.Bmp.GetHbitmap())

        Blt the image to the target
        TransparentBlt(targethDC, DestX, DestY, CompImage.TileWidth, CompImage.TileHeight, offscreenhDC, X, Y, CompImage.TileWidth, CompImage.TileHeight, CompImage.KeyColor)

        Equivalent BitBlt (minus transparency) commented out
        BitBlt(targethDC, DestX, DestY, CompImage.TileWidth, CompImage.TileHeight, offscreenhDC, X, Y, SRCCOPY)

        Select our bitmap out of the dc and delete it
        DeleteDC(SelectObject(offscreenhDC, oldObject))
End Sub

Also I created a BackBuffer class to store the hDCs:

Code:
Public Class BackBuffer : Inherits GameEngine
    Public BackSurface As New Bitmap(500, 500)
    Public Buffer As Graphics = Graphics.FromImage(BackSurface)
    Public targethDC As IntPtr = Buffer.GetHdc()
    Public offscreenhDC As IntPtr = CreateCompatibleDC(targethDC)
End Class

This is the TileImage Class:

Code:
Public Class TileImage
    Public Bmp As Bitmap = New Bitmap(Application.StartupPath & "\components.bmp")
    Public TileWidth As Integer = 36 The width in pixels of each tile
    Public TileHeight As Integer = 36 The height in pixels of each tile
    Public RowCount As Integer = 25 How many tiles per row
    Public KeyColor As Integer = 0 The value of the transparent color
End Class

And this is the game loop calling the draw function:

Code:
Dim BackBuffer As New BackBuffer
        Dim SystemMap As Graphics
        Private Sub GameLoop_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles GameLoop.Tick
        X = CInt(txtXPos.Text)
        Y = CInt(txtYPos.Text)
        Tile = CInt(txtImageNum.Text)

        NewSprite.Draw(BackBuffer, Tile, X, Y)

        Update frame count
        i = i + 1
        lbli.Text = CStr(i)

        Draw BackBuffer to screen
        SystemMap.DrawImageUnscaled(BackBuffer.BackSurface, 0, 0)
    End Sub

There is actually more in there but most of it is commented out while I changed the TileImage Class to prevent conflicts.

Anyway, if anyone can spot where Im going wrong Id greatly appreciate the help - Im about ready to tear my hair out. Every time I run the application the page file use (in the task manager) starts to climb at a steady rate.
 
Last edited by a moderator:
Also noticed the image is no longer drawing now :S But the frames are still ticking along and the memory is still being eaten up.
 
First of all, instead of using + or & to join files, use System.IO.Path.Join. The Join function ensures that there is no confusion with doubled or missing path separators.
C#:
// (using System.IO)
Path.Join("C:\\Windows", "Microsoft.Net");
Path.Join("C:\\Windows\\", "Microsoft.Net");
Path.Join("C:\\Windows\\", "\\Microsoft.Net");
Path.Join("C:\\Windows", "\\Microsoft.Net");
// all four return "C:\\Windows\\Microsoft.Net"

Also, you will need to create and release the BackBuffers hDC each time you render. This is because of the way that a GDI+ hDC works. When you call GetHDC, it makes a buffer full of 100% transparent pixels and returns the hDC to which you can render your graphics, and when you release it, it drawns the buffer over the original image, hence you wont see your graphics until you release the DC.
 
Maloric said:
In response to Cags, the function is to draw sprites to the screen (well, to the backbuffer which is then blitted to the screen).

I realise what you are attempting todo. I was just saying that Im pretty sure you could do it at least 10x a second using the Graphics object (GDI+) and it would be far simpler.
 
You are probably right, Cags. A while back I started a movie maker project. The graphics engine implementation I was currently using was GDI+ based, rather than GDI or DirectX, and at resolutions of 640x480 I was able to get 60 fps. Of course, it also depends on the computer.
 
Well Im open to the idea of using the GDI+ but Im not sure how - I need something that can take a designated rectangular area from a bitmap file. Any tips?
 
Well using the Graphics object you can use the DrawImage() method, this has overrides for using part of a bitmap image, as well as for using a colour key range for transparency.
 
Well, not quite willing to give up on the code Id already written, I went through the process of commenting out every line and observing the memory usage with each line I uncommented. As a result, I have narrowed down the problem to the following two lines:

Code:
[B]oldObject = SelectObject(SourcehDC, Source.SourceBmp.GetHbitmap())[/B]

        Blt the image to the target
        TransparentBlt(targethDC, DestX, DestY, Source.TileWidth, Source.TileHeight, SourcehDC, X, Y, Source.TileWidth, Source.TileHeight, Source.KeyColor)

        Equivalent BitBlt (minus transparency) commented out
        BitBlt(targethDC, DestX, DestY, ImageType.TileWidth, ImageType.TileHeight, offscreenhDC, X, Y, SRCCOPY)

        Select our bitmap out of the dc and delete it
        [B]DeleteDC(SelectObject(SourcehDC, oldObject))[/B]

(the two lines in bold - in the sprite.draw function)

So for some reason the APIs own SelectObject function is causing the memory leak. With these two lines uncommented the page file usage creeps steadily up.

The following is my declaration of the SelectObject function:

Code:
Public Declare Function SelectObject Lib "gdi32" Alias "SelectObject" ( _
        ByVal hdc As IntPtr, _
        ByVal hObject As IntPtr) As IntPtr

Im assuming that declaration is correct, so the problem is probably caused through me not disposing something correctly to do with the oldobject. Any ideas?
 
Shouldnt you be using DeleteObject to release the object created by SelectObject? If you check the return value of DeleteDC it should be zero - any non-zero value indicates failure.
 
PlausiblyDamp said:
Shouldnt you be using DeleteObject to release the object created by SelectObject? If you check the return value of DeleteDC it should be zero - any non-zero value indicates failure.

I WANT YOUR BABIES!

Thank you, this worked like a dream! I had tried using the DeleteObject but it hadnt made any difference. I hadnt, however, tried it on the (SelectObject(SourcehDC, oldObject)) line, as to be honest I wasnt really sure on what it was doing. Youve saved me hours of frustration and for that Im exceptionally grateful.
 
Im not sure if this is really helpful; but here is my implimentation of a Sprite class using GDI+.


C#:
class Sprite
    {
        Bitmap TiledSprite;
        Graphics g;

        int iSpriteX = 0;
        int iSpriteY = 0;

        int iSpeedX = 0;
        int iSpeedY = 0;

        int iWidth = 64;
        int iHeight = 64;

        int iCurrentFrame = 0;
        int iFramesRow = 0;
        int iNumFrames = 0;

        Rectangle DrawableRange = new Rectangle(0,0,800,600);

        /// <summary>
        /// Creates a new sprite object.
        /// </summary>
        /// <param name="gfx">Reference to a graphics object</param>
        /// <param name="FilePath">Path to a Bitmap</param>
        /// <param name="TransColor">Color to make Transparent</param>
        /// <param name="iFrames">Number of frames in the target Bitmap</param>
        /// <param name="iCols">Number of columns in the target Bitmap</param>
        public Sprite(ref Graphics gfx, String FilePath, Color TransColor, int iFrames, int iCols)
        {
            g = gfx;

            TiledSprite = new Bitmap(FilePath);
            TiledSprite.MakeTransparent(TransColor);

            iCurrentFrame = 1;
            iFramesRow = iCols;
            iNumFrames = iFrames;
        }

        /// <summary>
        /// Increments the current frame, and resets to zero if the last frame has been reached.
        /// Creates forward animation.
        /// </summary>
        public void NextFrame()
        {
            if ((iCurrentFrame + 1) < (iNumFrames))
            {
                iCurrentFrame = iCurrentFrame + 1;
            }
            else
            {
                iCurrentFrame = 0;
            }
        }
        /// <summary>
        /// Decrements the current frame, and resets to the last frame if the first frame has been reached.
        /// Creates backward animation.
        /// </summary>
        public void PreviousFrame()
        {
            if ((iCurrentFrame - 1) > 0)
            {
                iCurrentFrame = iCurrentFrame - 1;
            }
            else
            {
                // We use -1 because the number of frames is a non-zero value
                // but in programming we start at 0 instead of one.
                iCurrentFrame = iNumFrames-1;
            }
        }
 
        /// <summary>
        /// Draw the sprite to the referenced graphics object assuming it is inside the DrawableRange.
        /// </summary>
        /// <returns>Returns a boolean indicating success or failure.</returns>
        public bool Render()
        {
            try
            {
                if (Rectangle.Intersect(DrawableRange, this.Bounds) != null)
                {
                    Rectangle srcRect = new Rectangle();

                    srcRect.X = (iCurrentFrame % iFramesRow) * iWidth;
                    srcRect.Y = (iCurrentFrame / iFramesRow) * iHeight;
                    srcRect.Width = iWidth;
                    srcRect.Height = iHeight;

                    g.DrawImage(TiledSprite, new Rectangle(Location, new Size(iWidth, iHeight)), srcRect, GraphicsUnit.Pixel);
                    return true;
                }
            }
            catch (Exception ex)  
            {
                Console.Write(ex.Message);
                return false;
            }
        }

        /// <summary>
        /// Moves sprite to specified location 
        /// </summary>
        /// <param name="pos">A Point Object that indicates where to draw the sprite</param>
        public void Move(Point pos)
        {
            iSpriteX = pos.X;
            iSpriteY = pos.Y;
        }

        /// <summary>
        /// Move the sprite at the current sprite speed X and Y
        /// </summary>
        public void Move()
        {
            Move(new Point(iSpriteX + iSpeedX, iSpriteY + iSpeedY));
        }

        /// <summary>
        /// Allow you to move the sprite at a different rate than the current rate, without actually changing the sprites speed x/y values
        /// </summary>
        /// <param name="AmountX">Number of pixels to move along the X-axis</param>
        /// <param name="AmountY">Number of pixels to move along the Y-axis</param>
        public void Move(int AmountX, int AmountY)
        {
            Move(new Point(iSpriteX + AmountX, iSpriteY + AmountY));
        }

        /// <summary>
        /// Allows you to move along a single axis a given number of pixels
        /// </summary>
        /// <param name="Amount">Number of pixels to move</param>
        /// <param name="Verticle">true value moves along the Y-axis, false moves along the X-axis</param>
        public void Move(int Amount, bool Verticle)
        {
            if (Verticle == true)
            {
                Move(new Point(iSpriteX, iSpriteY + Amount));
            }
            else
            {
                Move(new Point(iSpriteX + Amount, iSpriteY));
            }
        }

        /// <summary>
        /// Checks for a collision with another Sprite object
        /// </summary>
        /// <param name="OtherSprite">A sprite to check for a collision with</param>
        /// <returns>Returns a boolean value indicating if a collision has occured</returns>
        public bool CollisionWith(Sprite OtherSprite)
        {

            if (this.Bounds.IntersectsWith(OtherSprite.Bounds) == false)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        /// <summary>
        /// Not even really AI, just keeps the sprite on the viewable area of the screen
        /// </summary>
        public void StayInBounds()
        {
            Move();
            if(iSpriteX < 0 | (iSpriteX + iWidth) > DrawableRange.Width)
            {
                iSpeedX *= -1;
            }
            if (iSpriteY < 0 | (iSpriteY + iHeight) > DrawableRange.Height)
            {
                iSpeedY *= -1;
            }           
        }

        // Sprite Positionals
        public Point Location
        {
            get { return new Point(iSpriteX, iSpriteY); }
            set { iSpriteX = value.X; iSpriteY = value.Y; }
        }
        public int X
        {
            get { return iSpriteX; }
            set { iSpriteX = value; }
        }
        public int Y
        {
            get { return iSpriteY; }
            set { iSpriteY = value; }
        }

        // Sprite Size
        public Size Dementions
        {
            get { return new Size(iWidth, iHeight); }
            set { iWidth = value.Width; iHeight = value.Height; }
        }
        public int Width
        {
            get { return iWidth; }
            set { iWidth = value; }
        }
        public int Height
        {
            get { return iHeight; }
            set { iHeight = value; }
        }

        public Rectangle Bounds
        {
            get { return new Rectangle(iSpriteX, iSpriteY, iWidth, iHeight); }
            set { Location = value.Location; Dementions = value.Size;}
        }

        // Sprite Movements
        public int SpeedX
        {
            get { return iSpeedX; }
            set { iSpeedX = value; }
        }
        public int SpeedY
        {
            get { return iSpeedY; }
            set { iSpeedY = value; }
        }


        /// <summary>
        /// Represents the area in which sprites will be drawn in, if a sprite is outside of this rectangle it will not be drawn.
        /// </summary>
        public Rectangle DrawableArea
        {
            get { return DrawableRange; }
            set { DrawableRange = value; }
        }

    }

Hope this Helps
 
Back
Top