3
\$\begingroup\$

I've created a program that uses System.Graphics to render a simple (small) 2D scene to the screen. The problem is, for some reason, it starts to quickly accumulate memory. In fact, it goes from only 100,000K Mem to ten times that and then crashes. As expected, the frame-rate of the program drops dramatically as well (100+ to 1fps).

I believe the culprit is in my rendering loop. Problem is, as hard as I search, besides a couple bits of messy code, I can't find anything relevant to the problem.

It may not be inside of this code snippet - if not, that's ok. I'll just continue my search personally through my application.

Here is my Engine.vb source:

Public Class Engine

    Public debugEnabled As Boolean = False
    Private _currentMap
    Public Property currentMap As Map
        Get
            Return _currentMap
        End Get
        Set(ByVal value As Map)
            _currentMap = value
            luaEngine.Close()
            cTime.Reset()
            sprites.Clear()
            cTime.Start()
            luaEngine = New Lua
        End Set
    End Property

    Public currentData As MemoryGrid
    Public currentInstance As MemoryGrid
    Public fForm As Control

    Public cTime As New Stopwatch

    Public buffer As Bitmap

    Friend Shared luaEngine As New Lua

    Public sprites As New Dictionary(Of String, Bitmap)

    #Region "EVENTS"

            Public Event event_keydown(ByVal key As String)
            Public Event event_keyup(ByVal key As String)

    #End Region

    Public Sub Render()
        Dim fpsTimer As New Stopwatch
        fpsTimer.Start()
        If fForm IsNot Nothing Then
            If buffer IsNot Nothing Then
                Dim e As Graphics = fForm.CreateGraphics()
                e.InterpolationMode = Drawing2D.InterpolationMode.NearestNeighbor
                e.DrawImage(buffer, New Rectangle(0, 0, fForm.Width, fForm.Height))
                buffer.Dispose()
            End If
            buffer = New Bitmap(fForm.Width, fForm.Height)
            Using gfx As Graphics = Graphics.FromImage(buffer)
                gfx.FillRectangle(Brushes.Black, 0, 0, fForm.Width, fForm.Height)
                If currentMap IsNot Nothing Then
                    For z As Integer = 0 To Grid.LAYERLIMIT
                        For x As Integer = currentMap.DisplayXOffset To currentMap.DisplayXOffset + Math.Ceiling(fForm.Width / 8)
                            For y As Integer = currentMap.DisplayYOffset To currentMap.DisplayYOffset + Math.Ceiling(fForm.Height / 8)
                                Dim terrObj As GridElement = currentMap.Level.getCell(z, x, y)
                                If terrObj IsNot Nothing Then
                                    Dim _position As Point
                                    Dim _size As Size
                                    terrObj.tileImage(Me, _position, _size)
                                    Dim cSprites As Bitmap = Nothing : sprites.TryGetValue(terrObj.SpriteSheet & ".png", cSprites)
                                    gfx.DrawImage(cSprites, (x - currentMap.DisplayXOffset) * 8 + terrObj.XOffset, (y - currentMap.DisplayYOffset) * 8 + terrObj.YOffset, New RectangleF(_position.X, _position.Y, _size.Width, _size.Height), Drawing.GraphicsUnit.Pixel)
                                End If
                            Next
                        Next
                    Next
                End If
                gfx.DrawString("FPS: " & Math.Round(1000 / fpsTimer.ElapsedMilliseconds), New Font("Arial", 7), Brushes.Red, New PointF(5, 5))
                fpsTimer.Stop()
            End Using
        End If
    End Sub

'...

There are a couple more procedures after this, but, they are related to my Lua implementation. The problem has existed since before I implemented Lua.

Basically, the variables are at the top, I also have my Sub Render() at the bottom. If you need any of the other code, I'll make sure to post it. Just say so in the comments.

REMEMBER: I'm not 100% sure that the issue lies in this one class (or in this class at all). If you can't find a reason, so be it. Anything to speed it up (if perhaps my messy programming has slowed it down that bad) would also be appreciated.

EDIT 1

Please take a look at the following screenshot of me using ANTS Memory Profiler:

enter image description here

I have a multidimensional array that contains instances of a class called 'GridElement'. The array is 3D. I use it to layer and get elements from specific locations to display. According to this though, it says it takes of like 94% of my application's memory, and for the most part, it's filled with empty slots. The highest number of elements I've put into it is like 20-30 (which is a fraction of what it needs to be able to handle).

So basically, how is this empty 3D array taking up so much space when it has only a couple of elements in it?

\$\endgroup\$
3
  • \$\begingroup\$ So you chose my response as correct, but I'm curious; what was the ultimate cause? \$\endgroup\$ – Ed S. Sep 29 '11 at 19:19
  • \$\begingroup\$ @EdS. The problem wasn't actually directly inside of the information above. Using the ANT program and some simple tools (basically, elimination via commenting out code and running), I determined that it was the line running terrObj.tileImage(Me, _position, _size). I've figure out why thanks to you. It will just be a while before I get a non-hacked up method for getting it to work. Thanks a ton :) \$\endgroup\$ – Freesnöw Sep 29 '11 at 20:21
  • \$\begingroup\$ Just to make sure: is your code working now? This post is still receiving close votes, but it'll stop if the code does work. \$\endgroup\$ – Jamal Nov 14 '13 at 4:16
5
\$\begingroup\$

First off; Use a good memory profiler. If you can reason about why something like this is happening quickly then fine. Otherwise you are wasting time when there are plenty of great tools out there which can make your life a whole lot easier. I personally use RedGate's Profiler for .NET apps (I have no affiliation with them, I just think it's really good and it has a 14 day free trial, fully featured).

Now, onto your code...

You are creating Graphics objects without disposing them in one area. It should be:

Using e As Graphics = fForm.CreateGraphics()
  e.InterpolationMode = Drawing2D.InterpolationMode.NearestNeighbor
  e.DrawImage(buffer, New Rectangle(0, 0, fForm.Width, fForm.Height))
  buffer.Dispose()
End Using

Next, you have some code which is really not optimal in a tight render loop. For example, using TryGetValue is a bad idea as it will throw a (handled) exception if the value is not found. You really don't want to be throwing exceptions in that area of code. Design it such that you can assume the value exists.

You are also creating a lot of Bitmaps. If that is called in a tight loop then you may not be allowing enough time for the GC to come in an clean up after you. Also, the memory is likely allocated on the LOH (Large Object Heap), so you may have a lot of fragmentation going on. Why not use one Bitmap and be done with it? You don't need to create a new one each time if the width and height is constant, which it really should be for a game. Just draw over the same frame buffer in each call to Render.

Again though, go use a decent profiler. It will tell you where your memory is allocated and it will also alert you to LOH fragmentation, as well as IDisposable objects that you are not calling Dispose() on.

EDIT: You are reading your profiling results incorrectly. It is telling you that 784MB (the vast majority of the memory allocated to your process) is unmanaged, i.e., probably Bitamps. Look for IDisposables that you are not disposing of. Have you tried using a single screen buffer instead of creating one every frame like I suggested?

You also have ~800MB allocated to the runtime that is not being used. You need to continue with the profiler and analyze the results. That is just the first page, they have tutorials which will lead you through the rest of it.

\$\endgroup\$
4
  • \$\begingroup\$ Could you please take a look at my edit please? \$\endgroup\$ – Freesnöw Sep 28 '11 at 13:45
  • \$\begingroup\$ Dictionary.TryGetValue() doesn't throw. It's like Int32.TryParse - the idea is specifically to avoid throwing an exception, by returning False if the operation fails. \$\endgroup\$ – Mathieu Guindon Nov 14 '13 at 1:15
  • 1
    \$\begingroup\$ @retailcoder: You didn't read closely enough. An exception is thrown, and then handles, and then false is returned. That doesn't mean that the exception was never thrown. In a tight loop you want to avoid any exceptions being thrown \$\endgroup\$ – Ed S. Nov 14 '13 at 1:24
  • \$\begingroup\$ You're right, I did skim through. Downvote removed :) \$\endgroup\$ – Mathieu Guindon Nov 14 '13 at 1:26

Not the answer you're looking for? Browse other questions tagged or ask your own question.