diff --git a/02-02.md b/02-02.md index 41cf197..7b857f7 100644 --- a/02-02.md +++ b/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. -![](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 diff --git a/03-03.md b/03-03.md index b74e127..58a2b13 100644 --- a/03-03.md +++ b/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. -![](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, diff --git a/03-10.md b/03-10.md index c71b192..1fd910e 100644 --- a/03-10.md +++ b/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.) -![](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 diff --git a/04-02.md b/04-02.md index e46f783..338d12e 100644 --- a/04-02.md +++ b/04-02.md @@ -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 diff --git a/04-04.md b/04-04.md index 7044560..8ef2fee 100644 --- a/04-04.md +++ b/04-04.md @@ -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 diff --git a/04-05.md b/04-05.md index e5787b8..ed44020 100644 --- a/04-05.md +++ b/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. -![](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** diff --git a/04-06.md b/04-06.md index 9f84aac..37df23e 100644 --- a/04-06.md +++ b/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. -![](images/04-05.jpg)\ - **Figure 4.5**  *The PC bus dynamic RAM (DRAM) refresh.* \ No newline at end of file +![**Figure 4.5**  *The PC bus dynamic RAM (DRAM) refresh.*](images/04-05.jpg) \ No newline at end of file diff --git a/04-08.md b/04-08.md index 8989d29..97f3973 100644 --- a/04-08.md +++ b/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. -![](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. diff --git a/05-02.md b/05-02.md index da23f08..5ec12f2 100644 --- a/05-02.md +++ b/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()**. -![](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 diff --git a/06-02.md b/06-02.md index e92a6fb..07ef0f3 100644 --- a/06-02.md +++ b/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. -![](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? diff --git a/08-02.md b/08-02.md index d8d74e3..bbc98a9 100644 --- a/08-02.md +++ b/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 -![](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. diff --git a/08-03.md b/08-03.md index 67782b7..2c28656 100644 --- a/08-03.md +++ b/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. -![](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++ diff --git a/08-05.md b/08-05.md index 0a13c6e..5e5593e 100644 --- a/08-05.md +++ b/08-05.md @@ -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 { diff --git a/09-02.md b/09-02.md index 7f03935..79bf892 100644 --- a/09-02.md +++ b/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**. -![](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 diff --git a/09-03.md b/09-03.md index 16406f5..74156ee 100644 --- a/09-03.md +++ b/09-03.md @@ -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** diff --git a/09-05.md b/09-05.md index 956595c..97645ef 100644 --- a/09-05.md +++ b/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. -![](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 diff --git a/09-07.md b/09-07.md index 5e6c487..cae3b2f 100644 --- a/09-07.md +++ b/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!) -![](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 diff --git a/10-02.md b/10-02.md index 98ba0cc..a470090 100644 --- a/10-02.md +++ b/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. -![](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** diff --git a/10-03.md b/10-03.md index e6acdd2..f8b8001 100644 --- a/10-03.md +++ b/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. -![](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 diff --git a/11-03.md b/11-03.md index 4328071..6c69f67 100644 --- a/11-03.md +++ b/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.) -![](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 diff --git a/11-04.md b/11-04.md index 97f71fe..e32c274 100644 --- a/11-04.md +++ b/11-04.md @@ -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 diff --git a/11-08.md b/11-08.md index f858d08..c3edbae 100644 --- a/11-08.md +++ b/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. -![](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 diff --git a/12-02.md b/12-02.md index 29a0eef..00a45d2 100644 --- a/12-02.md +++ b/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, -![](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} diff --git a/13-01.md b/13-01.md index b4341f8..32a4444 100644 --- a/13-01.md +++ b/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. -![](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.* diff --git a/13-02.md b/13-02.md index 672815b..d71c07d 100644 --- a/13-02.md +++ b/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. -![](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 diff --git a/14-02.md b/14-02.md index 91b923f..3d1994d 100644 --- a/14-02.md +++ b/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. -![](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 diff --git a/15-01.md b/15-01.md index 13edd26..ee66a9c 100644 --- a/15-01.md +++ b/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.) -![](images/15-01.jpg)\ - **Figure 15.1**  *The basic concept of a linked list.* \ No newline at end of file +![**Figure 15.1**  *The basic concept of a linked list.*](images/15-01.jpg) \ No newline at end of file diff --git a/15-02.md b/15-02.md index 83d3c22..8caa01b 100644 --- a/15-02.md +++ b/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. -![](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.* \ No newline at end of file +![**Figure 15.3**  *Representing an empty list.*](images/15-03.jpg) \ No newline at end of file diff --git a/15-03.md b/15-03.md index 41cf7bf..f4c7215 100644 --- a/15-03.md +++ b/15-03.md @@ -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** diff --git a/16-08.md b/16-08.md index 6703298..d98ddf2 100644 --- a/16-08.md +++ b/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. -![](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 diff --git a/17-04.md b/17-04.md index 4d6f49e..7f20d70 100644 --- a/17-04.md +++ b/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. -![](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** diff --git a/17-06.md b/17-06.md index 7c0007c..e18f069 100644 --- a/17-06.md +++ b/17-06.md @@ -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} diff --git a/18-04.md b/18-04.md index c967740..32ee990 100644 --- a/18-04.md +++ b/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.] -![](images/18-01.jpg)\ - **Figure 18.1**  *Cell triplet storage.* \ No newline at end of file +![**Figure 18.1**  *Cell triplet storage.*](images/18-01.jpg) \ No newline at end of file diff --git a/20-01.md b/20-01.md index 2ee0638..9e30a66 100644 --- a/20-01.md +++ b/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**. -![](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 diff --git a/20-02.md b/20-02.md index 05d511c..aed722d 100644 --- a/20-02.md +++ b/20-02.md @@ -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 diff --git a/20-03.md b/20-03.md index 3298ff6..8baf0dd 100644 --- a/20-03.md +++ b/20-03.md @@ -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 diff --git a/20-04.md b/20-04.md index f697580..aaa09eb 100644 --- a/20-04.md +++ b/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. -![](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) diff --git a/21-01.md b/21-01.md index 994f4bc..37d19e3 100644 --- a/21-01.md +++ b/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. -![](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) diff --git a/23-03.md b/23-03.md index e601636..3dbc533 100644 --- a/23-03.md +++ b/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. -![](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 diff --git a/23-04.md b/23-04.md index 7297612..4cf6fb2 100644 --- a/23-04.md +++ b/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. -![](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 diff --git a/24-01.md b/24-01.md index 6ac6995..5f8364d 100644 --- a/24-01.md +++ b/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. -![](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 diff --git a/25-01.md b/25-01.md index d14be2e..b647731 100644 --- a/25-01.md +++ b/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. -![](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 diff --git a/25-03.md b/25-03.md index c74013e..f96d31f 100644 --- a/25-03.md +++ b/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. -![](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 diff --git a/27-01.md b/27-01.md index 233767c..f148851 100644 --- a/27-01.md +++ b/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. -![](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.* diff --git a/29-01.md b/29-01.md index 144cfcc..c3491ee 100644 --- a/29-01.md +++ b/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. -![](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 diff --git a/29-03.md b/29-03.md index 518ff2f..323d62b 100644 --- a/29-03.md +++ b/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. -![](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) diff --git a/29-04.md b/29-04.md index ba6fa39..c3bb7e5 100644 --- a/29-04.md +++ b/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.) -![](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, diff --git a/29-06.md b/29-06.md index 9729909..e73015c 100644 --- a/29-06.md +++ b/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. -![](images/29-04.jpg)\ - **Figure 29.4**  *Graphics mode register fields.* +![**Figure 29.4**  *Graphics mode register fields.*](images/29-04.jpg) diff --git a/30-01.md b/30-01.md index 7fcd52d..204dcc2 100644 --- a/30-01.md +++ b/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. -![](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 diff --git a/31-02.md b/31-02.md index 797894b..e3e5e72 100644 --- a/31-02.md +++ b/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: -![](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* diff --git a/32-05.md b/32-05.md index 2712adc..c8f8e11 100644 --- a/32-05.md +++ b/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. -![](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 diff --git a/33-01.md b/33-01.md index 5cfb4fb..fc53fb2 100644 --- a/33-01.md +++ b/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. -![](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 diff --git a/35-01.md b/35-01.md index 0c784f7..4174f2b 100644 --- a/35-01.md +++ b/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. -![](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) diff --git a/35-02.md b/35-02.md index 94379d3..28d34a1 100644 --- a/35-02.md +++ b/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. -![](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 diff --git a/35-04.md b/35-04.md index caeab1e..875762b 100644 --- a/35-04.md +++ b/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. -![](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) diff --git a/35-05.md b/35-05.md index b68e2b6..9729ff9 100644 --- a/35-05.md +++ b/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. -![](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} diff --git a/36-01.md b/36-01.md index 9b32c9b..ec79099 100644 --- a/36-01.md +++ b/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. -![](images/36-01.jpg)\ - **Figure 36.1**  *Standard Bresenham's line drawing.* +![**Figure 36.1**  *Standard Bresenham's line drawing.*](images/36-01.jpg) diff --git a/36-02.md b/36-02.md index 362e061..e725949 100644 --- a/36-02.md +++ b/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. -![](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 diff --git a/36-03.md b/36-03.md index 60fe219..a918279 100644 --- a/36-03.md +++ b/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. -![](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** diff --git a/38-01.md b/38-01.md index d616ab8..04352aa 100644 --- a/38-01.md +++ b/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. -![](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 diff --git a/38-02.md b/38-02.md index 12a9d34..43e0853 100644 --- a/38-02.md +++ b/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). - ![](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 diff --git a/40-01.md b/40-01.md index 1dd7390..f9e7687 100644 --- a/40-01.md +++ b/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. -![](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 diff --git a/40-02.md b/40-02.md index 3e1c0e3..79c240e 100644 --- a/40-02.md +++ b/40-02.md @@ -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, diff --git a/41-02.md b/41-02.md index feae887..ab34d27 100644 --- a/41-02.md +++ b/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. -![](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** diff --git a/42-01.md b/42-01.md index 655b19d..108f06e 100644 --- a/42-01.md +++ b/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. -![](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 diff --git a/42-02.md b/42-02.md index 57bc3fd..578be42 100644 --- a/42-02.md +++ b/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. -![](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 diff --git a/43-01.md b/43-01.md index 5c145aa..f28766f 100644 --- a/43-01.md +++ b/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. -![](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) diff --git a/43-02.md b/43-02.md index e4fbd1f..e55627b 100644 --- a/43-02.md +++ b/43-02.md @@ -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 diff --git a/43-05.md b/43-05.md index cd089f2..350e72b 100644 --- a/43-05.md +++ b/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. -![](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 diff --git a/44-05.md b/44-05.md index 089b412..bc7f534 100644 --- a/44-05.md +++ b/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.* -![](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) diff --git a/44-06.md b/44-06.md index c0cad65..71a4c5d 100644 --- a/44-06.md +++ b/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.) -![](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 diff --git a/45-02.md b/45-02.md index dbb2344..54dff78 100644 --- a/45-02.md +++ b/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. -![](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} diff --git a/47-03.md b/47-03.md index a48ea3a..4b87fbc 100644 --- a/47-03.md +++ b/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. -![](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** diff --git a/47-06.md b/47-06.md index f7f989c..d2b602a 100644 --- a/47-06.md +++ b/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. -![](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 diff --git a/48-01.md b/48-01.md index de740b1..e1e0a65 100644 --- a/48-01.md +++ b/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. -![](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 diff --git a/48-03.md b/48-03.md index 3abb120..d78dde7 100644 --- a/48-03.md +++ b/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. -![](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 diff --git a/49-04.md b/49-04.md index 266b30f..374fee4 100644 --- a/49-04.md +++ b/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. -![](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** diff --git a/50-02.md b/50-02.md index 0e23e4b..2cc724e 100644 --- a/50-02.md +++ b/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). -![](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 diff --git a/51-02.md b/51-02.md index 662b013..ce522a1 100644 --- a/51-02.md +++ b/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. -![](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 diff --git a/51-04.md b/51-04.md index f6e5068..f54757a 100644 --- a/51-04.md +++ b/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. -![](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) diff --git a/53-03.md b/53-03.md index c4bb194..543d617 100644 --- a/53-03.md +++ b/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. -![](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) diff --git a/54-03.md b/54-03.md index 6e13c7d..c7adcfb 100644 --- a/54-03.md +++ b/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. -![](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 diff --git a/54-05.md b/54-05.md index 796c94d..12563d5 100644 --- a/54-05.md +++ b/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. -![](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, diff --git a/55-01.md b/55-01.md index 063db40..288afec 100644 --- a/55-01.md +++ b/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. -![](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 diff --git a/55-03.md b/55-03.md index 1c597d8..9a1cec1 100644 --- a/55-03.md +++ b/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. -![](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 diff --git a/55-04.md b/55-04.md index 08c2dd7..cdc8834 100644 --- a/55-04.md +++ b/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. -![](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 diff --git a/56-01.md b/56-01.md index ca3b3b3..21edf7a 100644 --- a/56-01.md +++ b/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. -![](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 diff --git a/56-02.md b/56-02.md index 50e7643..f13a03f 100644 --- a/56-02.md +++ b/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. -![](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\ \ diff --git a/57-02.md b/57-02.md index 7bb0eed..a861356 100644 --- a/57-02.md +++ b/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. -![](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 diff --git a/58-01.md b/58-01.md index 77359c9..6470d32 100644 --- a/58-01.md +++ b/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. -![](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) diff --git a/58-02.md b/58-02.md index 3d791d1..d1d5bc5 100644 --- a/58-02.md +++ b/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. -![](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 diff --git a/58-03.md b/58-03.md index c752f59..0d6c416 100644 --- a/58-03.md +++ b/58-03.md @@ -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 diff --git a/58-04.md b/58-04.md index c19d6ef..072ca92 100644 --- a/58-04.md +++ b/58-04.md @@ -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 diff --git a/59-02.md b/59-02.md index ba2148d..ae27ce0 100644 --- a/59-02.md +++ b/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. -![](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 diff --git a/59-03.md b/59-03.md index a203caf..2683d08 100644 --- a/59-03.md +++ b/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. -![](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; diff --git a/59-04.md b/59-04.md index a61f85b..6398e26 100644 --- a/59-04.md +++ b/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. -![](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** diff --git a/60-02.md b/60-02.md index 89130f0..37e4aca 100644 --- a/60-02.md +++ b/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. -![](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 diff --git a/61-02.md b/61-02.md index a3a9c1e..7c6b7cb 100644 --- a/61-02.md +++ b/61-02.md @@ -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) diff --git a/61-03.md b/61-03.md index 55c3dd6..875d047 100644 --- a/61-03.md +++ b/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. -![](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) diff --git a/61-04.md b/61-04.md index b789643..de27fc3 100644 --- a/61-04.md +++ b/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. -![](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) diff --git a/62-01.md b/62-01.md index e4e8774..242d614 100644 --- a/62-01.md +++ b/62-01.md @@ -86,8 +86,7 @@ the root wall, and each half of the world is then split again by the root's children, and so on, until the world is carved into subspaces along the lines of all the walls. -![](images/62-01.jpg)\ - **Figure 62.1**  *Vertical walls and a BSP tree to represent them.* +![**Figure 62.1**  *Vertical walls and a BSP tree to represent them.*](images/62-01.jpg) Our objective is to draw the world so that whenever walls overlap we see the nearer wall at each overlapped pixel. The simplest way to do that is diff --git a/62-03.md b/62-03.md index 57cf0cd..d021600 100644 --- a/62-03.md +++ b/62-03.md @@ -111,8 +111,7 @@ wall's t values accordingly. The test for x clipping is very simple, because the edges of the frustum are defined as the planes where x==z and -x==z. -![](images/62-02.jpg)\ - **Figure 62.2**  *Clipping to the view pyramid.* +![**Figure 62.2**  *Clipping to the view pyramid.*](images/62-02.jpg) The final clip stage is clipping by y coordinate, and this is the most complicated, because vertical walls can be clipped at an angle in y, as @@ -134,5 +133,4 @@ results to the width of the screen, then we'll be ready to draw. Although this step is logically separate from clipping, it is performed as the last step for visible walls in **ClipWalls()**. -![](images/62-03.jpg)\ - **Figure 62.3**  *Why y clipping is more complex than x or z clipping.* +![**Figure 62.3**  *Why y clipping is more complex than x or z clipping.*](images/62-03.jpg) diff --git a/62-04.md b/62-04.md index 34c8567..5732f1d 100644 --- a/62-04.md +++ b/62-04.md @@ -66,8 +66,7 @@ All the visible, front-facing walls are drawn into a buffer by **DrawWallsBackToFront()**, then **UpdateWorld()** calls Win32 to copy the new frame to the screen. The frame of animation is complete. -![](images/62-04.jpg)\ - **Figure 62.4**  *Fast backspace culling test in screenspace.* +![**Figure 62.4**  *Fast backspace culling test in screenspace.*](images/62-04.jpg) #### Notes on the BSP Renderer {#Heading10} diff --git a/64-02.md b/64-02.md index 5fdcd42..723ad21 100644 --- a/64-02.md +++ b/64-02.md @@ -40,11 +40,9 @@ to figure out what parts of which polygons are visible would be free, and the processing time would be the same for all possible viewpoints, giving the game a smooth visual flow. -![](images/64-01.jpg)\ - **Figure 64.1**  *Quake's polygons are stored as empty leaves.* +![**Figure 64.1**  *Quake's polygons are stored as empty leaves.*](images/64-01.jpg) -![](images/64-02.jpg)\ - **Figure 64.2**  *Pixels visible from the current viewpoint.* +![**Figure 64.2**  *Pixels visible from the current viewpoint.*](images/64-02.jpg) As it happens, it is easy to determine which polygons are outside the frustum or partially clipped, and it's quite possible to figure out @@ -77,8 +75,7 @@ into two pieces created by the nodes above it in the tree, and the node's children then further carve that subspace into all the leaves that descend from the node. -![](images/64-03.jpg)\ - **Figure 64.3**  *The substance described by node E.* +![**Figure 64.3**  *The substance described by node E.*](images/64-03.jpg) Since a node's subspace is bounded and convex, it is possible to test whether it is entirely outside the frustum. If it is, *all* of the diff --git a/64-03.md b/64-03.md index 59c8215..70ddefb 100644 --- a/64-03.md +++ b/64-03.md @@ -34,12 +34,10 @@ there were no more polygons or the beam tree became entirely solid. Once the beam tree was completed, the visible portions of the polygons that had contributed to the beam tree were drawn. -![](images/64-04.jpg)\ - **Figure 64.4**  *Partitioning the screen into 2-D regions.* +![**Figure 64.4**  *Partitioning the screen into 2-D regions.*](images/64-04.jpg) -![](images/64-05.jpg)\ - **Figure 64.5**  *Beams as wedges projecting from the viewpoint to -polygon edges.* +![**Figure 64.5**  *Beams as wedges projecting from the viewpoint to +polygon edges.*](images/64-05.jpg) The advantage to working with a 3-D beam tree, rather than a 2-D region, is that determining which side of a beam plane a polygon vertex is on diff --git a/65-02.md b/65-02.md index a7369be..96107fe 100644 --- a/65-02.md +++ b/65-02.md @@ -142,9 +142,7 @@ clip the edge to the plane, as described above, and add the point at which the line intersects the clip plane as the next polygon vertex, as shown in Figure 65.3. Listing 65.2 shows a polygon-clipping function. -![](images/65-01.jpg)\ - **Figure 65.1**  *The distance from the plane to the inside endpoint, -measured along the normal.* +![**Figure 65.1**  *The distance from the plane to the inside endpoint, +measured along the normal.*](images/65-01.jpg) -![](images/65-02.jpg)\ - **Figure 65.2**  *Clipping a polygon.* +![**Figure 65.2**  *Clipping a polygon.*](images/65-02.jpg) diff --git a/65-03.md b/65-03.md index 90bca82..450d258 100644 --- a/65-03.md +++ b/65-03.md @@ -88,8 +88,7 @@ for a simple 3-D clipping example that allows you to move around and look at polygonal models from any angle. The full code for this program is available on the CD-ROM in the file DDJCLIP.ZIP. -![](images/65-03.jpg)\ - **Figure 65.3**  *Clipping a polygon edge.* +![**Figure 65.3**  *Clipping a polygon edge.*](images/65-03.jpg) **LISTING 65.3 L65\_3.c** diff --git a/66-02.md b/66-02.md index 86b7aec..05d4cf6 100644 --- a/66-02.md +++ b/66-02.md @@ -61,8 +61,7 @@ be drawn, as shown in Figure 66.2. This may sound a lot like z-buffering fine for smaller moving objects, as described earlier), but there are crucial differences. -![](images/66-01.jpg)\ - **Figure 66.1**  *Span generation.* +![**Figure 66.1**  *Span generation.*](images/66-01.jpg) By contrast with z-buffering, only visible portions of visible spans are scanned out pixel by pixel (although all polygon edges must still be @@ -112,9 +111,8 @@ spans encountered thus far, always with no overlap. This approach involves generating all spans for each polygon in turn, with each span immediately being sorted, clipped, and added to the appropriate bucket. -![](images/66-02.jpg)\ - **Figure 66.2**  *Two sets of spans sorted and clipped against one -another.* +![**Figure 66.2**  *Two sets of spans sorted and clipped against one +another.*](images/66-02.jpg) With edge-sorting, edges are stored in x-sorted, linked list buckets according to their start scan line. Each polygon in turn is decomposed diff --git a/66-03.md b/66-03.md index b1bf598..7f63f15 100644 --- a/66-03.md +++ b/66-03.md @@ -52,9 +52,8 @@ polygons, and that cuts the work involved in sorting, clipping, and rasterizing edges nearly in half, while also shrinking the world database quite a bit due to the sharing. -![](images/66-03.jpg)\ - **Figure 66.3**  *Activating a polygon when a leading edge is -encountered in the AEL.* +![**Figure 66.3**  *Activating a polygon when a leading edge is +encountered in the AEL.*](images/66-03.jpg) One final advantage of edge-sorting is that it makes no distinction between convex and concave polygons. That's not an important @@ -65,9 +64,8 @@ down, and concave polygons help a lot in that regard. While it's possible to handle concave polygons with span-sorting, that can involve significant performance penalties. -![](images/66-04.jpg)\ - **Figure 66.4**  *Deactivating a polygon when a trailing edge is -encountered in the AEL.* +![**Figure 66.4**  *Deactivating a polygon when a trailing edge is +encountered in the AEL.*](images/66-04.jpg) Nonetheless, there's no cut-and-dried answer as to which approach is better. In the end, span-sorting and edge-sorting amount to the same diff --git a/67-02.md b/67-02.md index acc3b91..c35dbfd 100644 --- a/67-02.md +++ b/67-02.md @@ -65,8 +65,7 @@ two spans may cross such that part of each span is visible, in which case the spans have to be split and drawn appropriately, as shown in Figure 67.1. -![](images/67-01.jpg)\ - **Figure 67.1**  *Intersecting span sorting.* +![**Figure 67.1**  *Intersecting span sorting.*](images/67-01.jpg) Intersecting is the slowest and most complicated type of span sorting, because it is necessary to compare 1/z values at two points in order to @@ -105,8 +104,7 @@ falsely reporting close-but-not-abutting edges as ties, proved to be troublesome in Quake, and the epsilon calculations and extra comparisons slowed things down. -![](images/67-02.jpg)\ - **Figure 67.2**  *Abutting span sorting.* +![**Figure 67.2**  *Abutting span sorting.*](images/67-02.jpg) I do think that abutting 1/z span sorting could have been made reliable enough for production use in Quake, were it not that we share edges diff --git a/68-02.md b/68-02.md index 8e65f61..58e8114 100644 --- a/68-02.md +++ b/68-02.md @@ -32,8 +32,7 @@ eliminate t-junctions anyway, because otherwise dropouts can occur along polygon edges). These problems can be eased by adding extra edges, but that increases the rasterization load. -![](images/68-01.jpg)\ - **Figure 68.1**  *Adding an extra vertex directly beneath a light.* +![**Figure 68.1**  *Adding an extra vertex directly beneath a light.*](images/68-01.jpg) #### Perspective Correctness {#Heading6} @@ -86,9 +85,8 @@ patterns, and require lots of triangles to support shadowing and other lighting detail. Finally, triangles don't solve clipping or viewing variance. -![](images/68-02.jpg)\ - **Figure 68.2**  *How Gouraud shading varies with polygon screen -orientation.* +![**Figure 68.2**  *How Gouraud shading varies with polygon screen +orientation.*](images/68-02.jpg) Yet another problem is that while it may work well to add extra geometry so that spotlights and shadows show up well, that's feasible only for diff --git a/68-03.md b/68-03.md index 3a4fd22..0627356 100644 --- a/68-03.md +++ b/68-03.md @@ -73,9 +73,8 @@ complicated and full of special cases, which makes it hard to optimize; worse, this approach doesn't work well with the procedural and post-processing techniques I'll discuss shortly. -![](images/68-03.jpg)\ - **Figure 68.3**  *Tiling the texture and lighting the texels from the -light map.* +![**Figure 68.3**  *Tiling the texture and lighting the texels from the +light map.*](images/68-03.jpg) Moreover, surface-based lighting tends to spend more of its time in inner loops, because polygons can have any number of sides and don't diff --git a/68-04.md b/68-04.md index c0ea3c8..aed004f 100644 --- a/68-04.md +++ b/68-04.md @@ -49,8 +49,7 @@ to mipmapping and the edge list, 600K has proven to be plenty for the surface cache at 320x200, even in the most complex scenes, and at 640x480, a little more than 1 MB suffices. -![](images/68-04.jpg)\ - **Figure 68.4**  *How mipmapping reduces surface caching requirements.* +![**Figure 68.4**  *How mipmapping reduces surface caching requirements.*](images/68-04.jpg) All mipmapped texture tiles are generated as a preprocessing step, and loaded from disk at runtime. One interesting point is that a key to diff --git a/69-03.md b/69-03.md index 4cef3e9..8918328 100644 --- a/69-03.md +++ b/69-03.md @@ -27,8 +27,7 @@ mapping, avoiding the work required for perspective. Mind you, the models are perspective-correct at the vertices; it's just the pixels between the vertices that suffer slight warping. -![](images/69-01.jpg)\ - **Figure 69.1**  *Quake's triangle-model drawing pipeline.* +![**Figure 69.1**  *Quake's triangle-model drawing pipeline.*](images/69-01.jpg) #### Trading Subpixel Precision for Speed {#Heading8} diff --git a/69-04.md b/69-04.md index 668b0c1..f8f524e 100644 --- a/69-04.md +++ b/69-04.md @@ -169,8 +169,7 @@ clear success. D_PolysetRecursiveTriangle (lp3, new, lp2); } -![](images/69-02.jpg)\ - **Figure 69.2**  *One recursive subdivision triangle-drawing step.* +![**Figure 69.2**  *One recursive subdivision triangle-drawing step.*](images/69-02.jpg) #### More Ideas that Might Work {#Heading11}