Reputation: 41
I am trying to get more performance out of c# GDI+ DrawLines function. When I run a profiler on the the code I see that almost half of the time spend in the DrawLines function is preparing the point array to be send over to the native GDI+ dll. This seems like a big overhead and I am wondering if anyone could come up with a more efficient way interop-ing with the DrawLines function than the native implementation of the DrawLines function in System.Drawing
Here are the native Systemm.Drawing functions:
public void DrawLines(Pen pen, PointF[] points)
{
if (pen == null)
{
throw new ArgumentNullException("pen");
}
if (points == null)
{
throw new ArgumentNullException("points");
}
IntPtr handle = SafeNativeMethods.Gdip.ConvertPointToMemory(points);
int status = SafeNativeMethods.Gdip.GdipDrawLines(new HandleRef(this,this.NativeGraphics), new HandleRef(pen, pen.NativePen), new HandleRef(this, handle), points.Length);
Marshal.FreeHGlobal(handle);
this.CheckErrorStatus(status);
}
internal static IntPtr ConvertPointToMemory(PointF[] points)
{
if (points == null)
{
throw new ArgumentNullException("points");
}
int num2 = Marshal.SizeOf(new GPPOINTF().GetType());
int length = points.Length;
IntPtr ptr = Marshal.AllocHGlobal((int) (length * num2));
for (int i = 0; i < length; i++)
{
Marshal.StructureToPtr(new GPPOINTF(points[i]), (IntPtr) (((long) ptr) + (i * num2)), false);
}
return ptr;
}
Upvotes: 4
Views: 2485
Reputation: 117
This method "DrawLines" is somewhat unfortunate (and several other methods in the .NET-GDI+-API as well). First of all, it takes just an array, whcih means that the number of points must match the size of the array exactly; if you are not sure how many points you will have when preparing the points, you have to call Array.Resize which will copy the data. Secondly, the first action in the DrawLine-method is to copy the points - why not pass the original data to GDI+? In effect, the data gets copied twice before it gets to GDI+.
What can be done about it:
The first idea could look something like this:
public void DrawLines(System.Drawing.Color color, ref System.Drawing.Point[] points, int pointsCount)
{
IntPtr pen = IntPtr.Zero;
int status = GdipCreatePen1(
color.ToArgb(),
1,
(int)GraphicsUnit.World,
out pen);
unsafe
{
fixed (Point* pointerPoints = points)
{
status = GdipDrawLinesI(new HandleRef(this, this.handleGraphics), new HandleRef(this, pen), (IntPtr)pointerPoints, pointsCount);
}
}
status = GdipDeletePen(new HandleRef(this, pen));
}
[DllImport(GDIPlusDll, SetLastError = true, ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
private static extern int GdipDrawLinesI(HandleRef graphics, HandleRef pen, IntPtr points, int count);
[DllImport(GDIPlusDll, SetLastError = true, ExactSpelling = true)]
private static extern int GdipDeletePen(HandleRef pen);
[DllImport(GDIPlusDll, SetLastError = true, ExactSpelling = true)]
private static extern int GdipCreatePen1(int argb, float width, int unit, out IntPtr pen);
BTW - it is possible to get access to the native handles in the .NET-GDI+ objects by using reflection (of course, this is undocumented, you need to access a private method). For the System.Drawing.Font object this would look something like this:
Type type = typeof(System.Drawing.Font);
System.Reflection.PropertyInfo propInfoNativeFontHandle = type.GetProperty("NativeFont", System.Reflection.BindingFlags.GetProperty | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
System.Drawing.Font font = ...
IntPtr nativeHandle = propInfoNativeFontHandle.GetValue(font, null)
Upvotes: 4
Reputation: 21722
If you are trying to improve performance of 2D drawing operations in C# on Windows you should consider Direct 2D#.
Upvotes: 0