diff --git a/01-02.md b/01-02.md index afeee9f..d5be722 100644 --- a/01-02.md +++ b/01-02.md @@ -73,7 +73,7 @@ value before reading the next byte, we'll minimize memory requirements and be able to handle any size file at all. Sounds good, eh? Listing 1.1 shows an implementation of this approach. -Listing 1.1 uses C's **read()** function to read a single byte, adds the +Listing 1.1 uses C's `read()` function to read a single byte, adds the byte into the checksum value, and loops back to handle the next byte until the end of the file is reached. The code is compact, easy to write, and functions perfectly—with one slight hitch: diff --git a/01-03.md b/01-03.md index 122e17c..ece0858 100644 --- a/01-03.md +++ b/01-03.md @@ -143,7 +143,7 @@ that, we have to understand what's wrong with the current design. #### Know the Territory {#Heading9} Just why is Listing 1.1 so slow? In a word: overhead. The C library -implements the **read()** function by calling DOS to read the desired +implements the `read()` function by calling DOS to read the desired number of bytes. (I figured this out by watching the code execute with a debugger, but you can buy library source code from both Microsoft and Borland.) That means that Listing 1.1 (and Listing 1.3 as well) executes @@ -158,7 +158,7 @@ if the desired byte has already been read, read it from the disk if not, store the byte in the specified location, and return. All of that takes a *long* time—far, far longer than the rest of the main loop in Listing 1.1. In short, Listing 1.1 spends virtually all of its time executing -**read(),** and most of that time is spent somewhere down in DOS. +`read()`, and most of that time is spent somewhere down in DOS. You can verify this for yourself by watching the code with a debugger or using a code profiler, but take my word for it: There's a great deal of @@ -174,13 +174,13 @@ bytes internally, doling them out to the application as needed by reading them from memory rather than calling DOS. Let's try using stream I/O and see what happens. -Listing 1.4 is similar to Listing 1.1, but uses **fopen()** and -**getc()** (rather than **open()** and **read()**) to access the file +Listing 1.4 is similar to Listing 1.1, but uses `fopen()` and +`getc()` (rather than `open()` and `read()`) to access the file being checksummed. The results confirm our theories splendidly, and validate our new design. As shown in Table 1.1, Listing 1.4 runs more than an order of magnitude faster than even the assembly version of Listing 1.1, *even though Listing 1.1 and Listing 1.4 look almost the -same*. To the casual observer, **read()** and **getc()** would seem +same*. To the casual observer, `read()` and `getc()` would seem slightly different but pretty much interchangeable, and yet in this application the performance difference between the two is about the same as that between a 4.77 MHz PC and a 16 MHz 386. \ No newline at end of file diff --git a/01-04.md b/01-04.md index ff64474..bdb00a7 100644 --- a/01-04.md +++ b/01-04.md @@ -93,15 +93,15 @@ approaches. Listing 1.4 is good, but let's see if there are other—perhaps less obvious—ways to get the same results faster. Let's start by considering -why Listing 1.4 is so much better than Listing 1.1. Like **read()**, -**getc()** calls DOS to read from the file; the speed improvement of -Listing 1.4 over Listing 1.1 occurs because **getc()** eads many bytes +why Listing 1.4 is so much better than Listing 1.1. Like `read()`, +`getc()` calls DOS to read from the file; the speed improvement of +Listing 1.4 over Listing 1.1 occurs because `getc()` eads many bytes at once via DOS, then manages those bytes for us. That's faster than -reading them one at a time using **read()**—but there's no reason to +reading them one at a time using `read()`—but there's no reason to think that it's faster than having our program read and manage blocks itself. Easier, yes, but not faster. -Consider this: Every invocation of **getc()** involves pushing a +Consider this: Every invocation of `getc()` involves pushing a parameter, executing a call to the C library function, getting the parameter (in the C library code), looking up information about the desired stream, unbuffering the next byte from the stream, and returning diff --git a/02-01.md b/02-01.md index 2266ce2..b82d043 100644 --- a/02-01.md +++ b/02-01.md @@ -87,8 +87,8 @@ performed just once, rather than four times. While the code may not look much different from the original, and in fact still contains exactly the same number of instructions, the performance of the entire subroutine improved by about 10 percent from just this one change. (Incidentally, -that wasn't the end of the optimization; I eliminated the **DEC** and -**JNJ** instructions by expanding the four iterations of the loop—but +that wasn't the end of the optimization; I eliminated the `DEC` and +`JNJ` instructions by expanding the four iterations of the loop—but that's a tale for another chapter.) The point is this: To write truly superior assembly programs, you need diff --git a/03-01.md b/03-01.md index d4dabc1..76a2e11 100644 --- a/03-01.md +++ b/03-01.md @@ -109,7 +109,7 @@ precise, the 8253 counts once every 838.1 nanoseconds. (A nanosecond is one billionth of a second, and is abbreviated ns.) Listing 3.1 shows 8253-based timer software, consisting of three -subroutines: **ZTimerOn, ZTimerOff**, and **ZTimerReport**. For the +subroutines: `ZTimerOn`, `ZTimerOff`, and `ZTimerReport`. For the remainder of this book, I'll refer to these routines collectively as the "Zen timer." C-callable versions of the two precision Zen timers are presented in Chapter K on the companion CD-ROM. \ No newline at end of file diff --git a/03-03.md b/03-03.md index 58a2b13..cc5dcbb 100644 --- a/03-03.md +++ b/03-03.md @@ -27,8 +27,8 @@ and you'll be on the right road. #### Starting the Zen Timer {#Heading6} -**ZTimerOn** is called at the start of a segment of code to be timed. -**ZTimerOn** saves the context of the calling code, disables interrupts, +`ZTimerOn` is called at the start of a segment of code to be timed. +`ZTimerOn` saves the context of the calling code, disables interrupts, sets timer 0 of the 8253 to mode 2 (divide-by-N mode), sets the initial timer count to 0, restores the context of the calling code, and returns. (I'd like to note that while Intel's documentation for the 8253 seems to @@ -36,21 +36,21 @@ indicate that a timer won't reset to 0 until it finishes counting down, in actual practice, timers seem to reset to 0 as soon as they're loaded.) -Two aspects of **ZTimerOn** are worth discussing further. One point of -interest is that **ZTimerOn** disables interrupts. (**ZTimerOff** later -restores interrupts to the state they were in when **ZTimerOn** was -called.) Were interrupts not disabled by **ZTimerOn**, keyboard, mouse, +Two aspects of `ZTimerOn` are worth discussing further. One point of +interest is that `ZTimerOn` disables interrupts. (`ZTimerOff` later +restores interrupts to the state they were in when `ZTimerOn` was +called.) Were interrupts not disabled by `ZTimerOn`, keyboard, mouse, timer, and other interrupts could occur during the timing interval, and the time required to service those interrupts would incorrectly and erratically appear to be part of the execution time of the code being measured. As a result, code timed with the Zen timer should not expect any hardware interrupts to occur during the interval between any call to -**ZTimerOn** and the corresponding call to **ZTimerOff**, and should not +`ZTimerOn` and the corresponding call to `ZTimerOff`, and should not enable interrupts during that time. ### Time and the PC {#Heading7} -A second interesting point about **ZTimerOn** is that it may introduce +A second interesting point about `ZTimerOn` is that it may introduce some small inaccuracy into the system clock time whenever it is called. To understand why this is so, we need to examine the way in which both the 8253 and the PC's system clock (which keeps the current time) work. @@ -89,11 +89,11 @@ connected to the hardware interrupt 0 (IRQ0) line on the system board, so every 54.925 ms, timer 0 causes hardware interrupt 0 to occur. The interrupt vector for IRQ0 is set by the BIOS at power-up time to -point to a BIOS routine, **TIMER\_INT,** that maintains a time-of-day -count. **TIMER\_INT** keeps a 16-bit count of IRQ0 interrupts in the +point to a BIOS routine, `TIMER_INT`, that maintains a time-of-day +count. `TIMER_INT` keeps a 16-bit count of IRQ0 interrupts in the BIOS data area at address 0000:046C (all addresses in this book are given in segment:offset hexadecimal pairs); this count turns over once -an hour (less a few microseconds), and when it does, **TIMER\_INT** +an hour (less a few microseconds), and when it does, `TIMER_INT` updates a 16-bit hour count at address 0000:046E in the BIOS data area. This count is the basis for the current time and date that DOS supports via functions 2AH (2A hexadecimal) through 2DH and by way of the DATE diff --git a/03-04.md b/03-04.md index f77fca3..8ba04fe 100644 --- a/03-04.md +++ b/03-04.md @@ -26,7 +26,7 @@ see whether an IRQ0 interrupt is pending. (Remember, interrupts are off while the Zen timer runs, so the timer interrupt cannot be recognized until the Zen timer stops and enables interrupts.) If an IRQ0 interrupt is pending, then timer 0 has turned over and generated a timer -interrupt. Recall that **ZTimerOn** initially sets timer 0 to 0, in +interrupt. Recall that `ZTimerOn` initially sets timer 0 to 0, in order to allow for the longest possible period—about 54 ms—before timer 0 reaches 0 and generates the timer interrupt. @@ -39,7 +39,7 @@ started. In addition, a timer interrupt is generated when timer 0 is switched from mode 3 to mode 2, advancing the system clock by up to 54.925 ms, although this only happens the first time the Zen timer is run after a warm or cold boot. Finally, up to 54.925 ms can again be -lost when **ZTimerOff** is called, since that routine again sets the +lost when `ZTimerOff` is called, since that routine again sets the timer count to zero. Net result: The system clock will run up to 110 ms (about a ninth of a second) slow each time the Zen timer is used. @@ -70,27 +70,27 @@ is correct. ### Stopping the Zen Timer {#Heading8} -At some point after **ZTimerOn** is called, **ZTimerOff** must always be -called to mark the end of the timing interval. **ZTimerOff** saves the +At some point after `ZTimerOn` is called, `ZTimerOff` must always be +called to mark the end of the timing interval. `ZTimerOff` saves the context of the calling program, latches and reads the timer 0 count, converts that count from the countdown value that the timer maintains to -the number of counts elapsed since **ZTimerOn** was called, and stores +the number of counts elapsed since `ZTimerOn` was called, and stores the result. Immediately after latching the timer 0 count—and before -enabling interrupts—**ZTimerOff** checks the 8259 interrupt controller +enabling interrupts—`ZTimerOff` checks the 8259 interrupt controller to see if there is a pending timer interrupt, setting a flag to mark that the timer overflowed if there is indeed a pending timer interrupt. -After that, **ZTimerOff** executes just the overhead code of -**ZTimerOn** and **ZTimerOff** 16 times, and averages and saves the +After that, `ZTimerOff` executes just the overhead code of +`ZTimerOn` and `ZTimerOff` 16 times, and averages and saves the results in order to determine how many of the counts in the timing result just obtained were incurred by the overhead of the Zen timer rather than by the code being timed. -Finally, **ZTimerOff** restores the context of the calling program, +Finally, `ZTimerOff` restores the context of the calling program, including the state of the interrupt flag that was in effect when -**ZTimerOn** was called to start timing, and returns. +`ZTimerOn` was called to start timing, and returns. -One interesting aspect of **ZTimerOff** is the manner in which timer 0 +One interesting aspect of `ZTimerOff` is the manner in which timer 0 is stopped in order to read the timer count. We don't actually have to stop timer 0 to read the count; the 8253 provides a special latched read feature for the specific purpose of reading the count while a time is @@ -102,48 +102,48 @@ without breaking stride. ### Reporting Timing Results {#Heading9} -**ZTimerReport** may be called to display timing results at any time -after both **ZTimerOn** and **ZTimerOff** have been called. -**ZTimerReport** first checks to see whether the timer overflowed -(counted down to 0 and turned over) before **ZTimerOff** was called; if -overflow did occur, **ZTimerOff** prints a message to that effect and -returns. Otherwise, **ZTimerReport** subtracts the reference count +`ZTimerReport` may be called to display timing results at any time +after both `ZTimerOn` and `ZTimerOff` have been called. +`ZTimerReport` first checks to see whether the timer overflowed +(counted down to 0 and turned over) before `ZTimerOff` was called; if +overflow did occur, `ZTimerOff` prints a message to that effect and +returns. Otherwise, `ZTimerReport` subtracts the reference count (representing the overhead of the Zen timer) from the count measured -between the calls to **ZTimerOn** and **ZTimerOff**, converts the result +between the calls to `ZTimerOn` and `ZTimerOff`, converts the result from timer counts to microseconds, and prints the resulting time in microseconds to the standard output. -Note that **ZTimerReport** need not be called immediately after -**ZTimerOff**. In fact, after a given call to **ZTimerOff, -ZTimerReport** can be called at any time right up until the next call to -**ZTimerOn**. +Note that `ZTimerReport` need not be called immediately after +`ZTimerOff`. In fact, after a given call to `ZTimerOff, +ZTimerReport` can be called at any time right up until the next call to +`ZTimerOn`. You may want to use the Zen timer to measure several portions of a program while it executes normally, in which case it may not be -desirable to have the text printed by **ZTimerReport** interfere with +desirable to have the text printed by `ZTimerReport` interfere with the program's normal display. There are many ways to deal with this. One approach is removal of the invocations of the DOS print string function -(INT 21H with AH equal to 9) from **ZTimerReport**, instead running the +(INT 21H with AH equal to 9) from `ZTimerReport`, instead running the program under a debugger that supports screen flipping (such as Turbo Debugger or CodeView), placing a breakpoint at the start of -**ZTimerReport**, and directly observing the count in microseconds as -**ZTimerReport** calculates it. +`ZTimerReport`, and directly observing the count in microseconds as +`ZTimerReport` calculates it. -A second approach is modification of **ZTimerReport** to place the +A second approach is modification of `ZTimerReport` to place the result at some safe location in memory, such as an unused portion of the BIOS data area. -A third approach is alteration of **ZTimerReport** to print the result +A third approach is alteration of `ZTimerReport` to print the result over a serial port to a terminal or to another PC acting as a terminal. Similarly, many debuggers can be run from a remote terminal via a serial link. -Yet another approach is modification of **ZTimerReport** to send the +Yet another approach is modification of `ZTimerReport` to send the result to the printer via either DOS function 5 or BIOS interrupt 17H. -A final approach is to modify **ZTimerReport** to print the result to +A final approach is to modify `ZTimerReport` to print the result to the auxiliary output via DOS function 4, and to then write and load a -special device driver named **AUX**, to which DOS function 4 output +special device driver named `AUX`, to which DOS function 4 output would automatically be directed. This device driver could send the result anywhere you might desire. The result might go to the secondary display adapter, over a serial port, or to the printer, or could simply diff --git a/03-05.md b/03-05.md index bae43eb..9ea4a6a 100644 --- a/03-05.md +++ b/03-05.md @@ -13,7 +13,7 @@ pages: 048-050 ### Notes on the Zen Timer {#Heading10} The Zen timer subroutines are designed to be near-called from assembly -language code running in the public segment **Code**. The Zen timer +language code running in the public segment `Code`. The Zen timer subroutines can, however, be called from any assembly or high-level language code that generates OBJ files that are compatible with the Microsoft linker, simply by modifying the segment that the timer code @@ -26,9 +26,9 @@ transparent to the calling code. If you do change the Zen timer routines to far procedures in order to call them from code running in another segment, be sure to make *all* -the Zen timer routines far, including **ReferenceZTimerOn** and -**ReferenceZTimerOff**. (You'll have to put **FAR PTR** overrides on the -calls from **ZTimerOff** to the latter two routines if you do make them +the Zen timer routines far, including `ReferenceZTimerOn` and +`ReferenceZTimerOff`. (You'll have to put `FAR PTR` overrides on the +calls from `ZTimerOff` to the latter two routines if you do make them far.) If the reference routines aren't the same type—near or far—as the other routines, they won't reflect the true overhead incurred by starting and stopping the Zen timer. @@ -76,9 +76,9 @@ computers. Listing 3.2 shows a test-bed program for measuring code performance with the Zen timer. This program sets DS equal to CS (for reasons we'll discuss shortly), includes the code to be measured from the file -TESTCODE, and calls **ZTimerReport** to display the timing results. +TESTCODE, and calls `ZTimerReport` to display the timing results. Consequently, the code being measured should be in the file TESTCODE, -and should contain calls to **ZTimerOn** and **ZTimerOff** . +and should contain calls to `ZTimerOn` and `ZTimerOff` . **LISTING 3.2 PZTEST.ASM** @@ -124,10 +124,10 @@ Code ends Listing 3.3 shows some sample code to be timed. This listing measures the time required to execute 1,000 loads of AL from the memory variable -**MemVar** . Note that Listing 3.3 calls **ZTimerOn** to start timing, -performs 1,000 **MOV** instructions in a row, and calls **ZTimerOff** to +`MemVar` . Note that Listing 3.3 calls `ZTimerOn` to start timing, +performs 1,000 `MOV` instructions in a row, and calls `ZTimerOff` to end timing. When Listing 3.2 is named TESTCODE and included by Listing -3.3, Listing 3.2 calls **ZTimerReport** to display the execution time +3.3, Listing 3.2 calls `ZTimerReport` to display the execution time after the code in Listing 3.3 has been run. **LISTING 3.3 LST3-3.ASM** @@ -160,7 +160,7 @@ Skip: ``` It's worth noting that Listing 3.3 begins by jumping around the memory -variable **MemVar**. This approach lets us avoid reproducing Listing 3.2 +variable `MemVar`. This approach lets us avoid reproducing Listing 3.2 in its entirety for each code fragment we want to measure; by defining any needed data right in the code segment and jumping around that data, each listing becomes self-contained and can be plugged directly into @@ -169,7 +169,7 @@ anything else precisely so that data can be embedded in code fragments being timed. Note that only after the initial jump is performed in Listing 3.3 is the Zen timer started, since we don't want to include the execution time of start-up code in the timing interval. That's why the -calls to **ZTimerOn** and **ZTimerOff** are in TESTCODE, not in +calls to `ZTimerOn` and `ZTimerOff` are in TESTCODE, not in PZTEST.ASM; this way, we have full control over which portion of TESTCODE is timed, and we can keep set-up code and the like out of the timing interval. \ No newline at end of file diff --git a/03-06.md b/03-06.md index 78ef405..53b7a8a 100644 --- a/03-06.md +++ b/03-06.md @@ -107,7 +107,7 @@ repetitions of a given instruction are timed, there's just too much noise in the timing process—between dynamic RAM refresh, the prefetch queue, and the internal state of the processor at the start of timing—for that last digit to have any significance.) Given the test -PC's 4.77 MHz clock, this works out to about 17 cycles per **MOV**, +PC's 4.77 MHz clock, this works out to about 17 cycles per `MOV`, which is actually a good bit longer than Intel's specified 10-cycle execution time for this instruction. (See the MASM or TASM documentation, or Intel's processor reference manuals, for official @@ -127,8 +127,8 @@ pztime In fact, that's exactly how I timed each of the listings in this book. Code fragments you write yourself can be timed in just the same way. If you wish to time code directly in place in your programs, rather than in -the test-bed program of Listing 3.2, simply insert calls to **ZTimerOn, -ZTimerOff**, and **ZTimerReport** in the appropriate places and link +the test-bed program of Listing 3.2, simply insert calls to `ZTimerOn, +ZTimerOff`, and `ZTimerReport` in the appropriate places and link PZTIMER to your program. ### The Long-Period Zen Timer {#Heading12} diff --git a/03-07.md b/03-07.md index 0a5635f..b504e4c 100644 --- a/03-07.md +++ b/03-07.md @@ -67,10 +67,10 @@ to accept the fact that on PS/2 computers we may occasionally get a reading that's off by 54 ms, and leave it at that. I've set up Listing 3.5 so that it can assemble to either use or not use -the undocumented timer-stopping feature, as you please. The **PS2** -equate selects between the two modes of operation. If **PS2** is 1 (as +the undocumented timer-stopping feature, as you please. The `PS2` +equate selects between the two modes of operation. If `PS2` is 1 (as it is in Listing 3.5), then the latch-and-read method is used; if -**PS2** is 0, then the undocumented timer-stop approach is used. The +`PS2` is 0, then the undocumented timer-stop approach is used. The latch-and-read method will work on all PC-compatible computers, but may occasionally produce results that are incorrect by 54 ms. The timer-stop approach avoids synchronization problems, but doesn't work on all diff --git a/03-08.md b/03-08.md index db42705..bd85c0c 100644 --- a/03-08.md +++ b/03-08.md @@ -15,25 +15,25 @@ approach could conceivably cause erratic 8253 operation, which could in turn seriously affect your computer's operation until the next reboot. In non-8253-compatible systems, I've observed not only wildly incorrect timing results, but also failure of a diskette drive to operate properly -after the long-period Zen timer with **PS2** set to 0 has run, so be -alert for signs of trouble if you do set **PS2** to 0. +after the long-period Zen timer with `PS2` set to 0 has run, so be +alert for signs of trouble if you do set `PS2` to 0. Rebooting should clear up any timer-related problems of the sort described above. (This gives us another reason to reboot at the end of each code-timing session.) You should *immediately* reboot and set the -**PS2** equate to 1 if you get erratic or obviously incorrect results -with the long-period Zen timer when **PS2** is set to 0. If you want to -set **PS2** to 0, it would be a good idea to time a few of the listings -in this book with **PS2** set first to 1 and then to 0, to make sure +`PS2` equate to 1 if you get erratic or obviously incorrect results +with the long-period Zen timer when `PS2` is set to 0. If you want to +set `PS2` to 0, it would be a good idea to time a few of the listings +in this book with `PS2` set first to 1 and then to 0, to make sure that the results match. If they're consistently different, you should -set **PS2** to 1. +set `PS2` to 1. While the the non-PS/2 version is more dangerous than the PS/2 version, it also produces more accurate results when it does work. If you have a non-PS/2 PC-compatible computer, the choice between the two timing approaches is yours. -If you do leave the **PS2** equate at 1 in Listing 3.5, you should +If you do leave the `PS2` equate at 1 in Listing 3.5, you should repeat each code-timing run several times before relying on the results to be accurate to more than 54 ms, since variations may result from the possible lack of synchronization between the timer 0 count and the BIOS @@ -58,7 +58,7 @@ all you have to do is link in the long-period timer instead. Listing 3.6 shows a test-bed program for the long-period Zen timer. While this program is similar to Listing 3.2, it's worth noting that -Listing 3.6 waits for a few seconds before calling **ZTimerOn**, thereby +Listing 3.6 waits for a few seconds before calling `ZTimerOn`, thereby allowing any pending keyboard interrupts to be processed. Since interrupts must be left on in order to time periods longer than 54 ms, the interrupts generated by keystrokes (including the upstroke of the diff --git a/03-09.md b/03-09.md index dd90b81..56ae8bd 100644 --- a/03-09.md +++ b/03-09.md @@ -124,31 +124,31 @@ the result is 72,544 µs, or about 3.63 µs per load of AL from memory. This is just slightly longer than the time per load of AL measured by the precision Zen timer, as we would expect given that interrupts are left enabled by the long-period Zen timer. The extra fraction of a -microsecond measured per **MOV** reflects the time required to execute +microsecond measured per `MOV` reflects the time required to execute the BIOS code that handles the 18.2 timer interrupts that occur each second. Note that the command can take as much as 10 minutes to finish on a slow PC if you are using MASM, with most of that time spent assembling Listing 3.8. Why? Because MASM is notoriously slow at assembling -**REPT** blocks, and the block in Listing 3.8 is repeated 20,000 times. +`REPT` blocks, and the block in Listing 3.8 is repeated 20,000 times. ### Using the Zen Timer from C {#Heading15} The Zen timer can be used to measure code performance when programming in C—but not right out of the box. As presented earlier, the timer is designed to be called from assembly language; some relatively minor -modifications are required before the **ZTimerOn** (start timer), -**ZTimerOff** (stop timer), and **ZTimerReport** (display timing +modifications are required before the `ZTimerOn` (start timer), +`ZTimerOff` (stop timer), and `ZTimerReport` (display timing results) routines can be called from C. There are two separate cases to be dealt with here: small code model and large; I'll tackle the simpler one, the small code model, first. Altering the Zen timer for linking to a small code model C program -involves the following steps: **C** hange **ZTimerOn** to -**\_ZTimerOn**, change **ZTimerOff** to **\_ZTimerOff**, change -**ZTimerReport** to **\_ZTimerReport**, and change **Code** to -**\_TEXT** . Figure 3.2 shows the line numbers and new states of all +involves the following steps: `C` hange `ZTimerOn` to +`_ZTimerOn`, change `ZTimerOff` to `_ZTimerOff`, change +`ZTimerReport` to `_ZTimerReport`, and change `Code` to +`_TEXT` . Figure 3.2 shows the line numbers and new states of all lines from Listing 3.1 that must be changed. These changes convert the code to use C-style external label names and the small model C code segment. (In C++, use the "C" specifier, as in diff --git a/03-10.md b/03-10.md index 60a4915..ed65a22 100644 --- a/03-10.md +++ b/03-10.md @@ -10,7 +10,7 @@ chapter: '03' pages: 070-073 --- -when declaring the timer routines **extern**, so that name-mangling +when declaring the timer routines `extern`, so that name-mangling doesn't occur, and the linker can find the routines' C-style names.) That's all it takes; after doing this, you'll be able to use the Zen @@ -59,8 +59,8 @@ push cs call near ptr ReferenceZTimerOn ``` -(and likewise for **ReferenceZTimerOff** ), which works because -**ReferenceZTimerOn** is in the same segment as the calling code. This +(and likewise for `ReferenceZTimerOff` ), which works because +`ReferenceZTimerOn` is in the same segment as the calling code. This is normally a great optimization, being both smaller and faster than a far call. However, it's not so great for the Zen @@ -68,12 +68,12 @@ far call. However, it's not so great for the Zen timer, because our purpose in calling the reference timing code is to determine exactly how much time is taken by overhead code—including the -far calls to **ZTimerOn** and **ZTimerOf**f! By converting the far calls +far calls to `ZTimerOn` and `ZTimerOf`f! By converting the far calls to push/near call pairs within the Zen timer module, TASM makes it impossible to emulate exactly the overhead of the Zen timer, and makes timings slightly (about 16 cycles on a 386) less accurate. -What's the solution? Put the **NOSMART** directive at the start of the +What's the solution? Put the `NOSMART` directive at the start of the Zen timer code. This directive instructs TASM to turn off all optimizations, including converting far calls to push/near call pairs. By the way, there is, to the best of my knowledge, no such problem with diff --git a/04-02.md b/04-02.md index 1d6eef7..db1b3b5 100644 --- a/04-02.md +++ b/04-02.md @@ -57,13 +57,13 @@ required for every word-sized access to a memory operand. For instance, mov ax,word ptr [MemVar] ``` -takes 4 cycles longer to read the word at address **MemVar** than +takes 4 cycles longer to read the word at address `MemVar` than ```nasm mov al,byte ptr [MemVar] ``` -takes to read the byte at address **MemVar.** (Actually, the difference +takes to read the byte at address `MemVar`. (Actually, the difference between the two isn't very likely to be exactly 4 cycles, for reasons that will become clear once we discuss the prefetch queue and dynamic RAM refresh cycle-eaters later in this chapter.) @@ -88,7 +88,7 @@ add byte ptr [MemVar],al String instructions can suffer from the 8-bit bus cycle-eater to a greater extent than other instructions. Believe it or not, a single -**REP MOVSW** instruction can lose as much as 131,070 word-sized memory +`REP MOVSW` instruction can lose as much as 131,070 word-sized memory accesses x 4 cycles, or *524,280 cycles* to the 8-bit bus cycle-eater! In other words, one 8088 instruction (admittedly, an instruction that does a great deal) can take over one-tenth of a second longer on an 8088 @@ -115,9 +115,9 @@ instance, Listing 4.1, which uses a byte-sized memory variable as a loop counter, runs in 10.03 s per loop. That's 20 percent faster than the 12.05 µs per loop execution time of Listing 4.2, which uses a word-sized counter. Why the difference in execution times? Simply because each -word-sized **DEC** performs 4 byte-sized memory accesses (two to read +word-sized `DEC` performs 4 byte-sized memory accesses (two to read the word-sized operand and two to write the result back to memory), -while each byte-sized **DEC** performs only 2 byte-sized memory accesses +while each byte-sized `DEC` performs only 2 byte-sized memory accesses in all. **LISTING 4.1 LST4-1.ASM** @@ -161,8 +161,8 @@ listings in this book. Throughout this book I've modeled the sample code after working code so that the timing results are applicable to real-world programming. In Listings 4.1 and 4.2, for example, I could have shown a still greater advantage for byte-sized operands simply by -performing 1,000 **DEC** instructions in a row, with no branching at -all. However, **DEC** instructions don't exist in a vacuum, so in the +performing 1,000 `DEC` instructions in a row, with no branching at +all. However, `DEC` instructions don't exist in a vacuum, so in the listings I used code that both decremented the counter and tested the result. The difference is that between decrementing a memory location (simply an instruction) and using a loop counter (a functional diff --git a/04-03.md b/04-03.md index 51cb5ec..321b73e 100644 --- a/04-03.md +++ b/04-03.md @@ -47,7 +47,7 @@ accesses must be. (Remember that the Bus Interface Unit must perform two byte-sized memory accesses in order to handle a word-sized memory operand.) However, Listing 4.3 is considerably faster because it expends only 4 additional cycles to read the second byte of each word, while -Listing 4.4 performs a second **LODSB,** requiring 13 cycles, to read +Listing 4.4 performs a second `LODSB`, requiring 13 cycles, to read the second byte of each word. **LISTING 4.3 LST4-3.ASM** @@ -166,7 +166,7 @@ shr ax,1 shr ax,1 ``` -should execute in 10 cycles, since each **SHR** takes 2 cycles to +should execute in 10 cycles, since each `SHR` takes 2 cycles to execute, according to Intel's specifications. Those specifications contain Intel's official instruction execution times, but in this case—and in many others—the specifications are drastically wrong. Why? diff --git a/04-04.md b/04-04.md index 8f7c421..2f545a4 100644 --- a/04-04.md +++ b/04-04.md @@ -12,7 +12,7 @@ pages: 087-090 #### Official Execution Times Are Only Part of the Story {#Heading10} -The sequence of 5 **SHR** instructions in the last example is 10 bytes +The sequence of 5 `SHR` instructions in the last example is 10 bytes long. That means that it can never execute in less than 24 cycles even if the 4-byte prefetch queue is full when it starts, since 6 instruction bytes would still remain to be fetched, at 4 cycles per fetch. If the @@ -90,17 +90,17 @@ that), and some of the other prefetch states may well produce distinctly different results. For example, consider the code in Listings 4.5 and 4.6. Listing 4.5 -shows our familiar **SHR** case. Here, because the prefetch queue is +shows our familiar `SHR` case. Here, because the prefetch queue is always empty, execution time should work out to about 4 cycles per byte, -or 8 cycles per **SHR,** as shown in Figure 4.3. (Figure 4.3 illustrates +or 8 cycles per `SHR`, as shown in Figure 4.3. (Figure 4.3 illustrates the relationship between instruction fetching and execution in a simplified way, and is not intended to show the exact timings of 8088 operations.) That's quite a contrast to the official 2-cycle execution -time of **SHR**. In fact, the Zen timer reports that Listing 4.5 +time of `SHR`. In fact, the Zen timer reports that Listing 4.5 executes in 1.81µs per byte, or slightly *more* than 4 cycles per byte. (The extra time is the result of the dynamic RAM refresh cycle-eater, which we'll discuss shortly.) Going by Listing 4.5, we would conclude -that the "true" execution time of **SHR** is 8.64 cycles. +that the "true" execution time of `SHR` is 8.64 cycles. **LISTING 4.5 LST4-5.ASM** @@ -138,27 +138,27 @@ that the "true" execution time of **SHR** is 8.64 cycles. ![**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 -prefetch queue is always full when they finish, each **SHR** should be -ready and waiting in the prefetch queue when the preceding **MUL** ends. -As a result, we'd expect that each **SHR** would execute in 2 cycles; +Now let's examine Listing 4.6. Here each `SHR` follows a `MUL` +instruction. Since `MUL` instructions take so long to execute that the +prefetch queue is always full when they finish, each `SHR` should be +ready and waiting in the prefetch queue when the preceding `MUL` ends. +As a result, we'd expect that each `SHR` would execute in 2 cycles; together with the 118-cycle execution time of multiplying 0 times 0, the -total execution time should come to 120 cycles per **SHR/MUL** pair, as +total execution time should come to 120 cycles per `SHR/MUL` pair, as shown in Figure 4.4. And, by God, when we run Listing 4.6 we get an -execution time of 25.14 µs per **SHR/MUL** pair, or *exactly* 120 -cycles! According to these results, the "true" execution time of **SHR** +execution time of 25.14 µs per `SHR/MUL` pair, or *exactly* 120 +cycles! According to these results, the "true" execution time of `SHR` would seem to be 2 cycles, quite a change from the conclusion we drew from Listing 4.5. -The key point is this: We've seen one code sequence in which **SHR** +The key point is this: We've seen one code sequence in which `SHR` took 8-plus cycles to execute, and another in which it took only 2 -cycles. Are we talking about two different forms of **SHR** here? Of +cycles. Are we talking about two different forms of `SHR` here? Of course not—the difference is purely a reflection of the differing states in which the preceding code left the prefetch queue. In Listing 4.5, -each **SHR** after the first few follows a slew of other **SHR** +each `SHR` after the first few follows a slew of other `SHR` instructions which have sucked the prefetch queue dry, so overall -performance reflects instruction fetch time. By contrast, each **SHR** -in Listing 4.6 follows a **MUL** instruction which leaves the prefetch +performance reflects instruction fetch time. By contrast, each `SHR` +in Listing 4.6 follows a `MUL` instruction which leaves the prefetch queue full, so overall performance reflects Execution Unit execution time. \ No newline at end of file diff --git a/04-05.md b/04-05.md index 83b4f6a..bd0939c 100644 --- a/04-05.md +++ b/04-05.md @@ -30,9 +30,9 @@ The truth is that it never hurts performance to reduce either the cycle count or the byte count of a given bit of code, but there's no guarantee that one or the other will improve performance either. For example, consider Listing 4.7, which consists of a series of 4-cycle, 2-byte -**MOV AL,0** instructions, and which executes at the rate of 1.81 µs per -instruction. Now consider Listing 4.8, which replaces the 4-cycle **MOV -AL,0** with the 3-cycle (but still 2-byte) **SUB AL,AL,** Despite its +`MOV AL,0` instructions, and which executes at the rate of 1.81 µs per +instruction. Now consider Listing 4.8, which replaces the 4-cycle `MOV +AL,0` with the 3-cycle (but still 2-byte) `SUB AL,AL,` Despite its 1-cycle-per-instruction advantage, Listing 4.8 runs at exactly the same 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 @@ -108,19 +108,19 @@ in this book, and use the Zen timer to measure your code. Don't think that because overall instruction execution time is determined by both instruction fetch time and Execution Unit execution time, the two times should be added together when estimating -performance. For example, practically speaking, each **SHR** in Listing +performance. For example, practically speaking, each `SHR` in Listing 4.5 does not take 8 cycles of instruction fetch time plus 2 cycles of Execution Unit execution time to execute. Figure 4.3 shows that while a -given **SHR** is executing, the fetch of the next **SHR** is starting, +given `SHR` is executing, the fetch of the next `SHR` is starting, and since the two operations are overlapped for 2 cycles, there's no sense in charging the time to both instructions. You could think of the -extra instruction fetch time for **SHR** in Listing 4.5 as being 6 +extra instruction fetch time for `SHR` in Listing 4.5 as being 6 cycles, which yields an overall execution time of 8 cycles when added to the 2 cycles of Execution Unit execution time. -Alternatively, you could think of each **SHR** in Listing 4.5 as taking +Alternatively, you could think of each `SHR` in Listing 4.5 as taking 8 cycles to fetch, and then executing in effectively 0 cycles while the -next **SHR** is being fetched. Whichever perspective you prefer is fine. +next `SHR` is being fetched. Whichever perspective you prefer is fine. The important point is that the time during which the execution of one instruction and the fetching of the next instruction overlap should only be counted toward the overall execution time of one of the instructions. @@ -150,7 +150,7 @@ While short instructions minimize overall prefetch time, ironically they actually often suffer more from the prefetch queue bottleneck than do long instructions. Short instructions generally have such fast execution times that they drain the prefetch queue despite their small size. For -example, consider the **SHR** of Listing 4.5, which runs at only 25 +example, consider the `SHR` of Listing 4.5, which runs at only 25 percent of its Execution Unit execution time even though it's only 2 bytes long, thanks to the prefetch queue bottleneck. Short instructions are nonetheless generally faster than long instructions, thanks to the diff --git a/04-07.md b/04-07.md index 5bf03d2..f8c4d63 100644 --- a/04-07.md +++ b/04-07.md @@ -14,7 +14,7 @@ pages: 097-099 Let's look at examples from opposite ends of the spectrum in terms of the impact of DRAM refresh on code performance. First, consider the -series of **MUL** instructions in Listing 4.9. Since a 16-bit **MUL** on +series of `MUL` instructions in Listing 4.9. Since a 16-bit `MUL` on the 8088 executes in between 118 and 133 cycles and is only 2 bytes long, there should be plenty of time for the prefetch queue to fill after each instruction, even after DRAM refresh has taken its slice of @@ -42,15 +42,15 @@ request a memory access from the Bus Interface Unit.) call ZTimerOff ``` -Running Listing 4.9, we find that each **MUL** executes in 24.72 µs, or -exactly 118 cycles. Since that's the shortest time in which **MUL** can +Running Listing 4.9, we find that each `MUL` executes in 24.72 µs, or +exactly 118 cycles. Since that's the shortest time in which `MUL` can execute, we can see that no performance is lost to DRAM refresh. Listing 4.9 clearly illustrates that DRAM refresh only affects code performance when a DRAM refresh forces the Execution Unit of the 8088 to wait for a memory access. -Now let's look at the series of **SHR** instructions shown in Listing -4.10. Since **SHR** executes in 2 cycles but is 2 bytes long, the +Now let's look at the series of `SHR` instructions shown in Listing +4.10. Since `SHR` executes in 2 cycles but is 2 bytes long, the prefetch queue should be empty while Listing 4.10 executes, with the 8088 prefetching instruction bytes non-stop. As a result, the time per instruction of Listing 4.10 should precisely reflect the time required @@ -71,8 +71,8 @@ to fetch the instruction bytes. ``` Since 4 cycles are required to read each instruction byte, we'd expect -each **SHR** to execute in 8 cycles, or 1.676 µs, if there were no DRAM -refresh. In fact, each **SHR** in Listing 4.10 executes in 1.81 µs, +each `SHR` to execute in 8 cycles, or 1.676 µs, if there were no DRAM +refresh. In fact, each `SHR` in Listing 4.10 executes in 1.81 µs, indicating that DRAM refresh is taking 7.4 percent of the program's execution time. That's nearly 2 percent more than our worst-case estimate of the loss to DRAM refresh overhead! In fact, the result @@ -84,7 +84,7 @@ accesses for as many as 6 cycles, depending on the timing of the DRAM refresh's DMA request relative to the 8088's internal instruction execution state. When the code in Listing 4.10 runs, each DRAM refresh holds up the CPU for either 5 or 6 cycles, depending on where the 8088 -is in executing the current **SHR** instruction when the refresh request +is in executing the current `SHR` instruction when the refresh request occurs. Now we see that things can get even worse than we thought: *DRAM refresh can steal as much as 8.33 percent of available memory access time—6 out of every 72 cycles—from the 8088.* diff --git a/04-09.md b/04-09.md index 12f41bd..57893fa 100644 --- a/04-09.md +++ b/04-09.md @@ -79,8 +79,8 @@ computer standards. That sounds pretty serious, but we did make an unfounded assumption about memory access speed. Let's get some hard numbers. Listing 4.11 -accesses display memory at the 8088's maximum speed, by way of a **REP -MOVSW** with display memory as both source and destination. The code in +accesses display memory at the 8088's maximum speed, by way of a `REP +MOVSW` with display memory as both source and destination. The code in Listing 4.11 executes in 3.18 µs per access to display memory—not as long as we had assumed, but a long time nonetheless. @@ -119,7 +119,7 @@ long as we had assumed, but a long time nonetheless. For comparison, let's see how long the same code takes when accessing normal system RAM instead of display memory. The code in Listing 4.12, -which performs a **REP MOVSW** from the code segment to the code +which performs a `REP MOVSW` from the code segment to the code segment, executes in 1.39 µs per display memory access. That means that on average, 1.79 µs (more than 8 cycles!) are lost to the display adapter cycle-eater on each access. In other words, the display adapter diff --git a/04-10.md b/04-10.md index cfdb150..9fb3700 100644 --- a/04-10.md +++ b/04-10.md @@ -14,7 +14,7 @@ pages: 106-109 > A line-drawing subroutine, which executes perhaps a dozen instructions > for each display memory access, generally loses less performance to the > display adapter cycle-eater than does a block-copy or scrolling -> subroutine that uses **REP MOVS** instructions. Scaled and +> subroutine that uses `REP MOVS` instructions. Scaled and > three-dimensional graphics, which spend a great deal of time performing > calculations (often using very slow floating-point arithmetic), tend to > suffer less. diff --git a/05-01.md b/05-01.md index 1992ec1..5b3c861 100644 --- a/05-01.md +++ b/05-01.md @@ -72,7 +72,7 @@ The application we're going to examine searches a file for a specified string. We'll develop a program that will search the file specified on the command line for a string (also specified on the comline), then report whether the string was found or not. (Because the searched-for -string is obtained via **argv**, it can't contain any whitespace +string is obtained via `argv`, it can't contain any whitespace characters.) This is a *very* limited subset of what search utilities such as grep @@ -115,9 +115,9 @@ access overhead). ### Avoiding the String Trap {#Heading4} The easiest approach would be to use a C/C++ library function. The -closest match to what we need is **strstr()**, which searches one string -for the first occurrence of a second string. However, while **strstr()** +closest match to what we need is `strstr()`, which searches one string +for the first occurrence of a second string. However, while `strstr()` would work, it isn't ideal for our purposes. The problem is this: Where we want to search a fixed-length buffer for the first occurrence of a -string, **strstr()** searches a *string* for the first occurrence of +string, `strstr()` searches a *string* for the first occurrence of another string. \ No newline at end of file diff --git a/05-02.md b/05-02.md index 98fa345..aeabd15 100644 --- a/05-02.md +++ b/05-02.md @@ -10,11 +10,11 @@ chapter: '05' pages: 115-118 --- -We could put a zero byte at the end of our buffer to allow **strstr()** -to work, but why bother? The **strstr()** function must spend time +We could put a zero byte at the end of our buffer to allow `strstr()` +to work, but why bother? The `strstr()` function must spend time either checking for the end of the string being searched or determining the length of that string—wasted effort given that we already know -exactly how long our search buffer is. Even if a given **strstr()** +exactly how long our search buffer is. Even if a given `strstr()` implementation is well-written, its performance will suffer, at least for our application, from unnecessary overhead. @@ -28,26 +28,26 @@ for our application, from unnecessary overhead. Given that no C/C++ library function meets our needs precisely, an obvious alternative approach is the brute-force technique that uses -**memcmp()** to compare *every* potential matching location in the +`memcmp()` to compare *every* potential matching location in the buffer to the string we're searching for, as illustrated in Figure 5.1. By the way, we could, of course, use our own code, working with pointers -in a loop, to perform the comparison in place of **memcmp()**. But -**memcmp()** will almost certainly use the very fast **REPZ CMPS** +in a loop, to perform the comparison in place of `memcmp()`. But +`memcmp()` will almost certainly use the very fast `REPZ CMPS` instruction. However, *never assume!* It wouldn't hurt to use a debugger -to check out the actual machine-code implementation of **memcmp()** from +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()**. +language implementation of `memcmp()`. ![**Figure 5.1**  *The brute-force searching technique.*](images/05-01.jpg) -Invoking **memcmp()** for each potential match location works, but +Invoking `memcmp()` for each potential match location works, but entails considerable overhead. Each comparison requires that parameters -be pushed and that a call to and return from **memcmp()** be performed, +be pushed and that a call to and return from `memcmp()` be performed, along with a pass through the comparison loop. Surely there's a better way! -Indeed there is. We can eliminate most calls to **memcmp()** by +Indeed there is. We can eliminate most calls to `memcmp()` by performing a simple test on each potential match location that will reject most such locations right off the bat. We'll just check whether the first character of the potentially matching buffer location matches @@ -60,16 +60,16 @@ Figure 5.2. ### Using memchr() {#Heading6} There's yet a better way to implement this approach, however. Use the -**memchr()** function, which does nothing more or less than find the +`memchr()` function, which does nothing more or less than find the next occurrence of a specified character in a fixed-length buffer -(presumably by using the extremely efficient **REPNZ SCASB** +(presumably by using the extremely efficient `REPNZ SCASB` instruction, although again it wouldn't hurt to check). By using -**memchr()** to scan for potential matches that can then be fully tested -with **memcmp()**, we can build a highly efficient search engine that +`memchr()` to scan for potential matches that can then be fully tested +with `memcmp()`, we can build a highly efficient search engine that takes good advantage of the information we have about the buffer being 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. +heavily on repeated string instructions, assuming that the `memchr()` +and `memcmp()` library functions are properly coded. ![**Figure 5.2**  *The faster string-searching technique.*](images/05-02.jpg) @@ -94,7 +94,7 @@ let's make it restartable. As it happens, there's no great trick to putting the pieces of this search program together. Basically, we'll read in a buffer of data (we'll work with 16K at a time to avoid signed overflow problems with -integers), search it for a match with the **memchr()/memcmp()** engine +integers), search it for a match with the `memchr()`/`memcmp()` engine described, and exit with a "string found" response if the desired string is found. @@ -115,7 +115,7 @@ That's really all there is to it. Listing 5.1 shows the file-searching program. As you can see, it's not particularly complex, although a few fairly opaque lines of code are required to handle merging the end of one block with the start of the next. The code that searches a single -block—the function **SearchForString()—**is simple and compact (as it +block—the function `SearchForString()`—is simple and compact (as it should be, given that it's by far the most heavily-executed code in the listing). diff --git a/05-04.md b/05-04.md index 201d248..7e0e0a9 100644 --- a/05-04.md +++ b/05-04.md @@ -13,7 +13,7 @@ pages: 121-122 ### Interpreting Where the Cycles Go {#Heading8} To boost the overall performance of Listing 5.1, I would normally -convert **SearchForString()** to assembly language at this point. +convert `SearchForString()` to assembly language at this point. However, I'm not going to do that, and the reason is as important a lesson as any discussion of optimized assembly code is likely to be. Take a moment to examine some interesting performance aspects of the C @@ -28,7 +28,7 @@ think. When Listing 5.1 is run on a 1 MB assembly source file, it takes about three seconds to find the string "xxxend" (which is at the end of the file) on a 20 MHz 386 machine, with the entire file in a disk cache. If -**BLOCK\_SIZE** is trimmed from 16K to 4K, *execution time does not +`BLOCK_SIZE` is trimmed from 16K to 4K, *execution time does not increase perceptibly!* At 2K, the program slows slightly; it's not until the block size shrinks to 64 bytes that execution time becomes approximately double that of the 16K buffer. @@ -38,23 +38,23 @@ for the best performance, the increment in performance may not be very large, and might not justify the extra memory required for those larger blocks. Our next discovery is that, even though we read the file in large chunks, most of the execution time of Listing 5.1 is nonetheless -spent in executing the **read()** function. +spent in executing the `read()` function. -When I replaced the **read()** function call in Listing 5.1 with code +When I replaced the `read()` function call in Listing 5.1 with code that simply fools the program into thinking that a 1 MB file is being read, the program ran almost instantaneously—in less than 1/2 second, even when the searched-for string wasn't anywhere to be found. By contrast, Listing 5.1 requires three seconds to run even when searching for a single character that isn't found anywhere in the file, the case -in which a single call to **memchr()** (and thus a single **REPNZ -SCASB**) can eliminate an entire block at a time. +in which a single call to `memchr()` (and thus a single `REPNZ +SCASB`) can eliminate an entire block at a time. All in all, the time required for DOS disk access calls is taking up at least 80 percent of execution time, and search time is less than 20 percent of overall execution time. In fact, search time is probably a good deal less than 20 percent of the total, given that the overhead of loading the program, running through the C startup code, opening the -file, executing **printf()**, and exiting the program and returning to +file, executing `printf()`, and exiting the program and returning to the DOS shell are also included in my timings. Given which, it should be apparent why converting to assembly language isn't worth the trouble—the best we could do by speeding up the search is a 10 percent or so @@ -74,13 +74,13 @@ If, for example, your application will typically search buffers in which the first character of the search string occurs frequently as might be the case when searching a text buffer for a string starting with the space character an assembly implementation might be several times -faster. Why? Because assembly code can switch from **REPNZ SCASB** to -match the first character to **REPZ CMPS** to check the remaining +faster. Why? Because assembly code can switch from `REPNZ SCASB` to +match the first character to `REPZ CMPS` to check the remaining characters in just a few instructions. -In contrast, Listing 5.1 must return from **memchr()**, set up -parameters, and call **memcmp()** in order to do the same thing. -Likewise, assembly can switch back to **REPNZ SCASB** after a non-match +In contrast, Listing 5.1 must return from `memchr()`, set up +parameters, and call `memcmp()` in order to do the same thing. +Likewise, assembly can switch back to `REPNZ SCASB` after a non-match much more quickly than Listing 5.1. The switching overhead is high; when searching a file completely filled with the character z for the string "zy," Listing 5.1 takes almost 1/2 minute, or nearly an order of diff --git a/05-05.md b/05-05.md index 32d1671..fc6cb4d 100644 --- a/05-05.md +++ b/05-05.md @@ -54,7 +54,7 @@ When I set out to write this chapter, I fully intended to write an assembly language version of Listing 5.1, and I expected the assembly version to be much faster. When I actually looked at where execution time was going (which I did by modifying the program to remove the calls -to the **read()** function, but a code profiler could be used to do the +to the `read()` function, but a code profiler could be used to do the same thing much more easily), I found that the best code in the world wouldn't make much difference. diff --git a/06-01.md b/06-01.md index 1d51f54..99013b4 100644 --- a/06-01.md +++ b/06-01.md @@ -60,7 +60,7 @@ programming, and especially so when working in assembly language, where many instructions have talents above and beyond their obvious abilities. On the other hand, there are also a number of instructions, such as -**LOOP**, that are designed to perform specific functions but aren't +`LOOP`, that are designed to perform specific functions but aren't always the best instructions for those functions. So don't judge a book by its cover, either. @@ -84,9 +84,9 @@ of not judging a book by its cover. The point to all this: You must come to regard the x86 family instructions for what they do, not what you're used to thinking they do. -Yes, **SHL** shifts a pattern left—but a look-up table can do the same -thing, and can often do it faster. **ADD** can indeed add two operands, -but it can't put the result in a third register; **LEA** can. The +Yes, `SHL` shifts a pattern left—but a look-up table can do the same +thing, and can often do it faster. `ADD` can indeed add two operands, +but it can't put the result in a third register; `LEA` can. The instruction set is your raw material for writing high-performance code. By limiting yourself to thinking only in certain well-established ways about the various instructions, you're putting yourself at a substantial diff --git a/06-02.md b/06-02.md index cf99bbe..a98cf75 100644 --- a/06-02.md +++ b/06-02.md @@ -14,7 +14,7 @@ The two approaches are functionally interchangeable but *not* equivalent from a performance standpoint, and which is better depends on the particular context. If it's a one-shot memory access, it's best to let the processor perform the addition; it's generally faster at doing this -than a separate **ADD** instruction would be. If it's a memory access +than a separate `ADD` instruction would be. If it's a memory access within a loop, however, it's advantageous on the 8088 CPU to perform the addition outside the loop, if possible, reducing effective address calculation time inside the loop, as in the following: @@ -27,13 +27,13 @@ LoopTop: loop LoopTop ``` -Here, **MOV AL,[BX]** is two cycles faster than **MOV AL,[BX+SI]**. +Here, `MOV AL,[BX]` is two cycles faster than `MOV AL,[BX+SI]`. -On a 286 or 386, however, the balance shifts. **MOV AL,[BX+SI]** takes -no longer than **MOV AL,[BX]** on these processors because effective +On a 286 or 386, however, the balance shifts. `MOV AL,[BX+SI]` takes +no longer than `MOV AL,[BX]` on these processors because effective address calculations generally take no extra time at all. (According to the MASM manual, one extra clock is required if three memory addressing -components, as in **MOV AL,[BX+SI+1]**, are used. I have not been able +components, as in `MOV AL,[BX+SI+1]`, are used. I have not been able to confirm this from Intel publications, but then I haven't looked all that hard.) If you're optimizing for the 286 or 386, then, you can take advantage of the processor's ability to perform arithmetic as part of @@ -59,15 +59,15 @@ instructions, at that. How? -With **LEA**, the only instruction that performs memory addressing -calculations but doesn't actually address memory. **LEA** accepts a +With `LEA`, the only instruction that performs memory addressing +calculations but doesn't actually address memory. `LEA` accepts a standard memory addressing operand, but does nothing more than store the calculated memory offset in the specified register, which may be any -general-purpose register. The operation of **LEA** is illustrated in +general-purpose register. The operation of `LEA` is illustrated in Figure 6.1, which also shows the operation of register-to-register -**ADD**, for comparis on. +`ADD`, for comparis on. -What does that give us? Two things that **ADD** doesn't provide: the +What does that give us? Two things that `ADD` doesn't provide: the ability to perform addition with either two or three operands, and the ability to store the result in *any* register, not just in one of the source operands. @@ -102,20 +102,20 @@ or: lea di,[si+2] ``` -Mind you, the only components **LEA** can add are BX or BP, SI or DI, -and a constant displacement, so it's not going to replace **ADD** most -of the time. Also, **LEA** is considerably slower than **ADD** on an -8088, although it is just as fast as **ADD** on a 286 or 386 when fewer -than three memory addressing components are used. **LEA** is 1 cycle -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. +Mind you, the only components `LEA` can add are BX or BP, SI or DI, +and a constant displacement, so it's not going to replace `ADD` most +of the time. Also, `LEA` is considerably slower than `ADD` on an +8088, although it is just as fast as `ADD` on a 286 or 386 when fewer +than three memory addressing components are used. `LEA` is 1 cycle +slower than `ADD` on a 486 if the sum of two registers is used to +point to memory, but no slower than `ADD` on a Pentium. On both a 486 +and Pentium, `LEA` can also be slowed down by addressing interlocks. ![**Figure 6.1**  *Operation of ADD Reg,Reg vs. LEA Reg,{Addr}.*](images/06-01.jpg) #### The Wonders of LEA on the 386 {#Heading5} -**LEA** really comes into its own as a "super-ADD" instruction on the +`LEA` really comes into its own as a "super-ADD" instruction on the 386, 486, and Pentium, where it can take advantage of the enhanced memory addressing modes of those processors. (The 486 and Pentium offer the same modes as the 386, so I'll refer only to the 386 from now on.) @@ -129,15 +129,15 @@ that's good for. Well, the obvious advantage is that any two 32-bit registers, or any 32-bit register and any constant, or any two 32-bit registers and any constant, can be added together, with the result stored in any register. -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 +This makes the 32-bit `LEA` much more generally useful than the +standard 16-bit `LEA` in the role of an `ADD` with an independent destination. ![**Figure 6.2**  *Operation of the 32-bit LEA reg,[Addr].*](images/06-02.jpg) -But what else can **LEA** do on a 386, besides add? +But what else can `LEA` do on a 386, besides add? -It can multiply any register used as an index. **LEA** can multiply only +It can multiply any register used as an index. `LEA` can multiply only by the power-of-two values 2, 4, or 8, but that's useful more often than you might imagine, especially when dealing with pointers into tables. Besides, multiplying by 2, 4, or 8 amounts to a left shift of 1, 2, or 3 @@ -162,8 +162,8 @@ when pointing to an entry in a doubly indexed table. ### Multiplication with LEA Using Non-Powers of Two {#Heading6} -Are you impressed yet with all that **LEA** can do on the 386? Believe -it or not, one more feature still awaits us. **LEA** can actually +Are you impressed yet with all that `LEA` can do on the 386? Believe +it or not, one more feature still awaits us. `LEA` can actually perform a fast multiply of a 32-bit register by some values *other* than powers of two. You see, the same 32-bit register can be both base and index on the 386, and can be scaled as the index while being used @@ -174,8 +174,8 @@ EBX by 5 with: lea ebx,[ebx+ebx*4] ``` -Without **LEA** and scaling, multiplication of EBX by 5 would require -either a relatively slow **MUL**, along with a set-up instruction or +Without `LEA` and scaling, multiplication of EBX by 5 would require +either a relatively slow `MUL`, along with a set-up instruction or two, or three separate instructions along the lines of the following ```nasm @@ -192,11 +192,11 @@ cycles is a pretty neat trick, even though it works only on a 386 or 486. > ![](images/i.jpg) -> The full list of values that **LEA** can multiply a register by on a 386 +> The full list of values that `LEA` can multiply a register by on a 386 > or 486 is: 2, 3, 4, 5, 8, and 9. That list doesn't include every > multiplier you might want, but it covers some commonly used ones, and > the performance is hard to beat. I'd like to extend my thanks to Duane Strong of Metagraphics for his -help in brainstorming uses for the 386 version of **LEA** and for +help in brainstorming uses for the 386 version of `LEA` and for pointing out the complications of 486 instruction timings. \ No newline at end of file diff --git a/07-01.md b/07-01.md index 446fe3e..abd0e62 100644 --- a/07-01.md +++ b/07-01.md @@ -91,37 +91,37 @@ there? Let me put it this way: If I were, I'd never admit it! #### When LOOP Is a Bad Idea {#Heading3} Let's examine first an instruction that is less than it appears to be: -**LOOP**. There's no mystery about what **LOOP** does; it decrements CX +`LOOP`. There's no mystery about what `LOOP` does; it decrements CX and branches if CX doesn't decrement to zero. It's so beautifully suited to the task of counting down loops that any experienced x86 programmer -instinctively stuffs the loop count in CX and reaches for **LOOP** when -setting up a loop. That's fine—**LOOP** does, of course, work as +instinctively stuffs the loop count in CX and reaches for `LOOP` when +setting up a loop. That's fine—`LOOP` does, of course, work as advertised—but there is one problem: > ![](images/i.jpg) -> On half of the processors in the x86 family, **LOOP** is slower than -> **DEC CX** followed by **JNZ**. (Granted, **DEC CX/JNZ** isn't precisely -> equivalent to **LOOP,** because **DEC** alters the flags and LOOP +> On half of the processors in the x86 family, `LOOP` is slower than +> `DEC CX` followed by `JNZ`. (Granted, `DEC CX/JNZ` isn't precisely +> equivalent to `LOOP`, because `DEC` alters the flags and LOOP > doesn't, but in most situations they're comparable.) How can this be? Don't ask me, ask Intel. On the 8088 and 80286, -**LOOP** is indeed faster than **DEC CX/JNZ** by a cycle, and **LOOP** +`LOOP` is indeed faster than `DEC CX/JNZ` by a cycle, and `LOOP` is generally a little faster still because it's a byte shorter and so -can be fetched faster. On the 386, however, things change; **LOOP** is -two cycles *slower* than **DEC/JNZ,** and the fetch time for one extra +can be fetched faster. On the 386, however, things change; `LOOP` is +two cycles *slower* than `DEC/JNZ` and the fetch time for one extra byte on even an uncached 386 generally isn't significant. (Remember that -the 386 fetches four instruction bytes at a pop.) **LOOP** is three -cycles slower than **DEC/JNZ** on the 486, and the 486 executes +the 386 fetches four instruction bytes at a pop.) `LOOP` is three +cycles slower than `DEC/JNZ` on the 486, and the 486 executes instructions in so few cycles that those three cycles mean that -**DEC/JNZ** is nearly *twice* as fast as **LOOP**. Then, too, unlike -**LOOP, DEC** doesn't require that **CX** be used, so the **DEC/JNZ** +`DEC/JNZ` is nearly *twice* as fast as `LOOP`. Then, too, unlike +`LOOP, DEC` doesn't require that `CX` be used, so the `DEC/JNZ` solution is both faster and more flexible on the 386 and 486, and on the Pentium as well. (By the way, all this is not just theory; I've timed -the relative performances of **LOOP** and **DEC CX/JNZ** on a cached +the relative performances of `LOOP` and `DEC CX/JNZ` on a cached 386, and LOOP really is slower.) > ![](images/i.jpg) -> Things are stranger still for **LOOP**'s relative **JCXZ,** which -> branches if and only if CX is zero. **JCXZ** is faster than **AND -> CX,CX/JZ** on the 8088 and 80286, and equivalent on the 80386—but is +> Things are stranger still for `LOOP`'s relative `JCXZ`, which +> branches if and only if CX is zero. `JCXZ` is faster than `AND +> CX,CX/JZ` on the 8088 and 80286, and equivalent on the 80386—but is > about twice as slow on the 486! \ No newline at end of file diff --git a/07-02.md b/07-02.md index 3c266d4..e86d5c9 100644 --- a/07-02.md +++ b/07-02.md @@ -10,7 +10,7 @@ chapter: '07' pages: 139-141 --- -By the way, don't fall victim to the lures of **JCXZ** and do something +By the way, don't fall victim to the lures of `JCXZ` and do something like this: ```nasm @@ -18,19 +18,19 @@ and cx,ofh ;Isolate the desired field jcxz SkipLoop ;If field is 0, don't bother ``` -The **AND** instruction has already set the Zero flag, so this +The `AND` instruction has already set the Zero flag, so this ```nasm and cx,0fh ;Isolate the desired field jz SkipLoop ;If field is 0, don't bother ``` -will do just fine and is faster on all processors. Use **JCXZ** only +will do just fine and is faster on all processors. Use `JCXZ` only when the Zero flag isn't already set to reflect the status of CX. ### The Lessons of LOOP and JCXZ {#Heading4} -What can we learn from **LOOP** and **JCXZ?** First, that a single +What can we learn from `LOOP` and `JCXZ`? First, that a single instruction that is intended to do a complex task is not necessarily faster than several instructions that together do the same thing. Second, that the relative merits of instructions and optimization rules @@ -39,11 +39,11 @@ vary to a surprisingly large degree across the x86 family. In particular, if you're going to write 386 protected mode code, which will run only on the 386, 486, and Pentium, you'd be well advised to rethink your use of the more esoteric members of the x86 instruction -set. **LOOP, JCXZ,** the various accumulator-specific instructions, and +set. `LOOP, JCXZ`, the various accumulator-specific instructions, and even the string instructions in many circumstances no longer offer the advantages they did on the 8088. Sometimes they're just not any faster than more general instructions, so they're not worth going out of your -way to use; sometimes, as with **LOOP,** they're actually slower, and +way to use; sometimes, as with `LOOP`, they're actually slower, and you'd do well to avoid them altogether in the 386/486 world. Reviewing the instruction cycle times in the MASM or TASM manuals, or looking over the cycle times in Intel's literature, is a good place to start; @@ -53,8 +53,8 @@ relative performance levels of x86 instructions. #### Avoiding LOOPS of Any Stripe {#Heading5} -Cycle counting and directly substituting instructions (**DEC CX/JNZ** -for **LOOP,** for example) are techniques that belong at the lowest +Cycle counting and directly substituting instructions (`DEC CX/JNZ` +for `LOOP`, for example) are techniques that belong at the lowest level of optimization. It's an important level, but it's fairly mechanical; once you've learned the capabilities and relative performance levels of the various instructions, you should be able to @@ -74,16 +74,16 @@ characteristics. Your job is to sequence those blocks so that they perform well. It doesn't matter what the instructions are intended to do or what their names are; all that matters is what they *do.* -Our discussion of **LOOP** versus **DEC/JNZ** is an excellent example of +Our discussion of `LOOP` versus `DEC/JNZ` is an excellent example of optimization by cycle counting. It's worth knowing, but once you've -learned it, you just routinely use **DEC/JNZ** at the bottom of loops in +learned it, you just routinely use `DEC/JNZ` at the bottom of loops in 386/486-specific code, and that's that. Besides, you'll save at most a few cycles each time, and while that helps a little, it's not going to make all *that* much difference. Now let's step back for a moment, and with no preconceptions consider what the x86 instruction set can do for us. The bulk of the time with -both **LOOP** and **DEC/JNZ** is taken up by branching, which just +both `LOOP` and `DEC/JNZ` is taken up by branching, which just happens to be one of the slowest aspects of every processor in the x86 family, and the rest is taken up by decrementing the count register and checking whether it's zero. There may be ways to perform those tasks a @@ -101,17 +101,17 @@ Consider Listing 7.1, which searches a buffer until either the specified byte is found, a zero byte is found, or the specified number of characters have been checked. Such a function would be useful for scanning up to a maximum number of characters in a zero-terminated -buffer. Listing 7.1, which uses **LOOP** in the main loop, performs a +buffer. Listing 7.1, which uses `LOOP` in the main loop, performs a search of the sample string for a period (‘.') in 170 µs on a 20 MHz cached 386. -When the **LOOP** in Listing 7.1 is replaced with **DEC CX/JNZ,** +When the `LOOP` in Listing 7.1 is replaced with `DEC CX/JNZ`, performance improves to 168 µs, less than 2 percent faster than Listing 7.1. Actually, instruction fetching, instruction alignment, cache characteristics, or something similar is affecting these results; I'd expect a slightly larger improvement—around 7 percent—but that's the most that counting cycles could buy us in this case. (All right, -already; **LOOPNZ** could be used at the bottom of the loop, and other +already; `LOOPNZ` could be used at the bottom of the loop, and other optimizations are surely possible, but all that won't add up to anywhere near the benefits we're about to see from local optimization, and that's the whole point.) \ No newline at end of file diff --git a/07-03.md b/07-03.md index ed0ef81..8e4c482 100644 --- a/07-03.md +++ b/07-03.md @@ -108,9 +108,9 @@ SearchMaxLengthendp ### Unrolling Loops {#Heading7} Listing 7.2 takes a different tack, unrolling the loop so that four -bytes are checked for each **LOOP** performed. The same instructions are +bytes are checked for each `LOOP` performed. The same instructions are used inside the loop in each listing, but Listing 7.2 is arranged so -that three-quarters of the **LOOP**s are eliminated. Listings 7.1 and +that three-quarters of the `LOOP`s are eliminated. Listings 7.1 and 7.2 perform exactly the same task, and they use the same instructions in the loop—the searching algorithm hasn't changed in any way—but we have sequenced the instructions differently in Listing 7.2, and that makes diff --git a/07-04.md b/07-04.md index d8ec3ff..245e7a5 100644 --- a/07-04.md +++ b/07-04.md @@ -144,8 +144,8 @@ SearchMaxLengthendp ``` How much difference? Listing 7.2 runs in 121 µs—40 percent faster than -Listing 7.1, even though Listing 7.2 still uses **LOOP** rather than -**DEC CX/JNZ.** (The loop in Listing 7.2 could be unrolled further, too; +Listing 7.1, even though Listing 7.2 still uses `LOOP` rather than +`DEC CX/JNZ`. (The loop in Listing 7.2 could be unrolled further, too; it's just a question of how much more memory you want to trade for ever-decreasing performance benefits.) That's typical of local optimization; it won't often yield the order-of-magnitude improvements diff --git a/07-05.md b/07-05.md index 5be701d..62610dd 100644 --- a/07-05.md +++ b/07-05.md @@ -80,25 +80,25 @@ BIT_PATTERN=BIT_PATTERN SHL 1 #### NOT Flips Bits—Not Flags {#Heading9} -The **NOT** instruction flips all the bits in the operand, from 0 to 1 -or from 1 to 0. That's as simple as could be, but **NOT** nonetheless +The `NOT` instruction flips all the bits in the operand, from 0 to 1 +or from 1 to 0. That's as simple as could be, but `NOT` nonetheless has a minor but interesting talent: It doesn't affect the flags. That can be irritating; I once spent a good hour tracking down a bug caused -by my unconscious assumption that **NOT** does set the flags. After all, +by my unconscious assumption that `NOT` does set the flags. After all, every other arithmetic and logical instruction sets the flags; why not -**NOT**? Probably because **NOT** isn't considered to be an arithmetic +`NOT`? Probably because `NOT` isn't considered to be an arithmetic or logical instruction at all; rather, it's a data manipulation -instruction, like **MOV** and the various rotates. (These are **RCR, -RCL, ROR,** and **ROL,** which affect only the Carry and Overflow +instruction, like `MOV` and the various rotates. (These are `RCR`, +`RCL`, `ROR`, and `ROL`, which affect only the Carry and Overflow flags.) NOT is often used for tasks, such as flipping masks, where there's no reason to test the state of the result, and in that context it can be handy to keep the flags unmodified for later testing. > ![](images/i.jpg) -> Besides, if you want to **NOT** an operand and set the flags in the -> process, you can just **XOR** it with -1. Put another way, the only -> functional difference between **NOT AX** and **XOR AX,0FFFFH** is that -> **XOR** modifies the flags and **NOT** doesn't. +> Besides, if you want to `NOT` an operand and set the flags in the +> process, you can just `XOR` it with -1. Put another way, the only +> functional difference between `NOT AX` and `XOR AX,0FFFFH` is that +> `XOR` modifies the flags and `NOT` doesn't. The x86 instruction set offers many ways to accomplish almost any task. Understanding the subtle distinctions between the instructions—whether @@ -109,17 +109,17 @@ you're trying to minimize branching. #### Incrementing with and without Carry {#Heading10} Another case in which there are two slightly different ways to perform a -task involves adding 1 to an operand. You can do this with **INC,** as -in **INC AX,** or you can do it with **ADD,** as in **ADD AX,1.** What's -the difference? The obvious difference is that **INC** is usually a byte -or two shorter (the exception being **ADD AL,1,** which at two bytes is -the same length as **INC AL**), and is faster on some processors. Less -obvious, but no less important, is that **ADD** sets the Carry flag -while **INC** leaves the Carry flag untouched. +task involves adding 1 to an operand. You can do this with `INC`, as +in `INC AX`, or you can do it with `ADD`, as in `ADD AX,1`. What's +the difference? The obvious difference is that `INC` is usually a byte +or two shorter (the exception being `ADD AL,1`, which at two bytes is +the same length as `INC AL`), and is faster on some processors. Less +obvious, but no less important, is that `ADD` sets the Carry flag +while `INC` leaves the Carry flag untouched. -Why is that important? Because it allows **INC** to function as a data +Why is that important? Because it allows `INC` to function as a data pointer manipulation instruction for multi-word arithmetic. You can use -**INC** to advance the pointers in code like that shown in Listing 7.5 +`INC` to advance the pointers in code like that shown in Listing 7.5 without having to do any work to preserve the Carry status from one addition to the next. @@ -137,7 +137,7 @@ LOOP_TOP: LOOP LOOP_TOP ``` -If **ADD** were used, the Carry flag would have to be saved between +If `ADD` were used, the Carry flag would have to be saved between additions, with code along the lines shown in Listing 7.6. **LISTING 7.6 L7-6.ASM** @@ -158,38 +158,38 @@ It's not that the Listing 7.6 approach is necessarily better or worse; that depends on the processor and the situation. The Listing 7.6 approach is *different,* and if you understand the differences, you'll be able to choose the best approach for whatever code you happen to -write. (**DEC** has the same property of preserving the Carry flag, by +write. (`DEC` has the same property of preserving the Carry flag, by the way.) There are a couple of interesting aspects to the last example. First, -note that **LOOP** doesn't affect any flags at all; this allows the +note that `LOOP` doesn't affect any flags at all; this allows the Carry flag to remain unchanged from one addition to the next. Not altering the arithmetic flags is a common characteristic of program control instructions (as opposed to arithmetic and logical instructions -like **SUB** and **AND,** which do alter the flags). +like `SUB` and `AND`, which do alter the flags). > ![](images/i.jpg) > The rule is not that the arithmetic flags change whenever the CPU > performs a calculation; rather, the flags change whenever you execute an -> arithmetic, logical, or flag control (such as **CLC** to clear the Carry +> arithmetic, logical, or flag control (such as `CLC` to clear the Carry > flag) instruction. -Not only do **LOOP** and **JCXZ** not alter the flags, but **REP MOVS**, +Not only do `LOOP` and `JCXZ` not alter the flags, but `REP MOVS`, which counts down CX to 0, doesn't affect the flags either. The other interesting point about the last example is the use of -**LAHF** and **SAHF,** which transfer the low byte of the FLAGS register +`LAHF` and `SAHF`, which transfer the low byte of the FLAGS register to and from AH, respectively. These instructions were created to help provide compatibility with the 8080's (that's *8080*, not *8088*) -**PUSH** **PSW** and **POP PSW** instructions, but turn out to be +`PUSH` `PSW` and `POP PSW` instructions, but turn out to be compact (one byte) instructions for saving and restoring the arithmetic -flags. A word of caution, however: **SAHF** restores the Carry, Zero, +flags. A word of caution, however: `SAHF` restores the Carry, Zero, Sign, Auxiliary Carry, and Parity flags—but *not* the Overflow flag, which resides in the high byte of the FLAGS register. Also, be aware -that **LAHF** and **SAHF** provide a fast way to preserve the flags on +that `LAHF` and `SAHF` provide a fast way to preserve the flags on an 8088 but are relatively slow instructions on the 486 and Pentium. -There are times when it's a clear liability that **INC** doesn't set the +There are times when it's a clear liability that `INC` doesn't set the Carry flag. For instance ```nasm diff --git a/08-02.md b/08-02.md index fc4c351..031db61 100644 --- a/08-02.md +++ b/08-02.md @@ -53,14 +53,14 @@ over C is that it allows the use of all forms of instructions and all registers in all ways, whereas C compilers tend to use a subset of registers and instructions in a limited number of ways. Yes and no. It's true that C compilers typically don't generate instructions such as -**XLAT,** rotates, or the string instructions. On the other hand, -**XLAT** and rotates are useful in a limited set of circumstances, and +`XLAT`, rotates, or the string instructions. On the other hand, +`XLAT` and rotates are useful in a limited set of circumstances, and string instructions *are* used in the C library functions. In fact, C library code is likely to be carefully optimized by experts, and may be much better than equivalent code you'd produce yourself. Am I saying that C compilers produce better code than you do? No, I'm -saying that they *can,* unless you use assembly language properly. +saying that they *can*, unless you use assembly language properly. Writing code in assembly language rather than C guarantees nothing. > ![](images/i.jpg) @@ -83,7 +83,7 @@ to something like Figure 8.1A. You might look at that and tweak it to the code shown in Figure 8.1B. Congratulations! You've successfully eliminated all stack frame access, -you've used **LOOP** (although **DEC SI/JNZ** is actually faster on 386 +you've used `LOOP` (although `DEC SI/JNZ` is actually faster on 386 and later machines, as I explained in the last chapter), and you've used a string instruction. Unfortunately, the new code isn't going to run very much faster. Maybe 25 percent faster, maybe a little more. Big @@ -107,13 +107,13 @@ of better assembly language code in the small section of code that most affects overall performance. For example, consider that the data searched in the last example is stored in an array of structures, with each structure in the array containing other information as well. In -this situation, **REP SCASW** couldn't be used because the data searched +this situation, `REP SCASW` couldn't be used because the data searched through wouldn't be contiguous. However, if the need for performance in searching the array is urgent enough, there's no reason why you can't reorganize the data. This might mean removing the array elements from the structures and storing them in -their own array so that **REP SCASW** *could* be used. +their own array so that `REP SCASW` *could* be used. > ![](images/i.jpg) > Organizing a program's data so that the performance of the critical @@ -145,5 +145,5 @@ That said, let me show some of these precepts in action. Listing 8.1 is the sample C application I'm going to use to examine optimization in action. Listing 8.1 isn't really complete—it doesn't handle the "no-matches" case well, and it assumes that the sum of all -matches will fit into an **int—**but it will do just fine as an +matches will fit into an `int`-but it will do just fine as an optimization example. \ No newline at end of file diff --git a/08-03.md b/08-03.md index aee9f56..40ef278 100644 --- a/08-03.md +++ b/08-03.md @@ -136,9 +136,9 @@ unsigned int FindIDAverage(unsigned int SearchedForID, The main body of Listing 8.1 constructs a linked list of memory blocks of various sizes and stores an array of structures across those blocks, -as shown in Figure 8.2. The function **FindIDAverage** in Listing 8.1 +as shown in Figure 8.2. The function `FindIDAverage` in Listing 8.1 searches through that array for all matches to a specified ID number and -returns the average value of all such matches. **FindIDAverage** +returns the average value of all such matches. `FindIDAverage` contains two nested loops, the outer one repeating once for each linked 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 @@ -147,11 +147,11 @@ statements, and should lend itself rather well to compiler optimization. ![**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++ +`FindIDAverage` nicely. Listing 8.2 shows the code Microsoft C/C++ generates for the inner loop, consisting of a mere seven assembly language instructions inside the loop. The compiler is smart enough to convert the loop index variable, which counts up but is used for nothing -but counting loops, into a count-down variable so that the **LOOP** +but counting loops, into a count-down variable so that the `LOOP` instruction can be used. **LISTING 8.2 L8-2.COD** diff --git a/08-04.md b/08-04.md index 18aca33..cd5735e 100644 --- a/08-04.md +++ b/08-04.md @@ -12,7 +12,7 @@ pages: 160-163 It's hard to squeeze much more performance from this code by tweaking it, as exemplified by Listing 8.3, a fine-tuned assembly version of -**FindIDAverage** that was produced by looking at the assembly output of +`FindIDAverage` that was produced by looking at the assembly output of MS C/C++ and tightening it. Listing 8.3 eliminates all stack frame access in the inner loop, but that's about all the tightening there is to do. The result, as shown in Table 8.1, is that Listing 8.3 runs a @@ -97,8 +97,8 @@ _FindIDAverage ENDP Listing 8.4 tosses some sophisticated optimization techniques into the mix. The loop is unrolled eight times, eliminating a good deal of -branching, and **SCASW** is used instead of **CMP [DI],AX.** (Note, -however, that **SCASW** is in fact slower than **CMP [DI],AX** on the +branching, and `SCASW` is used instead of `CMP [DI],AX`. (Note, +however, that `SCASW` is in fact slower than `CMP [DI],AX` on the 386 and 486, and is sometimes faster on the 286 and 8088 only because it's shorter and therefore may prefetch faster.) This advanced tweaking produces a 39 percent improvement over the original C code—substantial, diff --git a/08-05.md b/08-05.md index 0aa1980..3c60cbc 100644 --- a/08-05.md +++ b/08-05.md @@ -170,9 +170,9 @@ _FindIDAverage2 ENDP end ``` -The whole point of this rearrangement is to allow us to use **REP -SCASW** to search through each block, and that's exactly what -**FindIDAverage2** in Listing 8.6 does. The result: Listing 8.6 +The whole point of this rearrangement is to allow us to use `REP +SCASW` to search through each block, and that's exactly what +`FindIDAverage2` in Listing 8.6 does. The result: Listing 8.6 calculates the average about *three times* as fast as the original C implementation and more than twice as fast as Listing 8.4, heavily optimized as the latter code is. diff --git a/09-01.md b/09-01.md index 1cd833d..a0d8884 100644 --- a/09-01.md +++ b/09-01.md @@ -78,14 +78,14 @@ feedback. #### Another Look at LEA {#Heading3} -Several people have pointed out that while **LEA** is great for +Several people have pointed out that while `LEA` is great for performing certain additions (see Chapter 6), it isn't a perfect -replacement for **ADD**. What's the difference? **LEA**, an addressing +replacement for `ADD`. What's the difference? `LEA`, an addressing instruction by trade, doesn't affect the flags, while the arithmetic -**ADD** instruction most certainly does. This is no problem when +`ADD` instruction most certainly does. This is no problem when performing additions that involve only quantities that fit in one machine word (32 bits in 386 protected mode, 16 bits otherwise), but it -renders **LEA** useless for multiword operations, which use the Carry +renders `LEA` useless for multiword operations, which use the Carry flag to tie together partial results. For example, these instructions ```nasm @@ -100,11 +100,11 @@ LEA EAX,[EAX+EBX] ADC EDX,ECX ``` -because **LEA** doesn't affect the Carry flag. +because `LEA` doesn't affect the Carry flag. -The no-carry characteristic of **LEA** becomes a distinct advantage when +The no-carry characteristic of `LEA` becomes a distinct advantage when performing pointer arithmetic, however. For instance, the following code -uses **LEA** to advance the pointers while adding one 128-bit memory +uses `LEA` to advance the pointers while adding one 128-bit memory variable to another such variable: ```nasm @@ -120,15 +120,15 @@ ADDLOOP: LOOP ADDLOOP ``` -(Yes, I could use **LODSD** instead of **MOV/LEA**; I'm just -illustrating a point here. Besides, **LODS** is only 1 cycle faster than -**MOV/LEA** on the 386, and is actually more than twice as slow on the -486.) If we used **ADD** rather than **LEA** to advance the pointers, -the carry from one **ADC** to the next would have to be preserved with -either **PUSHF/POPF** or **LAHF/SAHF**. (Alternatively, we could use -multiple **INC**s, since **INC** doesn't affect the Carry flag.) +(Yes, I could use `LODSD` instead of `MOV/LEA`; I'm just +illustrating a point here. Besides, `LODS` is only 1 cycle faster than +`MOV/LEA` on the 386, and is actually more than twice as slow on the +486.) If we used `ADD` rather than `LEA` to advance the pointers, +the carry from one `ADC` to the next would have to be preserved with +either `PUSHF/POPF` or `LAHF/SAHF`. (Alternatively, we could use +multiple `INC`s, since `INC` doesn't affect the Carry flag.) -In short, **LEA** is indeed different from **ADD**. Sometimes it's +In short, `LEA` is indeed different from `ADD`. Sometimes it's better. Sometimes not; that's the nature of the various instruction substitutions and optimizations that will occur to you over time. There's no such thing as "best" instructions on the x86; it all depends @@ -180,7 +180,7 @@ ADC CX,CX ;CX=1 if copy length was odd, REP MOVSB ;copy any odd byte ``` -(**ADC CX,CX** can be replaced with **RCL CX,1**; which is faster +(`ADC CX,CX` can be replaced with `RCL CX,1`; which is faster depends on the processor type.) It might be hard to believe that the above is faster than this: diff --git a/09-02.md b/09-02.md index 728c30d..ca74ed2 100644 --- a/09-02.md +++ b/09-02.md @@ -11,17 +11,17 @@ pages: 172-175 --- However, it generally is. Sure, if the length is odd, John's approach -incurs a penalty approximately equal to the **REP** startup time for -**MOVSB**. However, if the length is even, John's approach doesn't +incurs a penalty approximately equal to the `REP` startup time for +`MOVSB`. However, if the length is even, John's approach doesn't branch, saving cycles and not emptying the prefetch queue. If copy lengths are evenly distributed between even and odd, John's approach is faster in most x86 systems. (Not on the 486, though.) -John also points out that on the 386, multiple **LEA**s can be combined -to perform multiplications that can't be handled by a single **LEA**, +John also points out that on the 386, multiple `LEA`s can be combined +to perform multiplications that can't be handled by a single `LEA`, much as multiple shifts and adds can be used for multiplication, only -faster. **LEA** can be used to multiply in a single instruction on the -386, but only by the values 2, 3, 4, 5, 8, and 9; several **LEA**s +faster. `LEA` can be used to multiply in a single instruction on the +386, but only by the values 2, 3, 4, 5, 8, and 9; several `LEA`s strung together can handle a much wider range of values. For example, video programmers are undoubtedly familiar with the following code to multiply AX times 80 (the width in bytes of the bitmap in most PC @@ -38,7 +38,7 @@ SH LAX,1 ;*64 ADD AX,BX ;*80 ``` -Using **LEA** on the 386, the above could be reduced to +Using `LEA` on the 386, the above could be reduced to ```nasm LEA EAX,[EAX*2] ;*2 @@ -68,12 +68,12 @@ ADD AX,BX ;*80 That brings us to multiplication, one of the slowest of x86 operations and one that allows for considerable optimization. One way to speed up -multiplication is to use shift and add, **LEA**, or a lookup table to +multiplication is to use shift and add, `LEA`, or a lookup table to hard-code a multiplication operation for a fixed multiplier, as shown above. Another is to take advantage of the early-out feature of the 386 (and the 486, but in the interests of brevity I'll just say "386" from now on) by arranging your operands so that the multiplier (always the -rightmost operand following **MUL** or **IMUL**) is no larger than the +rightmost operand following `MUL` or `IMUL`) is no larger than the other operand. > ![](images/i.jpg) @@ -97,17 +97,17 @@ regularly indicate that multiplication takes 3 to 4 cycles longer than the specs indicate, but the cycle-per-bit advantage of smaller multipliers holds true nonetheless.) -This highlights another interesting point: **MUL** and **IMUL** on the +This highlights another interesting point: `MUL` and `IMUL` on the 386 are so fast that alternative multiplication approaches, while generally still faster, are worthwhile only in truly time-critical code. > ![](images/i.jpg) > On 386SXs and uncached 386s, where code size can significantly affect -> performance due to instruction prefetching, the compact **MUL** and -> **IMUL** instructions can approach and in some cases even outperform the +> performance due to instruction prefetching, the compact `MUL` and +> `IMUL` instructions can approach and in some cases even outperform the > "optimized" alternatives. -All in all, **MUL** and **IMUL** are reasonable performers on the 386, +All in all, `MUL` and `IMUL` are reasonable performers on the 386, no longer to be avoided in most cases—and you can help that along by arranging your code to make the smaller operand the multiplier whenever you know which operand is smaller. @@ -121,33 +121,33 @@ scale value is the logical choice for the multiplier. #### Optimizing Optimized Searching {#Heading6} -Rob Williams writes with a wonderful optimization to the **REPNZ -SCASB-**based optimized searching routine I discussed in Chapter 5. As a +Rob Williams writes with a wonderful optimization to the `REPNZ +SCASB`-based optimized searching routine I discussed in Chapter 5. As a quick refresher, I described searching a buffer for a text string as -follows: Scan for the first byte of the text string with **REPNZ -SCASB**, then use **REPZ CMPS** to check for a full match whenever -**REPNZ SCASB** finds a match for the first character, as shown in +follows: Scan for the first byte of the text string with `REPNZ +SCASB`, then use `REPZ CMPS` to check for a full match whenever +`REPNZ SCASB` finds a match for the first character, as shown in Figure 9.1. The principle is that most buffer characters won't match the -first character of any given string, so **REPNZ SCASB**, by far the +first character of any given string, so `REPNZ SCASB`, by far the 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**. +entirety with `REPZ CMPS`. ![**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 -deal is handling **REPNZ SCASB** matches, which require checking the -remainder of the string with **REPZ CMPS** and restarting **REPNZ -SCASB** if no match is found. +deal is handling `REPNZ SCASB` matches, which require checking the +remainder of the string with `REPZ CMPS` and restarting `REPNZ +SCASB` if no match is found. > ![](images/i.jpg) -> Rob points out that the number of **REPNZ SCASB** matches can easily be +> Rob points out that the number of `REPNZ SCASB` matches can easily be > reduced simply by scanning for the character in the searched-for string > that appears least often in the buffer being searched. Imagine, if you will, that you're searching for the string "EQUAL." By -my approach, you'd use **REPNZ SCASB** to scan for each occurrence of +my approach, you'd use `REPNZ SCASB` to scan for each occurrence of "E," which crops up quite often in normal text. Rob points out that it would make more sense to scan for "Q," then back up one character and check the whole string when a "Q" is found, as shown in Figure 9.2. "Q" diff --git a/09-03.md b/09-03.md index 1311b6d..f3b6d5a 100644 --- a/09-03.md +++ b/09-03.md @@ -14,22 +14,22 @@ Listing 9.1 implements the scan-on-first-character approach. Listing 9.2 scans for whatever character the caller specifies. Listing 9.3 is a test program used to compare the two approaches. How much difference does Rob's revelation make? Plenty. Even when the entire C function call to -**FindString** is timed—**strlen** calls, parameter pushing, calling, -setup, and all—the version of **FindString** in Listing 9.2, which is +`FindString` is timed—`strlen` calls, parameter pushing, calling, +setup, and all—the version of `FindString` in Listing 9.2, which is directed by Listing 9.3 to scan for the infrequently-occurring "Q," is about 40 percent faster on a 20 MHz cached 386 for the test search of -Listing 9.3 than is the version of **FindString** in Listing 9.1, which +Listing 9.3 than is the version of `FindString` in Listing 9.1, which always scans for the first character, in this case "E." However, when only the search loops (the code that actually does the searching) in the -two versions of **FindString** are compared, Listing 9.2 is more than +two versions of `FindString` are compared, Listing 9.2 is more than *twice* as fast as Listing 9.1—a remarkable improvement over code that -already uses **REPNZ SCASB** and **REPZ CMPS**. +already uses `REPNZ SCASB` and `REPZ CMPS`. What I like so much about Rob's approach is that it demonstrates that optimization involves much more than instruction selection and cycle counting. Listings 9.1 and 9.2 use pretty much the same instructions, -and even use the same approach of scanning with **REPNZ SCASB** and -using **REPZ CMPS** to check scanning matches. +and even use the same approach of scanning with `REPNZ SCASB` and +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 diff --git a/09-05.md b/09-05.md index 4ffe34d..e63a32b 100644 --- a/09-05.md +++ b/09-05.md @@ -80,7 +80,7 @@ _sort: pop dx ;get return address (entry point) #### Full 32-Bit Division {#Heading8} One of the most annoying limitations of the x86 is that while the -dividend operand to the **DIV** instruction can be 32 bits in size, both +dividend operand to the `DIV` instruction can be 32 bits in size, both the divisor and the result must be 16 bits. That's particularly annoying in regards to the result because sometimes you just don't know whether the ratio of the dividend to the divisor is greater than 64K-1 or @@ -118,5 +118,5 @@ can be done easily enough by remembering the signs of the dividend and divisor, dividing the absolute value of the dividend by the absolute value of the divisor, and applying the stored signs to set the proper signs for the quotient and remainder. There may be more clever ways to -produce the same result, by using **IDIV**, for example; if you know of +produce the same result, by using `IDIV`, for example; if you know of one, drop me a line c/o Coriolis Group Books. \ No newline at end of file diff --git a/09-07.md b/09-07.md index c970aa5..43db2be 100644 --- a/09-07.md +++ b/09-07.md @@ -14,19 +14,19 @@ pages: 185-188 Next, we come to an item that cycle counters will love, especially since it involves apparently incorrect documentation on Intel's part. -According to Intel's documents, all **RCR** and **RCL** instructions, +According to Intel's documents, all `RCR` and `RCL` instructions, which perform rotations through the Carry flag, as shown in Figure 9.4, take 9 cycles on the 386 when working with a register operand. My measurements indicate that the 9-cycle execution time almost holds true for *multibit* rotate-through-carries, which I've timed at 8 cycles -apiece; for example, **RCR AX,CL** takes 8 cycles on *my* 386, as does -**RCL DX,2**. Contrast that with **ROR** and **ROL**, which can rotate +apiece; for example, `RCR AX,CL` takes 8 cycles on *my* 386, as does +`RCL DX,2`. Contrast that with `ROR` and `ROL`, which can rotate the contents of a register any number of bits in just 3 cycles. However, rotating by one bit through the Carry flag does *not* take 9 cycles, contrary to Intel's *80386 Programmer's Reference Manual*, or -even 8 cycles. In fact, **RCR** *reg*,1 and **RCL** *reg*,1 take 3 -cycles, just like **ROR, ROL, SHR,** and **SHL**. At least, that's how +even 8 cycles. In fact, `RCR` *reg*,1 and `RCL` *reg*,1 take 3 +cycles, just like `ROR`, `ROL`, `SHR`, and `SHL`. At least, that's how 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!) @@ -34,12 +34,12 @@ though!) ![**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 +Reference Manual*, the 486 can `RCR` or `RCL` a register by one bit in 3 cycles, but takes between 8 and 30 cycles to perform a multibit -register **RCR** or **RCL**! +register `RCR` or `RCL`! -No great lesson here, just a caution to be leery of multibit **RCR** and -**RCL** when performance matters—and to take cycle-time documentation +No great lesson here, just a caution to be leery of multibit `RCR` and +`RCL` when performance matters—and to take cycle-time documentation with a grain of salt. #### Hardwired Far Jumps {#Heading11} @@ -47,8 +47,8 @@ with a grain of salt. Did you ever wonder how to code a far jump to an absolute address in assembly language? Probably not, but if you ever do, you're going to be glad for this next item, because the obvious solution doesn't work. You -might think all it would take to jump to, say, 1000:5 would be **JMP FAR -PTR 1000:5**, but you'd be wrong. That won't even assemble. You might +might think all it would take to jump to, say, 1000:5 would be `JMP FAR +PTR 1000:5`, but you'd be wrong. That won't even assemble. You might then think to construct in memory a far pointer containing 1000:5, as in the following: @@ -60,9 +60,9 @@ Ptr dd ? jmp [Ptr] ``` -That will work, but at a price in performance. On an 8088, **JMP DWORD -PTR [*mem*]** (an indirect far jump) takes at least 37 cycles; **JMP -DWORD PTR *label*** (a direct far jump) takes only 15 cycles (plus, +That will work, but at a price in performance. On an 8088, `JMP DWORD +PTR [*mem*]` (an indirect far jump) takes at least 37 cycles; `JMP +DWORD PTR *label*` (a direct far jump) takes only 15 cycles (plus, almost certainly, some cycles for instruction fetching). On a 386, an indirect far jump is documented to take at least 43 cycles in real mode (31 in protected mode); a direct far jump is documented to take at least @@ -81,7 +81,7 @@ preferable. Listing 9.7 shows a short program that performs a direct far call to 1000:5. (Don't run it, unless you want to crash your system!) It does this by creating a dummy segment at 1000H, so that the label -**FarLabel** can be created with the desired far attribute at the proper +`FarLabel` can be created with the desired far attribute at the proper location. (Segments created with "AT" don't cause the generation of any actual bytes or the allocation of any memory; they're just templates.) It's a little kludgey, but at least it does work. There may be a better @@ -132,8 +132,8 @@ mov eax,1 ``` takes only 2 cycles to execute, but is 5 bytes long (because native mode -constants are dwords and the **MOV** instruction doesn't sign-extend). -Both code fragments are ways to set **EAX** to 1 (although the first +constants are dwords and the `MOV` instruction doesn't sign-extend). +Both code fragments are ways to set `EAX` to 1 (although the first affects the flags and the second doesn't); this is a classic trade-off of speed for space. Second, @@ -148,15 +148,15 @@ move bx,-1 ``` takes 2 cycles to execute and is 5 bytes long. Both instructions set -**EBX** to -1; this is a classic trade-off of—gee, it's not a trade-off -at all, is it? **OR** is a better way to set a 32-bit register to all -1-bits, just as **SUB** or **XOR** is a better way to set a register to +`EBX` to -1; this is a classic trade-off of—gee, it's not a trade-off +at all, is it? `OR` is a better way to set a 32-bit register to all +1-bits, just as `SUB` or `XOR` is a better way to set a register to all 0-bits. Who woulda thunk it? Just goes to show how the 32-bit displacements and constants of 386 native mode change the familiar landscape of 80x86 optimization. -Be warned, though, that I've found **OR, AND, ADD**, and the like to be -a cycle slower than **MOV** when working with immediate operands on the +Be warned, though, that I've found `OR`, `AND`, `ADD`, and the like to be +a cycle slower than `MOV` when working with immediate operands on the 386 under some circumstances, for reasons that thus far escape me. This just reinforces the first rule of optimization: Measure your code in action, and place not your trust in documented cycle times. \ No newline at end of file diff --git a/11-01.md b/11-01.md index 3e8d120..ab3671d 100644 --- a/11-01.md +++ b/11-01.md @@ -102,7 +102,7 @@ les ax,dword ptr [LongVar] mov dx,es ``` -which loads **LongVar** into DX:AX faster than this: +which loads `LongVar` into DX:AX faster than this: ```nasm mov ax,word ptr [LongVar] diff --git a/11-02.md b/11-02.md index 5b61ab1..163f2b5 100644 --- a/11-02.md +++ b/11-02.md @@ -100,7 +100,7 @@ wait states hurt plenty—and the place they hurt most is instruction fetching. Consider this: The 286 can store an immediate value to memory, as in -**MOV [WordVar],0**, in just 3 cycles. However, that instruction is 6 +`MOV [WordVar],0`, in just 3 cycles. However, that instruction is 6 bytes long. The 286 is capable of fetching 1 word every 2 cycles; however, the one-wait-state architecture of the AT stretches that to 3 cycles. Consequently, nine cycles are needed to fetch the six @@ -117,7 +117,7 @@ the above example: A 4-to-1 ratio of instruction fetch time to execution time is in a class with the best (or worst!) that's found on the 8088. Let's check out the prefetch queue cycle-eater in action. Listing 11.1 -times **MOV [WordVar],0**. The Zen timer reports that on a +times `MOV [WordVar],0`. The Zen timer reports that on a one-wait-state 10 MHz 286-based AT clone (the computer used for all tests in this chapter), Listing 11.1 runs in 1.27 µs per instruction. That's 12.7 cycles per instruction, just as we calculated. (That extra diff --git a/11-03.md b/11-03.md index fb589bd..44b6802 100644 --- a/11-03.md +++ b/11-03.md @@ -84,15 +84,15 @@ different workaround, we'll consider it to be a new cycle-eater.) The way to deal with the data alignment cycle-eater is straightforward: *Don't perform word-sized accesses to odd addresses on the 286 if you can help it*. The easiest way to avoid the data alignment cycle-eater is -to place the directive **EVEN** before each of your word-sized -variables. **EVEN** forces the offset of the next byte assembled to be -even by inserting a **NOP** if the current offset is odd; consequently, +to place the directive `EVEN` before each of your word-sized +variables. `EVEN` forces the offset of the next byte assembled to be +even by inserting a `NOP` if the current offset is odd; consequently, you can ensure that any word-sized variable can be accessed efficiently -by the 286 simply by preceding it with **EVEN**. +by the 286 simply by preceding it with `EVEN`. Listing 11.2, which accesses memory a word at a time with each word starting at an odd address, runs on a 10 MHz AT clone in 1.27 ms per -repetition of **MOVSW**, or 0.64 ms per word-sized memory access. That's +repetition of `MOVSW`, or 0.64 ms per word-sized memory access. That's 6-plus cycles per word-sized access, which breaks down to two separate memory accesses—3 cycles to access the high byte of each word and 3 cycles to access the low byte of each word, the inevitable result of @@ -123,7 +123,7 @@ Skip: On the other hand, Listing 11.3, which is exactly the same as Listing 11.2 save that the memory accesses are word-aligned (start at even -addresses), runs in 0.64 ms per repetition of **MOVSW**, or 0.32 µs per +addresses), runs in 0.64 ms per repetition of `MOVSW`, or 0.32 µs per word-sized memory access. That's 3 cycles per word-sized access—exactly twice as fast as the non-word-aligned accesses of Listing 11.2, just as we predicted. diff --git a/11-04.md b/11-04.md index 124146d..2be1921 100644 --- a/11-04.md +++ b/11-04.md @@ -33,7 +33,7 @@ not explain in any way. After rolling it around in my head for a while, I took a look at the code under a debugger...and the answer leaped out at me. *The loop began at an odd address!* That meant that two instruction fetches were required each time through the loop; one to get -the opcode byte of the **LOOP** instruction, which resided at the end of +the opcode byte of the `LOOP` instruction, which resided at the end of one word-aligned word, and another to get the displacement byte, which resided at the start of the next word-aligned word. @@ -51,8 +51,8 @@ LoopTop: While word-aligning branch destinations can improve branching performance, it's a nuisance and can increase code size a good deal, so -it's not worth doing in most code. Besides, **EVEN** inserts a **NOP** -instruction if necessary, and the time required to execute a **NOP** can +it's not worth doing in most code. Besides, `EVEN` inserts a `NOP` +instruction if necessary, and the time required to execute a `NOP` can sometimes cancel the performance advantage of having a word-aligned branch destination. @@ -103,19 +103,19 @@ make the stack pointer odd by adding an odd value to it or subtracting an odd value from it, or by loading it with an odd value.) An odd stack pointer on the 286 or 386 (or a non-doubleword-aligned stack in 32-bit protected mode on the 386, 486, or Pentium) will significantly reduce -the performance of **PUSH,** **POP,** **CALL**, and **RET**, as well as -**INT** and **IRET**, which are executed to invoke DOS and BIOS +the performance of `PUSH`, `POP`, `CALL`, and `RET`, as well as +`INT` and `IRET`, which are executed to invoke DOS and BIOS functions, handle keystrokes and incoming serial characters, and manage the mouse. I know of a Forth programmer who vastly improved the performance of a complex application on the AT simply by forcing the Forth interpreter to maintain an even stack pointer at all times. -An interesting corollary to this rule is that you shouldn't **INC SP** -twice to add 2, even though that takes fewer bytes than **ADD SP,2**. -The stack pointer is odd between the first and second **INC**, so any +An interesting corollary to this rule is that you shouldn't `INC SP` +twice to add 2, even though that takes fewer bytes than `ADD SP,2`. +The stack pointer is odd between the first and second `INC`, so any interrupt occurring between the two instructions will be serviced more slowly than it normally would. The same goes for decrementing twice; use -**SUB SP,2** instead. +`SUB SP,2` instead. > ![](images/i.jpg) > Keep the stack pointer aligned at all times. @@ -163,7 +163,7 @@ computers are actually the same. As you'll recall from my earlier discussion of the matter in Chapter 4, display adapters offer only a limited number of accesses to display memory during any given period of time. The 8088 is capable of making use of most but not all of those -slots with **REP MOVSW**, so the number of memory accesses allowed by a +slots with `REP MOVSW`, so the number of memory accesses allowed by a display adapter such as a standard VGA is reasonably well-matched to an 8088's memory access speed. Granted, access to a VGA slows the 8088 down considerably—but, as we're about to find out, "considerably" is a diff --git a/11-05.md b/11-05.md index dec3d9b..5871b24 100644 --- a/11-05.md +++ b/11-05.md @@ -12,7 +12,7 @@ pages: 220-222 Under ideal conditions, a 286 can access memory much, much faster than an 8088. A 10 MHz 286 is capable of accessing a word of system memory -every 0.20 ms with **REP MOVSW**, dwarfing the 1 byte every 1.31 µs that +every 0.20 ms with `REP MOVSW`, dwarfing the 1 byte every 1.31 µs that the 8088 in a PC can manage. However, access to display memory is anything but ideal for a 286. For one thing, most display adapters are 8-bit devices, although newer adapters are 16-bit in nature. One @@ -29,7 +29,7 @@ factors that reduce the speed with which the 286 can access display memory. Far more cycles are eaten by the inherent memory-access limitations of display adapters—that is, the limited number of display memory accesses that display adapters make available to the 286. Look at -it this way: If **REP MOVSW** on a PC can use more than half of all +it this way: If `REP MOVSW` on a PC can use more than half of all available accesses to display memory, then how much faster can code running on a 286 or 386 possibly run when accessing display memory? @@ -97,17 +97,17 @@ programs are still limited to 1 MB of addressable memory on the 286. In either mode, each segment is still limited to 64K.) There are also a handful of 286-specific real-mode instructions, and -they can be quite useful. **BOUND** checks array bounds. **ENTER** and -**LEAVE** support compact and speedy stack frame construction and +they can be quite useful. `BOUND` checks array bounds. `ENTER` and +`LEAVE` support compact and speedy stack frame construction and removal, ideal for interfacing to high-level languages such as C and Pascal (although these instructions are actually relatively slow on the 386 and its successors, and should be used with caution when performance -matters). **INS** and **OUTS** are new string instructions that support -efficient data transfer between memory and I/O ports. Finally, **PUSHA** -and **POPA** push and pop all eight general-purpose registers. +matters). `INS` and `OUTS` are new string instructions that support +efficient data transfer between memory and I/O ports. Finally, `PUSHA` +and `POPA` push and pop all eight general-purpose registers. A couple of old instructions gain new features on the 286. For one, the -286 version of **PUSH** is capable of pushing a constant on the stack. +286 version of `PUSH` is capable of pushing a constant on the stack. For another, the 286 allows all shifts and rotates to be performed for not just 1 bit or the number of bits specified by CL, but for *any* constant number of bits. @@ -143,5 +143,5 @@ complex 386-specific instructions are slower than equivalent sequences of simple instructions on the 486 and especially on the Pentium.) What's more, both old and new instructions support 32-bit operations on the 386. For example, it's relatively simple to copy data in chunks of 4 -bytes on a 386, even in real mode, by using the **MOVSD** ("move string -double") instruction, or to negate a 32-bit value with **NEG eax**. \ No newline at end of file +bytes on a 386, even in real mode, by using the `MOVSD` ("move string +double") instruction, or to negate a 32-bit value with `NEG eax`. \ No newline at end of file diff --git a/11-06.md b/11-06.md index d950a03..6ae4899 100644 --- a/11-06.md +++ b/11-06.md @@ -56,7 +56,7 @@ around the 286 and 386, many of the instruction-specific optimizations no longer hold, for the execution times of most instructions are quite different on the 286 and 386 than on the 8088. We have already seen one such example of the sometimes vast difference between 8088 and 286/386 -instruction execution times: **MOV [WordVar],0**, which has an Execution +instruction execution times: `MOV [WordVar],0`, which has an Execution Unit execution time of 20 cycles on the 8088, has an EU execution time of just 3 cycles on the 286 and 2 cycles on the 386. @@ -65,17 +65,17 @@ has been improved enormously on the 286 and 386. The key to this improvement is the near elimination of effective address (EA) calculation time. Where an 8088 takes from 5 to 12 cycles to calculate an EA, a 286 or 386 usually takes no time whatsoever to perform the -calculation. If a base+index+displacement addressing mode, such as **MOV -AX,[WordArray+bx+si]**, is used on a 286 or 386, 1 cycle is taken to +calculation. If a base+index+displacement addressing mode, such as `MOV +AX,[WordArray+bx+si]`, is used on a 286 or 386, 1 cycle is taken to perform the EA calculation, but that's both the worst case and the only case in which there's any EA overhead at all. The elimination of EA calculation time means that the EU execution time of memory-addressing instructions is much closer to the EU execution -time of register-only instructions. For instance, on the 8088 **ADD -[WordVar],100H** is a 31-cycle instruction, while **ADD DX,100H** is a +time of register-only instructions. For instance, on the 8088 `ADD +[WordVar],100H` is a 31-cycle instruction, while `ADD DX,100H` is a 4-cycle instruction—a ratio of nearly 8 to 1. By contrast, on the 286 -**ADD [WordVar],100H** is a 7-cycle instruction, while **ADD DX,100H** +`ADD [WordVar],100H` is a 7-cycle instruction, while `ADD DX,100H` is a 3-cycle instruction—a ratio of just 2.3 to 1. It would seem, then, that it's less necessary to use the registers on @@ -84,12 +84,12 @@ reasons we've already seen. The key is this: The 286 can execute memory-addressing instructions so fast that there's no spare instruction prefetching time during those instructions, so the prefetch queue runs dry, especially on the AT, with its one-wait-state memory. On the AT, -the 6-byte instruction **ADD [WordVar],100H** is effectively at least a +the 6-byte instruction `ADD [WordVar],100H` is effectively at least a 15-cycle instruction, because 3 cycles are needed to fetch each of the -three instruction words and 6 more cycles are needed to read **WordVar** +three instruction words and 6 more cycles are needed to read `WordVar` and write the result back to memory. -Granted, the register-only instruction **ADD DX,100H** also slows +Granted, the register-only instruction `ADD DX,100H` also slows down—to 6 cycles—because of instruction prefetching, leaving a ratio of 2.5 to 1. Now, however, let's look at the performance of the same code on an 8088. The register-only code would run in 16 cycles (4 instruction @@ -106,7 +106,7 @@ in 10.05 ms. On a 10 MHz AT clone, Listing 11.4 runs in 0.64 ms, while Listing 11.5 runs in 1.80 ms. Obviously, the AT is much faster...but the ratio of Listing 11.5 to Listing 11.4 is virtually identical on both computers, at 2.78 for the PC and 2.81 for the AT. If anything, the -register-only form of **ADD** has a slightly *larger* advantage on the +register-only form of `ADD` has a slightly *larger* advantage on the AT than it does on the PC in this case. Theory confirmed. diff --git a/11-07.md b/11-07.md index c06499b..f6de27b 100644 --- a/11-07.md +++ b/11-07.md @@ -71,9 +71,9 @@ The more things change, the more they remain the same.... #### POPF and the 286 {#Heading17} We've one final 286-related item to discuss: the hardware malfunction of -**POPF** under certain circumstances on the 286. +`POPF` under certain circumstances on the 286. -The problem is this: Sometimes **POPF** permits interrupts to occur when +The problem is this: Sometimes `POPF` permits interrupts to occur when interrupts are initially off and the setting popped into the Interrupt flag from the stack keeps interrupts off. In other words, an interrupt can happen even though the Interrupt flag is never set to 1. Now, I @@ -85,29 +85,29 @@ absolutely disabled, with no chance of an interrupt sneaking through. For example, a critical portion of a disk BIOS might need to retrieve data from the disk controller the instant it becomes available; even a few hundred microseconds of delay could result in a sector's worth of -data misread. In this case, one misplaced interrupt during a **POPF** +data misread. In this case, one misplaced interrupt during a `POPF` could result in a trashed hard disk if that interrupt occurs while the disk BIOS is reading a sector of the File Allocation Table. -There is a workaround for the **POPF** bug. While the workaround is easy -to use, it's considerably slower than **POPF**, and costs a few bytes as +There is a workaround for the `POPF` bug. While the workaround is easy +to use, it's considerably slower than `POPF`, and costs a few bytes as well, so you won't want to use it in code that can tolerate interrupts. On the other hand, in code that truly cannot be interrupted, you should view those extra cycles and bytes as cheap insurance against mysterious and erratic program crashes. -One obvious reason to discuss the **POPF** workaround is that it's +One obvious reason to discuss the `POPF` workaround is that it's useful. Another reason is that the workaround is an excellent example of Zen-level assembly coding, in that there's a well-defined goal to be achieved but no obvious way to do so. The goal is to reproduce the -functionality of the **POPF** instruction without using **POPF**, and -the place to start is by asking exactly what **POPF** does. +functionality of the `POPF` instruction without using `POPF`, and +the place to start is by asking exactly what `POPF` does. -All **POPF** does is pop the word on top of the stack into the FLAGS -register, as shown in Figure 11.4. How can we do that without **POPF**? -Of course, the 286's designers intended us to use **POPF** for this +All `POPF` does is pop the word on top of the stack into the FLAGS +register, as shown in Figure 11.4. How can we do that without `POPF`? +Of course, the 286's designers intended us to use `POPF` for this purpose, and didn't intentionally provide any alternative approach, so we'll have to devise an alternative approach of our own. To do that, we'll have to search for instructions that contain some of the same -functionality as **POPF**, in the hope that one of those instructions -can be used in some way to replace **POPF**. \ No newline at end of file +functionality as `POPF`, in the hope that one of those instructions +can be used in some way to replace `POPF`. \ No newline at end of file diff --git a/11-08.md b/11-08.md index 7592c66..16aa7d2 100644 --- a/11-08.md +++ b/11-08.md @@ -10,26 +10,26 @@ chapter: '11' pages: 226-231 --- -Well, there's only one instruction other than **POPF** that loads the -FLAGS register directly from the stack, and that's **IRET**, which loads +Well, there's only one instruction other than `POPF` that loads the +FLAGS register directly from the stack, and that's `IRET`, which loads the FLAGS register from the stack as it branches, as shown in Figure -11.5. iret has no known bugs of the sort that plague **POPF**, so it's +11.5. iret has no known bugs of the sort that plague `POPF`, so it's certainly a candidate to replace popf in non-interruptible applications. -Unfortunately, **IRET** loads the FLAGS register with the *third* word +Unfortunately, `IRET` loads the FLAGS register with the *third* word down on the stack, not the word on top of the stack, as is the case with -**POPF**; the far return address that **IRET** pops into CS:IP lies +`POPF`; the far return address that `IRET` pops into CS:IP lies between the top of the stack and the word popped into the FLAGS register. -Obviously, the segment:offset that **IRET** expects to find on the stack +Obviously, the segment:offset that `IRET` expects to find on the stack above the pushed flags isn't present when the stack is set up for -**POPF**, so we'll have to adjust the stack a bit before we can -substitute **IRET** for **POPF**. What we'll have to do is push the +`POPF`, so we'll have to adjust the stack a bit before we can +substitute `IRET` for `POPF`. What we'll have to do is push the segment:offset of the instruction after our workaround code onto the -stack right above the pushed flags. **IRET** will then branch to that +stack right above the pushed flags. `IRET` will then branch to that address and pop the flags, ending up at the instruction after the workaround code with the flags popped. That's just the result that would -have occurred had we executed **POPF**—WITH the bonus that no interrupts +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. @@ -38,7 +38,7 @@ after the pop. 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 that instruction is a tried-and-true trick. We can do something similar -here, but in this case we need a far call, since **IRET** requires both +here, but in this case we need a far call, since `IRET` requires both a segment and an offset. We'll also branch backward so that the address pushed on the stack will point to the instruction we want to continue with. The code works out like this: @@ -66,7 +66,7 @@ popfskip: The operation of this code is illustrated in Figure 11.6. -The **POPF** workaround can best be implemented as a macro; we can also +The `POPF` workaround can best be implemented as a macro; we can also emulate a far call by pushing CS and performing a near call, thereby shrinking the workaround code by 1 byte: @@ -84,7 +84,7 @@ popfskip: By the way, the flags can be popped much more quickly if you're willing to alter a register in the process. For example, the following macro -emulates **POPF** with just one branch, but wipes out AX: +emulates `POPF` with just one branch, but wipes out AX: ```nasm EMULATE_POPF_TRASH_AX macro @@ -95,11 +95,11 @@ EMULATE_POPF_TRASH_AX macro endm ``` -It's not a perfect substitute for **POPF**, since **POPF** doesn't alter -any registers, but it's faster and shorter than **EMULATE\_POPF** when +It's not a perfect substitute for `POPF`, since `POPF` doesn't alter +any registers, but it's faster and shorter than `EMULATE_POPF` when you can spare the register. If you're using 286-specific instructions, you can use which is shorter still, alters no registers, and branches -just once. (Of course, this version of **EMULATE\_POPF** won't work on +just once. (Of course, this version of `EMULATE_POPF` won't work on an 8088.) ```nasm @@ -114,15 +114,15 @@ EMULATE_POPFmacro ![**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 -branches. Anyone in his/her right mind would prefer **POPF** to a +The standard version of `EMULATE_POPF` is 6 bytes longer than +`POPF` and much slower, as you'd expect given that it involves three +branches. Anyone in his/her right mind would prefer `POPF` to a larger, slower, three-branch macro—given a choice. In noncode, however, there's no choice here; the safer—if slower—approach is the best. (Having people associate your programs with crashed computers is *not* a desirable situation, no matter how unfair the circumstances under which it occurs.) -And now you know the nature of and the workaround for the **POPF** bug. +And now you know the nature of and the workaround for the `POPF` bug. Whether you ever need the workaround or not, it's a neatly packaged example of the tremendous flexibility of the x86 instruction set. \ No newline at end of file diff --git a/12-01.md b/12-01.md index f179f94..6a98830 100644 --- a/12-01.md +++ b/12-01.md @@ -40,8 +40,8 @@ Substitute "processor" for the various digging implements, and you get an idea of just how different the optimization rules for the 486 are from what you're used to. Okay, it's not quite *that* bad—but upon encountering a processor where string instructions are often to be -avoided and memory-to-register **MOV**s are frequently as fast as -register-to-register **MOV**s, Dorothy was heard to exclaim (before she +avoided and memory-to-register `MOV`s are frequently as fast as +register-to-register `MOV`s, Dorothy was heard to exclaim (before she sank out of sight in a swirl of hopelessly mixed metaphors), "I don't think we're in Kansas anymore, Toto." @@ -53,15 +53,15 @@ RISC elements, and it's those elements that are most responsible for making 486 optimization unique. Simple, common instructions are executed in a single cycle by a RISC-like core processor, but other instructions are executed pretty much as they were on the 386, where every -instruction takes at least 2 cycles. For example, **MOV AL, [TestChar]** +instruction takes at least 2 cycles. For example, `MOV AL, [TestChar]` takes only 1 cycle on the 486, assuming both instruction and data are in -the cache—3 cycles faster than the 386—but **STOSB** takes 5 cycles, 1 +the cache—3 cycles faster than the 386—but `STOSB` takes 5 cycles, 1 cycle *slower* than on the 386. The floating-point execution unit inside the 486 is also much faster than the 387 math coprocessor, largely because, being in the same silicon as the CPU (the 486 has a math coprocessor built in), it is more tightly coupled. The results are -sometimes startling: **FMUL** (floating point multiply) is usually -faster on the 486 than **IMUL** (integer multiply)! +sometimes startling: `FMUL` (floating point multiply) is usually +faster on the 486 than `IMUL` (integer multiply)! An encyclopedic approach to 486 optimization would take a book all by itself, so in this chapter I'm only going to hit the highlights of 486 @@ -116,9 +116,9 @@ CPUs. That is not the sense in which "indexed addressing" is meant here, however. In real mode, indexed addressing means that two registers, rather than one or none, are used to point to memory. (In this context, the use of one register to address memory is "base addressing," no -matter what register is used.) **MOV AX, [BX+DI]** and **MOV CL, -[BP+SI+10]** perform indexed addressing; **MOV AX,[BX]** and **MOV DL, -[SI+1]** do not. +matter what register is used.) `MOV AX, [BX+DI]` and `MOV CL, +[BP+SI+10]` perform indexed addressing; `MOV AX,[BX]` and `MOV DL, +[SI+1]` do not. > ![](images/i.jpg) > Therefore, in real mode, the rule is to avoid using two registers to diff --git a/12-02.md b/12-02.md index 5dc7ef6..e4aa9d5 100644 --- a/12-02.md +++ b/12-02.md @@ -14,9 +14,9 @@ which calculates the same sum and leaves the registers in the same state as the first example, but avoids indexed addressing. In protected mode, the definition of indexed addressing is a tad more -complex. The use of two registers to address memory, as in **MOV EAX, -[EDX+EDI]**, still qualifies for the one-cycle penalty. In addition, the -use of 386/486 scaled addressing, as in **MOV [ECX\*2],EAX**, also +complex. The use of two registers to address memory, as in `MOV EAX, +[EDX+EDI]`, still qualifies for the one-cycle penalty. In addition, the +use of 386/486 scaled addressing, as in `MOV [ECX*2],EAX`, also constitutes indexed addressing, even if only one register is used to point to memory. @@ -24,8 +24,8 @@ All this fuss over one cycle! You might well wonder how much difference one cycle could make. After all, on the 8088, effective address calculations take a *minimum* of 5 cycles. On the 486, however, 1 cycle is a big deal because many instructions, including most register-only -instructions (**MOV**, **ADD**, **CMP**, and so on) execute in just 1 -cycle. In particular, **MOV**s to and from memory execute in 1 cycle—if +instructions (`MOV`, `ADD`, `CMP`, and so on) execute in just 1 +cycle. In particular, `MOV`s to and from memory execute in 1 cycle—if they're not hampered by something like indexed addressing, in which case they slow to half speed (or worse, as we will see shortly). @@ -64,7 +64,7 @@ MOV AX,[BX] ``` there's no way that the 486 can calculate the address referenced by -**MOV AX,[BX]** until **MOV BX,OFFSET MemVar** finishes, so pipelining +`MOV AX,[BX]` until `MOV BX,OFFSET MemVar` finishes, so pipelining that calculation ahead of time is not possible. A good workaround is rearranging your code so that at least one instruction lies between the loading of the memory pointer and its use. For example, @@ -102,7 +102,7 @@ it's used. In 32-bit protected mode, however, the penalty is, in fact, the 1 cycle that Intel . -Considering that **MOV** normally takes only one cycle total, that's +Considering that `MOV` normally takes only one cycle total, that's quite a loss. For example, the postdecrement loop shown above is 2 full cycles faster than the preincrement loop, resulting in a 29 percent improvement in the performance of the entire loop. But wait, there's diff --git a/12-03.md b/12-03.md index ee26fd1..8a6ea27 100644 --- a/12-03.md +++ b/12-03.md @@ -26,14 +26,14 @@ Rule \#2A: Rule \#2 sometimes, but not always, applies to the stack pointer when it is implicitly used to point to memory. Intel states that the stack pointer is an implied destination register -for **CALL**, **ENTER**, **LEAVE**, **RET**, **PUSH**, and **POP** +for `CALL`, `ENTER`, `LEAVE`, `RET`, `PUSH`, and `POP` (which alter (E)SP), and that it is the implied base addressing register -for **PUSH**, **POP**, and **RET** (which use (E)SP to address memory). +for `PUSH`, `POP`, and `RET` (which use (E)SP to address memory). Intel then implies that the aforementioned addressing pipeline penalty is incurred whenever the stack pointer is used as a destination by one of the first set of instructions and is then immediately used to address memory by one of the second set. This raises the specter of unpleasant -programming contortions such as intermixing **PUSH**es and **POP**s with +programming contortions such as intermixing `PUSH`es and `POP`s with other instructions to avoid interrupting the addressing pipeline. Fortunately, matters are actually not so grim as Intel's documentation would indicate; my tests indicate that the addressing pipeline penalty @@ -52,7 +52,7 @@ et to exhibit the addressing pipeline interruption phenomenon (SP is both destination and addressing register for both instructions, according to -Intel), but this code runs in six cycles per **POP/RET** pair, matching +Intel), but this code runs in six cycles per `POP/RET` pair, matching the official execution times exactly. Likewise, a sequence like ```nasm @@ -66,7 +66,7 @@ runs in one cycle per instruction, just as it should. On the other hand, performing arithmetic directly on SP as an *explicit* destination—for example, to deallocate local variables—and then using -**PUSH**, **POP**, or **RET**, definitely can interrupt the addressing +`PUSH`, `POP`, or `RET`, definitely can interrupt the addressing pipeline. For example ```nasm @@ -89,12 +89,12 @@ I certainly haven't tried all possible combinations, but the results so far indicate that the stack pointer incurs the addressing pipeline penalty only if (E)SP is the *explicit* destination of one instruction and is then used by one of the two following instructions to address -memory. So, for instance, SP isn't the explicit operand of **POP AX—**AX -is—and no cycles are lost if **POP AX** is followed by **POP** or -**RET**. Happily, then, we need not worry about the sequence in which we -use **PUSH** and **POP**. However, adding to, moving to, or subtracting +memory. So, for instance, SP isn't the explicit operand of `POP AX`-AX +is—and no cycles are lost if `POP AX` is followed by `POP` or +`RET`. Happily, then, we need not worry about the sequence in which we +use `PUSH` and `POP`. However, adding to, moving to, or subtracting from the stack pointer should ideally be done at least two cycles before -**PUSH**, **POP**, **RET**, or any other instruction that uses the stack +`PUSH`, `POP`, `RET`, or any other instruction that uses the stack pointer to address memory. #### Problems with Byte Registers {#Heading9} @@ -144,7 +144,7 @@ important that you understand why this rule exists (only that it *does* in fact exist), but it is an interesting window on the way the 486 works. -In case you're curious, there's no such penalty for the typical **XLAT** +In case you're curious, there's no such penalty for the typical `XLAT` sequence like ```nasm @@ -154,20 +154,20 @@ mov al,[si] xlat ``` -even though AL must be converted to a word by **XLAT** before it can be +even though AL must be converted to a word by `XLAT` before it can be added to BX and used to address memory. In fact, none of the penalties -mentioned in this chapter apply to **XLAT**, apparently because **XLAT** +mentioned in this chapter apply to `XLAT`, apparently because `XLAT` is so slow—4 cycles—that it gives the 486 time to perform addressing calculations during the course of the instruction. > ![](images/i.jpg) -> While it's nice that **XLAT** doesn't suffer from the various 486 -> addressing penalties, the reason for that is basically that **XLAT** is -> slow, so there's still no compelling reason to use **XLAT** on the 486. +> While it's nice that `XLAT` doesn't suffer from the various 486 +> addressing penalties, the reason for that is basically that `XLAT` is +> slow, so there's still no compelling reason to use `XLAT` on the 486. In general, penalties for interrupting the 486's pipeline apply primarily to the fast core instructions of the 486, most notably -register-only instructions and **MOV**, although arithmetic and logical +register-only instructions and `MOV`, although arithmetic and logical operations that access memory are also often affected. I don't know all the performance dependencies, and I don't plan to; figuring all of them out would be a big, boring job of little value. Basically, on the 486 diff --git a/12-04.md b/12-04.md index a542d9f..c3fc8d5 100644 --- a/12-04.md +++ b/12-04.md @@ -76,9 +76,9 @@ in this case—one cycle before using a register to address memory produces no penalty; loading 2 cycles ahead is the only case that normally incurs a penalty. However, think of Rule \#4 as meaning that loading a byte register disrupts the memory addressing pipeline as it -starts up. Viewed that way, we can see that **MOV BX,OFFSET MemVar** +starts up. Viewed that way, we can see that `MOV BX,OFFSET MemVar` interrupts the addressing pipeline, forcing it to start again, and then, -presumably, **MOV CL,AL** interrupts the pipeline again because the +presumably, `MOV CL,AL` interrupts the pipeline again because the pipeline is now on its first cycle: the one that loading a byte register can affect. @@ -150,7 +150,7 @@ Note that Listings 12.1 and 12.2 each repeat the timing of the code under test a second time, to make sure that the instructions are in the cache on the second pass, the one for which results are displayed. Also note that the code is less than 8K in size, so that it can all fit in -the 486's 8K internal cache. If I double the **REPT** value in Listing +the 486's 8K internal cache. If I double the `REPT` value in Listing 12.2 to 2,000, making the test code larger than 8K, the execution time more than doubles to 224 µs, or 3.7 cycles per repetition; the extra seven-tenths of a cycle comes from fetching non-cached instruction diff --git a/13-02.md b/13-02.md index b0489e5..3c35764 100644 --- a/13-02.md +++ b/13-02.md @@ -11,11 +11,11 @@ pages: 252-254 --- "When I looked closely as this, I realized that the two cycles for the -final **ADD** is just the sum of 1 cycle to load the data from memory, +final `ADD` is just the sum of 1 cycle to load the data from memory, and 1 cycle to add it to DX, so the code could just as well have been written as shown in Listing 13.3. The final breakthrough came when I realized that by initializing AX to zero outside the loop, I could -rearrange it as shown in Listing 13.4 and do the final **ADD DX,AX** +rearrange it as shown in Listing 13.4 and do the final `ADD DX,AX` after the loop. This way there are two single-cycle instructions between the first and the fourth line, avoiding all pipeline stalls, for a total throughput of two cycles/char." @@ -52,12 +52,12 @@ Clever 486 optimization can pay off big. QED. ### BSWAP: More Useful Than You Might Think {#Heading4} There are only 3 non-system instructions unique to the 486. None is -earthshaking, but they have their uses. Consider **BSWAP. BSWAP** does +earthshaking, but they have their uses. Consider `BSWAP`. `BSWAP` does just what its name implies, swapping the bytes (not bits) of a 32-bit register from one end of the register to the other, as shown in Figure -13.2. (**BSWAP** can only work with 32-bit registers; memory locations +13.2. (`BSWAP` can only work with 32-bit registers; memory locations and 16-bit registers are not valid operands.) The obvious use of -**BSWAP** is to convert data from Intel format (least significant byte +`BSWAP` is to convert data from Intel format (least significant byte first in memory, also called *little endian*) to Motorola format (most significant byte first in memory, or *big endian*), like so: @@ -67,16 +67,16 @@ bswap stosd ``` -**BSWAP** can also be useful for reversing the order of pixel bits from +`BSWAP` can also be useful for reversing the order of pixel bits from a bitmap so that they can be rotated 32 bits at a time with an -instruction such as **ROR EAX,1**. Intel's byte ordering for multiword +instruction such as `ROR EAX,1`. Intel's byte ordering for multiword values (least-significant byte first) loads pixels in the wrong order, -so far as word rotation is concerned, but **BSWAP** can take care of +so far as word rotation is concerned, but `BSWAP` can take care of that. ![**Figure 13.2**  *BSWAP in operation.*](images/13-02.jpg) -As it turns out, though, **BSWAP** is also useful in an unexpected way, +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 registers. As any assembly language programmer knows, the x86 register set is too small; or, to phrase that another way, it sure would be nice @@ -118,8 +118,8 @@ Not necessarily. Shifts and rotates are among the worst performing instructions of the 486, taking 2 to 3 cycles to execute. Thus, it takes 2 cycles to rotate the skip value into CX in Listing 13.5, and 2 more cycles to rotate it back to the upper half of ECX. I'd say four cycles -is a pretty steep price to pay, especially considering that a **MOV** to -or from memory takes only one cycle. Basically, using **ROR** to access +is a pretty steep price to pay, especially considering that a `MOV` to +or from memory takes only one cycle. Basically, using `ROR` to access a 16-bit value in the upper half of a 16-bit register is a pretty marginal technique, unless for some reason you can't access memory at all (for example, if you're using BP as a working register, temporarily diff --git a/13-03.md b/13-03.md index 2c4d70b..bbde3b6 100644 --- a/13-03.md +++ b/13-03.md @@ -10,11 +10,11 @@ chapter: '13' pages: 254-256 --- -On the 386, **ROR** was the only way to split a 32-bit register into two -16-bit registers. On the 486, however, **BSWAP** can not only do the -job, but can do it better, because **BSWAP** executes in just one cycle. -**BSWAP** has the added benefit of not affecting any flags, unlike -**ROR**. With **BSWAP**-based code like that in Listing 13.6, the upper +On the 386, `ROR` was the only way to split a 32-bit register into two +16-bit registers. On the 486, however, `BSWAP` can not only do the +job, but can do it better, because `BSWAP` executes in just one cycle. +`BSWAP` has the added benefit of not affecting any flags, unlike +`ROR`. With `BSWAP`-based code like that in Listing 13.6, the upper 16 bits of a register can be accessed with only 2 cycles of overhead and without altering any flags, making the technique of packing two 16-bit registers into one 32-bit register much more useful. @@ -37,8 +37,8 @@ looptop: ### Pushing and Popping Memory {#Heading5} -Pushing or popping a memory location, as in **PUSH WORD PTR [BX]** or -**POP [MemVar]**, is a compact, easy way to get a value onto or off of +Pushing or popping a memory location, as in `PUSH WORD PTR [BX]` or +`POP [MemVar]`, is a compact, easy way to get a value onto or off of the stack, especially when pushing parameters for calling a C-compatible function. However, on a 486, these are unattractive instructions from a performance perspective. Pushing a memory location takes four cycles; by @@ -70,23 +70,23 @@ is so slow? The rule on the 486 is that simple operations, which can be executed in a single cycle by the 486's RISC core, are fast; whereas complex operations, which must be carried out in microcode just as they were on the 386, are almost all relatively slow. Slow, complex -operations include all the string instructions except **REP MOVS,** as -well as **XLAT, LOOP,** and, of course, **PUSH *mem*** and **POP -*mem.*** +operations include all the string instructions except `REP MOVS`, as +well as `XLAT`, `LOOP`, and, of course, `PUSH *mem*` and `POP +*mem*`. > ![](images/i.jpg) > Whenever possible, try to use the 486's 1-cycle instructions, including -> **MOV, ADD, SUB, CMP, ADC, SBB, XOR, AND, OR, TEST, LEA**, and **PUSH -> reg** and **POP reg**. These instructions have an added benefit in that -> it's often possible to rearrange them for maximum pipeline efficiency, -> as is the case with Terje's optimization described earlier in this -> chapter. +> `MOV`, `ADD`, `SUB`, `CMP`, `ADC`, `SBB`, `XOR`, `AND`, `OR`, `TEST`, +> `LEA`, and `PUSH reg` and `POP reg`. These instructions have an added +> benefit in that it's often possible to rearrange them for maximum +> pipeline efficiency, as is the case with Terje's optimization described +> earlier in this chapter. ### Optimal 1-Bit Shifts and Rotates {#Heading6} On a 486, the n-bit forms of the shift and rotate instructions—as in -**ROR AX,2** and **SHL BX,9**—are 2-cycle instructions, but the 1-bit -forms—as in **ROR AX,1** and **SHL BX,1—**are *3-cycle* instructions. Go +`ROR AX,2` and `SHL BX,9`—are 2-cycle instructions, but the 1-bit +forms—as in `ROR AX,1` and `SHL BX,1`-are *3-cycle* instructions. Go figure. Assemblers default to the 1-bit instruction for 1-bit shifts and @@ -96,12 +96,12 @@ the n-bit form doesn't even exist on an 8088. In a really critical loop, however, it might be worth hand-assembling the n-bit form of a single-bit shift or rotate in order to save that cycle. The easiest way to do this is to assemble a 2-bit form of the desired instruction, as in -**SHL AX,2,** then look at the hex codes that the assembler generates -and use **DB** to insert them in your program code, with the value two -replaced with the value one. For example, you could determine that **SHL -AX,2** assembles to the bytes 0C1H 0E0H 002H, either by looking at the +`SHL AX,2`, then look at the hex codes that the assembler generates +and use `DB` to insert them in your program code, with the value two +replaced with the value one. For example, you could determine that `SHL +AX,2` assembles to the bytes 0C1H 0E0H 002H, either by looking at the disassembly in a debugger or by having the assembler generate a listing -file. You could then insert the n-bit version of **SHL AX,1** in your +file. You could then insert the n-bit version of `SHL AX,1` in your code as follows: ```nasm @@ -111,12 +111,12 @@ mov dx,ax ``` At the end of this sequence, DX will contain 2, and the fast n-bit -version of **SHL AX,1** will have executed. If you use this approach, +version of `SHL AX,1` will have executed. If you use this approach, I'd recommend using a macro, rather than sticking DBs in the middle of your code. Again, this technique is advantageous *only* on a 486. It also doesn't -apply to **RCL** and **RCR,** where you definitely want to use the 1-bit +apply to `RCL` and `RCR`, where you definitely want to use the 1-bit versions whenever you can, because the n-bit versions are horrendously slow. But if you're optimizing for the 486, these tidbits can save a few critical cycles—and Lord knows that if you're optimizing for the diff --git a/13-04.md b/13-04.md index 1cd37ca..9a4bcbb 100644 --- a/13-04.md +++ b/13-04.md @@ -23,7 +23,7 @@ mov al,BaseTable[ecx+edx*4] uses a perfectly valid 32-bit address, with the byte accessed being the one at the offset in DS pointed to by the sum of EDX times 4 plus the -offset of **BaseTable** plus ECX. This is a very powerful memory +offset of `BaseTable` plus ECX. This is a very powerful memory addressing scheme, far superior to 8088-style 16-bit addressing, but it's not without its quirks and costs, so let's take a quick look at 32-bit addressing. (By the way, 32-bit addressing is not limited to @@ -49,8 +49,8 @@ aforementioned 1-cycle penalty for using 32-bit addressing in real mode) is a 1-cycle penalty imposed for using an index register. In this context, you use an index register when you use a register that's scaled, or when you use the sum of two registers to point to memory. -**MOV BL,[EBX\*2]** uses an index register and takes an extra cycle, as -does **MOV CL,[EAX+EDX]; MOV CL,[EAX+100H]** is not indexed, however. +`MOV BL,[EBX*2]` uses an index register and takes an extra cycle, as +does `MOV CL,[EAX+EDX]; MOV CL,[EAX+100H]` is not indexed, however. The other cost of 32-bit addressing is in instruction size. Old-style 16-bit addressing usually (except in a few special cases) uses one extra @@ -61,8 +61,8 @@ constant value to add into the address. In many cases, 32-bit addressing continues to use the Mod-R/M byte, albeit with a different interpretation; in these cases, 32-bit addressing is no larger than 16-bit addressing, except when a 32-bit displacement is involved. For -example, **MOV AL, [EBX]** is a 2-byte instruction; **MOV AL, -[EBX+10H]** is a 3-byte instruction; and **MOV AL, [EBX+10000H]** is a +example, `MOV AL, [EBX]` is a 2-byte instruction; `MOV AL, +[EBX+10H]` is a 3-byte instruction; and `MOV AL, [EBX+10000H]` is a 6-byte instruction. > ![](images/i.jpg) diff --git a/14-01.md b/14-01.md index a8e8c33..631b796 100644 --- a/14-01.md +++ b/14-01.md @@ -71,7 +71,7 @@ information, you may want to refer to the discussion of string searching in the excellent *Algorithms in C,* by Robert Sedgewick (Addison-Wesley), which served as the primary reference for this chapter. (If you look at Sedgewick, be aware that in the Boyer-Moore -listing on page 288, there is a mistake: "j \> 0" in the **for** loop +listing on page 288, there is a mistake: "j \> 0" in the `for` loop should be "j \>= 0," unless I'm missing something.) String searching is the simple matter of finding the first occurrence of @@ -80,7 +80,7 @@ bytes (the buffer). The obvious, brute-force approach is to try every possible match location, starting at the beginning of the buffer and advancing one position after each mismatch, until either a match is found or the buffer is exhausted. There's even a nifty string -instruction, **REPZ CMPS,** that's perfect for comparing the pattern to +instruction, `REPZ CMPS`, that's perfect for comparing the pattern to the contents of the buffer at each location. What could be simpler? We have some important information that we're not yet using, though. @@ -91,10 +91,10 @@ usually be even, neither will any one character constitute half the buffer, or anything close. A reasonable conclusion is that the first character of the pattern will rarely match the first character of the buffer location currently being checked. This allows us to use the -speedy **REPNZ SCASB** to whiz through the buffer, eliminating most -potential match locations with single repetitions of **SCASB.** Only +speedy `REPNZ SCASB` to whiz through the buffer, eliminating most +potential match locations with single repetitions of `SCASB`. Only when that first character does (infrequently) match must we drop back to -the slower **REPZ CMPS** approach. +the slower `REPZ CMPS` approach. It's important to understand that we're assuming that the buffer is typical text. That's what I meant at the outset, when I said that the @@ -106,26 +106,26 @@ information you need may be under your nose. > a great deal of useful, if somewhat imprecise, information. If the buffer contains the letter ‘A' repeated 1,000 times, followed by -the letter ‘B,' then the **REPNZ SCASB/REPZ CMPS** approach will be much -slower than the brute-force **REPZ CMPS** approach when searching for -the pattern "AB," because **REPNZ SCASB** would match at every buffer +the letter ‘B,' then the `REPNZ SCASB/REPZ CMPS` approach will be much +slower than the brute-force `REPZ CMPS` approach when searching for +the pattern "AB," because `REPNZ SCASB` would match at every buffer location. You could construct a horrendous worst-case scenario for almost any good optimization; the key is understanding the usual conditions under which your code will work. As discussed in Chapter 9, we also know that certain characters have lower probabilities of matching than others. In a normal buffer, ‘T' -will match far more often than ‘X.' Therefore, if we use **REPNZ SCASB** +will match far more often than ‘X.' Therefore, if we use `REPNZ SCASB` to scan for the least common letter in the search string, rather than the first letter, we'll greatly decrease the number of times we have to -drop back to **REPZ CMPS,** and the search time will become very close -to the time it takes **REPNZ SCASB** to go from the start of the buffer +drop back to `REPZ CMPS`, and the search time will become very close +to the time it takes `REPNZ SCASB` to go from the start of the buffer to the match location. If the distance to the first match is N bytes, -the least-common **REPNZ SCASB** approach will take about as long as N -repetitions of **REPNZ SCASB.** +the least-common `REPNZ SCASB` approach will take about as long as N +repetitions of `REPNZ SCASB`. -At this point, we're pretty much searching at the speed of **REPNZ -SCASB.** On the x86, there simply is no faster way to test each +At this point, we're pretty much searching at the speed of `REPNZ +SCASB`. On the x86, there simply is no faster way to test each character in turn. In order to get any faster, we'd have to check fewer characters—but we can't do that and still be sure of finding all matches. Can we? diff --git a/14-02.md b/14-02.md index f62a840..89836c6 100644 --- a/14-02.md +++ b/14-02.md @@ -48,7 +48,7 @@ character, as often happens, we can only skip ahead 1 byte, as usual. Look at it differently, though: What if we compare the pattern starting with the last (rightmost) byte, rather than the first (leftmost) byte? In other words, what if we compare from high memory toward low, in the -direction in which string instructions go after the **STD** instruction? +direction in which string instructions go after the `STD` instruction? After all, we're comparing one set of bytes (the pattern) to another set of bytes (a portion of the buffer); it doesn't matter in the least in what order we compare them, so long as all the bytes in one set are @@ -70,7 +70,7 @@ in the pattern; that's how many locations there are in the buffer that *might* have matched, but have just been shown not to, because they overlap the mismatched character that doesn't belong in the pattern. In this case, we can skip ahead by the full pattern length in the buffer! -This is how we can outperform even **REPNZ SCASB; REPNZ SCASB** has to +This is how we can outperform even `REPNZ SCASB; REPNZ SCASB` has to check every byte in the buffer, but Boyer-Moore doesn't. Figure 14.1 illustrates the operation of a Boyer-Moore search when the diff --git a/14-03.md b/14-03.md index 89c75e5..953fd19 100644 --- a/14-03.md +++ b/14-03.md @@ -15,13 +15,13 @@ Boyer-Moore searching; Listing 14.2 is a test-bed program that searches up to the first 32K of a file for a pattern. Table 14.1 (all times measured with Turbo Profiler on a 20 MHz cached 386, searching a modified version of the text of this chapter) shows that this -implementation is generally much slower than **REPNZ SCASB,** although +implementation is generally much slower than `REPNZ SCASB`, although it does come close when searching for long patterns. Listing 14.1 is designed primarily to make later assembly implemenmore comprehensible, rather than faster; Sedge's implementation uses arrays rather than pointers, is a great deal more compact and very clever, and may be -somewhat faster. Regardless, the far superior performance of **REPNZ -SCASB** clearly indicates that assembly language is in order at this +somewhat faster. Regardless, the far superior performance of `REPNZ +SCASB` clearly indicates that assembly language is in order at this point. | | "g;" | "Yogi" | "igoY" | "Adrian" | "Conclusion" | "You don't know what you know" | @@ -43,7 +43,7 @@ parentheses).\ The entry "Standard Boyer-Moore in ASM" in Table 14.1 refers to straight-forward hand optimization of Listing 14.1, code that is not included in this chapter for the perfectly good reason that it is slower -in most cases than **REPNZ SCASB.** I say this casually now, but not so +in most cases than `REPNZ SCASB`. I say this casually now, but not so yesterday, when I had all but concluded that Boyer-Moore was simply inferior on the x86, due to two architectural quirks: the string instructions and slow branch. I had even coined a neat phrase for it: diff --git a/14-04.md b/14-04.md index 32fccfa..ceb6a7f 100644 --- a/14-04.md +++ b/14-04.md @@ -162,16 +162,16 @@ void main() { Well, architecture carries a lot of weight, but it sure as heck isn't destiny. I had simply fallen into the trap of figuring that the algorithm was so clever that I didn't have to do any thinking myself. -The path leading to **REPNZ SCASB** from the original brute-force -approach of **REPZ CMPSB** at every location had been based on my +The path leading to `REPNZ SCASB` from the original brute-force +approach of `REPZ CMPSB` at every location had been based on my observation that the first character comparison at each buffer location usually fails. Why not apply the same concept to Boyer-Moore? Listing 14.3 is just like the standard implementation—except that it's optimized to handle a first-comparison mismatch as quickly as possible in the loop -at **QuickSearchLoop**, much as **REPNZ SCASB** optimizes +at `QuickSearchLoop`, much as `REPNZ SCASB` optimizes first-comparison mismatches for the brute-force approach. The results in Table 14.1 speak for themselves; Listing 14.3 is more than twice as fast as what I assure you was already a nice, tight assembly implementation -(and unrolling **QuickSearchLoop** could boost performance by up to 10 -percent more). Listing 14.3 is also *four times* faster than **REPNZ -SCASB** in one case. \ No newline at end of file +(and unrolling `QuickSearchLoop` could boost performance by up to 10 +percent more). Listing 14.3 is also *four times* faster than `REPNZ +SCASB` in one case. \ No newline at end of file diff --git a/14-06.md b/14-06.md index 8d5508d..3e99321 100644 --- a/14-06.md +++ b/14-06.md @@ -14,7 +14,7 @@ Table 14.1 represents a limited and decidedly unscientific comparison of searching techniques. Nonetheless, the overall trend is clear: For all but the shortest patterns, well-implemented Boyer-Moore is generally as good as or better than—sometimes *much* better than—brute-force -searching. (For short patterns, you might want to use **REPNZ SCASB,** +searching. (For short patterns, you might want to use `REPNZ SCASB`, thereby getting the best of both worlds.) Know your data and use your smarts. Don't stop thinking just because @@ -25,8 +25,8 @@ you're implementing a big-name algorithm; you know more than it does. We can do substantially better yet than Listing 14.3 if we're willing to accept tighter limits on the data. Limiting the length of the searched-for pattern to a maximum of 255 bytes allows us to use the -**XLAT** instruction and generally tighten the critical loop. (Be aware, -however, that **XLAT** is a relatively expensive instruction on the 486 +`XLAT` instruction and generally tighten the critical loop. (Be aware, +however, that `XLAT` is a relatively expensive instruction on the 486 and Pentium.) Putting a copy of the searched-for string at the end of the search buffer as a sentinel, so that the search never fails, frees us from counting down the buffer length, and makes it easy to unroll the @@ -185,7 +185,7 @@ _FindString endp ``` Note that Table 14.1 includes the time required to build the skip table -each time **FindString** is called. This time could be eliminated for +each time `FindString` is called. This time could be eliminated for all but the first search when repeatedly searching for a particular pattern, by building the skip table externally and passing a pointer to it as a parameter. diff --git a/15-01.md b/15-01.md index ee66a9c..8359b71 100644 --- a/15-01.md +++ b/15-01.md @@ -126,23 +126,23 @@ The basic concept of a linked list—the one I came up with for that *DDJ* column—is straightforward, as shown in Figure 15.1. A head pointer points to the first node in the list, which points to the next node, which points to the next, and so on, until the last node in the list is -reached (typically denoted by a **NULL** next-node pointer). +reached (typically denoted by a `NULL` next-node pointer). Conceptually, nothing could be simpler. From an implementation perspective, however, there are serious flaws with this model. The fundamental problem is that the model of Figure 15.1 unnecessarily complicates link manipulation. In order to delete a node, for example, -you must change the preceding node's **NextNode** pointer to point to +you must change the preceding node's `NextNode` pointer to point to the following node, as shown in Listing 15.1. (Listing 15.2 is the -header file LLIST.H, which is **\#include**d by all the linked list +header file LLIST.H, which is `#include`d by all the linked list listings in this chapter.) Easy enough—unless the preceding node happens -to be the head pointer, which doesn't *have* a **NextNode** field, +to be the head pointer, which doesn't *have* a `NextNode` field, because it's not a node, so Listing 15.1 won't work. Cumbersome special code and extra information (a pointer to the head of the list) are required to handle the head-pointer case, as shown in Listing 15.3. (I'll grant you that if you make the next-node pointer the first field -in the **LinkNode** structure, at offset 0, then you could successfully -point to the head pointer and pretend it was a **LinkNode** +in the `LinkNode` structure, at offset 0, then you could successfully +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.) diff --git a/15-02.md b/15-02.md index 47f1f61..b34083f 100644 --- a/15-02.md +++ b/15-02.md @@ -72,7 +72,7 @@ struct LinkNode *DeleteNodeAfter(struct LinkNode **HeadOfListPtr, However, it is true that if you're going to store a variety of types of structures in your linked lists, you should start each node with the -**LinkNode** field. That way, the link pointer is in the same place in +`LinkNode` field. That way, the link pointer is in the same place in *every* structure, and the same linked list code can handle all of the structure types by casting them to the base link-node structure type. This is a less than elegant approach, but it works. C++ can handle data @@ -80,7 +80,7 @@ mixing more cleanly than C, via derivation from a base link-node class. Note that Listings 15.1 and 15.3 have to specify the linked-list delete operation as "delete the *next* node," rather than "delete this node," -because in order to relink it's necessary to access the **NextNode** +because in order to relink it's necessary to access the `NextNode` field of the node preceding the node to be deleted, and it's impossible to backtrack in a singly linked list. For this reason, singly-linked list operations tend to work with the structure preceding the one of @@ -112,10 +112,10 @@ Likewise, there should be a separate node for the tail of the list, so that every node that contains real data is guaranteed to have a node on either side of it. In this scheme, an empty list contains two nodes, as shown in Figure 15.3. Although it is not necessary, the tail node may -point to itself as its own next node, rather than contain a **NULL** +point to itself as its own next node, rather than contain a `NULL` pointer. This way, a deletion operation on an empty list will have no effect—quite unlike the same operation performed on a list terminated -with a **NULL** pointer. The tail node of a list terminated like this +with a `NULL` pointer. The tail node of a list terminated like this can be detected because it will be the only node for which the next-node pointer equals the current-node pointer. diff --git a/15-03.md b/15-03.md index 96bd53b..f2dffd4 100644 --- a/15-03.md +++ b/15-03.md @@ -65,9 +65,9 @@ there. Look it up *before* turning on your optimizer afterburners! Listings 15.1 and 15.6 together form a suite of C functions for maintaining a circular linked list sorted by ascending value. (Listing 15.5 requires modification before it will work with circular lists.) -Listing 15.7 is an assembly language version of **InsertNodeSorted()**; +Listing 15.7 is an assembly language version of `InsertNodeSorted()`; note the tremendous efficiency of the scanning loop in -**InsertNodeSorted()—**four instructions per node!—thanks to the dummy +`InsertNodeSorted()`-four instructions per node!—thanks to the dummy head/tail/sentinel node. Listing 15.8 is a simple application that illustrates the use of the linked-list functions in Listings 15.1 and 15.6. diff --git a/15-04.md b/15-04.md index 29f68d3..b9ffdcb 100644 --- a/15-04.md +++ b/15-04.md @@ -164,7 +164,7 @@ void main() In one of my *PC TECHNIQUES* "Pushing the Envelope" columns, I passed along one of David Stafford's fiendish programming puzzles: Write a -C-callable function to find the greatest or smallest unsigned **int**. +C-callable function to find the greatest or smallest unsigned `int`. Not a big deal—except that David had *already* done it in 24 bytes, so the challenge was to do it in 24 bytes or less. @@ -172,8 +172,8 @@ Such routines soon began coming at me from all angles. However (and I hate to say this because some of my correspondents were *very* pleased with the thought that they had bested David), no one has yet met the challenge—because most of you folks missed a key point. When David said, -"Write a function to find the greatest or smallest unsigned **int** in -24 bytes or less," he meant, "Write the **hi** and the **lo** functions +"Write a function to find the greatest or smallest unsigned `int` in +24 bytes or less," he meant, "Write the `hi` and the `lo` functions in 24 bytes or less—*combined*." Oh. diff --git a/16-03.md b/16-03.md index e02807e..8a66449 100644 --- a/16-03.md +++ b/16-03.md @@ -106,16 +106,16 @@ _ScanBuffer endp ``` Listing 16.4 features several interesting tricks. First, it uses -**LODSB** and **XLAT** in succession, a very neat way to get a +`LODSB` and `XLAT` in succession, a very neat way to get a pointed-to byte, advance the pointer, and look up the value indexed by the byte in a table, all with just two instruction bytes. (Interestingly, Listing 16.4 would probably run quite a bit better still -on an 8088, where **LODSB** and **XLAT** have a greater advantage over -conventional instructions. On the 486 and Pentium, however, **LODSB** -and **XLAT** lose much of their appeal, and should be replaced with -**MOV** instructions.) Better yet, **LODSB** and **XLAT** don't alter -the flags, so the Zero flag status set before **LODSB** is still around -to be tested after **XLAT** . +on an 8088, where `LODSB` and `XLAT` have a greater advantage over +conventional instructions. On the 486 and Pentium, however, `LODSB` +and `XLAT` lose much of their appeal, and should be replaced with +`MOV` instructions.) Better yet, `LODSB` and `XLAT` don't alter +the flags, so the Zero flag status set before `LODSB` is still around +to be tested after `XLAT` . Finally, if you look closely, you will see that Listing 16.4 jumps out of the loop to increment the word count in the case where a word is diff --git a/16-04.md b/16-04.md index cf56bca..19d580c 100644 --- a/16-04.md +++ b/16-04.md @@ -47,18 +47,18 @@ CMP DH,[EBX+EAX] Harmless enough, save for two things. First, EBX happened to be zero at this point (a leftover from an earlier version of the code, as it turned out), so it was superfluous as a memory-addressing component; this made -it possible to use base-only addressing (**[EAX]**) rather than -base+index addressing (**[EBX+EAX]**), which saves a cycle on the 386. -Second: Changing the instruction to **CMP [EAX],DH** saved 2 cycles—just +it possible to use base-only addressing (`[EAX]`) rather than +base+index addressing (`[EBX+EAX]`), which saves a cycle on the 386. +Second: Changing the instruction to `CMP [EAX],DH` saved 2 cycles—just enough, by good fortune, to speed up the whole program by 5 percent. > ![](images/i.jpg) -> **CMP reg,[mem]** takes 6 cycles on the 386, but **CMP [ mem ],reg** -> takes only 5 cycles; you should always perform**CMP** with the memory +> `CMP reg,[mem]` takes 6 cycles on the 386, but `CMP [ mem ],reg` +> takes only 5 cycles; you should always perform`CMP` with the memory > operand on the left on the 386. -(Granted, **CMP [*mem*],*reg*** is 1 cycle slower than **CMP -*reg*,[*mem*]** on the 286, and they're both the same on the 8088; in +(Granted, `CMP [*mem*],*reg*` is 1 cycle slower than `CMP +*reg*,[*mem*]` on the 286, and they're both the same on the 8088; in this case, though, the code was specific to the 386. In case you're curious, both forms take 2 cycles on the 486; quite a lot faster, eh?) @@ -69,7 +69,7 @@ no longer be valid from the 8088/286 world into the wonderful new world of 386 native-mode programming. The second lesson is that after you've slaved over your code for a while, you're in no shape to see its flaws, or to be able to get the new perspectives needed to speed it up. I'll -bet Terje looked at that **[EBX+EAX]** addressing a hundred times while +bet Terje looked at that `[EBX+EAX]` addressing a hundred times while trying to speed up his code, but he didn't really see what it did; instead, he saw what it was supposed to do. Mental shortcuts like this are what enable us to deal with the complexities of assembly language diff --git a/16-05.md b/16-05.md index e0226cd..0bada01 100644 --- a/16-05.md +++ b/16-05.md @@ -13,7 +13,7 @@ pages: 308-311 Table 16.2 has two times for each entry listed: the first value is the overall counting time, including time spent in the main program, disk I/O, and everything else; the second value is the time actually spent -counting words, the time spent in **ScanBuffer** . The first value is +counting words, the time spent in `ScanBuffer` . The first value is the time perceived by the user, but the second value best reflects the quality of the optimization in each entry, since the rest of the overall execution time is fixed. diff --git a/16-06.md b/16-06.md index 13fb74c..22492e6 100644 --- a/16-06.md +++ b/16-06.md @@ -35,20 +35,20 @@ the instruction set. The basic framework is still the same as my code (which in turn is basically the same as that of the original C code), but that framework is implemented more efficiently. -One obvious level 1 optimization is using a **word** rather than -**dword** counter. **ScanBuffer** can never be called upon to handle +One obvious level 1 optimization is using a `word` rather than +`dword` counter. `ScanBuffer` can never be called upon to handle more than 64K bytes at a time, so no more than 32K words can ever be -found. Given that, it's a logical step to use **INC** rather than -**ADD/ADC** to keep count, adding the tally into the full 32-bit count +found. Given that, it's a logical step to use `INC` rather than +`ADD/ADC` to keep count, adding the tally into the full 32-bit count only upon exiting the function. Another useful optimization is aligning -loop tops and other branch destinations to **word** , or better yet -**dword** , boundaries. +loop tops and other branch destinations to `word` , or better yet +`dword` , boundaries. Eliminating branches was very popular, as it should be on x86 processors. Branches were eliminated in a remarkable variety of ways. Many of you unrolled the loop, a technique that does pay off nicely. A word of caution: Some of you unrolled the loop by simply stacking -repetitions of the inner loop one after the other, with **DEC CX/JZ** +repetitions of the inner loop one after the other, with `DEC CX/JZ` appearing after each repetition to detect the end of the buffer. Part of the point of unrolling a loop is to reduce the number of times you have to check for the end of the buffer! The trick to this is to set CX to @@ -60,9 +60,9 @@ fraction of the number of unrolled repetitions is required to make the whole thing come out right. Listing 16.5 (QSCAN3.ASM) illustrates this technique. -Another effective optimization is the use of **LODSW** rather than -**LODSB** , thereby processing two bytes per memory access. This has the -effect of unrolling the loop one time, since with **LODSW** , looping is +Another effective optimization is the use of `LODSW` rather than +`LODSB` , thereby processing two bytes per memory access. This has the +effect of unrolling the loop one time, since with `LODSW` , looping is performed at most only once every two bytes. Cutting down the branches used to loop is only part of the branching @@ -80,11 +80,11 @@ code. Listing 16.6, contributed by Willem Clements, of Granada, Spain, illustrates a variety of level 1 optimizations: the two-loop approach, -the use of a 16- rather than 32-bit counter, and the use of **LODSW** . +the use of a 16- rather than 32-bit counter, and the use of `LODSW` . Together, these optimizations made Willem's code nearly twice as fast as mine in Listing 16.4. A few details could stand improvement; for -example, **AND AX,AX** is a shorter way to test for zero than **CMP -AX,0** , and **ALIGN 2** could be used. Nonetheless, this is good code, +example, `AND AX,AX` is a shorter way to test for zero than `CMP +AX,0` , and `ALIGN 2` could be used. Nonetheless, this is good code, and it's also fairly compact and reasonably easy to understand. In short, this is an excellent example of how an hour or so of hand-optimization might accomplish significantly improved performance at diff --git a/16-07.md b/16-07.md index 53532ed..791bfff 100644 --- a/16-07.md +++ b/16-07.md @@ -127,8 +127,8 @@ exactly that. They stepped back, thought about what the code actually needed to do, rather than just improving how it already worked, and implemented code that sprang from that new perspective. -You can see one example of this in Listing 16.6, where Willem uses **CMP -AX,0101H** to check two bytes at once. While you might think of this as +You can see one example of this in Listing 16.6, where Willem uses `CMP +AX,0101H` to check two bytes at once. While you might think of this as nothing more than a doubling up of tests, it's a little more than that, especially when taken together with the use of two loops. This is a break with the serial nature of the C code, a recognition that word @@ -157,7 +157,7 @@ non-word. A word is not a transition, it is the presence of a group of characters. Thought of this way, the code would have counted the word when it first detected the group. Had you done this, your main program would not have needed to look for the possible last transition or deal -with the semantics of the value in **CharValue**."* +with the semantics of the value in `CharValue`."* John Richardson, of New York, contributed a good example of the benefits of a different perspective (in this case, a hardware perspective). John diff --git a/16-08.md b/16-08.md index 06c2961..a3eb9ea 100644 --- a/16-08.md +++ b/16-08.md @@ -29,7 +29,7 @@ ScanLoop: ``` John later divides the transition count by two to get the word count. -(Food for thought: It's also possible to use **CMP** and **ADC** to +(Food for thought: It's also possible to use `CMP` and `ADC` to detect words without branching.) John's approach makes it clear that word-counting is nothing more than a @@ -121,7 +121,7 @@ ASCII here, so the high bit is ignored.) Thus, David is able to add the word/not status for each pair of bytes to the main word count simply by getting the two bytes, working in the carry status from the last byte, and using the resulting value to index into the 64K table, adding in the -1 or 0 value found in that table. A sequence of **MOV/ADC/ADD** suffices +1 or 0 value found in that table. A sequence of `MOV/ADC/ADD` suffices to perform all word-counting tasks for a pair of bytes. Three instructions, no branches—pretty nearly perfect code. diff --git a/17-03.md b/17-03.md index 063d073..9dfe262 100644 --- a/17-03.md +++ b/17-03.md @@ -14,8 +14,8 @@ pages: 329-331 How slow is Listing 17.1? Table 17.1 shows that even on a 486, Listing 17.1 does fewer than three 96x96 generations per second. (The times in -Table 17.1 are for 1,000 generations of a 96x96 cell map with **seed=1, -LIMIT\_18\_HZ=0, WRAP\_EDGES=1,** and **magnifier=2,** running on a 33 +Table 17.1 are for 1,000 generations of a 96x96 cell map with `seed=1, +LIMIT_18_HZ=0, WRAP_EDGES=1`, and `magnifier=2`, running on a 33 MHz 486.) Since my target is 18 generations per second with a 200x200 cellmap on a 20 MHz 386, Listing 17.1 is too slow by a rather wide margin—75 times too slow, in fact. You might say we have a little @@ -25,41 +25,41 @@ The first rule of optimization is: Only optimize where it matters. Use a profiler, or risk making a fool of yourself. Consider Listings 17.1 and 17.2. Where do you think the potential for significant speed-up lies? I'll tell you one place where I thought there was considerable -potential—in **draw\_pixel()**. As a programmer of high-speed graphics, +potential—in `draw_pixel()`. As a programmer of high-speed graphics, I figured any drawing function that was not only written in C/C++ but also recalculated the target address from scratch for each pixel would be among the first optimization targets. I also expected to get major gains out of going to a Ping-Pong arrangement so that I didn't have to -copy the new cellmap back to **current\_map** after calculating the next +copy the new cellmap back to `current_map` after calculating the next generation. | | Listing 17.1 | Listing 17.3 | Listing 17.4 | |--------------------------|--------------|--------------|--------------| | **Total execution time** | 340 secs | 94 secs | 45 secs | -| **cell\_state()** | 275 | 21 | — | -| **next\_generation()** | 60 | 14 | 40 | -| **count\_neighbors()** | — | 54 | — | -| **draw\_pixel()** | 2 | 2 | 2 | -| **set\_cell()** | <1 | <1 | <1 | -| **clear\_cell()** | <1 | <1 | <1 | -| **copy\_cells()** | <1 | <1 | <1 | +| `cell_state()` | 275 | 21 | — | +| `next_generation()` | 60 | 14 | 40 | +| `count_neighbors()` | — | 54 | — | +| `draw_pixel()` | 2 | 2 | 2 | +| `set_cell()` | <1 | <1 | <1 | +| `clear_cell()` | <1 | <1 | <1 | +| `copy_cells()` | <1 | <1 | <1 | Table: Table 17.1 Execution times for the game of life. I was wrong. Wrong, wrong, wrong. (But at least I was smart enough to use a profiler before actually writing any new code.) Table 17.1 shows where the time actually goes in Listings 17.1 and 17.2. As you can see, -the time taken by **draw\_pixel(),** **copy\_cells(),** and *everything* +the time taken by `draw_pixel()`, `copy_cells()`, and *everything* other than calculating the next generation is nothing more than noise. We could optimize these routines right down to executing *instantaneously,* and you know what? It wouldn't make the slightest perceptible difference in how fast the program runs. Given the present state of our Game of Life implementation, the only areas worth looking -at for possible optimizations are **cell\_state()** and -**next\_generation().** +at for possible optimizations are `cell_state()` and +`next_generation().` > ![](images/i.jpg) -> It's worth noting, though, that one reason **draw\_pixel()** doesn't +> It's worth noting, though, that one reason `draw_pixel()` doesn't > much affect performance is that in Listing 17.1, we're smart enough to > redraw pixels only when their states change, rather than during every > generation. Detecting and eliminating redundant operations is part of @@ -68,11 +68,11 @@ at for possible optimizations are **cell\_state()** and ### The Hazards and Advantages of Abstraction {#Heading6} -How can we speed up **cell\_state()** and **next\_generation()**? I'll +How can we speed up `cell_state()` and `next_generation()`? I'll tell you how *not* to do it: By writing those member functions in -assembly. It's tempting to say that **cell\_state()** is taking all the +assembly. It's tempting to say that `cell_state()` is taking all the time, so we need to speed it up with assembly, but what we really need -to do is figure out *why* **cell\_state()** is taking all the time, then +to do is figure out *why* `cell_state()` is taking all the time, then address that aspect of the program directly. Once you know where you need to optimize, the one word to keep in mind @@ -100,7 +100,7 @@ algorithm running on top of a highly abstract programming model, you'll almost certainly end up with a slow program. In Listing 17.1, the abstraction that's killing us is that of looking at the eight neighbors with eight completely independent operations, requiring eight calls to -**cell\_state()** and eight calculations of cell address and cell mask. +`cell_state()` and eight calculations of cell address and cell mask. In fact, given the nature of cell storage, the eight neighbors are in a fixed relationship to one another, and the addresses and masks of all eight can generally be found very easily via hard-wired offsets and diff --git a/17-04.md b/17-04.md index a7344c5..cf640f0 100644 --- a/17-04.md +++ b/17-04.md @@ -35,7 +35,7 @@ all. But doesn't that extra copying of the edges take time? Sure, but only a little; we can build it into the cellmap copying function, and then frankly we won't even notice it. Avoiding tens or hundreds of thousands -of calls to **cell\_state(),** on the other hand, will be *very* +of calls to `cell_state()`, on the other hand, will be *very* noticeable. Listing 17.3 shows the alterations to Listing 17.1 required to implement a hard-wired neighbor-counting function. This is a minor change, in truth, implemented in about half an hour and not making the diff --git a/17-05.md b/17-05.md index dc03cbc..dfd0f28 100644 --- a/17-05.md +++ b/17-05.md @@ -23,16 +23,16 @@ implement it is. You can often improve performance a good deal by implementing only the level of generality you need, but at the same time decreased generality makes it more difficult to change or port the program at some later date. A Game of Life implementation, such as -Listing 17.1, that's built on **set\_cell()**, **clear\_cell()**, and -**get\_cell()** is completely general; you can change the cell storage +Listing 17.1, that's built on `set_cell()`, `clear_cell()`, and +`get_cell()` is completely general; you can change the cell storage format simply by changing the constructor and those three functions. -Listing 17.3 is harder to change because **count\_neighbors()** would +Listing 17.3 is harder to change because `count_neighbors()` would also have to be altered, and it's more complex than any of the other functions. So, in Listing 17.3, we've gotten under the hood and changed the cellmap format a little, and gotten impressive results. But now -**count\_neighbors()** is hard-wired for optimized counting, and it's +`count_neighbors()` is hard-wired for optimized counting, and it's still taking up more than half the time. Maybe now it's time to go to assembly? @@ -135,12 +135,12 @@ neighbor_count++; ``` Listing 17.4 and Listing 17.3 are functionally the same; the only -difference lies in how **next\_generation()** is implemented. (Only -**next\_generation()** is shown in Listing 17.4; the program is +difference lies in how `next_generation()` is implemented. (Only +`next_generation()` is shown in Listing 17.4; the program is otherwise identical to Listing 17.3.) Listing 17.4 applies the following -optimizations to **next\_generation()**: +optimizations to `next_generation()`: -The neighbor-counting code is brought into **next\_generation,** +The neighbor-counting code is brought into `next_generation`, eliminating many function calls and from-scratch address/mask calculations; all multiplies are eliminated by using pointers and addition; and all cells are accessed directly via pointers and masks, @@ -149,7 +149,7 @@ calculations. The net effect of these optimizations is that Listing 17.4 is more than twice as fast as Listing 17.3; we've achieved the desired 18 generations -per second, albeit only on a 486, and only at 96x96. (The **\#define** +per second, albeit only on a 486, and only at 96x96. (The `#define` that enables code limiting the speed to 18 Hz, which seemed ridiculous in Listing 17.1, is actually useful for keeping the generations from iterating too quickly when Listing 17.4 is running on a 486, especially diff --git a/17-06.md b/17-06.md index 45440c5..2ee07d1 100644 --- a/17-06.md +++ b/17-06.md @@ -115,8 +115,8 @@ representation for an off-cell that has no neighbors is a zero byte, we can skip over scads of unchanged cells at a pop simply by scanning for non-zero bytes. This is much faster than explicitly testing cell states and neighbor counts, and lends itself beautifully to assembly language -implementation as **REPZ SCASB** or (with a little cleverness) **REPZ -SCASW.** (Unfortunately, there's no C library function that can scan +implementation as `REPZ SCASB` or (with a little cleverness) `REPZ +SCASW`. (Unfortunately, there's no C library function that can scan memory for the next byte that's non-zero.) Listing 17.5 is a Game of Life implementation that uses the diff --git a/17-08.md b/17-08.md index c63666f..5fe7a64 100644 --- a/17-08.md +++ b/17-08.md @@ -34,7 +34,7 @@ it's a lot easier to work with the neighbor-count model. There's no complex mask and pointer management, and the only thing that *really* needs to be optimized is scanning for zero bytes. (And, in fact, I haven't optimized even that because it's done in a C++ loop; it should -really be **REPZ SCASB.**) +really be `REPZ SCASB`.) In truth, none of the code in Listing 17.5 is particularly well-optimized, and, as I noted, the program must be compiled with the diff --git a/18-05.md b/18-05.md index dbedcde..1d2a362 100644 --- a/18-05.md +++ b/18-05.md @@ -46,7 +46,7 @@ table indexed by the cell triplet itself. The value of the lookup table entry is equal to what the high byte should be in the next generation. If this value is equal to the current high byte, then no changes are necessary to the cell. Otherwise it is placed in the change list. Look -at the code in the **Test()** and **Fix()** functions to see how this is +at the code in the `Test()` and `Fix()` functions to see how this is done." [This step is as important as it is obscure. David has a 64K table organized so that if you use a word describing a cell triplet as a lookup index, the byte you will read will be the state of the high byte diff --git a/19-01.md b/19-01.md index 3ca3401..b67df60 100644 --- a/19-01.md +++ b/19-01.md @@ -111,7 +111,7 @@ discuss in the next chapter), so on the Pentium it is possible to execute two instructions, even instructions that access memory, in a single clock. The cycle times for instruction execution in a given pipe (both pipes process instructions at the same speed) are comparable to -those for the 486, although some instructions—notably **MUL**, the +those for the 486, although some instructions—notably `MUL`, the repeated string instructions, and some of the shifts and rotates—have gotten faster. diff --git a/19-03.md b/19-03.md index 929f45a..41b6857 100644 --- a/19-03.md +++ b/19-03.md @@ -15,21 +15,21 @@ pages: 375-377 I'll spend the rest of this chapter covering a variety of Pentium optimization tips. For starters, effective address calculations (that is, the addition and scaling required to calculate a memory operand's -address, as for example in **MOV EAX,[EBX+ECX\*2+4]**) never take any +address, as for example in `MOV EAX,[EBX+ECX*2+4]`) never take any extra cycles on the Pentium (other than possibly an AGI cycle), even for -the use of base+index addressing (as in **MOV [ESI+EDI],EAX**) or -scaling (\*2, \*4, or \*8, as in **INC ARRAY[ESI\*4]**). On the 486, +the use of base+index addressing (as in `MOV [ESI+EDI],EAX`) or +scaling (\*2, \*4, or \*8, as in `INC ARRAY[ESI*4]`). On the 486, both of the latter cases cause a 1-cycle penalty. The faster effective -address calculations have the side effect of making **LEA** very -attractive as an arithmetic instruction. **LEA** can add any two +address calculations have the side effect of making `LEA` very +attractive as an arithmetic instruction. `LEA` can add any two registers, one of which can be multiplied by one, two, four, or eight, plus a constant value, and can store the result in any register—all in one cycle, apart from AGIs. Not only that, but as we'll see in the next -chapter, **LEA** can go through either pipe, whereas **SHL** can only go -through the U-pipe, so **LEA** is often a superior choice for -multiplication by three, four, five, eight, or nine. (**ADD** is the -best choice for multiplication by two.) If you use **LEA** for -arithmetic, do remember that unlike **ADD** and **SHL**, it doesn't +chapter, `LEA` can go through either pipe, whereas `SHL` can only go +through the U-pipe, so `LEA` is often a superior choice for +multiplication by three, four, five, eight, or nine. (`ADD` is the +best choice for multiplication by two.) If you use `LEA` for +arithmetic, do remember that unlike `ADD` and `SHL`, it doesn't modify any flags. As on the 486, memory operands should not cross any more alignment @@ -61,8 +61,8 @@ bigger overall. Instruction prefixes are awfully expensive; avoid them if you can. (These include size and addressing prefixes, segment overrides, -**LOCK**, and the 0FH prefixes that extend the instruction set with -instructions such as **MOVSX**. The exceptions are conditional jumps, a +`LOCK`, and the 0FH prefixes that extend the instruction set with +instructions such as `MOVSX`. The exceptions are conditional jumps, a fast special case.) At a minimum, a prefix byte generally takes an extra cycle and shuts down the V-pipe for that cycle, effectively costing as much as two normal instructions (although prefix cycles can overlap with @@ -81,34 +81,34 @@ variables to longs when performing calculations.) Likewise, you should if possible avoid putting data in the code segment and referring to it with a CS: prefix, or otherwise using segment overrides. -**LOCK** is a particularly costly instruction, especially on +`LOCK` is a particularly costly instruction, especially on multiprocessor machines, because it locks the bus and requires that the hardware be brought into a synchronized state. The cost varies depending -on the processor and system, but **LOCK** can make an **INC [*mem*]** +on the processor and system, but `LOCK` can make an `INC [*mem*]` instruction (which normally takes 3 cycles) 5, 10, or more cycles -slower. Most programmers will never use **LOCK** on purpose—it's +slower. Most programmers will never use `LOCK` on purpose—it's primarily an operating system instruction—but there's a hidden gotcha -here because the **XCHG** instruction always locks the bus when used +here because the `XCHG` instruction always locks the bus when used with a memory operand. > ![](images/i.jpg) -> **XCHG** is a tempting instruction that's often used in assembly +> `XCHG` is a tempting instruction that's often used in assembly > language; for example, exchanging with video memory is a popular way to > read and write VGA memory in a single instruction—but it's now a bad -> idea. As it happens, on the 486 and Pentium, using **MOV**s to read and +> idea. As it happens, on the 486 and Pentium, using `MOV`s to read and > write memory is faster, anyway; and even on the 486, my measurements -> indicate a five-cycle tax for **LOCK** in general, and a nine-cycle -> execution time for **XCHG** with memory. Avoid **XCHG** with memory if +> indicate a five-cycle tax for `LOCK` in general, and a nine-cycle +> execution time for `XCHG` with memory. Avoid `XCHG` with memory if > you possibly can. -As with the 486, don't use **ENTER** or **LEAVE**, which are slower than -the equivalent discrete instructions. Also, start using **TEST -*reg,reg*** instead of **AND *reg,reg*** or **OR *reg,reg*** to test +As with the 486, don't use `ENTER` or `LEAVE`, which are slower than +the equivalent discrete instructions. Also, start using `TEST +*reg,reg*` instead of `AND *reg,reg*` or `OR *reg,reg*` to test whether a register is zero. The reason, as we'll see in Chapter 21, is -that **TEST**, unlike **AND** and **OR**, never modifies the target -register. Although in this particular case **AND** and **OR** don't +that `TEST`, unlike `AND` and `OR`, never modifies the target +register. Although in this particular case `AND` and `OR` don't modify the target register either, the Pentium has no way of knowing -that ahead of time, so if **AND** or **OR** goes through the U-pipe, the +that ahead of time, so if `AND` or `OR` goes through the U-pipe, the Pentium may have to shut down the V-pipe for a cycle to avoid potential -dependencies on the result of the **AND** or **OR**. **TEST** suffers +dependencies on the result of the `AND` or `OR`. `TEST` suffers from no such potential dependencies. diff --git a/19-04.md b/19-04.md index 01f974d..d45df78 100644 --- a/19-04.md +++ b/19-04.md @@ -74,10 +74,10 @@ on any given iteration. The Pentium has all the instructions of the 486, plus a few new ones. One much-needed instruction that has finally made it into the -instruction set is **CPUID**, which allows your code to determine what -processor it's running on. **CPUID** is 15 years late, but at least it's -finally here. Another new instruction is **CMPXCHG8B**, which does a -compare and conditional exchange on a qword. **CMPXCHG8B** doesn't seem +instruction set is `CPUID`, which allows your code to determine what +processor it's running on. `CPUID` is 15 years late, but at least it's +finally here. Another new instruction is `CMPXCHG8B`, which does a +compare and conditional exchange on a qword. `CMPXCHG8B` doesn't seem to me to be a particularly useful instruction, but I'm sure Intel wouldn't have added it without a reason; if you know of a use for it, please pass it along to me. diff --git a/20-01.md b/20-01.md index c635eec..3f6cc04 100644 --- a/20-01.md +++ b/20-01.md @@ -67,7 +67,7 @@ we'll see shortly, that doesn't mean that optimized Pentium code looks much like optimized 486 code, or that fast 486 code runs particularly well on a Pentium. (Fast Pentium code, on the other hand, does tend to run well on the 486; the only major downsides are that it's larger, and -that the **FXCH** instruction, which is largely free on the Pentium, is +that the `FXCH` instruction, which is largely free on the Pentium, is expensive on the 486.) So discard your x86 preconceptions as we delve into superscalar optimization for this one-of-a-kind processor. @@ -107,9 +107,9 @@ instructions and is always active, with the objective being to keep the V-pipe also working as much of the time as possible.) The U-pipe is generally similar to a full 486 in terms of both capabilities and instruction cycle counts. The V-pipe is a 486 subset, able to execute -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**. +simple instructions such as `MOV` and `ADD`, but unable to handle +`MUL, DIV`, string instructions, any sort of rotation or shift, or +even `ADC` or `SBB`. ![**Figure 20.1**  *The Pentium's two pipes.*](images/20-01.jpg) @@ -117,16 +117,16 @@ Getting two instructions executing simultaneously in the two pipes is trickier than it sounds, not only because the V-pipe can handle only a relatively small subset of the Pentium's instruction set, but also because those instructions that the V-pipe can handle are able to pair -only with certain U-pipe instructions. For example, **MOVSD** uses both -pipes, so no instruction can be executed in parallel with **MOVSD**. +only with certain U-pipe instructions. For example, `MOVSD` uses both +pipes, so no instruction can be executed in parallel with `MOVSD`. > ![](images/i.jpg) -> The use of both pipes does make **MOVSD** nearly twice as fast on the +> The use of both pipes does make `MOVSD` nearly twice as fast on the > Pentium as on the 486, but it's nonetheless slower than using equivalent > simpler instructions that allow for superscalar execution. Stick to the > Pentium's RISC-like instructions—the pairable instructions I'll discuss > next—when you're seeking maximum performance, with just a few exceptions -> such as **REP MOVS** and **REP STOS**. +> such as `REP MOVS` and `REP STOS`. Trickier yet, register contention can shut down the V-pipe on any given cycle, and Address Generation Interlocks (AGIs) can stall either pipe at diff --git a/20-02.md b/20-02.md index d0611d1..1520960 100644 --- a/20-02.md +++ b/20-02.md @@ -21,9 +21,9 @@ in Table 20.1 can go through the V-pipe. In addition, the V-pipe can execute a separate instruction only when one of the instructions listed in Table 20.2 is executing in the U-pipe; superscalar execution is not possible while any instruction not listed in Table 20.2 is executing in -the U-pipe. So, for example, if you use **SHR EDX,CL**, which takes 4 +the U-pipe. So, for example, if you use `SHR EDX,CL`, which takes 4 cycles to execute, no other instructions can execute during those 4 -cycles; if, on the other hand, you use **SHR EDX,10**, it will take 1 +cycles; if, on the other hand, you use `SHR EDX,10`, it will take 1 cycle to execute in the U-pipe, and another instruction can potentially execute concurrently in the V-pipe. (As you can see, similar instruction sequences can have vastly different performance characteristics on the @@ -93,13 +93,13 @@ same clock speed, even without Pentium-specific optimization, contrary to some reports. Besides, almost all operations can be performed by combinations of -pairable instructions. For example, **PUSH [*mem*]** is not on either -list, but both **MOV *reg*,[*mem*]** and **PUSH *reg*** are, and those +pairable instructions. For example, `PUSH [*mem*]` is not on either +list, but both `MOV *reg*,[*mem*]` and `PUSH *reg*` are, and those two instructions can be used to push a value stored in memory. In fact, given the proper instruction stream, the discrete instructions can perform this operation effectively in just 1 cycle (taking one-half of each of 2 cycles, for 2\*0.5 = 1 cycle total execution time), as shown -in Figure 20.3—a full cycle *faster* than **PUSH [*mem*]**, which takes +in Figure 20.3—a full cycle *faster* than `PUSH [*mem*]`, which takes 2 cycles. ```nasm @@ -189,7 +189,7 @@ mov [MemVar],edx The single complex instruction takes 3 cycles and is 6 bytes long; with proper sequencing, interleaving the simple instructions with other -instructions that don't use EDX or **Mem Var**, the three-instruction +instructions that don't use EDX or `Mem Var`, the three-instruction sequence can be reduced to 1.5 cycles, but it is *14* bytes long. > ![](images/i.jpg) diff --git a/20-03.md b/20-03.md index 4377dbb..65a82dd 100644 --- a/20-03.md +++ b/20-03.md @@ -12,18 +12,18 @@ pages: 390-393 ### Lockstep Execution {#Heading5} -You may wonder why anyone would bother breaking **ADD [MemVar],EAX** +You may wonder why anyone would bother breaking `ADD [MemVar],EAX` into three instructions, given that this instruction can go through either pipe with equal ease. The answer is that while the -memory-accessing instructions other than **MOV, PUSH**, and **POP** -listed in Table 20.1 (that is, **INC/DEC [*mem*], -ADD/SUB/XOR/AND/OR/CMP/ADC/SBB *reg*,[*mem*]**, and -**ADD/SUB/XOR/AND/OR/CMP/ADC/SBB [*mem*],*reg/immed***) can be paired, +memory-accessing instructions other than `MOV, PUSH`, and `POP` +listed in Table 20.1 (that is, `INC/DEC [*mem*], +ADD/SUB/XOR/AND/OR/CMP/ADC/SBB *reg*,[*mem*]`, and +`ADD/SUB/XOR/AND/OR/CMP/ADC/SBB [*mem*],*reg/immed*`) can be paired, they do not provide the 100 percent overlap that we seek. If you look at Tables 20.1 and 20.2, you will see that instructions taking from 1 to 3 cycles can pair. However, any pair of instructions goes through the two -pipes in lockstep. This means, for example, that if **ADD [EBX],EDX** is -going through the U-pipe, and **INC EAX** is going through the V-pipe, +pipes in lockstep. This means, for example, that if `ADD [EBX],EDX` is +going through the U-pipe, and `INC EAX` is going through the V-pipe, the V-pipe will be idle for 2 of the 3 cycles that the U-pipe takes to execute its instruction, as shown in Figure 20.4. Out of the theoretical 6 cycles of work that can be done during this time, we actually get only @@ -108,12 +108,12 @@ below. This is yet another example of how different Pentium optimization can be from everything we've learned about its predecessors. The problem with pairing non-single-cycle instructions arises when a -pipe executes an instruction other than **MOV** that has an explicit +pipe executes an instruction other than `MOV` that has an explicit memory operand. (I'll call these *complex memory instructions*. They're the only pairable instructions, other than branches, that take more than one cycle.) We've already seen that, because instructions go through the pipes in lockstep, if one pipe executes a complex memory instruction -such as **ADD EAX,[EBX]** while the other pipe executes a single-cycle +such as `ADD EAX,[EBX]` while the other pipe executes a single-cycle instruction, the pipe with the faster instruction will sit idle for part of the time, wasting cycles. You might think that if both pipes execute complex instructions of the same length, then neither would lie idle, diff --git a/20-04.md b/20-04.md index e762a9d..33c701d 100644 --- a/20-04.md +++ b/20-04.md @@ -10,7 +10,7 @@ chapter: '20' pages: 393-396 --- -However, this beneficial pairing does not extend to non-**MOV** +However, this beneficial pairing does not extend to non-`MOV` instructions with explicit memory destination operands, such as **ADD [EBX],EAX**. The Pentium executes only one such memory instruction at a time; if two memory-destination complex instructions get paired, first @@ -18,7 +18,7 @@ the U-pipe instruction is executed, and then the V-pipe instruction, with only one cycle of overlap, as shown in Figure 20.6. I don't know for sure, but I'd guess that this is to guarantee that the two pipes will never perform out-of-order access to any given memory location. -Thus, even though **AND [EBX],AL** pairs with **AND [ECX],DL**, the two +Thus, even though `AND [EBX],AL` pairs with `AND [ECX],DL`, the two 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. @@ -54,14 +54,14 @@ mov [ebx],dl ``` and be done with it. The problem here is one of dependency. Before the -Pentium can execute **AND DL,AL,**, it must first know what is in DL, +Pentium can execute `AND DL,AL,`, it must first know what is in DL, and it can't know that until it loads DL from the address pointed to by -EBX. Therefore, **AND DL,AL** can't happen until the cycle after **MOV -DL,[EBX]** executes. Likewise, the result can't be stored until the -cycle after **AND DL,AL** has finished. This means that these +EBX. Therefore, `AND DL,AL` can't happen until the cycle after `MOV +DL,[EBX]` executes. Likewise, the result can't be stored until the +cycle after `AND DL,AL` has finished. This means that these instructions, as written, can't possibly pair, so the sequence takes the -same three cycles as **AND [EBX],AL**. (Now it should be clear why **AND -[EBX]**, AL takes 3 cycles.) Consequently, it's necessary to interleave +same three cycles as `AND [EBX],AL`. (Now it should be clear why `AND +[EBX]`, AL takes 3 cycles.) Consequently, it's necessary to interleave these instructions with instructions that use other registers, so this set of operations can execute in one pipe while the other, unrelated set executes in the other pipe, as is done in Figure 20.7. @@ -110,7 +110,7 @@ Finally, bear in mind that if the instructions being executed have not already been executed at least once since they were loaded into the internal cache, they can pair only if the first (U-pipe) instruction is not only pairable but also exactly 1 byte long, a category that includes -only **INC *reg*, DEC *reg*, PUSH *reg***, and **POP *reg***. Knowing +only `INC *reg*, DEC *reg*, PUSH *reg*`, and `POP *reg*`. Knowing this can help you understand why sometimes, timing reveals that your code runs slower than it seems it should, although this will generally occur only when the cache working set for the code you're timing is on diff --git a/21-01.md b/21-01.md index 064d627..3c74b1f 100644 --- a/21-01.md +++ b/21-01.md @@ -114,13 +114,13 @@ mov edx,[ebp-8] ;V-pipe cycle 3 lockstep idle ;V-pipe cycle 4 mov edx,[ebp-8] ``` -This commonplace code loses a U-pipe cycle to the AGI caused by **AND -EBX,EBX**, followed by the attempt two instructions later to use EBX to +This commonplace code loses a U-pipe cycle to the AGI caused by `AND +EBX,EBX`, followed by the attempt two instructions later to use EBX to point to memory. The code loses a V-pipe cycle as well, because lockstep execution won't let the next V-pipe instruction execute until the paired U-pipe instruction that suffered the AGI finishes. The solution is to -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 +use `TEST EBX,EBX` instead of `AND; TEST` can't modify EBX, so no +AGI occurs. Sure, `AND EBX,EBX` doesn't modify EBX either, but the Pentium doesn't know that, so it has to insert the AGI. ![**Figure 21.2**  *An AGI can cost as many as 3 cycles.*](images/21-02.jpg) diff --git a/21-02.md b/21-02.md index f7d39ea..e3d9b7b 100644 --- a/21-02.md +++ b/21-02.md @@ -11,7 +11,7 @@ pages: 402-405 --- As on the 486, you should keep a careful eye out for AGIs involving the -stack pointer. Implicit modifiers of ESP, such as **PUSH** and **POP**, +stack pointer. Implicit modifiers of ESP, such as `PUSH` and `POP`, are special-cased so you don't have to worry about AGIs. However, if you explicitly modify ESP with this instruction @@ -32,7 +32,7 @@ either explicitly with instructions like this one moveax,[esp+20h] ``` -or via **PUSH**, **POP**, or other instructions that implicitly use ESP +or via `PUSH`, `POP`, or other instructions that implicitly use ESP as an addressing register. On the 486, any instruction that had both a constant value and an @@ -44,7 +44,7 @@ mov dword ptr [ebp+16],1 suffered a 1-cycle penalty, taking a total of 2 cycles. Such instructions take only one cycle on the Pentium, but they cannot pair, -so they're still the most expensive sort of **MOV**. Knowing this can +so they're still the most expensive sort of `MOV`. Knowing this can speed up something as simple as zeroing two memory variables, as in ```nasm @@ -67,7 +67,7 @@ mov [MemVar2],0 ;U-pipe 2 Note, however, that my experiments thus far indicate that the two writes in the first case don't actually pair (possibly because the memory variables have never been read into the internal cache), so you might -want to insert an instruction between the two **MOV**s—and, of course, +want to insert an instruction between the two `MOV`s—and, of course, this is yet another reason why you should always measure your code's actual performance. @@ -85,9 +85,9 @@ inc eax ;U-pipe cycle 1 and ebx,eax ;U-pipe cycle 2 ``` -in a single cycle; **AND EBX,EAX** can't execute until the value in EAX -is known, and that can't happen until **INC EAX** is done. Consequently, -the V-pipe idles while **INC EAX** executes in the U-pipe. We saw this +in a single cycle; `AND EBX,EAX` can't execute until the value in EAX +is known, and that can't happen until `INC EAX` is done. Consequently, +the V-pipe idles while `INC EAX` executes in the U-pipe. We saw this in the last chapter when we discussed splitting instructions into simple instructions, and it is by far the most common sort of register contention, known as read-after-write register contention. @@ -109,8 +109,8 @@ mov al,[Var] ;U-pipe cycle 2 where an attempt is made to set both EAX and its AL subregister on the same cycle. Write-after-write contention implies that the two -instructions comprising the above substitute for **MOVZX** should have -at least one unrelated instruction between them when **SUB EAX,EAX** +instructions comprising the above substitute for `MOVZX` should have +at least one unrelated instruction between them when `SUB EAX,EAX` executes in the V-pipe. #### Exceptions to Register Contention {#Heading5} @@ -129,10 +129,10 @@ are free of charge. Also, stack-related instructions that modify ESP only implicitly (without ESP as part of any explicit operand) do not cause AGIs, and neither do they cause register contention with other instructions that -use ESP only implicitly; such instructions include **PUSH *reg/immed*, -POP *reg***, and **CALL**. (However, these instructions do cause +use ESP only implicitly; such instructions include `PUSH *reg/immed*, +POP *reg*`, and `CALL`. (However, these instructions do cause register contention on ESP—but not AGIs—with instructions that use ESP -explicitly, such as **MOV EAX,[ESP+4]**.) Without this special case, the +explicitly, such as `MOV EAX,[ESP+4]`.) Without this special case, the following sequence would hardly use the V-pipe at all: ```nasm @@ -167,7 +167,7 @@ predicted by the Pentium's branch prediction circuitry (as discussed in the last chapter), it executes in a single cycle, pairing if it runs in the V-pipe; if mispredicted, conditional jumps take 4 cycles in the U-pipe and 5 cycles in the V-pipe, and mispredicted calls and -unconditional jumps take 3 cycles in either pipe. Note that **RET** +unconditional jumps take 3 cycles in either pipe. Note that `RET` can't pair. ### Who's in First? {#Heading6} diff --git a/21-03.md b/21-03.md index 47181ff..5f358a0 100644 --- a/21-03.md +++ b/21-03.md @@ -19,7 +19,7 @@ the U-pipe, as do all instructions after conditional branches that fall through. Instructions with prefix bytes are generally good U-pipe markers, although they're expensive instructions that should be avoided whenever possible, and have at least one aberration with regard to pipe -usage, as discussed below. Shifts, rotates, **ADC, SBB**, and all other +usage, as discussed below. Shifts, rotates, `ADC, SBB`, and all other instructions not listed in Table 20.1 in the last chapter are likewise U-pipe markers. @@ -75,11 +75,11 @@ Wrong, wrong, wrong! As detailed in Listing 21.1, this loop should take 6 cycles per checksummed word in 32-bit protected mode, a ridiculously high number for the Pentium. (You'll see why I say "should take," not "takes," shortly.) We should lose 2 cycles in each pipe to the two size -prefixes (because the **ADD**s are 16-bit operations in a 32-bit +prefixes (because the `ADD`s are 16-bit operations in a 32-bit segment), and another 2 cycles because of register contention that -arises when **ADC AX,0** has to wait for the result of **ADD AX,[ESI]**. -Then, too, even though **DEC** and **JNZ** can pair and the branch -prediction for **JNZ** is presumably correct virtually all the time, +arises when `ADC AX,0` has to wait for the result of `ADD AX,[ESI]`. +Then, too, even though `DEC` and `JNZ` can pair and the branch +prediction for `JNZ` is presumably correct virtually all the time, they do take a full cycle, and maybe we can do something about that as well. @@ -87,11 +87,11 @@ The first thing to do is to time the code in Listing 21.1 to verify our analysis. When I unleashed the Zen timer on Listing 21.1, I found, to my surprise, that the code actually takes only five cycles per checksum word processed, not six. A little more experimentation revealed that -adding a size prefix to the two-cycle **ADD EAX,[ESI]** instruction +adding a size prefix to the two-cycle `ADD EAX,[ESI]` instruction doesn't cost anything, certainly not the one full cycle in each pipe that a prefix is supposed to take. More experimentation showed that prefix bytes do cost the documented extra cycle when used with one-cycle -instructions such as **MOV**. At this point, my preliminary conclusion +instructions such as `MOV`. At this point, my preliminary conclusion is that prefixes can pair with the first cycle of at least some multiple-cycle instructions. Determining exactly why this happens will take further research on my part, but the most important conclusion is diff --git a/21-04.md b/21-04.md index 4133635..5736f45 100644 --- a/21-04.md +++ b/21-04.md @@ -10,19 +10,19 @@ chapter: '21' pages: 407-409 --- -The first, obvious thing we can do to Listing 21.1 is change **ADC -AX,0** to **ADC EAX,0**, eliminating a prefix byte and saving a full -cycle. Now we're down from five to four cycles. What next? +The first, obvious thing we can do to Listing 21.1 is change `ADC AX,0` to +`ADC EAX,0`, eliminating a prefix byte and saving a full cycle. Now we're +down from five to four cycles. What next? Listing 21.2 shows one interesting alternative that doesn't really buy us anything. Here, we've eliminated all size prefixes by doing -byte-sized **MOVs** and **ADDs**, but because the size prefix on **ADD -AX,[ESI]**, for whatever reason, didn't cost anything in Listing 21.1, +byte-sized `MOVs` and `ADDs`, but because the size prefix on `ADD +AX,[ESI]`, for whatever reason, didn't cost anything in Listing 21.1, our efforts are to no avail—Listing 21.2 still takes 4 cycles per checksummed word. What's worth noting about Listing 21.2 is the extent to which the code is broken into simple instructions and reordered so as to avoid size prefixes, register contention, AGIs, and data bank -conflicts (the latter because both **[ESI]** and **[ESI+1]** are in the +conflicts (the latter because both `[ESI]` and `[ESI+1]` are in the same cache data bank, as discussed in the last chapter). **LISTING 21.2 L21-2.ASM** @@ -66,7 +66,7 @@ instructions than 486-optimized code. Again, note the careful mixing of byte-sized reads to avoid AGIs, register contention, and cache bank collisions, in particular the way in which the byte reads of memory are interspersed with the additions to avoid register contention, and the -placement of **ADD ESI,4** to avoid an AGI. +placement of `ADD ESI,4` to avoid an AGI. **LISTING 21.3 L21-3.ASM** diff --git a/21-05.md b/21-05.md index 8c8f514..aef574f 100644 --- a/21-05.md +++ b/21-05.md @@ -109,10 +109,10 @@ significant difference in overall system performance. I don't claim that Listing 21.5 is the fastest possible way to do a TCP/IP checksum on a Pentium; in fact, it isn't. Unrolling the loop one -more time, together with a trick of Terje's that uses **LEA** to advance -ESI (neither **LEA** nor **DEC** affects the carry flag, allowing Terje +more time, together with a trick of Terje's that uses `LEA` to advance +ESI (neither `LEA` nor `DEC` affects the carry flag, allowing Terje to add the carry from the previous loop iteration into the next -iteration's checksum via **ADC**), produces a version that's a full 33 +iteration's checksum via `ADC`), produces a version that's a full 33 percent faster. Nonetheless, Listings 21.1 through 21.5 illustrate many of the techniques and considerations in Pentium optimization. Hand-optimization for the Pentium isn't simple, and requires careful diff --git a/22-01.md b/22-01.md index 564b708..c45542a 100644 --- a/22-01.md +++ b/22-01.md @@ -92,25 +92,25 @@ Bye:mov sp,bp ;restore original stack pointer ClearS endp ``` -The first thing you'll notice about Listing 22.1 is that **ClearS** uses -a **REP STOSW** instruction. That means that we're not going to improve +The first thing you'll notice about Listing 22.1 is that `ClearS` uses +a `REP STOSW` instruction. That means that we're not going to improve performance by any great amount, no matter how clever we are. While we -can eliminate some cycles, the bulk of the work in **ClearS** is done by +can eliminate some cycles, the bulk of the work in `ClearS` is done by that one repeated string instruction, and there's no way to improve on that. Does that mean that Listing 22.1 is as good as it can be? Hardly. While -the speed of **ClearS** is very good, there's another side to the -optimization equation: size. The whole of **ClearS** is 52 bytes long as +the speed of `ClearS` is very good, there's another side to the +optimization equation: size. The whole of `ClearS` is 52 bytes long as it stands—but, as we'll see, that size is hardly set in stone. -Where do we begin with **ClearS**? For starters, there's an instruction -in there that serves no earthly purpose—**MOV SP,BP**. SP is guaranteed +Where do we begin with `ClearS`? For starters, there's an instruction +in there that serves no earthly purpose—`MOV SP,BP`. SP is guaranteed to be equal to BP at that point anyway, so why reload it with the same value? Removing that instruction saves us two bytes. Well, that was certainly easy enough! We're not going to find any more -totally non-functional instructions in **ClearS**, however, so let's get +totally non-functional instructions in `ClearS`, however, so let's get on to some serious optimizing. We'll look first for cases where we know of better instructions for particular tasks than those that were chosen. For example, there's no need to load any register, whether segment or diff --git a/22-02.md b/22-02.md index 00112be..c35536c 100644 --- a/22-02.md +++ b/22-02.md @@ -36,15 +36,15 @@ Bye: ClearS endp ``` -(The **OnStack** structure definition doesn't change in any of our +(The `OnStack` structure definition doesn't change in any of our examples, so I'm not going clutter up this chapter by reproducing it for -each new version of **ClearS**.) +each new version of `ClearS`.) Okay, loading ES and DI directly saves another four bytes. We've -squeezed a total of 6 bytes—about 11 percent—out of **ClearS**. What +squeezed a total of 6 bytes—about 11 percent—out of `ClearS`. What next? -Well, **LES** would serve better than two **MOV** instructions for +Well, `LES` would serve better than two `MOV` instructions for loading ES and DI as shown in Listing 22.3. **LISTING 22.3 L22-3.ASM** @@ -77,7 +77,7 @@ That's good for another three bytes. We're down to 43 bytes, and counting. We can save 3 more bytes by clearing the low and high bytes of AX and -BX, respectively, by using **SUB *reg8,reg8*** rather than ANDing 16-bit +BX, respectively, by using `SUB *reg8,reg8*` rather than ANDing 16-bit values as shown in Listing 22.4. **LISTING 22.4 L22-4.ASM** diff --git a/22-03.md b/22-03.md index 5fb12f0..43f093b 100644 --- a/22-03.md +++ b/22-03.md @@ -37,7 +37,7 @@ pack both the attribute and the fill value into the same word, but that's not part of the specification for this particular routine.) Another nifty instruction-rearrangement trick saves 6 more bytes. -**ClearS** checks to see whether the far pointer is null (zero) at the +`ClearS` checks to see whether the far pointer is null (zero) at the start of the routine...then loads and uses that same far pointer later on. Let's get that pointer into registers and keep it there; that way we can check to see whether it's null with a single comparison, and can use @@ -72,11 +72,11 @@ Realistically, how much smaller can we make this code? About one-third smaller yet, as it turns out—but in order to do that, we must stretch our minds and use the 8088's instructions in unusual ways. Let me ask you this: What do most of the instructions in the current -version of **ClearS** do? +version of `ClearS` do? They either load parameters from the stack frame or set up the registers so that the parameters can be accessed. Mind you, there's nothing wrong -with the stack-frame-oriented instructions used in **ClearS**; those +with the stack-frame-oriented instructions used in `ClearS`; those instructions access the stack frame in a highly efficient way, exactly as the designers of the 8088 intended, and just as the code generated by a high-level language would. That means that we aren't going to be able @@ -90,12 +90,12 @@ stack...*the stack*...THE STACK! Ye gods! That's easy—we can use the *stack pointer* to address the stack rather than BP. While it's true that the stack pointer can't be used for *mod-reg-rm* addressing, as BP can, it *can* be used to pop data off the -stack—and **POP** is a one-byte instruction. Instructions don't get any +stack—and `POP` is a one-byte instruction. Instructions don't get any shorter than that. There is one detail to be taken care of before we can put our plan into action: The return address—the address of the calling code—is on top of -the stack, so the parameters we want can't be reached with **POP**. +the stack, so the parameters we want can't be reached with `POP`. That's easily solved, however—we'll just pop the return address into an unused register, then branch through that register when we're done, as we learned to do in Chapter 14. As we pop the parameters, we'll also be @@ -103,7 +103,7 @@ removing them from the stack, thereby neatly avoiding the need to discard them when it's time to return. With that problem dealt with, Listing 22.7 shows the Zenned version of -**ClearS**. +`ClearS`. **LISTING 22.7 L22-7.ASM** @@ -126,13 +126,13 @@ Bye: ClearS endp ``` -At long last, we're down to the bare metal. This version of **ClearS** +At long last, we're down to the bare metal. This version of `ClearS` is just 19 bytes long. That's just 37 percent as long as the original version, *without any change whatsoever in the functionality that -**ClearS** makes available to the calling code*. The code is bound to +`ClearS` makes available to the calling code*. The code is bound to run a bit faster too, given that there are far fewer instruction bytes and fewer memory accesses. -All in all, the Zenned version of **ClearS** is a vast improvement over +All in all, the Zenned version of `ClearS` is a vast improvement over the original. Probably not the best possible implementation—*never say never!*—but an awfully good one. diff --git a/23-02.md b/23-02.md index 9965a9e..1d94e68 100644 --- a/23-02.md +++ b/23-02.md @@ -45,8 +45,8 @@ The ports used to control the VGA are shown in Table 23.1. The CRTC, SC, and GC Data registers are located at the addresses of their respective Index registers plus one. However, the AC Index and Data registers are located at the same address, 3C0H. The function of this port toggles on -every **OUT** to 3C0H, and resets to Index mode (in which the Index -register is programmed by the next **OUT** to 3C0H) on every read from +every `OUT` to 3C0H, and resets to Index mode (in which the Index +register is programmed by the next `OUT` to 3C0H) on every read from the Input Status 1 register (3DAH when the VGA is in a color mode, | Register | Address | @@ -76,27 +76,27 @@ addressing of the now-vanished Color/Graphics Adapter and Monochrome Display Adapter. The method used in the VGA BIOS to set registers is to point DX to the -desired Index register, load AL with the index, perform a byte **OUT**, +desired Index register, load AL with the index, perform a byte `OUT`, increment DX to point to the Data register (except in the case of the AC, where DX remains the same), load AL with the desired data, and -perform a byte **OUT**. A handy shortcut is to point DX to the desired +perform a byte `OUT`. A handy shortcut is to point DX to the desired Index register, load AL with the index, load AH with the data, and -perform a word **OUT**. Since the high byte of the **OUT** value goes to +perform a word `OUT`. Since the high byte of the `OUT` value goes to port DX+1, this is equivalent to the first method but is faster. However, this technique does not work for programming the AC Index and Data registers; both AC registers are addressed at 3C0H, so two separate -byte **OUT**s must be used to program the AC. (Actually, word **OUT**s +byte `OUT`s must be used to program the AC. (Actually, word `OUT`s to the AC do work in the EGA, but not in the VGA, so they shouldn't be used.) As mentioned above, you must be sure which mode—Index or Data—the -AC is in before you do an **OUT** to 3C0H; you can read the Input Status +AC is in before you do an `OUT` to 3C0H; you can read the Input Status 1 register at any time to force the AC to Index mode. -How safe is the word-**OUT** method of addressing VGA registers? I have, +How safe is the word-`OUT` method of addressing VGA registers? I have, in the past, run into adapter/computer combinations that had trouble -with word **OUT**s; however, all such problems I am aware of have been +with word `OUT`s; however, all such problems I am aware of have been fixed. Moreover, a great deal of graphics software now uses word -**OUT**s, so any computer or VGA that doesn't properly support word -**OUT**s could scarcely be considered a clone at all. +`OUT`s, so any computer or VGA that doesn't properly support word +`OUT`s could scarcely be considered a clone at all. > ![](images/i.jpg) > A speed tip: The setting of each chip's Index register remains the same @@ -120,11 +120,11 @@ fixed. Moreover, a great deal of graphics software now uses word > INC DX ;point to GC Data register> > > and then the Bit Mask register could be set repeatedly with the -> byte-size **OUT** instruction +> byte-size `OUT` instruction > > OUT DX,AL ;AL contains Bit Mask register setting > -> which is generally faster (and never slower) than a word-sized **OUT**, +> which is generally faster (and never slower) than a word-sized `OUT`, > and which does not require AH to be set, freeing up a register. Of > course, this method only works if the GC Index register remains > unchanged throughout the loop. \ No newline at end of file diff --git a/23-05.md b/23-05.md index 1eef014..9380238 100644 --- a/23-05.md +++ b/23-05.md @@ -48,21 +48,21 @@ simultaneously. The images are written to a nondisplayed portion of VGA memory in order to take advantage of a useful VGA hardware feature, the ability to copy all four planes at once. As shown by the image-loading code discussed -above, four different sets of reads and writes—and several **OUT**s as +above, four different sets of reads and writes—and several `OUT`s as well—are required to copy a multicolored image into VGA memory as would be needed to draw the same image into a non-planar pixel buffer. This causes unacceptably slow performance, all the more so because the wait states that occur on accesses to VGA memory make it very desirable to -minimize display memory accesses, and because **OUT**s tend to be very +minimize display memory accesses, and because `OUT`s tend to be very slow. The solution is to take advantage of the VGA's write mode 1, which is selected via bits 0 and 1 of the GC Mode register (GC register 5). (Be careful to preserve bits 2-7 when setting bits 0 and 1, as is done in -Listing 23.1.) In write mode 1, a single **CPU** read loads the +Listing 23.1.) In write mode 1, a single `CPU` read loads the addressed byte from all four planes into the VGA's four internal -latches, and a single **CPU** write writes the contents of the latches -to the four planes. During the write, the byte written by the **CPU** is +latches, and a single `CPU` write writes the contents of the latches +to the four planes. During the write, the byte written by the `CPU` is irrelevant. The sample program uses write mode 1 to copy the images that were diff --git a/23-06.md b/23-06.md index 983d661..70560e9 100644 --- a/23-06.md +++ b/23-06.md @@ -62,8 +62,8 @@ require dozens or hundreds of images. The tradeoffs between virtual page size, page flipping, and image storage must always be kept in mind when designing programs for the VGA. -To see the program run in 640x200 16-color mode, comment out the **EQU** -line for **MEDRES\_VIDEO\_MODE**. +To see the program run in 640x200 16-color mode, comment out the `EQU` +line for `MEDRES_VIDEO_MODE`. ### The Hazards of VGA Clones {#Heading10} diff --git a/24-01.md b/24-01.md index 5f8364d..eedccab 100644 --- a/24-01.md +++ b/24-01.md @@ -43,7 +43,7 @@ writes the result to that plane. This arrangement allows four display memory bytes to be modified by a single CPU write (which must often be preceded by a single CPU read, as we will see). The benefit is vastly improved performance; if the CPU had to select each of the four planes -in turn via **OUT**s and perform the four logical operations itself, VGA +in turn via `OUT`s and perform the four logical operations itself, VGA performance would slow to a crawl. Figure 24.1 is a simplified depiction of data flow around the ALUs. Each diff --git a/24-03.md b/24-03.md index 5bdba8c..685f0d9 100644 --- a/24-03.md +++ b/24-03.md @@ -70,7 +70,7 @@ only because it is necessary to perform a read to load the latches, and there is no way to read without placing a value in a register. This is a bit of a nuisance, since it means that the value of some 8-bit register must be destroyed. Under certain circumstances, a single logical -instruction such as **XOR** or **AND** can be used to perform both the +instruction such as `XOR` or `AND` can be used to perform both the read to load the latches and then write to modify display memory without affecting any CPU registers, as we'll see later on. @@ -84,7 +84,7 @@ display mode. The great virtue of the BIOS write string function in the case of the VGA is that it provides an uncomplicated way to get text on the screen reliably in any mode and color, over any background. -The expression used to load DX in the **TEXT\_UP** macro in the sample +The expression used to load DX in the `TEXT_UP` macro in the sample program may seem strange, but it's a convenient way to save a byte of program code and a few cycles of execution time. DX is being loaded with a word value that's composed of two independent immediate byte values. @@ -96,8 +96,8 @@ MOV DH,VALUE2 ``` which requires four instruction bytes. By shifting the value destined for the high byte into the high byte with MASM's shift-left operator, -**SHL** (\*100H would work also), and then logically combining the -values with MASM's **OR** operator (or the **ADD** operator), both +`SHL` (\*100H would work also), and then logically combining the +values with MASM's `OR` operator (or the `ADD` operator), both halves of DX can be loaded with a single instruction, as in ```nasm @@ -106,7 +106,7 @@ MOV DX,(VALUE2 SHL 8) OR VALUE1 which takes only three bytes and is faster, being a single instruction. (Note, though, that in 32-bit protected mode, there's a size and -performance penalty for 16-bit instructions such as the **MOV** above; +performance penalty for 16-bit instructions such as the `MOV` above; see the first part of this book for details.) As shown, a macro is an ideal place to use this technique; the macro invocation can refer to two separate byte values, making matters easier for the programmer, while @@ -114,21 +114,21 @@ the macro itself can combine the values into a single word-sized constant. > ![](images/i.jpg) -> A minor optimization tip illustrated in the listing is the use of **INC -> AX** and **DEC AX** in the **DrawVerticalBox** subroutine when only AL +> A minor optimization tip illustrated in the listing is the use of `INC +> AX` and `DEC AX` in the `DrawVerticalBox` subroutine when only AL > actually needs to be modified. Word-sized register increment and > decrement instructions (or dword-sized instructions in 32-bit protected > mode) are only one byte long, while byte-size register increment and > decrement instructions are two bytes long. Consequently, when size > counts, it is worth using a whole 16-bit (or 32-bit) register instead of -> the low 8 bits of that register for **INC** and **DEC**—if you don't +> the low 8 bits of that register for `INC` and `DEC`—if you don't > need the upper portion of the register for any other purpose, or if you -> can be sure that the **INC** or **DEC** won't affect the upper part of +> can be sure that the `INC` or `DEC` won't affect the upper part of > the register. The latches and ALUs are central to high-performance VGA code, since they allow programs to process across all four memory planes without a -series of **OUT**s and read/write operations. It is not always easy to +series of `OUT`s and read/write operations. It is not always easy to arrange a program to exploit this power, however, because the ALUs are far more limited than a CPU. In many instances, however, additional hardware in the VGA, including the bit mask, the set/reset features, and diff --git a/25-01.md b/25-01.md index b647731..a457ca2 100644 --- a/25-01.md +++ b/25-01.md @@ -43,14 +43,14 @@ unmodified, AND, OR, and XOR) that we looked at in the last chapter. The barrel shifter is powerful, but (as sometimes happens in this business) it sounds more useful than it really is. This is because the GC can only rotate CPU data, a task that the CPU itself is perfectly -capable of performing. Two **OUT**s are needed to select a given +capable of performing. Two `OUT`s are needed to select a given rotation: one to set the GC Index register, and one to set the Data Rotate register. However, with careful programming it's sometimes possible to leave the GC Index always pointing to the Data Rotate -register, so only one **OUT** is needed. Even so, it's often easier +register, so only one `OUT` is needed. Even so, it's often easier and/or faster to simply have the CPU rotate the data of interest CL times than to set the Data Rotate register. (Bear in mind that a single -**OUT** takes from 11 to 31 cycles on a 486—and longer if the VGA is +`OUT` takes from 11 to 31 cycles on a 486—and longer if the VGA is sluggish at responding to OUTs, as many VGAs are.) If only the VGA could rotate *latched* data, then there would be all sorts of useful applications for rotation, but, sadly, only CPU data can be rotated. diff --git a/25-06.md b/25-06.md index d864e4c..68631e3 100644 --- a/25-06.md +++ b/25-06.md @@ -46,17 +46,17 @@ discussed in the next chapter. ### A Brief Note on Word OUTs {#Heading9} In the early days of the EGA and VGA, there was considerable debate -about whether it was safe to do word **OUT**s (**OUT DX,AX**) to set +about whether it was safe to do word `OUT`s (`OUT DX,AX`) to set Index/Data register pairs in a single instruction. Long ago, there were a few computers with buses that weren't quite PC-compatatible, in that -the two bytes in each word **OUT** went to the VGA in the wrong order: +the two bytes in each word `OUT` went to the VGA in the wrong order: Data register first, then Index register, with predictably disastrous results. Consequently, I generally wrote my code in those days to use -two 8-bit **OUT**s to set indexed registers. Later on, I made it a habit -to use macros that could do either one 16-bit **OUT** or two 8-bit -**OUT**s, depending on how I chose to assemble the code, and in fact -you'll find both ways of dealing with **OUT**s sprinkled through the +two 8-bit `OUT`s to set indexed registers. Later on, I made it a habit +to use macros that could do either one 16-bit `OUT` or two 8-bit +`OUT`s, depending on how I chose to assemble the code, and in fact +you'll find both ways of dealing with `OUT`s sprinkled through the code in this part of the book. Using macros for word OUTs is still not a bad idea in that it does no harm, but in my opinion it's no longer -necessary. Word **OUT**s are standard now, and it's been a long time +necessary. Word `OUT`s are standard now, and it's been a long time since I've heard of them causing any problems. diff --git a/26-01.md b/26-01.md index 73a59b2..f979946 100644 --- a/26-01.md +++ b/26-01.md @@ -37,7 +37,7 @@ Write mode 3 is strange indeed, and its use is not immediately obvious. The first time I encountered write mode 3, I understood immediately how it functioned, but could think of very few useful applications for it. As time passed, and as I came to understand the atrocious performance -characteristics of **OUT** instructions, and the importance of text and +characteristics of `OUT` instructions, and the importance of text and pattern drawing as well, write mode 3 grew considerably in my estimation. In fact, my esteem for this mode ultimately reached the point where in the last major chunk of 16-color graphics code I wrote, @@ -57,7 +57,7 @@ first.) That's what write mode 3 does—but what is it *for?* It turns out that write mode 3 is excellent for a surprisingly large number of purposes, because it makes it possible to avoid the bane of VGA performance, -**OUT**s. Some uses for write mode 3 include lines, circles, and solid +`OUT`s. Some uses for write mode 3 include lines, circles, and solid and two-color pattern fills. Most importantly, write mode 3 is ideal for transparent text; that is, it makes it possible to draw text in 16-color graphics mode quickly without wiping out the background in the process. diff --git a/26-03.md b/26-03.md index 509d257..e5435fa 100644 --- a/26-03.md +++ b/26-03.md @@ -20,7 +20,7 @@ without rotation characters could only be drawn on byte boundaries. > ![](images/i.jpg) > As I pointed out in Chapter 25, the CPU is perfectly capable of rotating > the data itself, and it's often the case that that's more efficient. The -> problem with using the Data Rotate register is that the **OUT** that +> problem with using the Data Rotate register is that the `OUT` that > sets that register is time-consuming, especially for proportional text, > which requires a different rotation for each character. Also, if the > code performs full-byte accesses to display memory—that is, if it @@ -68,7 +68,7 @@ character to the left of the byte boundary) and then draw the left portions only of all 40 characters in write mode 3. Then the bit mask could be set up for the right portion of each character, and the right portions of all 40 characters could be drawn. The VGA's fast rotator -would be used to do all rotation, and the only **OUT**s required would +would be used to do all rotation, and the only `OUT`s required would be those required to set the bit mask and data rotation. This technique could well outperform single-character bit-mapped text drivers such as the one in Listing 26.1 by a significant margin. Listing 26.2 @@ -425,7 +425,7 @@ by ANDing in software. Even more significantly, we would have the CPU combine adjacent characters into complete, rotated bytes whenever possible, so that only one drawing operation would be required per byte of display memory modified. By doing this, we would eliminate all -per-character **OUT**s, and would minimize display memory accesses, +per-character `OUT`s, and would minimize display memory accesses, approximately doubling text-drawing speed. As a final note, consider that non-transparent text could also be diff --git a/27-03.md b/27-03.md index 7a42738..0460571 100644 --- a/27-03.md +++ b/27-03.md @@ -33,10 +33,10 @@ Write mode 2 is ideal because in this application color can vary from one pixel to the next, and in write mode 2 all that's required to set pixel color is a change of the lower nibble of the byte written by the CPU. Set/reset could be used to achieve the same result, but an -index/data pair of **OUT**s would be required to set the Set/Reset +index/data pair of `OUT`s would be required to set the Set/Reset register to each new color. Similarly, the Map Mask register could be used in write mode 0 to set pixel color, but in this case not only would -an index/data pair of **OUT**s be required but there would also be no +an index/data pair of `OUT`s be required but there would also be no guarantee that data already in display memory wouldn't interfere with the color of the pixel being drawn, since the Map Mask register allows only selected planes to be drawn to. diff --git a/27-04.md b/27-04.md index fd2e7e8..6b29064 100644 --- a/27-04.md +++ b/27-04.md @@ -21,7 +21,7 @@ Set/reset tends to be superior when many pixels in succession are drawn in the same color, since with set/reset enabled for all planes the Set/Reset register provides the color data and as a result the CPU is free to draw whatever byte value it wishes. For example, the CPU can -execute an **OR** instruction to display memory when set/reset is +execute an `OR` instruction to display memory when set/reset is enabled for all planes, thus both loading the latches and writing the color value with a single instruction, secure in the knowledge that the value it writes is ignored in favor of the set/reset color. diff --git a/28-03.md b/28-03.md index 03e0f0d..9ef9305 100644 --- a/28-03.md +++ b/28-03.md @@ -41,7 +41,7 @@ pixels. That's a lot of programming. The code is also likely to run slowly, all the more so because a standard IBM VGA takes an average of 1.1 microseconds to complete each memory read, and read mode 0 requires four reads in order to read the four planes, not to mention the even -greater amount of time taken by the **OUT**s required to switch between +greater amount of time taken by the `OUT`s required to switch between the planes. (1.1 microseconds may not sound like much, but on a 66-MHz 486, it's 73 clock cycles! Local-bus VGAs can be a good deal faster, but a read from the fastest local-bus adapter I've yet seen would still cost diff --git a/28-04.md b/28-04.md index fe7e186..ffaa347 100644 --- a/28-04.md +++ b/28-04.md @@ -226,6 +226,6 @@ instructions—a read followed by a write—are used to perform this task. The code in Listing 28.2 uses this approach. Suppose, however, that you've set the VGA to read mode 1, with the Color Don't Care register set to 0 (meaning all reads of VGA memory will return 0FFH). Under these -circumstances, you can use a single **AND** instruction to both read and +circumstances, you can use a single `AND` instruction to both read and write VGA memory, since ANDing any value with 0FFH leaves that value unchanged. diff --git a/28-05.md b/28-05.md index 07b3787..263cc2d 100644 --- a/28-05.md +++ b/28-05.md @@ -14,12 +14,12 @@ Listing 28.3 illustrates an efficient use of write mode 3 in conjunction with read mode 1 and a Color Don't Care register setting of 0. The mask in AL is passed directly to the VGA's bit mask (that's how write mode 3 works—see Chapter 4 for details). Because the VGA always returns 0FFH, -the single **AND** instruction loads the latches, and writes the value +the single `AND` instruction loads the latches, and writes the value in AL, unmodified, to the VGA, where it is used to generate the bit mask. This is more compact and register-efficient than using separate instructions to read and write, although it is not necessarily faster by -cycle count, because on a 486 or a Pentium **MOV** is a 1-cycle -instruction, but **AND** with memory is a 3-cycle instruction. However, +cycle count, because on a 486 or a Pentium `MOV` is a 1-cycle +instruction, but `AND` with memory is a 3-cycle instruction. However, given display memory wait states, it is often the case that the two approaches run at the same speed, and the register that the above approach frees up can frequently be used to save one or more cycles in diff --git a/29-04.md b/29-04.md index 56be7dd..6bc382e 100644 --- a/29-04.md +++ b/29-04.md @@ -22,7 +22,7 @@ the EGA BIOS provides us with video function 10H, which supports setting either any one palette register or all 16 palette registers (and the overscan register as well) with a single video interrupt. -Video function 10H is invoked by performing an **INT** 10H with AH set +Video function 10H is invoked by performing an `INT` 10H with AH set to 10H. If AL is 0 (subfunction 0), then BL contains the number of the palette register to set, and BH contains the value to set that register to. If AL is 1 (subfunction 1), then BH contains the value to set the diff --git a/29-06.md b/29-06.md index 345dd32..2af7559 100644 --- a/29-06.md +++ b/29-06.md @@ -59,11 +59,11 @@ register, but it's safer. It's also slower; for cases where you must set a field repeatedly, it might be worthwhile to read and mask the register once at the start, and save it in a variable, so that the value is readily available in memory and need not be repeatedly read from the -port. This approach is especially attractive because **IN**s are much +port. This approach is especially attractive because `IN`s are much slower than memory accesses on 386 and 486 machines. Astute readers may wonder why I didn't put a delay sequence, such as -**JMP \$+2**, between the **IN** and **OUT** involving the same +`JMP \$+2`, between the `IN` and `OUT` involving the same register. There are, after all, guidelines from IBM, specifying that a certain period should be allowed to elapse before a second access to an I/O port is attempted, because not all devices can respond as rapidly as diff --git a/30-03.md b/30-03.md index 847844e..7ec801a 100644 --- a/30-03.md +++ b/30-03.md @@ -12,14 +12,14 @@ pages: 572-574 #### VGA and EGA Split-Screen Operation Don't Mix {#Heading5} -You must set the **IS\_VGA** equate at the start of Listing 30.1 +You must set the `IS_VGA` equate at the start of Listing 30.1 correctly for the adapter the code will run on in order for the program to perform properly. This equate determines how the upper bits of the -split screen start scan line are set by **SetSplitScreenRow**. If -**IS\_VGA** is 0 (specifying an EGA target), then bit 8 of the split +split screen start scan line are set by `SetSplitScreenRow`. If +`IS_VGA` is 0 (specifying an EGA target), then bit 8 of the split screen start scan line is set by programming the entire Overflow register to 1FH; this is hard-wired for the 350-scan-line modes of the -EGA. If **IS\_VGA** is 1 (specifying a VGA target), then bits 8 and 9 of +EGA. If `IS_VGA` is 1 (specifying a VGA target), then bits 8 and 9 of the split screen start scan line are set by reading the registers they reside in, changing only the split-screen-related bits, and writing the modified settings back to their respective registers. diff --git a/30-04.md b/30-04.md index bea9e4f..f419cab 100644 --- a/30-04.md +++ b/30-04.md @@ -69,7 +69,7 @@ key press, Listing 30.2 sets bit 5 of the Mode Control register and pans the normal screen again. This time, the split screen doesn't budge an inch—*if* the code is running on a VGA. -By the way, if **IS\_VGA** is set to 0 in Listing 30.2, the program will +By the way, if `IS_VGA` is set to 0 in Listing 30.2, the program will assemble in a form that will run on the EGA and *only* the EGA. Pel panning suppression in the split screen won't work in this version, however, because the EGA lacks the capability to support that feature. diff --git a/31-02.md b/31-02.md index e3e5e72..c9a0f0a 100644 --- a/31-02.md +++ b/31-02.md @@ -63,7 +63,7 @@ a straightforward planar model of memory. That means taking the VGA out of chain 4 mode and doubleword mode, turning off the double display of each scan line, making sure chain mode, odd/even mode, and word mode are turned off, and selecting byte mode for video data display. All that's -done in the **Set320x400Mode** subroutine in Listing 31.1, which we'll +done in the `Set320x400Mode` subroutine in Listing 31.1, which we'll discuss next. #### Reading and Writing Pixels {#Heading6} diff --git a/31-04.md b/31-04.md index c2a6187..65e2a3a 100644 --- a/31-04.md +++ b/31-04.md @@ -11,15 +11,15 @@ pages: 599-600 --- The interesting aspects of Listing 31.1 are three. First, the -**Set320x400Mode** subroutine selects 320x400 256-color mode. This is +`Set320x400Mode` subroutine selects 320x400 256-color mode. This is accomplished by performing a mode 13H mode set followed by then putting -the VGA into standard planar byte mode. **Set320x400Mode** zeros display +the VGA into standard planar byte mode. `Set320x400Mode` zeros display memory as well. It's necessary to clear display memory even after a mode 13H mode set because the mode 13H mode set clears only the 64K of display memory that can be accessed in that mode, leaving 192K of display memory untouched. -The second interesting aspect of Listing 31.1 is the **WritePixel** +The second interesting aspect of Listing 31.1 is the `WritePixel` subroutine, which draws a colored pixel at any *x,y* addressable location on the screen. Although it may not be obvious because I've optimized the code a little, the process of drawing a pixel is @@ -44,14 +44,14 @@ which is equivalent to: The pixel's color is then written to the addressed byte in the addressed plane. That's all there is to it! -The third item of interest in Listing 31.1 is the **ReadPixel** -subroutine. **ReadPixel** is virtually identical to **WritePixel**, save -that in **ReadPixel** the Read Map register is programmed with a plane -number, while **WritePixel** uses a plane *mask* to set the Map Mask +The third item of interest in Listing 31.1 is the `ReadPixel` +subroutine. `ReadPixel` is virtually identical to `WritePixel`, save +that in `ReadPixel` the Read Map register is programmed with a plane +number, while `WritePixel` uses a plane *mask* to set the Map Mask register. Of course, that difference merely reflects a fundamental difference in the operation of the two registers. (If that's Greek to you, refer back to Chapters 23-30 for a refresher on VGA programming.) -**ReadPixel** isn't used in Listing 31.1, but I've included it because, +`ReadPixel` isn't used in Listing 31.1, but I've included it because, as I said above, the read and write pixel functions together can support a whole host of more complex graphics functions. @@ -81,12 +81,12 @@ graphics function. A fast line draw for 320x400 256-color mode would be simple (although not as fast as would be possible in mode 13H). Fast image copies could be implemented by copying one-quarter of the image to one plane, one-quarter to the next plane, and so on for all four planes, -thereby eliminating the **OUT** per pixel that sequential processing +thereby eliminating the `OUT` per pixel that sequential processing requires. If you're really into performance, you could store your images with all the bytes for plane 0 grouped together, followed by all the -bytes for plane 1, and so on. That would allow a single **REP MOVS** +bytes for plane 1, and so on. That would allow a single `REP MOVS` instruction to copy all the bytes for a given plane, with just four -**REP MOVS** instructions copying the whole image. In a number of cases, +`REP MOVS` instructions copying the whole image. In a number of cases, in fact, 320x400 256-color mode can actually be much faster than mode 13H, because the VGA's hardware can be used to draw four or even eight pixels with a single access; I'll return to the topic of @@ -96,7 +96,7 @@ high-performance programming in 256-color modes other than mode 13H It's all a bit complicated, but as I say, you should be able to design an adequately fast—and often *very* fast—version for 320x400 mode of whatever graphics function you need. If you're not all that concerned -with speed, **WritePixel** and **ReadPixel** should meet your needs. +with speed, `WritePixel` and `ReadPixel` should meet your needs. ### Two 256-Color Pages {#Heading7} @@ -108,7 +108,7 @@ memory, and is—unsurprisingly—displayed by setting the start address to 8000H.) Finally, Listing 31.2 draws vertical color bars in page 0 and flips back to page 0 when another key is pressed. -The color bar routines don't use the **WritePixel** subroutine from +The color bar routines don't use the `WritePixel` subroutine from Listing 31.1; they go straight to display memory instead for improved speed. As I mentioned above, better speed yet could be achieved by a color-bar algorithm that draws all the pixels in plane 0, then all the diff --git a/31-05.md b/31-05.md index feb50d0..fe561a0 100644 --- a/31-05.md +++ b/31-05.md @@ -283,9 +283,9 @@ The displays produced by Listing 31.2 make it clear that 320x400 ### Something to Think About {#Heading8} You can, if you wish, use the display memory organization of 320x400 -mode in 320x200 mode by modifying **Set320x400Mode** to leave the +mode in 320x200 mode by modifying `Set320x400Mode` to leave the maximum scan line setting at 1 in the mode set. (The version of -**Set320x400Mode** in Listings 31.1 and 31.2 forces the maximum scan +`Set320x400Mode` in Listings 31.1 and 31.2 forces the maximum scan line to 0, doubling the effective resolution of the screen.) Why would you want to do that? For one thing, you could then choose from not two but *four* 320x200 256-color display pages, starting at offsets 0, diff --git a/32-01.md b/32-01.md index 5a61156..5ca3a53 100644 --- a/32-01.md +++ b/32-01.md @@ -108,9 +108,9 @@ after which we'll look at how it works. I suspect that once you see what this mode looks like, you'll be more than eager to learn how to use it. Listing 32.1 contains three C-callable assembly functions. As you would -expect, **Set360x480Mode** places the VGA into 360x480 256mode. -**Draw360x480Dot** draws a pixel of the specified color at the specified -location. Finally, **Read360x480Dot** returns the color of the pixel at +expect, `Set360x480Mode` places the VGA into 360x480 256mode. +`Draw360x480Dot` draws a pixel of the specified color at the specified +location. Finally, `Read360x480Dot` returns the color of the pixel at the specified location. (This last function isn't actually used in the example program in this chapter, but is included for completeness.) diff --git a/32-05.md b/32-05.md index c8f8e11..2b2609b 100644 --- a/32-05.md +++ b/32-05.md @@ -42,7 +42,7 @@ registers controlling the total number of characters per scan line, the number of characters displayed, the horizontal sync pulse, horizontal blanking, the offset from the start of one line to the start of the next, and the clock speed all have to be altered in order to set up -360x480 256-color mode. The function **Set360x480Mode** in Listing 32.1 +360x480 256-color mode. The function `Set360x480Mode` in Listing 32.1 does all that, and sets up the registers that control vertical resolution, as well. @@ -59,7 +59,7 @@ already know how to draw in 360x480 256-color mode; the conversion between the two is a simple matter of changing the working screen width from 320 pixels to 360 pixels. In fact, if you were to take the 320x400 256-color pixel reading and pixel writing code from Chapter 31 and -change the **SCREEN\_WIDTH** equate from 320 to 360, those routines +change the `SCREEN_WIDTH` equate from 320 to 360, those routines would work perfectly in 360x480 256-color mode. The organization of display memory in 360x480 256-color mode is almost diff --git a/33-03.md b/33-03.md index 76b6067..059d5a7 100644 --- a/33-03.md +++ b/33-03.md @@ -106,15 +106,15 @@ an uneasy feeling, so I'd be most interested in hearing from any readers. A final point is that the process of loading both the palette RAM and -DAC registers involves performing multiple **OUT**s to the same +DAC registers involves performing multiple `OUT`s to the same register. Many people whose opinions I respect recommend delaying -between I/O accesses to the same port by performing a **JMP \$+2** +between I/O accesses to the same port by performing a `JMP \$+2` (jumping flushes the prefetch queue and forces a memory access—or at least a cache access—to fetch the next instruction byte). In fact, some -people recommend two **JMP \$+2** instructions between I/O accesses to +people recommend two `JMP \$+2` instructions between I/O accesses to the same port, and *three* jumps between I/O accesses to the same port -that go in opposite directions (**OUT** followed by **IN** or **IN** -followed by **OUT**). This is clearly necessary when accessing some +that go in opposite directions (`OUT` followed by `IN` or `IN` +followed by `OUT`). This is clearly necessary when accessing some motherboard chips, but I don't know how applicable it is when accessing VGAs, so make of it what you will. Input from knowledgeable readers is eagerly solicited. diff --git a/34-01.md b/34-01.md index 0fe6fd2..2ae11ea 100644 --- a/34-01.md +++ b/34-01.md @@ -82,12 +82,12 @@ least reliable and capable in that mode, as we'll see next. Here's the problem with loading the entire DAC repeatedly: The DAC contains 256 color storage locations, each loaded via either 3 or 4 -**OUT** instructions (more on that next), so at least 768 **OUT**s are -needed to load the entire DAC. That many **OUT**s take a considerable -amount of time, all the more so because **OUT**s are painfully slow on +`OUT` instructions (more on that next), so at least 768 `OUT`s are +needed to load the entire DAC. That many `OUT`s take a considerable +amount of time, all the more so because `OUT`s are painfully slow on 486s and Pentiums, and because the DAC is frequently on the ISA bus (although VLB and PCI are increasingly common), where wait states are -inserted in fast computers. In an 8 MHz AT, 768 **OUT**s alone would +inserted in fast computers. In an 8 MHz AT, 768 `OUT`s alone would take 288 microseconds, and the data loading and looping that are also required would take in the ballpark of 1,800 microseconds more, for a minimum of 2 milliseconds total. diff --git a/34-02.md b/34-02.md index e6fc5c0..8d7ab96 100644 --- a/34-02.md +++ b/34-02.md @@ -16,7 +16,7 @@ The DAC can be loaded either directly or through subfunctions 10H (for a single DAC register) or 12H (for a block of DAC registers) of the BIOS video service interrupt 10H, function 10H, described in Chapter 33. For cycling the contents of the entire DAC, the block-load function (invoked -by executing **INT** 10H with AH = 10H and AL = 12H to load a block of +by executing `INT` 10H with AH = 10H and AL = 12H to load a block of CX DAC locations, starting at location BX, from the block of RGB triplets—3 bytes per triplet—starting at ES:DX into the DAC) would be the better of the two, due to the considerably greater efficiency of @@ -31,7 +31,7 @@ they have problems. Serious problems. The difficulty is this: IBM's BIOS specification describes exactly how the parameters passed to the BIOS control the loading of DAC locations, and all clone BIOSes meet that specification scrupulously, which is to -say that if you invoke **INT** 10H, function 10H, subfunction 12H with a +say that if you invoke `INT` 10H, function 10H, subfunction 12H with a given set of parameters, you can be sure that you will end up with the same values loaded into the same DAC locations on all VGAs from all vendors. IBM's spec does *not*, however, describe whether vertical @@ -85,19 +85,19 @@ we'll see next. #### Loading the DAC Directly {#Heading6} So we must load the DAC directly in order to perform color cycling. The -DAC is loaded directly by sending (with an **OUT** instruction) the +DAC is loaded directly by sending (with an `OUT` instruction) the number of the DAC location to be loaded to the DAC Write Index register -at 3C8H and then performing three **OUT**s to write an RGB triplet to +at 3C8H and then performing three `OUT`s to write an RGB triplet to the DAC Data register at 3C9H. This approach must be repeated 256 times -to load the entire DAC, requiring over a thousand **OUT**s in all. +to load the entire DAC, requiring over a thousand `OUT`s in all. There is another, somewhat faster approach, but one that has its risks. After an RGB triplet is written to the DAC Data register, the DAC Write Index register automatically increments to point to the next DAC location, and this repeats indefinitely as successive RGB triplets are written to the DAC. By taking advantage of this feature, the entire DAC -can be loaded with just 769 **OUT**s: one **OUT** to the DAC Write Index -register and 768 **OUT**s to the DAC Data register. +can be loaded with just 769 `OUT`s: one `OUT` to the DAC Write Index +register and 768 `OUT`s to the DAC Data register. So what's the drawback? Well, imagine that as you're loading the DAC, an interrupt-driven TSR (such as a program switcher or multitasker) diff --git a/34-03.md b/34-03.md index 77cc1c0..a0fbb1b 100644 --- a/34-03.md +++ b/34-03.md @@ -23,10 +23,10 @@ the screen. The attribute of each vertical line is one greater than that of the preceding line, so there's a smooth gradient of attributes from left to right. Once everything is set up, the program starts cycling the colors stored in however many DAC locations are specified by the -**CYCLE\_SIZE** equate; as many as all 256 DAC locations can be cycled. -(Actually, **CYCLE\_SIZE**-1 locations are cycled, because location 0 is +`CYCLE_SIZE` equate; as many as all 256 DAC locations can be cycled. +(Actually, `CYCLE_SIZE`-1 locations are cycled, because location 0 is kept constant in order to keep the background and border colors from -changing, but **CYCLE\_SIZE** locations are *loaded*, and it's the +changing, but `CYCLE_SIZE` locations are *loaded*, and it's the number of locations we can load without problems that we're interested in.) diff --git a/34-04.md b/34-04.md index c4fdab3..7b21e3d 100644 --- a/34-04.md +++ b/34-04.md @@ -15,19 +15,19 @@ directly? With interrupts enabled or disabled? *Et cetera?* However you like, actually. Four equates at the top of Listing 34.1 select the sort of color cycling performed; by changing these equates -and **CYCLE\_SIZE**, you can get a feel for how well various approaches +and `CYCLE_SIZE`, you can get a feel for how well various approaches to color cycling work with whatever combination of computer system and VGA you care to test. -The **USE\_BIOS** equate is simple. Set **USE\_BIOS** to 1 to load the +The `USE_BIOS` equate is simple. Set `USE_BIOS` to 1 to load the DAC through the block-load-DAC BIOS function, or to 0 to load the DAC -directly with **OUT**s. +directly with `OUT`s. -If **USE\_BIOS** is 1, the only other equate of interest is -**WAIT\_VSYNC**. If **WAIT\_VSYNC** is 1, the program waits for the -leading edge of vertical sync before loading the DAC; if **WAIT\_VSYNC** +If `USE_BIOS` is 1, the only other equate of interest is +`WAIT_VSYNC`. If `WAIT_VSYNC` is 1, the program waits for the +leading edge of vertical sync before loading the DAC; if `WAIT_VSYNC` is 0, the program doesn't wait before loading. The effect of setting or -not setting **WAIT\_VSYNC** depends on whether the BIOS of the VGA the +not setting `WAIT_VSYNC` depends on whether the BIOS of the VGA the program is running on waits for vertical sync before loading the DAC. You may end up with a double wait, causing color cycling to proceed at half speed, you may end up with no wait at all, causing cycling to occur @@ -35,42 +35,42 @@ far too rapidly (and almost certainly with hideous on-screen effects), or you may actually end up cycling at the proper one-cycle-per-frame rate. -If **USE\_BIOS** is 0, **WAIT\_VSYNC** still applies. However, you will -always want to set **WAIT\_VSYNC** to 1 when **USE\_BIOS** is 0; +If `USE_BIOS` is 0, `WAIT_VSYNC` still applies. However, you will +always want to set `WAIT_VSYNC` to 1 when `USE_BIOS` is 0; otherwise, cycling will occur much too fast, and a good deal of continuous on-screen garbage is likely to make itself evident as the program loads the DAC non-stop. -If **USE\_BIOS** is 0, **GUARD\_AGAINST\_INTS** determines whether the +If `USE_BIOS` is 0, `GUARD_AGAINST_INTS` determines whether the possibility of the DAC loading process being interrupted is guarded against by disabling interrupts and setting the write index once for every location loaded and whether the DAC's autoincrementing feature is relied upon or not. -If **GUARD\_AGAINST\_INTS** is 1, the following sequence is followed for +If `GUARD_AGAINST_INTS` is 1, the following sequence is followed for the loading of each DAC location in turn: Interrupts are disabled, the DAC Write Index register is set appropriately, the RGB triplet for the location is written to the DAC Data register, and interrupts are enabled. This is the slow but safe approach described earlier. -Matters get still more interesting if **GUARD\_AGAINST\_INTS** is 0. In -that case, if **NOT\_8088** is 0, then an autoincrementing load is +Matters get still more interesting if `GUARD_AGAINST_INTS` is 0. In +that case, if `NOT_8088` is 0, then an autoincrementing load is performed in a straightforward fashion; the DAC Write Index register is set to the index of the first location to load and the RGB triplet is -sent to the DAC by way of three **LODSB/OUT DX,AL** pairs, with **LOOP** +sent to the DAC by way of three `LODSB/OUT DX,AL` pairs, with `LOOP` repeating the process for each of the locations in turn. -If, however, **NOT\_8088** is 1, indicating that the processor is a 286 -or better (perhaps **AT\_LEAST\_286** would have been a better name), +If, however, `NOT_8088` is 1, indicating that the processor is a 286 +or better (perhaps `AT_LEAST_286` would have been a better name), then after the initial DAC Write Index value is set, all 768 DAC -locations are loaded with a single **REP OUTSB**. This is clearly the +locations are loaded with a single `REP OUTSB`. This is clearly the fastest approach, but it runs the risk, albeit remote, that the loading sequence will be interrupted and the DAC registers will become garbled. My own experience with Listing 34.1 indicates that it is sometimes possible to load all 256 locations cleanly but sometimes it is not; it all depends on the processor, the bus speed, the VGA, and the DAC, as -well as whether autoincrementation and **REP OUTSB** are used. I'm not +well as whether autoincrementation and `REP OUTSB` are used. I'm not going to bother to report how many DAC locations I *could* successfully load with each of the various approaches, for the simple reason that I don't have enough data points to make reliable suggestions, and I don't @@ -93,7 +93,7 @@ First of all, I'd like to point out that when color cycling does work, it's a thing of beauty. Assemble Listing 34.1 so that it doesn't use the BIOS to load the DAC, doesn't guard against interrupts, and uses 286-specific instructions if your computer supports them. Then tinker -with **CYCLE\_SIZE** until the color cycling is perfectly clean on your +with `CYCLE_SIZE` until the color cycling is perfectly clean on your computer. Color cycling looks stunningly smooth, doesn't it? And this is crude color cycling, working with the default color set; switch over to a color set that gradually works its way through various hues and diff --git a/34-05.md b/34-05.md index d7e2bf4..4728759 100644 --- a/34-05.md +++ b/34-05.md @@ -97,13 +97,13 @@ loading the DAC does, so it should ideally be performed during vertical blanking. The DAC can also be read by way of the BIOS in either of two ways. -**INT** 10H, function 10H (AH=10H), subfunction 15H (AL=15H) reads out a +`INT` 10H, function 10H (AH=10H), subfunction 15H (AL=15H) reads out a single DAC location, specified by BX; this function returns the RGB triplet stored in the specified location with the red component in the lower 6 bits of DH, the green component in the lower 6 bits of CH, and the blue component in the lower 6 bits of CL. -**INT** 10H, function 10H (AH=10H), subfunction 17H (AL=17H) reads out a +`INT` 10H, function 10H (AH=10H), subfunction 17H (AL=17H) reads out a block of DAC locations of length CX, starting with the location specified by BX. ES:DX must point to the buffer in which the RGB values from the specified block of DAC locations are to be stored. The form of @@ -114,7 +114,7 @@ load a block of registers. Listing 34.1 illustrates reading the DAC both through the BIOS block-read function and directly, with the direct-read code capable of conditionally assembling to either guard against interrupts or not and -to use **REP INSB** or not. As you can see, reading the DAC settings is +to use `REP INSB` or not. As you can see, reading the DAC settings is very much symmetric with setting the DAC. #### Cycling Down {#Heading12} diff --git a/35-03.md b/35-03.md index af95b66..566db0a 100644 --- a/35-03.md +++ b/35-03.md @@ -14,8 +14,8 @@ pages: 661-664 It's time to get down and look at some actual working code. Listing 35.1 is a C implementation of Bresenham's line-drawing algorithm for modes -0EH, 0FH, 10H, and 12H of the VGA, called as function **EVGALine**. -Listing 35.2 is a sample program to demonstrate the use of **EVGALine**. +0EH, 0FH, 10H, and 12H of the VGA, called as function `EVGALine`. +Listing 35.2 is a sample program to demonstrate the use of `EVGALine`. **LISTING 35.1 L35-1.C** diff --git a/35-04.md b/35-04.md index 6d78720..b07a4ec 100644 --- a/35-04.md +++ b/35-04.md @@ -100,20 +100,20 @@ void main() #### Looking at EVGALine {#Heading7} -The **EVGALine** function itself performs four operations. **EVGALine** +The `EVGALine` function itself performs four operations. `EVGALine` first sets up the VGA's hardware so that all pixels drawn will be in the desired color. This is accomplished by setting two of the VGA's registers, the Enable Set/Reset register and the Set/Reset register. Setting the Enable Set/Reset to the value 0FH, as is done in -**EVGALine**, causes all drawing to produce pixels in the color +`EVGALine`, causes all drawing to produce pixels in the color contained in the Set/Reset register. Setting the Set/Reset register to the passed color, in conjunction with the Enable Set/Reset setting of -0FH, causes all drawing done by **EVGALine** and the functions it calls +0FH, causes all drawing done by `EVGALine` and the functions it calls to generate the passed color. In summary, setting up the Enable Set/Reset and Set/Reset registers in this way causes the remainder of -**EVGALine** to draw a line in the specified color. +`EVGALine` to draw a line in the specified color. -**EVGALine** next performs a simple check to cut in half the number of +`EVGALine` next performs a simple check to cut in half the number of line orientations that must be handled separately. Figure 35.4 shows the eight possible line orientations among which a Bresenham's algorithm implementation must distinguish. (In interpreting Figure 35.4, assume @@ -128,16 +128,16 @@ start to the line end. > ![](images/i.jpg) > A moment of thought will show, however, that four of the line > orientations are redundant. Each of the four orientations for which -> **DeltaY**, the Y component of the line, is less than 0 (that is, for +> `DeltaY`, the Y component of the line, is less than 0 (that is, for > which the line start Y coordinate is greater than the line end Y > coordinate) can be transformed into one of the four orientations for > which the line start Y coordinate is less than the line end Y coordinate > simply by reversing the line start and end coordinates, so that the line -> is drawn in the other direction. **EVGALine** does this by swapping +> is drawn in the other direction. `EVGALine` does this by swapping > (X0,Y0) (the line start coordinates) with (X1,Y1) (the line end > coordinates) whenever Y0 is greater than Y1. -This accomplished, **EVGALine** must still distinguish among the four +This accomplished, `EVGALine` must still distinguish among the four remaining line orientations. Those four orientations form two major categories, orientations for which the X dimension is the major axis of the line and orientations for which the Y dimension is the major axis. @@ -146,10 +146,10 @@ finish) and 2 (where X decreases from start to finish) fall into the latter category, and differ in only one respect, the direction in which the X coordinate moves when it changes. Handling of the running error of the line is exactly the same for both cases, as one would expect given -the symmetry of lines differing only in the sign of **DeltaX**, the X -coordinate of the line. Consequently, for those cases where **DeltaX** +the symmetry of lines differing only in the sign of `DeltaX`, the X +coordinate of the line. Consequently, for those cases where `DeltaX` is less than zero, the direction of X movement is made negative, and the -absolute value of **DeltaX** is used for error term calculations. +absolute value of `DeltaX` is used for error term calculations. Similarly, octants 0 (where X increases from start to finish) and 3 (where X decreases from start to finish) differ only in the direction in diff --git a/35-05.md b/35-05.md index 9729ff9..5907738 100644 --- a/35-05.md +++ b/35-05.md @@ -10,34 +10,34 @@ chapter: '35' pages: 667-670 --- -There is one line-drawing function for octants 0 and 3, **Octant0**, and -one line-drawing function for octants 1 and 2, **Octant1**. A single -function with **if** statements could certainly be used to handle all +There is one line-drawing function for octants 0 and 3, `Octant0`, and +one line-drawing function for octants 1 and 2, `Octant1`. A single +function with `if` statements could certainly be used to handle all four octants, but at a significant performance cost. There is, on the other hand, very little performance cost to grouping octants 0 and 3 together and octants 1 and 2 together, since the two octants in each pair differ only in the direction of change of the X coordinate. -**EVGALine** determines which line-drawing function to call and with +`EVGALine` determines which line-drawing function to call and with what value for the direction of change of the X coordinate based on two -criteria: whether **DeltaX** is negative or not, and whether the -absolute value of **DeltaX** (|**DeltaX**|) is less than **DeltaY** or -not, as shown in Figure 35.5. Recall that the value of **DeltaY**, and +criteria: whether `DeltaX` is negative or not, and whether the +absolute value of `DeltaX` (|`DeltaX`|) is less than `DeltaY` or +not, as shown in Figure 35.5. Recall that the value of `DeltaY`, and hence the direction of change of the Y coordinate, is guaranteed to be non-negative as a result of the earlier elimination of four of the line orientations. After calling the appropriate function to draw the line (more on those -functions shortly), **EVGALine** restores the state of the Enable +functions shortly), `EVGALine` restores the state of the Enable Set/Reset register to its default of zero. In this state, the Set/Reset register has no effect, so it is not necessary to restore the state of -the Set/Reset register as well. **EVGALine** also restores the state of +the Set/Reset register as well. `EVGALine` also restores the state of the Bit Mask register (which, as we will see, is modified by -**EVGADot**, the pixel-drawing routine actually used to draw each pixel -of the lines produced by **EVGALine**) to its default of 0FFH. While it -would be more modular to have **EVGADot** restore the state of the Bit +`EVGADot`, the pixel-drawing routine actually used to draw each pixel +of the lines produced by `EVGALine`) to its default of 0FFH. While it +would be more modular to have `EVGADot` restore the state of the Bit Mask register after drawing each pixel, it would also be considerably -slower to do so. The same could be said of having **EVGADot** set the +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. @@ -45,57 +45,57 @@ modularity would improve, speed would suffer markedly. #### Drawing Each Line {#Heading8} -The **Octant0** and **Octant1** functions draw lines for which -|**DeltaX**| is greater than **DeltaY** and lines for which |**DeltaX**| -is less than or equal to **DeltaY**, respectively. The parameters to -**Octant0** and **Octant1** are the starting point of the line, the -length of the line in each dimension, and **XDirection**, the amount by -which the X coordinate should be changed when it moves. **XDirection** +The `Octant0` and `Octant1` functions draw lines for which +|`DeltaX`| is greater than `DeltaY` and lines for which |`DeltaX`| +is less than or equal to `DeltaY`, respectively. The parameters to +`Octant0` and `Octant1` are the starting point of the line, the +length of the line in each dimension, and `XDirection`, the amount by +which the X coordinate should be changed when it moves. `XDirection` must be either 1 (to draw toward the right edge of the screen) or -1 (to draw toward the left edge of the screen). No value is required for the -amount by which the Y coordinate should be changed; since **DeltaY** is +amount by which the Y coordinate should be changed; since `DeltaY` is guaranteed to be positive, the Y coordinate always changes by 1 pixel. -**Octant0** draws lines for which |**DeltaX**| is greater than -**DeltaY**. For such lines, the X coordinate of each pixel drawn differs +`Octant0` draws lines for which |`DeltaX`| is greater than +`DeltaY`. For such lines, the X coordinate of each pixel drawn differs from the previous pixel by either 1 or -1, depending on the value of -**XDirection**. (This makes it possible for **Octant0** to draw lines in -both octant 0 and octant 3.) Whenever **ErrorTerm** becomes +`XDirection`. (This makes it possible for `Octant0` to draw lines in +both octant 0 and octant 3.) Whenever `ErrorTerm` becomes non-negative, indicating that the next Y coordinate is a better approximation of the line being drawn, the Y coordinate is increased by 1. -**Octant1** draws lines for which |**DeltaX**| is less than or equal to +`Octant1` draws lines for which |`DeltaX`| is less than or equal to DeltaY. For these lines, the Y coordinate of each pixel drawn is 1 greater than the Y coordinate of the previous pixel. Whenever -**ErrorTerm** becomes non-negative, indicating that the next X +`ErrorTerm` becomes non-negative, indicating that the next X coordinate is a better approximation of the line being drawn, the X coordinate is advanced by either 1 or -1, depending on the value of -**XDirection**. (This makes it possible for **Octant1** to draw lines in +`XDirection`. (This makes it possible for `Octant1` to draw lines in both octant 1 and octant 2.) #### Drawing Each Pixel {#Heading9} -At the core of **Octant0** and **Octant1** is a pixel-drawing function, -**EVGADot**. **EVGADot** draws a pixel at the specified coordinates in +At the core of `Octant0` and `Octant1` is a pixel-drawing function, +`EVGADot`. `EVGADot` draws a pixel at the specified coordinates in whatever color the hardware of the VGA happens to be set up for. As -described earlier, since the entire line drawn by **EVGALine** is of the +described earlier, since the entire line drawn by `EVGALine` is of the same color, line-drawing performance is improved by setting the VGA's -hardware up once in **EVGALine** before the line is drawn, and then -drawing all the pixels in the line in the same color via **EVGADot**. +hardware up once in `EVGALine` before the line is drawn, and then +drawing all the pixels in the line in the same color via `EVGADot`. -**EVGADot** makes certain assumptions about the screen. First, it +`EVGADot` makes certain assumptions about the screen. First, it assumes that the address of the byte controlling the pixels at the start of a given row on the screen is 80 bytes after the start of the row -immediately above it. In other words, this implementation of **EVGADot** +immediately above it. In other words, this implementation of `EVGADot` only works for screens configured to be 80 bytes wide. Since this is the -standard configuration of all of the modes **EVGALine** is designed to +standard configuration of all of the modes `EVGALine` is designed to work in, the assumption of 80 bytes per row should be no problem. If it -is a problem, however, **EVGADot** could easily be modified to retrieve +is a problem, however, `EVGADot` could easily be modified to retrieve the BIOS integer variable at address 0040:004A, which contains the number of bytes per row for the current video mode. -Second, **EVGADot** assumes that screen memory is organized as a linear +Second, `EVGADot` assumes that screen memory is organized as a linear bitmap starting at address A000:0000, with the pixel at the upper left of the screen controlled by bit 7 of the byte at offset 0, the next pixel to the right controlled by bit 6, the ninth pixel controlled by @@ -110,12 +110,12 @@ entirely. As explained later, however, it's not the value that's ORed that matters, given the way we've set up the VGA's hardware; it's the act of ORing itself, and the value 0FEH forces the compiler to perform the OR operation.) Again, this is the normal way in which modes 0EH, -0FH, 10H, and 12H operate. As described earlier, **EVGADot** also +0FH, 10H, and 12H operate. As described earlier, `EVGADot` also assumes that the VGA is set up so that each pixel drawn in the above-mentioned manner will be drawn in the correct color. -Given those assumptions, **EVGADot** becomes a surprisingly simple -function. First, **EVGADot** builds a far pointer that points to the +Given those assumptions, `EVGADot` becomes a surprisingly simple +function. First, `EVGADot` builds a far pointer that points to the byte of display memory controlling the pixel to be drawn. Second, a mask is generated consisting of zeros for all bits except the bit controlling the pixel to be drawn. Third, the Bit Mask register is set to that mask, @@ -127,10 +127,10 @@ to be drawn. ORing with 0FEH first reads display memory, thereby loading the VGA's internal latches with the contents of the display memory byte controlling the pixel to be drawn, and then writes to display memory with the value 0FEH. Because of the unusual way in which the VGA's data -paths work and the way in which **EVGALine** sets up the VGA's Enable +paths work and the way in which `EVGALine` sets up the VGA's Enable Set/Reset and Set/Reset registers, the value that is written by the -**OR** instruction is ignored. Instead, the value that actually gets -placed in display memory is the color that was passed to **EVGALine** +`OR` instruction is ignored. Instead, the value that actually gets +placed in display memory is the color that was passed to `EVGALine` and placed in the Set/Reset register. The Bit Mask register, which was set up in step three above, allows only the single bit controlling the pixel to be drawn to be set to this color value. For more on the various diff --git a/35-06.md b/35-06.md index 24eb384..af2dfad 100644 --- a/35-06.md +++ b/35-06.md @@ -11,44 +11,44 @@ pages: 670-671 --- The result of all this is simply a single pixel drawn in the color set -up in **EVGALine**. **EVGADot** may seem excessively complex for a +up in `EVGALine`. `EVGADot` may seem excessively complex for a function that does nothing more that draw one pixel, but programming the VGA isn't trivial (as we've seen in the early chapters of this part). -Besides, while the explanation of **EVGADot** is lengthy, the code +Besides, while the explanation of `EVGADot` is lengthy, the code itself is only five lines long. -Line drawing would be somewhat faster if the code of **EVGADot** were -made an inline part of **Octant0** and **Octant1**, thereby saving the +Line drawing would be somewhat faster if the code of `EVGADot` were +made an inline part of `Octant0` and `Octant1`, thereby saving the overhead of preparing parameters and calling the function. Feel free to -do this if you wish; I maintained **EVGADot** as a separate function for +do this if you wish; I maintained `EVGADot` as a separate function for clarity and for ease of inserting a pixel-drawing function for a different graphics adapter, should that be desired. If you do install a pixel-drawing function for a different adapter, or a fundamentally different mode such as a 256-color SuperVGA mode, remember to remove the -hardware-dependent **outportb** lines in **EVGALine** itself. +hardware-dependent `outportb` lines in `EVGALine` itself. ### Comments on the C Implementation {#Heading10} -**EVGALine** does no error checking whatsoever. My assumption in writing -**EVGALine** was that it would be ultimately used as the lowest-level +`EVGALine` does no error checking whatsoever. My assumption in writing +`EVGALine` was that it would be ultimately used as the lowest-level primitive of a graphics software package, with operations such as error checking and clipping performed at a higher level. Similarly, -**EVGALine** is tied to the VGA's screen coordinate system of (0,0) to +`EVGALine` is tied to the VGA's screen coordinate system of (0,0) to (639,199) (in mode 0EH), (0,0) to (639,349) (in modes 0FH and 10H), or (0,0) to (639,479) (in mode 12H), with the upper left corner considered to be (0,0). Again, transformation from any coordinate system to the -coordinate system used by **EVGALine** can be performed at a higher -level. **EVGALine** is specifically designed to do one thing: draw lines +coordinate system used by `EVGALine` can be performed at a higher +level. `EVGALine` is specifically designed to do one thing: draw lines into the display memory of the VGA. Additional functionality can be -supplied by the code that calls **EVGALine**. +supplied by the code that calls `EVGALine`. -The version of **EVGALine** shown in Listing 35.1 is reasonably fast, -but it is not as fast as it might be. Inclusion of **EVGADot** directly -into **Octant0** and **Octant1**, and, indeed, inclusion of **Octant0** -and **Octant1** directly into **EVGALine** would speed execution by +The version of `EVGALine` shown in Listing 35.1 is reasonably fast, +but it is not as fast as it might be. Inclusion of `EVGADot` directly +into `Octant0` and `Octant1`, and, indeed, inclusion of `Octant0` +and `Octant1` directly into `EVGALine` would speed execution by saving the overhead of calling and parameter passing. Handpicked register variables might speed performance as well, as would the use of -word **OUT**s rather than byte **OUT**s. A more significant performance +word `OUT`s rather than byte `OUT`s. A more significant performance increase would come from eliminating separate calculation of the address and mask for each pixel. Since the location of each pixel relative to the previous pixel is known, the address and mask could simply be @@ -65,20 +65,20 @@ best way to go. Why produce hard-to-understand C code to boost speed a bit when assembly-language code can perform the same task at two or more times the speed? -Given which, a high-speed assembly language version of **EVGALine** +Given which, a high-speed assembly language version of `EVGALine` would seem to be a logical next step. ### Bresenham's Algorithm in Assembly {#Heading11} Listing 35.3 is a high-performance implementation of Bresenham's algorithm, written entirely in assembly language. The code is callable -from C just as is Listing 35.1, with the same name, **EVGALine**, and +from C just as is Listing 35.1, with the same name, `EVGALine`, and with the same parameters. Either of the two can be linked to any program -that calls **EVGALine**, since they appear to be identical to the +that calls `EVGALine`, since they appear to be identical to the calling program. The only difference between the two versions is that the sample program in Listing 35.2 runs over three times as fast on a 486 with an ISA-bus VGA when calling the assembly-language version of -**EVGALine** as when calling the C version, and the difference would be +`EVGALine` as when calling the C version, and the difference would be considerably greater yet on a local bus, or with the use of write mode 3. Link each version with Listing 35.2 and compare performance—the difference is startling. diff --git a/35-07.md b/35-07.md index d27545d..6eb0a7f 100644 --- a/35-07.md +++ b/35-07.md @@ -396,12 +396,12 @@ One point I do want to make is that Listing 35.3 incorporates a clever notion for which credit is due Jim Mackraz, who described the notion in a letter written in response to an article I wrote long ago in the late and lamented *Programmer's Journal*. Jim's suggestion was that when -drawing lines for which |**DeltaX**| is greater than |**DeltaY**|, bits +drawing lines for which |`DeltaX`| is greater than |`DeltaY`|, bits set to 1 for each of the pixels controlled by a given byte can be accumulated in a register, rather than drawing each pixel individually. All the pixels controlled by that byte can then be drawn at once, with a single access to display memory, when all pixel processing associated -with that byte has been completed. This approach can save many **OUT**s +with that byte has been completed. This approach can save many `OUT`s and many display memory reads and writes when drawing nearly-horizontal lines, and that's important because EGAs and VGAs hold the CPU up for a considerable period of time on each I/O operation and display memory diff --git a/36-02.md b/36-02.md index e725949..257ec49 100644 --- a/36-02.md +++ b/36-02.md @@ -59,9 +59,9 @@ however, the discussion also applies to Y-major lines, with X and Y reversed. The minimum possible length for any run in an X-major line is -**int(XDelta/YDelta)**, where **XDelta** is the X-dimension of the line -and **YDelta** is the Y-dimension. The maximum possible length is -**int(XDelta/YDelta)+ 1**. The trick, then, is knowing which of these +`int(XDelta/YDelta)`, where `XDelta` is the X-dimension of the line +and `YDelta` is the Y-dimension. The maximum possible length is +`int(XDelta/YDelta)+ 1`. The trick, then, is knowing which of these two lengths to select for each run. To see how we can make this selection, refer to Figure 36.4. For each one-pixel step along the minor axis (Y, in this case), we advance at least three pixels. The full @@ -83,17 +83,17 @@ floating-point arithmetic is slow and fixed-point arithmetic is imprecise. Therefore, we take a cue from standard Bresenham's and scale all the error-term calculations up so that we can work with integers. The fractional X (major axis) advance per one-pixel Y (minor axis) -advance is the fractional portion of **XDelta/YDelta**. This value is -exactly equivalent **to (XDelta % YDelta)/YDelta**. We'll scale this up -by multiplying it by **YDelta\*2**, so that the amount by which we +advance is the fractional portion of `XDelta/YDelta`. This value is +exactly equivalent `to (XDelta % YDelta)/YDelta`. We'll scale this up +by multiplying it by `YDelta*2`, so that the amount by which we adjust the error term up for each one-pixel minor-axis advance is -**(XDelta % YDelta)\*2**. +`(XDelta % YDelta)*2`. We'll similarly scale up the one pixel by which we adjust the error term down after it turns over, so our downward error-term adjustment is -**YDelta\*2**. Therefore, before drawing each run, we'll add **(XDelta % -YDelta)\*2** to the error term. If the error term runs over (reaches one -full pixel), we'll lengthen the run by 1, and subtract **YDelta\*2** +`YDelta*2`. Therefore, before drawing each run, we'll add `(XDelta % +YDelta)*2` to the error term. If the error term runs over (reaches one +full pixel), we'll lengthen the run by 1, and subtract `YDelta*2` from the error term. (All values are multiplied by 2 so that the initial error term, which involves a 0.5 term, can be scaled up to an integer, as discussed next.) diff --git a/37-02.md b/37-02.md index 386061b..c86c8d0 100644 --- a/37-02.md +++ b/37-02.md @@ -26,7 +26,7 @@ profiling before you optimize. When I went to speed up run-length slice lines, I initially manually converted the last chapter's C code into assembly. Then I streamlined -the register usage and used **REP STOS** wherever possible. Listing 37.1 +the register usage and used `REP STOS` wherever possible. Listing 37.1 is that code. At that point, line drawing was surely faster, although I didn't know exactly how much faster. Equally surely, there were significant optimizations yet to be made, and I was itching to get on to @@ -94,8 +94,8 @@ the freed registers could be used to implement more esoteric approaches like unrolling the Y-major inner loop; such unrolling could take advantage of the knowledge that only two run lengths are possible for any given line. Strangely enough, on the 486 it might also be worth -unrolling the X-major inner loop, which consists of **REP STOSB**, -because of the slow start-up time of **REP** relative to the speed of +unrolling the X-major inner loop, which consists of `REP STOSB`, +because of the slow start-up time of `REP` relative to the speed of branching on that processor. Special code could be implemented for lines with integral slopes, diff --git a/38-02.md b/38-02.md index e8821a1..4032695 100644 --- a/38-02.md +++ b/38-02.md @@ -59,11 +59,11 @@ have at them. ### Filling Non-Overlapping Convex Polygons {#Heading6} Without further ado, Listing 38.1 contains a function, -**FillConvexPolygon**, that accepts a list of points that describe a +`FillConvexPolygon`, that accepts a list of points that describe a convex polygon, with the last point assumed to connect to the first, and scans it into a list of lines to fill, then passes that list to the -function **DrawHorizontalLineList** in Listing 38.2. Listing 38.3 is a -sample program that calls **FillConvexPolygon** to draw polygons of +function `DrawHorizontalLineList` in Listing 38.2. Listing 38.3 is a +sample program that calls `FillConvexPolygon` to draw polygons of various sorts, and Listing 38.4 is a header file included by the other listings. Here are the listings; we'll pick up discussion on the other side. diff --git a/38-04.md b/38-04.md index ca980b5..6a7b31b 100644 --- a/38-04.md +++ b/38-04.md @@ -50,7 +50,7 @@ Once we know where the left edge starts in the vertex list, we can scan-convert it a line segment at a time until the bottom vertex is reached. Each point is stored as the starting X coordinate for the corresponding scan line in the list we'll pass to -**DrawHorizontalLineList**. The nearest X coordinate on each scan line +`DrawHorizontalLineList`. The nearest X coordinate on each scan line that's on or to the right of the left edge is selected. The last point of each line segment making up the left edge isn't scan-converted, producing two desirable effects. First, it avoids drawing each vertex @@ -71,7 +71,7 @@ nearest point to the left of but not on that line in its original location. Sketch it out and you'll see what I mean. Once the two edges are scan-converted, the whole line list is passed to -**DrawHorizontalLineList**, and the polygon is drawn. +`DrawHorizontalLineList`, and the polygon is drawn. Finis. diff --git a/39-01.md b/39-01.md index ed9f427..080e563 100644 --- a/39-01.md +++ b/39-01.md @@ -106,16 +106,16 @@ Our original polygon filling code involved three major tasks, each performed by a separate function: * Tracing each polygon edge to generate a coordinate list (performed - by the function **ScanEdge);** + by the function `ScanEdge`); * Drawing the scanned-out horizontal lines that constitute the filled - polygon (**DrawHorizontalLineList** ); and + polygon (`DrawHorizontalLineList`); and * Characterizing the polygon and coordinating the tracing and drawing - (**FillConvexPolygon** ). + (`FillConvexPolygon`). The amount of time that the previous chapter's sample program spent in each of these areas is shown in Table 39.1. As you can see, half the time was spent drawing and the other half was spent tracing the polygon -edges (the time spent in **FillConvexPolygon** was relatively +edges (the time spent in `FillConvexPolygon` was relatively minuscule), so we have our choice of where to begin optimizing. #### Fast Drawing {#Heading4} @@ -131,16 +131,16 @@ succession, using something along the lines of **\*ScrPtr++ = FillColor;** inside a loop. However, it seems silly to use a loop when the x86 has an instruction, -**REP STOS**, that's uniquely suited to filling linear memory buffers. -There's no way to use **REP STOS** directly in C code, but it's a good -bet that the **memset** library function uses **REP STOS**, so you could -greatly enhance performance by using **memset** to draw each scan line +`REP STOS`, that's uniquely suited to filling linear memory buffers. +There's no way to use `REP STOS` directly in C code, but it's a good +bet that the `memset` library function uses `REP STOS`, so you could +greatly enhance performance by using `memset` to draw each scan line of the polygon in a single shot. That, however, is easier said than -done. The **memset** function linked in from the library is tied to the +done. The `memset` function linked in from the library is tied to the memory model in use; in small (which includes Tiny, Small, or Medium) -data models **memset** accepts only near pointers, so it can't be used +data models `memset` accepts only near pointers, so it can't be used to access screen memory. Consequently, a large (which includes Compact, -Large, or Huge) data model must be used to allow **memset** to draw to +Large, or Huge) data model must be used to allow `memset` to draw to display memory—a clear case of the tail wagging the dog. This is an excellent example of why, although it is possible to use C to do virtually anything, it's sometimes much simpler just to use a little diff --git a/39-02.md b/39-02.md index ab73213..356bb2e 100644 --- a/39-02.md +++ b/39-02.md @@ -11,7 +11,7 @@ pages: 727-730 --- At any rate, Listing 39.1 for this chapter shows a version of -**DrawHorizontalLineList** that uses memset to draw each scan line of +`DrawHorizontalLineList` that uses memset to draw each scan line of the polygon in a single call. When linked to Chapter 38's test program, Listing 39.1 increases pure drawing speed (disregarding edge tracing and other nondrawing time) by more than an order of magnitude over Chapter @@ -37,7 +37,7 @@ Table: Table 39.1 Polygon fill performance. All times are in seconds, as measured with Turbo Profiler on a 20-MHz cached 386 with no math coprocessor installed. Note that time spent in -**main()** is not included. C code was compiled with Borland C++ with +`main()` is not included. C code was compiled with Borland C++ with maximum optimization (-G -O -Z -r -a); assembly language code was assembled with TASM. Percentages of combined times are rounded to the nearest percent, so the sum of the three percentages does not always @@ -95,7 +95,7 @@ drawn, or if a faster or slower computer/VGA combination were used. These factors notwithstanding, the test program does fill a variety of polygons of varying complexity sized from large to small and in between, and certainly the order of magnitude difference between Listing 39.1 and -the old version of **DrawHorizontalLineList** is a clear indication of +the old version of `DrawHorizontalLineList` is a clear indication of which code is superior. Anyway, Listing 39.1 has the desired effect of vastly improving drawing @@ -105,7 +105,7 @@ logical to optimize the tracing code next. #### Fast Edge Tracing {#Heading5} -There's no secret as to why last chapter's **ScanEdge** was so slow: It +There's no secret as to why last chapter's `ScanEdge` was so slow: It used floating point calculations. One secret of fast graphics is using integer or fixed-point calculations, instead. (Sure, the floating point code would run faster if a math coprocessor were installed, but it would @@ -124,7 +124,7 @@ integer-based test image to one produced by floating-point calculations, two pixels out of the whole screen differed, leading me to suspect a bug in the integer code. It turned out, however, that's in those two cases, the floating point results were sufficiently imprecise to creep from -just under an integer value to just over it, so that the **ceil** +just under an integer value to just over it, so that the `ceil` function returned a coordinate that was one too large. > ![](images/i.jpg) diff --git a/39-03.md b/39-03.md index b0e38d5..fcce57d 100644 --- a/39-03.md +++ b/39-03.md @@ -148,12 +148,12 @@ void ScanEdge(int X1, int Y1, int X2, int Y2, int SetXStart, The C implementation in Listing 39.2 is now nearly 20 times as fast as the original, which is good enough for most purposes. Still, it requires -that one of the large data models be used (for **memset** ), and it's +that one of the large data models be used (for `memset` ), and it's certainly not the fastest possible code. The obvious next step is assembly language. Listing 39.3 is an assembly language version of -**DrawHorizontalLineList** . In actual use, it proved to be about 36 +`DrawHorizontalLineList` . In actual use, it proved to be about 36 percent faster than Listing 39.1; better than a poke in the eye with a sharp stick, but just barely. There's more to these timing results than meets that eye, though. Display memory generally responds much more diff --git a/39-04.md b/39-04.md index 6f08808..560d25f 100644 --- a/39-04.md +++ b/39-04.md @@ -14,7 +14,7 @@ And indeed it does. When the test program is modified to draw to a local buffer, both the C and assembly language versions get 0.29 seconds faster, that being a measure of the time taken by display memory wait states. With those wait states factored out, the assembly language -version of **DrawHorizontalLineList** becomes almost three times as fast +version of `DrawHorizontalLineList` becomes almost three times as fast as the C code. > ![](images/i.jpg) @@ -123,32 +123,32 @@ _DrawHorizontalLineList endp #### Maximizing REP STOS {#Heading7} -Listing 39.3 doesn't take the easy way out and use **REP STOSB** to fill -each scan line; instead, it uses **REP STOSW** to fill as many pixel -pairs as possible via word-sized accesses, using **STOSB** only to do +Listing 39.3 doesn't take the easy way out and use `REP STOSB` to fill +each scan line; instead, it uses `REP STOSW` to fill as many pixel +pairs as possible via word-sized accesses, using `STOSB` only to do odd bytes. Word accesses to odd addresses are always split by the processor into 2-byte accesses. Such word accesses take twice as long as word accesses to even addresses, so Listing 39.3 makes sure that all -word accesses occur at even addresses, by performing a leading **STOSB** +word accesses occur at even addresses, by performing a leading `STOSB` first if necessary. Listing 39.3 is another case in which it's worth knowing the environment in which your code will run. Extra code is required to perform aligned word-at-a-time filling, resulting in extra overhead. For very small or narrow polygons, that overhead might overwhelm the advantage of drawing -a word at a time, making plain old **REP STOSB** faster. +a word at a time, making plain old `REP STOSB` faster. ### Faster Edge Tracing {#Heading8} -Finally, Listing 39.4 is an assembly language version of **ScanEdge**. +Finally, Listing 39.4 is an assembly language version of `ScanEdge`. Listing 39.4 is a relatively straightforward translation from C to assembly, but is nonetheless about twice as fast as Listing 39.2. -The version of **ScanEdge** in Listing 39.4 could certainly be sped up -still further by unrolling the loops. **FillConvexPolygon**, the overall +The version of `ScanEdge` in Listing 39.4 could certainly be sped up +still further by unrolling the loops. `FillConvexPolygon`, the overall coordination routine, hasn't even been converted to assembly language, so that could be sped up as well. I haven't bothered with these -optimizations because all code other than **DrawHorizontalLineList** +optimizations because all code other than `DrawHorizontalLineList` takes only 14 percent of the overall polygon filling time when drawing to display memory; the potential return on optimizing nondrawing code simply isn't great enough to justify the effort. Part of the value of a @@ -162,9 +162,9 @@ overall time. Again, *know where the cycles go* . -By the way, note that all the versions of **ScanEdge** and -**FillConvexPolygon** that we've looked at are adapter-independent, and +By the way, note that all the versions of `ScanEdge` and +`FillConvexPolygon` that we've looked at are adapter-independent, and that the C code is also machine-independent; all adapter-specific code -is isolated in **DrawHorizontalLineList**. This makes it easy to add +is isolated in `DrawHorizontalLineList`. This makes it easy to add support for other graphics systems, such as the 8514/A, the XGA, or, for that matter, a completely non-PC system. diff --git a/40-03.md b/40-03.md index ae949b5..2a608a2 100644 --- a/40-03.md +++ b/40-03.md @@ -12,8 +12,8 @@ pages: 750-753 ### Complex Polygon Filling: An Implementation {#Heading5} -Listing 40.1 just shown presents a function, **FillPolygon()**, that -fills polygons of all shapes. If **CONVEX\_FILL\_LINKED** is defined, +Listing 40.1 just shown presents a function, `FillPolygon()`, that +fills polygons of all shapes. If `CONVEX_FILL_LINKED` is defined, the fast convex fill code from Chapter 39 is linked in and used to draw convex polygons. Otherwise, convex polygons are handled as if they were complex. Nonconvex polygons are also handled as complex, although this diff --git a/40-04.md b/40-04.md index d0354b0..1cd1f3e 100644 --- a/40-04.md +++ b/40-04.md @@ -51,7 +51,7 @@ However, most of that time is spent drawing individual pixels; when Listing 40.2 is replaced with the fast assembly line segment drawing code in Listing 40.5, performance improves by two and one-half times, to about half as fast as the fast convex fill code. Even after conversion -to assembly in Listing 40.5, **DrawHorizontalLineSeg** still takes more +to assembly in Listing 40.5, `DrawHorizontalLineSeg` still takes more than half of the total execution time, and the remaining time is spread out fairly evenly over the various subroutines in Listing 40.1. Consequently, there's no single place in which it's possible to greatly diff --git a/42-03.md b/42-03.md index a6dac1e..1c14f88 100644 --- a/42-03.md +++ b/42-03.md @@ -16,8 +16,8 @@ The true test of any antialiasing technique is how good it looks, so let's have a look at Wu antialiasing in action. Listing 42.1 is a C implementation of Wu antialiasing. Listing 42.2 is a sample program that draws a variety of Wu-antialiased lines, followed by non-antialiased -lines, for comparison. Listing 42.3 contains **DrawPixel()** and -**SetMode()** functions for mode 13H, the VGA's 320x200 256-color mode. +lines, for comparison. Listing 42.3 contains `DrawPixel()` and +`SetMode()` functions for mode 13H, the VGA's 320x200 256-color mode. Finally, Listing 42.4 is a simple, non-antialiased line-drawing routine. Link these four listings together and run the resulting program to see both Wu-antialiased and non-antialiased lines. diff --git a/42-04.md b/42-04.md index 76a5e80..0223811 100644 --- a/42-04.md +++ b/42-04.md @@ -81,8 +81,8 @@ void DrawLine(int X0, int Y0, int X1, int Y1, int Color) } ``` -Listing 42.1 isn't particularly fast, because it calls **DrawPixel()** -for each pixel. On the other hand, **DrawPixel()** makes it easy to try +Listing 42.1 isn't particularly fast, because it calls `DrawPixel()` +for each pixel. On the other hand, `DrawPixel()` makes it easy to try out Wu antialiasing in a variety of modes; just adapt the code in Listing 42.3 for the 256-color mode you want to support. For example, Listing 42.5 shows code to draw Wu-antialiased lines in 640x480 @@ -141,7 +141,7 @@ void SetMode() ``` Listing 42.1 requires that the DAC palette be set up so that a -**NumLevel**-long block of palette entries contains linearly decreasing +`NumLevel`-long block of palette entries contains linearly decreasing intensities of the drawing color. The size of the block is programmable, but must be a power of two. The more intensity levels, the better. Wu says that 32 intensities are enough; on my system, eight and even four diff --git a/43-01.md b/43-01.md index f28766f..9081078 100644 --- a/43-01.md +++ b/43-01.md @@ -67,8 +67,8 @@ to take advantage of the bit-plane architecture and color palette of the VGA to develop an animation architecture that can handle several overlapping images with terrific speed and with virtually perfect visual quality. This technique produces no overlap effects or flicker and -allows us to use the fastest possible method to draw images—the **REP -MOVS** instruction. It has its limitations, but unlike Mode X and some +allows us to use the fastest possible method to draw images—the `REP +MOVS` instruction. It has its limitations, but unlike Mode X and some other animation techniques, the techniques I'll show you in this chapter will also work on the EGA, which may be important in some applications. diff --git a/43-02.md b/43-02.md index e55627b..e9a8b89 100644 --- a/43-02.md +++ b/43-02.md @@ -32,7 +32,7 @@ and around the edges of images in forward planes. Finally, and most importantly, it would meet all the criteria needed to allow us to store each image in a single plane, letting us manipulate the images very quickly and with no reprogramming of the VGA's hardware other than the -few **OUT** instructions required to select the plane we want to write +few `OUT` instructions required to select the plane we want to write to. Which leaves only one question: How do we get this magical diff --git a/43-04.md b/43-04.md index 3564b42..096c9e2 100644 --- a/43-04.md +++ b/43-04.md @@ -35,7 +35,7 @@ like—dare I say it?—a games machine. Listing 43.1 was designed to run at the absolute fastest speed, and as I mentioned it puts in a pretty amazing performance on the slowest PCs of all. Assuming you'll be running Listing 43.1 on an faster computer, -you'll have to crank up the **DELAY** equate at the start of Listing +you'll have to crank up the `DELAY` equate at the start of Listing 43.1 to slow things down to a reasonable pace. (It's not a very good game where all the pieces are a continual blur!) Even on something as modest as a 286-based AT, Listing 43.1 runs much too fast without a @@ -47,11 +47,11 @@ into the hundreds on a cutting-edge local-bus 486 or Pentium. I'm not going to discuss Listing 43.1 in detail; the code is very thoroughly commented and should speak for itself, and most of the individual components of Listing 43.1—the Map Mask register, mode sets, -word versus byte **OUT** instructions to the VGA—have been covered in +word versus byte `OUT` instructions to the VGA—have been covered in earlier chapters. Do notice, however, that Listing 43.1 sets the palette exactly as I described earlier. This is accomplished by passing a pointer to a 17-byte array (1 byte for each of the 16 palette registers, -and 1 byte for the border color) to the BIOS video interrupt (**INT** +and 1 byte for the border color) to the BIOS video interrupt (`INT` 10H), function 10H, subfunction 2. Bit-plane animation does have inherent limitations, which we'll get to diff --git a/44-04.md b/44-04.md index b10d8b0..babb5f1 100644 --- a/44-04.md +++ b/44-04.md @@ -85,9 +85,9 @@ will change the first 4 pixels on the screen (the left nibble of the byte at offset 0 in display memory) to blue, and will leave the next 4 pixels (the right nibble of the byte at offset 0) unchanged. -Using one **MOV** to read from display memory and another to write to +Using one `MOV` to read from display memory and another to write to display memory is not particularly efficient on some processors. In -Listing 44.2, I instead use **XCHG**, which reads and then writes a +Listing 44.2, I instead use `XCHG`, which reads and then writes a memory location in a single operation, as in: ```nasm @@ -98,7 +98,7 @@ xchg es:[0],al ``` Again, the actual value that's read is irrelevant. In general, the -**XCHG** approach is more compact than two **MOV**s, and is faster on +`XCHG` approach is more compact than two `MOV`s, and is faster on 386 and earlier processors, but slower on 486s and Pentiums. If all pixels in a byte of display memory are to be drawn in a single @@ -117,11 +117,11 @@ mov byte ptr es:[di],0ffh > If you're familiar with VGA programming, you're no doubt aware that > everything that can be done with write mode 3 can also be accomplished > in write mode 0 or write mode 2 by using the Bit Mask register. However, -> setting the Bit Mask register requires at least one **OUT** per byte +> setting the Bit Mask register requires at least one `OUT` per byte > written, in addition to the read and write of display memory, and -> **OUT**s are often slower than display memory accesses, especially on +> `OUT`s are often slower than display memory accesses, especially on > 386s and 486s. One of the great virtues of write mode 3 is that it -> requires virtually no **OUT**s and is therefore substantially faster for +> requires virtually no `OUT`s and is therefore substantially faster for > masking than the other write modes. In short, write mode 3 is a good choice for single-color drawing that diff --git a/45-01.md b/45-01.md index ba1b74d..28bad23 100644 --- a/45-01.md +++ b/45-01.md @@ -101,19 +101,19 @@ tests, as run on two 486/33 SuperVGA systems under the Phar Lap not-very-scientific spot test, and I don't want to unfairly malign, say, a VGA whose only sin is being plugged into a lousy motherboard, or vice versa.) Under Phar Lap, 32-bit protected-mode apps run with full I/O -privileges, meaning that the **OUT** instructions I measured had the -best official cycle times possible on the 486: 10 cycles. **OUT** +privileges, meaning that the `OUT` instructions I measured had the +best official cycle times possible on the 486: 10 cycles. `OUT` officially takes 16 cycles in real mode on a 486, and officially takes a mind-boggling 30 cycles in protected mode if running *without* full I/O privileges (as is normally the case for protected-mode applications). Basically, I/O is just plain slow on a 486. -As slow as 30 or even 10 cycles is for an **OUT**, one could only wish -that VGA I/O were actually that fast. The fastest measured **OUT** to a +As slow as 30 or even 10 cycles is for an `OUT`, one could only wish +that VGA I/O were actually that fast. The fastest measured `OUT` to a VGA in Table 45.1 is 26 cycles, and the slowest is 126—this for an operation that's *supposed* to take 10 cycles. To put this in context, -**MUL** takes only 13 to 42 cycles, and a normal **MOV** to or from -system memory takes exactly one cycle on the 486. In short, **OUT**s to +`MUL` takes only 13 to 42 cycles, and a normal `MOV` to or from +system memory takes exactly one cycle on the 486. In short, `OUT`s to VGAs are as much as 100 times slower than normal memory accesses, and are generally two to four times slower than even display memory accesses, although there are exceptions. diff --git a/45-02.md b/45-02.md index 643b221..72011d5 100644 --- a/45-02.md +++ b/45-02.md @@ -37,11 +37,11 @@ Table: Table 45.1 Results of I/O performance tests run under the Phar Lap386|DOS-Extender. > ![](images/i.jpg) -> **OUT**s, in general, are lousy on the 486 (and to think they only took -> three cycles on the 286!). **OUT**s to VGAs are particularly lousy. +> `OUT`s, in general, are lousy on the 486 (and to think they only took +> three cycles on the 286!). `OUT`s to VGAs are particularly lousy. > Display memory performance is pretty poor, especially for reads. The > conclusions are obvious, I would hope. Structure your graphics code, -> and, in general, all 486 code, to avoid **OUT**s. +> and, in general, all 486 code, to avoid `OUT`s. For graphics, this especially means using write mode 3 rather than the bit-mask register. When you must use the bit mask, arrange drawing so diff --git a/46-03.md b/46-03.md index d6c87ff..10032fc 100644 --- a/46-03.md +++ b/46-03.md @@ -56,7 +56,7 @@ active and alive. I've implemented the simplest possible form of internal animation in Listing 46.1—alternation between two images—but even this level of internal animation greatly improves the feel of the overall animation. You could easily increase the number of images cycled -through, simply by increasing the value of **InternalAnimateMax** for a +through, simply by increasing the value of `InternalAnimateMax` for a given entity. You could also implement more complex image-selection logic to produce more interesting and less predictable internal-animation effects, such as jumping, ducking, running, and the diff --git a/47-03.md b/47-03.md index bc87ef6..7044349 100644 --- a/47-03.md +++ b/47-03.md @@ -45,7 +45,7 @@ register (Graphics Controller register 4) to P. It goes without saying that this is one ugly bitmap organization, requiring a lot of overhead to manipulate a single pixel. The write pixel code shown in Listing 47.2 must determine the appropriate plane -and perform a 16-bit **OUT** to select that plane for each pixel +and perform a 16-bit `OUT` to select that plane for each pixel written, and likewise for the read pixel code shown in Listing 47.3. Calculating and mapping in a plane once for each pixel written is scarcely a recipe for performance. diff --git a/47-04.md b/47-04.md index 157a379..1b09897 100644 --- a/47-04.md +++ b/47-04.md @@ -118,16 +118,16 @@ _FillRectangleX endp ``` The two major weaknesses of Listing 47.4 both result from selecting the -plane on a pixel by pixel basis. First, endless **OUT**s (which are +plane on a pixel by pixel basis. First, endless `OUT`s (which are particularly slow on 386s, 486s, and Pentiums, much slower than accesses -to display memory) must be performed, and, second, **REP STOS** can't be +to display memory) must be performed, and, second, `REP STOS` can't be used. Listing 47.5 overcomes both these problems by tailoring the fill technique to the organization of display memory. Each plane is filled in its entirety in one burst before the next plane is processed, so only -five **OUT**s are required in all, and **REP STOS** can indeed be used; -I've used **REP STOSB** in Listings 47.5 and 47.6. **REP STOSW** could -be used and would improve performance on most VGAs; however, **REP -STOSW** requires extra overhead to set up, so it can be slower for small +five `OUT`s are required in all, and `REP STOS` can indeed be used; +I've used `REP STOSB` in Listings 47.5 and 47.6. `REP STOSW` could +be used and would improve performance on most VGAs; however, `REP +STOSW` requires extra overhead to set up, so it can be slower for small rectangles, especially on 8-bit VGAs. Note that doing an entire plane at a time can produce a "fading-in" effect for large images, because all columns for one plane are drawn before any columns for the next. If this diff --git a/47-05.md b/47-05.md index 391db2f..8d9f3bd 100644 --- a/47-05.md +++ b/47-05.md @@ -18,7 +18,7 @@ pages: 887-889 > Mode X in mind. For example, icons could be prearranged in system memory > with the pixels organized into four plane-oriented sets (or, again, in > four sets per scan line to avoid a fading-in effect) to facilitate -> copying to the screen a plane at a time with **REP MOVS**. +> copying to the screen a plane at a time with `REP MOVS`. **LISTING 47.5 L47-5.ASM** diff --git a/47-06.md b/47-06.md index d2b602a..c8ae15e 100644 --- a/47-06.md +++ b/47-06.md @@ -51,7 +51,7 @@ potentially speeding up operations like rectangle fills by four times. 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 -Map Mask to set up to four pixels per **STOS.** The only trick to +Map Mask to set up to four pixels per `STOS`. The only trick to Listing 47.6 is that any left or right edge that isn't aligned to a multiple-of-four pixel column (that is, a column at which one four-pixel set ends and the next begins) must be clipped via the Map Mask register, diff --git a/48-05.md b/48-05.md index 168bbf2..7b1e18b 100644 --- a/48-05.md +++ b/48-05.md @@ -41,7 +41,7 @@ that pixels are stored in system memory in exactly the order in which they will ultimately appear on the screen; that is, in the same linear order that mode 13H uses. It would be more efficient to store all the pixels for one plane first, then all the pixels for the next plane, and -so on for all four planes, because many **OUT**s could be avoided, but +so on for all four planes, because many `OUT`s could be avoided, but that would make images rather hard to create. And, while it is true that the speed of drawing images is, in general, often a critical performance factor, the speed of copying images from system memory to display memory diff --git a/49-02.md b/49-02.md index d79f404..fe2b21a 100644 --- a/49-02.md +++ b/49-02.md @@ -34,8 +34,8 @@ corresponding to each four-pixel set as we copy four pixels at a time via the latches. Listing 49.2 performs fast masked copying. This code expects to receive -a pointer to a **MaskedImage** structure, which in turn points to four -**AlignedMaskedImage** structures that describe the four possible image +a pointer to a `MaskedImage` structure, which in turn points to four +`AlignedMaskedImage` structures that describe the four possible image and mask alignments. The aligned images are already stored in display memory, and the aligned masks are already stored in system memory; further, the masks are predigested into Map Mask register-compatible diff --git a/49-03.md b/49-03.md index d51a595..4c0825a 100644 --- a/49-03.md +++ b/49-03.md @@ -12,7 +12,7 @@ pages: 922-924 It would be handy to have a function that, given a base image and mask, generates the four image and mask alignments and fills in the -**MaskedImage** structure. Listing 49.3, together with the include file +`MaskedImage` structure. Listing 49.3, together with the include file in Listing 49.4 and the system memory-to-display memory block-copy routine in Listing 48.4 (in the previous chapter) does just that. It would be faster if Listing 49.3 were in assembly language, but there's @@ -137,6 +137,6 @@ used separate parameters for simplicity and flexibility. > transparent copying depends upon the processor and the video adapter. > The advantage of Mode X masked copying is the 32-bit parallelism; the > disadvantages are the need to read display memory and the need to -> perform an **OUT** for every four pixels. (**OUT** is a slow 486/Pentium -> instruction, and most VGAs respond to **OUT**s much more slowly than to +> perform an `OUT` for every four pixels. (`OUT` is a slow 486/Pentium +> instruction, and most VGAs respond to `OUT`s much more slowly than to > display memory writes.) \ No newline at end of file diff --git a/50-04.md b/50-04.md index e67ab2a..f1b65fc 100644 --- a/50-04.md +++ b/50-04.md @@ -24,7 +24,7 @@ for the program, and Listing 50.5 is the main animation program. Other modules required are: Listings 47.1 and 47.6 from Chapter 47 (Mode X mode set, rectangle fill); Listing 49.6 from Chapter 49; Listing 39.4 -from Chapter 39 (polygon edge scan); and the **FillConvexPolygon()** +from Chapter 39 (polygon edge scan); and the `FillConvexPolygon()` function from Listing 38.1 in Chapter 38. All necessary code modules, along with a project file, are present in the subdirectory for this chapter on the listings disk, whether they were presented in this diff --git a/51-02.md b/51-02.md index ce522a1..3b74eff 100644 --- a/51-02.md +++ b/51-02.md @@ -107,7 +107,7 @@ previous chapters are: Listings 50.1 and 50.2 from Chapter 50 (draw clipped line list, matrix math functions); Listings 47.1 and 47.6 from Chapter 47, (Mode X mode set, rectangle fill); Listing 49.6 from Chapter 49; Listing 39.4 from Chapter 39 (polygon edge scan); and the -**FillConvexPolygon()** function from Listing 38.1 from Chapter 38. All +`FillConvexPolygon()` function from Listing 38.1 from Chapter 38. All necessary modules, along with a project file, will be present in the subdirectory for this chapter on the listings diskette, whether they were presented in this chapter or some earlier chapter. This may crowd diff --git a/51-04.md b/51-04.md index cb3228d..8ee8e12 100644 --- a/51-04.md +++ b/51-04.md @@ -134,7 +134,7 @@ eight vertices in a cube, with three faces sharing each vertex. In this way, the transformation burden is lightened by two-thirds. Also, as mentioned earlier, backface removal is performed with integers, in screen coordinates, rather than with floating-point values in screen -space. Finally, the **RecalcXForm** flag is set whenever the user +space. Finally, the `RecalcXForm` flag is set whenever the user changes the object-to-world transformation. Only when this flag is set is the full object-to-view transformation recalculated and the object's vertices transformed and projected again; otherwise, the values already diff --git a/51-06.md b/51-06.md index 496eeb5..8b23771 100644 --- a/51-06.md +++ b/51-06.md @@ -101,9 +101,9 @@ extern struct Rect EraseRect[]; In the previous chapter, I added 0.5 and truncated in order to round values from floating-point to integer format. Here, in Listing 51.2, -I've switched to adding 0.5 and using the **floor()** function. For +I've switched to adding 0.5 and using the `floor()` function. For positive values, the two approaches are equivalent; for negative values, -only the **floor()** approach works properly. +only the `floor()` approach works properly. ### Object Representation {#Heading7} diff --git a/52-01.md b/52-01.md index 16f3113..948ddb1 100644 --- a/52-01.md +++ b/52-01.md @@ -84,8 +84,8 @@ program is the following: fill); * Listing 49.6 from Chapter 49; * Listing 39.4 from Chapter 39 (polygon edge scan); and - * The **FillConvexPolygon( )** function from Listing 38.1 from Chapter - 38. Note that the **struct** keywords in **FillConvexPolygon( )** + * The `FillConvexPolygon( )` function from Listing 38.1 from Chapter + 38. Note that the `struct` keywords in `FillConvexPolygon( )` must be removed to reflect the switch to typedefs in the animation header file. diff --git a/52-08.md b/52-08.md index 85ee732..ff04c03 100644 --- a/52-08.md +++ b/52-08.md @@ -65,7 +65,7 @@ powerful, but there's a dark side to them as well. Obviously, native 386 instructions won't work on 8088 and 286 machines. That's rectifiable; equivalent multiplication and division routines could be implemented for real mode and performance would still be reasonable. It sure is nice to -be able to plug in a 32-bit **IMUL** or **DIV** and be done with it, +be able to plug in a 32-bit `IMUL` or `DIV` and be done with it, though. More importantly, 32-bit fixed-point arithmetic has limitations in range and accuracy. Points outside a 64Kx64Kx64K space can't be handled, imprecision tends to creep in over the course of multiple @@ -90,7 +90,7 @@ overflow. The distribution of execution time in the animation code is no longer wildly biased toward transformation, but sine and cosine are certainly still sucking up cycles. Likewise, the overhead in the calls to -**FixedMul()** and **FixedDiv()** is costly. Much of this is correctable +`FixedMul()` and `FixedDiv()` is costly. Much of this is correctable with a little carefully crafted assembly language and a lookup table; I'll provide that shortly. diff --git a/53-01.md b/53-01.md index 0eab67e..aaa2c94 100644 --- a/53-01.md +++ b/53-01.md @@ -72,13 +72,13 @@ then implementing the fixed-point multiplication and division functions in assembly in order to take advantage of the 386's 32-bit capabilities. There's another area of the program that fairly cries out for assembly language: matrix math. The function to multiply a matrix by a vector -(**XformVec()**) and the function to concatenate matrices -(**ConcatXforms()**) both loop heavily around calls to **FixedMul();** a +(`XformVec()`) and the function to concatenate matrices +(`ConcatXforms()`) both loop heavily around calls to **FixedMul();** a lot of calling and looping can be eliminated by converting these functions to pure assembly language. Listing 53.1 is the module FIXED.ASM from this chapter's iteration of -X-Sharp, with **XformVec()** and **ConcatXforms()** implemented in +X-Sharp, with `XformVec()` and `ConcatXforms()` implemented in assembly language. The code is heavily optimized, to the extent of completely unrolling the loops via macros so that looping is eliminated altogether. FIXED.ASM is highly effective; the time taken for matrix diff --git a/53-03.md b/53-03.md index 543d617..ccec60f 100644 --- a/53-03.md +++ b/53-03.md @@ -19,21 +19,21 @@ reports what percent of total time is represented by all the areas that were profiled; if you're profiling all areas, whatever's not explicitly accounted for seems to be the floating-point emulator time. This quirk fooled me for a while, leading me to think sine and cosine weren't major -drags on performance, because the **sin()** and **cos()** functions +drags on performance, because the `sin()` and `cos()` functions spend most of their time in the emulator, and that time doesn't show up in Turbo Profiler's statistics on those functions. Once I figured out -what was going on, it turned out that not only were **sin()** and -**cos()** major drags, they were taking up over half the total execution +what was going on, it turned out that not only were `sin()` and +`cos()` major drags, they were taking up over half the total execution time by themselves. The solution is a lookup table. Listing 53.1 contains a function called -**CosSin()** that calculates both the sine and cosine of an angle, via a +`CosSin()` that calculates both the sine and cosine of an angle, via a lookup table. The function accepts angles in tenths of degrees; I decided to use tenths of degrees rather than radians because that way it's always possible to look up the sine and cosine of the exact angle requested, rather than approximating, as would be required with radians. Tenths of degrees should be fine enough control for most purposes; if -not, it's easy to alter **CosSin()** for finer gradations yet. GENCOS.C, +not, it's easy to alter `CosSin()` for finer gradations yet. GENCOS.C, the program used to generate the lookup table (COSTABLE.INC), included in Listing 53.1, can be found in the XSHARP22 subdirectory on the listings diskette. GENCOS.C can generate a cosine table with any @@ -93,11 +93,11 @@ complexity of the screen. Listing 53.2 shows X-Sharp file OLIST.C, which includes the key routines for depth sorting. Objects are now stored in a linked list. The initial, -empty list, created by **InitializeObjectList(),** consists of a +empty list, created by `InitializeObjectList()`, consists of a sentinel entry at either end, one at the farthest possible z coordinate, -and one at the nearest. New entries are inserted by **AddObject()** in +and one at the nearest. New entries are inserted by `AddObject()` in z-sorted order. Each time the objects are moved, before they're drawn at -their new locations, **SortObjects()** is called to Z-sort the object +their new locations, `SortObjects()` is called to Z-sort the object list, so that drawing will proceed from back to front. The Z-sorting is done on the basis of the objects' center points; a center-point field has been added to the object structure to support this, and the center diff --git a/53-04.md b/53-04.md index 4a6d61c..22738f4 100644 --- a/53-04.md +++ b/53-04.md @@ -138,7 +138,7 @@ INITBALL.C to match, but that's a tiny detail; by and large, the process of generating a ball-shaped object is now automated. In fact, we're not limited to ball-shaped objects; substitute a different vertex and face generation program for GENBALL.C, and you can make whatever convex -polyhedron you want; again, all you have to do is change the **Colors** +polyhedron you want; again, all you have to do is change the `Colors` array correspondingly. You can easily create multiple versions of the base object, too; INITCUBE.C is an example of this, creating 11 different cubes. diff --git a/54-01.md b/54-01.md index f62b736..6533093 100644 --- a/54-01.md +++ b/54-01.md @@ -36,10 +36,10 @@ I should have known better than to try to sneak this one by you. The most common feedback I've gotten on X-Sharp is that I should make it support the 8088 and 286. Well, I can take a hint as well as the next guy. Listing 54.1 is an improved version of FIXED.ASM, containing dual -386/8088 versions of **CosSin(), XformVec()**, and **ConcatXforms()**, -as well as **FixedMul()** and **FixedDiv()**. +386/8088 versions of `CosSin(), XformVec()`, and `ConcatXforms()`, +as well as `FixedMul()` and `FixedDiv()`. -Given the new version of FIXED.ASM, with **USE386** set to 0, X-Sharp +Given the new version of FIXED.ASM, with `USE386` set to 0, X-Sharp will now run on any processor. That's not to say that it will run fast on any processor, or at least not as fast as it used to. The switch to 8088 instructions makes X-Sharp's fixed-point calculations about 2.5 @@ -56,7 +56,7 @@ simple matter of adding together four partial products. A 32-bit divide is not so simple, however. In fact, in Listing 54.1 I've chosen not to implement a full 32x32 divide, but rather only a 32x16 divide. The reason is simple: performance. A 32x16 divide can be implemented on an -8088 with two **DIV** instructions, but a 32x32 divide takes a great +8088 with two `DIV` instructions, but a 32x32 divide takes a great deal more work, so far as I can see. (If anyone has a fast 32x32 divide, or has a faster way to handle signed multiplies and divides than the approach taken by Listing 54.1, please drop me a line care of the @@ -70,7 +70,7 @@ degradation; therefore, given the already slow performance of the 8088 and 286, I've opted for performance over precision. At any rate, please keep in mind that the non-386 version of -**FixedDiv()** is *not* a general-purpose 32x32 fixed-point division +`FixedDiv()` is *not* a general-purpose 32x32 fixed-point division routine. In fact, it will generate a divide-by-zero error if passed a fixed-point divisor between -1 and 1. As I've explained, the non-386 version of **Fixed-Div()** is designed to do just what X-Sharp needs, diff --git a/55-02.md b/55-02.md index 9b86e7d..dd5a58b 100644 --- a/55-02.md +++ b/55-02.md @@ -16,7 +16,7 @@ and X-Sharp represents colors internally as ideal, device-independent triplets, with 24-bit color precision. It's only after the final 24-bit RGB drawing color is calculated that the display adapter's color capabilities come into play, as the X-Sharp function -**ModelColorToColorIndex()** is called to map the desired RGB color to +`ModelColorToColorIndex()` is called to map the desired RGB color to the closest match the adapter is capable of displaying. Of course, that mapping is adapter-dependent. On a 24-bpp device, it's pretty obvious how the internal RGB color format maps to displayed pixel colors: @@ -28,7 +28,7 @@ straight to display pixels. But how on earth do we map those This is the "color definition" problem I mentioned at the start of this chapter. The VGA palette is arbitrarily programmable to any set of 256 colors, with each color defined by six bits each of red, green, and blue -intensity. In X-Sharp, the function **InitializePalette()** can be +intensity. In X-Sharp, the function `InitializePalette()` can be customized to set up the palette however we wish; this gives us nearly complete flexibility in defining the working color set. Even with infinite flexibility, however, 256 out of 16,000,000 or so possible @@ -41,7 +41,7 @@ One way to deal with the limited simultaneous color capabilities of the VGA is to build an application that uses only a subset of RGB space, then bias the VGA's palette toward that subspace. This is the approach used in the DEMO1 sample program in X-Sharp; Listings 55.2 and 55.3 show -the versions of **InitializePalette()** and **ModelColorToColorIndex()** +the versions of `InitializePalette()` and `ModelColorToColorIndex()` that set up and perform the color mapping for DEMO1. **LISTING 55.2 L55-2.C** diff --git a/55-03.md b/55-03.md index eaed444..33460f5 100644 --- a/55-03.md +++ b/55-03.md @@ -87,8 +87,8 @@ workarounds can make VGA graphics look nearly as good as 24-bpp graphics; but the burden falls on you, the programmer, to design your applications and color mapping to compensate for the VGA's limitations. To experiment with a different 256-color model in X-Sharp, just change -**InitializePalette()** to set up the desired palette and -**ModelColorToColorIndex()** to map 24-bit RGB triplets into the palette +`InitializePalette()` to set up the desired palette and +`ModelColorToColorIndex()` to map 24-bit RGB triplets into the palette you've set up. It's that simple, and the results can be striking indeed. #### A Bonus from the BitMan {#Heading4} diff --git a/56-02.md b/56-02.md index 0c6c99d..fcd1856 100644 --- a/56-02.md +++ b/56-02.md @@ -12,13 +12,13 @@ pages: 1050-1053 Ah, but what is an "equivalent amount"? Think of it this way. If a destination edge is 100 scan lines high, it will be stepped 100 times. -Then, we'll divide the **SourceXWidth** and **SourceYHeight** lengths of +Then, we'll divide the `SourceXWidth` and `SourceYHeight` lengths of the source edge by 100, and add those amounts to the source edge's coordinates each time the destination is stepped one scan line. Put another way, we have, as usual, arranged things so that in the -destination polygon we step **DestYHeight** times, where **DestYHeight** +destination polygon we step `DestYHeight` times, where `DestYHeight` is the height of the destination edge. The this approach arranges to -step the source image edge **DestYHeight** times also, to match what the +step the source image edge `DestYHeight` times also, to match what the destination is doing. ![**Figure 56.3**  *Mapping a texture onto a 2-D rotated polygon.*](images/56-03.jpg) @@ -26,18 +26,18 @@ destination is doing. 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 destination scan line uses precisely the same technique, as shown in -Figure 56.4. In the destination, we step **DestXWidth** times across +Figure 56.4. In the destination, we step `DestXWidth` times across each scan line of the polygon, once for each pixel on the scan line. -(**DestXWidth** is the horizontal distance between the two edges being +(`DestXWidth` is the horizontal distance between the two edges being scanned on any given scan line.) To match this, we divide -**SourceXWidth** and **SourceYHeight** (the lengths of the scan line in +`SourceXWidth` and `SourceYHeight` (the lengths of the scan line in the source image, as determined by the source edge points we've been tracking, as just described) by the width of the destination scan line, -**DestXWidth**, to produce **SourceXStep** and **SourceYStep**. Then, we -just step **DestXWidth** times, adding **SourceXStep** and -**SourceYStep** to **SourceX** and **SourceY** each time, and choose the -nearest image pixel to (**SourceX**,**SourceY**) to copy to (**DestX**, -**DestY**). (Note that the names used above, such as **SourceXWidth**, +`DestXWidth`, to produce `SourceXStep` and `SourceYStep`. Then, we +just step `DestXWidth` times, adding `SourceXStep` and +`SourceYStep` to `SourceX` and `SourceY` each time, and choose the +nearest image pixel to (`SourceX`,`SourceY`) to copy to (`DestX`, +`DestY`). (Note that the names used above, such as `SourceXWidth`, are used for descriptive purposes, and don't necessarily correspond to the actual variable names used in Listing 56.2.) diff --git a/57-04.md b/57-04.md index cc8ad0d..86b22c2 100644 --- a/57-04.md +++ b/57-04.md @@ -19,7 +19,7 @@ X (the undocumented 320x240 256-color VGA mode X-Sharp runs in) doesn't lend itself well to pixel-oriented operations like line drawing or texture mapping, the inner loop has been set up to minimize Mode X's overhead. A rotating plane mask is maintained in AL, with DX pointing to -the Map Mask register; thus, only a rotate and an **OUT** are required +the Map Mask register; thus, only a rotate and an `OUT` are required to select the plane to which to write, cycling from plane 0 through plane 3 and wrapping back to 0. Better yet, because we know that we're simply stepping horizontally across the destination scan line, we can diff --git a/58-02.md b/58-02.md index e60c495..87db21b 100644 --- a/58-02.md +++ b/58-02.md @@ -63,11 +63,11 @@ NoExtraYAdvance: Figure 58.2 shows why this cycling is necessary. In Mode X, the page-flipped 256-color mode of the VGA, each successive pixel across a -scanline is stored in a different hardware plane, and an **OUT** to the +scanline is stored in a different hardware plane, and an `OUT` to the VGA's hardware is needed to select the plane being drawn to. (See -Chapters 47, 48, and 49 for details.) An **OUT** instruction *by itself* +Chapters 47, 48, and 49 for details.) An `OUT` instruction *by itself* takes 16 cycles (and in the neighborhood of 30 cycles in virtual-86 or -non-privileged protected mode), and an **ROL** takes 2 more, for a total +non-privileged protected mode), and an `ROL` takes 2 more, for a total 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. @@ -118,9 +118,9 @@ between columns—outside the inner loop. A trivial change, not fundamental in any sense—and yet just that one change, plus unrolling the loop, reduced the inner loop to the 22-cycles-per-pixel version shown in Listing 58.2. That's exactly twice as fast as Listing 58.1—and -given how incredibly slow most VGAs are at completing **OUT**s, the +given how incredibly slow most VGAs are at completing `OUT`s, the real-world speedup should be considerably greater still. (The fastest -byte **OUT** I've ever measured for a VGA is 29 cycles, the slowest more +byte `OUT` I've ever measured for a VGA is 29 cycles, the slowest more than 60 cycles; in the latter case, Listing 58.2 would be on the order of *four* times faster than Listing 58.1.) diff --git a/58-03.md b/58-03.md index 4d68854..a4ceb48 100644 --- a/58-03.md +++ b/58-03.md @@ -73,11 +73,11 @@ MOV AH,[BX] ;cycle 1 U-pipe MOV [DI],AH ;cycle 2 U-pipe ``` -The second **MOV**, being dependent on the value loaded into AH by the -first **MOV**, can't execute until the first **MOV** is finished, so the +The second `MOV`, being dependent on the value loaded into AH by the +first `MOV`, can't execute until the first `MOV` is finished, so the Pentium's second pipe, the V-pipe, lies idle for a cycle. We can reclaim that cycle simply by shuffling another instruction between the two -**MOV**s. +`MOV`s. Advancing the destination pointer is easy to speed up: Just build the offset from one scanline to the next into each pixel-drawing instruction @@ -108,8 +108,8 @@ 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. Second, we can recognize that X advances exactly one extra byte when its -fractional part carries, and use **ADC** to account for X carries, as -shown in Figure 58.5. That single **ADC** can add in not only any X +fractional part carries, and use `ADC` to account for X carries, as +shown in Figure 58.5. That single `ADC` can add in not only any X carry, but both the X and Y integer advance amounts as well, thereby eliminating a good chunk of the source-advance code in Listing 58.2. Furthermore, we should somehow be able to use 32-bit registers and @@ -128,8 +128,8 @@ with bit 15 of EDX always kept at zero; this allows bit 15 to store the carry status from each Y advance. We can similarly store the fractional X and Y advance amounts in ECX, and can store the sum of the integer parts of the X and Y advance amounts in BP. With this arrangement, the -single instruction **ADD EDX,ECX** advances the fractional parts of both -X and Y, and the following instruction **ADC SI,BP** finishes advancing +single instruction `ADD EDX,ECX` advances the fractional parts of both +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. diff --git a/58-04.md b/58-04.md index c78401b..2843f66 100644 --- a/58-04.md +++ b/58-04.md @@ -91,7 +91,7 @@ time, but we'll also never be sure we were right; we'll know only that And yet—*someone* has to blaze the trail to higher performance, and that someone might as well be us. Let's look for weaknesses in Listing 58.3. None are readily apparent; the only cycle that looks even slightly -wasted is the size prefix on **ADD EDX,ECX**. As it turns out, that +wasted is the size prefix on `ADD EDX,ECX`. As it turns out, that cycle really *is* wasted, for there's a way to make the size prefix vanish without losing the benefits of 32-bit instructions: Move the code into a 32-bit segment and make *all* the instructions 32-bit. That's diff --git a/58-05.md b/58-05.md index 4f285f7..5e8f0d8 100644 --- a/58-05.md +++ b/58-05.md @@ -64,8 +64,8 @@ at what you can do! ### Texture Mapping Notes {#Heading8} Listing 58.3 contains no 486 pipeline stalls; it has Pentium stalls, but -not much can be done for them because of the size prefix on **ADD -EDX,ECX**, which takes 1 cycle to go through the U-pipe, and shuts down +not much can be done for them because of the size prefix on `ADD +EDX,ECX`, which takes 1 cycle to go through the U-pipe, and shuts down the V-pipe for that cycle. Listing 58.4, on the other hand, has been rearranged to eliminate all Pentium stalls save one. When the Y coordinate fractional part carries and ESI advances, the code executes diff --git a/59-03.md b/59-03.md index b28c4f8..c644910 100644 --- a/59-03.md +++ b/59-03.md @@ -96,7 +96,7 @@ and which is farther. Listing 59.1 shows a function that draws a BSP tree back-to-front. The decision whether a node's wall is facing forward, made by -**WallFacingForward()** in Listing 59.1, can, in general, be made by +`WallFacingForward()` in Listing 59.1, can, in general, be made by generating a normal to the node's wall in screenspace (perspective-corrected space as seen from the viewpoint) and checking whether the z component of the normal is positive or negative, or by diff --git a/59-04.md b/59-04.md index e650b99..fd4bab0 100644 --- a/59-04.md +++ b/59-04.md @@ -34,7 +34,7 @@ and, to my astonishment, not one interviewee has done a good job with this one yet. I ask the question in two stages, and I get remarkably consistent results. -First, I ask for an implementation of a function **WalkTree()** that +First, I ask for an implementation of a function `WalkTree()` that 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. @@ -110,7 +110,7 @@ through and through. They understand the concepts of visiting left and right subtrees, and they have a general picture of how traversal moves about the tree, but they do not understand exactly what the code-recursive version does. If they really comprehended everything that -happens in each iteration of **WalkTree()**—how each call saves the +happens in each iteration of `WalkTree()`—how each call saves the state, and what that implies for the order in which operations are performed—they would simply and without fuss implement code like that in Listing 59.4, working with the code-recursive version as a model. diff --git a/59-05.md b/59-05.md index 45b40bf..e57ec7d 100644 --- a/59-05.md +++ b/59-05.md @@ -26,7 +26,7 @@ stack that tells me to come back here when the left subtree is done. If, after visiting a node, there are no right children to visit and nothing left on the stack, I'm finished. The code does this at each node—and that's *all* it does. That's all Listing 59.4 does, too, but people tend -to get tangled up in pushes and pops and **while** loops when they use +to get tangled up in pushes and pops and `while` loops when they use data recursion. When the implementation model changes to one with which they are unfamiliar, they abandon the perfectly good model they used before and try to rederive it in the new context by the seat of their @@ -59,13 +59,13 @@ pants. #### Measure and Learn {#Heading10} How much difference does all this fuss make, anyway? Listing 59.5 is a -sample program that builds a tree, then calls **WalkTree** () to walk it +sample program that builds a tree, then calls `WalkTree` () to walk it 1,000 times, and times how long this takes. Using 32-bit Visual C++ 1.10 running on Windows NT, with default optimization selected, Listing 59.5 reports that Listing 59.4 is about 20 percent faster than Listing 59.2 on a 486/33, a reasonable return for a little code rearrangement, especially when you consider that the speedup is diluted by calling the -**Visit()** function and by the cache miss that happens on virtually +`Visit()` function and by the cache miss that happens on virtually every node access. (Listing 59.5 builds a rather unique tree, one in which every node has exactly two children. Different sorts of trees can and do produce different performance results. Always know what you're diff --git a/59-06.md b/59-06.md index beeb0f6..ee8f599 100644 --- a/59-06.md +++ b/59-06.md @@ -17,7 +17,7 @@ good job with Listing 59.2. Most impressively, when compiling Listing 59.2, the compiler actually converts all right-subtree descents from code recursion to data recursion, by simply jumping back to the left-subtree handling code instead of recursively calling -**WalkTree()**. This means that half the time Listing 59.4 has no +`WalkTree()`. This means that half the time Listing 59.4 has no advantage over Listing 59.2; in fact, it's at a disadvantage because the code that the compiler generates for handling right-subtree descent in Listing 59.4 is somewhat inefficient, but the right-subtree code in @@ -28,7 +28,7 @@ recursion than with code recursion, the advantage is only four instructions, because only one parameter is passed and because the compiler doesn't bother setting up an EBP-based stack frame, instead it uses ESP to address the stack. (And, in fact, this cost could be reduced -still further by eliminating the check for a NULL **pNode** at all but +still further by eliminating the check for a NULL `pNode` at all but the top level.) There are other interesting aspects to what the compiler does with Listings 59.2 and 59.4 but that's enough to give you the idea. It's worth noting that the compiler might not do as well with code diff --git a/60-02.md b/60-02.md index 37e4aca..3ef823e 100644 --- a/60-02.md +++ b/60-02.md @@ -130,12 +130,12 @@ builds the BSP tree. (Note that Listing 60.1 is excerpted from a C++ may even compile as a .C file, though I haven't checked.) The compiler begins by setting up an empty tree, then passes that tree and the complete set of line segments from which a BSP tree is to be generated -to **SelectBSPTree()**, which chooses a root node and calls -**BuildBSPTree()** to add that node to the tree and generate child trees -for each of the node's two subspaces. **BuildBSPTree()** calls -**SelectBSPTree()** recursively to select a root node for each of those +to `SelectBSPTree()`, which chooses a root node and calls +`BuildBSPTree()` to add that node to the tree and generate child trees +for each of the node's two subspaces. `BuildBSPTree()` calls +`SelectBSPTree()` recursively to select a root node for each of those child trees, and this continues until all lines have been assigned -nodes. **SelectBSP()** uses parametric clipping to decide on the -splitter, as described below, and **BuildBSPTree()** uses parametric +nodes. `SelectBSP()` uses parametric clipping to decide on the +splitter, as described below, and `BuildBSPTree()` uses parametric clipping to decide which subspace of the splitter each line belongs in, and to split lines, if necessary. diff --git a/61-01.md b/61-01.md index 04d4259..e7d3058 100644 --- a/61-01.md +++ b/61-01.md @@ -104,7 +104,7 @@ pretty sloppy definition, but it'll do for our purposes; if you want the Real McCoy, I suggest you check out *Calculus and Analytic Geometry*, by Thomas and Finney (Addison-Wesley: ISBN 0-201-52929-7). -So, for example, in 3-D, the vector **V** = [5 0 5] has a length, or +So, for example, in 3-D, the vector `V` = [5 0 5] has a length, or magnitude, by the Pythagorean theorem, of ![](images/61-01d.jpg) diff --git a/61-02.md b/61-02.md index 7c6b7cb..9230ac9 100644 --- a/61-02.md +++ b/61-02.md @@ -40,8 +40,8 @@ book dealing with my X-Sharp 3-D graphics library. ### The Dot Product {#Heading5} -Now we're ready to move on to the dot product. Given two vectors **U** = -[u~1~ u~2~ u~3~] and **V** = [v~1~ v~2~ v~3~], their dot product, +Now we're ready to move on to the dot product. Given two vectors `U` = +[u~1~ u~2~ u~3~] and `V` = [v~1~ v~2~ v~3~], their dot product, denoted by the symbol •, is calculated as: ![](images/61-02d.jpg) @@ -90,9 +90,9 @@ intensity at which the surface is illuminated, as in ![**Figure 61.1**  *The dot product.*](images/61-01.jpg) -where **I**~s~ is the intensity of illumination of the surface, **I**~l~ +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~ -(where **D**~l~ is the light direction vector) and the surface normal. +(where `D`~l~ is the light direction vector) and the surface normal. If the inverse light vector and the surface normal are both unit vectors, then this calculation can be performed with four multiplies and three additions—and no explicit cosine calculations—as @@ -101,7 +101,7 @@ three additions—and no explicit cosine calculations—as (eq. 6) -where **N**~s~ is the surface unit normal and **D**~l~ is the light unit +where `N`~s~ is the surface unit normal and `D`~l~ is the light unit direction vector, as shown in Figure 61.2. ### Cross Products and the Generation of Polygon Normals {#Heading7} diff --git a/61-04.md b/61-04.md index 66c9e21..60a618a 100644 --- a/61-04.md +++ b/61-04.md @@ -13,7 +13,7 @@ pages: 1141-1144 ### Using the Dot Product for Projection {#Heading9} Consider Equation 3 again, but this time make one of the vectors, say -**V**, a unit vector. Now the equation reduces to: +`V`, a unit vector. Now the equation reduces to: ![](images/61-08d.jpg) @@ -32,8 +32,8 @@ projected onto the unit vector, as shown in Figure 61.6. 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 -origin **O**~p~ with the plane unit normal **N**~p~, to project the +point to a plane? Just dot the vector from the point `P` to the plane +origin `O`~p~ with the plane unit normal `N`~p~, to project the vector onto the normal, then take the absolute value distance = |(P - Op) • Np| diff --git a/62-01.md b/62-01.md index 1893658..c81fbc9 100644 --- a/62-01.md +++ b/62-01.md @@ -35,16 +35,16 @@ to seek out areas for improvement in Quake and, for no particular reason, checked the number of writes performed while copying the frame to the screen in non-page-flipped mode. The answer was 64,000. That seemed odd, since there were 64,000 byte-sized pixels to copy, and I was -calling **memcpy()**, which of course performs copies a dword at a time +calling `memcpy()`, which of course performs copies a dword at a time whenever possible. I thought maybe the Pentium counters report the number of bytes written rather than the number of writes performed, but fortunately, this time I tested my assumptions by writing an ASM routine -to copy the frame a dword at a time, without the help of **memcpy()**. +to copy the frame a dword at a time, without the help of `memcpy()`. This time the Pentium counters reported 16,000 writes. Whoops. -As it turns out, the **memcpy()** routine in the DOS version of our +As it turns out, the `memcpy()` routine in the DOS version of our compiler (gcc) inexplicably copies memory a byte at a time. With my new routine, the non-page-flipped approach suddenly became slightly *faster* than page flipping. @@ -57,7 +57,7 @@ The second rule: When you do look foolish (and trust me, it *will* happen if you do challenging work) have a good laugh at yourself, and use it as a reminder of Rule \#1. I hadn't done any extra page-flipping work yet, so I didn't waste any time due to my faulty assumption that -**memcpy()** performed a maximum-speed copy, but that was just luck. I +`memcpy()` performed a maximum-speed copy, but that was just luck. I should have done experiments until I was sure I knew what was going on before drawing any conclusions and acting on them. @@ -67,7 +67,7 @@ before drawing any conclusions and acting on them. > always, always, always keep asking questions. It'll pay off big in the > long run. If I hadn't indulged my curiosity by running the Pentium > counter test on the copy to the screen, even though there was no -> specific reason to do so, I would never have discovered the **memcpy()** +> specific reason to do so, I would never have discovered the `memcpy()` > problem—and by so doing I doubled the performance of the entire program > in five minutes, a rare accomplishment indeed. diff --git a/62-03.md b/62-03.md index d021600..7d376f0 100644 --- a/62-03.md +++ b/62-03.md @@ -14,7 +14,7 @@ pages: 1157-1160 Conceptually rendering from a BSP tree really is that simple, but the implementation is a bit more complicated. The full rendering pipeline, -as coordinated by **UpdateWorld()**, is this: +as coordinated by `UpdateWorld()`, is this: * Update the current location. * Transform all wall endpoints into viewspace (the world as seen from @@ -37,7 +37,7 @@ The sample BSP program performs first-person rendering; that is, it renders the world as seen from your eyes as you move about. The rate of movement is controlled by key-handling code that's not shown in Listing 62.1; however, the variables set by the key-handling code are used in -**UpdateViewPos()** to bring the current location up to date. +`UpdateViewPos()` to bring the current location up to date. Note that the view position can change not only in x and z (movement around the but only viewing horizontally. Although the BSP tree is only @@ -54,7 +54,7 @@ The viewing angle (which controls direction of movement as well as view direction) can sweep through the full 360 degrees around the viewpoint, so long as it remains horizontal. The viewing angle is controlled by the key handler, and is used to define a unit vector stored in -**currentorientation** that explicitly defines the view direction (the z +`currentorientation` that explicitly defines the view direction (the z axis of viewspace), and implicitly defines the x axis of viewspace, because that axis is at right angles to the z axis, where x increases to the right of the viewer. @@ -62,7 +62,7 @@ the right of the viewer. As I discussed in the previous chapter, rotation to a new coordinate system can be performed by using the dot product to project points onto the axes of the new coordinate system, and that's what -**TransformVertices()** does, after first translating (moving) the +`TransformVertices()` does, after first translating (moving) the coordinate system to have its origin at the viewpoint. (It's necessary to perform the translation first so that the viewing rotation is around the viewpoint.) Note that this operation can equivalently be viewed as a @@ -70,7 +70,7 @@ matrix math operation, and that this is in fact the more common way to handle transformations. At the same time, the points are scaled in x according to -**PROJECTION\_RATIO** to provide the desired field of view. Larger scale +`PROJECTION_RATIO` to provide the desired field of view. Larger scale values result in narrower fields of view. When this is done the walls are in viewspace, ready to be clipped. @@ -85,7 +85,7 @@ walls—walls that lie entirely in the frustum—should be drawn in their entirety, fully clipped walls should not be drawn, and partially clipped walls must be trimmed before being drawn. -In Listing 62.1, **ClipWalls()** does this in three steps for each wall +In Listing 62.1, `ClipWalls()` does this in three steps for each wall in turn. First, the z coordinates of the two ends of the wall are calculated. (Remember, walls are vertical and their ends go straight up and down, so the top and bottom of each end have the same x and z @@ -116,7 +116,7 @@ and -x==z. 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 shown in Figure 62.3, so true 3-D clipping of all four wall vertices is -involved. We handle this in **ClipWalls()** by detecting trivial +involved. We handle this in `ClipWalls()` by detecting trivial rejection in y, using y==z and ==z as the y boundaries of the frustum. However, we leave partial clipping to be handled as a 2-D clipping problem; we are able to do this only because our earlier z-clip to the @@ -131,6 +131,6 @@ partially visible. All we have to do is project these vertices according to z distance—that is, perform perspective projection—and scale the 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()**. +as the last step for visible walls in `ClipWalls()`. ![**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 5732f1d..a073584 100644 --- a/62-04.md +++ b/62-04.md @@ -14,7 +14,7 @@ pages: 1160-1162 Now that we have all the walls clipped to the frustum, with vertices projected into screen coordinates, all we have to do is draw them back -to front; that's the job of **DrawWallsBackToFront()**. Basically, this +to front; that's the job of `DrawWallsBackToFront()`. Basically, this routine walks the BSP tree, descending recursively from each node to draw the farther children of each node first, then the wall at the node, then the nearer children. In the interests of efficiency, this @@ -24,7 +24,7 @@ performance speedup from data recursion turned out to be more modest than I had expected, based on past experience; see Chapter 59 for further details. -As it comes to each wall, **DrawWallsBackToFront()** first descends to +As it comes to each wall, `DrawWallsBackToFront()` first descends to draw the farther subtree. Next, if the wall is both visible and pointing toward the viewer, it is drawn as a solid polygon. The polygon filler (not shown in Listing 62.1) is a modification of the polygon filler I @@ -53,7 +53,7 @@ coordinates of the start and end vertices, a simple 2-D version of checking the direction of the screenspace normal. The wall orinetation test used for walking the BSP tree, performed in -**WallFacingViewer()** takes the other approach, and checks the +`WallFacingViewer()` takes the other approach, and checks the viewspace sign of the dot product of the wall's normal with a vector from the viewpoint to the wall. Again, this code takes advantage of the 2-D nature of the tree to generate the wall normal by swapping x and z @@ -63,7 +63,7 @@ projected into screenspace; for example, trying to project a wall at z==0 would result in division by zero. All the visible, front-facing walls are drawn into a buffer by -**DrawWallsBackToFront()**, then **UpdateWorld()** calls Win32 to copy +`DrawWallsBackToFront()`, then `UpdateWorld()` calls Win32 to copy the new frame to the screen. The frame of animation is complete. ![**Figure 62.4**  *Fast backspace culling test in screenspace.*](images/62-04.jpg) diff --git a/63-04.md b/63-04.md index 09a8374..bf12bdb 100644 --- a/63-04.md +++ b/63-04.md @@ -109,9 +109,9 @@ and each FLDCW takes 7 cycles, meaning that compilers often take at least 14 cycles for each float-\>int conversion. In assembly, you can just set the rounding state (or, likewise, the precision, for faster FDIVs) once at the start of the loop, and save all those FLDCW cycles -each time through the loop. This is even more true for **ceil()**, which +each time through the loop. This is even more true for `ceil()`, which many compilers implement as horrendously inefficient subroutines, even -though there are rounding modes for both **ceil()** and **floor()**. +though there are rounding modes for both `ceil()` and `floor()`. Again, though, be aware that results of FP calculations will be subtly different from compiler default behavior while chop, ceil, or floor mode is in effect. diff --git a/67-04.md b/67-04.md index e104d2f..85eee51 100644 --- a/67-04.md +++ b/67-04.md @@ -19,19 +19,19 @@ needed was backface culling and a polygon rasterizer. Listing 67.1 replaces this simple pipeline with a three-stage HSR process. After backface culling, the edges of each of the polygons in the scene are added to the global edge list, by way of -**AddPolygonEdges()**. After all edges have been added, the edges are -turned into spans by **ScanEdges()**, with each pixel on the screen +`AddPolygonEdges()`. After all edges have been added, the edges are +turned into spans by `ScanEdges()`, with each pixel on the screen being covered by one and only one span (that is, there's no overdraw). Once all the spans have been generated, they're drawn by -**DrawSpans()**, and rasterization is complete. +`DrawSpans()`, and rasterization is complete. -There's nothing tricky about **AddPolygonEdges()**, and **DrawSpans()**, +There's nothing tricky about `AddPolygonEdges()`, and `DrawSpans()`, as implemented in Listing 67.1, is very straightforward as well. In an implementation that supported texture mapping, however, all the spans wouldn't be put on one global span list and drawn at once, as is done in Listing 67.1, because that would result in drawing spans from all the surfaces in no particular order. (A surface is a drawing object that's -originally described by a polygon, but in **ScanEdges()** there is no +originally described by a polygon, but in `ScanEdges()` there is no polygon in the classic sense of a set of vertices bounding an area, but rather just a set of edges and a surface that describes how to draw the spans outlined by those edges.) That would mean constantly skipping from @@ -43,13 +43,13 @@ surface, and draw all the spans for one surface before moving on to the next surface. The core of Listing 67.1, and the most complex aspect of 1/z-sorted -spans, is **ScanEdges()**, where the global edge list is converted into +spans, is `ScanEdges()`, where the global edge list is converted into a set of spans describing the nearest surface at each pixel. This process is actually pretty simple, though, if you think of it as follows: For each scan line, there is a set of active edges, which are those -edges that intersect the scan line. A good part of **ScanEdges()** is +edges that intersect the scan line. A good part of `ScanEdges()` is dedicated to adding any edges that first appear on the current scan line (scan lines are processed from the top scan line on the screen to the bottom), removing edges that reach their bottom on the current scan @@ -58,7 +58,7 @@ next scan can be processed from left to right. All this is per-scan-line maintenance, and is basically just linked list insertion, deletion, and sorting. -The heart of the action is the loop in **ScanEdges()** that processes +The heart of the action is the loop in `ScanEdges()` that processes the edges on the current scan line from left to right, generating spans as needed. The best way to think of this loop is as a surface event processor, where each edge is an event with an associated surface. Each @@ -74,7 +74,7 @@ leading and trailing edges do not need to be explicitly paired, because they are implicitly paired by pointing to the same surface. This saves the memory and time that would otherwise be needed to track edge pairs. -One more element is required in order for **ScanEdges()** to work +One more element is required in order for `ScanEdges()` to work efficiently. Each time a leading or trailing edge occurs, it must be determined whether its surface is nearest (at a larger 1/z value than any currently active surface). In addition, for leading edges, the diff --git a/67-05.md b/67-05.md index 28b2d1a..d7eb43a 100644 --- a/67-05.md +++ b/67-05.md @@ -42,11 +42,11 @@ would be dynamically allocated from a vertex pool, so each polygon wouldn't have to contain enough space for the maximum possible number of vertices. -Each surface has a field named **state**, which is incremented when a +Each surface has a field named `state`, which is incremented when a leading edge for that surface is encountered, and decremented when a trailing edge is reached. A surface is activated by a leading edge only -if **state** increments to 1, and is deactivated by a trailing edge only -if **state** decrements to 0. This is another guard against arithmetic +if `state` increments to 1, and is deactivated by a trailing edge only +if `state` decrements to 0. This is another guard against arithmetic problems, in this case quantization during the conversion of vertex coordinates from floating point to fixed point. Due to this conversion, it is possible, although rare, for a polygon that is viewed nearly @@ -62,7 +62,7 @@ Lastly, as discussed in Chapter 66, Listing 67.1 uses the gradients for active surfaces each time a leading edge needs to be sorted into the surface stack. The natural origin for gradient calculations is the center of the screen, which is (x,y) coordinate (0,0) in viewspace. -However, when the gradients are calculated in **AddPolygonEdges()**, the +However, when the gradients are calculated in `AddPolygonEdges()`, the origin value is calculated at the upper-left corner of the screen. This is done so that screen x and y coordinates can be used directly to calculate 1/z, with no need to adjust the coordinates to be relative to diff --git a/70-02.md b/70-02.md index 5bae907..f71b80d 100644 --- a/70-02.md +++ b/70-02.md @@ -39,8 +39,8 @@ given viewpoint, as well as the entities that have to be updated over the network (for multiplayer games) and drawn. Calculating the PVS is expensive; Quake levels take 10 to 30 minutes to process on a four-processor Alpha, and even with speedup tweaks to the BSPer (the -most effective of which was replacing many calls to **malloc()** with -stack-based structures—beware of **malloc()** in performance-sensitive +most effective of which was replacing many calls to `malloc()` with +stack-based structures—beware of `malloc()` in performance-sensitive code), Quake 2 levels are taking up to an hour to process. (Note, however, that that includes BSPing, PVS calculations, and radiosity lighting, which I'll discuss later.) diff --git a/book-index.md b/book-index.md index a1d01ad..957269a 100644 --- a/book-index.md +++ b/book-index.md @@ -10,33 +10,33 @@ category: 'Web and Software Development: Game Development,Web and Software Devel # Index {#Heading1} -**Numbers** +`Numbers` 1/z sorting abutting span sorting, 1229-1230 -**AddPolygonEdges** function, **1232-1233,** 1238 +`AddPolygonEdges` function, **1232-1233,** 1238 vs. BSP-order sorting, 1226-1227 calculating 1/z value, 1220-1222 -**ClearEdgeLists** function, **1236-1237** +`ClearEdgeLists` function, **1236-1237** -**DrawSpans** function, **1236** +`DrawSpans` function, `1236` independent span sorting, 1230, **1231-1238,** 1239-1241 intersecting span sorting, 1228-1229 -**PolyFacesViewer** function, **1232** +`PolyFacesViewer` function, `1232` reliability, 1227 -**ScanEdges** function, **1234-1236,** 1238-1239 +`ScanEdges` function, **1234-1236,** 1238-1239 -**UpdateWorld** function, 1237-1238 +`UpdateWorld` function, 1237-1238 3-D animation @@ -55,13 +55,13 @@ depth sorting, 1000, **1001-1002** rotation -**ConcatXforms** function, **944** +`ConcatXforms` function, `944` matrix representation, 938-939 multiple axes of rotation, 948 -**XformVec** function, **943** +`XformVec` function, `943` rounding vs. truncation, 1002-1003 @@ -77,35 +77,35 @@ overview, 1195 polygon clipping -**BackRotateVector** function, **1203** +`BackRotateVector` function, `1203` clipping to frustum, 1200, **1201-1206,** 1206-1207 -**ClipToFrustum** function, **1204** +`ClipToFrustum` function, `1204` -**ClipToPlane** function, **1199** +`ClipToPlane` function, `1199` optimization, 1207 overview, 1197-1200 -**PolyFacesViewer** function, **1203** +`PolyFacesViewer` function, `1203` -**ProjectPolygon** function, **1201** +`ProjectPolygon` function, `1201` -**SetUpFrustum** function, **1204** +`SetUpFrustum` function, `1204` -**SetWorldspace** function, **1204** +`SetWorldspace` function, `1204` -**TransformPoint** function, **1203** +`TransformPoint` function, `1203` -**TransformPolygon** function, **1203** +`TransformPolygon` function, `1203` -**UpdateWorld** function, **1205** +`UpdateWorld` function, `1205` viewspace clipping, 1207 -**ZSortObjects** function, **1201** +`ZSortObjects` function, `1201` 3-D drawing @@ -168,25 +168,25 @@ rendering BSP trees clipping, 1158-1159 -**ClipWalls** function, **1152-1155,** 1158-1159 +`ClipWalls` function, **1152-1155,** 1158-1159 -**DrawWallsBackToFront** function, **1155-1156,** 1160-1161 +`DrawWallsBackToFront` function, **1155-1156,** 1160-1161 overview, 1149 reference materials, 1157 -**TransformVertices** function, **1151-1152,** 1158 +`TransformVertices` function, **1151-1152,** 1158 -**UpdateViewPos** function, **1151,** 1157 +`UpdateViewPos` function, **1151,** 1157 -**UpdateWorld** function, **1156-1157,** 1157 +`UpdateWorld` function, **1156-1157,** 1157 viewspace, transformation of objects to, 1158 wall orientation testing, 1160-1161 -**WallFacingViewer** function, **1150-1151,** 1161 +`WallFacingViewer` function, **1150-1151,** 1161 span-based drawing, and beam trees, 1187 @@ -345,11 +345,11 @@ palette RAM, 626 *See also* Registers; VGA registers. -adding with **LEA,** 131 +adding with `LEA`, 131 -**BSWAP** instruction, 252 +`BSWAP` instruction, 252 -multiplying with **LEA,** 132-133 +multiplying with `LEA`, 132-133 386 processor, 222 @@ -369,7 +369,7 @@ resolution, 360x480 256-color mode, 619-620 286 processor -**CMP** instruction, 161, 306 +`CMP` instruction, 161, 306 code alignment, 215-218 @@ -389,17 +389,17 @@ effective address calculations, 129, 223-225 instruction fetching, 215-218 -**LEA** vs. **ADD** instructions, 130 +`LEA` vs. `ADD` instructions, 130 lookup tables, vs. rotating or shifting, 145-146 -**LOOP** instruction vs. **DEC/JNZ** sequence, 139 +`LOOP` instruction vs. `DEC/JNZ` sequence, 139 memory access, performance, 223-225 new features, 221 -**POPF** instruction, and interrupts, 226 +`POPF` instruction, and interrupts, 226 protected mode, 208-209 @@ -427,7 +427,7 @@ pixel drawing demo program, **593-598,** 599-600 display memory, accessing, 621-622 -**Draw360x480Dot** subroutine, **613-614** +`Draw360x480Dot` subroutine, **613-614** drawing speed, 618 @@ -439,7 +439,7 @@ mode set routine (John Bridges), 609, **612,** 620-621 on VGA clones, 610-611 -**Read360x480Dot** subroutine, **614-615** +`Read360x480Dot` subroutine, **614-615** 256-color resolution, 619-620 @@ -451,7 +451,7 @@ vertical resolution, 619 alignment, stack pointer, 218-219 -**CMP** instruction, 161, 306 +`CMP` instruction, 161, 306 cycle-eaters, 209-210 @@ -467,17 +467,17 @@ DRAM refresh cycle-eater, 219 effective address calculations, 129, 223-225 -**LEA** instruction, 130-133, 172 +`LEA` instruction, 130-133, 172 -**LODSD** vs. **MOV/LEA** sequence, 171 +`LODSD` vs. `MOV/LEA` sequence, 171 lookup tables, vs. rotating or shifting, 145-146 -**LOOP** instruction vs. **DEC/JNZ** sequence, 139 +`LOOP` instruction vs. `DEC/JNZ` sequence, 139 memory access, performance, 223-225 -**MUL** and **IMUL** instructions, 173-174 +`MUL` and `IMUL` instructions, 173-174 multiplication operations, increasing speed of, 173-174 @@ -497,7 +497,7 @@ system wait states, 210-212 using 32-bit register as two 16-bit registers, 253-254 -**XCHG** vs. **MOV** instructions, 377, 832 +`XCHG` vs. `MOV` instructions, 377, 832 386SX processor, 16-bit bus cycle-eater, 81 @@ -507,11 +507,11 @@ AX register, setting to absolute value, 172 byte registers and lost cycles, 242-245 -**CMP** instruction +`CMP` instruction operands, order of, 306 -vs. **SCASW,** 161 +vs. `SCASW`, 161 copying bytes between registers, 172 @@ -525,19 +525,19 @@ effect on code timing, 246 optimization, 236 -**LAHF** and **SAHF** instructions, 148 +`LAHF` and `SAHF` instructions, 148 -**LEA** instruction, vs. **ADD,** 131 +`LEA` instruction, vs. `ADD`, 131 -**LODSB** instruction, 304 +`LODSB` instruction, 304 -**LODSD** instruction, vs. **MOV/LEA** sequence, 171 +`LODSD` instruction, vs. `MOV/LEA` sequence, 171 lookup tables, vs. rotating or shifting, 145-146 -**LOOP** instruction, vs. **DEC/JNZ** sequence, 139 +`LOOP` instruction, vs. `DEC/JNZ` sequence, 139 -**MOV** instruction, vs. **XCHG,** 377 +`MOV` instruction, vs. `XCHG`, 377 n-bit vs. 1-bit shift and rotate instructions, 255-256 @@ -559,7 +559,7 @@ timing code, 245-246 using 32-bit register as two 16-bit registers, 253-254 -**XCHG** instruction, vs. **MOV,** 377, 832 +`XCHG` instruction, vs. `MOV`, 377, 832 640x400 mode, mode set routine, **852-853** @@ -569,7 +569,7 @@ using 32-bit register as two 16-bit registers, 253-254 8088 processor -**CMP** instruction, 161, 306 +`CMP` instruction, 161, 306 cycle-eaters @@ -591,15 +591,15 @@ effective address calculation options, 129 vs. 8086 processor, 79-81 -**LAHF** and **SAHF** instructions, 148 +`LAHF` and `SAHF` instructions, 148 -**LEA** vs. **ADD,** 130 +`LEA` vs. `ADD`, 130 -**LODSB** instruction, 304 +`LODSB` instruction, 304 lookup tables, vs. rotating or shifting, 145-146 -**LOOP** instruction vs. **DEC/JNZ** sequence, 139 +`LOOP` instruction vs. `DEC/JNZ` sequence, 139 memory variables, size of, 83-85 @@ -631,7 +631,7 @@ timer operation, 43-45 undocumented features, 54, 65 -**A** +`A` Absolute value, setting AX register, 171 @@ -667,21 +667,21 @@ Active edge table (AET), 744 Adapters, display. *See* Display adapter cycle-eater. -**ADD** instruction +`ADD` instruction and Carry flag, 147-148 -vs. **INC,** 147-148, 219 +vs. `INC`, 147-148, 219 -vs. **LEA,** 130, 170-171 +vs. `LEA`, 130, 170-171 -**AddDirtyRect** function, **867-869** +`AddDirtyRect` function, **867-869** Addition, using LEA, 130, 131 -**AddObject** function, **1001-1002** +`AddObject` function, **1001-1002** -**AddPolygonEdges** function, **1232-1233,** 1238 +`AddPolygonEdges` function, **1232-1233,** 1238 Addressable memory, protected mode, 221 @@ -705,11 +705,11 @@ Addressing pipeline penalty Pentium processor, 400-403 -**AdvanceAET** function +`AdvanceAET` function complex polygons, **748-749** -monotone-vertical polygons, **769** +monotone-vertical polygons, `769` AET (active edge table), 744 @@ -727,7 +727,7 @@ non-alignment penalties, 376 TCP/IP checksum program, 409 -**REP STOS** instruction, 735 +`REP STOS` instruction, 735 386 processor, 218 @@ -753,11 +753,11 @@ overview, 451-452 Ambient shading, 1023, **1025-1027** -**AND** instruction, Pentium processor +`AND` instruction, Pentium processor AGIs (Address Generation Interlocks), 401-402 -vs. **TEST,** 377 +vs. `TEST`, 377 Animation @@ -849,11 +849,11 @@ Antialiasing, Wu's algorithm, 776-779, **780-791,** 791-792 Apparent motion, in animation, 1064 -**AppendRotationX** function, **964, 975** +`AppendRotationX` function, **964, 975** -**AppendRotationY** function, **964-965, 975** +`AppendRotationY` function, **964-965, 975** -**AppendRotationZ** function, **965, 976** +`AppendRotationZ` function, **965, 976** Appropriate technology, 775-776 @@ -911,7 +911,7 @@ Automatic variables, 184-185 AX register, setting to absolute value, 171 -**B** +`B` Backface culling. *See* Backface removal. @@ -931,7 +931,7 @@ solid cube rotation demo program, **957-961,** 962-963, **964-966,** 967 Background surfaces, 1240 -**BackRotateVector** function, **1203** +`BackRotateVector` function, `1203` Ball animation demo program, 431-441 @@ -1015,13 +1015,13 @@ Blocks. *See* Restartable blocks. Borders (overscan), 555-556 -**BOUND** instruction, 221 +`BOUND` instruction, 221 Boundary pixels, polygons rules for selecting, 712 -texture mapping, 1049-1052, 1065-1066, **1067** +texture mapping, 1049-1052, 1065-1066, `1067` Bounding volumes, 1184 @@ -1029,13 +1029,13 @@ Boyer-Moore algorithm assembly implementations, **271-274, 274-277** -C language implementation, **269** +C language implementation, `269` overview, 263-265 performance, 266-268 -test-bed program, **270** +test-bed program, `270` Branch prediction, Pentium processor, 377-378 @@ -1114,15 +1114,15 @@ potentially visible set (PVS), precalculating, 1188-1189 BSP compiler -**BuildBSPTree** function, **1125-1127** +`BuildBSPTree` function, **1125-1127** -**SelectBSPTree** function, **1124-1125** +`SelectBSPTree` function, **1124-1125** -**BuildBSPTree** function, **1125-1127** +`BuildBSPTree` function, **1125-1127** building, 1101-1104 -**BuildTree** function, **1112** +`BuildTree` function, `1112` data recursion vs. code recursion, 1108-1113 @@ -1156,27 +1156,27 @@ backface removal, 1160-1161 clipping, 1158-1159 -**ClipWalls** function, **1152-1155,** 1158-1159 +`ClipWalls` function, **1152-1155,** 1158-1159 -**DrawWallsBackToFront** function, **1155-1156,** 1160-1161 +`DrawWallsBackToFront` function, **1155-1156,** 1160-1161 overview, 1149 reference materials, 1157 -**TransformVertices** function, **1151-1152,** 1158 +`TransformVertices` function, **1151-1152,** 1158 -**UpdateViewPos** function, **1151,** 1157 +`UpdateViewPos` function, **1151,** 1157 -**UpdateWorld** function, **1156-1157,** 1157 +`UpdateWorld` function, **1156-1157,** 1157 viewspace, transformation of objects to, 1158 wall orientation testing, 1160-1161 -**WallFacingViewer** function, **1150-1151,** 1161 +`WallFacingViewer` function, **1150-1151,** 1161 -**SelectBSPTree** function, **1124-1125** +`SelectBSPTree` function, **1124-1125** splitting heuristic, 1128-1129 @@ -1200,21 +1200,21 @@ polygon culling, 1181-1184 PVS, precalculating, 1188-1189 -**WalkBSPTree** function, **1106** +`WalkBSPTree` function, `1106` -**WalkTree** function, 1109-1110 +`WalkTree` function, 1109-1110 BSP compiler -**BuildBSPTree** function, **1125-1127** +`BuildBSPTree` function, **1125-1127** overview, 1123 -**SelectBSPTree** function, **1124-1125** +`SelectBSPTree` function, **1124-1125** BSP models, Quake 3-D engine, 1284 -**BSWAP** instruction, 486 processor +`BSWAP` instruction, 486 processor 32-bit registers, using as two 16-bit registers, 253-254 @@ -1236,15 +1236,15 @@ in 16-bit checksum program, 15-16 in search engine, 114-115 -**BuildBSPTree** function, **1125-1127** +`BuildBSPTree` function, **1125-1127** -**BuildGET** function, **768-769** +`BuildGET` function, **768-769** -**BuildGETStructure** function, **747-748** +`BuildGETStructure` function, **747-748** -**BuildMaps** function, **353-355** +`BuildMaps` function, **353-355** -**BuildTree** function, **1112** +`BuildTree` function, `1112` Bus access @@ -1254,29 +1254,29 @@ Pentium processor, 377 Byte registers, 486 processor, 242-245 -Byte-**OUT** instruction, 429 +Byte-`OUT` instruction, 429 Byte-per-pixel mode. *See* Mode X. -**C** +`C` C library functions -**getc()** function, 12, 14 +`getc()` function, 12, 14 -**memchr()** function, 116 +`memchr()` function, 116 -**memcmp()** function, 116 +`memcmp()` function, 116 -**memcpy()** function, 1147-1148 +`memcpy()` function, 1147-1148 -**memset()** function, 727 +`memset()` function, 727 optimization, 15 -**read()** function, 12, 121 +`read()` function, 12, 121 -**strstr()** function, 115 +`strstr()` function, 115 Cache, internal. *See* Internal cache. @@ -1286,7 +1286,7 @@ Calculations, redundant, and optimization, 682-683 *Calculus and Analytic Geometry* (book), 1135 -**CALL** instruction +`CALL` instruction 486 processor, 241-242 @@ -1302,9 +1302,9 @@ subdivision rasterization, 1266-1267, **1267-1270** Carry flag -**DEC** instruction, 148 +`DEC` instruction, 148 -**INC** vs. **ADD** instructions, 147-148 +`INC` vs. `ADD` instructions, 147-148 LOOP instruction, 148 @@ -1314,11 +1314,11 @@ in word count program (David Stafford), 317-319 Cats, shipping via air freight, 697-698 -**Cellmap** class, **325-329, 333-335, 341-345** +`Cellmap` class, **325-329, 333-335, 341-345** Cellmap wrapping, Game of Life, 331-332, **333-335, 336,** 337-338 -**Cell\_state** method, **327, 334, 344** +`Cell_state` method, **327, 334, 344** CGA (Color/Graphics Adapter) @@ -1335,7 +1335,7 @@ rules, 346, 350 3-cell-per-word implementation (David Stafford), 351-352, **353-363,** 363-365 -**ScanBuffer** routine, 305, 307-319 +`ScanBuffer` routine, 305, 307-319 Change list, in Game of Life, 363-366 @@ -1354,9 +1354,9 @@ Chunky bitmaps, converting to planar, 504-505, **505-508** Circular linked lists, 288-292 -**Clear\_cell** method, **327, 334, 343** +`Clear_cell` method, **327, 334, 343** -**ClearEdgeLists** function, **1236-1237** +`ClearEdgeLists` function, **1236-1237** Clements, Willem, **313-315** @@ -1379,41 +1379,41 @@ overview, 1195 polygon clipping -**BackRotateVector** function, **1203** +`BackRotateVector` function, `1203` clipping to frustum, 1200, **1201-1206,** 1206-1207 -**ClipToFrustum** function, 1204 +`ClipToFrustum` function, 1204 -**ClipToPlane** function, 1199 +`ClipToPlane` function, 1199 optimization, 1207 overview, 1197-1200 -**PolyFacesViewer** function, **1203** +`PolyFacesViewer` function, `1203` -**ProjectPolygon** function, **1201** +`ProjectPolygon` function, `1201` -**SetUpFrustum** function, **1204** +`SetUpFrustum` function, `1204` -**SetWorldspace** function, **1204** +`SetWorldspace` function, `1204` -**TransformPoint** function, **1203** +`TransformPoint` function, `1203` -**TransformPolygon** function, **1203** +`TransformPolygon` function, `1203` -**UpdateViewPos** function, **1202** +`UpdateViewPos` function, `1202` -**UpdateWorld** function, **1205** +`UpdateWorld` function, `1205` viewspace clipping, 1207 -**ZSortObjects** function, **1201** +`ZSortObjects` function, `1201` -**ClipToFrustum** function, **1204** +`ClipToFrustum` function, `1204` -**ClipToPlane** function, **1199** +`ClipToPlane` function, `1199` Clock cycles @@ -1461,7 +1461,7 @@ stack addressing, 241-242 32-bit addressing modes, 256-258 -**FXCH** instruction, 1170 +`FXCH` instruction, 1170 indexed addressing, 237-238 @@ -1479,13 +1479,13 @@ non-word-alignment penalty, 217 1/z value of planes, calculating, 1221 -**OUT** instructions, 843, 1082-1083 +`OUT` instructions, 843, 1082-1083 Pentium processor branch prediction, 377-378 -cross product floating point optimization, 1171, **1172** +cross product floating point optimization, 1171, `1172` dot product floating point optimization, 1170 @@ -1493,7 +1493,7 @@ effective address calculations, 375-376 floating point instructions, 1167-1168 -**FXCH** instruction, 1170 +`FXCH` instruction, 1170 initial pipe, effect of, 405 @@ -1529,13 +1529,13 @@ effective address calculation, 223-225 system wait states, 211 -**CMP** instruction +`CMP` instruction operands, order of, 306 -vs. **SCASW,** 161 +vs. `SCASW`, 161 -**CMPXCHG8B** instruction, Pentium processor, 378 +`CMPXCHG8B` instruction, Pentium processor, 378 Code alignment @@ -1616,7 +1616,7 @@ screen blanking, **556-557** VGA, 557 -**ColorBarsUp** subroutine, **604** +`ColorBarsUp` subroutine, `604` Color-forcing demo program, **474-476** @@ -1630,7 +1630,7 @@ cautions for use of, 9 data recursion vs. code recursion, 1112-1113 -in **FindIDAverage** function, **159** +in `FindIDAverage` function, `159` Compilers @@ -1657,13 +1657,13 @@ polygon-filling programs, **745-752, 754** *Computer Graphics* (book), 1135, 1157 -**ConcatXforms** function +`ConcatXforms` function assembly implementation, **997-999, 1019-1022** C-language implementation, **944, 976** -**CONSTANT\_TO\_INDEXED\_REGISTER** macro, **594** +`CONSTANT_TO_INDEXED_REGISTER` macro, `594` Coordinate systems @@ -1671,11 +1671,11 @@ left-handed, 1140 right-handed, 935-937 -**Copy\_cells** method, 327, 333 +`Copy_cells` method, 327, 333 -**CopyDirtyRectangles** function, 850 +`CopyDirtyRectangles` function, 850 -**CopyDirtyRectangleToScreen** function, **866-867** +`CopyDirtyRectangleToScreen` function, **866-867** Copying @@ -1683,25 +1683,25 @@ bytes between registers, 172 pixels, using latches (Mode X), **905-907,** 908, -**871** +`871` -**CopyScreenToScreenMaskedX** subroutine, 918, **919-921** +`CopyScreenToScreenMaskedX` subroutine, 918, **919-921** -**CopyScreenToScreenX** subroutine, **905-907,** 908 +`CopyScreenToScreenX` subroutine, **905-907,** 908 -**CopySystemToScreenMaskedX** subroutine, **916-918** +`CopySystemToScreenMaskedX` subroutine, **916-918** -**CopySystemToScreenX** subroutine, 908, **909-911** +`CopySystemToScreenX` subroutine, 908, **909-911** -**CosSin** subroutine, **994-996,** 999, **1013-1015** +`CosSin` subroutine, **994-996,** 999, **1013-1015** -**Count\_neighbors** method, **334-335** +`Count_neighbors` method, **334-335** CPU reads from VGA memory, 526 -**CPUID** instruction, Pentium processor, 378 +`CPUID` instruction, Pentium processor, 378 -**CreateAlignedMaskedImage** function, 922-923 +`CreateAlignedMaskedImage` function, 922-923 Cross products @@ -1789,7 +1789,7 @@ wait states, 99-101 Cycles. *See* Clock cycles; Cycle-eaters. -**D** +`D` DAC (Digital/Analog Converter) @@ -1893,7 +1893,7 @@ C implementation, **1053-1058** disadvantages, 1052-1053, 1059 -**DrawTexturedPolygon,** 1055-1056 +`DrawTexturedPolygon`, 1055-1056 hardware dependence, 1053 @@ -1901,13 +1901,13 @@ multiple adjacent polygons, 1068 optimized implementation, **1069-1073,** 1074 -orientation independence, 1065-1067, **1067** +orientation independence, 1065-1067, `1067` performance, 1074 -**ScanOutLine** function, **1058-1059, 1067, 1069-1073,** 1074 +`ScanOutLine` function, **1058-1059, 1067, 1069-1073,** 1074 -**SetUpEdge** function, **1057-1058** +`SetUpEdge` function, **1057-1058** StepEdge function, 1056-1057 @@ -1915,15 +1915,15 @@ techniques, 1048-1051 *DDJ Essential Books on Graphics Programming* (CD), 1157 -**DEC** instruction +`DEC` instruction and Carry flag, 148 memory accesses, 83 -vs. **SUB,** 219 +vs. `SUB`, 219 -**DEC/JNZ** sequence, 139 +`DEC/JNZ` sequence, 139 Delay sequences @@ -1931,7 +1931,7 @@ loading palette RAM or DAC registers, 632 VGA programming, 558 -**DeleteNodeAfter** function, **284** +`DeleteNodeAfter` function, `284` Depth sorting of nonconvex objects, 1000, **1001-1002** @@ -1948,9 +1948,9 @@ Directed lighting, and shading, 1023, 1028 Directives -**EVEN,** 214 +`EVEN`, 214 -**NOSMART,** 72 +`NOSMART`, 72 Dirty-rectangle animation @@ -2034,7 +2034,7 @@ wait states, 101-103, 220, 733 Display memory planes. *See* Planes, VGA. -**DIV** instruction, 32-bit division, 181-184, 1008 +`DIV` instruction, 32-bit division, 181-184, 1008 Divide By Zero interrupt, 181 @@ -2058,7 +2058,7 @@ calculating, 1135-1137 calculating light intensity, 1137 -floating point optimization, 1170, **1171** +floating point optimization, 1170, `1171` line segments, clipping to planes, 1196-1197 @@ -2075,7 +2075,7 @@ of vectors, 1135-1136 Double-DDA texture mapping. *See* DDA (digital differential analyzer) texture mapping. -**D\_PolysetRecursiveTriangle** function, **1267-1270** +`D_PolysetRecursiveTriangle` function, **1267-1270** *Dr. Dobbs Journal,* 1190 @@ -2097,43 +2097,43 @@ and 8253 timer chip, 95 and Zen timer, 99 -**Draw360x480Dot** subroutine, **613-614** +`Draw360x480Dot` subroutine, **613-614** -**DrawBackground** function, **928** +`DrawBackground` function, `928` Draw-buffers, and beam trees, 1187 -**DrawBumperList** function, **823** +`DrawBumperList` function, `823` -**DrawEntities** function, **849, 866** +`DrawEntities` function, **849, 866** -**DrawGridCross** subroutine, **808** +`DrawGridCross` subroutine, `808` -**DrawGridVert** subroutine, **808-809** +`DrawGridVert` subroutine, **808-809** -**DrawHorizontalLineList** function +`DrawHorizontalLineList` function -monotone-vertical polygons, filling, **765** +monotone-vertical polygons, filling, `765` non-overlapping convex polygon -assembly implementation, **734** +assembly implementation, `734` C implementation, **717,** 720-721 -using memset() function, 727, **729** +using memset() function, 727, `729` -**DrawHorizontalLineList** subroutine, **941-943** +`DrawHorizontalLineList` subroutine, **941-943** -**DrawHorizontalLineSeg** function +`DrawHorizontalLineSeg` function -assembly implementation, **754** +assembly implementation, `754` C implementation, **750-751** -**DrawHorizontalRun** function, **692** +`DrawHorizontalRun` function, `692` -**DrawImage** subroutine, **828** +`DrawImage` subroutine, `828` Drawing @@ -2143,7 +2143,7 @@ fill patterns, using latches, 453 pixel drawing -**EVGADot** function, **661-662,** 669-670 +`EVGADot` function, **661-662,** 669-670 optimization, 1074, 1086 @@ -2163,31 +2163,31 @@ solid text using latches, 1039-1041, **1042-1044** using write mode 0, 832-833 -**DrawLine** function, **785** +`DrawLine` function, `785` -**DrawMasked** subroutine, **870** +`DrawMasked` subroutine, `870` -**DrawObject** subroutine, **809-810** +`DrawObject` subroutine, **809-810** -**Draw\_pixel** function, **328, 330** +`Draw_pixel` function, **328, 330** -**DrawPObject** function, **978-979, 1025-1027** +`DrawPObject` function, **978-979, 1025-1027** -**DrawRect** subroutine, **826-827** +`DrawRect` subroutine, **826-827** -**DrawSpans** function, **1236** +`DrawSpans` function, `1236` -**DrawSplitScreen** function, **824** +`DrawSplitScreen` function, `824` -**DrawTextString** subroutine, **1043-1044** +`DrawTextString` subroutine, **1043-1044** -**DrawTexturedPolygon** function, **1055-1056** +`DrawTexturedPolygon` function, **1055-1056** -**DrawVerticalRun** function, **692** +`DrawVerticalRun` function, `692` -**DrawVisibleFaces** function, **961** +`DrawVisibleFaces` function, `961` -**DrawWuLine** function +`DrawWuLine` function assembly implementation, **787-791** @@ -2207,7 +2207,7 @@ Dynamic palette adjustment, 1039 Dynamic RAM. *See* DRAM (dynamic RAM) refresh. -**E** +`E` EA (effective address) calculations @@ -2231,7 +2231,7 @@ Edge tracing overview, 711-713 -**ScanEdge** function +`ScanEdge` function assembly implementation, **735-738,** 735 @@ -2295,7 +2295,7 @@ and registers, 85 8088 processor -**CMP** instruction, 161, 306 +`CMP` instruction, 161, 306 cycle-eaters @@ -2317,15 +2317,15 @@ vs. 8086 processor, 79-81 effective address calculation options, 129 -**LAHF** and **SAHF** instructions, 148 +`LAHF` and `SAHF` instructions, 148 -**LEA** vs. **ADD,** 130 +`LEA` vs. `ADD`, 130 -**LODSB** instruction, 304 +`LODSB` instruction, 304 lookup tables, vs. rotating or shifting, 145-146 -**LOOP** instruction vs. **DEC/JNZ** sequence, 139 +`LOOP` instruction vs. `DEC/JNZ` sequence, 139 memory variables, size of, 83-85 @@ -2365,9 +2365,9 @@ setting drawing color, 666 specifying plane, 474 -**EnableSplitScreen** function, 824 +`EnableSplitScreen` function, 824 -**ENTER** instruction +`ENTER` instruction 486 processor, 241-242 @@ -2375,7 +2375,7 @@ Pentium processor, 377 286 processor, 221 -**Enter\_display\_mode** function, **328, 362** +`Enter_display_mode` function, **328, 362** Entities, Quake 3-D engine @@ -2391,7 +2391,7 @@ subdivision rasterization, 1286 z-buffering, 1285-1286 -**EraseEntities** function, **850, 867** +`EraseEntities` function, **850, 867** Error accumulation, Wu antialiasing algorithm, 778-779, 792 @@ -2419,11 +2419,11 @@ optimized assembly implementation, **200-202** recursive implementations, **198, 200** -**EVEN** directive, 214 +`EVEN` directive, 214 -**EVGADot** function, **661-662,** 669-670 +`EVGADot` function, **661-662,** 669-670 -**EVGALine** function +`EVGALine` function Bresenham's algorithm @@ -2435,15 +2435,15 @@ C-language implementation, **664-665,** 665-668, 670-671 Execution times. *See* Clock cycles; Instruction execution time. -**Exit\_display\_mode** function, **328, 329, 362** +`Exit_display_mode` function, **328, 329, 362** -**F** +`F` -**FADD** instruction, Pentium processor, 1167-1170 +`FADD` instruction, Pentium processor, 1167-1170 Far jumps, to absolute addresses, 186-187 -**FDIV** instruction, Pentium processor, 1167-1170 +`FDIV` instruction, Pentium processor, 1167-1170 Fetch time @@ -2457,9 +2457,9 @@ Files reading from -**getc()** function, 12, 14 +`getc()` function, 12, 14 -**read()** function, 12 +`read()` function, 12 restartable blocks, 16 @@ -2467,21 +2467,21 @@ text, searching for. *See* Search engine. Fill patterns, drawing using latches, 453 -**FillConvexPolygon** function, **714-716**, 720-721 +`FillConvexPolygon` function, **714-716**, 720-721 -**FillMonotoneVerticalPolygon** function, **763-764** +`FillMonotoneVerticalPolygon` function, **763-764** -**FillPatternX** subroutine, 899, **900-903**, 903-904 +`FillPatternX` subroutine, 899, **900-903**, 903-904 -**FillPolygon** function +`FillPolygon` function -complex polygons, **746** +complex polygons, `746` -monotone-vertical polygons, **767** +monotone-vertical polygons, `767` -**FillRect** subroutine, **869-870** +`FillRect` subroutine, **869-870** -**FillRectangleX** subroutine +`FillRectangleX` subroutine four-plane parallel processing, 888-891, **891-893** @@ -2489,43 +2489,43 @@ pixel-by-pixel plane selection, **885-887** plane-by-plane processing, **887-889** -**FindIDAverage** function +`FindIDAverage` function assembly implementations -based on compiler optimization, **160** +based on compiler optimization, `160` data structure reorganization, 163, **165-166** -unrolled loop, 161, **162** +unrolled loop, 161, `162` -C language implementation, **158** +C language implementation, `158` -compiler optimization, **159** +compiler optimization, `159` -**FindNodeBeforeValue** function, **289** +`FindNodeBeforeValue` function, `289` -**FindNodeBeforeValueNotLess** function, **286, 287** +`FindNodeBeforeValueNotLess` function, **286, 287** -**FindString** function +`FindString` function Boyer-Moore algorithm, **269, 271-274, 274-277** overview, 175 -scan-on-first-character approach, **176** +scan-on-first-character approach, `176` -scan-on-specified-character approach, **178** +scan-on-specified-character approach, `178` -**FirstPass** function, **355-358** +`FirstPass` function, **355-358** -**Fix** function, **358,** 365 +`Fix` function, **358,** 365 -**FixedDiv** subroutine, **982, 993, 1010-1012** +`FixedDiv` subroutine, **982, 993, 1010-1012** -FIXED\_MUL macro, **1016-1017** +`FIXED_MUL` macro, **1016-1017** -**FixedMul** subroutine, **981, 993-994, 1009-1010** +`FixedMul` subroutine, **981, 993-994, 1009-1010** Fixed-point arithmetic @@ -2537,27 +2537,27 @@ vs. integer arithmetic, 730, 1065 Flags -and **BSWAP** instruction, 254 +and `BSWAP` instruction, 254 Carry flag, 147-148, 185, 317-319 -**INC** vs. **ADD,** 147-148 +`INC` vs. `ADD`, 147-148 -and **LOOP** instruction, 148 +and `LOOP` instruction, 148 -and **NOT** instruction, 146-147 +and `NOT` instruction, 146-147 -**FLD** instruction, Pentium processor, 1167-1170 +`FLD` instruction, Pentium processor, 1167-1170 Floating point optimization clock cycles, core instructions, 1167-1168 -cross product optimization, 1171, **1172** +cross product optimization, 1171, `1172` -dot product optimization, 1170, **1171** +dot product optimization, 1170, `1171` -**FXCH** instruction, 1169-1170 +`FXCH` instruction, 1169-1170 interleaved instructions, 1169-1170 @@ -2577,7 +2577,7 @@ vs. fixed-point calculations, 985, 1206 vs. integer calculations, 730 -**FMUL** instruction +`FMUL` instruction 486 processor, 236 @@ -2589,11 +2589,11 @@ AX register, setting to absolute value, 172 byte registers and lost cycles, 242-245 -**CMP** instruction +`CMP` instruction operands, order of, 306 -vs. **SCASW,** 161 +vs. `SCASW`, 161 copying bytes between registers, 172 @@ -2607,19 +2607,19 @@ effect on code timing, 246 optimization, 236 -**LAHF** and **SAHF** instructions, 148 +`LAHF` and `SAHF` instructions, 148 -**LEA** instruction, vs. **ADD,** 131 +`LEA` instruction, vs. `ADD`, 131 -**LODSB** instruction, 304 +`LODSB` instruction, 304 -**LODSD** instruction, vs. **MOV/LEA** sequence, 171 +`LODSD` instruction, vs. `MOV/LEA` sequence, 171 lookup tables, vs. rotating or shifting, 145-146 -**LOOP** instruction, vs. **DEC/JNZ** sequence, 139 +`LOOP` instruction, vs. `DEC/JNZ` sequence, 139 -**MOV** instruction, vs. **XCHG,** 377 +`MOV` instruction, vs. `XCHG`, 377 n-bit vs. 1-bit shift and rotate instructions, 255-256 @@ -2641,17 +2641,17 @@ timing code, 245-246 using 32-bit register as two 16-bit registers, 253-254 -**XCHG** instruction, vs. **MOV,** 377, 832 +`XCHG` instruction, vs. `MOV`, 377, 832 FPU, Pentium processor clock cycles, core instructions, 1167-1168 -cross product optimization, 1171, **1172** +cross product optimization, 1171, `1172` -dot product optimization, 1170, **1171** +dot product optimization, 1170, `1171` -**FXCH** instruction, 1169-1170 +`FXCH` instruction, 1169-1170 interleaved instructions, 1169-1170 @@ -2667,9 +2667,9 @@ rounding control, 1174-1175 Frustum, clipping to, 1200, **1201-1206,** 1206-1207 -**FST** instruction, Pentium processor, 1167-1170 +`FST` instruction, Pentium processor, 1167-1170 -**FSUB** instruction, Pentium processor, 1167-1170 +`FSUB` instruction, Pentium processor, 1167-1170 Function 13H, VGA BIOS, 459 @@ -2677,9 +2677,9 @@ Function calls, performance, 153 *Fundamentals of Interactive Computer Graphics* (book), 660 -**FXCH** instruction, Pentium processor, 1169-1170 +`FXCH` instruction, Pentium processor, 1169-1170 -**G** +`G` Game of Life @@ -2774,7 +2774,7 @@ planes, specifying to be read, 542 Set/Reset register, 666 -**Gcd()** function +`Gcd()` function brute-force approach, 195 @@ -2794,7 +2794,7 @@ Euclid's algorithm, 197-200 subtraction approach, 196-197 -**Gcd\_recurs()** function, **199** +`Gcd_recurs()` function, `199` Generality, vs. performance, 335 @@ -2802,15 +2802,15 @@ Gerrold, David, 298 GET (global edge table), 744 -**Getc()** function +`Getc()` function overhead, 14 -vs. **read()** function, 12 +vs. `read()` function, 12 -**GetNextKey** subroutine, **598, 605** +`GetNextKey` subroutine, **598, 605** -**GetUpAndDown** function, **355** +`GetUpAndDown` function, `355` Global edge table (GET), 744 @@ -2844,7 +2844,7 @@ Great Buffalo Sauna Fiasco, 137-138 GUIs, and future of programming profession, 725-726 -**H** +`H` Hardware dependence, DDA (digital differential analyzer) texture mapping, 1053 @@ -2871,13 +2871,13 @@ sorted spans approach abutting span sorting, 1229-1230 -**AddPolygonEdges** function, **1232-1233,** 1238 +`AddPolygonEdges` function, **1232-1233,** 1238 BSP order vs. 1/z order, 1220, 1226 -**ClearEdgeLists** function, **1236-1237** +`ClearEdgeLists` function, **1236-1237** -**DrawSpans** function, **1236** +`DrawSpans` function, `1236` edge sorting, 1220-1222 @@ -2891,13 +2891,13 @@ intersecting span sorting, 1228-1229 overview, 1214-1215 -**PolyFacesViewer** function, **1232** +`PolyFacesViewer` function, `1232` rotation instructions, clock cycles, 185-186 -**ScanEdges** function, **1234-1236,** 1238-1239 +`ScanEdges` function, **1234-1236,** 1238-1239 -**UpdateWorld** function, **1237-1238** +`UpdateWorld` function, **1237-1238** High school graduates in Hawaii, 991-992 @@ -2907,7 +2907,7 @@ Horizontal resolution, 360x480 256-color mode, 620 Horizontal smooth panning. *See* Panning. -**I** +`I` id Software, 1118, 1190 @@ -2917,15 +2917,15 @@ Illowsky, Dan, 187, 315 Image precedence. *See* Bit-plane animation. -**IMUL** instruction +`IMUL` instruction 486 processor, 236 on 386 processor, 173-174 -**INC** instruction +`INC` instruction -vs. **ADD,** 147-148, 219 +vs. `ADD`, 147-148, 219 and Carry flag, 147-148 @@ -2933,21 +2933,21 @@ Incremental transformations of 3-D objects, 964 Independent span sorting -**AddPolygonEdges** function, **1232-1233,** 1238 +`AddPolygonEdges` function, **1232-1233,** 1238 -**ClearEdgeLists** function, **1236-1237** +`ClearEdgeLists` function, **1236-1237** -**DrawSpans** function, **1236** +`DrawSpans` function, `1236` overview, 1230 -**PolyFacesViewer** function, **1232** +`PolyFacesViewer` function, `1232` -**ScanEdges** function, **1234-1236,** 1238-1239 +`ScanEdges` function, **1234-1236,** 1238-1239 texture mapping, 1238 -**UpdateWorld** function, **1237-1238** +`UpdateWorld` function, **1237-1238** Index registers, VGA @@ -2961,17 +2961,17 @@ Indirect far jumps, 186 Information, sharing, 1190, 1194 -**InitCellmap** function, **361** +`InitCellmap` function, `361` -**InitializeCubes** function, **980-981** +`InitializeCubes` function, **980-981** -**InitializeFixedPoint** function, **977** +`InitializeFixedPoint` function, `977` -**InitializeObjectList** function, **1001** +`InitializeObjectList` function, `1001` -**InitializePalette** function, **1037** +`InitializePalette` function, `1037` -**InitLinkedList** function, **289** +`InitLinkedList` function, `289` Inorder tree traversal @@ -2981,11 +2981,11 @@ data recursive implementation, 1108, **1109-1110,** 1110 performance, 1111-1113 -**INS** instruction, 221 +`INS` instruction, 221 -**InsertNodeSorted** assembly routine, **290** +`InsertNodeSorted` assembly routine, `290` -**InsertNodeSorted** function, **289** +`InsertNodeSorted` function, `289` Instruction execution times @@ -3045,13 +3045,13 @@ Interleaved color cycling, 649-650 Interleaved operations, Pentium processor -**FXCH** instruction and floating point operations, 1169-1170 +`FXCH` instruction and floating point operations, 1169-1170 matrix transformation, 1172-1173, **1173-1174** overview, 394-395 -TCP/IP checksum program, **408** +TCP/IP checksum program, `408` Internal animation, 872 @@ -3093,13 +3093,13 @@ DAC, loading, 643, 648 Divide By Zero interrupt, 181 -and **IRET** instruction, 227 +and `IRET` instruction, 227 and long-period Zen timer, 53, 66 and page flipping, 446 -and **POPF** instruction, 226 +and `POPF` instruction, 226 and Zen timer, 43, 45-46 @@ -3109,23 +3109,23 @@ Intersecting span sorting, 1228-1229 Intuitive leaps, 1098 -**IRET** instruction, vs. **POPF** instruction, 226-231 +`IRET` instruction, vs. `POPF` instruction, 226-231 IRQ0 interrupts, and Zen timer, 45 -**IS\_VGA** equate, 572, 575 +`IS_VGA` equate, 572, 575 -**J** +`J` Jet Propulsion Lab, color perception research, 1035 **JMP \$+2** instructions, 558, 632 -**JMP DWORD PTR** instruction, 186-187 +`JMP DWORD PTR` instruction, 186-187 Jumps, to absolute addresses, 186-187 -**K** +`K` Kennedy, John, 171-172 @@ -3145,9 +3145,9 @@ Klerings, Peter, 350 Knuth, Donald, 323 -**L** +`L` -**LAHF** instruction, 148 +`LAHF` instruction, 148 Large code model @@ -3179,9 +3179,9 @@ overview, 452-453, 897-898 Latency, in QuakeWorld, 1291-1292 -**LEA** instruction +`LEA` instruction -vs. **ADD,** 130, 170-171 +vs. `ADD`, 130, 170-171 multiplication operations, 132-133, 172, 375-376 @@ -3191,7 +3191,7 @@ addition, 131 multiplication, 132-133 -**LEAVE** instruction +`LEAVE` instruction 486 processor, 241-242 @@ -3253,13 +3253,13 @@ Line segments clipping to planes, 1195-1197 -representation, 1195, **1196** +representation, 1195, `1196` Linear addressing, VGA, 430 Linear-time sorting, 1099 -**LineDraw** function +`LineDraw` function assembly implementation, **699-704,** 704-706 @@ -3283,7 +3283,7 @@ Wu antialiasing algorithm, 776-779, **780-791,** 791-792 Line-drawing demo program, **615-618,** 618-619 -**LineIntersectPlane** function, **1142-1143** +`LineIntersectPlane` function, **1142-1143** Lines @@ -3315,7 +3315,7 @@ dummy nodes, 285-287 head pointers, 284, 285 -**InsertNodeSorted** assembly routine, **290** +`InsertNodeSorted` assembly routine, `290` overview, 282 @@ -3325,7 +3325,7 @@ sorting techniques, 755 tail nodes, 286 -test-bed program, **291** +test-bed program, `291` Little endian format, 252 @@ -3343,15 +3343,15 @@ lookup tables, 145-146 unrolling loops, 143-145, 305, 312, 377-378, 410 -**LOCK** instruction, 377 +`LOCK` instruction, 377 Lockstep execution, Pentium processor, 390-394, 400-403 -**LODSB** instruction, 304, 312 +`LODSB` instruction, 304, 312 -**LODSD** instruction, 171 +`LODSD` instruction, 171 -**LODSW** instruction, 312 +`LODSW` instruction, 312 Logical functions, ALU, 458 @@ -3375,19 +3375,19 @@ LZTIMER.ASM listing, **55-65** overview, 53 -**PS2** equate, 65-66 +`PS2` equate, 65-66 system clock inaccuracies, 43, 45-46, 48 test-bed program, 66-69 -TESTCODE listing, **69** +TESTCODE listing, `69` -**ZTimerOff** subroutine, **59-63** +`ZTimerOff` subroutine, **59-63** -**ZTimerOn** subroutine, **58-59** +`ZTimerOn` subroutine, **58-59** -**ZTimerReport** subroutine, **63-65** +`ZTimerReport` subroutine, **63-65** Lookup tables @@ -3405,17 +3405,17 @@ David Stafford's implementation, **309-311,** 317-319 WC50 (Terje Mathisen), 307 -**LOOP** instruction +`LOOP` instruction *See also* Loops. -vs. **DEC/JNZ** sequence, 139, 140-141 +vs. `DEC/JNZ` sequence, 139, 140-141 and flags, 148 Loops -*See also* **LOOP** instruction. +*See also* `LOOP` instruction. avoiding, 140 @@ -3423,7 +3423,7 @@ and branch prediction, Pentium processor, 377-378 unrolling, 143-145, 305, 312, 377-378, 410 -**M** +`M` Mackraz, Jim, 678 @@ -3523,11 +3523,11 @@ transformation, optimized, 1172-1173, **1173-1174** MDA (Monochrome Display Adapter), 104 -**Memchr()** function, 116 +`Memchr()` function, 116 -**Memcmp()** function, 116 +`Memcmp()` function, 116 -**Memcpy()** function, 1147-1148 +`Memcpy()` function, 1147-1148 Memory access @@ -3535,7 +3535,7 @@ Memory access clock cycles, bytes vs. words, 82, 83-85 -**DEC** instruction, 83 +`DEC` instruction, 83 and DRAM refresh, 98 @@ -3569,7 +3569,7 @@ data alignment, 213-215 Memory-addressing instructions, 223-225 -**Memset()** C library function, 727 +`Memset()` C library function, 727 Miles, John, 1081, 1093 @@ -3601,7 +3601,7 @@ bitmap organization, 882-883 features, 878-879 -**FillRectangleX** subroutine +`FillRectangleX` subroutine four-plane parallel processing, 888-891, **891-893** @@ -3645,15 +3645,15 @@ pattern fills, 899, **900-903,** 903-904 pixel access and hardware planes, 1082 -**ReadPixelX** subroutine, **884-885** +`ReadPixelX` subroutine, **884-885** vertical scanlines vs. horizontal, 1084-1086 -**WritePixelX** subroutine, **883-884** +`WritePixelX` subroutine, **883-884** -**ModelColor** structure, **1035** +`ModelColor` structure, `1035` -**ModelColorToColorIndex** function, 1036, **1038** +`ModelColorToColorIndex` function, 1036, `1038` Mod-R/M byte, 257 @@ -3665,41 +3665,41 @@ optimizing, 153 Monotone-vertical polygons, filling, 760-761, **761-771,** 771 -**MOV** instruction, 236, 377, 832 +`MOV` instruction, 236, 377, 832 -**MoveBouncer** function, **824-825** +`MoveBouncer` function, **824-825** -**MoveObject** function, **929** +`MoveObject` function, `929` -**MoveXSortedToAET** function +`MoveXSortedToAET` function -complex polygons, **749** +complex polygons, `749` -monotone-vertical polygons, **770** +monotone-vertical polygons, `770` -**MOVSD** instruction, 222, 386 +`MOVSD` instruction, 222, 386 -**MUL** instruction, 97, 173-174 +`MUL` instruction, 97, 173-174 Multiplication increasing speed of, 173-174 -using **LEA,** 132-133, 172 +using `LEA`, 132-133, 172 Multi-word arithmetic, 147-148 -**N** +`N` -**NEG** EAX instruction, 222 +`NEG` EAX instruction, 222 Negation, two's complement, 171 -**Next1** function, **353** +`Next1` function, `353` -**Next2** function, **353** +`Next2` function, `353` -**Next\_generation** method, **327-328,** **335,** 336, 337-338, **344** +`Next_generation` method, **327-328,** **335,** 336, 337-338, `344` Nonconvex objects, depth sorting, 1000, **1001-1002** @@ -3713,11 +3713,11 @@ direction of, 1140 Normals. *See* Normal vectors. -**NOSMART** assembler directive, 72 +`NOSMART` assembler directive, 72 -**NOT** instruction, 146-147, 147 +`NOT` instruction, 146-147, 147 -**O** +`O` Object collisions, detecting, **531-534** @@ -3725,15 +3725,15 @@ Object space, 935, 1135 Object-oriented programming, 725-726 -**Octant0** function +`Octant0` function -360x480 256-color mode line drawing demo program, **615** +360x480 256-color mode line drawing demo program, `615` Bresenham's line-drawing algorithm, **662,** 668-669 -**Octant1** function +`Octant1` function -360x480 256-color mode line drawing demo program, **616** +360x480 256-color mode line drawing demo program, `616` Bresenham's line-drawing algorithm, **663,** 668-669 @@ -3743,13 +3743,13 @@ Octants, and line orientations, 666-667 abutting span sorting, 1229-1230 -**AddPolygonEdges** function, **1232-1233,** 1238 +`AddPolygonEdges` function, **1232-1233,** 1238 vs. BSP-order sorting, 1226-1227 calculating 1/z value, 1220-1222 -**ClearEdgeLists** function, **1236-1237** +`ClearEdgeLists` function, **1236-1237** DrawSpans function, 1236 @@ -3757,13 +3757,13 @@ independent span sorting, 1230, **1231-1238,** 1239-1241 intersecting span sorting, 1228-1229 -**PolyFacesViewer** function, **1232** +`PolyFacesViewer` function, `1232` reliability, 1227 -**ScanEdges** function, **1234-1236,** 1238-1239 +`ScanEdges` function, **1234-1236,** 1238-1239 -**UpdateWorld** function, **1237-1238** +`UpdateWorld` function, **1237-1238** On-screen object collisions, detecting, **531-534** @@ -3821,11 +3821,11 @@ floating point operations clock cycles, core instructions, 1167-1168 -cross product optimization, 1171, **1172** +cross product optimization, 1171, `1172` dot product optimization, 1170, 1171 -**FXCH** instruction, 1169-1170 +`FXCH` instruction, 1169-1170 interleaved instructions, 1169-1170 @@ -3973,11 +3973,11 @@ Optimized searching, 174-180 Optimizing assemblers, 71-72 -**OR** instruction, 377 +`OR` instruction, 377 -Orientation-independent texture mapping, 1065-1066, **1067** +Orientation-independent texture mapping, 1065-1066, `1067` -**OUT** instruction +`OUT` instruction clock cycles, 1082-1083 @@ -3987,13 +3987,13 @@ loading palette RAM or DAC registers, 632 performance, 444, 843 -word-**OUT** vs. byte-**OUT,** 429, 479 +word-`OUT` vs. byte-`OUT`, 429, 479 vs. write mode 3, 483-484 -**OUTS** instruction, 221 +`OUTS` instruction, 221 -OUT\_WORD macro, **566,** 594 +`OUT_WORD` macro, **566,** 594 Overdraw problem, VSD @@ -4013,9 +4013,9 @@ in 16-bit checksum program, 12 in search engine, 121 -**memcmp()** function, 116 +`memcmp()` function, 116 -**strstr()** function, 115 +`strstr()` function, 115 of Zen timer, timing, 46, 72 @@ -4023,7 +4023,7 @@ Overlapping rectangles, in dirty-rectangle animation, 872-873 Overscan, 555-556, 641 -**P** +`P` Page flipping @@ -4103,7 +4103,7 @@ in split screens, 574-575, **575-582,** 582-583 in text mode, 442 -**PanRight** subroutine, **582** +`PanRight` subroutine, `582` Parametric lines @@ -4149,11 +4149,11 @@ floating point optimization clock cycles, core instructions, 1167-1168 -cross product optimization, 1171, **1172** +cross product optimization, 1171, `1172` -dot product optimization, 1170, **1171** +dot product optimization, 1170, `1171` -**FXCH** instruction, 1169-1170 +`FXCH` instruction, 1169-1170 interleaved instructions, 1169-1170 @@ -4173,15 +4173,15 @@ instruction fetching, 374 internal cache, 374-375, 396 -**LAHF** and **SAHF** instructions, 148 +`LAHF` and `SAHF` instructions, 148 -**LEA** vs. **ADD** instructions, 131 +`LEA` vs. `ADD` instructions, 131 -**LODSB** instruction, 304 +`LODSB` instruction, 304 -**LOOP** instruction vs. **DEC/JNZ** sequence, 139 +`LOOP` instruction vs. `DEC/JNZ` sequence, 139 -**MOV** vs. **XCHG** instructions, 377 +`MOV` vs. `XCHG` instructions, 377 optimization @@ -4229,7 +4229,7 @@ U-pipe, 385-386 V-pipe, 385-386, 386-387 -**XCHG** vs. **MOV** instructions, 377, 832 +`XCHG` vs. `MOV` instructions, 377, 832 *Pentium Processor Optimization Tools* (book), 1148 @@ -4282,9 +4282,9 @@ measuring, importance of, 34, 396 memory access, 223-225 -**OUT** instruction, 444 +`OUT` instruction, 444 -**OUT** instructions, 843 +`OUT` instructions, 843 PC-compatible computers, 48-49 @@ -4347,7 +4347,7 @@ Pixel drawing *See also* Pixels. -**EVGADot** function, **661-662,** 669-670 +`EVGADot` function, **661-662,** 669-670 optimization, 1074, 1086 @@ -4381,7 +4381,7 @@ clipping line segments to, 1195-1197 1/z value, calculating, 1221 -representation, **1196** +representation, `1196` Planes, VGA @@ -4433,57 +4433,57 @@ Pointer advancement optimization, 1086-1089, **1090-1091,** 1092-1093 Pointer arithmetic, 171 -Points, representation of, **1196** +Points, representation of, `1196` -**PolyFacesViewer** function, **1203,** 1232 +`PolyFacesViewer` function, **1203,** 1232 Polygon clipping -**BackRotateVector** function, **1203** +`BackRotateVector` function, `1203` clipping to frustum, 1200, **1201-1206,** 1206-1207 -**ClipToFrustum** function, **1204** +`ClipToFrustum` function, `1204` -**ClipToPlane** function, **1199** +`ClipToPlane` function, `1199` optimization, 1207 overview, 1197-1200 -**PolyFacesViewer** function, **1203** +`PolyFacesViewer` function, `1203` -**ProjectPolygon** function, **1201** +`ProjectPolygon` function, `1201` -**SetUpFrustum** function, **1204** +`SetUpFrustum` function, `1204` -**SetWorldspace** function, **1204** +`SetWorldspace` function, `1204` -**TransformPoint** function, **1203** +`TransformPoint` function, `1203` -**TransformPolygon** function, **1203** +`TransformPolygon` function, `1203` -**UpdateViewPos** function, **1202** +`UpdateViewPos` function, `1202` -**UpdateWorld** function, **1205** +`UpdateWorld` function, `1205` viewspace clipping, 1207 -**ZSortObjects** function, **1201** +`ZSortObjects` function, `1201` POLYGON.H header file -complex polygons, **751** +complex polygons, `751` -monotone-vertical polygons, filling, **771** +monotone-vertical polygons, filling, `771` non-overlapping convex polygons, **719-720** -texture mapped polygons, **1054** +texture mapped polygons, `1054` 3-D polygon rotation, **945-946** -3-D solid cube rotation program, **965** +3-D solid cube rotation program, `965` X-Sharp 3-D animation package, **982-984** @@ -4525,7 +4525,7 @@ normal vector, calculating, 955-956 projection in 3-D space, 937, **944-945,** 948 -representation, **1196** +representation, `1196` 3-D polygon rotation demo program @@ -4577,7 +4577,7 @@ edge tracing overview, 711-713 -**ScanEdge** function, **716-717,** 720-721, **730-732,** **735-738** +`ScanEdge` function, **716-717,** 720-721, **730-732,** **735-738** fitting adjacent polygons, 712-713 @@ -4621,11 +4621,11 @@ incremental transformations, **964-966** object representation, 967 -**POP** instruction, 241-242, 404 +`POP` instruction, 241-242, 404 -**POPA** instruction, 221 +`POPA` instruction, 221 -**POPF** instruction, 226, 226-231 +`POPF` instruction, 226, 226-231 Popping, memory locations vs. registers, 254-255 @@ -4701,17 +4701,17 @@ defined, 1135 floating point optimization, 1174 -**LineIntersectPlane** function, **1142-1143** +`LineIntersectPlane` function, **1142-1143** overview, 937, 948 -**XformAndProjectPoly** function, **944-945** +`XformAndProjectPoly` function, **944-945** rotation without matrices, 1143-1144 using dot product, 1141-1142 -**ProjectPolygon** function, **1201** +`ProjectPolygon` function, `1201` Proportional text, 489 @@ -4731,23 +4731,23 @@ overview, 208-209 32-bit addressing modes, 256-258 -**PS2** equate, long-period Zen timer, 65-66 +`PS2` equate, long-period Zen timer, 65-66 PS/2 computers, 54, 66 -**PUSH** instruction, 222, 241-242, 404 +`PUSH` instruction, 222, 241-242, 404 -**PUSHA** instruction, 221 +`PUSHA` instruction, 221 Pushing, memory locations vs. registers, 254-255 -PZTEST.ASM listing, Zen timer, **49** +PZTEST.ASM listing, Zen timer, `49` -PZTIME.BAT listing, Zen timer, **51** +PZTIME.BAT listing, Zen timer, `51` PZTIMER.ASM listing, Zen timer, **35-42** -**Q** +`Q` QLife program, **352-363** @@ -4791,7 +4791,7 @@ and visible surface determination (VSD), 1181 QuakeWorld, 1291-1292 -**R** +`R` Radiosity lighting, Quake 2, 1293 @@ -4809,15 +4809,15 @@ Rate of divergence, in 3-D drawing, 937 Raycast, subdividing, and beam trees, 1187 -**RCL** instruction, 185-186 +`RCL` instruction, 185-186 -**RCR** instruction, 185-186 +`RCR` instruction, 185-186 -**Read360x480Dot** subroutine, **614-615** +`Read360x480Dot` subroutine, **614-615** -**Read()** C library function +`Read()` C library function -vs. **getc()** function, 12 +vs. `getc()` function, 12 overhead, 121 @@ -4849,9 +4849,9 @@ Read/write/modify operations, 107 Read-after-write register contention, 404 -**ReadPixel** subroutine, **598**, 599 +`ReadPixel` subroutine, `598`, 599 -**ReadPixelX** subroutine, **884-885** +`ReadPixelX` subroutine, **884-885** Real mode. *See* 386 processor. @@ -4929,9 +4929,9 @@ SVGA programming, 626 VGA registers, 583 -**ReferenceZTimerOff** subroutine, 41 +`ReferenceZTimerOff` subroutine, 41 -**ReferenceZTimerOn** subroutine, 40 +`ReferenceZTimerOn` subroutine, 40 Reflections, in GLQuake, 1290 @@ -4985,43 +4985,43 @@ backface removal, 1160-1161 clipping, 1158-1159 -**ClipWalls** function, **1152-1155**, 1158-1159 +`ClipWalls` function, **1152-1155**, 1158-1159 -**DrawWallsBackToFront** function, **1155-1156**, 1160-1161 +`DrawWallsBackToFront` function, **1155-1156**, 1160-1161 overview, 1149 reference materials, 1157 -**TransformVertices** function, **1151-1152**, 1158 +`TransformVertices` function, **1151-1152**, 1158 -**UpdateViewPos** function, **1151**, 1157 +`UpdateViewPos` function, `1151`, 1157 -**UpdateWorld** function, **1156-1157**, 1157 +`UpdateWorld` function, **1156-1157**, 1157 viewspace, transformation of objects to, 1158 wall orientation testing, 1160-1161 -**WallFacingViewer** function, **1150-1151**, 1161 +`WallFacingViewer` function, **1150-1151**, 1161 *RenderMan Companion* (book), 742 -**REP MOVS** instruction, 148 +`REP MOVS` instruction, 148 -**REP MOVSW** instruction, 82, 105, 220 +`REP MOVSW` instruction, 82, 105, 220 -**REP SCASW** instruction, 166 +`REP SCASW` instruction, 166 -**REP STOS** instruction, 727, 735 +`REP STOS` instruction, 727, 735 -**REPNZ SCASB** instruction +`REPNZ SCASB` instruction vs. Boyer-Moore algorithm, 267-268, 271, 274 in string searching problem, 121-122, 174-175, 262-263 -**REPZ CMPS** instruction +`REPZ CMPS` instruction vs. Boyer-Moore algorithm, 267-268, 271, 274 @@ -5045,7 +5045,7 @@ Results, precalculating BSP trees and potentially visible set (PVS), 1188-1189 -**RET** instruction, 241-242 +`RET` instruction, 241-242 Reusable code, and future of programming profession, 725-726 @@ -5059,11 +5059,11 @@ Richardson, John, 316 Right-handed coordinate system, 935-937 -**ROL** instruction, 185-186 +`ROL` instruction, 185-186 Roll angle, in polygon clipping, 1206 -**ROR** instruction, 185-186 +`ROR` instruction, 185-186 Rotate instructions @@ -5073,11 +5073,11 @@ n-bit vs. 1-bit, 255-256 286 processor, 222 -**RotateAndMovePObject** function, 977-978 +`RotateAndMovePObject` function, 977-978 Rotation, 3-D animation -**ConcatXforms** function, 944 +`ConcatXforms` function, 944 matrix representation, 938-939 @@ -5085,7 +5085,7 @@ multiple axes of rotation, 948 using dot product, 1143-1144 -**XformVec** function, 943 +`XformVec` function, 943 Rotational variance, 1249 @@ -5119,9 +5119,9 @@ potential optimizations, 705 Ruts, mental, staying out of, 1147-1148 -**S** +`S` -**SAHF** instruction, 148 +`SAHF` instruction, 148 Sam the Golden Retriever, 841-842 @@ -5165,7 +5165,7 @@ in split screens, 564-565, 573 vertical, in texture mapping, 1084-1086 -**ScanBuffer** assembly routine +`ScanBuffer` assembly routine author's implementation, **301-302**, **303-304** @@ -5173,7 +5173,7 @@ hand-optimized implementation(Willem Clements), **313-315** lookup table implementation (David Stafford), **309-311**, 317-319 -**ScanEdge** function +`ScanEdge` function assembly implementation, **735-738**, 735 @@ -5181,21 +5181,21 @@ floating-point C implementation, **716-717**, 720-721 integer-based C implementation, **730-732** -**ScanEdges** function, **1234-1236**, 1238-1239 +`ScanEdges` function, **1234-1236**, 1238-1239 -**ScanOutAET** function +`ScanOutAET` function complex polygons, **749-750** -monotone-vertical polygons, **770** +monotone-vertical polygons, `770` -**ScanOutLine** function +`ScanOutLine` function assembly implementation, **1069-1073**, 1074 C-language implementation, **1058-1059**, **1067-1069** -**SCASW** instruction, 161 +`SCASW` instruction, 161 Screen blanking @@ -5233,7 +5233,7 @@ design considerations, 114 execution profile, 121 -**FindString** function, 175, **176**, **178**, **269** +`FindString` function, 175, `176`, `178`, `269` optimization, 174-180 @@ -5243,7 +5243,7 @@ search space and optimization, 122, 175 search techniques, 115-116, 175 -**SearchForString** function, **118** +`SearchForString` function, `118` Searching @@ -5255,9 +5255,9 @@ in linked list of arrays, 156-166 for specified byte in buffer, 141-145 -using **REP SCASW**, 166 +using `REP SCASW`, 166 -**SecondPass** function, **358-360** +`SecondPass` function, **358-360** Sedgewick, Robert (*Algorithms*), 192, 196 @@ -5271,7 +5271,7 @@ protected mode, 208-209 386 processor, 222 -**SelectBSPTree** function, **1124-1125** +`SelectBSPTree` function, **1124-1125** Selling ideas, 1193-1194 @@ -5279,13 +5279,13 @@ Sentinels, in linked lists, 286 Sequence Controller, VGA. *See* SC (Sequence Controller), VGA. -**Set320x400Mode** subroutine, 593, **596-597**, 599, **602-604** +`Set320x400Mode` subroutine, 593, **596-597**, 599, **602-604** -**Set320x240Mode** subroutine, **881-882** +`Set320x240Mode` subroutine, **881-882** -**Set360x480Mode** subroutine, **612**, 620-621 +`Set360x480Mode` subroutine, `612`, 620-621 -**Set640x400** function, **855** +`Set640x400` function, `855` Set/reset circuitry, VGA @@ -5305,25 +5305,25 @@ and write mode 2, 501-502, 509, 515 Set/Reset register, 666 -**SetBIOS8x8Font** subroutine, **830** +`SetBIOS8x8Font` subroutine, `830` -**Set\_cell** method, **327**, **334**, **342** +`Set_cell` method, `327`, `334`, `342` SETGC macro, 454, 475 -**SetPalette** function, **783-784** +`SetPalette` function, **783-784** -**SetPelPan** subroutine, **580** +`SetPelPan` subroutine, `580` SETSC macro, 474 -**SetSplitScreenScanLine** subroutine, **570-571**, **581** +`SetSplitScreenScanLine` subroutine, **570-571**, `581` -**SetStartAddress** subroutine, **570**, **580** +`SetStartAddress` subroutine, `570`, `580` -**SetUpEdge** function, **1057-1058** +`SetUpEdge` function, **1057-1058** -**SetWorldspace** function, **1204** +`SetWorldspace` function, `1204` Shading @@ -5355,19 +5355,19 @@ Shift instructions, 222, 255-256 Shifting bits, vs. lookup tables, 145-146 -**SHL** instruction, 376 +`SHL` instruction, 376 -**ShowBounceCount** function, **823-824** +`ShowBounceCount` function, **823-824** -**ShowPage** subroutine +`ShowPage` subroutine masked copying animation, Mode X, **929-930** -page flipping animation, **827** +page flipping animation, `827` -**Show\_text** function, **329**, **363** +`Show_text` function, `329`, `363` -**SHR** instruction, 88-91, 97 +`SHR` instruction, 88-91, 97 SIB byte, 257 @@ -5403,13 +5403,13 @@ Sorted span hidden surface removal abutting span sorting, 1229-1230 -**AddPolygonEdges** function, **1232-1233**, 1238 +`AddPolygonEdges` function, **1232-1233**, 1238 BSP order vs. 1/z order, 1220, 1226 -**ClearEdgeLists** function, **1236-1237** +`ClearEdgeLists` function, **1236-1237** -**DrawSpans** function, **1236** +`DrawSpans` function, `1236` edge sorting, 1220-1222 @@ -5423,11 +5423,11 @@ intersecting span sorting, 1228-1229 overview, 1214-1215 -**PolyFacesViewer** function, **1232** +`PolyFacesViewer` function, `1232` -**ScanEdges** function, **1234-1236**, 1238-1239 +`ScanEdges` function, **1234-1236**, 1238-1239 -**UpdateWorld** function, **1237-1238** +`UpdateWorld` function, **1237-1238** Sorting techniques @@ -5443,7 +5443,7 @@ and optimization, 755 z-buffers, 1212-1213 -**SortObjects** function, **1002** +`SortObjects` function, `1002` Span-based drawing, and beam trees, 1187 @@ -5469,9 +5469,9 @@ text mode, 584 turning on and off, 565 -**SplitScreenDown** subroutine, **572** +`SplitScreenDown` subroutine, `572` -**SplitScreenUp** subroutine, **572** +`SplitScreenUp` subroutine, `572` Spotlights @@ -5499,7 +5499,7 @@ Stack pointer alignment, 218-219 Stack-based variables, placement of, 184-185 -Stacks, **POPF** vs. **IRET**, 226-231 +Stacks, `POPF` vs. `IRET`, 226-231 Stafford, David @@ -5507,7 +5507,7 @@ Stafford, David Game of Life implementation, 351-352, **353-363**, 363-365 -**ScanBuffer** assembly routine, word count program, **309-311**, +`ScanBuffer` assembly routine, word count program, **309-311**, 317-319 24-byte hi/lo function, 292-293 @@ -5520,17 +5520,17 @@ State machines word count program, 315 -**StepEdge** function, **1056-1057** +`StepEdge` function, **1056-1057** -**STOSB** instruction, 236 +`STOSB` instruction, 236 String instructions, 107 String searching. *See* Search engine; Searching. -**Strstr()** function, 115 +`Strstr()` function, 115 -**SUB** instruction, 219 +`SUB` instruction, 219 Subdivision rasterization, 1266-1267, **1267-1270**, 1286 @@ -5588,7 +5588,7 @@ masked copy to display memory, **916-918**, 916 System wait states, 210-213 -**T** +`T` Table-driven state machines, 316-319 @@ -5598,17 +5598,17 @@ TASM (Turbo Assembler), 71-72 TCP/IP checksum program -basic implementation, **406** +basic implementation, `406` -dword implementation, **409** +dword implementation, `409` -interleaved implementation, **408** +interleaved implementation, `408` -unrolled loop implementation, **410** +unrolled loop implementation, `410` -**Test** function, **358**, 365 +`Test` function, `358`, 365 -**TEST** instruction, 377, 401-402 +`TEST` instruction, 377, 401-402 Texels @@ -5642,15 +5642,15 @@ split screen operations, 584-585 Text pages, flipping from graphics to text, 517 -TEXT\_UP macro, **454**, 459 +`TEXT_UP` macro, `454`, 459 -**TextUp** subroutine, **829** +`TextUp` subroutine, `829` Texture mapping *See also* DDA (digital differential analyzer) texture mapping. -boundary pixels, polygons, 1049-1052, 1066, **1067** +boundary pixels, polygons, 1049-1052, 1066, `1067` C implementation, **1053-1058** @@ -5672,7 +5672,7 @@ pointer advancement optimization, 1086-1089, **1090-1091** vertical scanlines, 1084-1086 -orientation independence, 1065-1066, **1067** +orientation independence, 1065-1066, `1067` overview, 1048 @@ -5697,11 +5697,11 @@ vertical scanlines, 1084-1086 *See also* Registers; VGA registers. -adding with **LEA**, 131 +adding with `LEA`, 131 -**BSWAP** instruction, 252 +`BSWAP` instruction, 252 -multiplying with **LEA**, 132-133 +multiplying with `LEA`, 132-133 386 processor, 222 @@ -5726,13 +5726,13 @@ depth sorting, 1000, **1001-1002** rotation -**ConcatXforms** function, **944** +`ConcatXforms` function, `944` matrix representation, 938-939 multiple axes of rotation, 948 -**XformVec** function, **943** +`XformVec` function, `943` rounding vs. truncation, 1002-1003 @@ -5748,37 +5748,37 @@ overview, 1195 polygon clipping -**BackRotateVector** function, **1203** +`BackRotateVector` function, `1203` clipping to frustum, 1200, **1201-1206**, 1206-1207 -**ClipToFrustum** function, **1204** +`ClipToFrustum` function, `1204` -**ClipToPlane** function, **1199** +`ClipToPlane` function, `1199` optimization, 1207 overview, 1197-1200 -**PolyFacesViewer** function, **1203** +`PolyFacesViewer` function, `1203` -**ProjectPolygon** function, **1201** +`ProjectPolygon` function, `1201` -**SetUpFrustum** function, **1204** +`SetUpFrustum` function, `1204` -**SetWorldspace** function, **1204** +`SetWorldspace` function, `1204` -**TransformPoint** function, **1203** +`TransformPoint` function, `1203` -**TransformPolygon** function, **1203** +`TransformPolygon` function, `1203` -**UpdateViewPos** function, **1202** +`UpdateViewPos` function, `1202` -**UpdateWorld** function, **1205** +`UpdateWorld` function, `1205` viewspace clipping, 1207 -**ZSortObjects** function, **1201** +`ZSortObjects` function, `1201` 3-D drawing @@ -5843,25 +5843,25 @@ backface removal, 1160-1161 clipping, 1158-1159 -**ClipWalls** function, **1152-1155**, 1158-1159 +`ClipWalls` function, **1152-1155**, 1158-1159 -**DrawWallsBackToFront** function, **1155-1156**, 1160-1161 +`DrawWallsBackToFront` function, **1155-1156**, 1160-1161 overview, 1149 reference materials, 1157 -**TransformVertices** function, **1151-1152**, 1158 +`TransformVertices` function, **1151-1152**, 1158 -**UpdateViewPos** function, **1151**, 1157 +`UpdateViewPos` function, `1151`, 1157 -**UpdateWorld** function, **1156-1157**, 1157 +`UpdateWorld` function, **1156-1157**, 1157 viewspace, transformation of objects to, 1158 wall orientation testing, 1160-1161 -**WallFacingViewer** function, **1150-1151**, 1161 +`WallFacingViewer` function, **1150-1151**, 1161 span-based drawing, and beam trees, 1187 @@ -5969,7 +5969,7 @@ object representation, 967 alignment, stack pointer, 218-219 -**CMP** instruction, 161, 306 +`CMP` instruction, 161, 306 cycle-eaters, 209-210 @@ -5985,17 +5985,17 @@ DRAM refresh cycle-eater, 219 effective address calculations, 129, 223-225 -**LEA** instruction, 130-133, 172 +`LEA` instruction, 130-133, 172 -**LODSD** vs. **MOV/LEA** sequence, 171 +`LODSD` vs. `MOV/LEA` sequence, 171 lookup tables, vs. rotating or shifting, 145-146 -**LOOP** instruction vs. **DEC/JNZ** sequence, 139 +`LOOP` instruction vs. `DEC/JNZ` sequence, 139 memory access, performance, 223-225 -**MUL** and **IMUL** instructions, 173-174 +`MUL` and `IMUL` instructions, 173-174 multiplication operations, increasing speed of, 173-174 @@ -6015,7 +6015,7 @@ system wait states, 210-212 using 32-bit register as two 16-bit registers, 253-254 -**XCHG** vs. **MOV** instructions, 377, 832 +`XCHG` vs. `MOV` instructions, 377, 832 386SX processor, 16-bit bus cycle-eater, 81 @@ -6023,7 +6023,7 @@ using 32-bit register as two 16-bit registers, 253-254 display memory, accessing, 621-622 -**Draw360x480Dot** subroutine, 613-614 +`Draw360x480Dot` subroutine, 613-614 drawing speed, 618 @@ -6031,11 +6031,11 @@ horizontal resolution, 620 line drawing demo program, **615-618**, 618-619 -mode set routine (John Bridges), 609, **612**, 620-621 +mode set routine (John Bridges), 609, `612`, 620-621 on VGA clones, 610-611 -**Read360x480Dot** subroutine, **614-615** +`Read360x480Dot` subroutine, **614-615** 256-color resolution, 619-620 @@ -6069,7 +6069,7 @@ stopping, 54, 65 Timer modes, 44, 45 -**TIMER\_INT** BIOS routine, 44 +`TIMER_INT` BIOS routine, 44 Timers @@ -6101,7 +6101,7 @@ incremental transformations, 964 steps in, 935-936 -**TransformPolygon** function, **1203** +`TransformPolygon` function, `1203` Translation in 3-D space, 937-938 @@ -6147,7 +6147,7 @@ X-Sharp animation package, 972, **973-984**, 984-985 286 processor -**CMP** instruction, 161, 306 +`CMP` instruction, 161, 306 code alignment, 215-218 @@ -6167,17 +6167,17 @@ effective address calculations, 129, 223-225 instruction fetching, 215-218 -**LEA** vs. **ADD** instructions, 130 +`LEA` vs. `ADD` instructions, 130 lookup tables, vs. rotating or shifting, 145-146 -**LOOP** instruction vs. **DEC/JNZ** sequence, 139 +`LOOP` instruction vs. `DEC/JNZ` sequence, 139 memory access, performance, 223-225 new features, 221 -**POPF** instruction, and interrupts, 226 +`POPF` instruction, and interrupts, 226 protected mode, 208-209 @@ -6199,7 +6199,7 @@ Two-pass lighting, 1262 Two's complement negation, 171 -**U** +`U` Unifying models, and optimization, 1110-1111 @@ -6209,9 +6209,9 @@ Unit vectors, dot product, 1136-1137 Unrolling loops, 143-145, 305, 312, 377-378, 410 -**UpdateViewPos** function, **1202** +`UpdateViewPos` function, `1202` -**UpdateWorld** function, **1205**, **1237-1238** +`UpdateWorld` function, `1205`, **1237-1238** U-pipe, Pentium processor @@ -6221,7 +6221,7 @@ overview, 385-386 pairable instructions, 388 -**V** +`V` Variables, word-sized vs. byte-sized, 82, 83-85 @@ -6239,7 +6239,7 @@ optimization of, 986 unit vectors, dot product, 1136-1137 -**VectorsUp** function +`VectorsUp` function Bresenham's line-drawing algorithm, **664-665** @@ -6347,9 +6347,9 @@ pixel-by-pixel plane selection, **885-887** plane-by-plane processing, **887-889** -**ReadPixelX** subroutine, **884-885** +`ReadPixelX` subroutine, **884-885** -**WritePixelX** subroutine, **883-884** +`WritePixelX` subroutine, **883-884** and page flipping, 444-445 @@ -6645,9 +6645,9 @@ VQuake, 1287-1280 VSD. *See* Visible surface determination (VSD). -**W** +`W` -**Wait30Frames** function, **854** +`Wait30Frames` function, `854` Wait states @@ -6663,15 +6663,15 @@ overview, 99 system memory wait states, 210-213 -**WaitForVerticalSyncEnd** subroutine, **569**, **579-580** +`WaitForVerticalSyncEnd` subroutine, `569`, **579-580** -**WaitForVerticalSyncStart** subroutine, **569**, **579** +`WaitForVerticalSyncStart` subroutine, `569`, `579` -**WalkBSPTree** function, **1106** +`WalkBSPTree` function, `1106` -**WalkTree** function +`WalkTree` function -code recursive version, **1108** +code recursive version, `1108` data recursive version, **1109-1110** @@ -6700,11 +6700,11 @@ edge triggered device, 316 fine-tuning optimization, 312-313 -initial C implementation, **299** +initial C implementation, `299` -lookup table, **303**, 304, 317-319 +lookup table, `303`, 304, 317-319 -**ScanBuffer** assembly routine +`ScanBuffer` assembly routine author's implementation, **301-302** @@ -6716,7 +6716,7 @@ as state machine, 315 theoretical maximum performance, 319 -Word-**OUT** instruction, 429 +Word-`OUT` instruction, 429 Word-sized variables, 8088 processor @@ -6796,9 +6796,9 @@ text, drawing, 484, 490, 496 Write-after-write register contention, 404 -**WritePixel** subroutine, **597**, 599 +`WritePixel` subroutine, `597`, 599 -**WritePixelX** subroutine, **883-884** +`WritePixelX` subroutine, **883-884** Writing pixels @@ -6820,7 +6820,7 @@ pixel intensity calculations, 778-779 Wu, Xiaolin. *See* Wu antialiasing algorithm. -**X** +`X` X86 family CPUs @@ -6838,7 +6838,7 @@ limitations for assembly programmers, 27 lookup tables, vs. rotating or shifting, 145-146 -**LOOP** instruction vs. **DEC/JNZ** sequence, 139 +`LOOP` instruction vs. `DEC/JNZ` sequence, 139 machine instructions, versatility, 128 @@ -6848,23 +6848,23 @@ overview, 208 transformation inefficiencies, 26 -**XCHG** instruction, 377, 832 +`XCHG` instruction, 377, 832 X-clipping, in BSP tree rendering, 1159 -**XformAndProjectPObject** function, **974** +`XformAndProjectPObject` function, `974` -**XformAndProjectPoints** function, **960** +`XformAndProjectPoints` function, `960` -**XformAndProjectPoly** function, **944-945** +`XformAndProjectPoly` function, **944-945** -**XformVec** function +`XformVec` function assembly implementation, **996-997**, **1017-1019** -C implementation, **943**, **976** +C implementation, `943`, `976` -**XLAT** instruction +`XLAT` instruction in Boyer-Moore algorithm, 274-277 @@ -6872,25 +6872,25 @@ byte registers, 243 with lookup table, 304 -**XOR** instruction, vs. **NOT**, 147 +`XOR` instruction, vs. `NOT`, 147 X-Sharp 3-D animation package -**AppendRotationX** function, **975** +`AppendRotationX` function, `975` -**AppendRotationY** function, **964-965**, **975** +`AppendRotationY` function, **964-965**, `975` -**AppendRotationZ** function, **965**, **976** +`AppendRotationZ` function, `965`, `976` code archives on diskette, 985 -**ConcatXforms** function +`ConcatXforms` function assembly implementation, **997-999**, **1019-1022** -C implementations, **944**, **976** +C implementations, `944`, `976` -**CosSin** subroutine, **994-996**, 999, **1013-1015** +`CosSin` subroutine, **994-996**, 999, **1013-1015** DDA (digital differential analyzer) texture mapping @@ -6900,7 +6900,7 @@ C implementation, **1053-1058** disadvantages, 1052-1053, 1059 -**DrawTexturedPolygon**, **1055-1056** +`DrawTexturedPolygon`, **1055-1056** hardware dependence, 1053 @@ -6908,35 +6908,35 @@ multiple adjacent polygons, 1068 optimized implementation, **1069-1073**, 1074 -orientation independence, 1065-1067, **1067** +orientation independence, 1065-1067, `1067` performance, 1074 -**ScanOutLine** function, **1058-1059**, **1067** +`ScanOutLine` function, **1058-1059**, `1067` -**SetUpEdge** function, **1057-1058** +`SetUpEdge` function, **1057-1058** -**StepEdge** function, **1056-1057** +`StepEdge` function, **1056-1057** techniques, 1048-1051 -**DrawPObject** function, **978-979** +`DrawPObject` function, **978-979** ambient and diffuse shading support, **1025-1027** -**FixedDiv** subroutine, **982**, **993**, **1010-1012** +`FixedDiv` subroutine, `982`, `993`, **1010-1012** -FIXED\_MUL macro, 1016-1017 +`FIXED_MUL` macro, 1016-1017 -**FixedMul** subroutine, **981**, **993-994**, **1009-1010** +`FixedMul` subroutine, `981`, **993-994**, **1009-1010** -**InitializeCubes** function, **980-981** +`InitializeCubes` function, **980-981** -**InitializeFixedPoint** function, **977** +`InitializeFixedPoint` function, `977` matrix math, assembly routines, 992, **996-999** -**ModelColorToColorIndex** function, 1036, **1038** +`ModelColorToColorIndex` function, 1036, `1038` older processors, support for, 1007-1008, **1008-1023** @@ -6950,29 +6950,29 @@ mapping to 256-color mode, 1036, **1037-1038**, 1039 overview, 1034-1035 -**RotateAndMovePObject** function, **977-978** +`RotateAndMovePObject` function, **977-978** -**XformAndProjectPObject** function, **974** +`XformAndProjectPObject` function, `974` -**XformVec** function +`XformVec` function assembly implementation, **996-997**, **1017-1019** -C implementation, **976** +C implementation, `976` -**XSortAET** function +`XSortAET` function -complex polygons, **748** +complex polygons, `748` -monotone-vertical polygons, **769** +monotone-vertical polygons, `769` -**Y** +`Y` Yaw angle, in polygon clipping, 1206 Y-clipping, in BSP tree rendering, 1159 -**Z** +`Z` Z-buffers @@ -7010,15 +7010,15 @@ prefetch queue cycle-eater, 88, 92 PS/2 compatibility, 66 -PZTEST.ASM listing, **49** +PZTEST.ASM listing, `49` -PZTIME.BAT listing, **51** +PZTIME.BAT listing, `51` PZTIMER.ASM listing, **35-42** -**ReferenceZTimerOff** subroutine, **41** +`ReferenceZTimerOff` subroutine, `41` -**ReferenceZTimerOn** subroutine, **40** +`ReferenceZTimerOn` subroutine, `40` reporting results, 47 @@ -7030,15 +7030,15 @@ system clock inaccuracies, 43, 45-46, 48 test-bed program, 48-52 -TESTCODE listing, **50** +TESTCODE listing, `50` timing 486 code, 245-246 -**ZTimerOff** subroutine, **38-41**, 46-47 +`ZTimerOff` subroutine, **38-41**, 46-47 -**ZTimerOn** subroutine, **37-38**, 43 +`ZTimerOn` subroutine, **37-38**, 43 -**ZTimerReport** subroutine, **41-42**, 47-48 +`ZTimerReport` subroutine, **41-42**, 47-48 Zero-wait-state memory, 211 @@ -7046,21 +7046,21 @@ Z-order display, masked images, 872 Z-sorting, for hidden surface removal, 1220-1222 -**ZSortObjects** function, **1201** +`ZSortObjects` function, `1201` -**ZTimerOff** subroutine +`ZTimerOff` subroutine long-period Zen timer, **59-63** Zen timer, **38-41**, 46-47 -**ZTimerOn** subroutine +`ZTimerOn` subroutine long-period Zen timer, **58-59** Zen timer, **37-38**, 43 -**ZTimerReport** subroutine +`ZTimerReport` subroutine long-period Zen timer, **63-65**