119 lines
5.2 KiB
Markdown
119 lines
5.2 KiB
Markdown
Chapter 36\
|
||
The Good, the Bad, and the Run-Sliced {#Heading1}
|
||
--------------------------------------
|
||
|
||
### Faster Bresenham Lines with Run-Length Slice Line Drawing {#Heading2}
|
||
|
||
Years ago, I worked at a company that asked me to write blazingly fast
|
||
line-drawing code for an AutoCAD driver. I implemented the basic
|
||
Bresenham's line-drawing algorithm; streamlined it as much as possible;
|
||
special-cased horizontal, diagonal, and vertical lines; broke out
|
||
separate, optimized routines for lines in each octant; and massively
|
||
unrolled the loops. When I was done, I had line drawing down to a mere
|
||
five or six instructions per pixel, and I handed the code over to the
|
||
AutoCAD driver person, content in the knowledge that I had pushed the
|
||
theoretical limits of the Bresenham's algorithm on the 80x86
|
||
architecture, and that this was as fast as line drawing could get on a
|
||
PC. That feeling lasted for about a week, until Dave Miller, who these
|
||
days is a Windows display-driver whiz at Engenious Solutions, casually
|
||
mentioned Bresenham's faster run-length slice line-drawing algorithm.
|
||
|
||
Remember Bill Murray's safety tip in *Ghostbusters*? It goes something
|
||
like this. Harold Ramis tells the Ghostbusters not to cross the beams of
|
||
the antighost guns. "Why?" Murray asks.
|
||
|
||
"It would be bad," Ramis says.
|
||
|
||
Murray says, "I'm fuzzy on the whole good/bad thing. What exactly do you
|
||
mean by ‘bad'?" It turns out that what Ramis means by bad is basically
|
||
the destruction of the universe.
|
||
|
||
"Important safety tip," Murray comments dryly.
|
||
|
||
I learned two important safety tips from my line-drawing experience;
|
||
neither involves the possible destruction of the universe, so far as I
|
||
know, but they are nonetheless worth keeping in mind. First, never,
|
||
never, never think you've written the fastest possible code. Odds are,
|
||
you haven't. Run your code past another good programmer, and he or she
|
||
will probably say, "But why don't you do this?" and you'll realize that
|
||
you could indeed do that, and your code would then be faster. Or relax
|
||
and come back to your code later, and you may well see another, faster
|
||
approach. There are a million ways to implement code for any task, and
|
||
you can almost always find a faster way if you need to.
|
||
|
||
Second, when performance matters, never have your code perform the same
|
||
calculation more than once. This sounds obvious, but it's astonishing
|
||
how often it's ignored. For example, consider this snippet of code:
|
||
|
||
for (i=0; i<RunLength; i++)
|
||
{
|
||
*WorkingScreenPtr = Color;
|
||
if (XDelta > 0)
|
||
{
|
||
WorkingScreenPtr++;
|
||
}
|
||
else
|
||
{
|
||
WorkingScreenPtr--;
|
||
}
|
||
}
|
||
|
||
Here, the programmer knows which way the line is going before the main
|
||
loop begins—but nonetheless performs that test every time through the
|
||
loop, when calculating the address of the next pixel. Far better to
|
||
perform the test only once, outside the loop, as shown here:
|
||
|
||
if (XDelta > 0)
|
||
{
|
||
for (i=0; i<RunLength; i++)
|
||
{
|
||
*WorkingScreenPtr++ = Color;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
for (i=0; i<RunLength; i++)
|
||
{
|
||
*WorkingScreenPtr-- = Color;
|
||
}
|
||
}
|
||
|
||
Think of it this way: A program is a state machine. It takes a set of
|
||
inputs and produces a corresponding set of outputs by passing through a
|
||
set of states. Your primary job as a programmer is to implement the
|
||
desired state machine. Your additional job as a performance programmer
|
||
is to minimize the lengths of the paths through the state machine. This
|
||
means performing as many tests and calculations as possible outside the
|
||
loops, so that the loops themselves can do as little work—that is, pass
|
||
through as few states—as possible.
|
||
|
||
Which brings us full circle to Bresenham's run-length slice line-drawing
|
||
algorithm, which just happens to be an excellent example of a minimized
|
||
state machine. In case you're fuzzy on the good/bad performance thing,
|
||
that's "good"—as in *fast*.
|
||
|
||
### Run-Length Slice Fundamentals {#Heading3}
|
||
|
||
First off, I have a confession to make: I'm not sure that the algorithm
|
||
I'll discuss is actually, precisely Bresenham's run-length slice
|
||
algorithm. It's been a long time since I read about this algorithm; in
|
||
the intervening years, I've misplaced Bresenham's article, and have been
|
||
unable to unearth it. As a result, I had to derive the algorithm from
|
||
scratch, which was admittedly more fun than reading about it, and also
|
||
ensured that I understood it inside and out. The upshot is that what I
|
||
discuss may or may not be Bresenham's run-length slice algorithm—but it
|
||
surely is fast.
|
||
|
||
The place to begin understanding the run-length slice algorithm is the
|
||
standard Bresenham's line-drawing algorithm. (I discussed the standard
|
||
Bresenham's line-drawing algorithm at length in the previous chapter.)
|
||
The basis of the standard approach is stepping one pixel at a time along
|
||
the major axis (the longer dimension of the line), while maintaining an
|
||
integer error term that indicates at each major-axis step how close the
|
||
line is to advancing halfway to the next pixel along the minor axis.
|
||
Figure 36.1 illustrates standard Bresenham's line drawing. The key point
|
||
here is that a calculation and a test are performed once for each step
|
||
along the major axis.
|
||
|
||
\
|
||
**Figure 36.1** *Standard Bresenham's line drawing.*
|