Memory leak in DrawSprite?

Hi All,
I have tried to use the DrawSprite method from the C# API but it generates a memory laak and I can’t figure our why… The example from RhinoCommon works like a charm, and mine don’t. The code is below:

public class TestSprite : Command
    {
        public TestSprite()
        {
            // Rhino only creates one instance of each command class defined in a
            // plug-in, so it is safe to store a refence in a static property.
            Instance = this;
        }

        ///<summary>The only instance of this command.</summary>
        public static TestSprite Instance
        {
            get; private set;
        }

        ///<returns>The command name as it appears on the Rhino command line.</returns>
        public override string EnglishName
        {
            get { return "DrawRectangle"; }
        }

        protected override Result RunCommand(RhinoDoc doc, RunMode mode)
        {
            return Test.SpriteDrawing(doc);
        }
    }

    public class Test
    {
        static float m_sprite_size = 200;

        public static Rhino.Commands.Result SpriteDrawing(RhinoDoc doc)
        {
            var size_option = new Rhino.Input.Custom.OptionDouble(m_sprite_size);
            var go = new Rhino.Input.Custom.GetOption();
            go.SetCommandPrompt("Sprite drawing mode");
            go.AddOptionDouble("Size", ref size_option);

            Rhino.Display.DisplayPipeline.PostDrawObjects += DisplayRectangle;

            doc.Views.Redraw();
            while (go.Get() == Rhino.Input.GetResult.Option)
            {
                m_sprite_size = (float)size_option.CurrentValue;
                doc.Views.Redraw();
            }

            Rhino.Display.DisplayPipeline.PostDrawObjects -= DisplayRectangle;
            return Rhino.Commands.Result.Success;
        }

        static Rhino.Display.DisplayBitmap mm;
        static void DisplayRectangle(object sender, Rhino.Display.DrawEventArgs e)
        {
            Bitmap bit = new Bitmap((int)m_sprite_size, (int)m_sprite_size);
            using (Graphics graphics = Graphics.FromImage(bit))
            {
                // Main frame
                using (Pen pen = new Pen(Color.Black))
                {
                    graphics.FillRectangle(Brushes.White, 0, 0, m_sprite_size, m_sprite_size);
                    graphics.DrawRectangle(pen, 0, 0, m_sprite_size, m_sprite_size);
                }
            }

            // Display
            RectangleF bounds = e.Viewport.Bounds;
            mm = new Rhino.Display.DisplayBitmap(bit);
            e.Display.DrawSprite(mm, new Point2d(bounds.Width/2 + m_sprite_size / 2, bounds.Height/2 + m_sprite_size/2), m_sprite_size);
            bit.Dispose();
        }
    }

Does anyone has an idea about what’s going on?
Thanks!

I think what is going on is that you’re not disposing the DisplayBitmap:

RectangleF bounds = e.Viewport.Bounds;
mm = new Rhino.Display.DisplayBitmap(bit);
e.Display.DrawSprite(mm, new Point2d(bounds.Width/2 + m_sprite_size / 2, bounds.Height/2 + m_sprite_size/2), m_sprite_size);
bit.Dispose();
mm.Dispose(); // add dispose statement here!

Hi,
Thanks for your help, I have actually tried that but does not help… And if you look at the example McNeel provides:
http://developer.rhino3d.com/samples/rhinocommon/spritedrawing/
it is how I did it andit works… Very confusing.
I have also tried declaring the DisplayBitmap within my DisplayRectangle method but does not change anything…

Revisting this old thread… I’ve been having the same issues. I followed the McNeel example, but it also seems to have the same memory leak problem when you use a ‘FileSprite’. The issue still seems to occur when creating new DisplayBitmaps. I’ve also tried disposing the displaybitmaps without success.

I am having a similar issue, when trying to make a 2D sketch overlay. I get a memory leak. I have tried creating a new Bitmap, Graphics, and DisplayBitmap, calling e.Display.DrawBitmap, and then calling Dispose on the Bitmap, Graphics, and DisplayBitmap objects, but this still does not fix the problem. I currently think that the problem is with the e.Display.DrawBitmap method itself when inputting new bitmaps to it.

   public class DrawBitmapConduit : Rhino.Display.DisplayConduit
{
    private DisplayBitmap m_display_bitmap;
    public Point2d DrawPt { private get; set; }
    public Point2d DrawPtPrev { private get; set; }
    public int Width { private get; set; }
    public int Height { private get; set; }
    
    Pen colorPen = new Pen(Brushes.Blue, 10);

    protected Bitmap flag;
    protected Graphics drawGraphics;

    public DrawBitmapConduit( RhinoDoc doc)
    {

        var active_viewport = doc.Views.ActiveView.ActiveViewport;

        Width = active_viewport.Size.Width;
        Height = active_viewport.Size.Height;

        DrawPt = new Point2d(0, 0);
        DrawPtPrev = new Point2d(0, 0);
        
        flag = new System.Drawing.Bitmap(Width, Height);
        drawGraphics = Graphics.FromImage(flag);
    }

    protected override void DrawForeground(Rhino.Display.DrawEventArgs e)
    {
        
        drawGraphics.FillEllipse(Brushes.Blue, (float)DrawPt.X-5, (float)DrawPt.Y-5, 10, 10);
        PointF tmpPt1 = new PointF((float)DrawPtPrev.X, (float)DrawPtPrev.Y);
        PointF tmpPt2 = new PointF((float)DrawPt.X,(float)DrawPt.Y);
        drawGraphics.DrawLine(colorPen, tmpPt1, tmpPt2);
        
        m_display_bitmap = new DisplayBitmap(flag);

        e.Display.DrawBitmap(m_display_bitmap, 0, 0);

        DrawPtPrev = DrawPt;
    }
}

You are creating a new DisplayBitmap every frame. Either create it in your class constructor or check to see if m_display_bitmap is null and only create one when it is null

Thanks for the quick response! Because this is a interactive sketching application, I need to be able to modify the underlying bitmap image of m_display_bitmap. Is there any way to update the underlying bitmap? so I won’t have to create a new one every frame?

You can create a new instance. Just make sure to call Dispose() on the old instance first.

I tried adding the dispose method right before the call, but still no luck. I tried it after as well, but no luck.

...    
if (m_display_bitmap != null) m_display_bitmap.Dispose();
m_display_bitmap = new DisplayBitmap(flag);
...

@dale
I had the same problem

protected override void DrawForeground(Rhino.Display.DrawEventArgs e)
        {
            if (e.Viewport.Name != "Perspective")
            {
                return;
            }
            if (Polyline == null)
            {
                return;
            }
            bitmap = bitmap ?? new Bitmap(e.Viewport.Bounds.Width, e.Viewport.Bounds.Height);
            using (var g = Graphics.FromImage(bitmap))
            {
                g.Clear(Color.Transparent);
                var points = Polyline.Select(x => e.Viewport.WorldToClient(x)).Select(x => new System.Drawing.Point((int)x.X, (int)x.Y)).ToArray();
                //IsClosed=true的 Polyline 中最后一个点和第一个点是相同的,但 GDI 中这样的点集是无法绘制的,所以丢掉最后一个点
                g.FillPolygon(brush, points.Take(points.Length - 1).ToArray());
                g.Flush();
                using (var b = new DisplayBitmap(bitmap))
                {
                    e.Display.DrawBitmap(b, 0, 0);
                }
            }
        }

Maybe you should (also) dispose of your bitmap?

using (var bitmap = new Bitmap(e.Viewport.Bounds.Width, e.Viewport.Bounds.Height))
{
...
}

– Dale

@dale
I did this at the beginning, and several usings were nested, but in addition to the less smooth operation, the memory consumption was still there.

The writing method I mentioned above is to keep the bitmap resident in memory, but only this one Bitmap will be created, and it will not be created frequently.

As soon as I comment out this code, the memory problem is solved immediately

using (var b = new DisplayBitmap(bitmap))
                {
                    e.Display.DrawBitmap(b, 0, 0);
                }
1 Like

@dale
I found the cause of the problem

DisplayBitmap source code
image

My test code
image

Memory leak still exists

I will try to temporarily solve the problem by forcing the call with reflection

3 Likes

RH-68639 is fixed in Rhino 7 Service Release 22

2 Likes

Hello, I am currently running into a memory leak when using DisplayBitmap. I have narrowed down the issue to the creation of new DisplayBitmaps. I have tried calling Dispose() like the other forum members above, but still see a memory leak. You say the problem has been fixed in 7.22, but I am using 7.27 and viewing the diassembled .dll in Visual Studio shows that the DisplayBitmap class is still not calling GDI’s DeleteObject() function (as decribed Bitmap.GetHbitmap Method (System.Drawing) | Microsoft Learn) and also noted in c# - WPF CreateBitmapSourceFromHBitmap() memory leak - Stack Overflow. It seems like this is a mistake as the official documentation says delete must be called, RhinoCommon isn’t calling delete (unless it is doing it somewhere we can’t see), and we get a memory leak. Here is an example of test code that will create a memory leak when it shouldn’t (we are calling this function many times). My understanding is that this shouldn’t create a memory leak.

Bitmap bitmap = new Bitmap(e.Width, e.Height, e.Width * 4, System.Drawing.Imaging.PixelFormat.Format32bppArgb, bufferHandle);
var dp = new DisplayBitmap(bitmap);
bitmap.Dispose();
dp.Dispose();

Getting rid of the “var dp = new DisplayBitmap(bitmap);” line (and corresponding dp.Dispose()) fixes the memory leak.

1 Like

Were you ever able to fix this? Thank you for helping direct me towards the cause of the bug too

DeleteObject is being called in some C++ code that you can’t see. I’ll get this on the bug list to try and repeat what you are seeing.
https://mcneel.myjetbrains.com/youtrack/issue/RH-74126/Memory-leak-in-DrawSprite

Thank you for doing that! If it helps, we are calling that code around ~60fps for repro purposes and seeing my whole 32GB of RAM fill up within a minute or two and crashes with:

[ERROR] FATAL UNHANDLED EXCEPTION: System.Runtime.InteropServices.ExternalException (0x80004005): A generic error occurred in GDI+.
   at System.Drawing.Bitmap.GetHbitmap(Color background)
   at System.Drawing.Bitmap.GetHbitmap()
   at Rhino.Display.DisplayBitmap..ctor(Bitmap bitmap)
   at Plugin.Rhino.UI.CefSharpManager.onBrowserPaint(Object sender, OnPaintEventArgs e)
   at CefSharp.OffScreen.ChromiumWebBrowser.CefSharp.Internals.IRenderWebBrowser.OnPaint(PaintElementType type, Rect dirtyRect, IntPtr buffer, Int32 width, Int32 height)
   at CefSharp.Internals.RenderClientAdapter.OnPaint(RenderClientAdapter* , scoped_refptr<CefBrowser>* browser, cef_paint_element_type_t type, vector<CefRect\,std::allocator<CefRect> >* dirtyRects, Void* buffer, Int32 width, Int32 height) in C:\projects\cefsharp\CefSharp.Core.Runtime\Internals\RenderClientAdapter.h:line 163
[END ERROR]

Wanted to update this thread with my workaround for this memory leak. I was able to make a C++ module to make a custom CRhinoDisplayConduit and expose a SetBitMap function that I call from the C# side and give it an IntPtr I get from CefSharp. With this strategy I no longer have a memory leak as I manage the C++ bitmap myself.

3 Likes

RH-74126 is fixed in Rhino 8 Service Release 14