Maloric Posted March 4, 2006 Posted March 4, 2006 I'm 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. 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. 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 didn't suffer from the slowdown. I used offscreen.dispose() but that doesn't seem to make any difference. Any just before anyone suggests it - no, it's not the dreaded TransparentBlt memory leak as I'm using XP and BitBlt also has this problem (it's commented out in there somewhere). Any help would be greatly appreciated. Jamie. Quote
Maloric Posted March 4, 2006 Author Posted March 4, 2006 I think it's 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 wouldn't explode. Quote
Leaders snarfblam Posted March 5, 2006 Leaders Posted March 5, 2006 There are memory leak issues associated with TransparentBlt, although whether or not these are specific to certain versions of Windows, I'm 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. Quote [sIGPIC]e[/sIGPIC]
Maloric Posted March 5, 2006 Author Posted March 5, 2006 There are memory leak issues associated with TransparentBlt' date=' although whether or not these are specific to certain versions of Windows, I'm 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.[/quote'] I know I'm creating a bitmap for every iteration of this function, but I don't know how else to do it. I can't 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 I'm not disposing the bitmap properly but I don't know how else to do it. Quote
Cags Posted March 5, 2006 Posted March 5, 2006 Just out of interest what is the method for. I've 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? Quote Anybody looking for a graduate programmer (Midlands, England)?
Leaders snarfblam Posted March 5, 2006 Leaders Posted March 5, 2006 GDI tends to execute a little faster than GDI+. GDI+ 2.0 is faster than 1.x, but still not as fast as GDI. I'm 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. Quote [sIGPIC]e[/sIGPIC]
Maloric Posted March 5, 2006 Author Posted March 5, 2006 GDI tends to execute a little faster than GDI+. GDI+ 2.0 is faster than 1.x, but still not as fast as GDI. I'm 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 isn't 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. I'll try that now and repost my results. Quote
Maloric Posted March 5, 2006 Author Posted March 5, 2006 (edited) Well I tried delcaring the bitmap in the TileImage class but to no avail. Here is the code as it looks now: 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: 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: 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: 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 I'm going wrong I'd greatly appreciate the help - I'm 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. Edited March 5, 2006 by Maloric Quote
Maloric Posted March 5, 2006 Author Posted March 5, 2006 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. Quote
Leaders snarfblam Posted March 5, 2006 Leaders Posted March 5, 2006 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. // (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 won't see your graphics until you release the DC. Quote [sIGPIC]e[/sIGPIC]
Cags Posted March 5, 2006 Posted March 5, 2006 In response to Cags' date=' the function is to draw sprites to the screen (well, to the backbuffer which is then blitted to the screen).[/quote'] I realise what you are attempting todo. I was just saying that I'm pretty sure you could do it at least 10x a second using the Graphics object (GDI+) and it would be far simpler. Quote Anybody looking for a graduate programmer (Midlands, England)?
Leaders snarfblam Posted March 5, 2006 Leaders Posted March 5, 2006 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. Quote [sIGPIC]e[/sIGPIC]
Maloric Posted March 5, 2006 Author Posted March 5, 2006 Well I'm open to the idea of using the GDI+ but I'm not sure how - I need something that can take a designated rectangular area from a bitmap file. Any tips? Quote
Cags Posted March 5, 2006 Posted March 5, 2006 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. Quote Anybody looking for a graduate programmer (Midlands, England)?
Maloric Posted March 6, 2006 Author Posted March 6, 2006 Well, not quite willing to give up on the code I'd 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: [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 API's 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: Public Declare Function SelectObject Lib "gdi32" Alias "SelectObject" ( _ ByVal hdc As IntPtr, _ ByVal hObject As IntPtr) As IntPtr I'm assuming that declaration is correct, so the problem is probably caused through me not disposing something correctly to do with the oldobject. Any ideas? Quote
Administrators PlausiblyDamp Posted March 6, 2006 Administrators Posted March 6, 2006 Shouldn't 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. Quote Posting Guidelines FAQ Post Formatting Intellectuals solve problems; geniuses prevent them. -- Albert Einstein
Maloric Posted March 6, 2006 Author Posted March 6, 2006 Shouldn't 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 hadn't made any difference. I hadn't, however, tried it on the (SelectObject(SourcehDC, oldObject)) line, as to be honest I wasn't really sure on what it was doing. You've saved me hours of frustration and for that I'm exceptionally grateful. Quote
Nate Bross Posted March 6, 2006 Posted March 6, 2006 I'm not sure if this is really helpful; but here is my implimentation of a Sprite class using GDI+. 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 Quote ~Nate� ___________________________________________ Please use the [vb]/[cs] tags on posted code. Please post solutions you find somewhere else. Follow me on Twitter here.
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.