But the “Process Memory” part in the “Diagnostic Tools” panel in Visual Studio works weird.
As soon as my own GH_component “DrawSprite” is set to run = true and running, the memory quickly raise up from around 2.2 GB to 4.8 GB. And then every time I press “Alt + Tab” to swap the Rhino UI with VisualStudio UI, the momery will raise up another 200MB until the memory of my computer is not enough to use.
Here is my code:
using System;
using Grasshopper.Kernel;
using Rhino.Geometry;
using Rhino.Display;
using System.Drawing;
namespace Hani
{
public class DrawSprite : GH_Component
{
static public RhinoView rhinoView;
static public Bitmap bmp;
bool run;
public DrawSprite()
: base("DrawSprite", "DS",
"Draw the Bitmap onto the Viewport",
"Hani", "Viewport")
{
}
protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
{
pManager.AddGenericParameter("rhinoView", "rView", "The viewport to draw Bitmap on.", GH_ParamAccess.item);
pManager.AddGenericParameter("Bitmap", "B", "The Bitmap to draw.", GH_ParamAccess.item);
pManager.AddBooleanParameter("run", "r", "Run or not", GH_ParamAccess.item);
}
protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
{
}
protected override void SolveInstance(IGH_DataAccess DA)
{
if (!DA.GetData<RhinoView>(0, ref rhinoView) || !DA.GetData<Bitmap>(1, ref bmp) || !DA.GetData<bool>(2, ref run))
return;
var conduit = new MyConduit();
if (run)
{
conduit.Enabled = true;
}
else if (!run)
{
conduit.Enabled = false;
}
}
class MyConduit : DisplayConduit
{
protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e)
{
base.CalculateBoundingBox(e);
var bbox = new BoundingBox();
bbox.Union(e.Display.Viewport.ConstructionPlane().Origin);
e.IncludeBoundingBox(bbox);
}
protected override void PostDrawObjects(DrawEventArgs e)
{
base.PreDrawObjects(e);
if (e.Viewport.Name == DrawSprite.rhinoView.MainViewport.Name)
{
e.Display.EnableDepthWriting(false);
e.Display.EnableDepthTesting(false);
DisplayBitmap d_bmp = new DisplayBitmap(DrawSprite.bmp);
int width = DrawSprite.rhinoView.MainViewport.Bounds.Width;
int height = DrawSprite.rhinoView.MainViewport.Bounds.Height;
DisplayPipeline ppl = DrawSprite.rhinoView.DisplayPipeline;
ppl.DrawSprite(d_bmp, new Point2d(width / 2, height / 2), width, height);
DrawSprite.rhinoView.Redraw();
e.Display.EnableDepthWriting(false);
e.Display.EnableDepthTesting(false);
}
}
}
}
I’ve tried to explicitly dispose d_bmp at the end of my PostDrawObjects in the if-block.
Sadly, it doesn’t work.
It is indeed because of the Bitmap. If I scale down the Bitmap to 1/10. The memory is not raising so quickly but it still meets another problem.
Here is the exception throwed by VisualStudio:
System.Runtime.InteropServices.ExternalException
HResult=0x80004005
Message=A general error occurred in GDI+.
Source=System.Drawing
StackTrace:
at System.Drawing.Bitmap.GetHbitmap(Color background)
at System.Drawing.Bitmap.GetHbitmap()
at Rhino.Display.DisplayBitmap..ctor(Bitmap bitmap)
at Hani.Vision.MyConduit.PostDrawObjects(DrawEventArgs e) in C:\Users\77950\Desktop\Hani\DrawSprite.cs:line 125
at Rhino.Display.DisplayConduit.ExecConduit(IntPtr pPipeline, UInt32 conduitSerialNumber, UInt32 channel)
Perhaps in your DrawSprite set static public Bitmap bmp to null by default. Then in SolveInstance you could try checking if bmp is valid and dispose that before getting the new bitmap.
Are you feeding your component ever changing bitmaps, as in you update it multiple times per time unit?
You’ll have to at least make sure you instantiate your conduit only once per component. Currently you’re newing up one on every SolveInstance. Better to move that to the constructor and keep a reference to it in a member variable, toggling it on and off when needed.
Hi Nathan, I’ve moved the new MyConduit() to the constructor of my GH_Component. I think this is a good change to make! But it doesn’t work.
And I’m testing with an unchanging bitmap, so I think the component may run only once which should be no differences with keeping the new MyConduit() in the SovleInstance() for now.
And I’ve tried running this code with a breakpoint and manully step by step, the memory is not raising up in this condition. But as soon as I remove the breakpoint and hit “continue”, the memory raises up certainly.
@nathanletwory Hi Nathan, I think the reason of this problem is pretty clear in the topic referred by Menno!
Maybe you can change the third constructor of Rhino.Display.DisplayBitmap Class form private to public, then I can solve the problem by importing gdi32.dll and calling the DeleteObject() method.
Here is the third constructor of Rhino.Display.DisplayBitmap:
Or is there any other possible solution? Thank you for your support!
Oh, if that is a solution to the problem you could access the private constructor by reflection.
I had to do the same for AreaMassProperties, but that is another story.
DisplayBitmap dbmp = null;
IntPtr pBmp; // defined elsewhere
var privateConstructors = typeof(DisplayBitmap).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
if (privateConstructors.Any()) {
foreach(var ci in privateConstructors) {
var param = ci.GetParameters();
if (param.Length == 1 && param[0].ParameterType == typeof(IntPtr)) {
dbmp = (DisplayBitmap) ci.Invoke(new object[]{pBmp});
}
}
}
if (null != dbmp) {
// you have successfully used a private constructor :)
}
// category - dirty hack but it works
I’m not sure what happens when the dbmp object gets Disposed though, it looks like it will try to free the pointer, so you may want to keep an eye on that, preventing a double free.
Sorry for the confusion. I’m looking for actual project source that, that I can run here, that allows me to reproduce the issue. The ZIP file you’ve pointed me to just contains some binaries written by someone else.
I put the source code of the “test_assembly” project inside. And I also use “BitmapPlus” (a plugin written by others) to display the bitmap on the Grasshopper canvas.