Convert figure images to markdown

This commit is contained in:
James Gregory 2013-12-31 07:56:10 +11:00
commit 77a95ec319
115 changed files with 218 additions and 403 deletions

View file

@ -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.
![](images/02-01.jpg)\
**Figure 2.1**  *The high-level language transformation
inefficiencies.*
![**Figure 2.1** *The high-level language transformation
inefficiencies.*](images/02-01.jpg)
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.
![](images/02-02.jpg)\
**Figure 2.2**  *Properly constructed assembly programs suffer no
transformation loss.*
![**Figure 2.2**  *Properly constructed assembly programs suffer no
transformation loss.*](images/02-02.jpg)
The key, of course, is the programmer, since in assembly the programmer
must essentially perform the transformation from the application

View file

@ -79,8 +79,7 @@ attention.
Timer 1 is dedicated to providing dynamic RAM refresh, and should not be
tampered with lest system crashes result.
![](images/03-01.jpg)\
**Figure 3.1**  *The configuration of the 8253 timer chip in the PC.*
![**Figure 3.1**  *The configuration of the 8253 timer chip in the PC.*](images/03-01.jpg)
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,

View file

@ -26,8 +26,7 @@ timer from C, as, for example, in:
timer—Listing 3.5—requires the same modifications, but to different
lines.)
![](images/03-02.jpg)\
**Figure 3.2**  *Changes for use with small code model C.*
![**Figure 3.2**  *Changes for use with small code model C.*](images/03-02.jpg)
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
![](images/03-03.jpg)\
**Figure 3.3**  *Changes for use with large code model C.*
![**Figure 3.3**  *Changes for use with large code model C.*](images/03-03.jpg)
timer, because our purpose in calling the reference timing code is to
determine exactly how much time is taken by overhead code—including the

View file

@ -10,11 +10,9 @@ chapter: '04'
pages: 080-084
---
![](images/04-01.jpg)\
**Figure 4.1**  *The location of the major cycle-eaters in the IBM PC.*
![**Figure 4.1**  *The location of the major cycle-eaters in the IBM PC.*](images/04-01.jpg)
![](images/04-02.jpg)\
**Figure 4.2**  *Internal data bus widths of the 8088.*
![**Figure 4.2**  *Internal data bus widths of the 8088.*](images/04-02.jpg)
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

View file

@ -128,9 +128,8 @@ that the "true" execution time of **SHR** is 8.64 cycles.
endm
call ZTimerOff
![](images/04-03.jpg)\
**Figure 4.3**  *Execution and instruction prefetching sequence for
Listing 4.5.*
![**Figure 4.3**  *Execution and instruction prefetching sequence for
Listing 4.5.*](images/04-03.jpg)
Now let's examine Listing 4.6. Here each **SHR** follows a **MUL**
instruction. Since **MUL** instructions take so long to execute that the

View file

@ -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.
![](images/04-04.jpg)\
**Figure 4.4**  *Execution and instruction prefetching sequence for
Listing 4.6.*
![**Figure 4.4**  *Execution and instruction prefetching sequence for
Listing 4.6.*](images/04-04.jpg)
**LISTING 4.7 LST4-7.ASM**

View file

@ -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.
![](images/04-05.jpg)\
**Figure 4.5**  *The PC bus dynamic RAM (DRAM) refresh.*
![**Figure 4.5**  *The PC bus dynamic RAM (DRAM) refresh.*](images/04-05.jpg)

View file

@ -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.
![](images/04-06.jpg)\
**Figure 4.6**  *Video wait states inserted by the display adapter.*
![**Figure 4.6**  *Video wait states inserted by the display adapter.*](images/04-06.jpg)
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.)
![](images/04-07.jpg)\
**Figure 4.7**  *Allocation of display memory access.*
![**Figure 4.7**  *Allocation of display memory access.*](images/04-07.jpg)
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.
![](images/04-08.jpg)\
**Figure 4.8**  *Display memory access slots.*
![**Figure 4.8**  *Display memory access slots.*](images/04-08.jpg)
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.

View file

@ -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()**.
![](images/05-01.jpg)\
**Figure 5.1**  *The brute-force searching technique.*
![**Figure 5.1**  *The brute-force searching technique.*](images/05-01.jpg)
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.
![](images/05-02.jpg)\
**Figure 5.2**  *The faster string-searching technique.*
![**Figure 5.2**  *The faster string-searching technique.*](images/05-02.jpg)
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

View file

@ -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.
![](images/06-01.jpg)\
**Figure 6.1**  *Operation of ADD Reg,Reg vs. LEA Reg,{Addr}.*
![**Figure 6.1**  *Operation of ADD Reg,Reg vs. LEA Reg,{Addr}.*](images/06-01.jpg)
#### 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.
![](images/06-02.jpg)\
**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?

View file

@ -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
![](images/08-01.jpg)\
**Figure 8.1**  *Tweaked compiler output for a loop.*
![**Figure 8.1**  *Tweaked compiler output for a loop.*](images/08-01.jpg)
to something like Figure 8.1A. You might look at that and tweak it to
the code shown in Figure 8.1B.

View file

@ -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.
![](images/08-02.jpg)\
**Figure 8.2**  *Linked array storage format (version 1).*
![**Figure 8.2**  *Linked array storage format (version 1).*](images/08-02.jpg)
As it happens, Microsoft C/C++ does optimize the inner loop of
**FindIDAverage** nicely. Listing 8.2 shows the code Microsoft C/C++

View file

@ -39,8 +39,7 @@ merely rearranged.
extern unsigned int FindIDAverage2(unsigned int,
struct BlockHeader *);
![](images/08-03.jpg)\
**Figure 8.3**  *Linked array storage format (version 2).*
![**Figure 8.3**  *Linked array storage format (version 2).*](images/08-03.jpg)
/* Structure that starts each variable-sized block */
struct BlockHeader {

View file

@ -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**.
![](images/09-01.jpg)\
**Figure 9.1**  *Simple searching method for locating a text string.*
![**Figure 9.1**  *Simple searching method for locating a text string.*](images/09-01.jpg)
Rob's revelation, which he credits without explanation to Edgar Allen
Poe (search nevermore?), was that by far the slowest part of the whole

View file

@ -35,8 +35,7 @@ using **REPZ CMPS** to check scanning matches.
![](images/i.jpg) *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.*
------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/09-02.jpg)\
**Figure 9.2**  *Faster searching method for locating a text string.*
![**Figure 9.2**  *Faster searching method for locating a text string.*](images/09-02.jpg)
**LISTING 9.1 L9-1.ASM**

View file

@ -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.
![](images/09-03.jpg)\
**Figure 9.3**  *Fast multiword division on the 386.*
![**Figure 9.3**  *Fast multiword division on the 386.*](images/09-03.jpg)
As for handling signed division with arbitrarily large dividends, that
can be done easily enough by remembering the signs of the dividend and

View file

@ -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!)
![](images/09-04.jpg)\
**Figure 9.4**  *Performing rotate instructions using the Carry flag.*
![**Figure 9.4**  *Performing rotate instructions using the Carry flag.*](images/09-04.jpg)
Interestingly, according to Intel's *i486 Microprocessor Programmer's
Reference Manual*, the 486 can **RCR** or **RCL** a register by one bit

View file

@ -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.
![](images/10-01.jpg)\
**Figure 10.1**  *Using a brute-force algorithm to find a GCD.*
![**Figure 10.1**  *Using a brute-force algorithm to find a GCD.*](images/10-01.jpg)
**Integer pairs for which to find GCD**

View file

@ -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.
![](images/10-02.jpg)\
**Figure 10.2**  *Using repeated subtraction algorithm to find a GCD.*
![**Figure 10.2**  *Using repeated subtraction algorithm to find a GCD.*](images/10-02.jpg)
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
![](images/i.jpg) *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.*
------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/10-03.jpg)\
**Figure 10.3**  *Using Euclid's algorithm to find a GCD.*
![**Figure 10.3**  *Using Euclid's algorithm to find a GCD.*](images/10-03.jpg)
Give your mind time and space to wander around the edges of important
programming problems before you settle on any one approach. I titled

View file

@ -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.)
![](images/11-01.jpg)\
**Figure 11.1**  *The data alignment cycle-eater.*
![**Figure 11.1**  *The data alignment cycle-eater.*](images/11-01.jpg)
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

View file

@ -22,11 +22,9 @@ no problems until I timed the following code:
loop LoopTop
call ZTimerOff
![](images/11-02.jpg)\
**Figure 11.2**  *Word-aligned prefetching on the 286.*
![**Figure 11.2**  *Word-aligned prefetching on the 286.*](images/11-02.jpg)
![](images/11-03.jpg)\
**Figure 11.3**  *How instruction bytes are fetched after a branch.*
![**Figure 11.3**  *How instruction bytes are fetched after a branch.*](images/11-03.jpg)
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

View file

@ -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.
![](images/11-04.jpg)\
**Figure 11.4**  *The operation of POPF.*
![**Figure 11.4**  *The operation of POPF.*](images/11-04.jpg)
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.
![](images/11-05.jpg)\
**Figure 11.5**  *The operation of IRET.*
![**Figure 11.5**  *The operation of IRET.*](images/11-05.jpg)
The operation of this code is illustrated in Figure 11.6.
@ -106,8 +104,7 @@ an 8088.)
iret
endm
![](images/11-06.jpg)\
**Figure 11.6**  *Workaround code for the POPF bug.*
![**Figure 11.6**  *Workaround code for the POPF bug.*](images/11-06.jpg)
The standard version of **EMULATE\_POPF** is 6 bytes longer than
**POPF** and much slower, as you'd expect given that it involves three

View file

@ -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,
![](images/12-01.jpg)\
**Figure 12.1**  *One-cycle-ahead address pipelining.*
![**Figure 12.1**  *One-cycle-ahead address pipelining.*](images/12-01.jpg)
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.
![](images/12-02.jpg)\
**Figure 12.2**  *Two-cycle-ahead address pipelining.*
![**Figure 12.2**  *Two-cycle-ahead address pipelining.*](images/12-02.jpg)
### Caveat Programmor {#Heading7}

View file

@ -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.
![](images/13-01.jpg)\
**Figure 13.1**  *Cycle-eaters in the original WC.*
![**Figure 13.1**  *Cycle-eaters in the original WC.*](images/13-01.jpg)
------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/i.jpg) *Remember, pipeline penalties diminish with increasing number of cycles, not instructions, between the pipeline disrupter and the potentially affected instruction.*

View file

@ -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.
![](images/13-02.jpg)\
**Figure 13.2**  *BSWAP in operation.*
![**Figure 13.2**  *BSWAP in operation.*](images/13-02.jpg)
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

View file

@ -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.
![](images/14-01.jpg)\
**Figure 14.1**  *Mismatch on first character checked.*
![**Figure 14.1**  *Mismatch on first character checked.*](images/14-01.jpg)
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!
![](images/14-02.jpg)\
**Figure 14.2**  *Mismatch on third character checked.*
![**Figure 14.2**  *Mismatch on third character checked.*](images/14-02.jpg)
### 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.
![](images/14-03.jpg)\
**Figure 14.3**  *Mismatch on character that appears in pattern.*
![**Figure 14.3**  *Mismatch on character that appears in pattern.*](images/14-03.jpg)
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

View file

@ -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.)
![](images/15-01.jpg)\
**Figure 15.1**  *The basic concept of a linked list.*
![**Figure 15.1**  *The basic concept of a linked list.*](images/15-01.jpg)

View file

@ -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.
![](images/15-02.jpg)\
**Figure 15.2**  *Using a dummy head and tail node with a linked list.*
![**Figure 15.2**  *Using a dummy head and tail node with a linked list.*](images/15-02.jpg)
------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/i.jpg) *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.
![](images/15-03.jpg)\
**Figure 15.3**  *Representing an empty list.*
![**Figure 15.3**  *Representing an empty list.*](images/15-03.jpg)

View file

@ -34,8 +34,7 @@ pages: 287-290
node that was >= */
}
![](images/15-04.jpg)\
**Figure 15.4**  *List terminated by a sentinel.*
![**Figure 15.4**  *List terminated by a sentinel.*](images/15-04.jpg)
### 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.
![](images/15-05.jpg)\
**Figure 15.5**  *Representing a circular list.*
![**Figure 15.5**  *Representing a circular list.*](images/15-05.jpg)
**LISTING 15.6 L15-6.C**

View file

@ -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.
![](images/16-01.jpg)\
**Figure 16.1**  *The two potential word count locations.*
![**Figure 16.1**  *The two potential word count locations.*](images/16-01.jpg)
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).
![](images/16-02.jpg)\
**Figure 16.2**  *Looking up a word count status.*
![**Figure 16.2**  *Looking up a word count status.*](images/16-02.jpg)
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

View file

@ -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.
![](images/17-01.jpg)\
**Figure 17.1**  *Edge-wrapping complications.*
![**Figure 17.1**  *Edge-wrapping complications.*](images/17-01.jpg)
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.
![](images/17-02.jpg)\
**Figure 17.2**  *The "padding cells" solution.*
![**Figure 17.2**  *The "padding cells" solution.*](images/17-02.jpg)
**LISTING 17.3 L17-3.CPP**

View file

@ -99,8 +99,7 @@ only one-tenth that of the original approach!
*Know your data.*
![](images/17-03.jpg)\
**Figure 17.3**  *New cell format.*
![**Figure 17.3**  *New cell format.*](images/17-03.jpg)
#### Acting on What We Know {#Heading10}

View file

@ -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.]
![](images/18-01.jpg)\
**Figure 18.1**  *Cell triplet storage.*
![**Figure 18.1**  *Cell triplet storage.*](images/18-01.jpg)

View file

@ -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**.
![](images/20-01.jpg)\
**Figure 20.1**  *The Pentium's two pipes.*
![**Figure 20.1**  *The Pentium's two pipes.*](images/20-01.jpg)
Getting two instructions executing simultaneously in the two pipes is
trickier than it sounds, not only because the V-pipe can handle only a

View file

@ -152,8 +152,7 @@ V-pipe.**
![](images/i.jpg) *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.*
------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/20-02.jpg)\
**Figure 20.2**  *Instruction flow through the two pipes.*
![**Figure 20.2**  *Instruction flow through the two pipes.*](images/20-02.jpg)
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
![](images/20-03.jpg)\
**Figure 20.3**  *Pushing a value from memory effectively in one
cycle.*
![**Figure 20.3**  *Pushing a value from memory effectively in one
cycle.*](images/20-03.jpg)
A more telling example is the following

View file

@ -43,8 +43,7 @@ that is often not correct.
![](images/i.jpg) *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.*
------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/20-04.jpg)\
**Figure 20.4**  *Lockstep execution and idle time in the V-pipe.*
![**Figure 20.4**  *Lockstep execution and idle time in the V-pipe.*](images/20-04.jpg)
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
![](images/20-05.jpg)\
**Figure 20.5**  *The Pentium's eight bank data cache.*
![**Figure 20.5**  *The Pentium's eight bank data cache.*](images/20-05.jpg)
bits 2, 3, and 4 (fall in the same bank) in tight loops, and you should
also avoid sequences like

View file

@ -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.
![](images/20-06.jpg)\
**Figure 20.6**  *Non-overlapped lockstep execution.*
![**Figure 20.6**  *Non-overlapped lockstep execution.*](images/20-06.jpg)
![](images/20-07.jpg)\
**Figure 20.7**  *Interleaving simple instructions for maximum
performance.*
![**Figure 20.7**  *Interleaving simple instructions for maximum
performance.*](images/20-07.jpg)
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!
![](images/20-08.jpg)\
**Figure 20.8**  *Prefix delays.*
![**Figure 20.8**  *Prefix delays.*](images/20-08.jpg)

View file

@ -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.
![](images/21-01.jpg)\
**Figure 21.1**  *An AGI can stall up to three instructions later.*
![**Figure 21.1**  *An AGI can stall up to three instructions later.*](images/21-01.jpg)
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.
![](images/21-02.jpg)\
**Figure 21.2**  *An AGI can cost as many as 3 cycles.*
![**Figure 21.2**  *An AGI can cost as many as 3 cycles.*](images/21-02.jpg)

View file

@ -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.
![](images/23-01.jpg)\
**Figure 23.1**  *Video data from memory to pixel.*
![**Figure 23.1**  *Video data from memory to pixel.*](images/23-01.jpg)
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

View file

@ -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.
![](images/23-02.jpg)\
**Figure 23.2**  *Video memory organization for Listing 23.1.*
![**Figure 23.2**  *Video memory organization for Listing 23.1.*](images/23-02.jpg)
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

View file

@ -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.
![](images/24-01.jpg)\
**Figure 24.1**  *VGA ALU data flow.*
![**Figure 24.1**  *VGA ALU data flow.*](images/24-01.jpg)
Occasionally, however, the independence of the latches from the display
memory location being written to can be used to great advantage. The

View file

@ -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.
![](images/25-01.jpg)\
**Figure 25.1**  *Data flow through the Graphics Controller.*
![**Figure 25.1**  *Data flow through the Graphics Controller.*](images/25-01.jpg)
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.
![](images/25-02.jpg)\
**Figure 25.2**  *Bit mask operation.*
![**Figure 25.2**  *Bit mask operation.*](images/25-02.jpg)
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

View file

@ -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.
![](images/25-03.jpg)\
**Figure 25.3**  *Data flow during a write mode 0 write operation.*
![**Figure 25.3**  *Data flow during a write mode 0 write operation.*](images/25-03.jpg)
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

View file

@ -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.
![](images/27-01.jpg)\
**Figure 27.1**  *VGA data flow in write mode 2.*
![**Figure 27.1**  *VGA data flow in write mode 2.*](images/27-01.jpg)
------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/i.jpg) *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.*

View file

@ -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.
![](images/29-01.jpg)\
**Figure 29.1**  *Saving EGA/VGA display memory.*
![**Figure 29.1**  *Saving EGA/VGA display memory.*](images/29-01.jpg)
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

View file

@ -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.
![](images/29-02.jpg)\
**Figure 29.2**  *Color translation via the palette registers.*
![**Figure 29.2**  *Color translation via the palette registers.*](images/29-02.jpg)

View file

@ -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.)
![](images/29-03.jpg)\
**Figure 29.3**  *Bit organization within a palette register.*
![**Figure 29.3**  *Bit organization within a palette register.*](images/29-03.jpg)
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,

View file

@ -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.
![](images/29-04.jpg)\
**Figure 29.4**  *Graphics mode register fields.*
![**Figure 29.4**  *Graphics mode register fields.*](images/29-04.jpg)

View file

@ -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.
![](images/30-01.jpg)\
**Figure 30.1**  *Display memory and the split screen.*
![**Figure 30.1**  *Display memory and the split screen.*](images/30-01.jpg)
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

View file

@ -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:
![](images/31-01.jpg)\
**Figure 31.1**  *Bitmap organization in 320x400 256-color mode in
320x400 256-color mode.*
![**Figure 31.1**  *Bitmap organization in 320x400 256-color mode in
320x400 256-color mode.*](images/31-01.jpg)
*n* = (*address* \* 4) + *plane*

View file

@ -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.
![](images/32-01.jpg)\
**Figure 32.1**  *Pixel organization in 360x480 256-color mode.*
![**Figure 32.1**  *Pixel organization in 360x480 256-color mode.*](images/32-01.jpg)
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

View file

@ -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.
![](images/33-01.jpg)\
**Figure 33.1**  *The VGA color generation path.*
![**Figure 33.1**  *The VGA color generation path.*](images/33-01.jpg)
(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

View file

@ -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.
![](images/35-01.jpg)\
**Figure 35.1**  *Approximating a true line from a pixel array.*
![**Figure 35.1**  *Approximating a true line from a pixel array.*](images/35-01.jpg)

View file

@ -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.
![](images/35-02.jpg)\
**Figure 35.2**  *Drawing between two pixel endpoints.*
![**Figure 35.2**  *Drawing between two pixel endpoints.*](images/35-02.jpg)
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.
![](images/35-03.jpg)\
**Figure 35.3**  *The error term in Bresenham's algorithm.*
![**Figure 35.3**  *The error term in Bresenham's algorithm.*](images/35-03.jpg)
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

View file

@ -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.
![](images/35-04.jpg)\
**Figure 35.4**  *Bresenham's eight possible line orientations.*
![**Figure 35.4**  *Bresenham's eight possible line orientations.*](images/35-04.jpg)

View file

@ -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.
![](images/35-05.jpg)\
**Figure 35.5**  *EVGALine's decision logic.*
![**Figure 35.5**  *EVGALine's decision logic.*](images/35-05.jpg)
#### Drawing Each Line {#Heading8}

View file

@ -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.
![](images/36-01.jpg)\
**Figure 36.1**  *Standard Bresenham's line drawing.*
![**Figure 36.1**  *Standard Bresenham's line drawing.*](images/36-01.jpg)

View file

@ -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.
![](images/36-02.jpg)\
**Figure 36.2**  *Run-length slice line drawing.*
![**Figure 36.2**  *Run-length slice line drawing.*](images/36-02.jpg)
![](images/36-03.jpg)\
**Figure 36.3**  *Runs in a slope 1/3.5 line.*
![**Figure 36.3**  *Runs in a slope 1/3.5 line.*](images/36-03.jpg)
### 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.
![](images/36-04.jpg)\
**Figure 36.4**  *How the error term determines run length.*
![**Figure 36.4**  *How the error term determines run length.*](images/36-04.jpg)
Practically speaking, however, we can't work with fractions because
floating-point arithmetic is slow and fixed-point arithmetic is

View file

@ -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.
![](images/36-05.jpg)\
**Figure 36.5**  *Balancing run-length slice lines: a) unbalanced; b)
balanced.*
![**Figure 36.5**  *Balancing run-length slice lines: a) unbalanced; b)
balanced.*](images/36-05.jpg)
**LISTING 36.1 L36-1.C**

View file

@ -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.
![](images/38-01.jpg)\
**Figure 38.1**  *Convex, nonconvex, and complex polygons.*
![**Figure 38.1**  *Convex, nonconvex, and complex polygons.*](images/38-01.jpg)
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.
![](images/38-02.jpg)\
**Figure 38.2**  *Drawing polygons with standard line-drawing
algorithms.*
![**Figure 38.2**  *Drawing polygons with standard line-drawing
algorithms.*](images/38-02.jpg)
There's one great drawback to tracing polygons with standard lines,
however: Adjacent polygons won't fit together properly, as shown in

View file

@ -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).
![](images/38-03.jpg)\
**Figure 38.3**  *The adjacent polygons problem.*
![**Figure 38.3**  *The adjacent polygons problem.*](images/38-03.jpg)
* Points located exactly on horizontal edges are drawn only if the
interior of the polygon is directly below them (horizontal top edges

View file

@ -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.
![](images/40-01.jpg)\
**Figure 40.1**  *Filling one scan line by finding intersecting edges.*
![**Figure 40.1**  *Filling one scan line by finding intersecting edges.*](images/40-01.jpg)
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.
![](images/40-02.jpg)\
**Figure 40.2**  *Checking currently active edges (solid lines).*
![**Figure 40.2**  *Checking currently active edges (solid lines).*](images/40-02.jpg)
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

View file

@ -61,8 +61,7 @@ the following:
7. If either the AET or GET isn't empty, go to step 2.
![](images/40-03.jpg)\
**Figure 40.3**  *The global and active edge tables as linked lists.*
![**Figure 40.3**  *The global and active edge tables as linked lists.*](images/40-03.jpg)
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,

View file

@ -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.
![](images/41-01.jpg)\
**Figure 41.1**  *Monotone-vertical polygons.*
![**Figure 41.1**  *Monotone-vertical polygons.*](images/41-01.jpg)
![](images/41-02.jpg)\
**Figure 41.2**  *Non-monotone-vertical polygons.*
![**Figure 41.2**  *Non-monotone-vertical polygons.*](images/41-02.jpg)
**LISTING 41.2 L41-2.C**

View file

@ -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.
![](images/42-01.jpg)\
**Figure 42.1**  *The basic concept of Wu antialiasing.*
![**Figure 42.1**  *The basic concept of Wu antialiasing.*](images/42-01.jpg)
You might expect that the implementation of Wu antialiasing would fall
into two distinct areas: tracing out the line (that is, finding the

View file

@ -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.
![](images/42-02.jpg)\
**Figure 42.2**  *Wu intensity calculations.*
![**Figure 42.2**  *Wu intensity calculations.*](images/42-02.jpg)
The intensity calculations take longer to describe than they do to
perform. All that's involved is a shift of the error accumulator to

View file

@ -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.
![](images/43-01.jpg)\
**Figure 43.1**  *How 4 bits of video data become 6 bits of color.*
![**Figure 43.1**  *How 4 bits of video data become 6 bits of color.*](images/43-01.jpg)
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.
![](images/43-02.jpg)\
**Figure 43.2**  *Storing images in separate planes.*
![**Figure 43.2**  *Storing images in separate planes.*](images/43-02.jpg)
![](images/43-03.jpg)\
**Figure 43.3**  *The problem of overlapping colors.*
![**Figure 43.3**  *The problem of overlapping colors.*](images/43-03.jpg)

View file

@ -75,8 +75,7 @@ transparency.
Table: Table 43.1 Palette RAM settings for bit-plane animation.
![](images/43-04.jpg)\
**Figure 43.4**  *How pixel precedence works.*
![**Figure 43.4**  *How pixel precedence works.*](images/43-04.jpg)
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

View file

@ -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.
![](images/43-05.jpg)\
**Figure 43.5**  *Pixel precedence for plane 3 only.*
![**Figure 43.5**  *Pixel precedence for plane 3 only.*](images/43-05.jpg)
When a technique has such tremendous visual and performance advantages
as does bit-plane animation, it behooves you to design your animation

View file

@ -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.*
![](images/44-01.jpg)\
**Figure 44.1**  *Memory allocation for mode 10h page flipping.*
![**Figure 44.1**  *Memory allocation for mode 10h page flipping.*](images/44-01.jpg)

View file

@ -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.)
![](images/44-02.jpg)\
**Figure 44.2**  *Memory allocation for mode 12h page flipping.*
![**Figure 44.2**  *Memory allocation for mode 12h page flipping.*](images/44-02.jpg)
The sample program for this chapter uses the split screen and page
flipping exactly as described above. The playfield through which the

View file

@ -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.
![](images/45-01.jpg)\
**Figure 45.1**  *Drawing directly to the screen.*
![**Figure 45.1**  *Drawing directly to the screen.*](images/45-01.jpg)
![](images/45-02.jpg)\
**Figure 45.2**  *Dirty rectangle animation.*
![**Figure 45.2**  *Dirty rectangle animation.*](images/45-02.jpg)
#### So Why Not Use Page Flipping? {#Heading6}

View file

@ -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.
![](images/47-01.jpg)\
**Figure 47.1**  *Mode X display memory organization.*
![**Figure 47.1**  *Mode X display memory organization.*](images/47-01.jpg)
**LISTING 47.2 L47-2.ASM**

View file

@ -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.
![](images/47-02.jpg)\
**Figure 47.2**  *Selecting planes with the Map Mask register.*
![**Figure 47.2**  *Selecting planes with the Map Mask register.*](images/47-02.jpg)
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

View file

@ -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.
![](images/48-01.jpg)\
**Figure 48.1**  *How the VGA latches are loaded.*
![**Figure 48.1**  *How the VGA latches are loaded.*](images/48-01.jpg)
![](images/48-02.jpg)\
**Figure 48.2**  *Writing 4 bytes to display memory in a single
operation.*
![**Figure 48.2**  *Writing 4 bytes to display memory in a single
operation.*](images/48-02.jpg)
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

View file

@ -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.
![](images/48-03.jpg)\
**Figure 48.3**  *A useful Mode X display memory layout.*
![**Figure 48.3**  *A useful Mode X display memory layout.*](images/48-03.jpg)
This is an efficient organization for animation, but there are certainly
many other possible setups. For example, you might choose to have a

View file

@ -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.
![](images/49-01.jpg)\
**Figure 49.1**  *An animated Mode X screen.*
![**Figure 49.1**  *An animated Mode X screen.*](images/49-01.jpg)
**LISTING 49.5 L49-5.C**

View file

@ -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).
![](images/50-01.jpg)\
**Figure 50.1**  *The 3-D drawing pipeline.*
![**Figure 50.1**  *The 3-D drawing pipeline.*](images/50-01.jpg)
![](images/50-02.jpg)\
**Figure 50.2**  *A right-handed coordinate system.*
![**Figure 50.2**  *A right-handed coordinate system.*](images/50-02.jpg)
#### 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.
![](images/50-03.jpg)\
**Figure 50.3**  *Perspective projection.*
![**Figure 50.3**  *Perspective projection.*](images/50-03.jpg)
#### 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.
![](images/50-04.jpg)\
**Figure 50.4**  *3-D rotation formulas.*
![**Figure 50.4**  *3-D rotation formulas.*](images/50-04.jpg)
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).
![](images/50-05.jpg)\
**Figure 50.5**  *A 4x4 Transformation Matrix.*
![**Figure 50.5**  *A 4x4 Transformation Matrix.*](images/50-05.jpg)
Happily (and not coincidentally), we put together a nice 2-D animation
framework back in Chapters 47, 48, and 49, during our exploratory

View file

@ -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.
![](images/51-01.jpg)\
**Figure 51.1**  *The cross-product of two vectors.*
![**Figure 51.1**  *The cross-product of two vectors.*](images/51-01.jpg)
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.
![](images/51-02.jpg)\
**Figure 51.2**  *Using the cross product to generate a polygon
normal.*
![**Figure 51.2**  *Using the cross product to generate a polygon
normal.*](images/51-02.jpg)
Backface removal may also be performed using the polygon vertices in
screen coordinates, which are integers. This is less accurate than using

View file

@ -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.
![](images/51-03.jpg)\
**Figure 51.3**  *Sample screens from the 3-D cube program.*
![**Figure 51.3**  *Sample screens from the 3-D cube program.*](images/51-03.jpg)
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.
![](images/51-04.jpg)\
**Figure 51.4**  *The object data structure*
![**Figure 51.4**  *The object data structure*](images/51-04.jpg)

View file

@ -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.
![](images/53-01.jpg)\
**Figure 53.1**  *Why back-to-front sorting doesn't always work
properly.*
![**Figure 53.1**  *Why back-to-front sorting doesn't always work
properly.*](images/53-01.jpg)

View file

@ -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.
![](images/54-01.jpg)\
**Figure 54.1**  *Illumination by a directed light source*
![**Figure 54.1**  *Illumination by a directed light source*](images/54-01.jpg)
![](images/54-02.jpg)\
**Figure 54.2**  *The dot product of two vectors.*
![**Figure 54.2**  *The dot product of two vectors.*](images/54-02.jpg)
The overall red shading for each polygon can be calculated by summing
the ambient-shading red component with the diffuse-shading component

View file

@ -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.
![](images/54-03.jpg)\
**Figure 54.3**  *The unit normal in the polygon data structure.*
![**Figure 54.3**  *The unit normal in the polygon data structure.*](images/54-03.jpg)
![](images/54-04.jpg)\
**Figure 54.4**  *The reversed light source vector.*
![**Figure 54.4**  *The reversed light source vector.*](images/54-04.jpg)
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,

View file

@ -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.
![](images/55-01.jpg)\
**Figure 55.1**  *The RGB color cube.*
![**Figure 55.1**  *The RGB color cube.*](images/55-01.jpg)
RGB is good for modeling colors generated by light sources, because red,
green, and blue are the additive primaries; that is, all other colors

View file

@ -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.
![](images/55-02.jpg)\
**Figure 55.2**  *Drawing solid text.*
![**Figure 55.2**  *Drawing solid text.*](images/55-02.jpg)
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

View file

@ -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.
![](images/55-03.jpg)\
**Figure 55.3**  *The data path in write mode 3.*
![**Figure 55.3**  *The data path in write mode 3.*](images/55-03.jpg)
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

View file

@ -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.
![](images/56-01.jpg)\
**Figure 56.1**  *Using reverse transformation to find the source pixel
color.*
![**Figure 56.1**  *Using reverse transformation to find the source pixel
color.*](images/56-01.jpg)
#### 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.
![](images/56-02.jpg)\
**Figure 56.2**  *Mapping a texture onto an untransformed polygon.*
![**Figure 56.2**  *Mapping a texture onto an untransformed polygon.*](images/56-02.jpg)
Now, matters get more complicated. What if the destination polygon is
rotated in two dimensions? We no longer have a neat direct mapping from

View file

@ -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.
![](images/56-03.jpg)\
**Figure 56.3**  *Mapping a texture onto a 2-D rotated polygon.*
![**Figure 56.3**  *Mapping a texture onto a 2-D rotated polygon.*](images/56-03.jpg)
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.
![](images/56-04.jpg)\
**Figure 56.4**  *Mapping a horizontal destination scan line back to
the source image.*
![**Figure 56.4**  *Mapping a horizontal destination scan line back to
the source image.*](images/56-04.jpg)
![](images/56-05.jpg)\
**Figure 56.5**  *Mapping a texture onto a narrower polygon.*
![**Figure 56.5**  *Mapping a texture onto a narrower polygon.*](images/56-05.jpg)
#### Notes on DDA Texture Mapping\
\

View file

@ -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.
![](images/57-01.jpg)\
**Figure 57.1**  *Gaps caused by mixing fixed-point and all-integer
math.*
![**Figure 57.1**  *Gaps caused by mixing fixed-point and all-integer
math.*](images/57-01.jpg)
Listing 57.1 is one solution to these problems. This code, which
replaces the equivalently named function presented in the previous

View file

@ -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.
![](images/58-01.jpg)\
**Figure 58.1**  *Texture mapping a single horizontal scanline.*
![**Figure 58.1**  *Texture mapping a single horizontal scanline.*](images/58-01.jpg)

View file

@ -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.
![](images/58-02.jpg)\
**Figure 58.2**  *Display memory organization in Mode X.*
![**Figure 58.2**  *Display memory organization in Mode X.*](images/58-02.jpg)
I must confess, with some embarrassment, that at this point I threw
myself into designing a solution that involved executing the texture

View file

@ -20,8 +20,7 @@ the least.
![](images/i.jpg) *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!*
------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/58-03.jpg)\
**Figure 58.3**  *Texture mapping a single vertical column.*
![**Figure 58.3**  *Texture mapping a single vertical column.*](images/58-03.jpg)
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.
![](images/58-04.jpg)\
**Figure 58.4**  *Original method for advancing the source texture
pointer.*
![**Figure 58.4**  *Original method for advancing the source texture
pointer.*](images/58-04.jpg)
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.
![](images/58-05.jpg)\
**Figure 58.5**  *Efficient method for advancing source texture
pointer.*
![**Figure 58.5**  *Efficient method for advancing source texture
pointer.*](images/58-05.jpg)
![](images/58-06.jpg)\
**Figure 58.6**  *Storing both X and Y fractional coordinates in one
register.*
![**Figure 58.6**  *Storing both X and Y fractional coordinates in one
register.*](images/58-06.jpg)
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

View file

@ -36,8 +36,7 @@ Or can we?
; fractional X advance in high word of ECX, bit
; 15 set to 0
![](images/58-07.jpg)\
**Figure 58.7**  *Final method for advancing source texture pointer.*
![**Figure 58.7**  *Final method for advancing source texture pointer.*](images/58-07.jpg)
; EDX = fractional source texture Y coordinate in lower
; 15 bits of CX, fractional source texture X coord

View file

@ -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.
![](images/59-01.jpg)\
**Figure 59.1**  *The painter's algorithm.*
![**Figure 59.1**  *The painter's algorithm.*](images/59-01.jpg)
#### 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?
![](images/59-02.jpg)\
**Figure 59.2**  *A sample set of walls, viewed from above.*
![**Figure 59.2**  *A sample set of walls, viewed from above.*](images/59-02.jpg)
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

View file

@ -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.
![](images/59-03.jpg)\
**Figure 59.3**  *Initial split along the line of wall C.*
![**Figure 59.3**  *Initial split along the line of wall C.*](images/59-03.jpg)
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?
![](images/59-04.jpg)\
**Figure 59.4**  *Split of wall C's front subspace along the line of
wall D.*
![**Figure 59.4**  *Split of wall C's front subspace along the line of
wall D.*](images/59-04.jpg)
![](images/59-05.jpg)\
**Figure 59.5**  *Split of wall C's back subspace along the line of
wall B.*
![**Figure 59.5**  *Split of wall C's back subspace along the line of
wall B.*](images/59-05.jpg)
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.
![](images/59-06.jpg)\
**Figure 59.6**  *The final BSP tree.*
![**Figure 59.6**  *The final BSP tree.*](images/59-06.jpg)
![](images/59-07.jpg)\
**Figure 59.7**  *Viewing the BSP tree from an arbitrary angle.*
![**Figure 59.7**  *Viewing the BSP tree from an arbitrary angle.*](images/59-07.jpg)
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.
![](images/59-08.jpg)\
**Figure 59.8**  *Back-to-front traversal of the BSP tree as viewed in
Figure 59.7.*
![**Figure 59.8**  *Back-to-front traversal of the BSP tree as viewed in
Figure 59.7.*](images/59-08.jpg)
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;

View file

@ -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.
![](images/59-09.jpg)\
**Figure 59.9**  *An inorder walk of a BSP tree.*
![**Figure 59.9**  *An inorder walk of a BSP tree.*](images/59-09.jpg)
**Listing 59.2 L59\_2.C**

View file

@ -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.
![](images/60-01.jpg)\
**Figure 60.1**  *A sample parametric line.*
![**Figure 60.1**  *A sample parametric line.*](images/60-01.jpg)
![](images/60-02.jpg)\
**Figure 60.2**  *Line segment storage in the BSP compiler.*
![**Figure 60.2**  *Line segment storage in the BSP compiler.*](images/60-02.jpg)
#### 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.
![](images/60-03.jpg)\
**Figure 60.3**  *How line intersection is calculated.*
![**Figure 60.3**  *How line intersection is calculated.*](images/60-03.jpg)
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

View file

@ -88,8 +88,7 @@ intensity at which the surface is illuminated, as in
(eq. 5)
![](images/61-01.jpg)\
**Figure 61.1**  *The dot product.*
![**Figure 61.1**  *The dot product.*](images/61-01.jpg)
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.
![](images/61-02.jpg)\
**Figure 61.2**  *The dot product as used in calculating lighting
intensity.*
![**Figure 61.2**  *The dot product as used in calculating lighting
intensity.*](images/61-02.jpg)

View file

@ -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.
![](images/61-03.jpg)\
**Figure 61.3**  *A problem with determining front/back visibility.*
![**Figure 61.3**  *A problem with determining front/back visibility.*](images/61-03.jpg)
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.
![](images/i.jpg) *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.*
------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
![](images/61-04.jpg)\
**Figure 61.4**  *How the cross product of polygon edge vectors
generates a polygon normal.*
![**Figure 61.4**  *How the cross product of polygon edge vectors
generates a polygon normal.*](images/61-04.jpg)
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.
![](images/61-05.jpg)\
**Figure 61.5**  *Backface culling with the dot product.*
![**Figure 61.5**  *Backface culling with the dot product.*](images/61-05.jpg)

View file

@ -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.
![](images/61-06.jpg)\
**Figure 61.6**  *How the dot product with a unit vector performs a
projection.*
![**Figure 61.6**  *How the dot product with a unit vector performs a
projection.*](images/61-06.jpg)
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.
![](images/61-07.jpg)\
**Figure 61.7**  *Using the dot product to get the distance from a
point to a plane.*
![**Figure 61.7**  *Using the dot product to get the distance from a
point to a plane.*](images/61-07.jpg)
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.
![](images/61-08.jpg)\
**Figure 61.8**  *Rotation to a new coordinate space by projection onto
new axes.*
![**Figure 61.8**  *Rotation to a new coordinate space by projection onto
new axes.*](images/61-08.jpg)

Some files were not shown because too many files have changed in this diff Show more