Convert figure images to markdown
This commit is contained in:
parent
307b59584f
commit
77a95ec319
115 changed files with 218 additions and 403 deletions
10
02-02.md
10
02-02.md
|
|
@ -35,9 +35,8 @@ not lend itself particularly well to compiler design. Even the 32-bit
|
|||
mode of the 386 and its successors, with their more powerful addressing
|
||||
modes, offer fewer registers than compilers would like.
|
||||
|
||||
\
|
||||
**Figure 2.1** *The high-level language transformation
|
||||
inefficiencies.*
|
||||

|
||||
|
||||
Assembly, on the other hand, is simply a human-oriented representation
|
||||
of machine language. As a result, assembly provides a difficult
|
||||
|
|
@ -53,9 +52,8 @@ machine language instructions on a one-to-one basis. As a result, the
|
|||
programmer is able to produce machine language code that's precisely
|
||||
tailored to the needs of each task a given application requires.
|
||||
|
||||
\
|
||||
**Figure 2.2** *Properly constructed assembly programs suffer no
|
||||
transformation loss.*
|
||||

|
||||
|
||||
The key, of course, is the programmer, since in assembly the programmer
|
||||
must essentially perform the transformation from the application
|
||||
|
|
|
|||
3
03-03.md
3
03-03.md
|
|
@ -79,8 +79,7 @@ attention.
|
|||
Timer 1 is dedicated to providing dynamic RAM refresh, and should not be
|
||||
tampered with lest system crashes result.
|
||||
|
||||
\
|
||||
**Figure 3.1** *The configuration of the 8253 timer chip in the PC.*
|
||||

|
||||
|
||||
Finally, timer 0 is used to drive the system clock. As programmed by the
|
||||
BIOS at power-up, every 65,536 (64K) counts, or 54.925 milliseconds,
|
||||
|
|
|
|||
6
03-10.md
6
03-10.md
|
|
@ -26,8 +26,7 @@ timer from C, as, for example, in:
|
|||
timer—Listing 3.5—requires the same modifications, but to different
|
||||
lines.)
|
||||
|
||||
\
|
||||
**Figure 3.2** *Changes for use with small code model C.*
|
||||

|
||||
|
||||
Altering the Zen timer for use in C's large code model is a tad more
|
||||
complex, because in addition to the above changes, all functions,
|
||||
|
|
@ -59,8 +58,7 @@ with
|
|||
is normally a great optimization, being both smaller and faster than a
|
||||
far call. However, it's not so great for the Zen
|
||||
|
||||
\
|
||||
**Figure 3.3** *Changes for use with large code model C.*
|
||||

|
||||
|
||||
timer, because our purpose in calling the reference timing code is to
|
||||
determine exactly how much time is taken by overhead code—including the
|
||||
|
|
|
|||
6
04-02.md
6
04-02.md
|
|
@ -10,11 +10,9 @@ chapter: '04'
|
|||
pages: 080-084
|
||||
---
|
||||
|
||||
\
|
||||
**Figure 4.1** *The location of the major cycle-eaters in the IBM PC.*
|
||||

|
||||
|
||||
\
|
||||
**Figure 4.2** *Internal data bus widths of the 8088.*
|
||||

|
||||
|
||||
As shown in Figure 4.1, the 8-bit bus cycle-eater lies squarely on the
|
||||
8088's external data bus. Technically, it might be more accurate to
|
||||
|
|
|
|||
5
04-04.md
5
04-04.md
|
|
@ -128,9 +128,8 @@ that the "true" execution time of **SHR** is 8.64 cycles.
|
|||
endm
|
||||
call ZTimerOff
|
||||
|
||||
\
|
||||
**Figure 4.3** *Execution and instruction prefetching sequence for
|
||||
Listing 4.5.*
|
||||

|
||||
|
||||
Now let's examine Listing 4.6. Here each **SHR** follows a **MUL**
|
||||
instruction. Since **MUL** instructions take so long to execute that the
|
||||
|
|
|
|||
5
04-05.md
5
04-05.md
|
|
@ -38,9 +38,8 @@ speed as Listing 4.7. The reason: Both instructions are 2 bytes long,
|
|||
and in both cases it is the 8-cycle instruction fetch time, not the 3 or
|
||||
4-cycle Execution Unit execution time, that limits performance.
|
||||
|
||||
\
|
||||
**Figure 4.4** *Execution and instruction prefetching sequence for
|
||||
Listing 4.6.*
|
||||

|
||||
|
||||
**LISTING 4.7 LST4-7.ASM**
|
||||
|
||||
|
|
|
|||
3
04-06.md
3
04-06.md
|
|
@ -135,5 +135,4 @@ we'll see shortly), depending on the extent to which DRAM refresh
|
|||
occupies cycles during which the 8088 would otherwise be accessing
|
||||
memory.
|
||||
|
||||
\
|
||||
**Figure 4.5** *The PC bus dynamic RAM (DRAM) refresh.*
|
||||

|
||||
9
04-08.md
9
04-08.md
|
|
@ -48,8 +48,7 @@ that the whole point of the wait state mechanism is to allow a device to
|
|||
stretch out any access to itself for however much time it needs to
|
||||
perform the access.
|
||||
|
||||
\
|
||||
**Figure 4.6** *Video wait states inserted by the display adapter.*
|
||||

|
||||
|
||||
As with DRAM refresh, wait states don't stop the 8088 completely. The
|
||||
Execution Unit can continue processing while wait states are inserted,
|
||||
|
|
@ -101,8 +100,7 @@ leaving as little as about 10 percent of all display memory accesses for
|
|||
the 8088. (These percentages vary considerably among the many EGA and
|
||||
VGA clones.)
|
||||
|
||||
\
|
||||
**Figure 4.7** *Allocation of display memory access.*
|
||||

|
||||
|
||||
Second, because the displayed dots (or *pixels,* short for "picture
|
||||
elements") must be drawn on the screen at a constant speed, many display
|
||||
|
|
@ -121,8 +119,7 @@ access just as it becomes available, again as shown in Figure 4.8. Any
|
|||
or all of the three factors I've described can result in wait states,
|
||||
slowing the 8088 and creating the display adapter cycle.
|
||||
|
||||
\
|
||||
**Figure 4.8** *Display memory access slots.*
|
||||

|
||||
|
||||
If some of this is Greek to you, don't worry. The important point is
|
||||
that display memory is not very fast compared to normal system memory.
|
||||
|
|
|
|||
6
05-02.md
6
05-02.md
|
|
@ -37,8 +37,7 @@ to check out the actual machine-code implementation of **memcmp()** from
|
|||
your compiler. If necessary, you could always write your own assembly
|
||||
language implementation of **memcmp()**.
|
||||
|
||||
\
|
||||
**Figure 5.1** *The brute-force searching technique.*
|
||||

|
||||
|
||||
Invoking **memcmp()** for each potential match location works, but
|
||||
entails considerable overhead. Each comparison requires that parameters
|
||||
|
|
@ -70,8 +69,7 @@ searched and the string we're searching for. Our engine also relies
|
|||
heavily on repeated string instructions, assuming that the **memchr()**
|
||||
and **memcmp()** library functions are properly coded.
|
||||
|
||||
\
|
||||
**Figure 5.2** *The faster string-searching technique.*
|
||||

|
||||
|
||||
We're going to go with the this approach in our file-searching program;
|
||||
the only trick lies in deciding how to integrate this approach with
|
||||
|
|
|
|||
6
06-02.md
6
06-02.md
|
|
@ -101,8 +101,7 @@ slower than **ADD** on a 486 if the sum of two registers is used to
|
|||
point to memory, but no slower than **ADD** on a Pentium. On both a 486
|
||||
and Pentium, **LEA** can also be slowed down by addressing interlocks.
|
||||
|
||||
\
|
||||
**Figure 6.1** *Operation of ADD Reg,Reg vs. LEA Reg,{Addr}.*
|
||||

|
||||
|
||||
#### The Wonders of LEA on the 386 {#Heading5}
|
||||
|
||||
|
|
@ -124,8 +123,7 @@ This makes the 32-bit **LEA** much more generally useful than the
|
|||
standard 16-bit **LEA** in the role of an **ADD** with an independent
|
||||
destination.
|
||||
|
||||
\
|
||||
**Figure 6.2** *Operation of the 32-bit LEA reg,[Addr].*
|
||||
![**Figure 6.2** *Operation of the 32-bit LEA reg,[Addr].*](images/06-02.jpg)
|
||||
|
||||
But what else can **LEA** do on a 386, besides add?
|
||||
|
||||
|
|
|
|||
3
08-02.md
3
08-02.md
|
|
@ -73,8 +73,7 @@ True optimization requires rethinking your code to take advantage of
|
|||
assembly language. A C loop that searches through an integer array for
|
||||
matches might compile
|
||||
|
||||
\
|
||||
**Figure 8.1** *Tweaked compiler output for a loop.*
|
||||

|
||||
|
||||
to something like Figure 8.1A. You might look at that and tweak it to
|
||||
the code shown in Figure 8.1B.
|
||||
|
|
|
|||
3
08-03.md
3
08-03.md
|
|
@ -142,8 +142,7 @@ block and the inner one repeating once for each array element in each
|
|||
block. The inner loop—the critical one—is compact, containing only four
|
||||
statements, and should lend itself rather well to compiler optimization.
|
||||
|
||||
\
|
||||
**Figure 8.2** *Linked array storage format (version 1).*
|
||||

|
||||
|
||||
As it happens, Microsoft C/C++ does optimize the inner loop of
|
||||
**FindIDAverage** nicely. Listing 8.2 shows the code Microsoft C/C++
|
||||
|
|
|
|||
3
08-05.md
3
08-05.md
|
|
@ -39,8 +39,7 @@ merely rearranged.
|
|||
extern unsigned int FindIDAverage2(unsigned int,
|
||||
struct BlockHeader *);
|
||||
|
||||
\
|
||||
**Figure 8.3** *Linked array storage format (version 2).*
|
||||

|
||||
|
||||
/* Structure that starts each variable-sized block */
|
||||
struct BlockHeader {
|
||||
|
|
|
|||
3
09-02.md
3
09-02.md
|
|
@ -119,8 +119,7 @@ fastest way to search on the PC, can be used to eliminate most potential
|
|||
matches; each remaining potential match can then be checked in its
|
||||
entirety with **REPZ CMPS**.
|
||||
|
||||
\
|
||||
**Figure 9.1** *Simple searching method for locating a text string.*
|
||||

|
||||
|
||||
Rob's revelation, which he credits without explanation to Edgar Allen
|
||||
Poe (search nevermore?), was that by far the slowest part of the whole
|
||||
|
|
|
|||
3
09-03.md
3
09-03.md
|
|
@ -35,8 +35,7 @@ using **REPZ CMPS** to check scanning matches.
|
|||
 *The difference between Listings 9.1 and 9.2 (which gives you more than a doubling of performance) is due entirely to understanding the nature of the data being handled, and biasing the code to reflect that knowledge.*
|
||||
------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
\
|
||||
**Figure 9.2** *Faster searching method for locating a text string.*
|
||||

|
||||
|
||||
**LISTING 9.1 L9-1.ASM**
|
||||
|
||||
|
|
|
|||
3
09-05.md
3
09-05.md
|
|
@ -110,8 +110,7 @@ applied to handling arbitrarily large dividends in 386 native mode code,
|
|||
but in that case the operation can proceed a dword, rather than a word,
|
||||
at a time.
|
||||
|
||||
\
|
||||
**Figure 9.3** *Fast multiword division on the 386.*
|
||||

|
||||
|
||||
As for handling signed division with arbitrarily large dividends, that
|
||||
can be done easily enough by remembering the signs of the dividend and
|
||||
|
|
|
|||
3
09-07.md
3
09-07.md
|
|
@ -31,8 +31,7 @@ fast they run on my 386, and I very much doubt that you'll find
|
|||
different execution times on other 386s. (Please let me know if you do,
|
||||
though!)
|
||||
|
||||
\
|
||||
**Figure 9.4** *Performing rotate instructions using the Carry flag.*
|
||||

|
||||
|
||||
Interestingly, according to Intel's *i486 Microprocessor Programmer's
|
||||
Reference Manual*, the 486 can **RCR** or **RCL** a register by one bit
|
||||
|
|
|
|||
3
10-02.md
3
10-02.md
|
|
@ -39,8 +39,7 @@ calculation. Table 10.1 shows how long it takes this approach to find
|
|||
the GCD for several integer pairs. As expected, performance is extremely
|
||||
poor when iS is large.
|
||||
|
||||
\
|
||||
**Figure 10.1** *Using a brute-force algorithm to find a GCD.*
|
||||

|
||||
|
||||
**Integer pairs for which to find GCD**
|
||||
|
||||
|
|
|
|||
6
10-03.md
6
10-03.md
|
|
@ -18,8 +18,7 @@ iterations this approach requires relative to Listing 10.1 depends
|
|||
heavily on the values of iL and iS, so it's not always faster, but, as
|
||||
Table 10.1 indicates, Listing 10.2 is generally much better code.
|
||||
|
||||
\
|
||||
**Figure 10.2** *Using repeated subtraction algorithm to find a GCD.*
|
||||

|
||||
|
||||
Listing 10.2 is a far graver misstep than Listing 10.1, for all that
|
||||
it's faster. Listing 10.1 is obviously a hacked-up, brute-force
|
||||
|
|
@ -98,8 +97,7 @@ especially for large numbers (and imagine if we were working with large
|
|||
 *Had I been implementing GCD determination without Sedgewick's help, I would surely not have settled for Listing 10.1—but I might well have ended up with Listing 10.2 in my enthusiasm over the "brilliant" discovery of subtracting the lesser Using Euclid's algorithm to find a GCD number from the greater. In a commercial product, my lack of patience and discipline could have been costly indeed.*
|
||||
------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
\
|
||||
**Figure 10.3** *Using Euclid's algorithm to find a GCD.*
|
||||

|
||||
|
||||
Give your mind time and space to wander around the edges of important
|
||||
programming problems before you settle on any one approach. I titled
|
||||
|
|
|
|||
3
11-03.md
3
11-03.md
|
|
@ -79,8 +79,7 @@ close relative of the 8088's 8-bit bus cycle-eater, but since it behaves
|
|||
differently—occurring only at odd addresses—and is avoided with a
|
||||
different workaround, we'll consider it to be a new cycle-eater.)
|
||||
|
||||
\
|
||||
**Figure 11.1** *The data alignment cycle-eater.*
|
||||

|
||||
|
||||
The way to deal with the data alignment cycle-eater is straightforward:
|
||||
*Don't perform word-sized accesses to odd addresses on the 286 if you
|
||||
|
|
|
|||
6
11-04.md
6
11-04.md
|
|
@ -22,11 +22,9 @@ no problems until I timed the following code:
|
|||
loop LoopTop
|
||||
call ZTimerOff
|
||||
|
||||
\
|
||||
**Figure 11.2** *Word-aligned prefetching on the 286.*
|
||||

|
||||
|
||||
\
|
||||
**Figure 11.3** *How instruction bytes are fetched after a branch.*
|
||||

|
||||
|
||||
Now, this code *should* run in, say, about 12 cycles per loop at most.
|
||||
Instead, it took over 14 cycles per loop, an execution time that I could
|
||||
|
|
|
|||
9
11-08.md
9
11-08.md
|
|
@ -33,8 +33,7 @@ have occurred had we executed **POPF**—WITH the bonus that no interrupts
|
|||
can accidentally occur when the Interrupt flag is 0 both before and
|
||||
after the pop.
|
||||
|
||||
\
|
||||
**Figure 11.4** *The operation of POPF.*
|
||||

|
||||
|
||||
How can we push the segment:offset of the next instruction? Well,
|
||||
finding the offset of the next instruction by performing a near call to
|
||||
|
|
@ -61,8 +60,7 @@ with. The code works out like this:
|
|||
; was reached has been popped into the FLAGS register, just as
|
||||
; if a POPF instruction had been executed.
|
||||
|
||||
\
|
||||
**Figure 11.5** *The operation of IRET.*
|
||||

|
||||
|
||||
The operation of this code is illustrated in Figure 11.6.
|
||||
|
||||
|
|
@ -106,8 +104,7 @@ an 8088.)
|
|||
iret
|
||||
endm
|
||||
|
||||
\
|
||||
**Figure 11.6** *Workaround code for the POPF bug.*
|
||||

|
||||
|
||||
The standard version of **EMULATE\_POPF** is 6 bytes longer than
|
||||
**POPF** and much slower, as you'd expect given that it involves three
|
||||
|
|
|
|||
6
12-02.md
6
12-02.md
|
|
@ -102,8 +102,7 @@ improvement in the performance of the entire loop. But wait, there's
|
|||
more. If a register is loaded 2 cycles (which generally means 2
|
||||
instructions, but, because some 486 instructions take more than 1 cycle,
|
||||
|
||||
\
|
||||
**Figure 12.1** *One-cycle-ahead address pipelining.*
|
||||

|
||||
|
||||
the 2 are not always equivalent) before it's used to point to memory, 1
|
||||
cycle is lost. Therefore, whereas this code
|
||||
|
|
@ -143,8 +142,7 @@ Figure 12.2 will do just fine for optimization purposes.)
|
|||
Clearly, there's considerable optimization potential in careful
|
||||
rearrangement of 486 code.
|
||||
|
||||
\
|
||||
**Figure 12.2** *Two-cycle-ahead address pipelining.*
|
||||

|
||||
|
||||
### Caveat Programmor {#Heading7}
|
||||
|
||||
|
|
|
|||
3
13-01.md
3
13-01.md
|
|
@ -104,8 +104,7 @@ from the use of DI to address memory (remember, the loop is unrolled, so
|
|||
the last instruction is followed by the first instruction), but because
|
||||
the intervening instruction takes two cycles, there's no penalty at all.
|
||||
|
||||
\
|
||||
**Figure 13.1** *Cycle-eaters in the original WC.*
|
||||

|
||||
|
||||
------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
 *Remember, pipeline penalties diminish with increasing number of cycles, not instructions, between the pipeline disrupter and the potentially affected instruction.*
|
||||
|
|
|
|||
3
13-02.md
3
13-02.md
|
|
@ -68,8 +68,7 @@ values (least-significant byte first) loads pixels in the wrong order,
|
|||
so far as word rotation is concerned, but **BSWAP** can take care of
|
||||
that.
|
||||
|
||||
\
|
||||
**Figure 13.2** *BSWAP in operation.*
|
||||

|
||||
|
||||
As it turns out, though, **BSWAP** is also useful in an unexpected way,
|
||||
having to do with making efficient use of the upper half of 32-bit
|
||||
|
|
|
|||
9
14-02.md
9
14-02.md
|
|
@ -83,8 +83,7 @@ advance of fewer bytes than the pattern length, and potentially as
|
|||
little as the same single byte distance by which the standard search
|
||||
approach advances.
|
||||
|
||||
\
|
||||
**Figure 14.1** *Mismatch on first character checked.*
|
||||

|
||||
|
||||
What if the mismatch occurs with a buffer character that *does* occur in
|
||||
the pattern? Then we can't skip past the mismatch location, but we can
|
||||
|
|
@ -104,8 +103,7 @@ move clear past the mismatch location. Otherwise, the pattern moves
|
|||
until a matching pattern byte lies atop the mismatch. That's all there
|
||||
is to it!
|
||||
|
||||
\
|
||||
**Figure 14.2** *Mismatch on third character checked.*
|
||||

|
||||
|
||||
### Boyer-Moore: The Good and the Bad {#Heading5}
|
||||
|
||||
|
|
@ -119,8 +117,7 @@ skips, help Boyer-Moore, as does a long distance to the match location,
|
|||
which helps diffuse the overhead of building the table of distances to
|
||||
skip ahead on all the possible mismatch values.
|
||||
|
||||
\
|
||||
**Figure 14.3** *Mismatch on character that appears in pattern.*
|
||||

|
||||
|
||||
The best case for Boyer-Moore is good indeed: About N/M comparisons are
|
||||
required, where N is the buffer length and M is the pattern length. This
|
||||
|
|
|
|||
3
15-01.md
3
15-01.md
|
|
@ -146,5 +146,4 @@ point to the head pointer and pretend it was a **LinkNode**
|
|||
structure—but that's an ugly and potentially dangerous trick, and we'll
|
||||
see a better approach next.)
|
||||
|
||||
\
|
||||
**Figure 15.1** *The basic concept of a linked list.*
|
||||

|
||||
6
15-02.md
6
15-02.md
|
|
@ -94,8 +94,7 @@ encountered linked lists, while designing a seed fill function for
|
|||
MetaWindows, back during my tenure at Metagraphics Corp. But I could
|
||||
have learned it by spending five minutes with Sedgewick's book.
|
||||
|
||||
\
|
||||
**Figure 15.2** *Using a dummy head and tail node with a linked list.*
|
||||

|
||||
|
||||
------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
 *The next-node pointer of the head node, which points to the first real node, is the only part of the head node that's actually used. This way the same code works on the head node as on the rest of the list, so there are no special cases.*
|
||||
|
|
@ -158,5 +157,4 @@ sentinels, but the performance benefits are considerable for those lend
|
|||
themselves to sentinels, but the performance benefits are considerable
|
||||
for those that do.
|
||||
|
||||
\
|
||||
**Figure 15.3** *Representing an empty list.*
|
||||

|
||||
6
15-03.md
6
15-03.md
|
|
@ -34,8 +34,7 @@ pages: 287-290
|
|||
node that was >= */
|
||||
}
|
||||
|
||||
\
|
||||
**Figure 15.4** *List terminated by a sentinel.*
|
||||

|
||||
|
||||
### Circular Lists {#Heading5}
|
||||
|
||||
|
|
@ -77,8 +76,7 @@ not so simple that a little knowledge doesn't make a substantial
|
|||
difference. Make it a habit to read Knuth or Sedgewick or the like
|
||||
before you write a single line of code.
|
||||
|
||||
\
|
||||
**Figure 15.5** *Representing a circular list.*
|
||||

|
||||
|
||||
**LISTING 15.6 L15-6.C**
|
||||
|
||||
|
|
|
|||
6
16-08.md
6
16-08.md
|
|
@ -123,8 +123,7 @@ and using the resulting value to index into the 64K table, adding in the
|
|||
to perform all word-counting tasks for a pair of bytes. Three
|
||||
instructions, no branches—pretty nearly perfect code.
|
||||
|
||||
\
|
||||
**Figure 16.1** *The two potential word count locations.*
|
||||

|
||||
|
||||
One detail remains to be attended to: setting the Carry flag for next
|
||||
time if the last byte was a non-separator. David does this in a bizarre
|
||||
|
|
@ -136,8 +135,7 @@ masked off before being added to the total count, so David is
|
|||
essentially using different parts of the count variables for different
|
||||
purposes (counting, and setting the Carry flag).
|
||||
|
||||
\
|
||||
**Figure 16.2** *Looking up a word count status.*
|
||||

|
||||
|
||||
There are a number of other interesting details in David's code,
|
||||
including the unrolling of the loop 64 times, so that 256 bytes in a row
|
||||
|
|
|
|||
6
17-04.md
6
17-04.md
|
|
@ -21,8 +21,7 @@ neighbor-counting for the bit-per-cell cellmap format, but it seems we'd
|
|||
need a lot of conditional code to handle wrapping, and that would slow
|
||||
things back down again.
|
||||
|
||||
\
|
||||
**Figure 17.1** *Edge-wrapping complications.*
|
||||

|
||||
|
||||
When a problem doesn't lend itself well to optimization, make it a
|
||||
practice to see if you can change the problem definition to one that
|
||||
|
|
@ -45,8 +44,7 @@ Listing 17.1, as shown in Table 17.1. We're up to about 10 generations
|
|||
per second on a 486; not where we want to be, but it is a vast
|
||||
improvement.
|
||||
|
||||
\
|
||||
**Figure 17.2** *The "padding cells" solution.*
|
||||

|
||||
|
||||
**LISTING 17.3 L17-3.CPP**
|
||||
|
||||
|
|
|
|||
3
17-06.md
3
17-06.md
|
|
@ -99,8 +99,7 @@ only one-tenth that of the original approach!
|
|||
|
||||
*Know your data.*
|
||||
|
||||
\
|
||||
**Figure 17.3** *New cell format.*
|
||||

|
||||
|
||||
#### Acting on What We Know {#Heading10}
|
||||
|
||||
|
|
|
|||
3
18-04.md
3
18-04.md
|
|
@ -207,5 +207,4 @@ as shown in Figure 18.1. Therefore, the neighbor count for a given cell
|
|||
never needs to reflect more than seven neighbors, because at least one
|
||||
of the eight neighbors' states is already encoded in the word.]
|
||||
|
||||
\
|
||||
**Figure 18.1** *Cell triplet storage.*
|
||||

|
||||
3
20-01.md
3
20-01.md
|
|
@ -111,8 +111,7 @@ simple instructions such as **MOV** and **ADD**, but unable to handle
|
|||
**MUL, DIV**, string instructions, any sort of rotation or shift, or
|
||||
even **ADC** or **SBB**.
|
||||
|
||||
\
|
||||
**Figure 20.1** *The Pentium's two pipes.*
|
||||

|
||||
|
||||
Getting two instructions executing simultaneously in the two pipes is
|
||||
trickier than it sounds, not only because the V-pipe can handle only a
|
||||
|
|
|
|||
8
20-02.md
8
20-02.md
|
|
@ -152,8 +152,7 @@ V-pipe.**
|
|||
 *A fundamental rule of Pentium optimization is that it pays to break complex instructions into equivalent simple instructions, then shuffle the simple instructions for maximum use of the V-pipe. This is true partly because most of the pairable instructions are simple instructions, and partly because breaking instructions into pieces allows more freedom to rearrange code to avoid the AGIs and register contention I'll discuss in the next chapter.*
|
||||
------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
\
|
||||
**Figure 20.2** *Instruction flow through the two pipes.*
|
||||

|
||||
|
||||
One downside of this "RISCification" (turning complex instructions into
|
||||
simple, RISC-like ones) of Pentium-optimized code is that it makes for
|
||||
|
|
@ -166,9 +165,8 @@ is one byte smaller than this sequence:
|
|||
mov eax,[esi]
|
||||
push eax
|
||||
|
||||
\
|
||||
**Figure 20.3** *Pushing a value from memory effectively in one
|
||||
cycle.*
|
||||

|
||||
|
||||
A more telling example is the following
|
||||
|
||||
|
|
|
|||
6
20-03.md
6
20-03.md
|
|
@ -43,8 +43,7 @@ that is often not correct.
|
|||
 *The actual rule is that we should strive to pair one-cycle instructions (or, at most, two-cycle instructions, but not three-cycle instructions), which in turn leads to the corollary that we should, in general, use mostly one-cycle instructions when optimizing.*
|
||||
------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
\
|
||||
**Figure 20.4** *Lockstep execution and idle time in the V-pipe.*
|
||||

|
||||
|
||||
Here's why. The Pentium is fully capable of handling instructions that
|
||||
use memory operands in either pipe, or, if necessary, in both pipes at
|
||||
|
|
@ -73,8 +72,7 @@ banks your paired memory accesses fall in—that's just too much work—but
|
|||
you might want to watch out for simultaneously read addresses that have
|
||||
the same values for address
|
||||
|
||||
\
|
||||
**Figure 20.5** *The Pentium's eight bank data cache.*
|
||||

|
||||
|
||||
bits 2, 3, and 4 (fall in the same bank) in tight loops, and you should
|
||||
also avoid sequences like
|
||||
|
|
|
|||
11
20-04.md
11
20-04.md
|
|
@ -23,12 +23,10 @@ instructions take 5 cycles in all to execute, and 4 cycles of idle
|
|||
time—2 in the U-pipe and 2 in the V-pipe, out of 10 cycles in all—are
|
||||
incurred in the process.
|
||||
|
||||
\
|
||||
**Figure 20.6** *Non-overlapped lockstep execution.*
|
||||

|
||||
|
||||
\
|
||||
**Figure 20.7** *Interleaving simple instructions for maximum
|
||||
performance.*
|
||||

|
||||
|
||||
The solution is to break the instructions into simple instructions and
|
||||
interleave them, as shown in Figure 20.7, which accomplishes the same
|
||||
|
|
@ -119,5 +117,4 @@ always been to grab the Zen timer and *measure actual performance*—and
|
|||
nowhere is this more true than on the Pentium. Don't believe it until
|
||||
you measure it!
|
||||
|
||||
\
|
||||
**Figure 20.8** *Prefix delays.*
|
||||

|
||||
|
|
|
|||
6
21-01.md
6
21-01.md
|
|
@ -95,8 +95,7 @@ Unfortunately, this tends to extend the lifetimes of pointer registers
|
|||
to span a greater number of instructions, making the Pentium's
|
||||
relatively small register set seem even smaller.
|
||||
|
||||
\
|
||||
**Figure 21.1** *An AGI can stall up to three instructions later.*
|
||||

|
||||
|
||||
As an example of a sort of AGI that's new to the Pentium, consider the
|
||||
following test for a NULL pointer, followed by the use of the pointer if
|
||||
|
|
@ -120,5 +119,4 @@ use **TEST EBX,EBX** instead of **AND; TEST** can't modify EBX, so no
|
|||
AGI occurs. Sure, **AND EBX,EBX** doesn't modify EBX either, but the
|
||||
Pentium doesn't know that, so it has to insert the AGI.
|
||||
|
||||
\
|
||||
**Figure 21.2** *An AGI can cost as many as 3 cycles.*
|
||||

|
||||
|
|
|
|||
3
23-03.md
3
23-03.md
|
|
@ -63,8 +63,7 @@ the VGA's hardware for vastly better performance. VGA text modes, which
|
|||
feature soft fonts, are another matter entirely, upon which we'll touch
|
||||
from time to time.
|
||||
|
||||
\
|
||||
**Figure 23.1** *Video data from memory to pixel.*
|
||||

|
||||
|
||||
With that background out of the way, we can get on to the sample VGA
|
||||
program shown in Listing 23.1. I suggest you run the program before
|
||||
|
|
|
|||
3
23-04.md
3
23-04.md
|
|
@ -37,8 +37,7 @@ displayed. For example, the Start Address High register could be set to
|
|||
cause the display screen to reflect memory starting at offset 8000H in
|
||||
each plane, rather than at the default offset of 0.
|
||||
|
||||
\
|
||||
**Figure 23.2** *Video memory organization for Listing 23.1.*
|
||||

|
||||
|
||||
The logical height of the virtual screen is defined by the amount of VGA
|
||||
memory available. As the VGA scans display memory for video data, it
|
||||
|
|
|
|||
3
24-01.md
3
24-01.md
|
|
@ -72,8 +72,7 @@ of the read results in a write operation that logically combines CPU
|
|||
data n with whatever data happens to be in the latches from the last
|
||||
read, which is normally undesirable.
|
||||
|
||||
\
|
||||
**Figure 24.1** *VGA ALU data flow.*
|
||||

|
||||
|
||||
Occasionally, however, the independence of the latches from the display
|
||||
memory location being written to can be used to great advantage. The
|
||||
|
|
|
|||
6
25-01.md
6
25-01.md
|
|
@ -38,8 +38,7 @@ shifted is controlled by bits 2-0 of GC register 3, the Data Rotate
|
|||
register, which also contains the ALU function select bits (data
|
||||
unmodified, AND, OR, and XOR) that we looked at in the last chapter.
|
||||
|
||||
\
|
||||
**Figure 25.1** *Data flow through the Graphics Controller.*
|
||||

|
||||
|
||||
The barrel shifter is powerful, but (as sometimes happens in this
|
||||
business) it sounds more useful than it really is. This is because the
|
||||
|
|
@ -73,8 +72,7 @@ to display memory. Briefly, the bit mask determines on a bit-by-bit
|
|||
basis whether the source for each byte written to display memory is the
|
||||
ALU for that plane or the latch for that plane.
|
||||
|
||||
\
|
||||
**Figure 25.2** *Bit mask operation.*
|
||||

|
||||
|
||||
The bit mask is controlled by GC register 8, the Bit Mask register. If a
|
||||
given bit of the Bit Mask register is 1, then the corresponding bit of
|
||||
|
|
|
|||
3
25-03.md
3
25-03.md
|
|
@ -72,8 +72,7 @@ the Map Mask register would be set to 09H to draw in high-intensity
|
|||
blue; here, bits 0 and 3 are set to 1, so only the blue plane (plane 0)
|
||||
and the intensity plane (plane 3) are written to.
|
||||
|
||||
\
|
||||
**Figure 25.3** *Data flow during a write mode 0 write operation.*
|
||||

|
||||
|
||||
Remember, though, that planes that are disabled by the Map Mask register
|
||||
are not written to or modified in any way. This means that the above
|
||||
|
|
|
|||
3
27-01.md
3
27-01.md
|
|
@ -92,8 +92,7 @@ the latch byte. Finally, the byte from the bit mask circuitry for each
|
|||
plane is written to that plane if the corresponding bit in the Map Mask
|
||||
register is set to 1.
|
||||
|
||||
\
|
||||
**Figure 27.1** *VGA data flow in write mode 2.*
|
||||

|
||||
|
||||
------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
 *It's worth noting two differences between write mode 2 and write mode 0, the standard write mode of the VGA. First, rotation of the CPU data byte does not take place in write mode 2. Second, the Set/Reset and Enable Set/Reset registers have no effect in write mode 2.*
|
||||
|
|
|
|||
3
29-01.md
3
29-01.md
|
|
@ -45,8 +45,7 @@ here, putting the screen into mode 10H, putting up some bittext so there
|
|||
is something to save, and creating the 112K file SNAPSHOT.SCR, which
|
||||
contains the visible portion of the mode 10H frame buffer.
|
||||
|
||||
\
|
||||
**Figure 29.1** *Saving EGA/VGA display memory.*
|
||||

|
||||
|
||||
The only part of Listing 29.1 that's even remotely tricky is the use of
|
||||
the Read Map register (Graphics Controller register 4) to make each of
|
||||
|
|
|
|||
3
29-03.md
3
29-03.md
|
|
@ -114,5 +114,4 @@ monitor. The best way to figure out what the 64 colors look like on your
|
|||
monitor is to see them, and that's just what the program in Listing
|
||||
29.3, which we'll discuss shortly, lets you do.
|
||||
|
||||
\
|
||||
**Figure 29.2** *Color translation via the palette registers.*
|
||||

|
||||
|
|
|
|||
3
29-04.md
3
29-04.md
|
|
@ -34,8 +34,7 @@ of video function 10H. If AL = 3 (subfunction 3), bit 0 of BL is set to
|
|||
1 to cause bit 7 of text attributes to select blinking, or set to 0 to
|
||||
cause bit 7 of text attributes to select highreverse video.)
|
||||
|
||||
\
|
||||
**Figure 29.3** *Bit organization within a palette register.*
|
||||

|
||||
|
||||
Listing 29.3 uses video function 10H, subfunction 2 to step through all
|
||||
64 possible colors. This is accomplished by putting up 16 color bars,
|
||||
|
|
|
|||
3
29-06.md
3
29-06.md
|
|
@ -78,5 +78,4 @@ publisher. You'd be doing a favor for a whole generation of graphics
|
|||
programmers who aren't sure whether they're skating on thin ice without
|
||||
those legendary delays.
|
||||
|
||||
\
|
||||
**Figure 29.4** *Graphics mode register fields.*
|
||||

|
||||
|
|
|
|||
3
30-01.md
3
30-01.md
|
|
@ -65,8 +65,7 @@ first scan line on the screen *and* the second scan line. There is no
|
|||
way to make the split screen cover the entire screen—it always comes up
|
||||
at least one scan line short.
|
||||
|
||||
\
|
||||
**Figure 30.1** *Display memory and the split screen.*
|
||||

|
||||
|
||||
So, where is the split screen start scan line stored? The answer varies
|
||||
a bit, depending on whether you're talking about the EGA or the VGA. On
|
||||
|
|
|
|||
5
31-02.md
5
31-02.md
|
|
@ -32,9 +32,8 @@ right and from the end of one scan line to the start of the next. Then
|
|||
the pixel number, n, of the pixel at display memory address *address* in
|
||||
plane *plane* is:
|
||||
|
||||
\
|
||||
**Figure 31.1** *Bitmap organization in 320x400 256-color mode in
|
||||
320x400 256-color mode.*
|
||||

|
||||
|
||||
*n* = (*address* \* 4) + *plane*
|
||||
|
||||
|
|
|
|||
3
32-05.md
3
32-05.md
|
|
@ -103,8 +103,7 @@ it's worth the trouble. Even the small taste we've gotten of the
|
|||
capabilities of these modes shows that they put the traditional CGA,
|
||||
EGA, and generally even VGA modes to shame.
|
||||
|
||||
\
|
||||
**Figure 32.1** *Pixel organization in 360x480 256-color mode.*
|
||||

|
||||
|
||||
There's more and better to come, though; in later chapters, we'll return
|
||||
to high-resolution 256-color programming in a big way, by exploring the
|
||||
|
|
|
|||
3
33-01.md
3
33-01.md
|
|
@ -102,8 +102,7 @@ each signal), and sends the three analog signals to the monitor. The DAC
|
|||
is a separate chip, external to the VGA chip, but it's an integral part
|
||||
of the VGA standard and is present on every VGA.
|
||||
|
||||
\
|
||||
**Figure 33.1** *The VGA color generation path.*
|
||||

|
||||
|
||||
(I'd like to take a moment to point out that you can't speak of "color"
|
||||
at any point in the color translation process until the output stage of
|
||||
|
|
|
|||
3
35-01.md
3
35-01.md
|
|
@ -137,5 +137,4 @@ a more accurate approximation of the line can be drawn by placing a
|
|||
given pixel one unit of screen resolution away from its predecessor in
|
||||
either the horizontal or the vertical direction, or both.
|
||||
|
||||
\
|
||||
**Figure 35.1** *Approximating a true line from a pixel array.*
|
||||

|
||||
|
|
|
|||
6
35-02.md
6
35-02.md
|
|
@ -37,8 +37,7 @@ In Figure 35.2, the X dimension is the major dimension. This means that
|
|||
drawn. The trick, then, is to decide on the correct Y coordinates to
|
||||
accompany those X coordinates.
|
||||
|
||||
\
|
||||
**Figure 35.2** *Drawing between two pixel endpoints.*
|
||||

|
||||
|
||||
It's easy enough to select the Y coordinates by eye in Figure 35.2. The
|
||||
appropriate Y coordinates are 0, 0, 1, 1, 2, 2, based on the Y
|
||||
|
|
@ -73,8 +72,7 @@ the next than to the current Y coordinate. The third pixel is drawn at
|
|||
adjustment of one pixel in the current Y coordinate. The running error
|
||||
of the pixel actually drawn at this point is C minus D.
|
||||
|
||||
\
|
||||
**Figure 35.3** *The error term in Bresenham's algorithm.*
|
||||

|
||||
|
||||
The fourth pixel has an X coordinate of 3. The running error at this
|
||||
point is E minus D; since this is less than 1/2, the current Y
|
||||
|
|
|
|||
3
35-04.md
3
35-04.md
|
|
@ -151,5 +151,4 @@ when the running error of the line dictates. In octants 1 and 2, the Y
|
|||
coordinate changes on every pixel and the X coordinate changes only when
|
||||
the running error dictates, since Y is the major axis.
|
||||
|
||||
\
|
||||
**Figure 35.4** *Bresenham's eight possible line orientations.*
|
||||

|
||||
|
|
|
|||
3
35-05.md
3
35-05.md
|
|
@ -41,8 +41,7 @@ slower to do so. The same could be said of having **EVGADot** set the
|
|||
Enable Set/Reset and Set/Reset registers for each pixel: While
|
||||
modularity would improve, speed would suffer markedly.
|
||||
|
||||
\
|
||||
**Figure 35.5** *EVGALine's decision logic.*
|
||||

|
||||
|
||||
#### Drawing Each Line {#Heading8}
|
||||
|
||||
|
|
|
|||
3
36-01.md
3
36-01.md
|
|
@ -126,5 +126,4 @@ 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.*
|
||||

|
||||
|
|
|
|||
9
36-02.md
9
36-02.md
|
|
@ -45,11 +45,9 @@ possible divisor. Both approaches produce the desired result, but that
|
|||
which takes maximum advantage of the available information and minimizes
|
||||
redundant work is preferable.
|
||||
|
||||
\
|
||||
**Figure 36.2** *Run-length slice line drawing.*
|
||||

|
||||
|
||||
\
|
||||
**Figure 36.3** *Runs in a slope 1/3.5 line.*
|
||||

|
||||
|
||||
### Run-Length Slice Implementation {#Heading4}
|
||||
|
||||
|
|
@ -78,8 +76,7 @@ close we are to needing an extra pixel; when the fractional sum reaches
|
|||
subtract 1 from the running sum (because we just advanced one pixel),
|
||||
and continue on.
|
||||
|
||||
\
|
||||
**Figure 36.4** *How the error term determines run length.*
|
||||

|
||||
|
||||
Practically speaking, however, we can't work with fractions because
|
||||
floating-point arithmetic is slow and fixed-point arithmetic is
|
||||
|
|
|
|||
5
36-03.md
5
36-03.md
|
|
@ -54,9 +54,8 @@ an optimized version, but for now, Listing 36.1 will make it much easier
|
|||
to grasp the principles of run-length slice drawing, and to understand
|
||||
the optimized code I'll present in the next chapter.
|
||||
|
||||
\
|
||||
**Figure 36.5** *Balancing run-length slice lines: a) unbalanced; b)
|
||||
balanced.*
|
||||

|
||||
|
||||
**LISTING 36.1 L36-1.C**
|
||||
|
||||
|
|
|
|||
8
38-01.md
8
38-01.md
|
|
@ -93,8 +93,7 @@ is reached. At first glance, rasterization does not seem to be
|
|||
particularly complicated, although it should be apparent that this
|
||||
simple approach is inadequate for nonconvex polygons.
|
||||
|
||||
\
|
||||
**Figure 38.1** *Convex, nonconvex, and complex polygons.*
|
||||

|
||||
|
||||
There are a couple of complications, however. The lesser complication is
|
||||
how to rasterize the polygon efficiently, given that it's difficult to
|
||||
|
|
@ -126,9 +125,8 @@ polygon drawn unfilled. Such polygons will look pretty much as they're
|
|||
supposed to, and all drawing on raster displays is, after all, only an
|
||||
approximation of an ideal.
|
||||
|
||||
\
|
||||
**Figure 38.2** *Drawing polygons with standard line-drawing
|
||||
algorithms.*
|
||||

|
||||
|
||||
There's one great drawback to tracing polygons with standard lines,
|
||||
however: Adjacent polygons won't fit together properly, as shown in
|
||||
|
|
|
|||
3
38-02.md
3
38-02.md
|
|
@ -25,8 +25,7 @@ with that, we're going to adopt the following rules:
|
|||
interior of the polygon is directly to the right (left edges are
|
||||
drawn, right edges aren't).
|
||||
|
||||
\
|
||||
**Figure 38.3** *The adjacent polygons problem.*
|
||||

|
||||
|
||||
* Points located exactly on horizontal edges are drawn only if the
|
||||
interior of the polygon is directly below them (horizontal top edges
|
||||
|
|
|
|||
6
40-01.md
6
40-01.md
|
|
@ -95,8 +95,7 @@ randomly; the X coordinate of an edge at one scan line is a consistent
|
|||
delta from that edge's X coordinate at the last scan line, and that is
|
||||
consistent for the length of the line.
|
||||
|
||||
\
|
||||
**Figure 40.1** *Filling one scan line by finding intersecting edges.*
|
||||

|
||||
|
||||
This allows us to reduce the number of edges that must be checked for
|
||||
intersection; on any given scan line, we only need to check for
|
||||
|
|
@ -109,8 +108,7 @@ running list of currently active edges—called the *active edge table*
|
|||
intersection with the current scan line. Then, we can simply fill each
|
||||
scan line in turn according to the list of active edges at that line.
|
||||
|
||||
\
|
||||
**Figure 40.2** *Checking currently active edges (solid lines).*
|
||||

|
||||
|
||||
Maintaining the AET from one scan line to the next involves three steps:
|
||||
First, we must add to the AET any edges that start on the current scan
|
||||
|
|
|
|||
3
40-02.md
3
40-02.md
|
|
@ -61,8 +61,7 @@ the following:
|
|||
|
||||
7. If either the AET or GET isn't empty, go to step 2.
|
||||
|
||||
\
|
||||
**Figure 40.3** *The global and active edge tables as linked lists.*
|
||||

|
||||
|
||||
That's really all there is to it. Compare Listing 40.1 to the fast
|
||||
convex polygon filling code from Chapter 39, and you'll see that,
|
||||
|
|
|
|||
6
41-02.md
6
41-02.md
|
|
@ -15,11 +15,9 @@ from Chapter 39, modified to be able to handle all monotone-vertical
|
|||
polygons, including nonsimple ones; the edge-scanning code (Listing 39.4
|
||||
from Chapter 39) remains the same, and so is not shown again here.
|
||||
|
||||
\
|
||||
**Figure 41.1** *Monotone-vertical polygons.*
|
||||

|
||||
|
||||
\
|
||||
**Figure 41.2** *Non-monotone-vertical polygons.*
|
||||

|
||||
|
||||
**LISTING 41.2 L41-2.C**
|
||||
|
||||
|
|
|
|||
3
42-01.md
3
42-01.md
|
|
@ -110,8 +110,7 @@ the right location, rather than the jagged pattern of line segments that
|
|||
non-antialiased line-drawing algorithms such as Bresenham's (see
|
||||
Chapters 35, 36, and 37) trace out.
|
||||
|
||||
\
|
||||
**Figure 42.1** *The basic concept of Wu antialiasing.*
|
||||

|
||||
|
||||
You might expect that the implementation of Wu antialiasing would fall
|
||||
into two distinct areas: tracing out the line (that is, finding the
|
||||
|
|
|
|||
3
42-02.md
3
42-02.md
|
|
@ -59,8 +59,7 @@ bits of the first pixel's value. All this works because what the error
|
|||
accumulator accumulates is precisely the ideal line's current distance
|
||||
between the two bracketing pixels.
|
||||
|
||||
\
|
||||
**Figure 42.2** *Wu intensity calculations.*
|
||||

|
||||
|
||||
The intensity calculations take longer to describe than they do to
|
||||
perform. All that's involved is a shift of the error accumulator to
|
||||
|
|
|
|||
9
43-01.md
9
43-01.md
|
|
@ -123,8 +123,7 @@ for a given image would reside in a single plane, we could do away with
|
|||
the cumbersome programming of the VGA's complex hardware that is needed
|
||||
to manipulate images that span multiple planes.
|
||||
|
||||
\
|
||||
**Figure 43.1** *How 4 bits of video data become 6 bits of color.*
|
||||

|
||||
|
||||
All in all, it would be a good deal if we could store each image in a
|
||||
single plane, as shown in Figure 43.2. However, a problem arises when
|
||||
|
|
@ -138,8 +137,6 @@ background-colored) parts of the forward image. Can we do that?
|
|||
|
||||
You bet.
|
||||
|
||||
\
|
||||
**Figure 43.2** *Storing images in separate planes.*
|
||||

|
||||
|
||||
\
|
||||
**Figure 43.3** *The problem of overlapping colors.*
|
||||

|
||||
|
|
|
|||
3
43-02.md
3
43-02.md
|
|
@ -75,8 +75,7 @@ transparency.
|
|||
|
||||
Table: Table 43.1 Palette RAM settings for bit-plane animation.
|
||||
|
||||
\
|
||||
**Figure 43.4** *How pixel precedence works.*
|
||||

|
||||
|
||||
Seems almost too easy, doesn't it? Nonetheless, it works beautifully, as
|
||||
we'll see very shortly. First, though, I'd like to point out that
|
||||
|
|
|
|||
3
43-05.md
3
43-05.md
|
|
@ -53,8 +53,7 @@ during which one image's fringe blanks a portion of another image is
|
|||
noticeable only upon close inspection, and not particularly unaesthetic
|
||||
even then.
|
||||
|
||||
\
|
||||
**Figure 43.5** *Pixel precedence for plane 3 only.*
|
||||

|
||||
|
||||
When a technique has such tremendous visual and performance advantages
|
||||
as does bit-plane animation, it behooves you to design your animation
|
||||
|
|
|
|||
3
44-05.md
3
44-05.md
|
|
@ -73,5 +73,4 @@ performed by displaying page 0 and drawing to page 1, then setting the
|
|||
start address to page 1 to display that page and drawing to page 0, and
|
||||
so on *ad infinitum.*
|
||||
|
||||
\
|
||||
**Figure 44.1** *Memory allocation for mode 10h page flipping.*
|
||||

|
||||
|
|
|
|||
3
44-06.md
3
44-06.md
|
|
@ -110,8 +110,7 @@ by the desire to have as many page-flipped scan lines as possible; you
|
|||
may, if you wish, have fewer page-flipped lines and reserve part of the
|
||||
bitmap for other uses, such as off-screen storage for images.)
|
||||
|
||||
\
|
||||
**Figure 44.2** *Memory allocation for mode 12h page flipping.*
|
||||

|
||||
|
||||
The sample program for this chapter uses the split screen and page
|
||||
flipping exactly as described above. The playfield through which the
|
||||
|
|
|
|||
6
45-02.md
6
45-02.md
|
|
@ -79,11 +79,9 @@ Figure 45.1 illustrates the visual problems associated with drawing
|
|||
directly to the screen; Figure 45.2 shows how dirty-rectangle animation
|
||||
solves these problems.
|
||||
|
||||
\
|
||||
**Figure 45.1** *Drawing directly to the screen.*
|
||||

|
||||
|
||||
\
|
||||
**Figure 45.2** *Dirty rectangle animation.*
|
||||

|
||||
|
||||
#### So Why Not Use Page Flipping? {#Heading6}
|
||||
|
||||
|
|
|
|||
3
47-03.md
3
47-03.md
|
|
@ -56,8 +56,7 @@ routines as basic primitives, and so you'll understand how the bitmap is
|
|||
organized, but the building blocks of high-performance graphics software
|
||||
are fills, copies, and bitblts, and it's there that Mode X shines.
|
||||
|
||||
\
|
||||
**Figure 47.1** *Mode X display memory organization.*
|
||||

|
||||
|
||||
**LISTING 47.2 L47-2.ASM**
|
||||
|
||||
|
|
|
|||
3
47-06.md
3
47-06.md
|
|
@ -47,8 +47,7 @@ to by each CPU access. Thus, it would seem that up to four pixels could
|
|||
be set by a single Mode X byte-sized write to display memory,
|
||||
potentially speeding up operations like rectangle fills by four times.
|
||||
|
||||
\
|
||||
**Figure 47.2** *Selecting planes with the Map Mask register.*
|
||||

|
||||
|
||||
And, as it turns out, four-plane parallelism works quite nicely indeed.
|
||||
Listing 47.6 is yet another rectangle-fill routine, this time using the
|
||||
|
|
|
|||
8
48-01.md
8
48-01.md
|
|
@ -41,12 +41,10 @@ memory byte, 4 bytes—one from each plane—can be loaded into the latches
|
|||
at once. Any or all of those 4 bytes can then be written anywhere in
|
||||
display memory with a single byte-sized write, as shown in Figure 48.2.
|
||||
|
||||
\
|
||||
**Figure 48.1** *How the VGA latches are loaded.*
|
||||

|
||||
|
||||
\
|
||||
**Figure 48.2** *Writing 4 bytes to display memory in a single
|
||||
operation.*
|
||||

|
||||
|
||||
The upshot is that the latches make it possible to copy data around from
|
||||
one part of display memory to another, 32 bits (four pixels) at a
|
||||
|
|
|
|||
3
48-03.md
3
48-03.md
|
|
@ -54,8 +54,7 @@ is used to restore the holes left after images move), the last 16 pixels
|
|||
and the remaining 31,728 pixels (7,932 addresses) of display memory are
|
||||
free for storage of icons, images, temporary buffers, or whatever.
|
||||
|
||||
\
|
||||
**Figure 48.3** *A useful Mode X display memory layout.*
|
||||

|
||||
|
||||
This is an efficient organization for animation, but there are certainly
|
||||
many other possible setups. For example, you might choose to have a
|
||||
|
|
|
|||
3
49-04.md
3
49-04.md
|
|
@ -54,8 +54,7 @@ background. This continues at a rate of up to 60 times a second until
|
|||
Esc is pressed to exit the program. See Figure 49.1 for a screen shot of
|
||||
the resulting image—add the animation in your imagination.
|
||||
|
||||
\
|
||||
**Figure 49.1** *An animated Mode X screen.*
|
||||

|
||||
|
||||
**LISTING 49.5 L49-5.C**
|
||||
|
||||
|
|
|
|||
15
50-02.md
15
50-02.md
|
|
@ -25,11 +25,9 @@ confusion. Therefore, Z decreases as distance along the line of sight
|
|||
increases; a view space coordinate of (0,0,-1000) is directly ahead,
|
||||
twice as far away as a coordinate of (0,0,-500).
|
||||
|
||||
\
|
||||
**Figure 50.1** *The 3-D drawing pipeline.*
|
||||

|
||||
|
||||
\
|
||||
**Figure 50.2** *A right-handed coordinate system.*
|
||||

|
||||
|
||||
#### Projection {#Heading5}
|
||||
|
||||
|
|
@ -62,8 +60,7 @@ for example, used to move objects from object space, in which the center
|
|||
of the object is typically the origin (0,0,0), into world space, where
|
||||
the object may be located anywhere.
|
||||
|
||||
\
|
||||
**Figure 50.3** *Perspective projection.*
|
||||

|
||||
|
||||
#### Rotation {#Heading7}
|
||||
|
||||
|
|
@ -80,8 +77,7 @@ First, it is possible to concatenate multiple rotations into a single
|
|||
matrix by multiplying them together in the desired order; that single
|
||||
matrix can then be used to perform the rotations more efficiently.
|
||||
|
||||
\
|
||||
**Figure 50.4** *3-D rotation formulas.*
|
||||

|
||||
|
||||
Second, 3x3 rotation matrices can become the upper-left-hand portions of
|
||||
4x4 matrices that also perform translation (and scaling as well, but we
|
||||
|
|
@ -112,8 +108,7 @@ functionality), and the ability to draw the projected polygon (complete
|
|||
with clipping) and handle the other details of animation (2-D
|
||||
functionality).
|
||||
|
||||
\
|
||||
**Figure 50.5** *A 4x4 Transformation Matrix.*
|
||||

|
||||
|
||||
Happily (and not coincidentally), we put together a nice 2-D animation
|
||||
framework back in Chapters 47, 48, and 49, during our exploratory
|
||||
|
|
|
|||
8
51-02.md
8
51-02.md
|
|
@ -51,8 +51,7 @@ choose to work with and the order in which we evaluate them, so we must
|
|||
establish some conventions for defining polygons and evaluating the
|
||||
cross-product.
|
||||
|
||||
\
|
||||
**Figure 51.1** *The cross-product of two vectors.*
|
||||

|
||||
|
||||
We'll define only convex polygons, with the vertices defined in
|
||||
clockwise order, as viewed from the outside; that is, if you're looking
|
||||
|
|
@ -78,9 +77,8 @@ viewer, and, despite its name, view space does not provide that
|
|||
information; unlike screen space, it does not reflect perspective
|
||||
effects.
|
||||
|
||||
\
|
||||
**Figure 51.2** *Using the cross product to generate a polygon
|
||||
normal.*
|
||||

|
||||
|
||||
Backface removal may also be performed using the polygon vertices in
|
||||
screen coordinates, which are integers. This is less accurate than using
|
||||
|
|
|
|||
6
51-04.md
6
51-04.md
|
|
@ -116,8 +116,7 @@ around the X axis, which runs horizontally across the screen; the latter
|
|||
four keys are most conveniently used by flipping the keypad to the
|
||||
numeric state.
|
||||
|
||||
\
|
||||
**Figure 51.3** *Sample screens from the 3-D cube program.*
|
||||

|
||||
|
||||
The demo involves six polygons, one for each side of the cube. Each of
|
||||
the polygons must be transformed and projected, so it would seem that 24
|
||||
|
|
@ -143,5 +142,4 @@ help a great deal to flag which of the objects had moved with respect to
|
|||
the viewer, performing a new transformation and projection only for
|
||||
those that had.
|
||||
|
||||
\
|
||||
**Figure 51.4** *The object data structure*
|
||||

|
||||
|
|
|
|||
5
53-03.md
5
53-03.md
|
|
@ -105,6 +105,5 @@ point for each object is now transformed along with the vertices. That's
|
|||
really all there is to depth sorting—and now we can have objects that
|
||||
overlap in X and Y.
|
||||
|
||||
\
|
||||
**Figure 53.1** *Why back-to-front sorting doesn't always work
|
||||
properly.*
|
||||

|
||||
|
|
|
|||
6
54-03.md
6
54-03.md
|
|
@ -90,11 +90,9 @@ have that, we can easily calculate the red diffuse shading from a
|
|||
directed light source as min(ID~red~xR~red~x(L'• N), 1) and likewise for
|
||||
the green and blue color components.
|
||||
|
||||
\
|
||||
**Figure 54.1** *Illumination by a directed light source*
|
||||

|
||||
|
||||
\
|
||||
**Figure 54.2** *The dot product of two vectors.*
|
||||

|
||||
|
||||
The overall red shading for each polygon can be calculated by summing
|
||||
the ambient-shading red component with the diffuse-shading component
|
||||
|
|
|
|||
6
54-05.md
6
54-05.md
|
|
@ -30,11 +30,9 @@ end-points has to be transformed, along with the rest of the vertices,
|
|||
and that takes time. Still, it's faster than calculating a unit normal
|
||||
for each polygon from scratch.
|
||||
|
||||
\
|
||||
**Figure 54.3** *The unit normal in the polygon data structure.*
|
||||

|
||||
|
||||
\
|
||||
**Figure 54.4** *The reversed light source vector.*
|
||||

|
||||
|
||||
We also need a unit vector for each directed light source. The directed
|
||||
light sources I've implemented in X-Sharp are spotlights; that is,
|
||||
|
|
|
|||
3
55-01.md
3
55-01.md
|
|
@ -75,8 +75,7 @@ Half-bright cyan is 0.0\*R, 0.5\*G, 0.5\*B. Quarter-bright gray is
|
|||
cube, as shown in Figure 55.1, with any particular color lying somewhere
|
||||
inside or on the cube.
|
||||
|
||||
\
|
||||
**Figure 55.1** *The RGB color cube.*
|
||||

|
||||
|
||||
RGB is good for modeling colors generated by light sources, because red,
|
||||
green, and blue are the additive primaries; that is, all other colors
|
||||
|
|
|
|||
3
55-03.md
3
55-03.md
|
|
@ -121,8 +121,7 @@ incredibly slow, so we'd like to reduce the number of accesses as much
|
|||
as possible. With the BitMan's approach, we can reduce the number of
|
||||
accesses to just one per font byte, and eliminate flicker, too.
|
||||
|
||||
\
|
||||
**Figure 55.2** *Drawing solid text.*
|
||||

|
||||
|
||||
The keys to fast solid text are the latches and write mode 3. The
|
||||
latches, as you may recall from earlier discussions in this book, are
|
||||
|
|
|
|||
3
55-04.md
3
55-04.md
|
|
@ -24,8 +24,7 @@ Figure 55.3. Thus, each byte written by the CPU (font data, presumably)
|
|||
selects foreground or background color for each of eight pixels, all
|
||||
done with a single write to display memory.
|
||||
|
||||
\
|
||||
**Figure 55.3** *The data path in write mode 3.*
|
||||

|
||||
|
||||
I know this sounds pretty esoteric, but think of it this way: The
|
||||
latches hold the background color in a form suitable for writing eight
|
||||
|
|
|
|||
8
56-01.md
8
56-01.md
|
|
@ -93,9 +93,8 @@ calculate the value for each destination pixel; that is, to antialias
|
|||
the image. This can greatly improve texture quality, although it is
|
||||
slower.
|
||||
|
||||
\
|
||||
**Figure 56.1** *Using reverse transformation to find the source pixel
|
||||
color.*
|
||||

|
||||
|
||||
#### Mapping Textures Made Easy {#Heading4}
|
||||
|
||||
|
|
@ -116,8 +115,7 @@ and the polygon, and the pixel mapping is one-to-one, so the appropriate
|
|||
part of each scan line of the image can simply be block copied to the
|
||||
destination.
|
||||
|
||||
\
|
||||
**Figure 56.2** *Mapping a texture onto an untransformed polygon.*
|
||||

|
||||
|
||||
Now, matters get more complicated. What if the destination polygon is
|
||||
rotated in two dimensions? We no longer have a neat direct mapping from
|
||||
|
|
|
|||
11
56-02.md
11
56-02.md
|
|
@ -21,8 +21,7 @@ is the height of the destination edge. The this approach arranges to
|
|||
step the source image edge **DestYHeight** times also, to match what the
|
||||
destination is doing.
|
||||
|
||||
\
|
||||
**Figure 56.3** *Mapping a texture onto a 2-D rotated polygon.*
|
||||

|
||||
|
||||
Now we're able to track the coordinates of the polygon edges through the
|
||||
source image in tandem with the destination edges. Stepping across each
|
||||
|
|
@ -63,12 +62,10 @@ image, and everything else happens automatically. In fact, mapping from
|
|||
any polygonal area of a bitmap to any destination polygon will work,
|
||||
given only that the two polygons have the same number of vertices.
|
||||
|
||||
\
|
||||
**Figure 56.4** *Mapping a horizontal destination scan line back to
|
||||
the source image.*
|
||||

|
||||
|
||||
\
|
||||
**Figure 56.5** *Mapping a texture onto a narrower polygon.*
|
||||

|
||||
|
||||
#### Notes on DDA Texture Mapping\
|
||||
\
|
||||
|
|
|
|||
5
57-02.md
5
57-02.md
|
|
@ -34,9 +34,8 @@ one new column of pixels from the next row and column of the texture
|
|||
map. This asymmetry was quite visible, and not at all the desired
|
||||
effect.
|
||||
|
||||
\
|
||||
**Figure 57.1** *Gaps caused by mixing fixed-point and all-integer
|
||||
math.*
|
||||

|
||||
|
||||
Listing 57.1 is one solution to these problems. This code, which
|
||||
replaces the equivalently named function presented in the previous
|
||||
|
|
|
|||
3
58-01.md
3
58-01.md
|
|
@ -128,5 +128,4 @@ up the code, but soon realized that all the clever coding in the world
|
|||
wasn't going to get me within 100 percent of John's performance so long
|
||||
as I had to cycle from one plane to the next for every pixel.
|
||||
|
||||
\
|
||||
**Figure 58.1** *Texture mapping a single horizontal scanline.*
|
||||

|
||||
|
|
|
|||
3
58-02.md
3
58-02.md
|
|
@ -70,8 +70,7 @@ of 18 cycles, double John's 9 cycles, just to handle plane management.
|
|||
Clearly, getting plane control out of the inner loop was absolutely
|
||||
necessary.
|
||||
|
||||
\
|
||||
**Figure 58.2** *Display memory organization in Mode X.*
|
||||

|
||||
|
||||
I must confess, with some embarrassment, that at this point I threw
|
||||
myself into designing a solution that involved executing the texture
|
||||
|
|
|
|||
18
58-03.md
18
58-03.md
|
|
@ -20,8 +20,7 @@ the least.
|
|||
 *That's what Zen programming is all about, though; tying together two pieces of seemingly unrelated information to good effect—and that's what I had failed to do. Like Robert Heinlein—like all of us—I had viewed the world through a filter composed of my ingrained assumptions, and one of those assumptions, based on all my past experience, was that pixel processing proceeds left to right. Eventually, I might have come up with Chris's approach; but I would only have come up with it when and if I relaxed and stepped back a little, and allowed myself—almost dared myself—to think of it. When you're optimizing, be sure to leave quiet, nondirected time in which to conjure up those less obvious solutions, and periodically try to figure out what assumptions you're making—and then question them!*
|
||||
------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
\
|
||||
**Figure 58.3** *Texture mapping a single vertical column.*
|
||||

|
||||
|
||||
There are a few complications with Chris's approach, not least that
|
||||
X-Sharp's polygon-filling convention (top and left edges included,
|
||||
|
|
@ -91,9 +90,8 @@ current coordinates with four separate 16-bit operations, and carries
|
|||
from fractional to integer parts are detected via conditional jumps, as
|
||||
shown in Figure 58.4. There's quite a lot we can do to improve this.
|
||||
|
||||
\
|
||||
**Figure 58.4** *Original method for advancing the source texture
|
||||
pointer.*
|
||||

|
||||
|
||||
First, we can sum the X and Y integer advance amounts outside the loop,
|
||||
then add them both to the source pointer with a single instruction.
|
||||
|
|
@ -123,13 +121,11 @@ X and Y, and the following instruction **ADC SI,BP** finishes advancing
|
|||
the source pointer in X. That's a mere 3 cycles, and all that remains is
|
||||
to finish advancing the source pointer in Y.
|
||||
|
||||
\
|
||||
**Figure 58.5** *Efficient method for advancing source texture
|
||||
pointer.*
|
||||

|
||||
|
||||
\
|
||||
**Figure 58.6** *Storing both X and Y fractional coordinates in one
|
||||
register.*
|
||||

|
||||
|
||||
Actually, we also advanced the source pointer by the Y integer amount
|
||||
back when we added BP to SI; all that's left is to detect whether our
|
||||
|
|
|
|||
3
58-04.md
3
58-04.md
|
|
@ -36,8 +36,7 @@ Or can we?
|
|||
; fractional X advance in high word of ECX, bit
|
||||
; 15 set to 0
|
||||
|
||||
\
|
||||
**Figure 58.7** *Final method for advancing source texture pointer.*
|
||||

|
||||
|
||||
; EDX = fractional source texture Y coordinate in lower
|
||||
; 15 bits of CX, fractional source texture X coord
|
||||
|
|
|
|||
6
59-02.md
6
59-02.md
|
|
@ -29,8 +29,7 @@ that are children of that node must likewise be outside the view volume,
|
|||
for reasons that will become clear as we delve into the workings of BSP
|
||||
trees.
|
||||
|
||||
\
|
||||
**Figure 59.1** *The painter's algorithm.*
|
||||

|
||||
|
||||
#### Limitations of BSP Trees {#Heading5}
|
||||
|
||||
|
|
@ -109,8 +108,7 @@ into two again, and so on, until each wall resides in its own unique
|
|||
subspace. An obvious question, then, is how should we carve up the world
|
||||
of Figure 59.2?
|
||||
|
||||
\
|
||||
**Figure 59.2** *A sample set of walls, viewed from above.*
|
||||

|
||||
|
||||
There are infinitely valid ways to carve up Figure 59.2, but the
|
||||
simplest is just to carve along the lines of the walls themselves, with
|
||||
|
|
|
|||
24
59-03.md
24
59-03.md
|
|
@ -18,8 +18,7 @@ valid choice for the initial split; we'll return to the issue of
|
|||
choosing splitting walls in the next chapter.) This splitting into front
|
||||
and back is the essential dualism of BSP trees.
|
||||
|
||||
\
|
||||
**Figure 59.3** *Initial split along the line of wall C.*
|
||||

|
||||
|
||||
Next, in Figure 59.4, the front subspace of wall C is split by wall D.
|
||||
This is the only wall in that subspace, so we're done with wall C's
|
||||
|
|
@ -30,13 +29,11 @@ There's a difference here, though: Wall A straddles the splitting line
|
|||
generated from wall B. Does wall A belong in the front or back subspace
|
||||
of wall B?
|
||||
|
||||
\
|
||||
**Figure 59.4** *Split of wall C's front subspace along the line of
|
||||
wall D.*
|
||||

|
||||
|
||||
\
|
||||
**Figure 59.5** *Split of wall C's back subspace along the line of
|
||||
wall B.*
|
||||

|
||||
|
||||
Both, actually. Wall A gets split into two pieces, which I'll call wall
|
||||
A and wall E; each piece is assigned to the appropriate subspace and
|
||||
|
|
@ -75,11 +72,9 @@ children is on the far side from the viewpoint, draw the wall, and then
|
|||
visit the node's nearer child, in that order. Visiting a child is
|
||||
recursive, involving the same far-near visiting order.
|
||||
|
||||
\
|
||||
**Figure 59.6** *The final BSP tree.*
|
||||

|
||||
|
||||
\
|
||||
**Figure 59.7** *Viewing the BSP tree from an arbitrary angle.*
|
||||

|
||||
|
||||
The key is that each BSP splitting line separates all the walls in the
|
||||
current subspace into two groups relative to the viewpoint, and every
|
||||
|
|
@ -88,9 +83,8 @@ single member of the nearer. By applying this ordering recursively, the
|
|||
BSP tree can be traversed to provide back-to-front or front-to-back
|
||||
ordering, with each node being visited only once.
|
||||
|
||||
\
|
||||
**Figure 59.8** *Back-to-front traversal of the BSP tree as viewed in
|
||||
Figure 59.7.*
|
||||

|
||||
|
||||
The type of tree walk used to produce front-to-back or back-to-front BSP
|
||||
traversal is known as an *inorder* walk. More on this very shortly;
|
||||
|
|
|
|||
3
59-04.md
3
59-04.md
|
|
@ -39,8 +39,7 @@ visits each node in a passed-in tree in inorder sequence. Each candidate
|
|||
unhesitatingly writes something like the perfectly good code in Listings
|
||||
59.2 and 59.3 shown next.
|
||||
|
||||
\
|
||||
**Figure 59.9** *An inorder walk of a BSP tree.*
|
||||

|
||||
|
||||
**Listing 59.2 L59\_2.C**
|
||||
|
||||
|
|
|
|||
9
60-02.md
9
60-02.md
|
|
@ -72,11 +72,9 @@ the original line segment. The biggest win, however, is that it allows
|
|||
us to use parametric line clipping, a very clean form of clipping,
|
||||
indeed.
|
||||
|
||||
\
|
||||
**Figure 60.1** *A sample parametric line.*
|
||||

|
||||
|
||||
\
|
||||
**Figure 60.2** *Line segment storage in the BSP compiler.*
|
||||

|
||||
|
||||
#### Parametric Line Clipping {#Heading5}
|
||||
|
||||
|
|
@ -114,8 +112,7 @@ x and y values. If you look closely at Listing 60.1, the core of the BSP
|
|||
compiler, you'll see that the parametric clipping code itself is
|
||||
exceedingly short and simple.
|
||||
|
||||
\
|
||||
**Figure 60.3** *How line intersection is calculated.*
|
||||

|
||||
|
||||
One interesting point about Listing 60.1 is that it generates normals to
|
||||
splitting surfaces simply by exchanging the x and y lengths of the
|
||||
|
|
|
|||
8
61-02.md
8
61-02.md
|
|
@ -88,8 +88,7 @@ intensity at which the surface is illuminated, as in
|
|||
|
||||
(eq. 5)
|
||||
|
||||
\
|
||||
**Figure 61.1** *The dot product.*
|
||||

|
||||
|
||||
where **I**~s~ is the intensity of illumination of the surface, **I**~l~
|
||||
is the intensity of the light, and q is the angle between **-D**~l~
|
||||
|
|
@ -122,6 +121,5 @@ normals are still normals in viewspace, but perspective projection does
|
|||
not preserve angles, so vectors that were surface normals in viewspace
|
||||
are no longer normals in screenspace.
|
||||
|
||||
\
|
||||
**Figure 61.2** *The dot product as used in calculating lighting
|
||||
intensity.*
|
||||

|
||||
|
|
|
|||
11
61-03.md
11
61-03.md
|
|
@ -22,8 +22,7 @@ its screenspace normal has a positive z, and vice-versa, as shown in
|
|||
Figure 61.3. So we need screenspace normals, but those can't readily be
|
||||
generated by transformation from worldspace.
|
||||
|
||||
\
|
||||
**Figure 61.3** *A problem with determining front/back visibility.*
|
||||

|
||||
|
||||
The solution is to use the cross product of two of the polygon's edges
|
||||
to generate a normal. The formula for the cross product is:
|
||||
|
|
@ -45,9 +44,8 @@ polygon edges, as shown in Figure 61.4.
|
|||
 *In fact, we can cull with only one-third the work needed to generate a full cross product; because we're interested only in the sign of the z component of the normal, we can skip entirely calculating the x and y components. The only caveat is to be careful that neither edge you choose is zero-length and that the edges aren't collinear, because the dot product can't produce a normal in those cases.*
|
||||
------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
\
|
||||
**Figure 61.4** *How the cross product of polygon edge vectors
|
||||
generates a polygon normal.*
|
||||

|
||||
|
||||
Perhaps the most often asked question about cross products is "Which way
|
||||
do normals generated by cross products go?" In a left-handed coordinate
|
||||
|
|
@ -113,5 +111,4 @@ clipping is needed. And yes, the dot product is also the way to do the
|
|||
actual clipping; but before we can talk about that, we need to
|
||||
understand the use of the dot product for projection.
|
||||
|
||||
\
|
||||
**Figure 61.5** *Backface culling with the dot product.*
|
||||

|
||||
|
|
|
|||
15
61-04.md
15
61-04.md
|
|
@ -28,9 +28,8 @@ equally. What it all works out to is that the value of the dot product
|
|||
of any vector with a unit vector is the length of the first vector
|
||||
projected onto the unit vector, as shown in Figure 61.6.
|
||||
|
||||
\
|
||||
**Figure 61.6** *How the dot product with a unit vector performs a
|
||||
projection.*
|
||||

|
||||
|
||||
This unlocks all sorts of neat stuff. Want to know the distance from a
|
||||
point to a plane? Just dot the vector from the point **P** to the plane
|
||||
|
|
@ -94,9 +93,8 @@ on. So when two 3-D experts, John Carmack and Billy Zelsnack, mentioned
|
|||
that they think of rotation differently, in a way that seemed more
|
||||
intuitive to me, I thought it was worth passing on.
|
||||
|
||||
\
|
||||
**Figure 61.7** *Using the dot product to get the distance from a
|
||||
point to a plane.*
|
||||

|
||||
|
||||
Their approach is this: Think of rotation as projecting coordinates onto
|
||||
new axes. That is, given that you have points in, say, worldspace,
|
||||
|
|
@ -119,6 +117,5 @@ and that new tools, or new ways to use old tools, are Good Things. My
|
|||
experience has been that rotation by projection, and dot product tricks
|
||||
in general, offer those sorts of benefits for 3-D.
|
||||
|
||||
\
|
||||
**Figure 61.8** *Rotation to a new coordinate space by projection onto
|
||||
new axes.*
|
||||

|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue