131 lines
6.9 KiB
Markdown
131 lines
6.9 KiB
Markdown
---
|
|
title: Michael Abrash's Graphics Programming Black Book, Special Edition
|
|
author: Michael Abrash
|
|
date: '1997-07-01'
|
|
isbn: '1576101746'
|
|
publisher: The Coriolis Group
|
|
category: 'Web and Software Development: Game Development,Web and Software Development:
|
|
Graphics and Multimedia Development'
|
|
chapter: '37'
|
|
pages: 704-706
|
|
---
|
|
|
|
#### How Fast Is Fast? {#Heading4}
|
|
|
|
Your first question is likely to be the following: Just how fast is
|
|
Listing 37.1? Is it optimized to the hilt or just pretty fast? The quick
|
|
answer is: It's *fast*. Listing 37.1 draws lines at a rate of nearly 1
|
|
million pixels per second on my 486/33, and is capable of still faster
|
|
drawing, as I'll discuss shortly. (The heavily optimized AutoCAD
|
|
line-drawing code that I mentioned in the last chapter drew 150,000
|
|
pixels per second on an EGA in a 386/16, and I thought I had died and
|
|
gone to Heaven. Such is progress.) The full answer is a more complicated
|
|
one, and ties in to the principle that if it is broken, maybe that's
|
|
okay—and to the principle of looking before you leap, also known as
|
|
profiling before you optimize.
|
|
|
|
When I went to speed up run-length slice lines, I initially manually
|
|
converted the last chapter's C code into assembly. Then I streamlined
|
|
the register usage and used `REP STOS` wherever possible. Listing 37.1
|
|
is that code. At that point, line drawing was surely faster, although I
|
|
didn't know exactly how much faster. Equally surely, there were
|
|
significant optimizations yet to be made, and I was itching to get on to
|
|
them, for they were bound to be a lot more interesting than a basic
|
|
C-to-assembly port.
|
|
|
|
Ego intervened at this point, however. I wanted to know how much of a
|
|
speed-up I had already gotten, so I timed the performance of the C code
|
|
and compared it to the assembly code. To my horror, I found that I had
|
|
not gotten even a two-times improvement! I couldn't understand how that
|
|
could be—the C code was decidedly unoptimized—until I hit on the idea of
|
|
measuring the maximum memory speed of the VGA to which I was drawing.
|
|
|
|
Bingo. The Paradise VGA in my 486/33 is fast for a single display-memory
|
|
write, because it buffers the data, lets the CPU go on its merry way,
|
|
and finishes the write when display memory is ready. However, the
|
|
maximum rate at which data can be written to the adapter turns out to be
|
|
no more than one byte every microsecond. Put another way, you can only
|
|
write one byte to this adapter every 33 clock cycles on a 486/33.
|
|
Therefore, no matter how fast I made the line-drawing code, it could
|
|
never draw more than 1,000,000 pixels per second in 256-color mode in my
|
|
system. The C code was already drawing at about half that rate, so the
|
|
potential speed-up for the assembly code was limited to a maximum of two
|
|
times, which is pretty close to what Listing 37.1 did, in fact, achieve.
|
|
When I compared the C and assembly implementations drawing to normal
|
|
system (nondisplay) memory, I found that the assembly code was actually
|
|
four times as fast as the C code.
|
|
|
|
> 
|
|
> In fact, Listing 37.1 draws VGA lines at about 92 percent of the maximum
|
|
> possible rate in my system—that is, it draws very nearly as fast as the
|
|
> VGA hardware will allow. All the optimization in the world would get me
|
|
> less than 10 percent faster line drawing—and only if I eliminated all
|
|
> overhead, an unlikely proposition at best. The code isn't fully
|
|
> optimized, but so what?
|
|
|
|
Now it's true that faster line-drawing code would likely be more
|
|
beneficial on faster VGAs, especially local-bus VGAs, and in slower
|
|
systems. For that reason, I'll list a variety of potential optimizations
|
|
to Listing 37.1. On the other hand, it's also true that Listing 37.1 is
|
|
capable of drawing lines at a rate of 2.2 million pixels per second on a
|
|
486/ 33, given fast enough VGA memory, so it should be able to drive
|
|
almost any non-local-bus VGA at nearly full speed. In short, Listing
|
|
37.1 is very fast, and, in many systems, further optimization is
|
|
basically a waste of time.
|
|
|
|
Profile before you optimize.
|
|
|
|
#### Further Optimizations {#Heading5}
|
|
|
|
Following is a quick tour of some of the many possible further
|
|
optimizations to Listing 37.1.
|
|
|
|
The run-handling loops could be unrolled more than the current two
|
|
times. However, bear in mind that a two-times unrolling gets more than
|
|
half the maximum unrolling benefit with less overhead than a more
|
|
heavily unrolled loop.
|
|
|
|
BX could be freed up in the Y-major code by breaking out separate loops
|
|
for X advances of 1 and -1. DX could be freed up by using AH as the
|
|
counter for the run loops, although this would limit the maximum line
|
|
length that could be handled. The freed registers could be used to keep
|
|
more of the whole-step and error variables in registers. Alternatively,
|
|
the freed registers could be used to implement more esoteric approaches
|
|
like unrolling the Y-major inner loop; such unrolling could take
|
|
advantage of the knowledge that only two run lengths are possible for
|
|
any given line. Strangely enough, on the 486 it might also be worth
|
|
unrolling the X-major inner loop, which consists of `REP STOSB`,
|
|
because of the slow start-up time of `REP` relative to the speed of
|
|
branching on that processor.
|
|
|
|
Special code could be implemented for lines with integral slopes,
|
|
because all runs are exactly the same length in such lines. Also, the
|
|
X-major code could try to write an aligned word at a time to display
|
|
memory whenever possible; this would improve the maximum possible
|
|
performance on some 16-bit VGAs.
|
|
|
|
One weakness of Listing 37.1 is that for lines with slopes between 0.5
|
|
and 2, the average run length is less than two, rendering run-length
|
|
slicing ineffective. This can be remedied by viewing lines in that range
|
|
as being composed of diagonal, rather than horizontal or vertical runs.
|
|
I haven't space to take this idea any further in this book, but it's not
|
|
very complicated, and it guarantees a minimum run length of 2, which
|
|
renders run drawing considerably more efficient, and makes techniques
|
|
such as unrolling the inner run-drawing loops more attractive.
|
|
|
|
Finally, be aware that run-length slice drawing is best for long lines,
|
|
because it has more and slower setup than a standard Bresenham's line
|
|
draw, including a divide. Run-length slice is great for 100-pixel lines,
|
|
but not necessarily for 20-pixel lines, and it's a sure thing that it's
|
|
not terrific for 3-pixel lines. Both approaches will work, but if
|
|
line-drawing performance is critical, whether you'll want to use
|
|
run-length slice or standard Bresenham's depends on the typical lengths
|
|
of the lines you'll be drawing. For lines of widely varying lengths, you
|
|
might want to implement both approaches, and choose the best one for
|
|
each line, depending on the line length—assuming, of course, that your
|
|
display memory is fast enough and your application demanding enough to
|
|
make that level of optimization worthwhile.
|
|
|
|
If your code looks broken from a performance perspective, think before
|
|
you fix it; that particular cat may be dead for a perfectly good reason.
|
|
I'll say it again: *Profile before you optimize*.
|