jbhunter's picture

Garbage Collection issue on Tesselation

I am using a Tesselate class found on this forum back from 2009, code from Michael Foetsch. I have had little problems getting my project in line and doing a little testing. My project is basically a paintbrush type application. I have a circle/polygon (my brush) that I am moving along a path in discrete intervals and subtracting the intersection with a base polygon. I then pass the vertices of the base polygon to the Tesselate class. I am using C# Clipper project which by the way works great to find the intersection and also find the result of the subtraction. I then tesselate the subtraction and pass the vertices to the Tesselate class. The code for the tesselate class is below. After getting some bugs out of the way I am able to get 2000+ iterations into my paint/boolean operations on my base polygon, but now I have a problem!

My issue is I am getting an exception "CallbackOnCollectedDelegate". I am not much of a expert on using unmanaged resources in C#, much of the reason I really love OpenTK! It seems to me that the Marshalling of the properties (coordinates, vertexData, etc) would take care of the garbage collection issues. Is the garbage collector removing a reference to one of the function delegates? How can I handle this? Can I somehow us GCHandle to pin a reference?

I am getting an exception on
Glu.TessEndPolygon(tess);

The exception states:
A callback was made on a garbage collected delegate of type 'AdaptivePaintDemo!AdaptivePaintDemo.Tesselate+BeginCallbackDelegate::Invoke'. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.

using System;
using System.Collections.Generic;
using System.Xml;
 //for triangulating paths with the GLU tesselator
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Drawing;
using OpenTK.Graphics;
 
namespace AdaptivePaintDemo
{
 
    //
    //This class use the OpenGL GLU tessellation
    //functions to triangulate a simple polygon.
    //
    //This code is based largely on Michael Foetsch
    //triangulation tutorial that can be found, as of
    //February 2, 2008, at http://www.geocities.com/foetsch/articles.htm.
    //
    //It interfaces with OpenGL through OpenTK.
    //
 
    static public class Tesselate
    {
        // Some of this code is lifted from the OpenTK tesselation sample:
        // Define the signatures for the callback functions, and declare the callbacks.
        delegate void BeginCallbackDelegate(int mode);
        delegate void EndCallbackDelegate();
        delegate void VertexCallbackDelegate(IntPtr v);
        delegate void EdgeFlagCallbackDelegate(int flag);
        delegate void ErrorCallbackDelegate(int code);
        unsafe delegate void CombineCallbackDelegate(
            [MarshalAs(UnmanagedType.LPArray, SizeConst = 3)]double[] coordinates,
            [MarshalAs(UnmanagedType.LPArray, SizeConst = 4)]double*[] vertexData,
            [MarshalAs(UnmanagedType.LPArray, SizeConst = 4)]double[] weight,
            double** dataOut);
 
        static OpenTK.GLControl glControl = null;
 
        // Triangulate
        // "Input" is a list of Vector2's that hold the point data to be triangulated.
        //(Vector2 is not shown but is a structure that has two floating points members X and Y).
        // "VertsPerContour" is an array that determines where to break up the contours in the
        //list of points in Input. VertsPerContour must sum to the number of entries in Input.
        //
        //Note: Outer contours must be given in counter-clockwise order while inner contours
        //      (holes) must be given in clock-wise order.
        //
        //Returns true on successfull triangulation, false on failure or error
        //
        unsafe static public bool Triangulate(PointF[] Input, int[] VertsPerContour)
        {
            InvalidPolygon = false;
            Indices = new List<int>();
 
            IntPtr tess = IntPtr.Zero;
 
            // create tesselation object
            if (glControl == null)
            {
                glControl = new OpenTK.GLControl();
            }
 
            tess = Glu.NewTess();
 
            // register tesselation callback
 
            Glu.TessCallback(tess, OpenTK.Graphics.TessCallback.TessBegin, new BeginCallbackDelegate(BeginCallback));
            Glu.TessCallback(tess, OpenTK.Graphics.TessCallback.TessEdgeFlag, new EdgeFlagCallbackDelegate(EdgeCallback));
            Glu.TessCallback(tess, OpenTK.Graphics.TessCallback.TessVertex, new VertexCallbackDelegate(VertexCallback));
            Glu.TessCallback(tess, OpenTK.Graphics.TessCallback.TessEnd, new EndCallbackDelegate(EndCallback));
            Glu.TessCallback(tess, OpenTK.Graphics.TessCallback.TessCombine, new CombineCallbackDelegate(CombineCallback));
            Glu.TessCallback(tess, OpenTK.Graphics.TessCallback.TessError, new ErrorCallbackDelegate(ErrorCallback));
 
            //copy input into a linear array of floats
            float[] Vertices = new float[3 * Input.Length];
            for (int v = 0; v < Input.Length; v++)
            {
                Vertices[v * 3 + 0] = Input[v].X;
                Vertices[v * 3 + 1] = Input[v].Y;
                Vertices[v * 3 + 2] = 0.0f;
            }
 
            // begin polygon
            Glu.TessBeginPolygon(tess, IntPtr.Zero);
 
            // go through the contours
            int CurrentContour = 0;
            int VertsThisContour = 0;
 
            for (int v = 0; v < Input.Length; v++)
            {
                if (v == 0)
                    Glu.TessBeginContour(tess);
 
                // pass the corresponding vertex to the tesselator object
                double[] VertsToPass = new double[3];
                VertsToPass[0] = Vertices[v * 3 + 0];
                VertsToPass[1] = Vertices[v * 3 + 1];
                VertsToPass[2] = Vertices[v * 3 + 2];
                Glu.TessVertex(tess, VertsToPass, v);
                if (InvalidPolygon)
                    break;
 
                VertsThisContour++;
 
                if (VertsThisContour >= VertsPerContour[CurrentContour])
                {
                    VertsThisContour = 0;
                    Glu.TessEndContour(tess);
 
                    CurrentContour++;
 
                    if (CurrentContour < (long)VertsPerContour.Length)
                    {
                        Glu.TessBeginContour(tess);
                    }
                }
            }
 
            if (InvalidPolygon)
            {
                // destroy the tesselation object
                Glu.DeleteTess(tess);
                tess = IntPtr.Zero;
 
                return false; //error in polygon definition
            }
            else
            {
                // end polygon
                //Glu.TessEndPolygon(tessHandle.AddrOfPinnedObject());
                Glu.TessEndPolygon(tess);
 
                // destroy the tessellation object
                Glu.DeleteTess(tess);
                tess = IntPtr.Zero;
 
                //The Indices object is now valid.
                return true;
            }
        }
 
        //After a successful call to Triangulate, this public list holds the indices
        //for the given input.
        public static List<int> Indices;
        static bool InvalidPolygon;
 
        //GLU callback functions
        static void BeginCallback(int type)
        {
        }
 
        static void EdgeCallback(int flag)
        {
        }
 
        static void VertexCallback(IntPtr vertexIndex)
        {
            unsafe
            {
                Indices.Add(*((int*)vertexIndex));
            }
        }
 
        static void EndCallback()
        {
        }
 
        unsafe static void CombineCallback(double[] coords, double*[] vertexData, double[] weight, double** outData)
        {
            //This means the polygon is self-intersecting.
            //See the OpenTK tesselation example for a working version of the
            //combine callback.
            InvalidPolygon = true;
        }
 
        static void ErrorCallback(int errno)
        {
            //some error ocurred, mark this triangulation as invalid
            InvalidPolygon = true;
        }
    }
}

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

The problem lies in these lines:

            Glu.TessCallback(tess, OpenTK.Graphics.TessCallback.TessBegin, new BeginCallbackDelegate(BeginCallback));
            Glu.TessCallback(tess, OpenTK.Graphics.TessCallback.TessEdgeFlag, new EdgeFlagCallbackDelegate(EdgeCallback));
            Glu.TessCallback(tess, OpenTK.Graphics.TessCallback.TessVertex, new VertexCallbackDelegate(VertexCallback));
            Glu.TessCallback(tess, OpenTK.Graphics.TessCallback.TessEnd, new EndCallbackDelegate(EndCallback));
            Glu.TessCallback(tess, OpenTK.Graphics.TessCallback.TessCombine, new CombineCallbackDelegate(CombineCallback));
            Glu.TessCallback(tess, OpenTK.Graphics.TessCallback.TessError, new ErrorCallbackDelegate(ErrorCallback));

Quoting MSDN on callbackOnCollectedDelegate MDA :

Quote:

Once a delegate has been marshaled out as an unmanaged function pointer, the garbage collector cannot track its lifetime. Instead, your code must keep a reference to the delegate for the lifetime of the unmanaged function pointer.

The solution is to store the delegates into fields for as long as you are using them.