87 lines
4.9 KiB
Markdown
87 lines
4.9 KiB
Markdown
The keys to the performance increase manifested in this chapter's code
|
|
are three. The first key is fixed-point arithmetic. In the previous two
|
|
chapters, we worked with floating-point coordinates and transformation
|
|
matrices. Those values are now stored as 32-bit fixed-point numbers, in
|
|
the form 16.16 (16 bits of whole number, 16 bits of fraction). 32-bit
|
|
fixed-point numbers allow sufficient precision for 3-D animation, but
|
|
can be manipulated with fast integer operations, rather than by slow
|
|
floating-point processor operations or excruciatingly slow
|
|
floating-point emulator operations. Although the speed advantage of
|
|
fixed-point varies depending on the operation, on the processor, and on
|
|
whether or not a coprocessor is present, fixed-point multiplication can
|
|
be as much as 100 times faster than the emulated floating-point
|
|
equivalent. (I'd like to take a moment to thank Chris Hecker for his
|
|
invaluable input in this area.)
|
|
|
|
The second performance key is the use of the 386's native 32-bit
|
|
multiply and divide instructions. C compilers operating in real mode
|
|
call library routines to perform multiplications and divisions involving
|
|
32-bit values, and those library functions are fairly slow, especially
|
|
for division. On a 386, 32-bit multiplication and division can be
|
|
handled with the bit of code in Listing 52.9—and most of even that code
|
|
is only for rounding.
|
|
|
|
The third performance key is maintaining and operating on only the
|
|
relevant portions of transformation matrices and coordinates. The bottom
|
|
row of every transformation matrix we'll use (in this book) is [0 0 0
|
|
1], so why bother using or recalculating it when concatenating
|
|
transforms and transforming points? Likewise for the fourth element of a
|
|
3-D vector in homogeneous coordinates, which is always 1. Basically,
|
|
transformation matrices are treated as consisting of a 3x3 rotation
|
|
matrix and a 3x1 translation vector, and coordinates are treated as 3x1
|
|
vectors. This saves a great many multiplications in the course of
|
|
transforming each point.
|
|
|
|
Just for fun, I reimplemented the animation of Listings 52.1 through
|
|
52.10 with floating-point instructions. Together, the preceeding
|
|
optimizations improve the performance of the entire animation—including
|
|
drawing time and overhead, and not just math—by more than ten times over
|
|
the code that uses the floating-point emulator. Amazing what one can
|
|
accomplish with a few dozen lines of assembly and a switch in number
|
|
format, isn't it? Note that no assembly code other than the native 386
|
|
multiply and divide is used in Listings 52.1 through 52.10, although the
|
|
polygon fill code is of course mostly in assembly; we've achieved 12
|
|
cubes animated at 15 fps while doing the 3-D work almost entirely in
|
|
Borland C++, and we're *still* doing sine and cosine via the
|
|
floating-point emulator. Happily, we're still nowhere near the upper
|
|
limit on the animation potential of the PC.
|
|
|
|
#### Drawbacks {#Heading6}
|
|
|
|
The techniques we've used to turbocharge 3-D animation are very
|
|
powerful, but there's a dark side to them as well. Obviously, native 386
|
|
instructions won't work on 8088 and 286 machines. That's rectifiable;
|
|
equivalent multiplication and division routines could be implemented for
|
|
real mode and performance would still be reasonable. It sure is nice to
|
|
be able to plug in a 32-bit **IMUL** or **DIV** and be done with it,
|
|
though. More importantly, 32-bit fixed-point arithmetic has limitations
|
|
in range and accuracy. Points outside a 64Kx64Kx64K space can't be
|
|
handled, imprecision tends to creep in over the course of multiple
|
|
matrix concatenations, and it's quite possible to generate the dreaded
|
|
divide by 0 interrupt if Z coordinates with absolute values less than
|
|
one are used.
|
|
|
|
I don't have space to discuss these issues in detail, but here are some
|
|
brief thoughts: The working 64Kx64Kx64K fixed-point space can be paged
|
|
into a larger virtual space. Imprecision of a pixel or two rarely
|
|
matters in terms of display quality, and deterioration of concatenated
|
|
rotations can be corrected by restoring orthogonality, for example by
|
|
periodically calculating one row of the matrix as the cross-product of
|
|
the other two (forcing it to be perpendicular to both). Alternatively,
|
|
transformations can be calculated from scratch each time an object or
|
|
the viewer moves, so there's no chance for cumulative error. 3-D
|
|
clipping with a front clip plane of -1 or less can prevent divide
|
|
overflow.
|
|
|
|
#### Where the Time Goes {#Heading7}
|
|
|
|
The distribution of execution time in the animation code is no longer
|
|
wildly biased toward transformation, but sine and cosine are certainly
|
|
still sucking up cycles. Likewise, the overhead in the calls to
|
|
**FixedMul()** and **FixedDiv()** is costly. Much of this is correctable
|
|
with a little carefully crafted assembly language and a lookup table;
|
|
I'll provide that shortly.
|
|
|
|
Regardless, with this chapter we have made the critical jump to a usable
|
|
level of performance and a serviceable general-purpose framework. From
|
|
here on out, it's the fun stuff.
|