Fast lines in DirectDraw
2009-03-30 | Filed Under Software development |
When building applications that include a direct-manipulation user interface, I tend to go with DirectDraw, as this API provides a seemingly simple and efficient way of implementing interactive graphical surfaces. However, as this article demonstrates, simplicity isn’t always necessarily a good thing.
Consider the following code, which aims to insert a collection of lines (each consisting of a collection of individual nodes) into a DirectDraw surface:
public void DrawSomeLines(Surface S, Point[][] pts) {
foreach (Point[] line in pts) {
for (int i = 1; i < line.Length; i++) {
S.DrawLine(line[i - 1].X, line[i - 1].Y, line[i].X, line[i].Y);
}
}
}
While this code is compact and self-documenting, it has one teeny tiny downside: it is slow. Really, really slow. Almost glacial. This isn’t a big deal when only drawing a few lines, but once hundreds or even thousands of lines have to be drawn onto a surface, an entire application can come to a grinding halt.
There is a surprisingly simple reason for this: the above code calls the DrawLine method once for every single segment of each individual line. Each such call does three things: the surface’s memory is locked, the line segment is added to the surface, and then the memory is unlocked again. This locking is performed so that different threads updating a single Surface object do not get in each others’ way. And it is precisely this rapid locking and unlocking of potentially large blocks of memory that requires a lot of time, thus making this approach to drawing lines excruciatingly slow.
Clearly, what is needed is a way to lock the surface just once, draw all lines in one fell swoop, and then unlock the surface again. While the DirectDraw SDK provides no way to do this, GDI+ does. We can thus replace our original DrawSomeLines method with this much faster version:
public void DrawSomeLines(Surface S, Point[][] pts) {
IntPtr dc = S.GetDc();
try {
foreach (Point[] line in pts) {
if (line.Length >= 2) {
MoveToEx(dc, line[0].X, line[0].Y, IntPtr.Zero);
for (int i = 1; i < line.Length; i++) {
LineTo(dc, line[i].X, line[i].Y);
}
}
}
} finally {
S.ReleaseDc(dc);
}
}
To avoid having to insert this boilerplate code every time lines are drawn to a surface, it is advisable to add the necessary GDI+ imports, the helper method RGB and the fast line-drawing method DrawLinesFast to a static class that extends the DirectDraw Surface class:
public static class DirectDrawSurfaceExtensions {
[DllImport("gdi32")]
public static extern
IntPtr MoveToEx(IntPtr hdc, int x, int y, IntPtr pt);
[DllImport("gdi32")]
public static extern
IntPtr LineTo(IntPtr hdc, int x, int y);
public static void DrawLinesFast(this Surface S, Point[][] pts) {
IntPtr dc = S.GetDc();
try {
foreach (Point[] line in pts) {
if (line.Length >= 2) {
MoveToEx(dc, line[0].X, line[0].Y, IntPtr.Zero);
for (int i = 1; i < line.Length; i++) {
LineTo(dc, line[i].X, line[i].Y);
}
}
}
} finally {
S.ReleaseDc(dc);
}
}
public static int RGB(Color C) {
return (C.R | (C.G << 8) | (C.B << 16));
}
}
Some simple benchmark experiments have shown this code to be several orders of magnitude faster than the DirectDraw API’s native DrawLine method.
Post Linx
Permalink | Trackback |
|
Print This Page |
Comments
Leave a Reply