diff --git a/01-01.md b/01-01.md index 11aeb31..47cee25 100644 --- a/01-01.md +++ b/01-01.md @@ -18,16 +18,16 @@ proper care, however, and those ugly boxes are capable of miracles. The key is this: Only on microcomputers do you have the run of the whole machine, without layers of operating systems, drivers, and the like getting in the way. You can do *anything* you want, and you can -understand everything that’s going on, if you so wish. +understand everything that's going on, if you so wish. -As we’ll see shortly, you should indeed so wish. +As we'll see shortly, you should indeed so wish. Is performance still an issue in this era of cheap 486 computers and super-fast Pentium computers? You bet. How many programs that *you* use -really run so fast that you wouldn’t be happier if they ran faster? -We’re so used to slow software that when a compile-and-link sequence +really run so fast that you wouldn't be happier if they ran faster? +We're so used to slow software that when a compile-and-link sequence that took two minutes on a PC takes just ten seconds on a 486 computer, -we’re ecstatic—when in truth we should be settling for nothing less than +we're ecstatic—when in truth we should be settling for nothing less than instantaneous response. Impossible, you say? Not with the proper design, including incremental @@ -35,13 +35,13 @@ compilation and linking, use of extended and/or expanded memory, and well-crafted code. PCs can do just about anything you can imagine (with a few obvious exceptions, such as applications involving super-computer-class number-crunching) if you believe that it can be -done, if you understand the computer inside and out, and if you’re +done, if you understand the computer inside and out, and if you're willing to think past the obvious solution to unconventional but potentially more fruitful approaches. -My point is simply this: PCs can work wonders. It’s not easy coaxing -them into doing that, but it’s rewarding—and it’s sure as heck fun. In -this book, we’re going to work some of those wonders, starting... +My point is simply this: PCs can work wonders. It's not easy coaxing +them into doing that, but it's rewarding—and it's sure as heck fun. In +this book, we're going to work some of those wonders, starting... ...now. @@ -58,14 +58,14 @@ pointless. Notice that the above definition most emphatically does *not* say anything about making the software as fast as possible. It also does not say anything about using assembly language, or an optimizing compiler, -or, for that matter, a compiler at all. It also doesn’t say anything +or, for that matter, a compiler at all. It also doesn't say anything about how the code was designed and written. What it does say is that -high-performance code shouldn’t get in the user’s way—and that’s *all*. +high-performance code shouldn't get in the user's way—and that's *all*. -That’s an important distinction, because all too many programmers think +That's an important distinction, because all too many programmers think that assembly language, or the right compiler, or a particular high-level language, or a certain design approach is the answer to -creating high-performance code. They’re not, any more than choosing a +creating high-performance code. They're not, any more than choosing a certain set of tools is the key to building a house. You do indeed need tools to build a house, but any of many sets of tools will do. You also need a blueprint, an understanding of everything that goes into a house, @@ -80,13 +80,13 @@ assembly language. The optimization at the end is just the finishing touch, however. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Without good design, good algorithms, and complete understanding of the program’s operation, your carefully optimized code will amount to one of mankind’s least fruitful creations—a fast slow program*. + ![](images/i.jpg) *Without good design, good algorithms, and complete understanding of the program's operation, your carefully optimized code will amount to one of mankind's least fruitful creations—a fast slow program*. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -“What’s a fast slow program?” you ask. That’s a good question, and a +"What's a fast slow program?" you ask. That's a good question, and a brief (true) story is perhaps the best answer. -#### When Fast Isn’t Fast {#Heading4} +#### When Fast Isn't Fast {#Heading4} In the early 1970s, as the first hand-held calculators were hitting the market, I knew a fellow named Irwin. He was a good student, and was @@ -98,21 +98,21 @@ calculator to a duel—and won, becoming a local legend in the process. When you get right down to it, though, Irwin was spitting into the wind. In a few short years his hard-earned slipstick skills would be worthless, and the entire discipline would be essentially wiped from the -face of the earth. What’s more, anyone with half a brain could see that +face of the earth. What's more, anyone with half a brain could see that changeover coming. Irwin had basically wasted the considerable effort and time he had spent optimizing his soon-to-be-obsolete skills. What does all this have to do with programming? Plenty. When you spend time optimizing poorly-designed assembly code, or when you count on an -optimizing compiler to make your code fast, you’re wasting the -optimization, much as Irwin did. Particularly in assembly, you’ll find +optimizing compiler to make your code fast, you're wasting the +optimization, much as Irwin did. Particularly in assembly, you'll find that without proper up-front design and everything else that goes into -high-performance design, you’ll waste considerable effort and time on +high-performance design, you'll waste considerable effort and time on making an inherently slow program as fast as possible—which is still slow—when you could easily have improved performance a great deal more -with just a little thought. As we’ll see, handcrafted assembly language +with just a little thought. As we'll see, handcrafted assembly language and optimizing compilers matter, but less than you might think, in the -grand scheme of things—and they scarcely matter at all unless they’re +grand scheme of things—and they scarcely matter at all unless they're used in the context of a good design and a thorough understanding of both the task at hand and the PC. diff --git a/01-02.md b/01-02.md index ad8977f..9feb1de 100644 --- a/01-02.md +++ b/01-02.md @@ -4,9 +4,9 @@ ### Rules for Building High-Performance Code {#Heading5} -We’ve got the following rules for creating high-performance software: +We've got the following rules for creating high-performance software: -- Know where you’re going (understand the objective of the software). +- Know where you're going (understand the objective of the software). - Make a big map (have an overall program design firmly in mind, so the various parts of the program and the data structures work well together). @@ -15,29 +15,29 @@ We’ve got the following rules for creating high-performance software: - Know the territory (understand exactly how the computer carries out each task). - Know when it matters (identify the portions of your programs where - performance matters, and don’t waste your time optimizing the rest). -- Always consider the alternatives (don’t get stuck on a single - approach; odds are there’s a better way, if you’re clever and + performance matters, and don't waste your time optimizing the rest). +- Always consider the alternatives (don't get stuck on a single + approach; odds are there's a better way, if you're clever and inventive enough). - Know how to turn on the juice (optimize the code as best you know how when it *does* matter). Making rules is easy; the hard part is figuring out how to apply them in the real world. For my money, examining some actual working code is -always a good way to get a handle on programming concepts, so let’s look +always a good way to get a handle on programming concepts, so let's look at some of the performance rules in action. -#### Know Where You’re Going {#Heading6} +#### Know Where You're Going {#Heading6} -If we’re going to create high-performance code, first we have to know -what that code is going to do. As an example, let’s write a program that +If we're going to create high-performance code, first we have to know +what that code is going to do. As an example, let's write a program that generates a 16-bit checksum of the bytes in a file. In other words, the program will add each byte in a specified file in turn into a 16-bit -value. This checksum value might be used to make sure that a file hasn’t +value. This checksum value might be used to make sure that a file hasn't been corrupted, as might occur during transmission over a modem or if a -Trojan horse virus rears its ugly head. We’re not going to do anything +Trojan horse virus rears its ugly head. We're not going to do anything with the checksum value other than print it out, however; right now -we’re only interested in generating that checksum value as rapidly as +we're only interested in generating that checksum value as rapidly as possible. #### Make a Big Map {#Heading7} @@ -50,27 +50,27 @@ bytes and adding them together. #### Make Lots of Little Maps {#Heading8} -Actually, we’re only going to make one little map, because we only have +Actually, we're only going to make one little map, because we only have one program section that requires much thought—the section that reads -the bytes and adds them up. What’s the best way to do this? +the bytes and adds them up. What's the best way to do this? It would be convenient to load the entire file into memory and then sum -the bytes in one loop. Unfortunately, there’s no guarantee that any -particular file will fit in the available memory; in fact, it’s a sure -thing that many files *won’t* fit into memory, so that approach is out. +the bytes in one loop. Unfortunately, there's no guarantee that any +particular file will fit in the available memory; in fact, it's a sure +thing that many files *won't* fit into memory, so that approach is out. -Well, if the whole file won’t fit into memory, one byte surely will. If +Well, if the whole file won't fit into memory, one byte surely will. If we read the file one byte at a time, adding each byte to the checksum -value before reading the next byte, we’ll minimize memory requirements +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: -It’s *slow*. +It's *slow*. **LISTING 1.1 L1-1.C** @@ -89,11 +89,11 @@ It’s *slow*. int ReadLength; if ( argc != 2 ) { - printf(“usage: checksum filename\n”); + printf("usage: checksum filename\n"); exit(1); } if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) { - printf(“Can’t open file: %s\n”, argv[1]); + printf("Can't open file: %s\n", argv[1]); exit(1); } @@ -105,13 +105,13 @@ It’s *slow*. Checksum += (unsigned int) Byte; } if ( ReadLength == -1 ) { - printf(“Error reading file %s\n”, argv[1]); + printf("Error reading file %s\n", argv[1]); exit(1); } /* Report the result */ - printf(“The checksum is: %u\n”, Checksum); + printf("The checksum is: %u\n", Checksum); exit(0); } @@ -127,14 +127,14 @@ requires over two and one-half minutes to checksum *one* file! ![](images/i.jpg) *Listings 1.2 and 1.3 form the C/assembly equivalent to Listing 1.1, and Listings 1.6 and 1.7 form the C/assembly equivalent to Listing 1.5.* ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------- -These results make it clear that it’s folly to rely on your compiler’s +These results make it clear that it's folly to rely on your compiler's optimization to make your programs fast. Listing 1.1 is simply poorly designed, and no amount of compiler optimization will compensate for that failing. To drive home the point, conListings 1.2 and 1.3, which together are equivalent to Listing 1.1 except that the entire checksum loop is written in tight assembly code. The assembly language implementation is indeed faster than any of the C versions, as shown in -Table 1.1, but it’s less than 10 percent faster, and it’s still +Table 1.1, but it's less than 10 percent faster, and it's still unacceptably slow. ------------------------ --------------------------------- -------------------- diff --git a/01-03.md b/01-03.md index a8084b8..4cfc32a 100644 --- a/01-03.md +++ b/01-03.md @@ -86,11 +86,11 @@ Ratio best\ 57.44 -**Note:** The execution times (in seconds) for this chapter’s listings +**Note:** The execution times (in seconds) for this chapter's listings were timed when the compiled listings were run on the WordPerfect 4.2 thesaurus file TH.WP (362,293 bytes in size), as compiled in the small model with Borland and Microsoft compilers with optimization on (opt) -and off (no opt). All times were measured with Paradigm Systems’ TIMER +and off (no opt). All times were measured with Paradigm Systems' TIMER program on a 10 MHz 1-wait-state AT clone with a 28-ms hard disk, with disk caching turned off. @@ -118,20 +118,20 @@ Table 1.1 Execution Times for WordPerfect Checksum. int ReadLength; if ( argc != 2 ) { - printf(“usage: checksum filename\n”); + printf("usage: checksum filename\n"); exit(1); } if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) { - printf(“Can’t open file: %s\n”, argv[1]); + printf("Can't open file: %s\n", argv[1]); exit(1); } if ( !ChecksumFile(Handle, &Checksum) ) { - printf(“Error reading file %s\n”, argv[1]); + printf("Error reading file %s\n", argv[1]); exit(1); } /* Report the result */ - printf(“The checksum is: %u\n”, Checksum); + printf("The checksum is: %u\n", Checksum); exit(0); } @@ -170,7 +170,7 @@ Table 1.1 Execution Times for WordPerfect Checksum. _ChecksumFile proc near push bp mov bp,sp - push si ;save C’s register variable + push si ;save C's register variable ; mov bx,[bp+Handle] ;get file handle sub si,si ;zero the checksum ;accumulator @@ -183,7 +183,7 @@ Table 1.1 Execution Times for WordPerfect Checksum. int 21h ;read the byte jcErrorEnd;an error occurred and ax,ax ;any bytes read? - jz Success ;no-end of file reached-we’re done + jz Success ;no-end of file reached-we're done add si,[TempWord] ;add the byte into the ;checksum total jmpChecksumLoop @@ -196,7 +196,7 @@ Table 1.1 Execution Times for WordPerfect Checksum. mov ax,1 ;success ; Done: - pop si ;restore C’s register variable + pop si ;restore C's register variable pop bp ret _ChecksumFileendp @@ -206,7 +206,7 @@ The lesson is clear: Optimization makes code faster, but without proper design, optimization just creates fast slow code. Well, then, how are we going to improve our design? Before we can do -that, we have to understand what’s wrong with the current design. +that, we have to understand what's wrong with the current design. #### Know the Territory {#Heading9} @@ -229,17 +229,17 @@ a *long* time—far, far longer than the rest of the main loop in Listing **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 -overhead to DOS calls, and that’s what’s draining the life out of +using a code profiler, but take my word for it: There's a great deal of +overhead to DOS calls, and that's what's draining the life out of Listing 1.1. How can we speed up Listing 1.1? It should be clear that we must somehow avoid invoking DOS for every byte in the file, and that means reading more than one byte at a time, then buffering the data and parceling it -out for examination one byte at a time. By gosh, that’s a description of -C’s stream I/O feature, whereby C reads files in chunks and buffers the +out for examination one byte at a time. By gosh, that's a description of +C's stream I/O feature, whereby C reads files in chunks and buffers the bytes internally, doling them out to the application as needed by -reading them from memory rather than calling DOS. Let’s try using stream +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 diff --git a/01-04.md b/01-04.md index b8abeb9..9e6f6d2 100644 --- a/01-04.md +++ b/01-04.md @@ -24,11 +24,11 @@ libraries do their work. In other words, *know the territory*! unsigned int Checksum; if ( argc != 2 ) { - printf(“usage: checksum filename\n”); + printf("usage: checksum filename\n"); exit(1); } - if ( (CheckFile = fopen(argv[1], “rb”)) == NULL ) { - printf(“Can’t open file: %s\n”, argv[1]); + if ( (CheckFile = fopen(argv[1], "rb")) == NULL ) { + printf("Can't open file: %s\n", argv[1]); exit(1); } @@ -41,7 +41,7 @@ libraries do their work. In other words, *know the territory*! } /* Report the result */ - printf(“The checksum is: %u\n”, Checksum); + printf("The checksum is: %u\n", Checksum); exit(0); } @@ -51,39 +51,39 @@ The last section contained a particularly interesting phrase: *the time-critical portions of your code*. Time-critical portions of your code are those portions in which the speed of the code makes a significant difference in the overall performance of your program—and by -“significant,” I don’t mean that it makes the code 100 percent faster, +"significant," I don't mean that it makes the code 100 percent faster, or 200 percent, or any particular amount at all, but rather that it -makes the program more responsive and/or usable *from the user’s +makes the program more responsive and/or usable *from the user's perspective*. -Don’t waste time optimizing non-time-critical code: set-up code, +Don't waste time optimizing non-time-critical code: set-up code, initialization code, and the like. Spend your time improving the performance of the code inside heavily-used loops and in the portions of your programs that directly affect response time. Notice, for example, -that I haven’t bothered to implement a version of the checksum program +that I haven't bothered to implement a version of the checksum program entirely in assembly; Listings 1.2 and 1.6 call assembly subroutines that handle the time-critical operations, but C is still used for checking command-line parameters, operning files, printing, and the like. ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *If you were to implement any of the listings in this chapter entirely in hand-optimized assembly, I suppose you might get a performance improvement of a few percent—but I rather doubt you’d get even that much, and you’d sure as heck spend an awful lot of time for whatever meager improvement does result. Let C do what it does well, and use assembly only when it makes a perceptible difference.* + ![](images/i.jpg) *If you were to implement any of the listings in this chapter entirely in hand-optimized assembly, I suppose you might get a performance improvement of a few percent—but I rather doubt you'd get even that much, and you'd sure as heck spend an awful lot of time for whatever meager improvement does result. Let C do what it does well, and use assembly only when it makes a perceptible difference.* ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Besides, we don’t want to optimize until the design is refined to our -satisfaction, and that won’t be the case until we’ve thought about other +Besides, we don't want to optimize until the design is refined to our +satisfaction, and that won't be the case until we've thought about other approaches. #### Always Consider the Alternatives {#Heading11} -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 +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 -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 -think that it’s faster than having our program read and manage blocks +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 +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 @@ -100,19 +100,19 @@ to improve on Listing 1.4: **1.**  The code is already fast enough. **2.**  The code works, and some people are content with code that -works, even when it’s slow enough to be annoying. +works, even when it's slow enough to be annoying. -**3.**  The C library is written in optimized assembly, and it’s likely +**3.**  The C library is written in optimized assembly, and it's likely to be faster than any code that the average programmer could write to perform essentially the same function. **4.**  The C library conveniently handles the buffering of file data, and it would be a nuisance to have to implement that capability. -I’ll ignore the first reason, both because performance is no longer an +I'll ignore the first reason, both because performance is no longer an issue if the code is fast enough and because the current application does *not* run fast enough—13 seconds is a long time. (Stop and wait for -13 seconds while you’re doing something intense, and you’ll see just how +13 seconds while you're doing something intense, and you'll see just how long it is.) The second reason is the hallmark of the mediocre programmer. Know when diff --git a/01-05.md b/01-05.md index eca7833..50b4503 100644 --- a/01-05.md +++ b/01-05.md @@ -4,8 +4,8 @@ The third reason is often fallacious. C library functions are not always written in assembly, nor are they always particularly well-optimized. -(In fact, they’re often written for *portability*, which has nothing to -do with optimization.) What’s more, they’re general-purpose functions, +(In fact, they're often written for *portability*, which has nothing to +do with optimization.) What's more, they're general-purpose functions, and often can be outperformed by well-but-not- brilliantly-written code that is well-matched to a specific task. As an example, consider Listing 1.5, which uses internal buffering to handle blocks of bytes at a time. @@ -14,7 +14,7 @@ Table 1.1 shows that Listing 1.5 is 2.5 to 4 times faster than Listing uses no assembly at all. ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Clearly, you can do well by using special-purpose C code in place of a C library function—if you have a thorough understanding of how the C library function operates and exactly what your application needs done. Otherwise, you’ll end up rewriting C library functions in C, which makes no sense at all.* + ![](images/i.jpg) *Clearly, you can do well by using special-purpose C code in place of a C library function—if you have a thorough understanding of how the C library function operates and exactly what your application needs done. Otherwise, you'll end up rewriting C library functions in C, which makes no sense at all.* ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- **LISTING 1.5 L1-5.C** @@ -38,17 +38,17 @@ uses no assembly at all. int WorkingLength, LengthCount; if ( argc != 2 ) { - printf(“usage: checksum filename\n”); + printf("usage: checksum filename\n"); exit(1); } if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) { - printf(“Can’t open file: %s\n”, argv[1]); + printf("Can't open file: %s\n", argv[1]); exit(1); } /* Get memory in which to buffer the data */ if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) { - printf(“Can’t get enough memory\n”); + printf("Can't get enough memory\n"); exit(1); } @@ -59,7 +59,7 @@ uses no assembly at all. do { if ( (WorkingLength = read(Handle, WorkingBuffer, BUFFER_SIZE)) == -1 ) { - printf(“Error reading file %s\n”, argv[1]); + printf("Error reading file %s\n", argv[1]); exit(1); } /* Checksum this chunk */ @@ -72,14 +72,14 @@ uses no assembly at all. } while ( WorkingLength ); /* Report the result */ - printf(“The checksum is: %u\n”, Checksum); + printf("The checksum is: %u\n", Checksum); exit(0); } That brings us to the fourth reason: avoiding an internal-buffered implementation like Listing 1.5 because of the difficulty of coding such an approach. True, it is easier to let a C library function do the work, -but it’s not all that hard to do the buffering internally. The key is +but it's not all that hard to do the buffering internally. The key is the concept of handling data in *restartable blocks;* that is, reading a chunk of data, operating on the data until it runs out, suspending the operation while more data is read in, and then continuing as though @@ -90,11 +90,11 @@ because checksumming works with one byte at a time, forgetting about each byte immediately after adding it into the total. Listing 1.5 reads in a block of bytes from the file, checksums the bytes in the block, and gets another block, repeating the process until the entire file has been -processed. In Chapter 5, we’ll see a more complex restartable block +processed. In Chapter 5, we'll see a more complex restartable block implementation, involving searching for text strings. -At any rate, Listing 1.5 isn’t much more complicated than Listing -1.4—and it’s a *lot* faster. Always consider the alternatives; a bit of +At any rate, Listing 1.5 isn't much more complicated than Listing +1.4—and it's a *lot* faster. Always consider the alternatives; a bit of clever thinking and program redesign can go a long way. #### Know How to Turn On the Juice {#Heading12} @@ -103,7 +103,7 @@ I have said time and again that optimization is pointless until the design is settled. When that time comes, however, optimization can indeed make a significant difference. Table 1.1 indicates that the optimized version of Listing 1.5 produced by Microsoft C outperforms an -unoptimized version of the same code by more than 60 percent. What’s +unoptimized version of the same code by more than 60 percent. What's more, a mostly-assembly version of Listing 1.5, shown in Listings 1.6 and 1.7, outperforms even the best-optimized C version of List1.5 by 26 percent. These are considerable improvements, well worth pursuing—once @@ -131,17 +131,17 @@ the design has been maxed out. int WorkingLength; if ( argc != 2 ) { - printf(“usage: checksum filename\n”); + printf("usage: checksum filename\n"); exit(1); } if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) { - printf(“Can’t open file: %s\n”, argv[1]); + printf("Can't open file: %s\n", argv[1]); exit(1); } /* Get memory in which to buffer the data */ if ( (WorkingBuffer = malloc(BUFFER_SIZE)) == NULL ) { - printf(“Can’t get enough memory\n”); + printf("Can't get enough memory\n"); exit(1); } @@ -152,16 +152,16 @@ the design has been maxed out. do { if ( (WorkingLength = read(Handle, WorkingBuffer, BUFFER_SIZE)) == -1 ) { - printf(“Error reading file %s\n”, argv[1]); + printf("Error reading file %s\n", argv[1]); exit(1); } - /* Checksum this chunk if there’s anything in it */ + /* Checksum this chunk if there's anything in it */ if ( WorkingLength ) ChecksumChunk(WorkingBuffer, WorkingLength, &Checksum); } while ( WorkingLength ); /* Report the result */ - printf(“The checksum is: %u\n”, Checksum); + printf("The checksum is: %u\n", Checksum); exit(0); } diff --git a/01-06.md b/01-06.md index 516b244..d8f77a0 100644 --- a/01-06.md +++ b/01-06.md @@ -34,7 +34,7 @@ _ChecksumChunkprocnear push bp mov bp,sp - push si ;save C’s register variable + push si ;save C's register variable ; cld ;make LODSB increment SI mov si,[bp+Buffer] ;point to buffer @@ -48,7 +48,7 @@ loop ChecksumLoop ;continue for all bytes in block mov [bx],dx ;save the new checksum ; - pop si ;restore C’s register variable + pop si ;restore C's register variable pop bp ret _ChecksumChunkendp @@ -58,7 +58,7 @@ Note that in Table 1.1, optimization makes little difference except in the case of Listing 1.5, where the design has been refined considerably. Execution time in the other cases is dominated by time spent in DOS and/or the C library, so optimization of the code you write is pretty -much irrelevant. What’s more, while the approximately two-times +much irrelevant. What's more, while the approximately two-times improvement we got by optimizing is not to be sneezed at, it pales against the up-to-50-times improvement we got by redesigning. @@ -74,41 +74,41 @@ tends to be considerably faster relative to C than it is in this very specific case. ------------------- ----------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Don’t get hung up on optimizing compilers or assembly language—the best optimizer is between your ears.* + ![](images/i.jpg) *Don't get hung up on optimizing compilers or assembly language—the best optimizer is between your ears.* ------------------- ----------------------------------------------------------------------------------------------------------- -All this is basically a way of saying: Know where you’re going, know the +All this is basically a way of saying: Know where you're going, know the territory, and know when it matters. -### Where We’ve Been, What We’ve Seen {#Heading13} +### Where We've Been, What We've Seen {#Heading13} -What have we learned? Don’t let other people’s code—even DOS—do the work +What have we learned? Don't let other people's code—even DOS—do the work for you when speed matters, at least not without knowing what that code does and how well it performs. -Optimization only matters after you’ve done your part on the program +Optimization only matters after you've done your part on the program design end. Consider the ratios on the vertical axis of Table 1.1, which show that optimization is almost totally wasted in the checksumming application without an efficient design. Optimization is no panacea. Table 1.1 shows a two-times improvement from optimization—and a 50-times-plus improvement from redesign. The longstanding debate about -which C compiler optimizes code best doesn’t matter quite so much in +which C compiler optimizes code best doesn't matter quite so much in light of Table 1.1, does it? Your organic optimizer matters much more -than your compiler’s optimizer, and there’s always assembly for those +than your compiler's optimizer, and there's always assembly for those usually small sections of code where performance really matters. -#### Where We’re Going {#Heading14} +#### Where We're Going {#Heading14} This chapter has presented a quick step-by-step overview of the design -process. I’m not claiming that this is the only way to create -high-performance code; it’s just an approach that works for me. Create +process. I'm not claiming that this is the only way to create +high-performance code; it's just an approach that works for me. Create code however you want, but never forget that design matters more than detailed optimization. Never stop looking for inventive ways to boost -performance—and never waste time speeding up code that doesn’t need to +performance—and never waste time speeding up code that doesn't need to be sped up. -I’m going to focus on specific ways to create high-performance code from -now on. In Chapter 5, we’ll continue to look at restartable blocks and +I'm going to focus on specific ways to create high-performance code from +now on. In Chapter 5, we'll continue to look at restartable blocks and internal buffering, in the form of a program that searches files for text strings. diff --git a/02-01.md b/02-01.md index 3b20e07..483e9d9 100644 --- a/02-01.md +++ b/02-01.md @@ -9,17 +9,17 @@ Chapter 2\ ### The Unique Nature of Assembly Language Optimization {#Heading2} As I showed in the previous chapter, optimization is by no means always -a matter of “dropping into assembly.” In fact, in performance tuning +a matter of "dropping into assembly." In fact, in performance tuning high-level language code, assembly should be used rarely, and then only -after you’ve made sure a badly chosen or clumsily implemented algorithm -isn’t eating you alive. Certainly if you use assembly at all, make +after you've made sure a badly chosen or clumsily implemented algorithm +isn't eating you alive. Certainly if you use assembly at all, make absolutely sure you use it *right*. The potential of assembly code to run *slowly* is poorly understood by a lot of people, but that potential is great, especially in the hands of the ignorant. Truly great optimization, however, happens *only* at the assembly level, and it happens in response to a set of dynamics that is totally -different from that governing C/C++ or Pascal optimization. I’ll be +different from that governing C/C++ or Pascal optimization. I'll be speaking of assembly-level optimization time and again in this book, but when I do, I think it will be helpful if you have a grasp of those assembly specific dynamics. @@ -32,7 +32,7 @@ Some time ago, I was asked to work over a critical assembly subroutine in order to make it run as fast as possible. The task of the subroutine was to construct a nibble out of four bits read from different bytes, rotating and combining the bits so that they ultimately ended up neatly -aligned in bits 3-0 of a single byte. (In case you’re curious, the +aligned in bits 3-0 of a single byte. (In case you're curious, the object was to construct a 16-color pixel from bits scattered over 4 bytes.) I examined the subroutine line by line, saving a cycle here and a cycle there, until the code truly seemed to be optimized. When I was @@ -47,7 +47,7 @@ done, the key part of the code looked something like this: dec dx ;count down the number of bits jnz LoopTop ;process the next bit, if any -Now, it’s hard to write code that’s much faster than seven instructions, +Now, it's hard to write code that's much faster than seven instructions, only one of which accesses memory, and most programmers would have called it a day at this point. Still, something bothered me, so I spent a bit of time going over the code again. Suddenly, the answer struck @@ -76,9 +76,9 @@ 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 +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.) +that's a tale for another chapter.) The point is this: To write truly superior assembly programs, you need to know what the various instructions do and which instructions execute diff --git a/02-02.md b/02-02.md index 9b9d11a..ccce3f4 100644 --- a/02-02.md +++ b/02-02.md @@ -38,11 +38,11 @@ computer—*but properly constructed assembly programs suffer no transformation loss*, as shown in Figure 2.2. Only one transformation is required when creating an assembler program, -and that single transformation is completely under the programmer’s +and that single transformation is completely under the programmer's control. Assemblers perform no transformation from source code to machine language; instead, they merely map assembler instructions to machine language instructions on a one-to-one basis. As a result, the -programmer is able to produce machine language code that’s precisely +programmer is able to produce machine language code that's precisely tailored to the needs of each task a given application requires. ![](images/02-02.jpg)\ @@ -88,7 +88,7 @@ enough. The single most critical aspect of the hardware, and the one about which it is hardest to learn, is the CPU. The x86 family CPUs have a complex, irregular instruction set, and, unlike most processors, they are neither -straightforward nor wellregarding true code performance. What’s more, +straightforward nor wellregarding true code performance. What's more, assembly is so difficult to learn that most articles and books that present assembly code settle for code that just works, rather than code that pushes the CPU to its limits. In fact, since most articles and @@ -100,7 +100,7 @@ knowledge to gather. A good portion of this book is devoted to seeking out such knowledge. ------------------- ------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Be forewarned, though: No matter how much you learn about programming the PC in assembly, there’s always more to discover.* + ![](images/i.jpg) *Be forewarned, though: No matter how much you learn about programming the PC in assembly, there's always more to discover.* ------------------- ------------------------------------------------------------------------------------------------------------------------------ ------------------------ --------------------------------- -------------------- diff --git a/02-03.md b/02-03.md index 3ba8734..0277af3 100644 --- a/02-03.md +++ b/02-03.md @@ -6,7 +6,7 @@ Is the never-ending collection of information all there is to the assembly optimization, then? Hardly. Knowledge is simply a necessary -base on which to build. Let’s take a moment to examine the objectives of +base on which to build. Let's take a moment to examine the objectives of good assembly programming, and the remainder of the forces that act on assembly optimization will fall into place. @@ -14,12 +14,12 @@ Basically, there are only two possible objectives to high-performance assembly programming: Given the requirements of the application, keep to a minimum either the number of processor cycles the program takes to run, or the number of bytes in the program, or some combination of both. -We’ll look at ways to achieve both objectives, but we’ll more often be +We'll look at ways to achieve both objectives, but we'll more often be concerned with saving cycles than saving bytes, for the PC generally offers relatively more memory than it does processing horsepower. In -fact, we’ll find that two-to-three times performance improvements *over -already tight assembly code* are often possible if we’re willing to -spend additional bytes in order to save cycles. It’s not always +fact, we'll find that two-to-three times performance improvements *over +already tight assembly code* are often possible if we're willing to +spend additional bytes in order to save cycles. It's not always desirable to use such techniques to speed up code, due to the heavy memory requirements—but it is almost always *possible*. @@ -32,7 +32,7 @@ only about how well that software performs, not how it was developed nor how it is maintained. These days, developers spend so much time focusing on such admittedly important issues as code maintainability and reusability, source code control, choice of development environment, and -the like that they often forget rule \#1: From the user’s perspective, +the like that they often forget rule \#1: From the user's perspective, *performance is fundamental*. ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -41,7 +41,7 @@ the like that they often forget rule \#1: From the user’s perspective, Knowledge of the sort described earlier is absolutely essential to fulfilling either of the objectives of assembly programming. What that -knowledge doesn’t do by itself is meet the need to write code that both +knowledge doesn't do by itself is meet the need to write code that both performs to the requirements of the application at hand and also operates as efficiently as possible in the PC environment. Knowledge makes that possible, but your programming instincts make it happen. And @@ -63,7 +63,7 @@ doing. Never underestimate the importance of the flexible mind. Good assembly code is better than good compiled code. Many people would have you -believe otherwise, but they’re wrong. That doesn’t mean that high-level +believe otherwise, but they're wrong. That doesn't mean that high-level languages are useless; far from it. High-level languages are the best choice for the majority of programmers, and for the bulk of the code of most applications. When the *best* code—the fastest or smallest code diff --git a/03-01.md b/03-01.md index 3205c66..314b0d4 100644 --- a/03-01.md +++ b/03-01.md @@ -8,22 +8,22 @@ Chapter 3\ ### Understanding and Using the Zen Timer {#Heading2} -When you’re pushing the envelope in writing optimized PC code, you’re +When you're pushing the envelope in writing optimized PC code, you're likely to become more than a little compulsive about finding approaches -that let you wring more speed from your computer. In the process, you’re +that let you wring more speed from your computer. In the process, you're bound to make mistakes, which is fine—as long as you watch for those mistakes and *learn* from them. A case in point: A few years back, I came across an article about 8088 -assembly language called “Optimizing for Speed.” Now, “optimize” is not -a word to be used lightly; *Webster’s Ninth New Collegiate Dictionary* -defines optimize as “to make as perfect, effective, or functional as -possible,” which certainly leaves little room for error. The author had, +assembly language called "Optimizing for Speed." Now, "optimize" is not +a word to be used lightly; *Webster's Ninth New Collegiate Dictionary* +defines optimize as "to make as perfect, effective, or functional as +possible," which certainly leaves little room for error. The author had, however, chosen a small, well-defined 8088 assembly language routine to refine, consisting of about 30 instructions that did nothing more than expand 8 bits to 16 bits by duplicating each bit. -The author of “Optimizing” had clearly fine-tuned the code with care, +The author of "Optimizing" had clearly fine-tuned the code with care, examining alternative instruction sequences and adding up cycles until he arrived at an implementation he calculated to be nearly 50 percent faster than the original routine. In short, he had used all the @@ -44,20 +44,20 @@ code performance is instruction *fetch* time, a topic to which I will return in later chapters. Had the author taken the time to measure the true performance of his -code, he wouldn’t have put his reputation on the line with relatively -low-performance code. What’s more, had he actually measured the +code, he wouldn't have put his reputation on the line with relatively +low-performance code. What's more, had he actually measured the performance of his code and found it to be unexpectedly slow, curiosity might well have led him to experiment further and thereby add to his store of reliable information about the CPU. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *There you have an important tenet of assembly language optimization: After crafting the best code possible, check it in action to see if it’s really doing what you think it is. If it’s not behaving as expected, that’s all to the good, since solving mysteries is the path to knowledge. You’ll learn more in this way, I assure you, than from any manual or book on assembly language.* + ![](images/i.jpg) *There you have an important tenet of assembly language optimization: After crafting the best code possible, check it in action to see if it's really doing what you think it is. If it's not behaving as expected, that's all to the good, since solving mysteries is the path to knowledge. You'll learn more in this way, I assure you, than from any manual or book on assembly language.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ *Assume nothing*. I cannot emphasize this strongly enough—when you care about performance, do your best to improve the code and then *measure* -the improvement. If you don’t measure performance, you’re just guessing, -and if you’re guessing, you’re not very likely to write top-notch code. +the improvement. If you don't measure performance, you're just guessing, +and if you're guessing, you're not very likely to write top-notch code. Ignorance about true performance can be costly. When I wrote video games for a living, I spent days at a time trying to wring more performance @@ -65,7 +65,7 @@ from my graphics drivers. I rewrote whole sections of code just to save a few cycles, juggled registers, and relied heavily on blurry-fast register-to-register shifts and adds. As I was writing my last game, I discovered that the program ran perceptibly faster if I used look-up -tables instead of shifts and adds for my calculations. It *shouldn’t* +tables instead of shifts and adds for my calculations. It *shouldn't* have run faster, according to my cycle counting, but it did. In truth, instruction fetching was rearing its head again, as it often does, and the fetching of the shifts and adds was taking as much as four times the @@ -88,7 +88,7 @@ measure of code performance is observing it in action. Clearly, one key to mastering Zen-class optimization is a tool with which to measure code performance. The most accurate way to measure performance is with expensive hardware, but reasonable measurements at -no cost can be made with the PC’s 8253 timer chip, which counts at a +no cost can be made with the PC's 8253 timer chip, which counts at a rate of slightly over 1,000,000 times per second. The 8253 can be started at the beginning of a block of code of interest and stopped at the end of that code, with the resulting count indicating how long the @@ -99,8 +99,8 @@ 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 -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 +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. ------------------------ --------------------------------- -------------------- diff --git a/03-02.md b/03-02.md index 7df281f..6d9684c 100644 --- a/03-02.md +++ b/03-02.md @@ -35,10 +35,10 @@ ; ; Note: These routines can introduce slight inaccuracies into the ; system clock count for each code section timed even if - ; timer 0 doesn’t overflow. If timer 0 does overflow, the + ; timer 0 doesn't overflow. If timer 0 does overflow, the ; system clock can become slow by virtually any amount of - ; time, since the system clock can’t advance while the - ; precison timer is timing. Consequently, it’s a good idea + ; time, since the system clock can't advance while the + ; precison timer is timing. Consequently, it's a good idea ; to reboot at the end of each timing session. (The ; battery-backed clock, if any, is not affected by the Zen ; timer.) @@ -49,7 +49,7 @@ ; in when ZTimerOn was called. ; - Code segment word public ‘CODE’ + Code segment word public ‘CODE' assumecs: Code, ds:nothing public ZTimerOn, ZTimerOff, ZTimerReport @@ -117,26 +117,26 @@ OutputStr label byte db 0dh, 0ah, ‘Timed count: ‘, 5 dup (?) ASCIICountEnd labelbyte - db ‘ microseconds’, 0dh, 0ah - db ‘$’ + db ‘ microseconds', 0dh, 0ah + db ‘$' ; ; String printed to report timer overflow. ; OverflowStr label byte db 0dh, 0ah - db ‘****************************************************’ + db ‘****************************************************' db 0dh, 0ah - db ‘* The timer overflowed, so the interval timed was *’ + db ‘* The timer overflowed, so the interval timed was *' db 0dh, 0ah - db ‘* too long for the precision timer to measure. *’ + db ‘* too long for the precision timer to measure. *' db 0dh, 0ah - db ‘* Please perform the timing test again with the *’ + db ‘* Please perform the timing test again with the *' db0dh, 0ah - db ‘* long-period timer. *’ + db ‘* long-period timer. *' db 0dh, 0ah - db ‘****************************************************’ + db ‘****************************************************' db 0dh, 0ah - db ‘$’ + db ‘$' ; ******************************************************************** ; * Routine called to start timing. * @@ -158,7 +158,7 @@ ; to 0 push ax ; - ; Turn on interrupts, so the timer interrupt can occur if it’s + ; Turn on interrupts, so the timer interrupt can occur if it's ; pending. ; sti @@ -171,7 +171,7 @@ mov al,00110100b ;mode 2 out MODE_8253,al ; - ; Set the timer count to 0, so we know we won’t get another + ; Set the timer count to 0, so we know we won't get another ; timer interrupt right away. ; Note: this introduces an inaccuracy of up to 54 ms in the system ; clock count each time it is executed. @@ -418,7 +418,7 @@ CTSLoop: sub dx, dx div bx - add dl,’0’ + add dl,'0' mov [si],dl dec si loop CTSLoop diff --git a/03-03.md b/03-03.md index adef4c0..691af5c 100644 --- a/03-03.md +++ b/03-03.md @@ -4,18 +4,18 @@ #### The Zen Timer Is a Means, Not an End {#Heading5} -We’re going to spend the rest of this chapter seeing what the Zen timer -can do, examining how it works, and learning how to use it. I’ll be +We're going to spend the rest of this chapter seeing what the Zen timer +can do, examining how it works, and learning how to use it. I'll be using the Zen timer again and again over the course of this book, so -it’s essential that you learn what the Zen timer can do and how to use +it's essential that you learn what the Zen timer can do and how to use it. On the other hand, it is by no means essential that you understand exactly how the Zen timer works. (Interesting, yes; essential, no.) -In other words, the Zen timer isn’t really part of the knowledge we -seek; rather, it’s one tool with which we’ll acquire that knowledge. -Consequently, you shouldn’t worry if you don’t fully grasp the inner +In other words, the Zen timer isn't really part of the knowledge we +seek; rather, it's one tool with which we'll acquire that knowledge. +Consequently, you shouldn't worry if you don't fully grasp the inner workings of the Zen timer. Instead, focus on learning how to *use* it, -and you’ll be on the right road. +and you'll be on the right road. #### Starting the Zen Timer {#Heading6} @@ -23,9 +23,9 @@ and you’ll be on the right road. **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 -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 +(I'd like to note that while Intel's documentation for the 8253 seems to +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 @@ -45,16 +45,16 @@ enable interrupts during that time. 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. +the 8253 and the PC's system clock (which keeps the current time) work. The 8253 actually contains three timers, as shown in Figure 3.1. All -three timers are driven by the system board’s 14.31818 MHz crystal, +three timers are driven by the system board's 14.31818 MHz crystal, divided by 12 to yield a 1.19318 MHz clock to the timers, so the timers count once every 838.1 ns. Each of the three timers counts down in a programmable way, generating a signal on its output pin when it counts down to 0. Each timer is capable of being halted at any time via a 0 -level on its gate input; when a timer’s gate input is 1, that timer -counts constantly. All in all, the 8253’s timers are inherently very +level on its gate input; when a timer's gate input is 1, that timer +counts constantly. All in all, the 8253's timers are inherently very flexible timing devices; unfortunately, much of that flexibility depends on how the timers are connected to external circuitry, and in the PC the timers are connected with specific purposes in mind. @@ -65,7 +65,7 @@ is the only timer with a programmable gate input in the PC; that is, timer 2 is the only timer that can be started and stopped under program control in the manner specified by Intel. On the other hand, the *output* of timer 2 is connected to nothing other than the speaker. In -particular, timer 2 cannot generate an interrupt to get the 8088’s +particular, timer 2 cannot generate an interrupt to get the 8088's attention. Timer 1 is dedicated to providing dynamic RAM refresh, and should not be @@ -114,16 +114,16 @@ Divide-by-N mode counts down by one from the initial count. When the count reaches zero, the timer turns over and starts counting down again without stopping, and a pulse is generated for a single clock period. While the pulse is not held for nearly as long as in square wave mode, -it doesn’t matter, since the 8259 interrupt controller is configured in +it doesn't matter, since the 8259 interrupt controller is configured in the PC to be edgeand hence cares only about the existence of a pulse from timer 0, not the duration of the pulse. As a result, timer 0 continues to generate timer interrupts in divide-by-N mode, and the system clock continues to maintain good time. Why not use timer 2 instead of timer 0 for precision timing? After all, -timer 2 has a programmable gate input and isn’t used for anything but -sound generation. The problem with timer 2 is that its output can’t -generate an interrupt; in fact, timer 2 can’t do anything but drive the +timer 2 has a programmable gate input and isn't used for anything but +sound generation. The problem with timer 2 is that its output can't +generate an interrupt; in fact, timer 2 can't do anything but drive the speaker. We need the interrupt generated by the output of timer 0 to tell us when the count has overflowed, and we will see shortly that the timer interrupt also makes it possible to time much longer periods than diff --git a/03-04.md b/03-04.md index 0fb32bb..a60d6e8 100644 --- a/03-04.md +++ b/03-04.md @@ -22,7 +22,7 @@ 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. -Now we’re ready to look at the ways in which the Zen timer can introduce +Now we're ready to look at the ways in which the Zen timer can introduce inaccuracy into the system clock. Since timer 0 is initially set to 0 by the Zen timer, and since the system clock ticks only when timer 0 counts off 54.925 ms and reaches 0 again, an average inaccuracy of one-half of @@ -42,10 +42,10 @@ Zen timer. The 8259 interrupt controller is capable of remembering at most one pending timer interrupt, so all timer interrupts after the first one during any given Zen timing interval are ignored. Consequently, if a timing interval exceeds 54.9 ms, the system clock -effectively stops 54.9 ms after the timing interval starts and doesn’t +effectively stops 54.9 ms after the timing interval starts and doesn't restart until the timing interval ends, losing time all the while. -The effects on the system time of the Zen timer aren’t a matter for +The effects on the system time of the Zen timer aren't a matter for great concern, as they are temporary, lasting only until the next warm or cold boot. System that have batteryclocks, (AT-style machines; that is, virtually all machines in common use) automatically reset the @@ -56,7 +56,7 @@ by at most a total of a few seconds, unless code that takes much longer than 54 ms to run is timed (in which case the Zen timer will notify you that the code is too long to time). -Nonetheless, it’s a good idea to reboot your computer at the end of each +Nonetheless, it's a good idea to reboot your computer at the end of each session with the Zen timer in order to make sure that the system clock is correct. @@ -83,12 +83,12 @@ including the state of the interrupt flag that was in effect when **ZTimerOn** was called to start timing, and returns. 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 +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 -running. (That’s a good thing, too; we’ve no documented way to stop -timer 0 if we wanted to, since its gate input isn’t connected. Later in -this chapter, though, we’ll see that timer 0 can be stopped after all.) +running. (That's a good thing, too; we've no documented way to stop +timer 0 if we wanted to, since its gate input isn't connected. Later in +this chapter, though, we'll see that timer 0 can be stopped after all.) We simply tell the 8253 to latch the current count, and the 8253 does so without breaking stride. @@ -113,7 +113,7 @@ ZTimerReport** can be called at any time right up until the next call to 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 -the program’s normal display. There are many ways to deal with this. One +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 program under a debugger that supports screen flipping (such as Turbo @@ -144,7 +144,7 @@ be stored in a buffer within the driver, to be dumped at a later time. David Miller for passing the idea on to me.) You may well want to devise still other approaches better suited to your -needs than those I’ve presented. Go to it! I’ve just thrown out a few +needs than those I've presented. Go to it! I've just thrown out a few possibilities to get you started. ------------------------ --------------------------------- -------------------- diff --git a/03-05.md b/03-05.md index 6290107..cfb1622 100644 --- a/03-05.md +++ b/03-05.md @@ -19,10 +19,10 @@ 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 +**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 +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. Please be aware that the inaccuracy that the Zen timer can introduce @@ -34,16 +34,16 @@ dynamic RAM refresh, and internal timing variations in the 8253 make it perhaps more accurate to describe the Zen timer as measuring code performance with an accuracy of better than 10µs. In fact, the Zen timer is actually most accurate in assessing code performance when timing -intervals longer than about 100 µs. At any rate, we’re most interested +intervals longer than about 100 µs. At any rate, we're most interested in using the Zen timer to assess the relative performance of various code sequences—that is, using it to compare and tweak code—and the timer is more than accurate enough for that purpose. -The Zen timer works on all PC-compatible computers I’ve tested it on, +The Zen timer works on all PC-compatible computers I've tested it on, including XTs, ATs, PS/2 computers, and 386, 486, and Pentium-based -machines. Of course, I haven’t been able to test it on *all* -PC-compatibles, but I don’t expect any problems; computers on which the -Zen timer doesn’t run can’t truly be called “PC-compatible.” +machines. Of course, I haven't been able to test it on *all* +PC-compatibles, but I don't expect any problems; computers on which the +Zen timer doesn't run can't truly be called "PC-compatible." On the other hand, there is certainly no guarantee that code performance as measured by the Zen timer will be the same on compatible computers as @@ -53,7 +53,7 @@ the opposite is true. For example, every PS/2 computer, even the relatively slow Model 30, executes code much faster than does a PC or XT. As another example, I set out to do the timings for my earlier book *Zen of Assembly Language* on an XTcomputer, only to find that the -computer wasn’t quite IBM-compatible regarding code performance. The +computer wasn't quite IBM-compatible regarding code performance. The differences were minor, mind you, but my experience illustrates the risk of assuming that a specific make of computer will perform in a certain way without actually checking. @@ -66,7 +66,7 @@ computers. ### A Sample Use of the Zen Timer {#Heading11} 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 +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. Consequently, the code being measured should be in the file TESTCODE, @@ -84,11 +84,11 @@ and should contain calls to **ZTimerOn** and **ZTimerOff** . ; ; By Michael Abrash ; - mystack segment para stack ‘STACK’ + mystack segment para stack ‘STACK' db 512 dup(?) mystack ends ; - Code segment para public ‘CODE’ + Code segment para public ‘CODE' assume cs:Code, ds:Code extrnZTimerOn:near, ZTimerOff:near, ZTimerReport:near Start proc near @@ -147,7 +147,7 @@ after the code in Listing 3.3 has been run. ; call ZTimerOff -It’s worth noting that Listing 3.3 begins by jumping around the memory +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 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, @@ -155,8 +155,8 @@ each listing becomes self-contained and can be plugged directly into Listing 3.2 as TESTCODE. Listing 3.2 sets DS equal to CS before doing 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 +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 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 diff --git a/03-06.md b/03-06.md index 20cd8e0..4464719 100644 --- a/03-06.md +++ b/03-06.md @@ -11,8 +11,8 @@ PZTEST.EXE. PZTIME.BAT (Listing 3.4) assumes that the file PZTIMER.ASM contains Listing 3.1, and the file PZTEST.ASM contains Listing 3.2. The command-line parameter to PZTIME.BAT is the name of the file to be copied to TESTCODE and included into PZTEST.ASM. (Note that Turbo -Assembler can be substituted for MASM by replacing “masm” with “tasm” -and “link” with “tlink” in Listing 3.4. The same is true of Listing +Assembler can be substituted for MASM by replacing "masm" with "tasm" +and "link" with "tlink" in Listing 3.4. The same is true of Listing 3.7.) **LISTING 3.4 PZTIME.BAT** @@ -26,7 +26,7 @@ and “link” with “tlink” in Listing 3.4. The same is true of Listing rem * Zen timer program PZTEST.EXE to time the code named as the * rem * command-line parameter. Listing 3.1 must be named * rem * PZTIMER.ASM, and Listing 3.2 must be named PZTEST.ASM. To * - rem * time the code in LST3-3, you’d type the DOS command: * + rem * time the code in LST3-3, you'd type the DOS command: * rem * * rem * pztime lst3-3 * rem * * @@ -57,7 +57,7 @@ and “link” with “tlink” in Listing 3.4. The same is true of Listing :ckexist if exist %1 goto docopy echo *************************************************************** - echo * The specified file, “%1,” doesn’t exist. * + echo * The specified file, "%1," doesn't exist. * echo *************************************************************** goto end rem @@ -89,16 +89,16 @@ of the code in Listing 3.3. When the above command is executed on an original 4.77 MHz IBM PC, the time reported by the Zen timer is 3619 µs, or about 3.62 µs per load of -AL from memory. (While the exact number is 3.619 µs per load of AL, I’m +AL from memory. (While the exact number is 3.619 µs per load of AL, I'm going to round off that last digit from now on. No matter how many -repetitions of a given instruction are timed, there’s just too much +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**, -which is actually a good bit longer than Intel’s specified 10-cycle +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 +documentation, or Intel's processor reference manuals, for official execution times.) Fear not, the Zen timer is right—**MOV AL,[MEMVAR]** really does take 17 cycles as used in Listing 3.3. Exactly why that is so is just what this book is all about. @@ -110,7 +110,7 @@ listing you wish to run into the file *filename* and enter the command: pztime -In fact, that’s exactly how I timed each of the listings in this book. +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, @@ -120,12 +120,12 @@ PZTIMER to your program. ### The Long-Period Zen Timer {#Heading12} With a few exceptions, the Zen timer presented above will serve us well -for the remainder of this book since we’ll be focusing on relatively +for the remainder of this book since we'll be focusing on relatively short code sequences that generally take much less than 54 ms to execute. Occasionally, however, we will need to time longer intervals. -What’s more, it is very likely that you will want to time code sequences +What's more, it is very likely that you will want to time code sequences longer than 54 ms at some point in your programming career. Accordingly, -I’ve also developed a Zen timer for periods longer than 54 ms. The +I've also developed a Zen timer for periods longer than 54 ms. The long-period Zen timer (so named by contrast with the precision Zen timer just presented) shown in Listing 3.5 can measure periods up to one hour in length. @@ -139,7 +139,7 @@ long periods. Practically speaking, however, there is no need for a timer that can measure more than a few minutes, since the DOS time of day and date functions (or, indeed, the DATE and TIME commands in a batch file) serve perfectly well for longer intervals. Since very long -timing intervals aren’t needed, the long-period Zen timer uses a +timing intervals aren't needed, the long-period Zen timer uses a simplified means of calculating elapsed time that is limited to measuring intervals of an hour or less. If a period longer than an hour is timed, the long-period Zen timer prints a message to the effect that @@ -150,7 +150,7 @@ of timing code that starts before midnight and ends after midnight; if that eventuality occurs, the long-period Zen timer reports that it was unable to time the code because midnight was crossed. If this happens to you, just time the code again, secure in the knowledge that at least you -won’t run into the problem again for 23-odd hours. +won't run into the problem again for 23-odd hours. You should not use the long-period Zen timer to time code that requires interrupts to be disabled for more than 54 ms at a stretch during the diff --git a/03-07.md b/03-07.md index 450cd1f..576b336 100644 --- a/03-07.md +++ b/03-07.md @@ -11,7 +11,7 @@ that occur during the timing interval, most notably keyboard and mouse interrupts, will increase the measured time. The long-period Zen timer has some of the same effects on the system -time as does the precision Zen timer, so it’s a good idea to reboot the +time as does the precision Zen timer, so it's a good idea to reboot the system after a session with the long-period Zen timer. The long-period Zen timer does not, however, have the same potential for introducing major inaccuracy into the system clock time during a single timing run @@ -20,7 +20,7 @@ to update normally. #### Stopping the Clock {#Heading13} -There’s a potential problem with the long-period Zen timer. The problem +There's a potential problem with the long-period Zen timer. The problem is this: In order to measure times longer than 54 ms, we must maintain not one but two timing components, the timer 0 count and the BIOS time-of-day count. The time-of-day count measures the passage of 54.9 ms @@ -36,9 +36,9 @@ direction.) The only way to avoid this problem is to stop timer 0, read both the timer and time-of-day counts while the timer is stopped, and then -restart the timer. Alas, the gate input to timer 0 isn’t -program-controllable in the PC, so there’s no documented way to stop the -timer. (The latched read feature we used in Listing 3.1 doesn’t stop the +restart the timer. Alas, the gate input to timer 0 isn't +program-controllable in the PC, so there's no documented way to stop the +timer. (The latched read feature we used in Listing 3.1 doesn't stop the timer; it latches a count, but the timer keeps running.) What should we do? @@ -49,23 +49,23 @@ until the count is loaded. Surprisingly, the timer count remains readable and correct while the timer is waiting for the initial load. In my experience, this approach works beautifully with fully -8253-compatible chips. However, there’s no guarantee that it will always -work, since it programs the 8253 in an undocumented way. What’s more, +8253-compatible chips. However, there's no guarantee that it will always +work, since it programs the 8253 in an undocumented way. What's more, IBM chose not to implement compatibility with this particular 8253 feature in the custom chips used in PS/2 computers. On PS/2 computers, we have no choice but to latch the timer 0 count and then stop the BIOS -count (by disabling interrupts) as quickly as possible. We’ll just have +count (by disabling interrupts) as quickly as possible. We'll just have 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. +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 +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 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 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 +approach avoids synchronization problems, but doesn't work on all computers. **LISTING 3.5 LZTIMER.ASM** @@ -103,13 +103,13 @@ computers. ; more than adequate. ; ; Note: The PS/2 version is assembled by setting the symbol PS2 to 1. - ; PS2 must be set to 1 on PS/2 computers because the PS/2’s + ; PS2 must be set to 1 on PS/2 computers because the PS/2's ; timers are not compatible with an undocumented timer-stopping ; feature of the 8253; the alternative timing approach that ; must be used on PS/2 computers leaves a short window ; during which the timer 0 count and the BIOS timer count may ; not be synchronized. You should also set the PS2 symbol to - ; 1 if you’re getting erratic or obviously incorrect results. + ; 1 if you're getting erratic or obviously incorrect results. ; ; Note: When PS2 is 0, the code relies on an undocumented 8253 ; feature to get more reliable readings. It is possible that @@ -140,7 +140,7 @@ computers. ; ; Note: These routines can introduce inaccuracies of up to a few ; tenths of a second into the system clock count for each - ; code section timed. Consequently, it’s a good idea to + ; code section timed. Consequently, it's a good idea to ; reboot at the conclusion of timing sessions. (The ; battery-backed clock, if any, is not affected by the Zen ; timer.) @@ -148,7 +148,7 @@ computers. ; All registers and all flags are preserved by all routines. ; - Code segment word public ‘CODE’ + Code segment word public ‘CODE' assume cs: Code, ds:nothing public ZTimerOn, ZTimerOff, ZTimerReport @@ -157,7 +157,7 @@ computers. ; system; when PS2 is 0, the readings are more reliable if the ; computer supports the undocumented timer-stopping feature, ; but may be badly off if that feature is not supported. In - ; fact, timer-stopping may interfere with your computer’s + ; fact, timer-stopping may interfere with your computer's ; overall operation by putting the 8253 into an undefined or ; incorrect state. Use with caution!!! ; @@ -231,10 +231,10 @@ computers. OutputStr labelbyte db 0dh, 0ah, ‘Timed count: ‘ TimedCountStr db10 dup (?) - db’ microseconds’, 0dh, 0ah - db ‘$’ + db' microseconds', 0dh, 0ah + db ‘$' ; - ; Temporary storage for timed count as it’s divided down by powers + ; Temporary storage for timed count as it's divided down by powers ; of ten when converting from doubleword binary to ASCII. ; CurrentCountLow dw ? @@ -262,25 +262,25 @@ computers. ; TurnOverStrlabelbyte db 0dh, 0ah - db ‘****************************************************’ + db ‘****************************************************' db 0dh, 0ah - db’* Either midnight passed or an hour or more passed *’ + db'* Either midnight passed or an hour or more passed *' db 0dh, 0ah - db’* while timing was in progress. If the former was *’ + db'* while timing was in progress. If the former was *' db 0dh, 0ah - db’* the case, please rerun the test; if the latter *’ + db'* the case, please rerun the test; if the latter *' db 0dh, 0ah - db’* was the case, the test code takes too long to *’ + db'* was the case, the test code takes too long to *' db 0dh, 0ah - db’* run to be timed by the long-period Zen timer. *’ + db'* run to be timed by the long-period Zen timer. *' db 0dh, 0ah - db ‘* Suggestions: use the DOS TIME command, the DOS *’ + db ‘* Suggestions: use the DOS TIME command, the DOS *' db 0dh, 0ah - db ‘* time function, or a watch. *’ + db ‘* time function, or a watch. *' db 0dh, 0ah - db ‘****************************************************’ + db ‘****************************************************' db 0dh, 0ah - db’$’ + db'$' ;******************************************************************** ;* Routine called to start timing. * @@ -302,7 +302,7 @@ computers. mov al,00110100b ;mode 2 out MODE_8253,al ; - ; Set the timer count to 0, so we know we won’t get another + ; Set the timer count to 0, so we know we won't get another ; timer interrupt right away. ; Note: this introduces an inaccuracy of up to 54 ms in the system ; clock count each time it is executed. @@ -329,7 +329,7 @@ computers. ; ; Store the timing start BIOS count. ; (Since the timer count was just set to 0, the BIOS count will - ; stay the same for the next 54 ms, so we don’t need to disable + ; stay the same for the next 54 ms, so we don't need to disable ; interrupts in order to avoid getting a half-changed count.) ; push ds @@ -394,12 +394,12 @@ computers. ; ; This is where a one-instruction-long window exists on the PS/2. ; The timer count and the BIOS count can lose synchronization; - ; since the timer keeps counting after it’s latched, it can turn - ; over right after it’s latched and cause the BIOS count to turn + ; since the timer keeps counting after it's latched, it can turn + ; over right after it's latched and cause the BIOS count to turn ; over before interrupts are disabled, leaving us with the timer ; count from before the timer turned over coupled with the BIOS ; count from after the timer turned over. The result is a count - ; that’s 54 ms too long. + ; that's 54 ms too long. ; else @@ -420,7 +420,7 @@ computers. cli ;stop the BIOS count ; ; Read the BIOS count. (Since interrupts are disabled, the BIOS - ; count won’t change.) + ; count won't change.) ; push ds sub ax,ax @@ -526,9 +526,9 @@ computers. ; ; Called by ZTimerOff to stop the timer and add the result to - ; ReferenceCount for overhead measurements. Doesn’t need to look + ; ReferenceCount for overhead measurements. Doesn't need to look ; at the BIOS count because timing a zero-length code fragment - ; isn’t going to take anywhere near 54 ms. + ; isn't going to take anywhere near 54 ms. ; ReferenceZTimerOff procnear @@ -596,8 +596,8 @@ computers. ; mov ax,[StartBIOSCountHigh] cmp ax,[EndBIOSCountHigh] - jz CalcBIOSTime ;hour count didn’t change, - ; so everything’s fine + jz CalcBIOSTime ;hour count didn't change, + ; so everything's fine inc ax cmp ax,[EndBIOSCountHigh] jnz TestTooLong ;midnight or two hour @@ -606,14 +606,14 @@ computers. mov ax,[EndBIOSCountLow] cmp ax,[StartBIOSCountLow] jb CalcBIOSTime ;a single hour boundary - ; passed--that’s OK, so long as - ; the total time wasn’t more + ; passed--that's OK, so long as + ; the total time wasn't more ; than an hour ; ; Over an hour elapsed or midnight passed during timing, which ; renders the results invalid. Notify the user. This misses the - ; case where a multiple of 24 hours has passed, but we’ll rely + ; case where a multiple of 24 hours has passed, but we'll rely ; on the perspicacity of the user to detect that case. ; TestTooLong: @@ -665,7 +665,7 @@ computers. mov di,offset PowersOfTenEnd - offset PowersOfTen - 4 mov si,offset TimedCountStr CTSNextDigit: - mov bl,’0’ + mov bl,'0' CTSLoop: mov ax,[CurrentCountLow] mov dx,[CurrentCountHigh] diff --git a/03-08.md b/03-08.md index e9d4f85..e693863 100644 --- a/03-08.md +++ b/03-08.md @@ -4,8 +4,8 @@ Moreover, because it uses an undocumented feature, the timer-stop 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 +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. @@ -17,7 +17,7 @@ each code-timing session.) You should *immediately* reboot and set the 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 +that the results match. If they're consistently different, you should set **PS2** to 1. While the the non-PS/2 version is more dangerous than the PS/2 version, @@ -29,15 +29,15 @@ 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 -time-of-day count. In fact, it’s a good idea to time code more than once -no matter which version of the long-period Zen timer you’re using, since +time-of-day count. In fact, it's a good idea to time code more than once +no matter which version of the long-period Zen timer you're using, since interrupts, which must be enabled in order for the long-period timer to work properly, may occur at any time and can alter execution time substantially. Finally, please note that the *precision* Zen timer works perfectly well on both PS/2 and non-PS/2 computers. The PS/2 and 8253 considerations -we’ve just discussed apply *only* to the longZen timer. +we've just discussed apply *only* to the longZen timer. ### Example Use of the Long-Period Zen Timer {#Heading14} @@ -49,7 +49,7 @@ the code being timed takes too long for the precision timer to handle, 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 +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 allowing any pending keyboard interrupts to be processed. Since interrupts must be left on in order to time periods longer than 54 ms, @@ -72,11 +72,11 @@ timing. ; ; By Michael Abrash ; - mystack segment para stack ‘STACK’ + mystack segment para stack ‘STACK' db 512 dup(?) mystack ends ; - Code segment para public ‘CODE’ + Code segment para public ‘CODE' assume cs:Code, ds:Code extrn ZTimerOn:near, ZTimerOff:near, ZTimerReport:near Startproc near @@ -102,7 +102,7 @@ timing. add dh,60 ;yes, a minute must have turned over, ; so add one minute CheckDelayTime: - sub dh,bh ;get time that’s passed + sub dh,bh ;get time that's passed cmp dh,7 ;has it been more than 6 seconds yet? jb DelayLoop ;not yet ; diff --git a/03-09.md b/03-09.md index 0df07b7..41e5d8e 100644 --- a/03-09.md +++ b/03-09.md @@ -13,7 +13,7 @@ rem * long-period Zen timer program LZTEST.EXE to time the code * rem * named as the command-line parameter. Listing 3.5 must be * rem * named LZTIMER.ASM, and Listing 3.6 must be named * - rem * LZTEST.ASM. To time the code in LST3-8, you’d type the * + rem * LZTEST.ASM. To time the code in LST3-8, you'd type the * rem * DOS command: * rem * * rem * lztime lst3-8 * @@ -45,7 +45,7 @@ :ckexist if exist %1 goto docopy echo *************************************************************** - echo * The specified file, “%1,” doesn’t exist. * + echo * The specified file, "%1," doesn't exist. * echo *************************************************************** goto end rem @@ -127,7 +127,7 @@ 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 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 +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 @@ -137,9 +137,9 @@ involves the following steps: **C** hange **ZTimerOn** 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 +segment. (In C++, use the "C" specifier, as in - extern “C” ZTimerOn(void); + extern "C" ZTimerOn(void); ------------------------ --------------------------------- -------------------- [Previous](03-08.html) [Table of Contents](index.html) [Next](03-10.html) diff --git a/03-10.md b/03-10.md index 081bda6..8c66c56 100644 --- a/03-10.md +++ b/03-10.md @@ -3,9 +3,9 @@ ------------------------ --------------------------------- -------------------- when declaring the timer routines **extern**, so that name-mangling -doesn’t occur, and the linker can find the routines’ C-style names.) +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 +That's all it takes; after doing this, you'll be able to use the Zen timer from C, as, for example, in: ZTimerOn(): @@ -14,14 +14,14 @@ timer from C, as, for example, in: ZTimerOff(); ZTimerReport(); -(I’m talking about the precision timer here. The long-period +(I'm talking about the precision timer here. The long-period timer—Listing 3.5—requires the same modifications, but to different lines.) ![](images/03-02.jpg)\ **Figure 3.2**  *Changes for use with small code model C.* -Altering the Zen timer for use in C’s large code model is a tad more +Altering the Zen timer for use in C's large code model is a tad more complex, because in addition to the above changes, all functions, including the internal reference timing routines that are used to calculate overhead so it can be subtracted out, must be converted to @@ -49,7 +49,7 @@ with (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 +far call. However, it's not so great for the Zen ![](images/03-03.jpg)\ **Figure 3.3**  *Changes for use with large code model C.* @@ -61,24 +61,24 @@ 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 MASM up through version 5.10A. In my mind, the whole business of optimizing assemblers is a mixed -blessing. In general, it’s nice to have the assembler shortening jumps +blessing. In general, it's nice to have the assembler shortening jumps and selecting sign-extended forms of instructions for you. On the other hand, the benefits of tricks like substituting push/near call pairs for far calls are relatively small, and those tricks can get in the way when complete control is needed. Sure, complete control is needed very rarely, but when it is, optimizing assemblers can cause subtle problems; -I discovered TASM’s alteration of far calls only because I happened to +I discovered TASM's alteration of far calls only because I happened to view the code in the debugger, and you might want to do the same if -you’re using a recent version of MASM. +you're using a recent version of MASM. -I’ve tested the changes shown in Figures 3.2 and 3.3 with TASM and +I've tested the changes shown in Figures 3.2 and 3.3 with TASM and Borland C++ 4.0, and also with the latest MASM and Microsoft C/C++ compiler. @@ -86,21 +86,21 @@ compiler. For those of you who wish to pursue the mechanics of code measurement further, one good article about measuring code performance with the 8253 -timer is “Programming Insight: High-Performance Software Analysis on the -IBM PC,” by Byron Sheppard, which appeared in the January, 1987 issue of +timer is "Programming Insight: High-Performance Software Analysis on the +IBM PC," by Byron Sheppard, which appeared in the January, 1987 issue of *Byte*. For complete if somewhat cryptic information on the 8253 timer -itself, I refer you to Intel’s *Microsystem Components Handbook*, which +itself, I refer you to Intel's *Microsystem Components Handbook*, which is also a useful reference for a number of other PC components, including the 8259 Programmable Interrupt Controller and the 8237 DMA Controller. For details about the way the 8253 is used in the PC, as -well as a great deal of additional information about the PC’s hardware -and BIOS resources, I suggest you consult IBM’s series of technical +well as a great deal of additional information about the PC's hardware +and BIOS resources, I suggest you consult IBM's series of technical reference manuals for the PC, XT, AT, Model 30, and microchannel computers, such as the Models 50, 60, and 80. -For our purposes, however, it’s not critical that you understand exactly +For our purposes, however, it's not critical that you understand exactly how the Zen timer works. All you really need to know is what the Zen -timer can do and how to use it, and we’ve accomplished that in this +timer can do and how to use it, and we've accomplished that in this chapter. #### Armed with the Zen Timer, Onward and Upward {#Heading18} @@ -123,7 +123,7 @@ of the inaccuracy introduced by the Zen timer becomes less over longer periods. Imperfections notwithstanding, the Zen timer is a good tool for -exploring C code and x86 family assembly language, and it’s a tool we’ll +exploring C code and x86 family assembly language, and it's a tool we'll use frequently for the remainder of this book. ------------------------ --------------------------------- -------------------- diff --git a/04-01.md b/04-01.md index c2ca18a..5aadf6f 100644 --- a/04-01.md +++ b/04-01.md @@ -21,19 +21,19 @@ of PC programmers to pedal-to-the-metal optimization. This chapter focuses almost entirely on the first popular x86-family processor, the 8088. Some of the specific features and results that I cite in this chapter are no longer applicable to modern x86-family -processors such as the 486 and Pentium, as I’ll point out later on when +processors such as the 486 and Pentium, as I'll point out later on when we discuss those processors. Nonetheless, the overall theme of this chapter—that understanding dimly-seen and poorly-documented code gremlins called cycle-eaters that lurk in your system is essential to performance programming—is every bit as valid today. Also, later chapters often refer back to the basic cycle-eaters described in this chapter, so this chapter is the foundation for the discussions of -x86-family optimization to come. What’s more, the Zen timer remains an +x86-family optimization to come. What's more, the Zen timer remains an excellent tool with which to flush out and examine cycle-eaters, as -we’ll see in later chapters, and this chapter is as good an illustration -of how to use the Zen timer as you’re likely to find. +we'll see in later chapters, and this chapter is as good an illustration +of how to use the Zen timer as you're likely to find. -So, don’t take either the absolute or the relative execution times +So, don't take either the absolute or the relative execution times presented in this chapter as gospel for newer processors, and read on to later chapters to see how the cycle-eaters and optimization rules have changed over time, but do take the time to at least skim through this @@ -46,8 +46,8 @@ Programming has many levels, ranging from the familiar (high-level languages, DOS calls, and the like) down to the esoteric things that lie on the shadowy edge of hardware-land. I call these *cycle-eaters* because, like the monsters in a bad 50s horror movie, they lurk in those -shadows, taking their share of your program’s performance without regard -to the forces of goodness or the U.S. Army. In this chapter, we’re going +shadows, taking their share of your program's performance without regard +to the forces of goodness or the U.S. Army. In this chapter, we're going to jump right in at the lowest level by examining the cycle-eaters that live beneath the programming interface; that is, beneath your application, DOS, and BIOS—in fact, beneath the instruction set itself. @@ -55,15 +55,15 @@ application, DOS, and BIOS—in fact, beneath the instruction set itself. Why start at the lowest level? Simply because cycle-eaters affect the performance of all assembler code, and yet are almost unknown to most programmers. A full understanding of code optimization requires an -understanding of cycle-eaters and their implications. That’s no simple +understanding of cycle-eaters and their implications. That's no simple task, and in fact it is in precisely that area that most books and articles about assembly programming fall short. Nearly all literature on assembly programming discusses only the programming interface: the instruction set, the registers, the flags, and the BIOS and DOS calls. Those topics cover the functionality of -assembly programs most thoroughly—but it’s performance above all else -that we’re after. No one ever tells you about the raw stuff of +assembly programs most thoroughly—but it's performance above all else +that we're after. No one ever tells you about the raw stuff of performance, which lies *beneath* the programming interface, in the dimly-seen realm—populated by instruction prefetching, dynamic RAM refresh, and wait states—where software meets hardware. This area is the @@ -78,56 +78,56 @@ Which brings us to cycle-eaters. Cycle-eaters are gremlins that live on the bus or in peripherals (and sometimes within the CPU itself), slowing the performance of PC code so -that it doesn’t execute at full speed. Most cycle-eaters (and all of -those haunting the older Intel processors) live outside the CPU’s +that it doesn't execute at full speed. Most cycle-eaters (and all of +those haunting the older Intel processors) live outside the CPU's Execution Unit, where they can *only* affect the CPU when the CPU performs a bus access (a memory or I/O read or write). Once your code and data are already inside the CPU, those cycle-eaters can no longer be a problem. Only on the 486 and Pentium CPUs will you find cycle-eaters -inside the chip, as we’ll see in later chapters. +inside the chip, as we'll see in later chapters. The nature and severity of the cycle-eaters vary enormously from processor to processor, and (especially) from memory architecture to memory architecture. In order to understand them all, we need first to understand the simplest among them, those that haunted the original -8088-based IBM PC. Later on in this book, I’ll be better able to explain +8088-based IBM PC. Later on in this book, I'll be better able to explain the newer generation of cycle-eaters in terms of those ancestral cycle-eaters—but we have to get the groundwork down first. -#### The 8088’s Ancestral Cycle-Eaters {#Heading5} +#### The 8088's Ancestral Cycle-Eaters {#Heading5} Internally, the 8088 is a 16-bit processor, capable of running at full speed at all times—unless external data is required. External data must -traverse the 8088’s external data bus and the PC’s data bus one byte at +traverse the 8088's external data bus and the PC's data bus one byte at a time to and from peripherals, with cycle-eaters lurking along every -step of the way. What’s more, external data includes not only memory +step of the way. What's more, external data includes not only memory operands *but also instruction bytes,* so even instructions with no -memory operands can suffer from cycle-eaters. Since some of the 8088’s -fastest instructions are register-only instructions, that’s important +memory operands can suffer from cycle-eaters. Since some of the 8088's +fastest instructions are register-only instructions, that's important indeed. The major cycle-eaters are: -- The 8088’s 8-bit external data bus. +- The 8088's 8-bit external data bus. - The prefetch queue. - Dynamic RAM refresh. - Wait states, notably display memory wait states and, in the AT and 80386 computers, system memory wait states. The locations of these cycle-eaters in the primordial 8088-based PC are -shown in Figure 4.1. We’ll cover each of the cycle-eaters in turn in -this chapter. The material won’t be easy since cycle-eaters are among +shown in Figure 4.1. We'll cover each of the cycle-eaters in turn in +this chapter. The material won't be easy since cycle-eaters are among the most subtle aspects of assembly programming. By the same token, however, this will be one of the most important and rewarding chapters -in this book. Don’t worry if you don’t catch everything in this chapter, +in this book. Don't worry if you don't catch everything in this chapter, but do read it all even if the going gets a bit tough. Cycle-eaters play a key role in later chapters, so some familiarity with them is highly desirable. ### The 8-Bit Bus Cycle-Eater {#Heading6} -*Look! Down on the motherboard! It’s a 16-bit processor! It’s an 8-bit -processor! It’s...* +*Look! Down on the motherboard! It's a 16-bit processor! It's an 8-bit +processor! It's...* ...an 8088! @@ -138,7 +138,7 @@ processor. The 8088 is internally a full 16-bit processor, equivalent to an 8086. (In fact, the 8086 is identical to the 8088, except that it has a full -16-bit bus. The 8088 is basically the poor man’s 8086, because it allows +16-bit bus. The 8088 is basically the poor man's 8086, because it allows a cheaper—albeit slower—system to be built, thanks to the half-sized bus.) In terms of the instruction set, the 8088 is clearly a 16-bit processor, capable of performing any given 16-bit operation—addition, diff --git a/04-02.md b/04-02.md index 327ba60..2391d24 100644 --- a/04-02.md +++ b/04-02.md @@ -9,12 +9,12 @@ **Figure 4.2**  *Internal data bus widths of the 8088.* As shown in Figure 4.1, the 8-bit bus cycle-eater lies squarely on the -8088’s external data bus. Technically, it might be more accurate to +8088's external data bus. Technically, it might be more accurate to place this cycle-eater in the Bus Interface Unit, which breaks 16-bit memory accesses into paired 8-bit accesses, but it is really the limited width of the external data bus that constricts data flow into and out of -the 8088. True, the original PC’s bus is also only 8 bits wide, but -that’s just to match the 8088’s 8-bit bus; even if the PC’s bus were 16 +the 8088. True, the original PC's bus is also only 8 bits wide, but +that's just to match the 8088's 8-bit bus; even if the PC's bus were 16 bits wide, data could still pass into and out of the 8088 chip itself only 1 byte at a time. @@ -43,7 +43,7 @@ doubleword from memory in two halves. One obvious effect of the 8-bit bus cycle-eater is that word-sized accesses to memory operands on the 8088 take 4 cycles longer than -byte-sized accesses. That’s why the official instruction timings +byte-sized accesses. That's why the official instruction timings indicate that for code running on an 8088 an additional 4 cycles are required for every word-sized access to a memory operand. For instance, @@ -54,11 +54,11 @@ takes 4 cycles longer to read the word at address **MemVar** than mov al,byte ptr [MemVar] 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 +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.) -What’s more, in some cases one instruction can perform multiple +What's more, in some cases one instruction can perform multiple word-sized accesses, incurring that 4-cycle penalty on each access. For example, adding a value to a word-sized memory variable requires two word-sized accesses—one to read the destination operand from memory @@ -79,7 +79,7 @@ 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 than on an 8086, simply because of the 8-bit bus. *One-tenth of a -second!* That’s a phenomenally long time in computer terms; in one-tenth +second!* That's a phenomenally long time in computer terms; in one-tenth of a second, the 8088 can perform more than 50,000 additions and subtractions. @@ -98,7 +98,7 @@ The obvious implication of the 8-bit bus cycle-eater is that byte-sized memory variables should be used whenever possible. After all, the 8088 performs *byte-sized* memory accesses just as quickly as the 8086. For 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 +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 @@ -138,18 +138,18 @@ in all. jnz LoopTop call ZTimerOff -I’d like to make a brief aside concerning code optimization in the -listings in this book. Throughout this book I’ve modeled the sample code +I'd like to make a brief aside concerning code optimization in the +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 +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 instruction sequence). If you come across code in this book that seems -less than optimal, it’s simply due to my desire to provide code that’s +less than optimal, it's simply due to my desire to provide code that's relevant to real programming problems. On the other hand, optimal code is an elusive thing indeed; by no means should you assume that the code in this book is ideal! Examine it, question it, and improve upon it, for diff --git a/04-03.md b/04-03.md index 25045c2..9d40088 100644 --- a/04-03.md +++ b/04-03.md @@ -2,7 +2,7 @@ [Previous](04-02.html) [Table of Contents](index.html) [Next](04-04.html) ------------------------ --------------------------------- -------------------- -Back to the 8-bit bus cycle-eater. As I’ve said, in 8088 work you should +Back to the 8-bit bus cycle-eater. As I've said, in 8088 work you should strive to use byte-sized memory variables whenever possible. That does *not* mean that you should use 2 byte-sized memory accesses to manipulate a word-sized memory variable in preference to 1 word-sized @@ -18,16 +18,16 @@ versus: Recall that every access to a memory byte takes at least 4 cycles; that limitation is built right into the 8088. The 8088 is also built so that the second byte-sized memory access to a 16-bit memory variable takes -just those 4 cycles and no more. There’s no way you can manipulate the +just those 4 cycles and no more. There's no way you can manipulate the second byte of a word-sized memory variable faster with a second separate byte-sized instruction in less than 4 cycles. As a matter of -fact, you’re bound to access that second byte much more slowly with a +fact, you're bound to access that second byte much more slowly with a separate instruction, thanks to the overhead of instruction fetching and execution, address calculation, and the like. For example, consider Listing 4.3, which performs 1,000 word-sized reads from memory. This code runs in 3.77 µs per word read on a 4.77 MHz 8088. -That’s 45 percent faster than the 5.49 µs per word read of Listing 4.4, +That's 45 percent faster than the 5.49 µs per word read of Listing 4.4, which reads the same 1,000 words as Listing 4.3 but does so with 2,000 byte-sized reads. Both listings perform exactly the same number of memory accesses—2,000 accesses, each byte-sized, as all 8088 memory @@ -67,14 +67,14 @@ efficient at that task than your code can possibly be. Word-sized variables should be stored in registers to the greatest feasible extent, since registers are inside the 8088, where 16-bit operations are just as fast as 8-bit operations because the 8-bit -cycle-eater can’t get at them. In fact, it’s a good idea to keep as many +cycle-eater can't get at them. In fact, it's a good idea to keep as many variables of all sorts in registers as you can. Instructions with register-only operands execute very rapidly, partially because they avoid both the time-consuming memory accesses and the lengthy address calculations associated with memory operands. There is yet another reason why register operands are preferable to -memory operands, and it’s an unexpected effect of the 8-bit bus +memory operands, and it's an unexpected effect of the 8-bit bus cycle-eater. Instructions with only register operands tend to be shorter (in terms of bytes) than instructions with memory operands, and when it comes to performance, shorter is usually better. In order to explain why @@ -82,32 +82,32 @@ that is true and how it relates to the 8-bit bus cycle-eater, I must diverge for a moment. For the last few pages, you may well have been thinking that the 8-bit -bus cycle-eater, while a nuisance, doesn’t seem particularly subtle or +bus cycle-eater, while a nuisance, doesn't seem particularly subtle or difficult to quantify. After all, any instruction reference tells us exactly how many cycles each instruction loses to the 8-bit bus -cycle-eater, doesn’t it? +cycle-eater, doesn't it? -Yes and no. It’s true that in general we know approximately how much +Yes and no. It's true that in general we know approximately how much longer a given instruction will take to execute with a word-sized memory operand than with a byte-sized operand, although the dynamic RAM refresh -and wait state cycle-eaters (which I’ll cover a little later) can raise +and wait state cycle-eaters (which I'll cover a little later) can raise the cost of the 8-bit bus cycle-eater considerably. However, *all* word-sized memory accesses lose 4 cycles to the 8-bit bus cycle-eater, -and there’s one sort of word-sized memory access we haven’t discussed +and there's one sort of word-sized memory access we haven't discussed yet: instruction fetching. The ugliest manifestation of the 8-bit bus cycle-eater is in fact the prefetch queue cycle-eater. ### The Prefetch Queue Cycle-Eater {#Heading9} -In an 8088 context, here’s the prefetch queue cycle-eater in a nutshell: -The 8088’s 8-bit external data bus keeps the Bus Interface Unit from +In an 8088 context, here's the prefetch queue cycle-eater in a nutshell: +The 8088's 8-bit external data bus keeps the Bus Interface Unit from fetching instruction bytes as fast as the 16-bit Execution Unit can execute them, so the Execution Unit often lies idle while waiting for the next instruction byte to be fetched. Exactly why does this happen? Recall that the 8088 is an 8086 internally, but accesses word-sized memory data at only one-half the -maximum rate of the 8086 due to the 8088’s 8-bit external data bus. +maximum rate of the 8086 due to the 8088's 8-bit external data bus. Unfortunately, instructions are among the word-sized data the 8086 fetches, meaning that the 8088 can fetch instructions at only one-half the speed of the 8086. On the other hand, the 8086-equivalent Execution @@ -119,24 +119,24 @@ up idling while waiting for instructions bytes to arrive. The BIU can fetch instruction bytes at a maximum rate of one byte every 4 cycles—*and that 4-cycle per instruction byte rate is the ultimate limit on overall instruction execution time, regardless of EU speed.* -While the EU may execute a given instruction that’s already in the -prefetch queue in less than 4 cycles per byte, over time the EU can’t -execute instructions any faster than they can arrive—and they can’t +While the EU may execute a given instruction that's already in the +prefetch queue in less than 4 cycles per byte, over time the EU can't +execute instructions any faster than they can arrive—and they can't arrive faster than 1 byte every 4 cycles. Clearly, then, the prefetch queue cycle-eater is nothing more than one aspect of the 8-bit bus cycle-eater. 8088 code often runs at less than -the Execution Unit’s maximum speed because the 8-bit data bus can’t keep -up with the demand for instruction bytes. That’s straightforward +the Execution Unit's maximum speed because the 8-bit data bus can't keep +up with the demand for instruction bytes. That's straightforward enough—so why all the fuss about the prefetch queue cycle-eater? -What makes the prefetch queue cycle-eater tricky is that it’s +What makes the prefetch queue cycle-eater tricky is that it's undocumented and unpredictable. That is, with a word-sized memory access, such as mov [bx],ax -it’s well-documented that an extra 4 cycles will always be required to +it's well-documented that an extra 4 cycles will always be required to write the upper byte of AX to memory. Not so with the prefetch queue cycle-eater lurking nearby. For instance, the instructions @@ -147,17 +147,17 @@ cycle-eater lurking nearby. For instance, the instructions shr ax,1 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 +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? Because they describe execution time *once an instruction reaches the prefetch queue.* They say nothing about whether a given instruction will -be in the prefetch queue when it’s time for that instruction to run, or +be in the prefetch queue when it's time for that instruction to run, or how long it will take that instruction to reach the prefetch queue if -it’s not there already. Thanks to the low performance of the 8088’s -external data bus, that’s a glaring omission—but, alas, an unavoidable -one. Let’s look at why the official execution times are wrong, and why -that can’t be helped. +it's not there already. Thanks to the low performance of the 8088's +external data bus, that's a glaring omission—but, alas, an unavoidable +one. Let's look at why the official execution times are wrong, and why +that can't be helped. ------------------------ --------------------------------- -------------------- [Previous](04-02.html) [Table of Contents](index.html) [Next](04-04.html) diff --git a/04-04.md b/04-04.md index 3c0fd3a..c775fff 100644 --- a/04-04.md +++ b/04-04.md @@ -9,16 +9,16 @@ 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 prefetch queue is empty at the start, the sequence *could* take 40 -cycles. In short, thanks to instruction fetching, the code won’t run at +cycles. In short, thanks to instruction fetching, the code won't run at its documented speed, and could take up to four times longer than it is supposed to. Why does Intel document Execution Unit execution time rather than overall instruction execution time, which includes both instruction fetch time and Execution Unit (EU) execution time? Well, instruction -fetching isn’t performed as part of instruction execution by the +fetching isn't performed as part of instruction execution by the Execution Unit, but instead is carried on in parallel by the Bus -Interface Unit (BIU) whenever the external data bus isn’t in use or +Interface Unit (BIU) whenever the external data bus isn't in use or whenever the EU runs out of instruction bytes to execute. Sometimes the BIU is able to use spare bus cycles to prefetch instruction bytes before the EU needs them, so in those cases instruction fetching takes no time @@ -34,14 +34,14 @@ overall execution time of the following instructions. ![](images/i.jpg) *In other words, while the execution time for a given instruction is constant, the fetch time for that instruction depends heavily on the context in which the instruction is executing—the amount of prefetching the preceding instructions allowed—and can vary from a full 4 cycles per instruction byte to no time at all.* ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -As we’ll see later, other cycle-eaters, such as DRAM refresh and display +As we'll see later, other cycle-eaters, such as DRAM refresh and display memory wait states, can cause prefetching variations even during -different executions of the same code sequence. Given that, it’s +different executions of the same code sequence. Given that, it's meaningless to talk about the prefetch time of a given instruction except in the context of a specific code sequence. So now you know why the official instruction execution times are often -wrong, and why Intel can’t provide better specifications. You also know +wrong, and why Intel can't provide better specifications. You also know now why it is that you must time your code if you want to know how fast it really is. @@ -57,16 +57,16 @@ the timer can cause problems. Some slight prefetch queue-induced inaccuracy usually exists even when the Zen timer is used to time longer code sequences, since the calls to -the Zen timer usually alter the code’s prefetch queue from its normal +the Zen timer usually alter the code's prefetch queue from its normal state. (Branches—jumps, calls, returns and the like—empty the prefetch queue.) Ideally, the Zen timer is used to measure the performance of an entire subroutine, so the prefetch queue effects of the branches at the start and end of the subroutine are similar to the effects of the calls -to the Zen timer when you’re measuring the subroutine’s performance. +to the Zen timer when you're measuring the subroutine's performance. Another way in which the prefetch queue cycle-eater complicates the use of the Zen timer involves the practice of timing the performance of a -few instructions over and over. I’ll often repeat one or two +few instructions over and over. I'll often repeat one or two instructions 100 or 1,000 times in a row in listings in this book in order to get timing intervals that are long enough to provide reliable measurements. However, as we just learned, the actual performance of any @@ -84,12 +84,12 @@ 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 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 +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 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. +which we'll discuss shortly.) Going by Listing 4.5, we would conclude +that the "true" execution time of **SHR** is 8.64 cycles. **LISTING 4.5 LST4-5.ASM** @@ -124,20 +124,20 @@ that the “true” execution time of **SHR** is 8.64 cycles. **Figure 4.3**  *Execution and instruction prefetching sequence for Listing 4.5.* -Now let’s examine Listing 4.6. Here each **SHR** follows a **MUL** +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; +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 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** +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 course not—the difference is purely a reflection of the differing states diff --git a/04-05.md b/04-05.md index 6f01b3d..d4aa19e 100644 --- a/04-05.md +++ b/04-05.md @@ -7,19 +7,19 @@ time—or even a mix of the two, if an instruction is partially prefetched—can determine code performance. Some people operate under a rule of thumb by which they assume that the execution time of each instruction is 4 cycles times the number of bytes in the instruction. -While that’s often true for register-only code, it frequently doesn’t +While that's often true for register-only code, it frequently doesn't hold for code that accesses memory. For one thing, the rule should be 4 cycles times the number of *memory accesses,* not instruction bytes, since all accesses take 4 cycles on the 8088-based PC. For another, memory-accessing instructions often have slower Execution Unit execution times than the 4 cycles per memory access rule would dictate, because -the 8088 isn’t very fast at calculating memory addresses. Also, the 4 -cycles per instruction byte rule isn’t true for register-only +the 8088 isn't very fast at calculating memory addresses. Also, the 4 +cycles per instruction byte rule isn't true for register-only instructions that are already in the prefetch queue when the preceding instruction ends. 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 +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 @@ -60,17 +60,17 @@ Listing 4.6.* endm call ZTimerOff -As you can see, it’s easy to be drawn into thinking you’re saving cycles -when you’re not. You can only improve the performance of a specific bit +As you can see, it's easy to be drawn into thinking you're saving cycles +when you're not. You can only improve the performance of a specific bit of code by reducing the factor—either instruction fetch time or -execution time, or sometimes a mix of the two—that’s limiting the +execution time, or sometimes a mix of the two—that's limiting the performance of that code. In case you missed it in all the excitement, the variability of prefetching means that our method of testing performance by executing -1,000 instructions in a row by no means produces “true” instruction +1,000 instructions in a row by no means produces "true" instruction execution times, any more than the official execution times in the Intel -manuals are “true” times. The fact of the matter is that a given +manuals are "true" times. The fact of the matter is that a given instruction takes *at least* as long to execute as the time given for it in the Intel manuals, but may take as much as 4 cycles per byte longer, depending on the state of the prefetch queue when the preceding @@ -84,24 +84,24 @@ What we *really* want is to know how long useful working code takes to run, not how long a single instruction takes, and the Zen timer gives us the tool we need to gather that information. Granted, it would be easier if we could just add up neatly documented instruction execution -times—but that’s not going to happen. Without actually measuring the -performance of a given code sequence, you simply don’t know how fast it +times—but that's not going to happen. Without actually measuring the +performance of a given code sequence, you simply don't know how fast it is. For crying out loud, even the people who *designed* the 8088 at -Intel couldn’t tell you exactly how quickly a given 8088 code sequence +Intel couldn't tell you exactly how quickly a given 8088 code sequence executes on the PC just by looking at it! Get used to the idea that execution times are only meaningful in context, learn the rules of thumb in this book, and use the Zen timer to measure your code. #### Approximating Overall Execution Times {#Heading12} -Don’t think that because overall instruction execution time is +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 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, -and since the two operations are overlapped for 2 cycles, there’s no +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 cycles, which yields an overall execution time of 8 cycles when added to @@ -116,7 +116,7 @@ be counted toward the overall execution time of one of the instructions. For all intents and purposes, one of the two instructions runs at no performance cost whatsoever while the overlap exists. -As a working definition, we’ll consider the execution time of a given +As a working definition, we'll consider the execution time of a given instruction in a particular context to start when the first byte of the instruction is sent to the Execution Unit and end when the first byte of the next instruction is sent to the EU. @@ -140,12 +140,12 @@ 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 -percent of its Execution Unit execution time even though it’s only 2 +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 combination of fewer instruction bytes and faster Execution Unit -execution times, and should be used as much as possible—just don’t -expect them to run at their “official” documented speeds. +execution times, and should be used as much as possible—just don't +expect them to run at their "official" documented speeds. ------------------------ --------------------------------- -------------------- [Previous](04-04.html) [Table of Contents](index.html) [Next](04-06.html) diff --git a/04-06.md b/04-06.md index cb0eaf0..3cd8733 100644 --- a/04-06.md +++ b/04-06.md @@ -4,10 +4,10 @@ More than anything, the above rules mean using the registers as heavily as possible, both because register-only instructions are short and -because they don’t perform memory accesses to read or write operands. +because they don't perform memory accesses to read or write operands. However, using the registers is a rule of thumb, not a commandment. In some circumstances, it may actually be *faster* to access memory. (The -look-up table technique is one such case.) What’s more, the performance +look-up table technique is one such case.) What's more, the performance of the prefetch queue (and hence the performance of each instruction) differs from one code sequence to the next, and can even differ during different executions of the *same* code sequence. @@ -20,42 +20,42 @@ bottom line. #### Holding Up the 8088 {#Heading14} -In this chapter I’ve taken you further and further into the depths of +In this chapter I've taken you further and further into the depths of the PC, telling you again and again that you must understand the computer at the lowest possible level in order to write good code. At -this point, you may well wonder, “Have we gotten low enough?” +this point, you may well wonder, "Have we gotten low enough?" Not quite yet. The 8-bit bus and prefetch queue cycle-eaters are -low-level indeed, but we’ve one level yet to go. Dynamic RAM refresh and +low-level indeed, but we've one level yet to go. Dynamic RAM refresh and wait states—our next topics—together form the lowest level at which the hardware of the PC affects code performance. Below this level, the PC is of interest only to hardware engineers. -Before we begin our discussion of dynamic RAM refresh, let’s step back +Before we begin our discussion of dynamic RAM refresh, let's step back for a moment to take an overall look at this lowest level of cycle-eaters. In truth, the distinctions between wait states and dynamic -RAM refresh don’t much matter to a programmer. What is important is that +RAM refresh don't much matter to a programmer. What is important is that you understand this: *Under certain circumstances, devices on the PC bus can stop the CPU for 1 or more cycles, making your code run more slowly than it seemingly should.* -Unlike all the cycle-eaters we’ve encountered so far, wait states and +Unlike all the cycle-eaters we've encountered so far, wait states and dynamic RAM refresh are strictly external to the CPU, as was shown in -Figure 4.1. Adapters on the PC’s bus, such as video and memory cards, -can insert wait states on any bus access, the idea being that they won’t +Figure 4.1. Adapters on the PC's bus, such as video and memory cards, +can insert wait states on any bus access, the idea being that they won't be able to complete the access properly unless the access is stretched out. Likewise, the channel of the DMA controller dedicated to dynamic RAM refresh can request control of the bus at any time, although the CPU must relinquish the bus before the DMA controller can take over. This -means that your code can’t directly control wait states or dynamic RAM +means that your code can't directly control wait states or dynamic RAM refresh. However, code *can* sometimes be designed to minimize the effects of these cycle-eaters, and even when the cycle-eaters slow your code without there being a thing in the world you can do about it, -you’re still better off understanding that you’re losing performance and -knowing why your code doesn’t run as fast as it’s supposed to than you +you're still better off understanding that you're losing performance and +knowing why your code doesn't run as fast as it's supposed to than you were programming in ignorance. -Let’s start with DRAM refresh, which affects the performance of every +Let's start with DRAM refresh, which affects the performance of every program that runs on the PC. ### Dynamic RAM Refresh: The Invisible Hand {#Heading15} @@ -64,29 +64,29 @@ Dynamic RAM (DRAM) refresh is sort of an act of God. By that I mean that DRAM refresh invisibly and inexorably steals a certain fraction of all available memory access time from your programs, when they are accessing memory for code and data. (When they are accessing cache on more recent -processors, theoretically the DRAM refresh cycle-eater doesn’t come into +processors, theoretically the DRAM refresh cycle-eater doesn't come into play, but there are other cycle-eaters waiting to prey on cache-bound -programs.) While you *could* stop DRAM refresh, you wouldn’t want to +programs.) While you *could* stop DRAM refresh, you wouldn't want to since that would be a sure prescription for crashing your computer. In the end, thanks to DRAM refresh, almost all code runs a bit slower on -the PC than it otherwise would, and that’s that. +the PC than it otherwise would, and that's that. A bit of background: A static RAM (SRAM) chip is a memory chip that retains its contents indefinitely so long as power is maintained. By contrast, each of several blocks of bits in a dynamic RAM (DRAM) chip -retains its contents for only a short time after it’s accessed for a +retains its contents for only a short time after it's accessed for a read or write. In order to get a DRAM chip to store data for an extended period, each of the blocks of bits in that chip must be accessed -regularly, so that the chip’s stored data is kept refreshed and valid. +regularly, so that the chip's stored data is kept refreshed and valid. So long as this is done often enough, a DRAM chip will retain its contents indefinitely. -All of the PC’s system memory consists of DRAM chips. Each DRAM chip in +All of the PC's system memory consists of DRAM chips. Each DRAM chip in the PC must be completely refreshed about once every four milliseconds -in order to ensure the integrity of the data it stores. Obviously, it’s +in order to ensure the integrity of the data it stores. Obviously, it's highly desirable that the memory in the PC retain the correct data indefinitely, so each DRAM chip in the PC *must* always be refreshed -within 4 µs of the last refresh. Since there’s no guarantee that a given +within 4 µs of the last refresh. Since there's no guarantee that a given program will access each and every DRAM block once every 4 µs, the PC contains special circuitry and programming for providing DRAM refresh. @@ -100,25 +100,25 @@ signal. (DMA stands for *direct memory access,* the ability of a device other than the 8088 to control the bus and access memory directly, without any help from the 8088.) As soon as the 8088 is between memory accesses, it gives control of the bus to the 8237, which in conjunction -with special circuitry on the PC’s motherboard then performs a single +with special circuitry on the PC's motherboard then performs a single 4-cycle read access to 1 of 256 possible addresses, advancing to the next address on each successive access. (The read access is only for the -purpose of refreshing the DRAM; the data that is read isn’t used.) +purpose of refreshing the DRAM; the data that is read isn't used.) The 256 addresses accessed by the refresh DMA accesses are arranged so that taken together they properly refresh all the memory in the PC. By -accessing one of the 256 addresses every 15.08 µs, all of the PC’s DRAM +accessing one of the 256 addresses every 15.08 µs, all of the PC's DRAM is refreshed in 256 x 15.08 µs, or 3.86 µs, which is just about the desired 4 µs time I mentioned earlier. (Only the first 640K of memory is refreshed in the PC; video adapters and other adapters above 640K containing memory that requires refreshing must provide their own DRAM refresh in pre-AT systems.) -Don’t sweat the details here. The important point is this: For at least -4 out of every 72 cycles, the original PC’s bus is given over to DRAM +Don't sweat the details here. The important point is this: For at least +4 out of every 72 cycles, the original PC's bus is given over to DRAM refresh and is not available to the 8088, as shown in Figure 4.5. That -means that as much as 5.56 percent of the PC’s already inadequate bus -capacity is lost. However, DRAM refresh doesn’t necessarily stop the +means that as much as 5.56 percent of the PC's already inadequate bus +capacity is lost. However, DRAM refresh doesn't necessarily stop the 8088 in its tracks for 4 cycles. The Execution Unit of the 8088 can keep processing while DRAM refresh is occurring, unless the EU needs to access memory. Consequently, DRAM refresh can slow code performance diff --git a/04-07.md b/04-07.md index bf1d4fa..24465f4 100644 --- a/04-07.md +++ b/04-07.md @@ -4,7 +4,7 @@ #### The Impact of DRAM Refresh {#Heading17} -Let’s look at examples from opposite ends of the spectrum in terms of +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 the 8088 executes in between 118 and 133 cycles and is only 2 bytes @@ -15,7 +15,7 @@ keep the Execution Unit well-supplied with instruction bytes at all times. Since Listing 4.9 uses no memory operands, the Execution Unit should never have to wait for data from memory, and DRAM refresh should have no impact on performance. (Remember that the Execution Unit can -operate normally during DRAM refreshes so long as it doesn’t need to +operate normally during DRAM refreshes so long as it doesn't need to request a memory access from the Bus Interface Unit.) **LISTING 4.9 LST4-9.ASM** @@ -33,13 +33,13 @@ 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 +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 +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 @@ -58,18 +58,18 @@ to fetch the instruction bytes. endm call ZTimerOff -Since 4 cycles are required to read each instruction byte, we’d expect +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, -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 +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 indicates that DRAM refresh is stealing not 4, but 5.33 cycles out of every 72 cycles. How can this be? The answer is that a given DRAM refresh can actually hold up CPU memory 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 +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 @@ -77,7 +77,7 @@ 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.* -Which of the two cases we’ve examined reflects reality? While either +Which of the two cases we've examined reflects reality? While either case *can* happen, the latter case—significant performance reduction, ranging as high as 8.33 percent—is far more likely to occur. This is especially true for high-performance assembly code, which uses fast @@ -93,8 +93,8 @@ DRAM refresh cycle-eater? Nothing. -As I’ve said before, DRAM refresh is an act of God. DRAM refresh is a -fundamental, unchanging part of the PC’s operation, and there’s nothing +As I've said before, DRAM refresh is an act of God. DRAM refresh is a +fundamental, unchanging part of the PC's operation, and there's nothing you or I can do about it. If refresh were any less frequent, the reliability of the PC would be compromised, so tinkering with either timer 1 or DMA channel 0 to reduce DRAM refresh overhead is out. Nor is @@ -102,7 +102,7 @@ there any way to structure code to minimize the impact of DRAM refresh. Sure, some instructions are affected less by DRAM refresh than others, but how many multiplies and divides in a row can you really use? I suppose that code *could* conceivably be structured to leave a free -memory access every 72 cycles, so DRAM refresh wouldn’t have any effect. +memory access every 72 cycles, so DRAM refresh wouldn't have any effect. In the old days when code size was measured in bytes, not K bytes, and processors were less powerful—and complex—programmers did in fact use similar tricks to eke every last bit of performance from their code. @@ -112,35 +112,35 @@ modest performance improvement that did result could never justify the increase in programming complexity and the limits on creative programming that such an approach would entail. Besides, all that effort goes to waste on faster 8088s, 286s, and other computers with different -execution speeds and refresh characteristics. There’s no way around it: +execution speeds and refresh characteristics. There's no way around it: Useful code accesses memory frequently and at irregular intervals, and over the long haul DRAM refresh always exacts its price. -If you’re still harboring thoughts of reducing the overhead of DRAM +If you're still harboring thoughts of reducing the overhead of DRAM refresh, consider this. Instructions that tend not to suffer very much from DRAM refresh are those that have a high ratio of execution time to -instruction fetch time, and those aren’t the fastest instructions of the -PC. It certainly wouldn’t make sense to use slower instructions just to -reduce DRAM refresh overhead, for it’s *total* execution time—DRAM +instruction fetch time, and those aren't the fastest instructions of the +PC. It certainly wouldn't make sense to use slower instructions just to +reduce DRAM refresh overhead, for it's *total* execution time—DRAM refresh, instruction fetching, and all—that matters. The important thing to understand about DRAM refresh is that it generally slows your code down, and that the extent of that performance reduction can vary considerably and unpredictably, depending on how the -DRAM refreshes interact with your code’s pattern of memory accesses. +DRAM refreshes interact with your code's pattern of memory accesses. When you use the Zen timer and get a fractional cycle count for the -execution time of an instruction, that’s often the DRAM refresh +execution time of an instruction, that's often the DRAM refresh cycle-eater at work. (The display adapter cycleis another possible culprit, and, on 386s and later processors, cache misses and pipeline execution hazards produce this sort of effect as well.) Whenever you get two timing results that differ less or more than they seemingly should, -that’s usually DRAM refresh too. Thanks to DRAM refresh, variations of +that's usually DRAM refresh too. Thanks to DRAM refresh, variations of up to 8.33 percent in PC code performance are par for the course. ### Wait States {#Heading19} Wait states are cycles during which a bus access by the CPU to a device -on the PC’s bus is temporarily halted by that device while the device +on the PC's bus is temporarily halted by that device while the device gets ready to complete the read or write. Wait states are well and truly the lowest level of code performance. Everything we have discussed (and will discuss)—even DMA accesses—can be affected by wait states. diff --git a/04-08.md b/04-08.md index eb1d30e..51c48c6 100644 --- a/04-08.md +++ b/04-08.md @@ -5,17 +5,17 @@ Wait states exist because the CPU must to be able to coexist with any adapter, no matter how slow (within reason). The 8088 expects to be able to complete each bus access—a memory or I/O read or write—in 4 cycles, -but adapters can’t always respond that quickly for a number of reasons. +but adapters can't always respond that quickly for a number of reasons. For example, display adapters must split access to display memory between the CPU and the circuitry that generates the video signal based -on the contents of display memory, so they often can’t immediately +on the contents of display memory, so they often can't immediately fulfill a request by the CPU for a display memory read or write. To resolve this conflict, display adapters can tell the CPU to wait during bus accesses by inserting one or more wait states, as shown in Figure 4.6. The CPU simply sits and idles as long as wait states are inserted, then completes the access as soon as the display adapter indicates its readiness by no longer inserting wait states. The same would be true of -any adapter that couldn’t keep up with the CPU. +any adapter that couldn't keep up with the CPU. Mind you, this is all transparent to executing code. An instruction that encounters wait states runs exactly as if there were no wait states, @@ -23,11 +23,11 @@ only slower. Wait states are nothing more or less than wasted time as far as the CPU and your program are concerned. By understanding the circumstances in which wait states can occur, you -can avoid them when possible. Even when it’s not possible to work around -wait states, it’s still to your advantage to understand how they can +can avoid them when possible. Even when it's not possible to work around +wait states, it's still to your advantage to understand how they can cause your code to run more slowly. -First, let’s learn a bit more about wait states by contrast with DRAM +First, let's learn a bit more about wait states by contrast with DRAM refresh. Unlike DRAM refresh, wait states do not occur on any regularly scheduled basis, and are of no particular duration. Wait states can only occur when an instruction performs a memory or I/O read or write. Both @@ -43,9 +43,9 @@ perform the access. ![](images/04-06.jpg)\ **Figure 4.6**  *Video wait states inserted by the display adapter.* -As with DRAM refresh, wait states don’t stop the 8088 completely. The +As with DRAM refresh, wait states don't stop the 8088 completely. The Execution Unit can continue processing while wait states are inserted, -so long as the EU doesn’t need to perform a bus access. However, in the +so long as the EU doesn't need to perform a bus access. However, in the PC, wait states most often occur when an instruction accesses a memory operand, so in fact the Execution Unit usually is stopped by wait states. (Instruction fetches rarely wait in an 8088-based PC because @@ -96,11 +96,11 @@ VGA clones.) ![](images/04-07.jpg)\ **Figure 4.7**  *Allocation of display memory access.* -Second, because the displayed dots (or *pixels,* short for “picture -elements”) must be drawn on the screen at a constant speed, many display +Second, because the displayed dots (or *pixels,* short for "picture +elements") must be drawn on the screen at a constant speed, many display adapters provide memory accesses only at fixed intervals. As a result, time can be lost while the 8088 synchronizes with the start of the next -display adapter memory access, even if the video circuitry isn’t +display adapter memory access, even if the video circuitry isn't accessing display memory at that time, as shown in Figure 4.8. Finally, the time it takes a display adapter to complete a memory access @@ -110,27 +110,27 @@ the time taken for display memory to complete an 8088 read or write access is often longer than the time taken for system memory to complete an access, even if the 8088 lucks into hitting a free display memory access just as it becomes available, again as shown in Figure 4.8. Any -or all of the three factors I’ve described can result in wait states, +or all of the three factors I've described can result in wait states, slowing the 8088 and creating the display adapter cycle. ![](images/04-08.jpg)\ **Figure 4.8**  *Display memory access slots.* -If some of this is Greek to you, don’t worry. The important point is +If some of this is Greek to you, don't worry. The important point is that display memory is not very fast compared to normal system memory. -How slow is it? *Incredibly* slow. Remember how slow IBM’s ill-fated -PC*jr*was? In case you’ve forgotten, I’ll refresh your memory: The +How slow is it? *Incredibly* slow. Remember how slow IBM's ill-fated +PC*jr*was? In case you've forgotten, I'll refresh your memory: The PC*jr*was at best only half as fast as the PC. The PC*jr* had an 8088 running at 4.77 MHz, just like the PC—why do you suppose it was so much -slower? I’ll tell you why: *All the memory in the PCjr was display +slower? I'll tell you why: *All the memory in the PCjr was display memory.* Enough said. All the memory in the PC is *not* display memory, however, -and unless you’re thickheaded enough to put code in display memory, the -PC isn’t going to run as slowly as a PC*jr.* (Putting code or other +and unless you're thickheaded enough to put code in display memory, the +PC isn't going to run as slowly as a PC*jr.* (Putting code or other non-video data in unused areas of display memory sounds like a neat idea—until you consider the effect on instruction prefetching of cutting -the 8088’s already-poor memory access performance in half. Running your +the 8088's already-poor memory access performance in half. Running your code from display memory is sort of like running on a hypothetical 8084—an 8086 with a *4-bit* bus. Not recommended!) Given that your code and data reside in normal system memory below the 640K mark, how great diff --git a/04-09.md b/04-09.md index 4b7cb8d..f209d66 100644 --- a/04-09.md +++ b/04-09.md @@ -3,43 +3,43 @@ ------------------------ --------------------------------- -------------------- The answer varies considerably depending on what display adapter and -what display mode we’re talking about. The display adapter cycle-eater +what display mode we're talking about. The display adapter cycle-eater is worst with the Enhanced Graphics Adapter (EGA) and the original Video Graphics Array (VGA). (Many VGAs, especially newer ones, insert many -fewer wait states than IBM’s original VGA. On the other hand, Super VGAs +fewer wait states than IBM's original VGA. On the other hand, Super VGAs have more bytes of display memory to be accessed in high-resolution mode.) While the Color/Graphics Adapter (CGA), Monochrome Display Adapter (MDA), and Hercules Graphics Card (HGC) all suffer from the display adapter cycle-eater as well, they suffer to a lesser degree. Since the VGA represents the base standard for PC graphics now and for the foreseeable future, and since it is the hardest graphics adapter to -wring performance from, we’ll restrict our discussion to the VGA (and +wring performance from, we'll restrict our discussion to the VGA (and its close relative, the EGA) for the remainder of this chapter. #### The Impact of the Display Adapter Cycle-Eater {#Heading21} Even on the EGA and VGA, the effect of the display adapter cycle-eater depends on the display mode selected. In text mode, the display adapter -cycle-eater is rarely a major factor. It’s not that the cycle-eater -isn’t present; however, a mere 4,000 bytes control the entire text mode -display, and even with the display adapter cycle-eater it just doesn’t +cycle-eater is rarely a major factor. It's not that the cycle-eater +isn't present; however, a mere 4,000 bytes control the entire text mode +display, and even with the display adapter cycle-eater it just doesn't take that long to manipulate 4,000 bytes. Even if the display adapter cycle-eater were to cause the 8088 to take as much as 5µs per display memory access—more than five times normal—it would still take only 4,000x 2x 5µs, or 40 µs, to read and write every byte of display memory. -That’s a lot of time as measured in 8088 cycles, but it’s less than the +That's a lot of time as measured in 8088 cycles, but it's less than the blink of an eye in human time, and video performance only matters in human time. After all, the whole point of drawing graphics is to convey visual information, and if that information can be presented faster than the eye can see, that is by definition fast enough. -That’s not to say that the display adapter cycle-eater *can’t* matter in +That's not to say that the display adapter cycle-eater *can't* matter in text mode. In Chapter 3, I recounted the story of a debate among letter-writers to a magazine about exactly how quickly characters could be written to display memory without causing snow. The writers carefully -added up Intel’s instruction cycle times to see how many writes to +added up Intel's instruction cycle times to see how many writes to display memory they could squeeze into a single horizontal retrace -interval. (On a CGA, it’s only during the short horizontal retrace +interval. (On a CGA, it's only during the short horizontal retrace interval and the longer vertical retrace interval that display memory can be accessed in 80-column text mode without causing snow.) Of course, now we know that their cardinal sin was to ignore the prefetch queue; @@ -63,15 +63,15 @@ graphics modes, the cumulative effects of display memory wait states can seriously impact code performance, even as measured in human time. For example, if we assume the same 5 µs per display memory access for -the EGA’s high-resolution graphics mode that we assumed for text mode, +the EGA's high-resolution graphics mode that we assumed for text mode, it would take 26,000 x 2 x 5 µs, or 260 µs, to scroll the screen once in -the EGA’s high-resolution graphics mode, mode 10H. That’s more than +the EGA's high-resolution graphics mode, mode 10H. That's more than one-quarter of a second—noticeable by human standards, an eternity by 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 +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 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. @@ -107,7 +107,7 @@ long as we had assumed, but a long time nonetheless. mov ax,0003h int 10h ;return to text mode -For comparison, let’s see how long the same code takes when accessing +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 segment, executes in 1.39 µs per display memory access. That means that @@ -136,7 +136,7 @@ cycle-eater can *more than double* the execution time of 8088 code! ; times call ZTimerOff -Bear in mind that we’re talking about a worst case here; the impact of +Bear in mind that we're talking about a worst case here; the impact of the display adapter cycle-eater is proportional to the percent of time a given code sequence spends accessing display memory. diff --git a/04-10.md b/04-10.md index 04604b9..83778b3 100644 --- a/04-10.md +++ b/04-10.md @@ -21,7 +21,7 @@ instructions per microsecond than can the 8088-based PC, it takes just as long to access display memory on those computers as on the 8088-based PC. Remember, the limited speed of access to a graphics adapter is an inherent characteristic of the adapter, so the fastest computer around -can’t access display memory one iota faster than the adapter will allow. +can't access display memory one iota faster than the adapter will allow. #### What to Do about the Display Adapter Cycle-Eater? {#Heading22} @@ -32,7 +32,7 @@ used to mask individual pixels and clip images. Why? Because read/modify/write operations require two display memory accesses (one read and one write) each time display memory is manipulated. Instead, we should try to use writes of the sort that set all the pixels in a given -byte of display memory at once, since such writes don’t require +byte of display memory at once, since such writes don't require accompanying read accesses. The key here is that only half as many display memory accesses are required to write a byte to display memory as are required to read a byte from display memory, mask part of it off @@ -67,7 +67,7 @@ states.) It would be handy to explore the display adapter cycle-eater issue in depth, with lots of example code and execution timings, but alas, I -don’t have the space for that right now. For the time being, all you +don't have the space for that right now. For the time being, all you really need to know about the display adapter cycle-eater is that on the 8088 you can lose more than 8 cycles of execution time on each access to display memory. For intensive access to display memory, the loss really @@ -80,10 +80,10 @@ code, and that is of course to measure the performance of that code. #### Cycle-Eaters: A Summary {#Heading23} -We’ve covered a great deal of sophisticated material in this chapter, so -don’t feel bad if you haven’t understood everything you’ve read; it will +We've covered a great deal of sophisticated material in this chapter, so +don't feel bad if you haven't understood everything you've read; it will all become clear from further reading, especially once you study, time, -and tune code that you have written yourself. What’s really important is +and tune code that you have written yourself. What's really important is that you come away from this chapter understanding that on the 8088: - The 8-bit bus cycle-eater causes each access to a word-sized operand @@ -100,24 +100,24 @@ that you come away from this chapter understanding that on the 8088: This basic knowledge about cycle-eaters puts you in a good position to understand the results reported by the Zen timer, and that means that -you’re well on your way to writing high-performance assembler code. +you're well on your way to writing high-performance assembler code. #### What Does It All Mean? {#Heading24} -There you have it: life under the programming interface. It’s not a +There you have it: life under the programming interface. It's not a particularly pretty picture for the inhabitants of that strange realm where hardware and software meet are little-known cycle-eaters that sap the speed from your unsuspecting code. Still, some of those cycle-eaters can be minimized by keeping instructions short, using the registers, using byte-sized memory operands, and accessing display memory as little as possible. None of the cycle-eaters can be eliminated, and dynamic RAM -refresh can scarcely be addressed at all; still, aren’t you better off +refresh can scarcely be addressed at all; still, aren't you better off knowing how fast your code *really* runs—and why—than you were reading the official execution times and guessing? And while specific cycle-eaters vary in importance on later x86-family processors, with some cycle-eaters vanishing altogether and new ones appearing, the concept that understanding these obscure gremlins is a key to -performance remains unchanged, as we’ll see again and again in later +performance remains unchanged, as we'll see again and again in later chapters. ------------------------ --------------------------------- -------------------- diff --git a/05-01.md b/05-01.md index 3c8ca34..1709eb5 100644 --- a/05-01.md +++ b/05-01.md @@ -20,17 +20,17 @@ The move took days to complete. *Never again*. -You’re probably wondering two things: What does this have to do with -high-performance programming, and why on earth didn’t I rent a truck and +You're probably wondering two things: What does this have to do with +high-performance programming, and why on earth didn't I rent a truck and get the move over in one or two trips, saving hours of driving? As it -happens, the second question answers the first. I didn’t rent a truck +happens, the second question answers the first. I didn't rent a truck because it *seemed* easier and cheaper to use cars—no big truck to drive, no rentals, spread the work out more manageably, and so on. -It wasn’t easier, and wasn’t even much cheaper. (It costs quite a bit to +It wasn't easier, and wasn't even much cheaper. (It costs quite a bit to drive a car 330 miles, to say nothing of the value of 15 hours of my time.) But, at the time, it seemed as though my approach would be easier -and cheaper. In fact, I didn’t realize just how much time I had wasted +and cheaper. In fact, I didn't realize just how much time I had wasted driving back and forth until I sat down to write this chapter. In Chapter 1, I briefly discussed using *restartable blocks*. This, you @@ -55,24 +55,24 @@ more effort and forethought, but would have paid off handsomely. ![](images/i.jpg) *The easy, familiar approach often has nothing in its favor except that it requires less thinking; not a great virtue when writing high-performance code—or when moving.* ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -And with that, let’s look at a fairly complex application of restartable +And with that, let's look at a fairly complex application of restartable blocks. #### Searching for Text {#Heading3} -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 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 -can do, and isn’t really intended to be a generally useful application; +can do, and isn't really intended to be a generally useful application; the purpose is to provide insight into restartable blocks in particular and optimization in general in the course of developing a search engine. That search engine will, however, be easy to plug into any program, and -there’s nothing preventing you from using it in a more fruitful context, +there's nothing preventing you from using it in a more fruitful context, like searching through a user-selectable file set. The first point to address in designing our program involves the @@ -82,9 +82,9 @@ that involve reading any byte of the file more than once, because disk access time is orders of magnitude slower than any data handling performed by our own code. Based on our experience in Chapter 1, we can also discard all approaches that get bytes either one at a time or in -small sets from DOS. We want to read big “buffers-full” of bytes at a +small sets from DOS. We want to read big "buffers-full" of bytes at a pop from the searched file, and the bigger the buffer the better—in -order to minimize DOS’s overhead. A good rough cut is a buffer that will +order to minimize DOS's overhead. A good rough cut is a buffer that will be between 16K and 64K, depending on the exact search approach, 64K being the maximum size because near pointers make for superior performance. @@ -93,13 +93,13 @@ So we know we want to work with a large buffer, filling it as infrequently as possible. Now we have to figure out how to search through a file by loading it into that large buffer in chunks. To accomplish this, we have to know how we want to do our searching, and -that’s not immediately obvious. Where do we begin? +that's not immediately obvious. Where do we begin? Well, it might be instructive to consider how we would search if our search involved only one buffer, already resident in memory. In other -words, suppose we don’t have to bother with file handling at all, and -further suppose that we don’t have to deal with searching through -multiple blocks. After all, that’s a good description of the +words, suppose we don't have to bother with file handling at all, and +further suppose that we don't have to deal with searching through +multiple blocks. After all, that's a good description of the all-important inner loop of our searching program, where the program will spend virtually all of its time (aside from the unavoidable disk access overhead). @@ -109,7 +109,7 @@ access overhead). 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()** -would work, it isn’t ideal for our purposes. The problem is this: Where +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 another string. diff --git a/05-02.md b/05-02.md index c886e95..2d09a46 100644 --- a/05-02.md +++ b/05-02.md @@ -11,7 +11,7 @@ implementation is well-written, its performance will suffer, at least for our application, from unnecessary overhead. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *This illustrates why you shouldn’t think of C/C++ library functions as black boxes; understand what they do and try to figure out how they do it, and relate that to their performance in the context you’re interested in.* + ![](images/i.jpg) *This illustrates why you shouldn't think of C/C++ library functions as black boxes; understand what they do and try to figure out how they do it, and relate that to their performance in the context you're interested in.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### Brute-Force Techniques {#Heading5} @@ -19,12 +19,12 @@ 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 -buffer to the string we’re searching for, as illustrated in Figure 5.1. +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** -instruction. However, *never assume!* It wouldn’t hurt to use a debugger +instruction. However, *never assume!* It wouldn't hurt to use a debugger 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()**. @@ -35,14 +35,14 @@ language implementation of **memcmp()**. 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, -along with a pass through the comparison loop. Surely there’s a better +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 performing a simple test on each potential match location that will -reject most such locations right off the bat. We’ll just check whether +reject most such locations right off the bat. We'll just check whether the first character of the potentially matching buffer location matches -the first character of the string we’re searching for. We could make +the first character of the string we're searching for. We could make this check by using a pointer in a loop to scan the buffer for the next match for the first character, stopping to check for a match with the rest of the string *only* when the first character matches, as shown in @@ -50,65 +50,65 @@ Figure 5.2. ### Using memchr() {#Heading6} -There’s yet a better way to implement this approach, however. Use the +There's yet a better way to implement this approach, however. Use 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** -instruction, although again it wouldn’t hurt to check). By using +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 takes good advantage of the information we have about the buffer being -searched and the string we’re searching for. Our engine also relies +searched and the string we're searching for. Our engine also relies heavily on repeated string instructions, assuming that the **memchr()** and **memcmp()** library functions are properly coded. ![](images/05-02.jpg)\ **Figure 5.2**  *The faster string-searching technique.* -We’re going to go with the this approach in our file-searching program; +We're going to go with the this approach in our file-searching program; the only trick lies in deciding how to integrate this approach with restartable blocks in order to search through files larger than our -buffer. This certainly isn’t the fastest-possible searching algorithm; +buffer. This certainly isn't the fastest-possible searching algorithm; as one example, the Boyer-Moore algorithm, which cleverly eliminates many buffer locations as potential matches in the process of checking preceding locations, can be considerably faster. However, the Boyer-Moore algorithm is quite complex to understand and implement, and -would distract us from our main focus, restartable blocks, so we’ll save +would distract us from our main focus, restartable blocks, so we'll save it for a later chapter (Chapter 14, to be precise). Besides, I suspect -you’ll find the approach we’ll use to be fast enough for most purposes. +you'll find the approach we'll use to be fast enough for most purposes. -Now that we’ve selected a searching approach, let’s integrate it with +Now that we've selected a searching approach, let's integrate it with file handling and searching through multiple blocks. In other words, -let’s make it restartable. +let's make it restartable. #### Making a Search Restartable {#Heading7} -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 +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 -described, and exit with a “string found” response if the desired string +described, and exit with a "string found" response if the desired string is found. -Otherwise, we’ll load in another buffer full of data from the file, +Otherwise, we'll load in another buffer full of data from the file, search it, and so on. The only trick lies in handling potentially matching sequences in the file that start in one buffer and end in the -next—that is, sequences that span buffers. We’ll handle this by copying +next—that is, sequences that span buffers. We'll handle this by copying the unchecked bytes at the end of one buffer to the start of the next and reading that many fewer bytes the next time we fill the buffer. The exact number of bytes to be copied from the end of one buffer to the start of the next is the length of the searched-for string minus 1, -since that’s how many bytes at the end of the buffer can’t be checked as +since that's how many bytes at the end of the buffer can't be checked as possible matches (because the check would run off the end of the buffer). -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 +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 -should be, given that it’s by far the most heavily-executed code in the +should be, given that it's by far the most heavily-executed code in the listing). Listing 5.1 nicely illustrates the core concept of restartable blocks: diff --git a/05-03.md b/05-03.md index 1cf4048..c39bd1d 100644 --- a/05-03.md +++ b/05-03.md @@ -15,7 +15,7 @@ #include /* alloc.h for Borland compilers, malloc.h for Microsoft compilers */ - #define BLOCK_SIZE 0x4000 /* we’ll process the file in 16K blocks */ + #define BLOCK_SIZE 0x4000 /* we'll process the file in 16K blocks */ /* Searches the specified number of sequences in the specified buffer for matches to SearchString of SearchStringLength. Note @@ -42,16 +42,16 @@ also matches */ if ( SearchStringLength == 1 ) { return(1); /* That one matching character was the whole - search string, so we’ve got a match */ + search string, so we've got a match */ } else { /* Check whether the remaining characters match */ if ( !memcmp(PotentialMatch + 1, SearchString + 1, SearchStringLength - 1) ) { - return(1); /* We’ve got a match */ + return(1); /* We've got a match */ } } - /* The string doesn’t match; keep going by pointing past the + /* The string doesn't match; keep going by pointing past the potential match location we just rejected */ SearchLength -= PotentialMatch - Buffer + 1; Buffer = PotentialMatch + 1; @@ -79,13 +79,13 @@ /* Check for the proper number of arguments */ if ( argc != 3 ) { - printf(“usage: search filename search-string\n”); + printf("usage: search filename search-string\n"); exit(1); } /* Try to open the file to be searched */ if ( (Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1 ) { - printf(“Can’t open file: %s\n”, argv[1]); + printf("Can't open file: %s\n", argv[1]); exit(1); } /* Calculate the length of text to search for */ @@ -93,7 +93,7 @@ SearchStringLength = strlen(SearchString); /* Try to get memory in which to buffer the data */ if ( (WorkingBlock = malloc(BLOCK_SIZE)) == NULL ) { - printf(“Can’t get enough memory\n”); + printf("Can't get enough memory\n"); exit(1); } @@ -102,7 +102,7 @@ NextLoadPtr = WorkingBlock; NextLoadCount = BLOCK_SIZE; Done = 0; /* Not done with search yet */ - Found = 0; /* Assume we won’t find a match */ + Found = 0; /* Assume we won't find a match */ /* Search the file in BLOCK_SIZE chunks */ do { /* Read in however many bytes are needed to fill out the block @@ -110,10 +110,10 @@ the rest of the bytes in the file, whichever is less */ if ( (WorkingLength = read(Handle, NextLoadPtr, NextLoadCount)) == -1 ) { - printf(“Error reading file %s\n”, argv[1]); + printf("Error reading file %s\n", argv[1]); exit(1); } - /* If we didn’t read all the bytes we requested, we’re done + /* If we didn't read all the bytes we requested, we're done after this block, whether we find a match or not */ if ( WorkingLength != NextLoadCount ) { Done = 1; @@ -132,7 +132,7 @@ WorkingLength - SearchStringLength + 1) <= 0 ) { Done = 1; /* Too few characters in this block for there to be any possible matches, so this - is the final block and we’re done without + is the final block and we're done without finding a match */ } @@ -140,7 +140,7 @@ /* Search this block */ if ( SearchForString(WorkingBlock, BlockSearchLength, SearchString, SearchStringLength) ) { - Found = 1; /* We’ve found a match */ + Found = 1; /* We've found a match */ Done = 1; } else { @@ -162,9 +162,9 @@ /* Report the results */ if ( Found ) { - printf(“String found\n”); + printf("String found\n"); } else { - printf(“String not found\n”); + printf("String not found\n"); } exit(Found); /* Return the found/not found status as the DOS errorlevel */ diff --git a/05-04.md b/05-04.md index 2d80003..10c691f 100644 --- a/05-04.md +++ b/05-04.md @@ -6,26 +6,26 @@ To boost the overall performance of Listing 5.1, I would normally convert **SearchForString()** to assembly language at this point. -However, I’m not going to do that, and the reason is as important a +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 implementation, and all should become much clearer. -As you’ll recall from Chapter 1, one of the important rules for +As you'll recall from Chapter 1, one of the important rules for optimization involves knowing when optimization is worth bothering with -at all. Another rule involves understanding where most of a program’s -execution time is going. That’s more true for Listing 5.1 than you might +at all. Another rule involves understanding where most of a program's +execution time is going. That's more true for Listing 5.1 than you might 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 +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 -increase perceptibly!* At 2K, the program slows slightly; it’s not until +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. -So the first thing we’ve discovered is that, while bigger blocks do make +So the first thing we've discovered is that, while bigger blocks do make 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 @@ -35,9 +35,9 @@ spent in executing the **read()** function. 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 +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 +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. @@ -48,7 +48,7 @@ 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 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 +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 improvement, and that would require more than doubling the performance of code that already uses repeated string instructions to do most of the @@ -58,7 +58,7 @@ Not likely. #### Knowing When Assembly Is Pointless {#Heading9} -So that’s why we’re not going to go to assembly language in this +So that's why we're not going to go to assembly language in this example—which is not to say it would never be worth converting the search engine in Listing 5.1 to assembly. @@ -75,7 +75,7 @@ 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 +"zy," Listing 5.1 takes almost 1/2 minute, or nearly an order of magnitude longer than when searching a file filled with normal text. ------------------------ --------------------------------- -------------------- diff --git a/05-05.md b/05-05.md index 89c81d7..6a3350c 100644 --- a/05-05.md +++ b/05-05.md @@ -12,30 +12,30 @@ searching through huge (segment-spanning) buffers. And so we find, as we so often will, that optimization is definitely not a cut-and-dried matter, and that there is no such thing as a single -“best” approach. +"best" approach. ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *You must know what your application will typically do, and you must know whether you’re more concerned with average or worst-case performance before you can decide how best to speed up your program—and, indeed, whether speeding it up is worth doing at all.* + ![](images/i.jpg) *You must know what your application will typically do, and you must know whether you're more concerned with average or worst-case performance before you can decide how best to speed up your program—and, indeed, whether speeding it up is worth doing at all.* ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -By the way, don’t think that just because very large block sizes don’t -much improve performance, it wasn’t worth using restartable blocks in +By the way, don't think that just because very large block sizes don't +much improve performance, it wasn't worth using restartable blocks in Listing 5.1. Listing 5.1 runs more than three times more slowly with a block size of 32 bytes than with a block size of 4K, and any byte-by-byte approach would surely be slower still, due to the overhead of repeated calls to DOS and/or the C stream I/O library. Restartable blocks do minimize the overhead of DOS file-access calls in -Listing 5.1; it’s just that there’s no way to reduce that overhead to +Listing 5.1; it's just that there's no way to reduce that overhead to the point where it becomes worth attempting to further improve the performance of our relatively efficient search engine. Although the -search engine is by no means fully optimized, it’s nonetheless as fast -as there’s any reason for it to be, given the balance of performance +search engine is by no means fully optimized, it's nonetheless as fast +as there's any reason for it to be, given the balance of performance among the components of this program. ### Always Look Where Execution Is Going {#Heading10} -I’ve explained two important lessons: Know when it’s worth optimizing +I've explained two important lessons: Know when it's worth optimizing further, and use restartable blocks to process large data sets as a series of blocks, with each block handled at high speed. The first lesson is less obvious than it seems. @@ -46,7 +46,7 @@ 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 same thing much more easily), I found that the best code in the world -wouldn’t make much difference. +wouldn't make much difference. ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ![](images/i.jpg) *When you try to speed up code, take a moment to identify the hot spots in your program so that you know where optimization is needed and whether it will make a significant difference before you invest your time.* @@ -54,17 +54,17 @@ wouldn’t make much difference. As for restartable blocks: Here we tackled a considerably more complex application of restartable blocks than we did in Chapter 1—which turned -out not to be so difficult after all. Don’t let irregularities in the +out not to be so difficult after all. Don't let irregularities in the programming tasks you tackle, such as strings that span blocks, fluster you into settling for easy, general—and slow—solutions. Focus on making the inner loop—the code that handles each block—as efficient as possible, then structure the rest of your code to support the inner loop. -Programming with restartable blocks isn’t easy, but when speed is an +Programming with restartable blocks isn't easy, but when speed is an issue, using restartable blocks in the right places more than pays for itself with greatly improved performance. And when speed is *not* an -issue, of course, or in code that’s not time-critical, you wouldn’t +issue, of course, or in code that's not time-critical, you wouldn't dream of wasting your time on optimization. Would you? diff --git a/06-01.md b/06-01.md index 8e74621..c784e97 100644 --- a/06-01.md +++ b/06-01.md @@ -8,7 +8,7 @@ Chapter 6\ ### How Machine Instructions May Do More Than You Think {#Heading2} -I first met Jeff Duntemann at an authors’ dinner hosted by *PC Tech +I first met Jeff Duntemann at an authors' dinner hosted by *PC Tech Journal* at Fall Comdex, back in 1985. Jeff was already reasonably well-known as a computer editor and writer, although not as famous as *Complete Turbo Pascal*, editions 1 through 672 (or thereabouts), *TURBO @@ -22,7 +22,7 @@ time, I nonetheless harbored vague ambitions of being a science-fiction writer when I grew up. (I have since realized that this hardly puts me in elite company, especially in the computer world, where it seems that every other person has told me they plan to write science fiction -“someday.” Given that probably fewer than 500—I’m guessing here—original +"someday." Given that probably fewer than 500—I'm guessing here—original science fiction and fantasy short stories, and perhaps a few more novels than that, are published each year in this country, I see a few mid-life crises coming.) @@ -31,10 +31,10 @@ At any rate, I had accumulated a small collection of rejection slips, and fancied myself something of an old hand in the field. At the end of the dinner, as the other writers complained half-seriously about how little they were paid for writing for *Tech Journal*, I leaned over to -Jeff and whispered, “You know, the pay isn’t so bad here. You should see -what they pay for science fiction—even to the guys who win awards!” +Jeff and whispered, "You know, the pay isn't so bad here. You should see +what they pay for science fiction—even to the guys who win awards!" -To which Jeff replied, “I know. I’ve been nominated for two Hugos.” +To which Jeff replied, "I know. I've been nominated for two Hugos." Oh. @@ -45,22 +45,22 @@ put on by a computer magazine, seated next to an editor who had just finished a book about Turbo Pascal, and, gosh, it was *obvious* that the appropriate topic was computers. -For once, the moral is *not* “don’t judge a book by its cover.” Jeff is +For once, the moral is *not* "don't judge a book by its cover." Jeff is in fact what he appeared to be at face value: a computer writer and -editor. However, he is more, too; face value wasn’t full value. You’ll -similarly find that face value isn’t always full value in computer +editor. However, he is more, too; face value wasn't full value. You'll +similarly find that face value isn't always full value in computer 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 -always the best instructions for those functions. So don’t judge a book +**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. -Assembly language for the x86 family isn’t like any other language (for +Assembly language for the x86 family isn't like any other language (for which we should, without hesitation, offer our profuse thanks). Assembly language reflects the design of the processor rather than the way we -think, so it’s full of multiple instructions that perform similar +think, so it's full of multiple instructions that perform similar functions, instructions with odd and often confusing side effects, and endless ways to string together different instructions to do much the same things, often with seemingly minuscule differences that can turn @@ -69,44 +69,44 @@ out to be surprisingly important. To produce the best code, you must decide precisely what you need to accomplish, then put together the sequence of instructions that accomplishes that end most efficiently, regardless of what the -instructions are usually used for. That’s why optimization for the PC is -an art, and it’s why the best assembly language for the x86 family will -almost always handily outperform compiled code. With that in mind, let’s -look past face value—and while we’re at it, I’ll toss in a few examples +instructions are usually used for. That's why optimization for the PC is +an art, and it's why the best assembly language for the x86 family will +almost always handily outperform compiled code. With that in mind, let's +look past face value—and while we're at it, I'll toss in a few examples 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. +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 +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 +about the various instructions, you're putting yourself at a substantial disadvantage every time you sit down to program. -In short, the x86 family can do much more than you think—if you’ll use +In short, the x86 family can do much more than you think—if you'll use everything it has to offer. Give it a shot! #### Memory Addressing and Arithmetic {#Heading3} Years ago, I saw a clip on the David Letterman show in which Letterman -walked into a store by the name of “Just Lamps” and asked, “So what do -you sell here?” +walked into a store by the name of "Just Lamps" and asked, "So what do +you sell here?" -“Lamps,” he was told. “Just lamps. Can’t you read?” +"Lamps," he was told. "Just lamps. Can't you read?" -“Lamps,” he said. “I see. And what else?” +"Lamps," he said. "I see. And what else?" From that bit of sublime idiocy we can learn much about divining the full value of an instruction. To wit: -Quick, what do the x86’s memory addressing modes do? +Quick, what do the x86's memory addressing modes do? -“Calculate memory addresses,” you no doubt replied. And you’re right, of +"Calculate memory addresses," you no doubt replied. And you're right, of course. But what *else* do they do? -They perform arithmetic, that’s what they do, and that’s a distinctly +They perform arithmetic, that's what they do, and that's a distinctly different and often useful perspective on memory address calculations. For example, suppose you have an array base address in BX and an index diff --git a/06-02.md b/06-02.md index 6b18975..ff23afc 100644 --- a/06-02.md +++ b/06-02.md @@ -4,10 +4,10 @@ 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 -within a loop, however, it’s advantageous on the 8088 CPU to perform 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 +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: @@ -24,22 +24,22 @@ 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 -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 +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 memory address calculations without taking a performance hit. The 486 is an odd case, in which the use of an index register or the use -of a base register that’s the destination of the previous instruction +of a base register that's the destination of the previous instruction may slow things down, so it is generally but not always better to perform the addition outside the loop on the 486. All memory addressing -calculations are free on the Pentium, however. I’ll discuss 486 +calculations are free on the Pentium, however. I'll discuss 486 performance issues in Chapters 12 and 13, and the Pentium in Chapters 19 through 21. ### Math via Memory Addressing {#Heading4} -You’re probably not particularly wowed to hear that you can use +You're probably not particularly wowed to hear that you can use addressing modes to perform memory addressing arithmetic that would otherwise have to be performed with separate arithmetic instructions. You may, however, be a tad more interested to hear that you can also use @@ -50,14 +50,14 @@ instructions, at that. How? With **LEA**, the only instruction that performs memory addressing -calculations but doesn’t actually address memory. **LEA** accepts a +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 Figure 6.1, which also shows the operation of register-to-register **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. @@ -70,7 +70,7 @@ the result in AX. The obvious solution is this: add ax,2 (It would be more compact to increment AX twice than to add two to it, -and would probably be faster on an 8088, but that’s not what we’re after +and would probably be faster on an 8088, but that's not what we're after at the moment.) An elegant alternative solution is simply: lea ax,[bx+di+2] @@ -85,7 +85,7 @@ 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 +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 @@ -98,16 +98,16 @@ and Pentium, **LEA** can also be slowed down by addressing interlocks. #### 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.) +the same modes as the 386, so I'll refer only to the 386 from now on.) The 386 can do two very interesting things: It can use *any* 32-bit register (EAX, EBX, and so on) as the memory addressing base register and/or the memory addressing index register, and it can multiply any 32-bit register used as an index by two, four, or eight in the process -of calculating a memory address, as shown in Figure 6.2. Let’s see what -that’s good for. +of calculating a memory address, as shown in Figure 6.2. Let's see what +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 @@ -122,7 +122,7 @@ destination. But what else can **LEA** do on a 386, besides add? 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 +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 bits, so we can now add up to two 32-bit registers and a constant, *and* @@ -168,10 +168,10 @@ 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 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.* + ![](images/i.jpg) *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 +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 pointing out the complications of 486 instruction timings. diff --git a/07-01.md b/07-01.md index deac1db..fecdfa6 100644 --- a/07-01.md +++ b/07-01.md @@ -8,16 +8,16 @@ Chapter 7\ ### Optimizing Halfway between Algorithms and Cycle Counting {#Heading2} -You might not think it, but there’s much to learn about performance +You might not think it, but there's much to learn about performance programming from the Great Buffalo Sauna Fiasco. To wit: The scene is Buffalo, New York, in the dead of winter, with the snow piled several feet deep. Four college students, living in typical student housing, are frozen to the bone. The third floor of their house, -uninsulated and so cold that it’s uninhabitable, has an ancient +uninsulated and so cold that it's uninhabitable, has an ancient bathroom. One fabulously cold day, inspiration strikes: -“Hey—we could make that bathroom into a *sauna!*” +"Hey—we could make that bathroom into a *sauna!*" Pandemonium ensues. Someone rushes out and buys a gas heater, and at considerable risk to life and limb hooks it up to an abandoned but still @@ -29,7 +29,7 @@ benches along the sides of the bathroom. *Voila*—instant sauna! They crank up the gas heater, put the bucket of rocks in front of it, close the door, take off their clothes, and sit down to steam -themselves. Mind you, it’s not yet 50 degrees Fahrenheit in this room, +themselves. Mind you, it's not yet 50 degrees Fahrenheit in this room, but the gas heater is roaring. Surely warmer times await. Indeed they do. The temperature climbs to 55 degrees, then 60, then 63, @@ -66,51 +66,51 @@ program is worth bothering with only in the context of a good design. ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- So, drawing fortitude from the knowledge that our quest is a pure and -worthy one, let’s resume our exploration of assembly language +worthy one, let's resume our exploration of assembly language instructions with hidden talents and instructions with well-known -talents that are less than they appear to be. In the process, we’ll come +talents that are less than they appear to be. In the process, we'll come to see that there is another, very important optimization level between the algorithm/design level and the cycle-counting/individual instruction -level. I’ll call this middle level *local optimization;* it involves +level. I'll call this middle level *local optimization;* it involves focusing on optimizing sequences of instructions rather than individual instructions, all with an eye to implementing designs as efficiently as possible given the capabilities of the x86 family instruction set. -And yes, in case you’re wondering, the above story is indeed true. Was I -there? Let me put it this way: If I were, I’d never admit it! +And yes, in case you're wondering, the above story is indeed true. Was I +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 -and branches if CX doesn’t decrement to zero. It’s so beautifully suited +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 +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 +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 doesn’t, but in most situations they’re comparable.)* + ![](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 doesn't, but in most situations they're comparable.)* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -How can this be? Don’t ask me, ask Intel. On the 8088 and 80286, +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** -is generally a little faster still because it’s a byte shorter and so +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 -byte on even an uncached 386 generally isn’t significant. (Remember that +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 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** +**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 +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 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 about twice as slow on the 486!* + ![](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 about twice as slow on the 486!* ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------ --------------------------------- -------------------- diff --git a/07-02.md b/07-02.md index 072cb42..e2736cc 100644 --- a/07-02.md +++ b/07-02.md @@ -2,19 +2,19 @@ [Previous](07-01.html) [Table of Contents](index.html) [Next](07-03.html) ------------------------ --------------------------------- -------------------- -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: and cx,ofh ;Isolate the desired field - jcxz SkipLoop ;If field is 0, don’t bother + jcxz SkipLoop ;If field is 0, don't bother The **AND** instruction has already set the Zero flag, so this and cx,0fh ;Isolate the desired field - jz SkipLoop ;If field is 0, don’t bother + jz SkipLoop ;If field is 0, don't bother 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. +when the Zero flag isn't already set to reflect the status of CX. ### The Lessons of LOOP and JCXZ {#Heading4} @@ -24,17 +24,17 @@ faster than several instructions that together do the same thing. Second, that the relative merits of instructions and optimization rules 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 +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 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 -you’d do well to avoid them altogether in the 386/486 world. Reviewing +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 +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; +the cycle times in Intel's literature, is a good place to start; published cycle times are closer to actual execution times on the 386 and 486 than on the 8088, and are reasonably reliable indicators of the relative performance levels of x86 instructions. @@ -43,12 +43,12 @@ relative performance levels of x86 instructions. 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 +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 -select the best instructions fairly easily. What’s more, this is a task -at which compilers excel. What I’m saying is that you shouldn’t get too -caught up in counting cycles because that’s a small (albeit important) +select the best instructions fairly easily. What's more, this is a task +at which compilers excel. What I'm saying is that you shouldn't get too +caught up in counting cycles because that's a small (albeit important) part of the optimization picture, and not the area in which your greatest advantage lies. @@ -59,24 +59,24 @@ that of *local optimization;* that is, selecting the best *sequence* of instructions for a task. The key to local optimization is viewing the 80x86 instruction set as a set of building blocks, each with unique characteristics. Your job is to sequence those blocks so that they -perform well. It doesn’t matter what the instructions are intended to do +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 -optimization by cycle counting. It’s worth knowing, but once you’ve +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 -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 +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 +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 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 +checking whether it's zero. There may be ways to perform those tasks a little faster by selecting different instructions, but they can get only -so fast, and branching can’t even get all that fast. +so fast, and branching can't even get all that fast. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ![](images/i.jpg) *The trick, then, is not to find the fastest way to decrement a count and branch conditionally, but rather to figure out how to accomplish the same result without decrementing or branching as often. Remember the Kobiyashi Maru problem in* Star Trek*?The same principle applies here: Redefine the problem to one that offers better solutions.* @@ -87,18 +87,18 @@ 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 -search of the sample string for a period (‘.’) in 170 µs on a 20 MHz +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,** 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 +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 -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 +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.) ------------------------ --------------------------------- -------------------- diff --git a/07-03.md b/07-03.md index 41acb5d..f385064 100644 --- a/07-03.md +++ b/07-03.md @@ -14,21 +14,21 @@ .data ; Sample string to search through. SampleString labelbyte - db ‘This is a sample string of a long enough length ’ - db ‘so that raw searching speed can outweigh any ’ - db ‘extra set-up time that may be required.’,0 + db ‘This is a sample string of a long enough length ' + db ‘so that raw searching speed can outweigh any ' + db ‘extra set-up time that may be required.',0 SAMPLE_STRING_LENGTH equ $-SampleString ; User prompt. - Prompt db ‘Enter character to search for:$’ + Prompt db ‘Enter character to search for:$' ; Result status messages. ByteFoundMsg db 0dh,0ah - db ‘Specified byte found.’,0dh,0ah,‘$’ + db ‘Specified byte found.',0dh,0ah,‘$' ZeroByteFoundMsg db 0dh, 0ah - db ‘Zero byte encountered.’,0dh,0ah,‘$’ + db ‘Zero byte encountered.',0dh,0ah,‘$' NoByteFoundMsg db 0dh,0ah - db ‘Buffer exhausted with no match.’, 0dh, 0ah, ‘$’ + db ‘Buffer exhausted with no match.', 0dh, 0ah, ‘$' .code Startprocnear @@ -45,12 +45,12 @@ call SearchMaxLength ;search the buffer mov dx,offset ByteFoundMsg ;assume we found the byte jc PrintStatus ;we did find the byte - ;we didn’t find the byte, figure out + ;we didn't find the byte, figure out ;whether we found a zero byte or ;ran out of buffer mov dx,offset NoByteFoundMsg - ;assume we didn’t find a zero byte - jcxz PrintStatus ;we didn’t find a zero byte + ;assume we didn't find a zero byte + jcxz PrintStatus ;we didn't find a zero byte mov dx,offset ZeroByteFoundMsg ;we found a zero byte PrintStatus: mov ah,9 ;DOS print string function @@ -79,18 +79,18 @@ SearchMaxLengthLoop: lodsb ;get the next byte cmp al,ah ;is this the byte we want? - jz ByteFound ;yes, we’re done with success + jz ByteFound ;yes, we're done with success and al,al ;is this the terminating 0 byte? - jz ByteNotFound ;yes, we’re done with failure - loop SearchMaxLengthLoop ;it’s neither, so check the next + jz ByteNotFound ;yes, we're done with failure + loop SearchMaxLengthLoop ;it's neither, so check the next ;byte, if any ByteNotFound: - clc ;return “not found” status + clc ;return "not found" status ret ByteFound: dec si ;point back to the location at which ;we found the searched-for byte - stc ;return “found” status + stc ;return "found" status ret SearchMaxLengthendp end Start @@ -102,7 +102,7 @@ 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 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 +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 all the difference. diff --git a/07-04.md b/07-04.md index 4febf66..b10827e 100644 --- a/07-04.md +++ b/07-04.md @@ -13,21 +13,21 @@ .data ; Sample string to search through. SampleStringlabelbyte - db ‘This is a sample string of a long enough length ’ - db ‘so that raw searching speed can outweigh any ’ - db ‘extra set-up time that may be required.’,0 + db ‘This is a sample string of a long enough length ' + db ‘so that raw searching speed can outweigh any ' + db ‘extra set-up time that may be required.',0 SAMPLE_STRING_LENGTH equ $-SampleString ; User prompt. - Prompt db ‘Enter character to search for:$’ + Prompt db ‘Enter character to search for:$' ; Result status messages. ByteFoundMsg db 0dh,0ah - db ‘Specified byte found.’,0dh,0ah,‘$’ + db ‘Specified byte found.',0dh,0ah,‘$' ZeroByteFoundMsg db 0dh,0ah - db ‘Zero byte encountered.’, 0dh, 0ah, ‘$’ + db ‘Zero byte encountered.', 0dh, 0ah, ‘$' NoByteFoundMsg db 0dh,0ah - db ‘Buffer exhausted with no match.’, 0dh, 0ah, ‘$’ + db ‘Buffer exhausted with no match.', 0dh, 0ah, ‘$' ; Table of initial, possibly partial loop entry points for ; SearchMaxLength. @@ -52,12 +52,12 @@ call SearchMaxLength ;search the buffer mov dx,offset ByteFoundMsg ;assume we found the byte jc PrintStatus ;we did find the byte - ;we didn’t find the byte, figure out + ;we didn't find the byte, figure out ;whether we found a zero byte or ;ran out of buffer mov dx,offset NoByteFoundMsg - ;assume we didn’t find a zero byte - jcxz PrintStatus ;we didn’t find a zero byte + ;assume we didn't find a zero byte + jcxz PrintStatus ;we didn't find a zero byte mov dx,offset ZeroByteFoundMsg ;we found a zero byte PrintStatus: mov ah,9 ;DOS print string function @@ -99,36 +99,36 @@ SearchMaxLengthEntry4: lodsb ;get the next byte cmp al,ah ;is this the byte we want? - jz ByteFound ;yes, we’re done with success + jz ByteFound ;yes, we're done with success and al,al ;is this the terminating 0 byte? - jz ByteNotFound ;yes, we’re done with failure + jz ByteNotFound ;yes, we're done with failure SearchMaxLengthEntry3: lodsb ;get the next byte cmp al,ah ;is this the byte we want? - jz ByteFound ;yes, we’re done with success + jz ByteFound ;yes, we're done with success and al,al ;is this the terminating 0 byte? - jz ByteNotFound ;yes, we’re done with failure + jz ByteNotFound ;yes, we're done with failure SearchMaxLengthEntry2: lodsb ;get the next byte cmp al,ah ;is this the byte we want? - jz ByteFound ;yes, we’re done with success + jz ByteFound ;yes, we're done with success and al,al ;is this the terminating 0 byte? - jz ByteNotFound ;yes, we’re done with failure + jz ByteNotFound ;yes, we're done with failure SearchMaxLengthEntry1: lodsb ;get the next byte cmp al,ah ;is this the byte we want? - jz ByteFound ;yes, we’re done with success + jz ByteFound ;yes, we're done with success and al,al ;is this the terminating 0 byte? - jz ByteNotFound ;yes, we’re done with failure - loop SearchMaxLengthLoop ;it’s neither, so check the next + jz ByteNotFound ;yes, we're done with failure + loop SearchMaxLengthLoop ;it's neither, so check the next ; four bytes, if any ByteNotFound: - clc ;return “not found” status + clc ;return "not found" status ret ByteFound: dec si ;point back to the location at which ; we found the searched-for byte - stc ;return “found” status + stc ;return "found" status ret SearchMaxLengthendp end Start @@ -136,11 +136,11 @@ 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; -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 +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 that algorithmic improvements can produce, but it can get you a critical -50 percent or 100 percent improvement when you’ve exhausted all other +50 percent or 100 percent improvement when you've exhausted all other avenues. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/07-05.md b/07-05.md index 16577e1..6a17efc 100644 --- a/07-05.md +++ b/07-05.md @@ -5,7 +5,7 @@ #### Rotating and Shifting with Tables {#Heading8} As another example of local optimization, consider the matter of -rotating or shifting a mask into position. First, let’s look at the +rotating or shifting a mask into position. First, let's look at the simple task of setting bit N of AX to 1. The obvious way to do this is to place N in CL, rotate the bit into @@ -18,7 +18,7 @@ position, and OR it with AX, as follows: This solution is obvious because it takes good advantage of the special ability of the x86 family to shift or rotate by the variable number of bits specified by CL. However, it takes an average of about 45 cycles on -an 8088. It’s actually far faster to precalculate the results, pass the +an 8088. It's actually far faster to precalculate the results, pass the bit number in BX, and look the shifted bit up, as shown in Listing 7.3. **LISTING 7.3 L7-3.ASM** @@ -40,7 +40,7 @@ instructions, but by selecting the fastest *sequence* of instructions. In the particular example above, we once again run into the difficulty of optimizing across the x86 family. The table lookup is faster on the -8088 and 286, but it’s slightly slower on the 386 and no faster on the +8088 and 286, but it's slightly slower on the 386 and no faster on the 486. However, 386/486-specific code could use enhanced addressing to accomplish the whole job in just one instruction, along the lines of the code snippet in Listing 7.4. @@ -63,34 +63,34 @@ code snippet in Listing 7.4. #### 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 -has a minor but interesting talent: It doesn’t affect the flags. That +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, every other arithmetic and logical instruction sets the flags; why not -**NOT**? Probably because **NOT** isn’t considered to be an arithmetic -or logical instruction at all; rather, it’s a data manipulation +**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 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 +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.* + ![](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.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ The x86 instruction set offers many ways to accomplish almost any task. Understanding the subtle distinctions between the instructions—whether -and which flags are set, for example—can be critical when you’re trying -to optimize a code sequence and you’re running out of registers, or when -you’re trying to minimize branching. +and which flags are set, for example—can be critical when you're trying +to optimize a code sequence and you're running out of registers, or when +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 +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 @@ -130,15 +130,15 @@ additions, with code along the lines shown in Listing 7.6. SAHF ;restore the carry flag LOOP LOOP_TOP -It’s not that the Listing 7.6 approach is necessarily better or worse; +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 +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 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 @@ -149,12 +149,12 @@ like **SUB** and **AND,** which do alter the flags). ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 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. +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 to and from AH, respectively. These instructions were created to help -provide compatibility with the 8080’s (that’s *8080*, not *8088*) +provide compatibility with the 8080's (that's *8080*, not *8088*) **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, @@ -163,13 +163,13 @@ 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 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 INC AX ADC DX,0 -does *not* increment the 32-bit value in DX:AX. To do that, you’d need +does *not* increment the 32-bit value in DX:AX. To do that, you'd need the following: ADD AX,1 diff --git a/08-01.md b/08-01.md index 3a52c4c..9004f52 100644 --- a/08-01.md +++ b/08-01.md @@ -6,41 +6,41 @@ Chapter 8\ Speeding Up C with Assembly Language {#Heading1} ------------------------------------- -### Jumping Languages When You Know It’ll Help {#Heading2} +### Jumping Languages When You Know It'll Help {#Heading2} -When I was a senior in high school, a pop song called “Seasons in the -Sun,” sung by one Terry Jacks, soared up the pop charts and spent, as -best I can recall, two straight weeks atop *Kasey Kasem’s American Top -40.* “Seasons in the Sun” wasn’t a particularly good song, primarily -because the lyrics were silly. I’ve never understood why the song was a +When I was a senior in high school, a pop song called "Seasons in the +Sun," sung by one Terry Jacks, soared up the pop charts and spent, as +best I can recall, two straight weeks atop *Kasey Kasem's American Top +40.* "Seasons in the Sun" wasn't a particularly good song, primarily +because the lyrics were silly. I've never understood why the song was a hit, but, as so often happens with undistinguished but popular music by -forgotten one- or two-shot groups (“Don’t Pull Your Love Out on Me -Baby,” “Billy Don’t Be a Hero,” *et al.*), I heard it everywhere for a +forgotten one- or two-shot groups ("Don't Pull Your Love Out on Me +Baby," "Billy Don't Be a Hero," *et al.*), I heard it everywhere for a month or so, then gave it not another thought for 15 years. Recently, though, I came across a review of a Rhino Records collection of obscure 1970s pop hits. Knowing that Jeff Duntemann is an aficionado of such esoterica (who do *you* know who owns an album by The Peppermint Trolley Company?), I sent the review to him. He was amused by it and, as -we kicked the names of old songs around, “Seasons in the Sun” came up. I -expressed my wonderment that a song that really wasn’t very good was +we kicked the names of old songs around, "Seasons in the Sun" came up. I +expressed my wonderment that a song that really wasn't very good was such a big hit. -“Well,” said Jeff, “I think it suffered in the translation from the -French.” +"Well," said Jeff, "I think it suffered in the translation from the +French." Ah-ha! Mystery solved. Apparently everyone but me knew that it was translated from French, and that novelty undoubtedly made the song a big hit. The translation was also surely responsible for the sappy lyrics; dollars to donuts that the original French lyrics were stronger. -Which brings us without missing a beat to this chapter’s theme, speeding +Which brings us without missing a beat to this chapter's theme, speeding up C with assembly language. When you seek to speed up a C program by converting selected parts of it (generally no more than a few functions) to assembly language, make sure you end up with high-performance assembly language code, not fine-tuned C code. Compilers like Microsoft C/C++ and Watcom C are by now pretty good at fine-tuning C code, and -you’re not likely to do much better by taking the compiler’s assembly +you're not likely to do much better by taking the compiler's assembly language output and tweaking it. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -49,29 +49,29 @@ language output and tweaking it. Apropos of which, when was the last time you heard of Terry Jacks? -#### Billy, Don’t Be a Compiler {#Heading3} +#### Billy, Don't Be a Compiler {#Heading3} The key to optimizing C programs with assembly language is, as always, writing good assembly language code, but with an added twist. Rule 1 -when converting C code to assembly is this: *Don’t think like a -compiler.* That’s more easily said than done, especially when the C code -you’re converting is readily available as a model and the assembly code +when converting C code to assembly is this: *Don't think like a +compiler.* That's more easily said than done, especially when the C code +you're converting is readily available as a model and the assembly code that the compiler generates is available as well. Nevertheless, the principle of not thinking like a compiler is essential, and is, in one -form or another, the basis for all that I’ll discuss below. +form or another, the basis for all that I'll discuss below. Before I discuss Rule 1 further, let me mention rule number 0: *Only optimize where it matters.* The bulk of execution time in any program is spent in a very small portion of the code, and most code beyond that -small portion doesn’t have any perceptible impact on performance. Unless -you’re supremely concerned with code size (an area in which -assembly-only programs can excel), I’d suggest that you write most of +small portion doesn't have any perceptible impact on performance. Unless +you're supremely concerned with code size (an area in which +assembly-only programs can excel), I'd suggest that you write most of your code in C and reserve assembly for the truly critical sections of -your code; that’s the formula that I find gives the most bang for the +your code; that's the formula that I find gives the most bang for the buck. -This is not to say that complete programs shouldn’t be *designed* with -optimized assembly language in mind. As you’ll see shortly, orienting +This is not to say that complete programs shouldn't be *designed* with +optimized assembly language in mind. As you'll see shortly, orienting your data structures towards assembly language can be a salubrious endeavor indeed, even if most of your code is in C. When it comes to actually optimizing code and/or converting it to assembly, though, do it @@ -85,7 +85,7 @@ assembly language optimization. ![](images/i.jpg) *Assembly language optimization is the final and far from the only step in the optimization chain, and as such should be performed last; converting to assembly too soon can lock in your code before the design is optimal. At the very least, conversion to assembly tends to make future changes and debugging more difficult, slowing you down and limiting your options.* ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -### Don’t Call Your Functions on Me, Baby {#Heading4} +### Don't Call Your Functions on Me, Baby {#Heading4} In order to think differently from a compiler, you must understand both what compilers and C programmers tend to do and how that differs from @@ -96,24 +96,24 @@ output file. (The latter is done with /Fa or /Fc in Microsoft C/C++ and -S in Borland C++.) C programmers tend to modularize their code with lots of function calls. -That’s good for readable, reliable, reusable code, and it allows the +That's good for readable, reliable, reusable code, and it allows the compiler to optimize better because it can deal with fewer variables and -statements in each optimization arena—but it’s not so good when viewed +statements in each optimization arena—but it's not so good when viewed from the assembly language level. Calls and returns are slow, especially in the large code model, and the pushes required to put parameters on the stack are expensive as well. What this means is that when you want to speed up a portion of a C program, you should identify the entire critical portion and move *all* -of that critical portion into an assembly language function. You don’t +of that critical portion into an assembly language function. You don't want to move a part of the inner loop into assembly language and then call it from C every time through the loop; the function call and return overhead would be unacceptable. Carve out the critical code *en masse* and move it into assembly, and try to avoid calls and returns even in your assembly code. True, in assembly you can pass parameters in registers, but the calls and returns themselves are still slow; if the -extra cycles they take don’t affect performance, then the code they’re -in probably isn’t critical, and perhaps you’ve chosen to convert too +extra cycles they take don't affect performance, then the code they're +in probably isn't critical, and perhaps you've chosen to convert too much code to assembly, eh? ------------------------ --------------------------------- -------------------- diff --git a/08-02.md b/08-02.md index f4bc8de..dabb075 100644 --- a/08-02.md +++ b/08-02.md @@ -7,9 +7,9 @@ C compilers work within the stack frame model, whereby variables reside in a block of stack memory and are accessed via offsets from BP. Compilers may store a couple of variables in registers and may briefly -keep other variables in registers when they’re used repeatedly, but the -stack frame is the underlying architecture. It’s a nice architecture; -it’s flexible, convenient, easy to program, and makes for fairly compact +keep other variables in registers when they're used repeatedly, but the +stack frame is the underlying architecture. It's a nice architecture; +it's flexible, convenient, easy to program, and makes for fairly compact code. However, stack frames have a few drawbacks. They must be constructed and destroyed, which takes both time and code. They are so easy to use that they tend to bias the assembly language programmer in @@ -18,15 +18,15 @@ Finally, you cannot use BP as a general-purpose register if you intend to access a stack frame, and having that seventh register available is sometimes useful indeed. -That doesn’t mean you shouldn’t use stack frames, which are useful and -often necessary. Just don’t fall victim to their undeniable charms. +That doesn't mean you shouldn't use stack frames, which are useful and +often necessary. Just don't fall victim to their undeniable charms. ### Torn Between Two Segments {#Heading6} C compilers are not terrific at handling segments. Some compilers can efficiently handle a single far pointer used in a loop by leaving ES set for the duration of the loop. But two far pointers used in the same loop -confuse every compiler I’ve seen, causing the full segment:offset +confuse every compiler I've seen, causing the full segment:offset address to be reloaded each time either pointer is used. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -41,15 +41,15 @@ if necessary, reorganize your code to minimize segment loading. You might think that the most obvious advantage assembly language has 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 +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 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. +much better than equivalent code you'd produce yourself. -Am I saying that C compilers produce better code than you do? No, I’m +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. Writing code in assembly language rather than C guarantees nothing. @@ -59,7 +59,7 @@ Writing code in assembly language rather than C guarantees nothing. Sure, you can probably use the registers more efficiently and take advantage of an instruction or two that the compiler missed, but the -code isn’t going to get a whole lot faster that way. +code isn't going to get a whole lot faster that way. True optimization requires rethinking your code to take advantage of assembly language. A C loop that searches through an integer array for @@ -71,19 +71,19 @@ matches might compile 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 -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 +Congratulations! You've successfully eliminated all stack frame access, +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 -deal. You’ve eliminated the trappings of the compiler—the stack frame -and the restricted register usage—but you’re still *thinking* like the +deal. You've eliminated the trappings of the compiler—the stack frame +and the restricted register usage—but you're still *thinking* like the compiler. Try this: repnz scasw jz Match -It’s a simple example—but, I hope, a convincing one. Stretch your brain +It's a simple example—but, I hope, a convincing one. Stretch your brain when you optimize. ### Taking It to the Limit {#Heading8} @@ -94,16 +94,16 @@ 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 -through wouldn’t be contiguous. +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 +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. ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Organizing a program’s data so that the performance of the critical sections can be optimized is a key part of design, and one that’s easily shortchanged unless, during the design stage, you thoroughly understand and work to bring together your data needs, the critical sections of your program, and potential assembly language optimizations.* + ![](images/i.jpg) *Organizing a program's data so that the performance of the critical sections can be optimized is a key part of design, and one that's easily shortchanged unless, during the design stage, you thoroughly understand and work to bring together your data needs, the critical sections of your program, and potential assembly language optimizations.* ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- More on this shortly. @@ -113,7 +113,7 @@ code into optimized assembly language: - Move the entire performance-critical section into a single assembly language function. -- Don’t use calls or stack frame accesses inside the critical code, if +- Don't use calls or stack frame accesses inside the critical code, if possible, and avoid unnecessary memory accesses of any kind. - Change segments as infrequently as possible. - Optimize in terms of what assembly does well, *not* in terms of @@ -126,9 +126,9 @@ That said, let me show some of these precepts in action. #### A C-to-Assembly Case Study {#Heading9} -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 +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 optimization example. diff --git a/08-03.md b/08-03.md index 1b4ed99..4da111f 100644 --- a/08-03.md +++ b/08-03.md @@ -29,7 +29,7 @@ in this variable-sized block */ }; - /* Structure that contains one element of the array we’ll search */ + /* Structure that contains one element of the array we'll search */ struct DataElement { unsigned int ID; /* ID # for array entry */ unsigned int Value; /* Value of array entry */ @@ -42,8 +42,8 @@ struct DataElement *WorkingDataPointer; struct BlockHeader **LastBlockPointer; - printf(”ID # for which to find average: “); - scanf(”%d”,&IDToFind); + printf("ID # for which to find average: "); + scanf("%d",&IDToFind); /* Build an array across 5 blocks, for testing */ /* Anchor the linked list to BaseArrayBlockPointer */ LastBlockPointer = &BaseArrayBlockPointer; @@ -71,10 +71,10 @@ /* Remember where to set link from this block to the next */ LastBlockPointer = &WorkingBlockPointer->NextBlock; } - /* Set the last block’s “next block” pointer to NULL to indicate + /* Set the last block's "next block" pointer to NULL to indicate that there are no more blocks */ WorkingBlockPointer->NextBlock = NULL; - printf(”Average of all elements with ID %d: %u\n”, + printf("Average of all elements with ID %d: %u\n", IDToFind, FindIDAverage(IDToFind, BaseArrayBlockPointer)); exit(0); } @@ -115,7 +115,7 @@ } } /* Point to the next block, and continue as long as that pointer - isn’t NULL */ + isn't NULL */ } while ((BlockPointer = BlockPointer->NextBlock) != NULL); /* Calculate the average of all matches */ if (IDMatchCount == 0) diff --git a/08-04.md b/08-04.md index 036f3d9..be2e277 100644 --- a/08-04.md +++ b/08-04.md @@ -2,17 +2,17 @@ [Previous](08-03.html) [Table of Contents](index.html) [Next](08-05.html) ------------------------ --------------------------------- -------------------- -It’s hard to squeeze much more performance from this code by tweaking +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 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 +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 modest 11 percent faster than Listing 8.1 on a 386. The results could vary considerably, depending on the nature of the data set searched through (average block size and frequency of matches). But, then, understanding the typical and worst case conditions is part of -optimization, isn’t it? +optimization, isn't it? **LISTING 8.3 L8-3.ASM** @@ -77,14 +77,14 @@ Table 8.1 Execution Times of FindIDAverage. * * * * * _FindIDAverage proc near - push bp ;Save caller’s stack frame + push bp ;Save caller's stack frame mov bp,sp ;Point to our stack frame push di ;Preserve C register variables push si sub dx,dx ;IDMatchSum = 0 mov bx,dx ;IDMatchCount = 0 mov si,[bp+BlockPointer] ;Pointer to first block - mov ax,[bp+SearchedForID] ;ID we’re looking for + mov ax,[bp+SearchedForID] ;ID we're looking for ; Search through all the linked blocks until the last block ; (marked with a NULL pointer to the next block) has been searched. BlockLoop: @@ -102,7 +102,7 @@ Table 8.1 Execution Times of FindIDAverage. NoMatch: add di,DATA_ELEMENT_SIZE ;point to the next element loop IntraBlockLoop - ; Point to the next block and continue if that pointer isn’t NULL. + ; Point to the next block and continue if that pointer isn't NULL. DoNextBlock: mov si,[si+NextBlock] ;Get pointer to the next block and si,si ;Is it a NULL pointer? @@ -110,12 +110,12 @@ Table 8.1 Execution Times of FindIDAverage. ; Calculate the average of all matches. sub ax,ax ;Assume we found no matches and bx,bx - jz Done ;We didn’t find any matches, return 0 + jz Done ;We didn't find any matches, return 0 xchg ax,dx ;Prepare for division div bx ;Return IDMatchSum / IDMatchCount Done: pop si ;Restore C register variables pop di - pop bp ;Restore caller’s stack frame + pop bp ;Restore caller's stack frame ret _FindIDAverage ENDP end @@ -125,7 +125,7 @@ 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 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 +it's shorter and therefore may prefetch faster.) This advanced tweaking produces a 39 percent improvement over the original C code—substantial, but not a tremendous return for the optimization effort invested. @@ -146,7 +146,7 @@ but not a tremendous return for the optimization effort invested. .code public _FindIDAverage _FindIDAverage proc near - push bp ;Save caller’s stack frame + push bp ;Save caller's stack frame mov bp,sp ;Point to our stack frame push di ;Preserve C register variables push si @@ -156,7 +156,7 @@ but not a tremendous return for the optimization effort invested. sub dx,dx ;IDMatchSum = 0 mov bx,dx ;IDMatchCount = 0 mov si,[bp+BlockPointer] ;Pointer to first block - mov ax,[bp+SearchedForID] ;ID we’re looking for + mov ax,[bp+SearchedForID] ;ID we're looking for ; Search through all of the linked blocks until the last block ; (marked with a NULL pointer to the next block) has been searched. BlockLoop: @@ -165,7 +165,7 @@ but not a tremendous return for the optimization effort invested. ; Search through all the DataElement entries within this block ; and accumulate data from all that match the desired ID. mov cx,[si+BlockCount] ;Number of elements in this block - jcxz DoNextBlock ;Skip this block if it’s empty + jcxz DoNextBlock ;Skip this block if it's empty mov bp,cx ;***stack frame no longer available*** add cx,7 shr cx,1 ;Number of repetitions of the unrolled @@ -202,7 +202,7 @@ but not a tremendous return for the optimization effort invested. M_IBL 2 M_IBL 1 loop IntraBlockLoop - ; Point to the next block and continue if that pointer isn’t NULL. + ; Point to the next block and continue if that pointer isn't NULL. DoNextBlock: mov si,[si+NextBlock] ;Get pointer to the next block and si,si ;Is it a NULL pointer? @@ -210,12 +210,12 @@ but not a tremendous return for the optimization effort invested. ; Calculate the average of all matches. sub ax,ax ;Assume we found no matches and bx,bx - jz Done ;We didn’t find any matches, return 0 + jz Done ;We didn't find any matches, return 0 xchg ax,dx ;Prepare for division div bx ;Return IDMatchSum / IDMatchCount Done: pop si ;Restore C register variables pop di - pop bp ;Restore caller’s stack frame + pop bp ;Restore caller's stack frame ret _FindIDAverage ENDP end diff --git a/08-05.md b/08-05.md index b4d8cf6..e7b42f3 100644 --- a/08-05.md +++ b/08-05.md @@ -50,8 +50,8 @@ merely rearranged. int *WorkingDataPointer; struct BlockHeader **LastBlockPointer; - printf(”ID # for which to find average: “); - scanf(”%d”,&IDToFind); + printf("ID # for which to find average: "); + scanf("%d",&IDToFind); /* Build an array across 5 blocks, for testing */ /* Anchor the linked list to BaseArrayBlockPointer */ @@ -79,10 +79,10 @@ merely rearranged. /* Remember where to set link from this block to the next */ LastBlockPointer = &WorkingBlockPointer->NextBlock; } - /* Set the last block’s “next block” pointer to NULL to indicate + /* Set the last block's "next block" pointer to NULL to indicate that there are no more blocks */ WorkingBlockPointer->NextBlock = NULL; - printf(”Average of all elements with ID %d: %u\n”, + printf("Average of all elements with ID %d: %u\n", IDToFind, FindIDAverage2(IDToFind, BaseArrayBlockPointer)); exit(0); } @@ -105,7 +105,7 @@ merely rearranged. .code public _FindIDAverage2 _FindIDAverage2 proc near - push bp ;Save caller’s stack frame + push bp ;Save caller's stack frame mov bp,sp ;Point to our stack frame push di ;Preserve C register variables push si @@ -113,7 +113,7 @@ merely rearranged. mov es,di cld mov si,[bp+BlockPointer] ;Pointer to first block - mov ax,[bp+SearchedForID] ;ID we’re looking for + mov ax,[bp+SearchedForID] ;ID we're looking for sub dx,dx ;IDMatchSum = 0 mov bp,dx ;IDMatchCount = 0 ;***stack frame no longer available*** @@ -123,9 +123,9 @@ merely rearranged. ; Search through all the DataElement entries within this block ; and accumulate data from all that match the desired ID. mov cx,[si+BlockCount] - jcxz DoNextBlock;Skip this block if there’s no data + jcxz DoNextBlock;Skip this block if there's no data ; to search through - mov bx,cx ;We’ll use BX to point to the + mov bx,cx ;We'll use BX to point to the shl bx,1 ; corresponding value entry in the ; case of an ID match (BX is the ; length in bytes of the ID array) @@ -139,7 +139,7 @@ merely rearranged. ; (SCASW has advanced DI 2 bytes) and cx,cx ;Is there more data to search through? jnz IntraBlockLoop ;yes - ; Point to the next block and continue if that pointer isn’t NULL. + ; Point to the next block and continue if that pointer isn't NULL. DoNextBlock: mov si,[si+NextBlock] ;Get pointer to the next block and si,si ;Is it a NULL pointer? @@ -147,18 +147,18 @@ merely rearranged. ; Calculate the average of all matches. sub ax,ax ;Assume we found no matches and bp,bp - jz Done ;We didn’t find any matches, return 0 + jz Done ;We didn't find any matches, return 0 xchg ax,dx ;Prepare for division div bp ;Return IDMatchSum / IDMatchCount Done: pop si ;Restore C register variables pop di - pop bp ;Restore caller’s stack frame + pop bp ;Restore caller's stack frame ret _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 +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 @@ -167,7 +167,7 @@ optimized as the latter code is. I trust you get the picture. The sort of instruction-by-instruction optimization that so many of us love to do as a kind of puzzle is fun, but compilers can do it nearly as well as you can, and in the future -will surely do it better. What a compiler *can’t* do is tie together the +will surely do it better. What a compiler *can't* do is tie together the needs of the program specification on the high end and the processor on the low end, resulting in critical code that runs just about as fast as the hardware permits. The only software that can do that is located diff --git a/09-01.md b/09-01.md index 6590d0d..771fbdc 100644 --- a/09-01.md +++ b/09-01.md @@ -19,46 +19,46 @@ mantle of the Earth and possibly all the way through to China. Many amusing cartoons were drawn to this effect. Unfortunately, Barry was too busy drawing cartoons, or, alternatively, -sleeping, to actually learn any math. In the long run, that didn’t turn +sleeping, to actually learn any math. In the long run, that didn't turn out to be a handicap for Barry, who went on to become vice-president of sales for a ham-packing company, where presumably he was rarely called -upon to derive the quadratic equation. Barry’s lack of scholarship +upon to derive the quadratic equation. Barry's lack of scholarship caused some problems back then, though. On one memorable occasion, Barry was half-asleep, with his eyes open but unfocused and his chin balanced -on his hand in the classic “if I fall asleep my head will fall off my -hand and I’ll wake up” posture, when Mr. Bourgeis popped a killer +on his hand in the classic "if I fall asleep my head will fall off my +hand and I'll wake up" posture, when Mr. Bourgeis popped a killer problem: -“Barry, solve this for X, please.” On the blackboard lay the equation: +"Barry, solve this for X, please." On the blackboard lay the equation: X - 1 = 0 -“Minus 1,” Barry said promptly. +"Minus 1," Barry said promptly. -Mr. Bourgeis shook his head mournfully. “Try again.” Barry thought hard. +Mr. Bourgeis shook his head mournfully. "Try again." Barry thought hard. He knew the fundamental rule that the answer to most mathematical questions is either 0, 1, infinity, -1, or minus infinity (do not apply this rule to balancing your checkbook, however); unfortunately, that gave him only a 25 percent chance of guessing right. -“One,” I whispered surreptitiously. +"One," I whispered surreptitiously. -“Zero,” Barry announced. Mr. Bourgeis shook his head even more sadly. +"Zero," Barry announced. Mr. Bourgeis shook his head even more sadly. -“One,” I whispered louder. Barry looked still more thoughtful—a bad -sign—so I whispered “one” again, even louder. Barry looked so thoughtful +"One," I whispered louder. Barry looked still more thoughtful—a bad +sign—so I whispered "one" again, even louder. Barry looked so thoughtful that his eyes nearly rolled up into his head, and I realized that he was just doing his best to convince Mr. Bourgeis that Barry had solved this one by himself. As Barry neared the climax of his stirring performance and opened his -mouth to speak, Mr. Bourgeis looked at him with great concern. “Barry, -can you hear me all right?” +mouth to speak, Mr. Bourgeis looked at him with great concern. "Barry, +can you hear me all right?" -“Yes, sir,” Barry replied. “Why?” +"Yes, sir," Barry replied. "Why?" -“Well, I could hear the answer all the way up here. Surely you could -hear it just one row away?” +"Well, I could hear the answer all the way up here. Surely you could +hear it just one row away?" The class went wild. They might as well have sent us home early for all we accomplished the rest of the day. @@ -66,15 +66,15 @@ we accomplished the rest of the day. I like to think I know more about performance programming than Barry knew about math. Nonetheless, I always welcome good ideas and comments, and many readers have sent me a slew of those over the years. So in this -chapter, I think I’ll return the favor by devoting a chapter to reader +chapter, I think I'll return the favor by devoting a chapter to reader feedback. #### Another Look at LEA {#Heading3} 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 -instruction by trade, doesn’t affect the flags, while the arithmetic +performing certain additions (see Chapter 6), it isn't a perfect +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 performing additions that involve only quantities that fit in one machine word (32 bits in 386 protected mode, 16 bits otherwise), but it @@ -89,7 +89,7 @@ could *not* be replaced 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 performing pointer arithmetic, however. For instance, the following code @@ -103,43 +103,43 @@ variable to another such variable: MOV EAX,[ESI] ;get the next element of one array ADC [EDI],EAX ;add it to the other array, with carry - LEA ESI,[ESI+4] ;advance one array’s pointer - LEA EDI,[EDI+4] ;advance the other array’s pointer + LEA ESI,[ESI+4] ;advance one array's pointer + LEA EDI,[EDI+4] ;advance the other array's pointer LOOP ADDLOOP -(Yes, I could use **LODSD** instead of **MOV/LEA**; I’m just +(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.) +multiple **INC**s, since **INC** doesn't affect the Carry flag.) -In short, **LEA** is indeed different from **ADD**. Sometimes it’s -better. Sometimes not; that’s the nature of the various instruction +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 -on what you’re trying to do. +There's no such thing as "best" instructions on the x86; it all depends +on what you're trying to do. -But there sure are a lot of interesting options, aren’t there? +But there sure are a lot of interesting options, aren't there? #### The Kennedy Portfolio {#Heading4} Reader John Kennedy regularly passes along intriguing assembly -programming tricks, many of which I’ve never seen mentioned anywhere +programming tricks, many of which I've never seen mentioned anywhere else. John likes to optimize for size, whereas I lean more toward speed, but many of his optimizations are good for both purposes. Here are a few of my favorites: -John’s code for setting AX to its absolute value is: +John's code for setting AX to its absolute value is: CWD XOR AX,DX SUB AX,DX This does nothing when bit 15 of AX is 0 (that is, if AX is positive). -When AX is negative, the code “nots” it and adds 1, which is exactly how -you perform a two’s complement negate. For the case where AX is not +When AX is negative, the code "nots" it and adds 1, which is exactly how +you perform a two's complement negate. For the case where AX is not negative, this trick usually beats the stuffing out of the standard absolute value code: @@ -148,11 +148,11 @@ absolute value code: NEG AX ;yes,negate it IsPositive: -However, John’s code is slower on a 486; as you’re no doubt coming to -realize (and as I’ll explain in Chapters 12 and 13), the 486 is an +However, John's code is slower on a 486; as you're no doubt coming to +realize (and as I'll explain in Chapters 12 and 13), the 486 is an optimization world unto itself. -Here’s how John copies a block of bytes from DS:SI to ES:DI, moving as +Here's how John copies a block of bytes from DS:SI to ES:DI, moving as much data as possible a word at a time: SHR CX,1 ;word count diff --git a/09-02.md b/09-02.md index 4736dd6..fe6b02a 100644 --- a/09-02.md +++ b/09-02.md @@ -2,15 +2,15 @@ [Previous](09-01.html) [Table of Contents](index.html) [Next](09-03.html) ------------------------ --------------------------------- -------------------- -However, it generally is. Sure, if the length is odd, John’s approach +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 +**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 +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**, +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 @@ -34,7 +34,7 @@ Using **LEA** on the 386, the above could be reduced to LEA EAX,[EAX*8] ;*16 LEA EAX,[EAX+EAX*4] ;*80 -which still isn’t as fast as using a lookup table like +which still isn't as fast as using a lookup table like MOV EAX,MultiplesOf80Table[EAX*4] @@ -55,7 +55,7 @@ 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 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 +(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 other operand. @@ -64,7 +64,7 @@ other operand. ![](images/i.jpg) *Why? Because the 386 processes one multiplier bit per cycle and immediately ends a multiplication when all significant bits of the multiplier have been processed, so fewer cycles are required to multiply a large multiplicand times a small multiplier than a small multiplicand times a large multiplier, by a factor of about 1 cycle for each significant multiplier bit eliminated.* ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -(There’s a minimum execution time on this trick; below 3 significant +(There's a minimum execution time on this trick; below 3 significant multiplier bits, no additional cycles are saved.) For example, multiplication of 32,767 times 1 is 12 cycles faster than multiplication of 1 times 32,727. @@ -82,7 +82,7 @@ This highlights another interesting point: **MUL** and **IMUL** on the 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 “optimized” alternatives.* + ![](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 "optimized" alternatives.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ All in all, **MUL** and **IMUL** are reasonable performers on the 386, @@ -90,10 +90,10 @@ 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. -That doesn’t mean that your code should test and swap operands to make -sure the smaller one is the multiplier; that rarely pays off. I’m -speaking more of the case where you’re scaling an array up by a value -that’s always in the range of, say, 2 to 10; because the scale value +That doesn't mean that your code should test and swap operands to make +sure the smaller one is the multiplier; that rarely pays off. I'm +speaking more of the case where you're scaling an array up by a value +that's always in the range of, say, 2 to 10; because the scale value will always be small and the array elements may have any value, the scale value is the logical choice for the multiplier. @@ -105,7 +105,7 @@ 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 -Figure 9.1. The principle is that most buffer characters won’t match the +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 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 @@ -114,7 +114,7 @@ entirety with **REPZ CMPS**. ![](images/09-01.jpg)\ **Figure 9.1**  *Simple searching method for locating a text string.* -Rob’s revelation, which he credits without explanation to Edgar Allen +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 @@ -124,11 +124,11 @@ SCASB** if no match is found. ![](images/i.jpg) *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 -“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” +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 +"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" is likely to occur much less often, resulting in many fewer whole-string checks and much faster processing. diff --git a/09-03.md b/09-03.md index c275c58..8c4a177 100644 --- a/09-03.md +++ b/09-03.md @@ -5,19 +5,19 @@ 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 +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 -directed by Listing 9.3 to scan for the infrequently-occurring “Q,” 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 -always scans for the first character, in this case “E.” However, when +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 *twice* as fast as Listing 9.1—a remarkable improvement over code that already uses **REPNZ SCASB** and **REPZ CMPS**. -What I like so much about Rob’s approach is that it demonstrates that +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 @@ -59,9 +59,9 @@ using **REPZ CMPS** to check scanning matches. .code public _FindString _FindStringprocnear - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to our stack frame - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di cld ;make string instructions increment pointers mov si,[bp+SearchString] ;pointer to string to search for @@ -80,12 +80,12 @@ using **REPZ CMPS** to check scanning matches. mov di,[bp+Buffer] ;point ES:DI to buffer to search thru lodsb ;put the first byte of the search string in AL mov bp,si ;set aside pointer to the second search byte - dec bx ;don’t need to compare the first byte of the - ; string with CMPS; we’ll do it with SCAS + dec bx ;don't need to compare the first byte of the + ; string with CMPS; we'll do it with SCAS FindStringLoop: mov cx,dx ;put remaining buffer search length in CX repnz scasb ;scan for the first byte of the string - jnz FindStringNotFound ;not found, so there’s no match + jnz FindStringNotFound ;not found, so there's no match ;found, so we have a potential match-check the ; rest of this candidate location push di ;remember the address of the next byte to scan @@ -96,14 +96,14 @@ using **REPZ CMPS** to check scanning matches. shr cx,1 ;convert to word for faster search jnc FindStringWord ;do word search if no odd byte cmpsb ;compare the odd byte - jnz FindStringNoMatch ;odd byte doesn’t match, so we - ; haven’t found the search string here + jnz FindStringNoMatch ;odd byte doesn't match, so we + ; haven't found the search string here FindStringWord: - jcxz FindStringFound ;test whether we’ve already checked + jcxz FindStringFound ;test whether we've already checked ; the whole string; if so, this is a match - ; bytes long; if so, we’ve found a match + ; bytes long; if so, we've found a match repz cmpsw ;check the rest of the string a word at a time - jz FindStringFound ;it’s a match + jz FindStringFound ;it's a match FindStringNoMatch: pop di ;get back pointer to the next byte to scan and dx,dx ;is there anything left to check? @@ -117,9 +117,9 @@ using **REPZ CMPS** to check scanning matches. ; address of the byte after the start of the ; potential match) FindStringDone: - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _FindStringendp end diff --git a/09-04.md b/09-04.md index 9ef92d8..bf20016 100644 --- a/09-04.md +++ b/09-04.md @@ -32,9 +32,9 @@ .code public _FindString _FindStringprocnear - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to our stack frame - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di cld ;make string instructions increment pointers mov si,[bp+SearchString] ;pointer to string to search for @@ -62,7 +62,7 @@ FindStringLoop: mov cx,dx ;put remaining buffer search length in CX repnz scasb ;scan for the scan byte - jnz FindStringNotFound ;not found, so there’s no match + jnz FindStringNotFound ;not found, so there's no match ;found, so we have a potential match-check the ; rest of this candidate location push di ;remember the address of the next byte to scan @@ -75,13 +75,13 @@ shr cx,1 ;convert to word for faster search jnc FindStringWord ;do word search if no odd byte cmpsb ;compare the odd byte - jnz FindStringNoMatch ;odd byte doesn’t match, so we - ; haven’t found the search string here + jnz FindStringNoMatch ;odd byte doesn't match, so we + ; haven't found the search string here FindStringWord: jcxz FindStringFound ;if the string is only 1 byte long, - ; we’ve found a match + ; we've found a match repz cmpsw ;check the rest of the string a word at a time - jz FindStringFound ;it’s a match + jz FindStringFound ;it's a match FindStringNoMatch: pop di ;get back pointer to the next byte to scan and dx,dx ;is there anything left to check? @@ -94,9 +94,9 @@ sub ax,bx ; string was found (earlier we pushed the ; address of the byte after the scan match) FindStringDone: - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _FindStringendp end @@ -111,26 +111,26 @@ extern unsigned char * FindString(unsigned char *, unsigned int, unsigned char *, unsigned int, unsigned int); void main(void); - static unsigned char TestBuffer[] = “When, in the course of human \ + static unsigned char TestBuffer[] = "When, in the course of human \ events, it becomes necessary for one people to dissolve the \ political bands which have connected them with another, and to \ assume among the powers of the earth the separate and equal station \ - to which the laws of nature and of nature’s God entitle them...”; + to which the laws of nature and of nature's God entitle them..."; void main() { - static unsigned char TestString[] = “equal”; + static unsigned char TestString[] = "equal"; unsigned char TempBuffer[DISPLAY_LENGTH+1]; unsigned char *MatchPtr; /* Search for TestString and report the results */ if ((MatchPtr = FindString(TestBuffer, (unsigned int) strlen(TestBuffer), TestString, (unsigned int) strlen(TestString), 1)) == NULL) { - /* TestString wasn’t found */ - printf(“\”%s\“ not found\n”, TestString); + /* TestString wasn't found */ + printf("\"%s\" not found\n", TestString); } else { /* TestString was found. Zero-terminate TempBuffer; strncpy - won’t do it if DISPLAY_LENGTH characters are copied */ + won't do it if DISPLAY_LENGTH characters are copied */ TempBuffer[DISPLAY_LENGTH] = 0; - printf(“\”%s\“ found. Next %d characters at match:\n\”%s\“\n”, + printf("\"%s\" found. Next %d characters at match:\n\"%s\"\n", TestString, DISPLAY_LENGTH, strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH)); } diff --git a/09-05.md b/09-05.md index 1650481..067a211 100644 --- a/09-05.md +++ b/09-05.md @@ -2,15 +2,15 @@ [Previous](09-04.html) [Table of Contents](index.html) [Next](09-06.html) ------------------------ --------------------------------- -------------------- -You’ll notice that in Listing 9.2 I didn’t use a table of character +You'll notice that in Listing 9.2 I didn't use a table of character frequencies in English text to determine the character for which to scan, but rather let the caller make that choice. Each buffer of bytes has unique characteristics, and English-letter frequency could well be inappropriate. What if the buffer is filled with French text? Cyrillic? -What if it isn’t text that’s being searched? It might be worthwhile for +What if it isn't text that's being searched? It might be worthwhile for an application to build a dynamic frequency table for each buffer so that the best scan character could be chosen for each search. Or perhaps -not, if the search isn’t time-critical or the buffer is small. +not, if the search isn't time-critical or the buffer is small. The point is that you can improve performance dramatically by understanding the nature of the data with which you work. (This is @@ -23,14 +23,14 @@ heads. #### Short Sorts {#Heading7} David Stafford (recently of Borland and Borland Japan) who happens to be -one of the best assembly language programmers I’ve ever met, has written +one of the best assembly language programmers I've ever met, has written a C-callable routine that sorts an array of integers in ascending order. -That wouldn’t be particularly noteworthy, except that David’s routine, +That wouldn't be particularly noteworthy, except that David's routine, shown in Listing 9.4, is exactly *25 bytes* long. Look at the code; -you’ll keep saying to yourself, “But this doesn’t work...oh, yes, I -guess it does.” As they say in the Prego spaghetti sauce ads, *it’s in +you'll keep saying to yourself, "But this doesn't work...oh, yes, I +guess it does." As they say in the Prego spaghetti sauce ads, *it's in there*—and what a job of packing. Anyway, David says that a 24-byte sort -routine eludes him, and he’d like to know if anyone can come up with +routine eludes him, and he'd like to know if anyone can come up with one. **LISTING 9.4 L9-4.ASM** @@ -72,18 +72,18 @@ one. 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 -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 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 not—and if you guess wrong, you get that godawful Divide By Zero interrupt. So, what is one to do when the result might not fit in 16 bits, or when the dividend is larger than 32 bits? Fall back to a software division approach? That will work—but oh so slowly. -There’s another technique that’s much faster than a pure software +There's another technique that's much faster than a pure software approach, albeit not so flexible. This technique allows arbitrarily large dividends and results, but the divisor is still limited to16 bits. -That’s not perfect, but it does solve a number of problems, in +That's not perfect, but it does solve a number of problems, in particular eliminating the possibility of a Divide By Zero interrupt from a too-large result. diff --git a/09-06.md b/09-06.md index 4d4a342..a77eb3c 100644 --- a/09-06.md +++ b/09-06.md @@ -34,12 +34,12 @@ .code public _Div _Divprocnear - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to our stack frame - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di - std ;we’re working from msb to lsb + std ;we're working from msb to lsb mov ax,ds mov es,ax ;for STOS mov cx,[bp+DividendLength] @@ -64,9 +64,9 @@ loop DivLoop mov ax,dx ;return the remainder cld ;restore default Direction flag setting - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _Divendp end @@ -74,7 +74,7 @@ **LISTING 9.6 L9-6.C** /* Sample use of Div function to perform division when the result - doesn’t fit in 16 bits */ + doesn't fit in 16 bits */ #include @@ -87,15 +87,15 @@ unsigned int k, j = 0x10; k = Div((unsigned int *)&i, sizeof(i), j, (unsigned int *)&m); - printf(“%lu / %u = %lu r %u\n”, i, j, m, k); + printf("%lu / %u = %lu r %u\n", i, j, m, k); } #### Sweet Spot Revisited {#Heading9} Way back in Volume 1, Number 1 of *PC TECHNIQUES*, (April/May 1990) I -wrote the very first of that magazine’s HAX (\#1), which extolled the +wrote the very first of that magazine's HAX (\#1), which extolled the virtues of placing your most commonly-used automatic (stack-based) -variables within the stack’s “sweet spot,” the area between +127 to -128 +variables within the stack's "sweet spot," the area between +127 to -128 bytes away from BP, the stack frame pointer. The reason was that the 8088 can store addressing displacements that fall within that range in a single byte; larger displacements require a full word of storage, @@ -117,19 +117,19 @@ on the processor, the memory architecture, and the code mix. On a 486, prefix bytes often cost a cycle; on a 386SX, increased code size often slows performance because instructions must be fetched through the half-pint 16-bit bus; on a 386, the effect depends on the instruction -mix and whether there’s a cache. +mix and whether there's a cache. ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *On balance, though, it’s as important to keep your most-used variables in the stack’s sweet spot in 386 native mode as it was on the 8088.* + ![](images/i.jpg) *On balance, though, it's as important to keep your most-used variables in the stack's sweet spot in 386 native mode as it was on the 8088.* ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------- -In assembly, it’s easy to control the organization of your stack frame. -In C, however, you’ll have to figure out the allocation scheme your +In assembly, it's easy to control the organization of your stack frame. +In C, however, you'll have to figure out the allocation scheme your compiler uses to allocate automatic variables, and declare automatics appropriately to produce the desired effect. It can be done: I did it in Turbo C some years back, and trimmed the size of a program (admittedly, -a large one) by several K—not bad, when you consider that the “sweet -spot” optimization is essentially free, with no code reorganization, +a large one) by several K—not bad, when you consider that the "sweet +spot" optimization is essentially free, with no code reorganization, change in logic, or heavy thinking involved. ------------------------ --------------------------------- -------------------- diff --git a/09-07.md b/09-07.md index 10300d1..6175963 100644 --- a/09-07.md +++ b/09-07.md @@ -5,28 +5,28 @@ #### Hard-Core Cycle Counting {#Heading10} 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, +it involves apparently incorrect documentation on Intel's part. +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 +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 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 +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 -fast they run on my 386, and I very much doubt that you’ll find +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!) ![](images/09-04.jpg)\ **Figure 9.4**  *Performing rotate instructions using the Carry flag.* -Interestingly, according to Intel’s *i486 Microprocessor Programmer’s +Interestingly, according to Intel's *i486 Microprocessor Programmer's 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**! @@ -38,10 +38,10 @@ with a grain of salt. #### Hardwired Far Jumps {#Heading11} 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 +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 +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: @@ -58,8 +58,8 @@ 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 12 cycles, about three times faster. In truth, the difference between -those two is nowhere near that big; the fastest I’ve measured for a -direct far jump is 21 cycles, and I’ve measured indirect far jumps as +those two is nowhere near that big; the fastest I've measured for a +direct far jump is 21 cycles, and I've measured indirect far jumps as fast as 30 cycles, so direct is still faster, but not by so much. (Oh, those cycle-time documentation blues!) Also, a direct far jump is documented to take at least 27 cycles in protected mode; why the big @@ -70,18 +70,18 @@ Although an indirect far jump will work, a direct far jump is still 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 +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 -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 +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 solution; if you have one, pass it along. **LISTING 9.7 L9-7.ASM** ; Program to perform a direct far jump to address 1000:5. - ; *** Do not run this program! It’s just an example of how *** + ; *** Do not run this program! It's just an example of how *** ; *** to build a direct far jump to an absolute address *** ; ; Tested with TASM 2 and MASM 5. @@ -97,10 +97,10 @@ solution; if you have one, pass it along. jmp FarLabel end start -By the way, if you’re wondering how I figured this out, I merely applied -my good friend Dan Illowsky’s long-standing rule for dealing with MASM: +By the way, if you're wondering how I figured this out, I merely applied +my good friend Dan Illowsky's long-standing rule for dealing with MASM: -If the obvious doesn’t work (and it usually doesn’t), just try +If the obvious doesn't work (and it usually doesn't), just try everything you can think of, no matter how ridiculous, until you find something that does—a rule with plenty of history on its side. @@ -117,9 +117,9 @@ takes 4 cycles to execute, but is only 3 bytes long, while 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). +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 +affects the flags and the second doesn't); this is a classic trade-off of speed for space. Second, or ebx,-1 @@ -129,14 +129,14 @@ takes 2 cycles to execute and is 3 bytes long, while 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 +**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 +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 diff --git a/10-01.md b/10-01.md index 97d2da6..2c8122b 100644 --- a/10-01.md +++ b/10-01.md @@ -13,65 +13,65 @@ In ink. With nary a blemish. The relevance of which will become apparent in a trice. -What my grandfather is, is a pattern matcher *par excellence*. You’re a -pattern matcher, too. So am I. We can’t help it; it comes with the -territory. Try focusing on text and not reading it. Can’t do it. Can you -hear the voice of someone you know and not recognize it? I can’t. And -how in the Nine Billion Names of God is it that we’re capable of -instantly recognizing one face out of the thousands we’ve seen in our +What my grandfather is, is a pattern matcher *par excellence*. You're a +pattern matcher, too. So am I. We can't help it; it comes with the +territory. Try focusing on text and not reading it. Can't do it. Can you +hear the voice of someone you know and not recognize it? I can't. And +how in the Nine Billion Names of God is it that we're capable of +instantly recognizing one face out of the thousands we've seen in our lifetimes—even years later, from a different angle and in different light? Although we take them for granted, our pattern-matching capabilities are surely a miracle on the order of loaves and fishes. -By “pattern matching,” I mean more than just recognition, though. I mean +By "pattern matching," I mean more than just recognition, though. I mean that we are generally able to take complex and often seemingly woefully inadequate data, instantaneously match it in an incredibly flexible way to our past experience, extrapolate, and reach amazing conclusions, something that computers can scarcely do at all. Crossword puzzles are an excellent example; given a couple of letters and a cryptic clue, -we’re somehow able to come up with one out of several hundred thousand -words that we know. Try writing a program to do that! What’s more, we -don’t process data in the serial brute-force way that computers do. +we're somehow able to come up with one out of several hundred thousand +words that we know. Try writing a program to do that! What's more, we +don't process data in the serial brute-force way that computers do. Solutions tend to be virtually instantaneous or not at all; none of -those “N log N” or “N^2”^ execution times for us. +those "N log N" or "N^2"^ execution times for us. It goes without saying that pattern matching is good; more than that, -it’s a large part of what we are, and, generally, the faster we are at +it's a large part of what we are, and, generally, the faster we are at it, the better. Not always, though. Sometimes insufficient information really is insufficient, and, in our haste to get the heady rush of coming up with a solution, incorrect or less-than-optimal conclusions are reached, as anyone who has ever done the *Times* Sunday crossword will attest. Still, my grandfather does that puzzle every Sunday *in -ink*. What’s his secret? Patience and discipline. He never fills a word -in until he’s confirmed it in his head via intersecting words, no matter +ink*. What's his secret? Patience and discipline. He never fills a word +in until he's confirmed it in his head via intersecting words, no matter how strong the urge may be to put something down where he can see it and -feel like he’s getting somewhere. +feel like he's getting somewhere. -There’s a surprisingly close parallel to programming here. Programming -is certainly a sort of pattern matching in the sense I’ve described +There's a surprisingly close parallel to programming here. Programming +is certainly a sort of pattern matching in the sense I've described above, and, as with crossword puzzles, following your programming instincts too quickly can be a liability. For many programmers, myself -included, there’s a strong urge to find a workable approach to a +included, there's a strong urge to find a workable approach to a particular problem and start coding it *right now*, what some people -call “hacking” a program. Going with the first thing your programming -pattern matcher comes up with can be a lot of fun; there’s instant -gratification and a feeling of unbounded creativity. Personally, I’ve +call "hacking" a program. Going with the first thing your programming +pattern matcher comes up with can be a lot of fun; there's instant +gratification and a feeling of unbounded creativity. Personally, I've always hungered to get results from my work as soon as possible; I gravitated toward graphics for its instant and very visible -gratification. Over time, however, I’ve learned patience. +gratification. Over time, however, I've learned patience. ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *I’ve come to spend an increasingly large portion of my time choosing algorithms, designing, and simply giving my mind quiet time in which to work on problems and come up with non-obvious approaches before coding; and I’ve found that the extra time up front more than pays for itself in both decreased coding time and superior programs.* + ![](images/i.jpg) *I've come to spend an increasingly large portion of my time choosing algorithms, designing, and simply giving my mind quiet time in which to work on problems and come up with non-obvious approaches before coding; and I've found that the extra time up front more than pays for itself in both decreased coding time and superior programs.* ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -In this chapter, I’m going to walk you through a simple but illustrative +In this chapter, I'm going to walk you through a simple but illustrative case history that nicely points up the wisdom of delaying gratification when faced with programming problems, so that your mind has time to chew on the problems from other angles. The alternative solutions you find by -doing this may seem obvious, once you’ve come up with them. They may not +doing this may seem obvious, once you've come up with them. They may not even differ greatly from your initial solutions. Often, however, they -will be much better—and you’ll never even have the chance to decide -whether they’re better or not if you take the first thing that comes +will be much better—and you'll never even have the chance to decide +whether they're better or not if you take the first thing that comes into your head and run with it. #### The Case for Delayed Gratification {#Heading3} @@ -80,38 +80,38 @@ Once upon a time, I set out to read *Algorithms*, by Robert Sedgewick (Addison-Wesley), which turned out to be a wonderful, stimulating, and most useful book, one that I recommend highly. My story, however, involves only what happened in the first 12 pages, for it was in those -pages that Sedgewick discussed Euclid’s algorithm. +pages that Sedgewick discussed Euclid's algorithm. -Euclid’s algorithm (discovered by Euclid, of Euclidean geometry fame, a +Euclid's algorithm (discovered by Euclid, of Euclidean geometry fame, a very long time ago, way back when computers still used core memory) is a straightforward algorithm that solves one of the simplest problems imaginable: finding the greatest common integer divisor (GCD) of two positive integers. Sedgewick points out that this is useful for reducing -a fraction to its lowest terms. I’m sure it’s useful for other things, +a fraction to its lowest terms. I'm sure it's useful for other things, as well, although none spring to mind. (A long time ago, I wrote an -article about optimizing a bit of code that wasn’t even vaguely +article about optimizing a bit of code that wasn't even vaguely time-critical, and got swamped with letters telling me so. I knew it -wasn’t time-critical; it was just a good example. So for now, close your +wasn't time-critical; it was just a good example. So for now, close your eyes and *imagine* that finding the GCD is not only necessary but must -also be done as quickly as possible, because it’s perfect for the point +also be done as quickly as possible, because it's perfect for the point I want to make here and now. Okay?) The problem at hand, then, is simply this: Find the largest integer -value that evenly divides two arbitrary positive integers. That’s all +value that evenly divides two arbitrary positive integers. That's all there is to it. So warm up your pattern matchers...and go! ### The Brute-Force Syndrome {#Heading4} -I have a funny feeling that you’d already figured out how to find the -GCD before I even said “go.” That’s what I did when reading +I have a funny feeling that you'd already figured out how to find the +GCD before I even said "go." That's what I did when reading *Algorithms;* before I read another word, I had to figure it out for myself. Programmers are like that; give them a problem and their eyes -immediately glaze over as they try to solve it before you’ve even shut +immediately glaze over as they try to solve it before you've even shut your mouth. That sort of instant response can certainly be impressive, but it can backfire, too, as it did in my case. You see, I fell victim to a common programming pitfall, the -“brute-force” syndrome. The basis of this syndrome is that there are +"brute-force" syndrome. The basis of this syndrome is that there are many problems that have obvious, brute-force solutions—with one small drawback. The drawback is that if you were to try to apply a brute-force solution by hand—that is, work a single problem out with pencil and @@ -120,11 +120,11 @@ patience and discipline to work on the problem for approximately seven hundred years, not counting eating and sleeping, in order to get an answer. Finding all the prime numbers less than 1,000,000 is a good example; just divide each number up to 1,000,000 by every lesser number, -and see what’s left standing. For most of the history of humankind, +and see what's left standing. For most of the history of humankind, people were forced to think of cleverer solutions, such as the Sieve of -Eratosthenes (we’d have been in big trouble if the ancient Greeks had +Eratosthenes (we'd have been in big trouble if the ancient Greeks had had computers), mainly because after about five minutes of brute -force-type work, people’s attention gets diverted to other important +force-type work, people's attention gets diverted to other important matters, such as how far a paper airplane will fly from a second-story window. diff --git a/10-02.md b/10-02.md index dd76a9d..ec0c35b 100644 --- a/10-02.md +++ b/10-02.md @@ -2,14 +2,14 @@ [Previous](10-01.html) [Table of Contents](index.html) [Next](10-03.html) ------------------------ --------------------------------- -------------------- -Not so nowadays, though. Computers love boring work; they’re very +Not so nowadays, though. Computers love boring work; they're very patient and disciplined, and, besides, one human year = seven dog years -= two zillion computer years. So when we’re faced with a problem that -has an obvious but exceedingly lengthy solution, we’re apt to say, “Ah, -let the computer do that, it’s fast,” and go back to making paper += two zillion computer years. So when we're faced with a problem that +has an obvious but exceedingly lengthy solution, we're apt to say, "Ah, +let the computer do that, it's fast," and go back to making paper airplanes. Unfortunately, brute-force solutions tend to be slow even when performed by modern-day microcomputers, which are capable of -several MIPS except when I’m late for an appointment and want to finish +several MIPS except when I'm late for an appointment and want to finish a compile and run just one more test before I leave, in which case the crystal in my computer is apparently designed to automatically revert to 1 Hz.) @@ -18,7 +18,7 @@ The solution that I instantly came up with to finding the GCD is about as brute- force as you can get: Divide both the larger integer (iL) and the smaller integer (iS) by every integer equal to or less than the smaller integer, until a number is found that divides both evenly, as -shown in Figure 10.1. This works, but it’s a lousy solution, requiring +shown in Figure 10.1. This works, but it's a lousy solution, requiring as many as iS\*2 divisions; *very* expensive, especially for large values of iS. For example, finding the GCD of 30,001 and 30,002 would require 60,002 divisions, which alone, disregarding tests and branches, @@ -88,7 +88,7 @@ Integer pairs for which to find GCD **Listing 10.3**\ (Division: code recursive\ - Euclid’s algorithm) + Euclid's algorithm) 20\ (33%) @@ -106,7 +106,7 @@ Integer pairs for which to find GCD (0.12%) **Listing 10.4**\ - (C version of data recursive Euclid’s algorithm; normal optimization) + (C version of data recursive Euclid's algorithm; normal optimization) 12\ (20%) @@ -142,7 +142,7 @@ Integer pairs for which to find GCD (0.05%) **Listing 10.5**\ - (Assembly version of data recursive Euclid’s algorithm) + (Assembly version of data recursive Euclid's algorithm) 10\ (17%) @@ -200,7 +200,7 @@ Table 10.1 Performance of GCD algorithm implementations. #### Wasted Breakthroughs {#Heading5} -Sedgewick’s first solution to the GCD problem was pretty much the one I +Sedgewick's first solution to the GCD problem was pretty much the one I came up with. He then pointed out that the GCD of iL and iS is the same as the GCD of iL-iS and iS. This was obvious (once Sedgewick pointed it out); by the very nature of division, any number that divides iL evenly @@ -213,13 +213,13 @@ in Listing 10.2. /* Finds and returns the greatest common divisor of two positive integers. Works by subtracting the smaller integer from the larger integer until either the values match (in which case - that’s the gcd), or the larger integer becomes the smaller of + that's the gcd), or the larger integer becomes the smaller of the two, in which case the two integers swap roles and the subtraction process continues. */ unsigned int gcd(unsigned int int1, unsigned int int2) { unsigned int temp; - /* If the two integers are the same, that’s the gcd and we’re + /* If the two integers are the same, that's the gcd and we're done */ if (int1 == int2) { return(int1); diff --git a/10-03.md b/10-03.md index 4185c52..518631a 100644 --- a/10-03.md +++ b/10-03.md @@ -3,22 +3,22 @@ ------------------------ --------------------------------- -------------------- Listing 10.2 repeatedly subtracts iS from iL until iL becomes less than -or equal to iS. If iL becomes equal to iS, then that’s the GCD; +or equal to iS. If iL becomes equal to iS, then that's the GCD; alternatively, if iL becomes *less* than iS, iL and iS switch values, and the process is repeated, as shown in Figure 10.2. The number of iterations this approach requires relative to Listing 10.1 depends -heavily on the values of iL and iS, so it’s not always faster, but, as +heavily on the values of iL and iS, so it's not always faster, but, as Table 10.1 indicates, Listing 10.2 is generally much better code. ![](images/10-02.jpg)\ **Figure 10.2**  *Using repeated subtraction algorithm to find a GCD.* Listing 10.2 is a far graver misstep than Listing 10.1, for all that -it’s faster. Listing 10.1 is obviously a hacked-up, brute-force +it's faster. Listing 10.1 is obviously a hacked-up, brute-force approach; no one could mistake it for anything else. It could be speeded up in any of a number of ways with a little thought. (Simply skipping testing all the divisors between iS and iS/2, not inclusive, would cut -the worst-case time in half, for example; that’s not a particularly +the worst-case time in half, for example; that's not a particularly *good* optimization, but it illustrates how easily Listing 10.1 can be improved.) Listing 10.1 is a hack job, crying out for inspiration. @@ -26,20 +26,20 @@ Listing 10.2, on the other hand, has gotten the inspiration—and largely wasted it through haste. Had Sedgewick not told me otherwise, I might well have assumed that Listing 10.2 was optimized, a mistake I would never have made with Listing 10.1. I experienced a conceptual -breakthrough when I understood Sedgewick’s point: A smaller number can +breakthrough when I understood Sedgewick's point: A smaller number can be subtracted from a larger number without affecting their GCD, thereby inexpensively reducing the scale of the problem. And, in my hurry to make this breakthrough reality, I missed its full scope. As Sedgewick says on the very next page, the number that one gets by subtracting iS from iL until iL is less than iS is precisely the same as the remainder that one gets by dividing iL by iS—again, this is inherent in the nature -of division—and *that* is the basis for Euclid’s algorithm, shown in -Figure 10.3. Listing 10.3 is an implementation of Euclid’s algorithm. +of division—and *that* is the basis for Euclid's algorithm, shown in +Figure 10.3. Listing 10.3 is an implementation of Euclid's algorithm. **LISTING 10.3 L10-3.C** /* Finds and returns the greatest common divisor of two integers. - Uses Euclid’s algorithm: divides the larger integer by the + Uses Euclid's algorithm: divides the larger integer by the smaller; if the remainder is 0, the smaller integer is the GCD, otherwise the smaller integer becomes the larger integer, the remainder becomes the smaller integer, and the process is @@ -49,7 +49,7 @@ Figure 10.3. Listing 10.3 is an implementation of Euclid’s algorithm. unsigned int gcd(unsigned int int1, unsigned int int2) { unsigned int temp; - /* If the two integers are the same, that’s the GCD and we’re + /* If the two integers are the same, that's the GCD and we're done */ if (int1 == int2) { return(int1); @@ -82,32 +82,32 @@ Figure 10.3. Listing 10.3 is an implementation of Euclid’s algorithm. return(gcd_recurs(smaller_int, temp)); } -As you can see from Table 10.1, Euclid’s algorithm is superior, +As you can see from Table 10.1, Euclid's algorithm is superior, especially for large numbers (and imagine if we were working with large *longs!*). ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Had I been implementing GCD determination without Sedgewick’s help, I would surely not have settled for Listing 10.1—but I might well have ended up with Listing 10.2 in my enthusiasm over the “brilliant” discovery of subtracting the lesser Using Euclid’s algorithm to find a GCD number from the greater. In a commercial product, my lack of patience and discipline could have been costly indeed.* + ![](images/i.jpg) *Had I been implementing GCD determination without Sedgewick's help, I would surely not have settled for Listing 10.1—but I might well have ended up with Listing 10.2 in my enthusiasm over the "brilliant" discovery of subtracting the lesser Using Euclid's algorithm to find a GCD number from the greater. In a commercial product, my lack of patience and discipline could have been costly indeed.* ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ![](images/10-03.jpg)\ - **Figure 10.3**  *Using Euclid’s algorithm to find a GCD.* + **Figure 10.3**  *Using Euclid's algorithm to find a GCD.* Give your mind time and space to wander around the edges of important programming problems before you settle on any one approach. I titled -this book’s first chapter “The Best Optimizer Is between Your Ears,” and -that’s still true; what’s even more true is that the optimizer between +this book's first chapter "The Best Optimizer Is between Your Ears," and +that's still true; what's even more true is that the optimizer between your ears does its best work not at the implementation stage, but at the very beginning, when you try to imagine how what you want to do and what a computer is capable of doing can best be brought together. ### Recursion {#Heading6} -Euclid’s algorithm lends itself to recursion beautifully, so much so +Euclid's algorithm lends itself to recursion beautifully, so much so that an implementation like Listing 10.3 comes almost without thought. -Again, though, take a moment to stop and consider what’s really going -on, at the assembly language level, in Listing 10.3. There’s recursion -and then there’s recursion; code recursion and data recursion, to be +Again, though, take a moment to stop and consider what's really going +on, at the assembly language level, in Listing 10.3. There's recursion +and then there's recursion; code recursion and data recursion, to be exact. Listing 10.3 is code recursion—recursion through calls—the sort most often used because it is conceptually simplest. However, code recursion tends to be slow because it pushes parameters and calls a @@ -120,7 +120,7 @@ recursive operations that Listing 10.3 does. **LISTING 10.4 L10-4.C** /* Finds and returns the greatest common divisor of two integers. - Uses Euclid’s algorithm: divides the larger integer by the + Uses Euclid's algorithm: divides the larger integer by the smaller; if the remainder is 0, the smaller integer is the GCD, otherwise the smaller integer becomes the larger integer, the remainder becomes the smaller integer, and the process is @@ -136,7 +136,7 @@ recursive operations that Listing 10.3 does. int2 = temp; } /* Now loop, dividing int1 by int2 and checking the remainder, - until the remainder is 0. At each step, if the remainder isn’t + until the remainder is 0. At each step, if the remainder isn't 0, assign int2 to int1, and the remainder to int2, then repeat */ for (;;) { @@ -154,9 +154,9 @@ recursive operations that Listing 10.3 does. #### Patient Optimization {#Heading7} -At long last, we’re ready to optimize GCD determination in the classic +At long last, we're ready to optimize GCD determination in the classic sense. Table 10.1 shows the performance of Listing 10.4 with and without -Microsoft C/C++’s maximum optimization, and also shows the performance +Microsoft C/C++'s maximum optimization, and also shows the performance of Listing 10.5, an assembly language version of Listing 10.4. Sure, the optimized versions are faster than the unoptimized version of Listing 10.4—but the gains are small compared to those realized from the diff --git a/10-04.md b/10-04.md index 7ce7e63..a47fd91 100644 --- a/10-04.md +++ b/10-04.md @@ -5,7 +5,7 @@ **LISTING 10.5 L10-5.ASM** ; Finds and returns the greatest common divisor of two integers. - ; Uses Euclid’s algorithm: divides the larger integer by the + ; Uses Euclid's algorithm: divides the larger integer by the ; smaller; if the remainder is 0, the smaller integer is the GCD, ; otherwise the smaller integer becomes the larger integer, the ; remainder becomes the smaller integer, and the process is @@ -29,21 +29,21 @@ public _gcd align 2 _gcd proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;set up our stack frame - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di ;Swap if necessary to make sure that int1 >= int2 mov ax,int1[bp] mov bx,int2[bp] cmp ax,bx ;is int1 >= int2? - jnb IntsSet ;yes, so we’re all set + jnb IntsSet ;yes, so we're all set xchg ax,bx ;no, so swap int1 and int2 IntsSet: ; Now loop, dividing int1 by int2 and checking the remainder, until - ; the remainder is 0. At each step, if the remainder isn’t 0, assign + ; the remainder is 0. At each step, if the remainder isn't 0, assign ; int2 to int1, and the remainder to int2, then repeat. GCDLoop: ;if the remainder of int1 divided by @@ -85,18 +85,18 @@ align2 Done: mov ax,bx ;return the GCD - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _gcd endp end Assembly language optimization is pattern matching on a local scale. -Frankly, it’s also the sort of boring, brute-force work that people are +Frankly, it's also the sort of boring, brute-force work that people are lousy at; compilers could out-optimize you at this level with one pass -tied behind their back *if* they knew as much about the code you’re -writing as you do, which they don’t. +tied behind their back *if* they knew as much about the code you're +writing as you do, which they don't. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ![](images/i.jpg) *Design optimization—conceptual breakthroughs in understanding the relationships between the needs of an application, the nature of the data the application works with, and what the computer can do—is global pattern matching.* @@ -106,7 +106,7 @@ Computers are *much* worse at that sort of pattern matching than humans; computers have no way to integrate vast amounts of disparate information, much of it only vaguely defined or subject to change. People, oddly enough, are *better* at global optimization than at local -optimization. For one thing, it’s more interesting. For another, it’s +optimization. For one thing, it's more interesting. For another, it's complex and imprecise enough to allow intuition and inspiration, two vastly underrated programming tools, to come to the fore. And, as I pointed out earlier, people tend to perform instantaneous solutions to @@ -122,12 +122,12 @@ job is to give your pattern matcher the opportunity to get to know each problem and run through it two or three times, from different angles, to see what unexpected solutions it can come up with. -Pull back the reins a little. Don’t measure progress by lines of code +Pull back the reins a little. Don't measure progress by lines of code written today; measure it instead by overall progress and by quality. Relax and listen to that quiet inner voice that provides the real breakthroughs. Stop, look, listen—and think. Not only will you find that -it’s a more productive and creative way to program—but you’ll also find -that it’s more fun. +it's a more productive and creative way to program—but you'll also find +that it's more fun. And think what you could do with all those extra computer years! diff --git a/11-01.md b/11-01.md index f98c5ef..1709d79 100644 --- a/11-01.md +++ b/11-01.md @@ -22,7 +22,7 @@ architecture, to a greater degree than you might expect, for the x86 family came into full maturity with the 386; the 486 and the Pentium are really nothing more than faster 386s, with very little in the way of new functionality. In contrast, the 286 added a number of instructions, -respectable performance, and protected mode to the 8088’s capabilities, +respectable performance, and protected mode to the 8088's capabilities, and the 386 added more instructions and a whole new set of addressing modes, and brought the x86 family into the 32-bit world that represents the future (and, increasingly, the present) of personal computing. This @@ -41,34 +41,34 @@ numerous permutations of the 486, and now the Pentium—really matter. The 8088 is now all but extinct in the PC arena. The 8086 was used fairly widely for a while, but has now all but disappeared. The 80186 -and 80188 never really caught on for use in PC and don’t require further +and 80188 never really caught on for use in PC and don't require further discussion. That leaves us with the high-end chips: the 286, the 386SX, the 386, the 486, and the Pentium. At this writing, the 386SX is fast going the way of the 8088; people are realizing that its relatively small cost -advantage over the 386 isn’t enough to offset its relatively large +advantage over the 386 isn't enough to offset its relatively large performance disadvantage. After all, the 386SX suffers from the same debilitating problem that looms over the 8088—a too-small bus. -Internally, the 386SX is a 32-bit processor, but externally, it’s a +Internally, the 386SX is a 32-bit processor, but externally, it's a 16-bit processor, a non-optimal architecture, especially for 32-bit code. -I’m not going to discuss the 386SX in detail. If you do find yourself +I'm not going to discuss the 386SX in detail. If you do find yourself programming for the 386SX, follow the same general rules you should follow for the 8088: use short instructions, use the registers as -heavily as possible, and don’t branch. In other words, avoid memory, +heavily as possible, and don't branch. In other words, avoid memory, since the 386SX is by definition better at processing data internally than it is at accessing memory. The 486 is a world unto itself for the purposes of optimization, and the -Pentium is a *universe* unto itself. We’ll treat them separately in +Pentium is a *universe* unto itself. We'll treat them separately in later chapters. This leaves us with just two processors: the 286 and the 386. Each was *the* PC standard in its day. The 286 is no longer used in new systems, but there are millions of 286-based systems still in daily use. The 386 -is still being used in new systems, although it’s on the downhill leg of +is still being used in new systems, although it's on the downhill leg of its lifespan, and it is in even wider use than the 286. The future clearly belongs to the 486 and Pentium, but the 286 and 386 are still very much a part of the present-day landscape. @@ -78,16 +78,16 @@ very much a part of the present-day landscape. Apart from vastly improved performance, the biggest difference between the 8088 and the 286 and 386 (as well as the later Intel CPUs) is that the 286 introduced protected mode, and the 386 greatly expanded the -capabilities of protected mode. We’re only going to talk about real-mode +capabilities of protected mode. We're only going to talk about real-mode operation of the 286 and 386 in this book, however. Protected mode -offers a whole new memory management scheme, one that isn’t supported by +offers a whole new memory management scheme, one that isn't supported by the 8088. Only code specifically written for protected mode can run in -that mode; it’s an alien and hostile environment for MS-DOS programs. +that mode; it's an alien and hostile environment for MS-DOS programs. In particular, segments are different creatures in protected mode. -They’re *selectors*—indexes into a table of segment descriptors—rather -than plain old registers, and can’t be set to arbitrary values. That -means that segments can’t be used for temporary storage or as part of a +They're *selectors*—indexes into a table of segment descriptors—rather +than plain old registers, and can't be set to arbitrary values. That +means that segments can't be used for temporary storage or as part of a fast indivisible 32-bit load from memory, as in les ax,dword ptr [LongVar] @@ -111,37 +111,37 @@ any one program may depend far less on code quality than on how efficiently the program uses operating system services and how often and under what circumstances the operating system preempts the program. Protected mode programs are often mostly collections of operating system -calls, and the performance of whatever code *isn’t* operating-system +calls, and the performance of whatever code *isn't* operating-system oriented may depend primarily on how large a time slice the operating system gives that code to run in. In short, taken as a whole, protected mode programming is a different -kettle of fish altogether from what I’ve been describing in this book. -There’s certainly a knack to optimizing specifically for protected mode -under a given operating system...but it’s not what we’ve been learning, +kettle of fish altogether from what I've been describing in this book. +There's certainly a knack to optimizing specifically for protected mode +under a given operating system...but it's not what we've been learning, and now is not the time to pursue it further. In general, though, the optimization strategies discussed in this book still hold true in -protected mode; it’s just issues specific to protected mode or a -particular operating system that we won’t discuss. +protected mode; it's just issues specific to protected mode or a +particular operating system that we won't discuss. #### In the Lair of the Cycle-Eaters, Part II {#Heading5} Under the programming interface, the 286 and 386 differ considerably from the 8088. Nonetheless, with one exception and one addition, the cycle-eaters remain much the same on computers built around the 286 and -386. Next, we’ll review each of the familiar cycle-eaters I covered in -Chapter 4 as they apply to the 286 and 386, and we’ll look at the new +386. Next, we'll review each of the familiar cycle-eaters I covered in +Chapter 4 as they apply to the 286 and 386, and we'll look at the new member of the gang, the data alignment cycle-eater. The one cycle-eater that vanishes on the 286 and 386 is the 8-bit bus cycle-eater. The 286 is a 16-bit processor both internally and externally, and the 386 is a 32-bit processor both internally and externally, so the Execution Unit/Bus Interface Unit size mismatch that -plagues the 8088 is eliminated. Consequently, there’s no longer any need +plagues the 8088 is eliminated. Consequently, there's no longer any need to use byte-sized memory variables in preference to word-sized variables, at least so long as word-sized variables start at even -addresses, as we’ll see shortly. On the other hand, access to byte-sized -variables still isn’t any *slower* than access to word-sized variables, +addresses, as we'll see shortly. On the other hand, access to byte-sized +variables still isn't any *slower* than access to word-sized variables, so you can use whichever size suits a given task best. ------------------------ --------------------------------- -------------------- diff --git a/11-02.md b/11-02.md index 9a99b71..5c48ac2 100644 --- a/11-02.md +++ b/11-02.md @@ -10,12 +10,12 @@ have larger prefetch queues than the 8088 (6 bytes for the 286, 16 bytes for the 386) and can perform memory accesses, including instruction fetches, in far fewer cycles than the 8088. -However, the prefetch queue cycle-eater *doesn’t* vanish on either the +However, the prefetch queue cycle-eater *doesn't* vanish on either the 286 or the 386, for several reasons. For one thing, branching instructions still empty the prefetch queue, so instruction fetching still slows things down after most branches; when the prefetch queue is -empty, it doesn’t much matter how big it is. (Even apart from emptying -the prefetch queue, branches aren’t particularly fast on the 286 or the +empty, it doesn't much matter how big it is. (Even apart from emptying +the prefetch queue, branches aren't particularly fast on the 286 or the 386, at a minimum of seven-plus cycles apiece. Avoid branching whenever possible.) @@ -52,8 +52,8 @@ with a 286 is fairly expensive, and memory that can keep up with a 386 is *very* expensive. Instead, computer designers use alternative memory architectures that offer more performance for the dollar—but less performance overall—than zero-wait-state memory. (It *is* possible to -build zero-wait-state systems for the 286 and 386; it’s just so -expensive that it’s rarely done.) +build zero-wait-state systems for the 286 and 386; it's just so +expensive that it's rarely done.) The IBM AT and true compatibles use one-wait-state memory (some AT clones use zero-wait-state memory, but such clones are less common than @@ -62,7 +62,7 @@ systems—including high-speed caches, interleaved memory, and static-column RAM—that insert anywhere from 0 to about 5 wait states (and many more if 8 or 16-bit memory expansion cards are used); the exact number of wait states inserted at any given time depends on the -interaction between the code being executed and the memory system it’s +interaction between the code being executed and the memory system it's running on. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -70,16 +70,16 @@ running on. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ The many memory systems in use make it impossible for us to optimize for -286/386 computers with the precision that’s possible on the 8088. +286/386 computers with the precision that's possible on the 8088. Instead, we must write code that runs reasonably well under the varying conditions found in the 286/386 arena. The wait states that occur on most accesses to system memory in 286 and 386 computers mean that nearly every access to system memory—memory in -the DOS’s normal 640K memory area—is slowed down. (Accesses in computers +the DOS's normal 640K memory area—is slowed down. (Accesses in computers with high-speed caches may be wait-state-free if the desired data is already in the cache, but will certainly encounter wait states if the -data isn’t cached; this phenomenon produces highly variable instruction +data isn't cached; this phenomenon produces highly variable instruction execution times.) While this is our first encounter with system memory wait states, we have run into a wait-state cycle-eater before: the display adapter cycle-eater, which we discussed along with the other @@ -104,14 +104,14 @@ Unit. And that, my friend, is unmistakably the prefetch queue cycle-eater. I might add that the prefetch queue cycle-eater is in rare good form in 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. +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 +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 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 -seven-tenths of a cycle comes from DRAM refresh, which we’ll get to +That's 12.7 cycles per instruction, just as we calculated. (That extra +seven-tenths of a cycle comes from DRAM refresh, which we'll get to shortly.) **LISTING 11.1 L11-1.ASM** @@ -137,7 +137,7 @@ shortly.) call ZTimerOff What does this mean? It means that, practically speaking, the 286 as -used in the AT doesn’t have a 16-bit bus. From a performance +used in the AT doesn't have a 16-bit bus. From a performance perspective, the 286 in an AT has two-thirds of a 16-bit bus (a 10.7-bit bus?), since every bus access on an AT takes 50 percent longer than it should. A 286 running at 10 MHz *should* be able to access memory at a diff --git a/11-03.md b/11-03.md index d1b8725..06d23ed 100644 --- a/11-03.md +++ b/11-03.md @@ -6,7 +6,7 @@ In short, a close relative of our old friend the 8-bit bus cycle-eater—the system memory wait state cycle-eater—haunts us still on all but zero-wait-state 286 and 386 computers, and that means that the prefetch queue cycle-eater is alive and well. (The system memory wait -state cycle-eater isn’t really a new cycle-eater, but rather a variant +state cycle-eater isn't really a new cycle-eater, but rather a variant of the general wait state cycle-eater, of which the display adapter cycle-eater is yet another variant.) While the 286 in the AT can fetch instructions much faster than can the 8088 in the PC, it can execute @@ -21,7 +21,7 @@ outrun even zero—5 cycles longer than the official execution time.) To summarize: -- Memory-accessing instructions don’t run at their official speeds on +- Memory-accessing instructions don't run at their official speeds on non-zero-wait-state 286/386 computers. - The prefetch queue cycle-eater reduces performance on 286/386 computers, particularly when non-zero-wait-state memory is used. @@ -31,21 +31,21 @@ To summarize: performance varies from one 286/386 computer to another, making precise optimization impossible. -What’s to be learned from all this? Several things: +What's to be learned from all this? Several things: - Keep your instructions short. -- Keep it in the registers; avoid memory, since memory generally can’t +- Keep it in the registers; avoid memory, since memory generally can't keep up with the processor. -- Don’t jump. +- Don't jump. Of course, those are exactly the rules that apply to 8088 optimization -as well. Isn’t it convenient that the same general rules apply across +as well. Isn't it convenient that the same general rules apply across the board? #### Data Alignment {#Heading7 align="center"} Thanks to its 16-bit bus, the 286 can access word-sized memory variables -just as fast as byte-sized variables. There’s a catch, however: That’s +just as fast as byte-sized variables. There's a catch, however: That's only true for word-sized variables that start at even addresses. When the 286 is asked to perform a word-sized access starting at an odd address, it actually performs two separate accesses, each of which @@ -62,20 +62,20 @@ address is easy to calculate: Two accesses take twice as long as one access. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *In other words, the effective capacity of the 286’s external data bus is* *halved* *when a word-sized access to an odd address is performed.* + ![](images/i.jpg) *In other words, the effective capacity of the 286's external data bus is* *halved* *when a word-sized access to an odd address is performed.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------ That, in a nutshell, is the data alignment cycle-eater, the one new cycle-eater of the 286 and 386. (The data alignment cycle-eater is a -close relative of the 8088’s 8-bit bus cycle-eater, but since it behaves +close relative of the 8088's 8-bit bus cycle-eater, but since it behaves differently—occurring only at odd addresses—and is avoided with a -different workaround, we’ll consider it to be a new cycle-eater.) +different workaround, we'll consider it to be a new cycle-eater.) ![](images/11-01.jpg)\ **Figure 11.1**  *The data alignment cycle-eater.* The way to deal with the data alignment cycle-eater is straightforward: -*Don’t perform word-sized accesses to odd addresses on the 286 if you +*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 @@ -85,7 +85,7 @@ 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 @@ -115,7 +115,7 @@ refresh. 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 -word-sized memory access. That’s 3 cycles per word-sized access—exactly +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. @@ -142,7 +142,7 @@ The data alignment cycle-eater has intriguing implications for speeding up 286/386 code. The expenditure of a little care and a few bytes to make sure that word-sized variables and memory blocks are word-aligned can literally double the performance of certain code running on the 286. -Even if it doesn’t double performance, word alignment usually helps and +Even if it doesn't double performance, word alignment usually helps and never hurts. #### Code Alignment {#Heading8} @@ -150,11 +150,11 @@ never hurts. Lack of word alignment can also interfere with instruction fetching on the 286, although not to the extent that it interferes with access to word-sized memory variables. The 286 prefetches instructions a word at a -time; even if a given instruction doesn’t begin at an even address, the +time; even if a given instruction doesn't begin at an even address, the 286 simply fetches the first byte of that instruction at the same time that it fetches the last byte of the previous instruction, as shown in Figure 11.2, then separates the bytes internally. That means that in -most cases, instructions run just as fast whether they’re word-aligned +most cases, instructions run just as fast whether they're word-aligned or not. There is, however, a non-word-alignment penalty on *branches* to odd @@ -163,7 +163,7 @@ addresses. On a branch to an odd address, the 286 is only able to fetch shown in Figure 11.3. In other words, lack of word alignment of the target instruction for any branch effectively cuts the instruction-fetching power of the 286 in half for the first instruction -fetch after that branch. While that may not sound like much, you’d be +fetch after that branch. While that may not sound like much, you'd be surprised at what it can do to tight loops; in fact, a brief story is in order. diff --git a/11-04.md b/11-04.md index da518d1..7a7b266 100644 --- a/11-04.md +++ b/11-04.md @@ -41,14 +41,14 @@ cycles per loop: call ZTimerOff 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** +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 sometimes cancel the performance advantage of having a word-aligned branch destination. ------------------- ----------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Consequently, it’s best to word-align only those branch destinations that can be reached solely by branching.* + ![](images/i.jpg) *Consequently, it's best to word-align only those branch destinations that can be reached solely by branching.* ------------------- ----------------------------------------------------------------------------------------------------------------- I recommend that you only go out of your way to word-align the start @@ -65,16 +65,16 @@ time-critical loops. #### Alignment and the 386 {#Heading9 align="center"} -So far we’ve only discussed alignment as it pertains to the 286. What, +So far we've only discussed alignment as it pertains to the 286. What, you may well ask, of the 386? The 386 adds the issue of *doubleword* alignment (that is, alignment to addresses that are multiples of four.) The rule for the 386 is: -Word-sized memory accesses should be word-aligned (it’s impossible for +Word-sized memory accesses should be word-aligned (it's impossible for word-aligned word-sized accesses to cross doubleword boundaries), and doubleword-sized memory accesses should be doubleword-aligned. However, in real (as opposed to 32-bit protected) mode, doubleword-sized memory -accesses are rare, so the simple word-alignment rule we’ve developed for +accesses are rare, so the simple word-alignment rule we've developed for the 286 serves for the 386 in real mode as well. As for code alignment...the subroutine-start word-alignment rule of the @@ -99,7 +99,7 @@ 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** +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 @@ -112,7 +112,7 @@ slowly than it normally would. The same goes for decrementing twice; use #### The DRAM Refresh Cycle-Eater: Still an Act of God {#Heading11 align="center"} -The DRAM refresh cycle-eater is the cycle-eater that’s least changed +The DRAM refresh cycle-eater is the cycle-eater that's least changed from its 8088 form on the 286 and 386. In the AT, DRAM refresh uses a little over five percent of all available memory accesses, slightly less than it uses in the PC, but in the same ballpark. While the DRAM refresh @@ -123,16 +123,16 @@ generally suffer less from DRAM refresh), the 5 percent figure is a good rule of thumb. Basically, the effect of the DRAM refresh cycle-eater is pretty much the -same throughout the PC-compatible world: fairly small, so it doesn’t -greatly affect performance; unavoidable, so there’s no point in worrying +same throughout the PC-compatible world: fairly small, so it doesn't +greatly affect performance; unavoidable, so there's no point in worrying about it anyway; and a nuisance since it results in fractional cycle counts when using the Zen timer. Just as with the PC, a given code sequence on the AT can execute at varying speeds at different times as a result of the interaction between the code and DRAM refresh. -There’s nothing much new with DRAM refresh on 286/386 computers, then. -Be aware of it, but don’t overly concern yourself—DRAM refresh is still -an act of God, and there’s not a blessed thing you can do about it. +There's nothing much new with DRAM refresh on 286/386 computers, then. +Be aware of it, but don't overly concern yourself—DRAM refresh is still +an act of God, and there's not a blessed thing you can do about it. Happily, the internal caches of the 486 and Pentium make DRAM refresh largely a performance non-issue on those processors. @@ -140,8 +140,8 @@ largely a performance non-issue on those processors. Finally we come to the last of the cycle-eaters, the display adapter cycle-eater. There are two ways of looking at this cycle-eater on -286/386 computers: (1) It’s much worse than it was on the PC, or (2) -it’s just about the same as it was on the PC. +286/386 computers: (1) It's much worse than it was on the PC, or (2) +it's just about the same as it was on the PC. Either way, the display adapter cycle-eater is extremely bad news on 286/386 computers and on 486s and Pentiums as well. In fact, this @@ -149,14 +149,14 @@ cycle-eater on those systems is largely responsible for the popularity of VESA local bus (VLB). The two ways of looking at the display adapter cycle-eater on 286/386 -computers are actually the same. As you’ll recall from my earlier +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 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 +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 relative term. What a VGA does to PC performance is nothing compared to what it does to faster computers. diff --git a/11-05.md b/11-05.md index 9280263..b1b302a 100644 --- a/11-05.md +++ b/11-05.md @@ -10,8 +10,8 @@ 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 consequence of that is that only 1 byte can be read or written per access to display memory; word-sized accesses to 8-bit devices are -automatically split into 2 separate byte-sized accesses by the AT’s bus. -Another consequence is that accesses are simply slower; the AT’s bus +automatically split into 2 separate byte-sized accesses by the AT's bus. +Another consequence is that accesses are simply slower; the AT's bus inserts additional wait states on accesses to 8-bit devices since it must assume that such devices were designed for PCs and may not run reliably at AT speeds. @@ -25,9 +25,9 @@ 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? -That’s right—less than twice as fast. +That's right—less than twice as fast. -In other words, instructions that access display memory won’t run a +In other words, instructions that access display memory won't run a whole lot faster on ATs and faster computers than they do on PCs. That explains one of the two viewpoints expressed at the beginning of this section: The display adapter cycle-eater is just about the same on @@ -40,7 +40,7 @@ performance of instructions that access display memory to the *maximum* performance of those instructions. Instructions that access display memory receive many more wait states when running on a 286 than they do on an 8088. Why? While the 286 is capable of accessing memory much more -often than the 8088, we’ve seen that the frequency of access to display +often than the 8088, we've seen that the frequency of access to display memory is determined not by processor speed but by the display adapter itself. As a result, both processors are actually allowed just about the same maximum number of accesses to display memory in any given time. By @@ -50,19 +50,19 @@ the 8088. And that explains the second viewpoint expressed above regarding the display adapter cycle-eater vis-a-vis the 286 and 386. The display adapter cycle-eater, as measured in cycles lost to wait states, is -indeed much worse on AT-class computers than it is on the PC, and it’s +indeed much worse on AT-class computers than it is on the PC, and it's worse still on more powerful computers. ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *How bad is the display adapter cycle-eater on an AT? It’s this bad: Based on my (not inconsiderable) experience in timing display adapter access, I’ve found that the display adapter cycle-eater can slow an AT—or even a 386 computer—to near-PC speeds when display memory is accessed.* + ![](images/i.jpg) *How bad is the display adapter cycle-eater on an AT? It's this bad: Based on my (not inconsiderable) experience in timing display adapter access, I've found that the display adapter cycle-eater can slow an AT—or even a 386 computer—to near-PC speeds when display memory is accessed.* ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -I know that’s hard to believe, but the display adapter cycle-eater gives +I know that's hard to believe, but the display adapter cycle-eater gives out just so many display memory accesses in a given time, and no more, no matter how fast the processor is. In fact, the faster the processor, the more the display adapter cycle-eater hurts the performance of instructions that access display memory. The display adapter cycle-eater -is not only still present in 286/386 computers, it’s worse than ever. +is not only still present in 286/386 computers, it's worse than ever. What can we do about this new, more virulent form of the display adapter cycle-eater? The workaround is the same as it was on the PC: Access @@ -73,15 +73,15 @@ display memory as little as you possibly can. The 286 and 386 offer a number of new instructions. The 286 has a relatively small number of instructions that the 8088 lacks, while the 386 has those instructions and quite a few more, along with new -addressing modes and data sizes. We’ll discuss the 286 and the 386 +addressing modes and data sizes. We'll discuss the 286 and the 386 separately in this regard. The 286 has a number of instructions designed for protected-mode -operations. As I’ve said, we’re not going to discuss protected mode in +operations. As I've said, we're not going to discuss protected mode in this book; in any case, protected-mode instructions are generally used -only by operating systems. (I should mention that the 286’s protected +only by operating systems. (I should mention that the 286's protected mode brings with it the ability to address 16 MB of memory, a -considerable improvement over the 8088’s 1 MB. In real mode, however, +considerable improvement over the 8088's 1 MB. In real mode, however, programs are still limited to 1 MB of addressable memory on the 286. In either mode, each segment is still limited to 64K.) @@ -104,7 +104,7 @@ constant number of bits. #### New Instructions and Features: The 386 {#Heading14} The 386 is somewhat more complex than the 286 regarding new features. -Once again, we won’t discuss protected mode, which on the 386 comes with +Once again, we won't discuss protected mode, which on the 386 comes with the ability to address up to 4 gigabytes per segment and 64 terabytes in all. In real mode (and in virtual-86 mode, which allows the 386 to multitask MS-DOS applications, and which is identical to real mode so @@ -112,10 +112,10 @@ far as MS-DOS programs are concerned), programs running on the 386 are still limited to 1 MB of addressable memory and 64K per segment. The 386 has many new instructions, as well as new registers, addressing -modes and data sizes that have trickled down from protected mode. Let’s +modes and data sizes that have trickled down from protected mode. Let's take a quick look at these new real-mode features. -Even in real mode, it’s possible to access many of the 386’s new and +Even in real mode, it's possible to access many of the 386's new and extended registers. Most of these registers are simply 32-bit extensions of the 16-bit registers of the 8088. For example, EAX is a 32-bit register containing AX as its lower 16 bits, EBX is a 32-bit register @@ -125,15 +125,15 @@ segment registers: FS and GS. The 386 also comes with a slew of new real-mode instructions beyond those supported by the 8088 and 286. These instructions can scan data on a bit-by-bit basis, set the Carry flag to the value of a specified bit, -sign-extend or zero-extend data as it’s moved, set a register or memory +sign-extend or zero-extend data as it's moved, set a register or memory variable to 1 or 0 on the basis of any of the conditions that can be tested with conditional jumps, and more. (Again, beware: Many of these complex 386-specific instructions are slower than equivalent sequences -of simple instructions on the 486 and especially on the Pentium.) What’s +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**. +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**. ------------------------ --------------------------------- -------------------- [Previous](11-04.html) [Table of Contents](index.html) [Next](11-06.html) diff --git a/11-06.md b/11-06.md index 9a4bbcd..90064e1 100644 --- a/11-06.md +++ b/11-06.md @@ -2,32 +2,32 @@ [Previous](11-05.html) [Table of Contents](index.html) [Next](11-07.html) ------------------------ --------------------------------- -------------------- -Finally, it’s possible in real mode to use the 386’s new addressing +Finally, it's possible in real mode to use the 386's new addressing modes, in which *any* 32-bit general-purpose register or pair of -registers can be used to address memory. What’s more, multiplication of +registers can be used to address memory. What's more, multiplication of memory-addressing registers by 2, 4, or 8 for look-ups in word, doubleword, or quadword tables can be built right into the memory addressing mode. (The 32-bit addressing modes are discussed further in later chapters.) In protected mode, these new addressing modes allow you -to address a full 4 gigabytes per segment, but in real mode you’re still +to address a full 4 gigabytes per segment, but in real mode you're still limited to 64K, even with 32-bit registers and the new addressing modes, unless you play some unorthodox tricks with the segment registers. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Note well: Those tricks don’t necessarily work with system software such as Windows, so I’d recommend against using them. If you want 4-gigabyte segments, use a 32-bit environment such as Win32.* + ![](images/i.jpg) *Note well: Those tricks don't necessarily work with system software such as Windows, so I'd recommend against using them. If you want 4-gigabyte segments, use a 32-bit environment such as Win32.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ #### Optimization Rules: The More Things Change... {#Heading15 align="center"} -Let’s see what we’ve learned about 286/386 optimization. Mostly what -we’ve learned is that our familiar PC cycle-eaters still apply, although +Let's see what we've learned about 286/386 optimization. Mostly what +we've learned is that our familiar PC cycle-eaters still apply, although in somewhat different forms, and that the major optimization rules for -the PC hold true on ATs and 386-based computers. You won’t go wrong on +the PC hold true on ATs and 386-based computers. You won't go wrong on any of these computers if you keep your instructions short, use the -registers heavily and avoid memory, don’t branch, and avoid accessing +registers heavily and avoid memory, don't branch, and avoid accessing display memory like the plague. -Although we haven’t touched on them, repeated string instructions are +Although we haven't touched on them, repeated string instructions are still desirable on the 286 and 386 since they provide a great deal of functionality per instruction byte and eliminate both the prefetch queue cycle-eater and branching. However, string instructions are not quite so @@ -35,8 +35,8 @@ spectacularly superior on the 286 and 386 as they are on the 8088 since non-string memory-accessing instructions have been speeded up considerably on the newer processors. -There’s one cycle-eater with new implications on the 286 and 386, and -that’s the data alignment cycle-eater. From the data alignment +There's one cycle-eater with new implications on the 286 and 386, and +that's the data alignment cycle-eater. From the data alignment cycle-eater we get a new rule: Word-align your word-sized variables, and start your subroutines at even addresses. @@ -58,8 +58,8 @@ 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 -perform the EA calculation, but that’s both the worst case and the only -case in which there’s any EA overhead at all. +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 @@ -69,10 +69,10 @@ time of register-only instructions. For instance, on the 8088 **ADD **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 -the 286 than it was on the 8088, but that’s simply not the case, for -reasons we’ve already seen. The key is this: The 286 can execute -memory-addressing instructions so fast that there’s no spare instruction +It would seem, then, that it's less necessary to use the registers on +the 286 than it was on the 8088, but that's simply not the case, for +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 @@ -82,15 +82,15 @@ and write the result back to memory. 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 +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 bytes at 4 cycles per byte), while the memory-accessing code would run in 40 cycles (6 instruction bytes at 4 cycles per byte, plus 2 -word-sized memory accesses at 8 cycles per word). That’s a ratio of 2.5 +word-sized memory accesses at 8 cycles per word). That's a ratio of 2.5 to 1, *exactly the same as on the 286*. This is all theoretical. We put our trust not in theory but in actual -performance, so let’s run this code through the Zen timer. On a PC, +performance, so let's run this code through the Zen timer. On a PC, Listing 11.4, which performs register-only addition, runs in 3.62 ms, while Listing 11.5, which performs addition to a memory variable, runs in 10.05 ms. On a 10 MHz AT clone, Listing 11.4 runs in 0.64 ms, while diff --git a/11-07.md b/11-07.md index a4d986c..93d8776 100644 --- a/11-07.md +++ b/11-07.md @@ -24,7 +24,7 @@ endm call ZTimerOff -What’s going on? Simply this: Instruction fetching is controlling +What's going on? Simply this: Instruction fetching is controlling overall execution time on *both* processors. Both the 8088 in a PC and the 286 in an AT can execute the bytes of the instructions in Listings 11.4 and 11.5 faster than they can be fetched. Since the instructions @@ -44,13 +44,13 @@ memory-accessing instructions on the 286 and 386 are much faster Given the system wait states prevalent on 286 and 386 computers, however, the prefetch queue is likely to be empty quite a bit, especially when code consisting of instructions with short EU execution -times is executed. Of course, that’s just the sort of code we’re likely -to write when we’re optimizing, so the performance of high-speed code is +times is executed. Of course, that's just the sort of code we're likely +to write when we're optimizing, so the performance of high-speed code is more likely to be controlled by instruction size than by EU execution time on most 286 and 386 computers, just as it is on the PC. All of which is just a way of saying that faster memory access and EA -calculation notwithstanding, it’s just as desirable to keep instructions +calculation notwithstanding, it's just as desirable to keep instructions short and memory accesses to a minimum on the 286 and 386 as it is on the 8088. And the way to do that is to use the registers as heavily as possible, use string instructions, use short forms of instructions, and @@ -60,45 +60,45 @@ 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 +We've one final 286-related item to discuss: the hardware malfunction of **POPF** under certain circumstances on the 286. 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 -don’t want to blow this particular bug out of proportion. It only causes +don't want to blow this particular bug out of proportion. It only causes problems in code that cannot tolerate interrupts under any -circumstances, and that’s a rare sort of code, especially in user +circumstances, and that's a rare sort of code, especially in user programs. However, some code really does need to have interrupts 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 +few hundred microseconds of delay could result in a sector's worth of 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 -well, so you won’t want to use it in code that can tolerate interrupts. +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 +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. 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 +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**. diff --git a/11-08.md b/11-08.md index ee4e501..c2c0e29 100644 --- a/11-08.md +++ b/11-08.md @@ -2,10 +2,10 @@ [Previous](11-07.html) [Table of Contents](index.html) [Next](12-01.html) ------------------------ --------------------------------- -------------------- -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 down on the stack, not the word on top of the stack, as is the case with @@ -14,13 +14,13 @@ 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 -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 +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 segment:offset of the instruction after our workaround code onto the 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 +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 can accidentally occur when the Interrupt flag is 0 both before and after the pop. @@ -32,7 +32,7 @@ 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 -a segment and an offset. We’ll also branch backward so that the address +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: @@ -72,7 +72,7 @@ shrinking the workaround code by 1 byte: call popfiret endm -By the way, the flags can be popped much more quickly if you’re willing +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: @@ -83,11 +83,11 @@ emulates **POPF** with just one branch, but wipes out AX: iret 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 -you can spare the register. If you’re using 286-specific instructions, +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.) .286 @@ -102,16 +102,16 @@ an 8088.) **Figure 11.6**  *Workaround code for the POPF bug.* The standard version of **EMULATE\_POPF** is 6 bytes longer than -**POPF** and much slower, as you’d expect given that it involves three +**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. +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. -Whether you ever need the workaround or not, it’s a neatly packaged +Whether you ever need the workaround or not, it's a neatly packaged example of the tremendous flexibility of the x86 instruction set. ------------------------ --------------------------------- -------------------- diff --git a/12-01.md b/12-01.md index 8ce2498..4b6ab9c 100644 --- a/12-01.md +++ b/12-01.md @@ -6,43 +6,43 @@ Chapter 12\ Pushing the 486 {#Heading1} ---------------- -### It’s Not Just a Bigger 386 {#Heading2} +### It's Not Just a Bigger 386 {#Heading2} So this traveling salesman is walking down a road, and he sees a group -of men digging a ditch with their bare hands. “Whoa, there!” he says. -“What you guys need is a Model 8088 ditch digger!” And he whips out a +of men digging a ditch with their bare hands. "Whoa, there!" he says. +"What you guys need is a Model 8088 ditch digger!" And he whips out a trowel and sells it to them. -A few days later, he stops back around. They’re happy with the trowel, +A few days later, he stops back around. They're happy with the trowel, but he sells them the latest ditch-digging technology, the Model 80286 spade. That keeps them content until he stops by again with a Model 80386 shovel (a full 32 inches wide, with a narrow point to emulate the trowel), and *that* holds them until he comes back around with what they really need: a Model 80486 bulldozer. -Having reached the top of the line, the salesman doesn’t pay them a call +Having reached the top of the line, the salesman doesn't pay them a call for a while. When he does, not only are they none too friendly, but -they’re digging with the 80386 shovel; the bulldozer is sitting off to -one side. “Why on earth are you using that shovel?” the salesman asks. -“Why aren’t you digging with the bulldozer?” +they're digging with the 80386 shovel; the bulldozer is sitting off to +one side. "Why on earth are you using that shovel?" the salesman asks. +"Why aren't you digging with the bulldozer?" -“Well, Lord knows we tried,” says the foreman, “but it was all we could -do just to lift the damn thing!” +"Well, Lord knows we tried," says the foreman, "but it was all we could +do just to lift the damn thing!" -Substitute “processor” for the various digging implements, and you get +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 +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 -sank out of sight in a swirl of hopelessly mixed metaphors), “I don’t -think we’re in Kansas anymore, Toto.” +sank out of sight in a swirl of hopelessly mixed metaphors), "I don't +think we're in Kansas anymore, Toto." #### Enter the 486 {#Heading3} No chip that is a direct, fully compatible descendant of the 8088, 286, and 386 could ever be called a RISC chip, but the 486 certainly contains -RISC elements, and it’s those elements that are most responsible for +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 @@ -57,23 +57,23 @@ 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 +itself, so in this chapter I'm only going to hit the highlights of 486 optimization, touching on several optimization rules, some documented, some not. You might also want to check out the following sources of 486 -information: *i486 Microprocessor Programmer’s Reference Manual,* from -Intel; “8086 Optimization: Aim Down the Middle and Pray,” in the March, -1991 *Dr. Dobb’s Journal*; and “Peak Performance: On to the 486,” in the -November, 1990 *Programmer’s Journal.* +information: *i486 Microprocessor Programmer's Reference Manual,* from +Intel; "8086 Optimization: Aim Down the Middle and Pray," in the March, +1991 *Dr. Dobb's Journal*; and "Peak Performance: On to the 486," in the +November, 1990 *Programmer's Journal.* ### Rules to Optimize By {#Heading4} -In Appendix G of the *i486 Microprocessor Programmer*’*s* *Reference +In Appendix G of the *i486 Microprocessor Programmer*'*s* *Reference Manual*, Intel lists a number of optimization techniques for the 486. -While neither exhaustive (we’ll look at two undocumented optimizations -shortly) nor entirely accurate (we’ll correct two of the rules here), -Intel’s list is certainly a good starting point. In particular, the list +While neither exhaustive (we'll look at two undocumented optimizations +shortly) nor entirely accurate (we'll correct two of the rules here), +Intel's list is certainly a good starting point. In particular, the list conveys the extent to which 486 optimization differs from optimization -for earlier x86 processors. Generally, I’ll be discussing optimization +for earlier x86 processors. Generally, I'll be discussing optimization for real mode (it being the most widely used mode at the moment), although many of the rules should apply to protected mode as well. @@ -84,7 +84,7 @@ although many of the rules should apply to protected mode as well. In other words, for cached code (which time-critical code almost always is), performance is predictable and can be calculated with good precision, and those calculations will apply on any 486. However, -“predictable” doesn’t mean “trivial”; the cycle times printed for the +"predictable" doesn't mean "trivial"; the cycle times printed for the various instructions are not the whole story. You must be aware of all the rules, documented and undocumented, that go into calculating actual execution times—and uncovering some of those rules is exactly what this @@ -95,15 +95,15 @@ chapter is about. Rule \#1: Avoid indexed addressing (that is, try not to use either two registers or scaled addressing to point to memory). -Intel cautions against using indexing to address memory because there’s -a one-cycle penalty for indexed addressing. True enough—but “indexed -addressing” might not mean what you expect. +Intel cautions against using indexing to address memory because there's +a one-cycle penalty for indexed addressing. True enough—but "indexed +addressing" might not mean what you expect. Traditionally, SI and DI are considered the index registers of the x86 -CPUs. That is not the sense in which “indexed addressing” is meant here, +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 +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. diff --git a/12-02.md b/12-02.md index 5e3f514..8ed43a7 100644 --- a/12-02.md +++ b/12-02.md @@ -18,7 +18,7 @@ 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 -they’re not hampered by something like indexed addressing, in which case +they're not hampered by something like indexed addressing, in which case they slow to half speed (or worse, as we will see shortly). For example, consider the summing example shown earlier. The version @@ -32,7 +32,7 @@ In a key loop on the 486, 1 cycle can indeed matter. #### Calculate Memory Pointers Ahead of Time {#Heading6} -Rule \#2: Don’t use a register as a memory pointer during the next two +Rule \#2: Don't use a register as a memory pointer during the next two cycles after loading it. Intel states that if the destination of one instruction is used as the @@ -44,16 +44,16 @@ start of the instruction that will need it, as shown in Figure 12.1; this effectively makes the address calculation time vanish, because it happens while the preceding instruction executes. -Of course, the 486 *can’t* perform an effective address calculation for +Of course, the 486 *can't* perform an effective address calculation for a target instruction ahead of time if one of the address components -isn’t known until the instruction starts, and that’s exactly the case -when the preceding instruction modifies one of the target instruction’s +isn't known until the instruction starts, and that's exactly the case +when the preceding instruction modifies one of the target instruction's addressing registers. For example, in the code MOV BX,OFFSET MemVar MOV AX,[BX] -there’s no way that the 486 can calculate the address referenced by +there's no way that the 486 can calculate the address referenced by **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 @@ -78,7 +78,7 @@ Now that we understand what Intel means by this rule, let me make a very important comment: My observations indicate that for real-mode code, the documentation understates the extent of the penalty for interrupting the address calculation pipeline by loading a memory pointer just before -it’s used. +it's used. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ![](images/i.jpg) *The truth of the matter appears to be that if a register is the destination of one instruction and is then used by the next instruction to address memory in real mode, not one but two cycles are lost!* @@ -87,17 +87,17 @@ 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 +improvement in the performance of the entire loop. But wait, there's more. If a register is loaded 2 cycles (which generally means 2 instructions, but, because some 486 instructions take more than 1 cycle, ![](images/12-01.jpg)\ **Figure 12.1**  *One-cycle-ahead address pipelining.* -the 2 are not always equivalent) before it’s used to point to memory, 1 +the 2 are not always equivalent) before it's used to point to memory, 1 cycle is lost. Therefore, whereas this code mov bx,offset MemVar @@ -123,16 +123,16 @@ loses only one cycle, and this code mov ax,[bx] jnz LoopTop -loses no cycles at all. Apparently, the 486’s addressing calculation +loses no cycles at all. Apparently, the 486's addressing calculation pipeline actually starts 2 cycles ahead, as shown in Figure 12.2. (In truth, my best guess at the moment is that the addressing pipeline really does start only 1 cycle ahead; the additional cycle crops up when the addressing pipeline has to wait for a register to be written into the register file before it can read it out for use in addressing -calculations. However, I’m guessing here, and the 2-cycle-ahead model in +calculations. However, I'm guessing here, and the 2-cycle-ahead model in Figure 12.2 will do just fine for optimization purposes.) -Clearly, there’s considerable optimization potential in careful +Clearly, there's considerable optimization potential in careful rearrangement of 486 code. ![](images/12-02.jpg)\ @@ -140,17 +140,17 @@ rearrangement of 486 code. ### Caveat Programmor {#Heading7} -A caution: I’m quite certain that the 2-cycle-ahead addressing pipeline -interruption penalty I’ve described exists in the two 486s I’ve tested. -However, there’s no guarantee that Intel won’t change this aspect of the +A caution: I'm quite certain that the 2-cycle-ahead addressing pipeline +interruption penalty I've described exists in the two 486s I've tested. +However, there's no guarantee that Intel won't change this aspect of the 486 in the future, especially given that the documentation indicates otherwise. Perhaps the 2-cycle penalty is the result of a bug in the initial steps of the 486, and will revert to the documented 1-cycle -penalty someday; likewise for the undocumented optimizations I’ll +penalty someday; likewise for the undocumented optimizations I'll describe below. Nonetheless, none of the optimizations I suggest would hurt performance even if the undocumented performance characteristics of the 486 were to vanish, and they certainly will help performance on at -least some 486s right now, so I feel they’re well worth using. +least some 486s right now, so I feel they're well worth using. ------------------------ --------------------------------- -------------------- [Previous](12-01.html) [Table of Contents](index.html) [Next](12-03.html) diff --git a/12-03.md b/12-03.md index d1014ec..4d9c816 100644 --- a/12-03.md +++ b/12-03.md @@ -2,11 +2,11 @@ [Previous](12-02.html) [Table of Contents](index.html) [Next](12-04.html) ------------------------ --------------------------------- -------------------- -There is, of course, no guarantee that I’m entirely correct about the +There is, of course, no guarantee that I'm entirely correct about the optimizations discussed in this chapter. Without knowing the internals of the 486, all I can do is time code and make inferences from the results; I invite you to deduce your own rules and cross-check them -against mine. Also, most likely there are other optimizations that I’m +against mine. Also, most likely there are other optimizations that I'm unaware of. If you have further information on these or any other undocumented optimizations, please write and let me know. And, of course, if anyone from Intel is reading this and wants to give us the @@ -27,11 +27,11 @@ 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 other instructions to avoid interrupting the addressing pipeline. -Fortunately, matters are actually not so grim as Intel’s documentation +Fortunately, matters are actually not so grim as Intel's documentation would indicate; my tests indicate that the addressing pipeline penalty pops up only spottily when the stack pointer is involved. -For example, you’d certainly expect a sequence such as +For example, you'd certainly expect a sequence such as : pop ax @@ -69,11 +69,11 @@ the sequence loses two cycles for the same reason. -I certainly haven’t tried all possible combinations, but the results so +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 +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 @@ -84,7 +84,7 @@ pointer to address memory. #### Problems with Byte Registers {#Heading9} There are two ways to lose cycles by using byte registers, and neither -of them is documented by Intel, so far as I know. Let’s start with the +of them is documented by Intel, so far as I know. Let's start with the lesser and simpler of the two. Rule \#3: Do not load a byte portion of a register during one @@ -101,7 +101,7 @@ So, for example, it would be a bad idea to do this because AL is loaded by one instruction, then AX is used as the source register for the next instruction. A cycle can be saved simply by -rearranging the instructions so that the byte register load isn’t +rearranging the instructions so that the byte register load isn't immediately followed by the word register usage, like so: mov ah,o @@ -115,16 +115,16 @@ Basically, when a byte destination register is part of a word source register for the next instruction, the 486 is unable to directly use the result from the first instruction as the source for the second instruction, because only part of the register required by the second -instruction is contained in the first instruction’s result. The full, +instruction is contained in the first instruction's result. The full, updated register value must be read from the register file, and that -value can’t be read out until the result from the first instruction has +value can't be read out until the result from the first instruction has been written *into* the register file, a process that takes an extra -cycle. I’m not going to explain this in great detail because it’s not +cycle. I'm not going to explain this in great detail because it's not 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 mov bx,offset MemTable @@ -139,17 +139,17 @@ 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.* + ![](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.* ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -In general, penalties for interrupting the 486’s pipeline apply +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 -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 +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 you should concentrate on using those fast core instructions when -performance matters, and all the rules I’ll discuss do indeed apply to +performance matters, and all the rules I'll discuss do indeed apply to those instructions. ------------------------ --------------------------------- -------------------- diff --git a/12-04.md b/12-04.md index 7bee0cb..55c06a9 100644 --- a/12-04.md +++ b/12-04.md @@ -2,17 +2,17 @@ [Previous](12-03.html) [Table of Contents](index.html) [Next](13-01.html) ------------------------ --------------------------------- -------------------- -You don’t need to understand every corner of the 486 universe unless -you’re a diehard ASMhead who does this stuff for fun. Just learn enough +You don't need to understand every corner of the 486 universe unless +you're a diehard ASMhead who does this stuff for fun. Just learn enough to be able to speed up the key portions of your programs, and spend the rest of your time on a fast design and overall implementation. #### More Fun with Byte Registers {#Heading10} -Rule \#4: Don’t load *any* byte register exactly 2 cycles before using +Rule \#4: Don't load *any* byte register exactly 2 cycles before using *any* register to address memory. -This, the last of this chapter’s rules, is the strangest of the lot. If +This, the last of this chapter's rules, is the strangest of the lot. If any byte register is loaded, and then two cycles later any register is used to point to memory, one cycle is lost. So, for example, this code @@ -43,7 +43,7 @@ runs in the expected three cycles. In truth, I do not know why this happens. Clearly, it has something to do with interrupting the start of the addressing pipeline, and I have my -theories about how this works, but at this point they’re pure +theories about how this works, but at this point they're pure speculation. Whatever the reason for this rule, ignorance of it—and of its interaction with the other rules—could lead to considerable performance loss in seemingly air-tight code. For instance, a casual @@ -68,7 +68,7 @@ pipeline is now on its first cycle: the one that loading a byte register can affect. ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *I know—it seems awfully complicated. It isn’t, really. Generally, try not to use byte destinations exactly two cycles before using a register to address memory, and try not to load a register either one or two cycles before using it to address memory, and you’ll be fine.* + ![](images/i.jpg) *I know—it seems awfully complicated. It isn't, really. Generally, try not to use byte destinations exactly two cycles before using a register to address memory, and try not to load a register either one or two cycles before using it to address memory, and you'll be fine.* ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- #### Timing Your Own 486 Code {#Heading11} @@ -82,7 +82,7 @@ address memory. Listing 12.1 ran in 120 µs on a 33 MHz 486, or 4 cycles per repetition (120 µs/1000 repetitions = 120 ns per repetition; 120 ns per repetition/30 ns per cycle = 4 cycles per repetition); Listing 12.2 ran in 90 µs, or 3 cycles, establishing that loading a byte register -costs a cycle only when it’s performed exactly 2 cycles before +costs a cycle only when it's performed exactly 2 cycles before addressing memory. **LISTING 12.1 LST12-1.ASM** @@ -90,7 +90,7 @@ addressing memory. ; Measures the effect of loading a byte register 2 cycles before ; using a register to address memory. mov bp,2 ;run the test code twice to make sure - ; it’s cached + ; it's cached sub bx,bx CacheFillLoop: call ZTimerOn ;start timing @@ -110,7 +110,7 @@ addressing memory. ; Measures the effect of loading a byte register 1 cycle before ; using a register to address memory. mov bp,2 ;run the test code twice to make sure - ; it’s cached + ; it's cached sub bx,bx CacheFillLoop: call ZTimerOn ;start timing @@ -129,31 +129,31 @@ 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 bytes. ------------------- ----------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Whenever you see non-integral timing results of this sort, it’s a good bet that the test code or data isn’t cached.* + ![](images/i.jpg) *Whenever you see non-integral timing results of this sort, it's a good bet that the test code or data isn't cached.* ------------------- ----------------------------------------------------------------------------------------------------------------------- ### The Story Continues {#Heading12} -There’s certainly plenty more 486 lore to explore, including the 486’s +There's certainly plenty more 486 lore to explore, including the 486's unique prefetch queue, more optimization rules, branching optimizations, performance implications of the cache, the cost of cache misses for reads, and the implications of cache write-through for writes. -Nonetheless, we’ve covered quite a bit of ground in this chapter, and I -trust you’ve gotten a feel for the considerable extent to which 486 -optimization differs from what you’re used to. Odd as 486 optimization -is, though, it’s well worth mastering, for the 486 is, at its best, so +Nonetheless, we've covered quite a bit of ground in this chapter, and I +trust you've gotten a feel for the considerable extent to which 486 +optimization differs from what you're used to. Odd as 486 optimization +is, though, it's well worth mastering, for the 486 is, at its best, so staggeringly fast that carefully crafted 486 code can do more than twice as much per cycle as the best 386 code—which makes it perhaps 50 times as fast as optimized code for the original PC. -Sometimes it *is* hard to believe we’re still in Kansas! +Sometimes it *is* hard to believe we're still in Kansas! ------------------------ --------------------------------- -------------------- [Previous](12-03.html) [Table of Contents](index.html) [Next](13-01.html) diff --git a/13-01.md b/13-01.md index f415831..03a4306 100644 --- a/13-01.md +++ b/13-01.md @@ -8,15 +8,15 @@ Chapter 13\ ### Pipelines and Other Hazards of the High End {#Heading2} -It’s a sad but true fact that 84 percent of American schoolchildren are +It's a sad but true fact that 84 percent of American schoolchildren are ignorant of 92 percent of American history. Not my daughter, though. We recently visited historical Revolutionary-War-vintage Fort Ticonderoga, -and she’s now 97 percent aware of a key element of our national +and she's now 97 percent aware of a key element of our national heritage: that the basic uniform for soldiers in those days was what appears to be underwear, plus a hat so that no one could complain that they were undermining family values. Ha! Just kidding! Actually, what she learned was that in those days, it was pure coincidence if a -cannonball actually hit anything it was aimed at, which isn’t surprising +cannonball actually hit anything it was aimed at, which isn't surprising considering the lack of rifling, precision parts, and ballistics. The guides at the fort shot off three cannons; the closest they came to the target was about 50 feet, and that was only because the wind helped. I @@ -26,21 +26,21 @@ necessarily, the enemy. Nowadays, of course, we have automatic weapons that allow a teenager to singlehandedly defeat the entire U.S. Army, not to mention so-called -“smart” bombs, which are smart in the sense that they can seek out and -empty a taxpayer’s wallet without being detected by radar. There’s an +"smart" bombs, which are smart in the sense that they can seek out and +empty a taxpayer's wallet without being detected by radar. There's an obvious lesson here about progress, which I leave you to deduce for yourselves. -Here’s the same lesson, in another form. Ten years ago, we had a slow +Here's the same lesson, in another form. Ten years ago, we had a slow processor, the 8088, for which it was devilishly hard to optimize, and for which there was no good optimization documentation available. Now we -have a processor, the 486, that’s 50 to 100 times faster than the +have a processor, the 486, that's 50 to 100 times faster than the 8088—and for which there is no good optimization documentation available. Sure, Intel provides a few tidbits on optimization in the -back of the *i486 Microprocessor Programmer’s Reference Manual,* but, as +back of the *i486 Microprocessor Programmer's Reference Manual,* but, as I discussed in Chapter 12, that information is both incomplete and not -entirely correct. Besides, most assembly language programmers don’t -bother to read Intel’s manuals (which are extremely informative and well +entirely correct. Besides, most assembly language programmers don't +bother to read Intel's manuals (which are extremely informative and well done, but only slightly more fun to read than the phone book), and go right on programming the 486 using outdated 8088 optimization techniques, blissfully unaware of a new and heavily mutated generation @@ -53,20 +53,20 @@ instructions. #### 486 Pipeline Optimization {#Heading3} -I’ve mentioned Terje Mathisen in my writings before. Terje is an +I've mentioned Terje Mathisen in my writings before. Terje is an assembly language programmer extraordinaire, and author of the incredibly fast public-domain word-counting program WC (which comes complete with source code; well worth a look, if you want to see what -*really* fast code looks like). Terje’s a regular participant in the -ibm.pc/fast.code topic on Bix. In a thread titled “486 Pipeline -Optimization, or TANSTATFC (There Ain’t No Such Thing As The Fastest -Code),” he detailed the following optimization to WC, perhaps the best -example of 486 pipeline optimization I’ve yet seen. +*really* fast code looks like). Terje's a regular participant in the +ibm.pc/fast.code topic on Bix. In a thread titled "486 Pipeline +Optimization, or TANSTATFC (There Ain't No Such Thing As The Fastest +Code)," he detailed the following optimization to WC, perhaps the best +example of 486 pipeline optimization I've yet seen. -Terje’s inner loop originally looked something like the code in Listing -13.1. (I’ve taken a few liberties for illustrative purposes.) Of course, +Terje's inner loop originally looked something like the code in Listing +13.1. (I've taken a few liberties for illustrative purposes.) Of course, Terje unrolls this loop a few times (128 times, to be exact). By the -way, in Listing 13.1 you’ll notice that Terje counts not only words but +way, in Listing 13.1 you'll notice that Terje counts not only words but also lines, at a rate of three instructions for every two characters! **LISTING 13.1 L13-1.ASM** @@ -81,13 +81,13 @@ instructions, one two-cycle instruction, and no branches. It *is* tight, but those three instructions actually take a minimum of 8 cycles to execute, as shown in Figure 13.1. The problem is that DI is loaded just before being used to address memory, and that costs 2 cycles because it -interrupts the 486’s internal instruction pipeline. Likewise, BX is +interrupts the 486's internal instruction pipeline. Likewise, BX is loaded just before being used to address memory, costing another two cycles. Thus, this loop takes twice as long as cycle counts would seem to indicate, simply because two registers are loaded immediately before -being used, disrupting the 486’s pipeline. +being used, disrupting the 486's pipeline. -Listing 13.2 shows Terje’s immediate response to these pipelining +Listing 13.2 shows Terje's immediate response to these pipelining problems; he simply swapped the instructions that load DI and BL. This one change cut execution time per character pair from eight cycles to five cycles! The load of BL is now separated by one instruction from the @@ -95,7 +95,7 @@ use of BX to address memory, so the pipeline penalty is reduced from two cycles to one cycle. The load of DI is also separated by one instruction from the use of DI to address memory (remember, the loop is unrolled, so the last instruction is followed by the first instruction), but because -the intervening instruction takes two cycles, there’s no penalty at all. +the intervening instruction takes two cycles, there's no penalty at all. ![](images/13-01.jpg)\ **Figure 13.1**  *Cycle-eaters in the original WC.* @@ -114,7 +114,7 @@ the intervening instruction takes two cycles, there’s no penalty at all. At this point, Terje had nearly doubled the performance of this code simply by moving one instruction. (Note that swapping the instructions also made it necessary to preload DI at the start of the loop; Listing -13.2 is not exactly equivalent to Listing 13.1.) I’ll let Terje describe +13.2 is not exactly equivalent to Listing 13.1.) I'll let Terje describe his next optimization in his own words: ------------------------ --------------------------------- -------------------- diff --git a/13-02.md b/13-02.md index 3a01d8a..df61863 100644 --- a/13-02.md +++ b/13-02.md @@ -2,7 +2,7 @@ [Previous](13-01.html) [Table of Contents](index.html) [Next](13-03.html) ------------------------ --------------------------------- -------------------- -“When I looked closely as this, I realized that the two cycles for the +"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, 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 @@ -10,7 +10,7 @@ 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** 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.” +throughput of two cycles/char." **LISTING 13.3 L13-3.ASM** @@ -27,11 +27,11 @@ throughput of two cycles/char.” ; appropriately for the pair mov ax,[bx+8000h] ;get increments for next time -I’d like to point out two fairly remarkable things. First, the single +I'd like to point out two fairly remarkable things. First, the single cycle that Terje saved in Listing 13.4 sped up his entire word-counting engine by 25 percent or more; Listing 13.4 is fully twice as fast as Listing 13.1—all the result of nothing more than shifting an instruction -and splitting another into two operations. Second, Terje’s word-counting +and splitting another into two operations. Second, Terje's word-counting engine can process more than 16 million characters *per second* on a 486/33. @@ -55,7 +55,7 @@ significant byte first in memory, or *big endian*), like so: **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 that. @@ -76,14 +76,14 @@ the obvious conclusion that it would be great if there were some way to use the upper and lower 16 bits of selected 386 registers as separate 16-bit registers, effectively increasing the available register space. -Unfortunately, the x86 instruction set doesn’t provide any way to work +Unfortunately, the x86 instruction set doesn't provide any way to work directly with only the upper half of a 32-bit register. The next best solution is to rotate the register to give you access in the lower 16 bits to the half you need at any particular time, with code along the lines of that in Listing 13.5. Having to rotate the 16-bit fields into -position certainly isn’t as good as having direct access to the upper -half, but surely it’s better than having to get the values out of -memory, isn’t it? +position certainly isn't as good as having direct access to the upper +half, but surely it's better than having to get the values out of +memory, isn't it? **LISTING 13.5 L13-5.ASM** @@ -102,12 +102,12 @@ memory, isn’t it? 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 +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 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 +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 making the stack frame inaccessible). ------------------------ --------------------------------- -------------------- diff --git a/13-03.md b/13-03.md index 443dac7..a6806bc 100644 --- a/13-03.md +++ b/13-03.md @@ -47,13 +47,13 @@ and the only cost is that the previous contents of AX are destroyed. Likewise, popping a memory location takes six cycles, but popping a register and writing it to memory takes only two cycles combined. The -*i486 Microprocessor Programmer’s Reference Manual* lists a 4-cycle +*i486 Microprocessor Programmer's Reference Manual* lists a 4-cycle execution time for popping a register, but pay that no mind; popping a register takes only 1 cycle. Why is it that such a convenient operation as pushing or popping memory 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 +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 @@ -61,7 +61,7 @@ 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.* + ![](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.* ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### Optimal 1-Bit Shifts and Rotates {#Heading6} @@ -72,9 +72,9 @@ 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 -rotates. That’s not unreasonable since the 1-bit form is a byte shorter +rotates. That's not unreasonable since the 1-bit form is a byte shorter and is just as fast as the n-bit forms on a 386 and faster on a 286, and -the n-bit form doesn’t even exist on an 8088. In a really critical loop, +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 @@ -92,14 +92,14 @@ code as follows: 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, -I’d recommend using a macro, rather than sticking DBs in the middle of +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 +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 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 +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 486—that is, if you need even more performance than you get from unoptimized code on a 486—you almost certainly need all the speed you can get. diff --git a/13-04.md b/13-04.md index 4454cd8..835202c 100644 --- a/13-04.md +++ b/13-04.md @@ -15,7 +15,7 @@ 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 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 +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 protected mode; 32-bit instructions may be used in real mode, although each instruction that uses 32-bit addressing must have an address-size @@ -26,18 +26,18 @@ register except ESP may also serve as the index register, which can be scaled by 1, 2, 4, or 8. (Scaling is very handy for performing lookups in arrays and tables.) The same register may serve as both base and index register, except for ESP, which can only be the base. -Incidentally, it makes sense that ESP can’t be scaled; ESP presumably -always points to a valid stack, and I can’t think of any reason you’d +Incidentally, it makes sense that ESP can't be scaled; ESP presumably +always points to a valid stack, and I can't think of any reason you'd want to use the stack pointer times 2, 4, or 8 in an address. ESP is, by its nature, a base rather than index pointer. -That’s all there is to the functionality of 32-bit addressing; it’s very +That's all there is to the functionality of 32-bit addressing; it's very simple, much simpler than 16-bit addressing, with its sharply limited memory addressing register combinations. The costs of 32-bit addressing are a bit more subtle. The only performance cost (apart from the 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 +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. @@ -45,7 +45,7 @@ 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 byte, which Intel calls the Mod-R/M byte, which is placed immediately -after each instruction’s opcode to describe the memory addressing mode, +after each instruction's opcode to describe the memory addressing mode, plus 1 or 2 optional bytes of addressing displacement—that is, a 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 @@ -60,15 +60,15 @@ example, **MOV AL, [EBX]** is a 2-byte instruction; **MOV AL, ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- However, because 32-bit addressing supports many more addressing -combinations than 16-bit addressing, the Mod-R/M byte can’t describe all +combinations than 16-bit addressing, the Mod-R/M byte can't describe all the combinations. Therefore, whenever an index register (as described above) is involved, a second byte, the SIB byte, follows the Mod-R/M byte to provide additional address information. Consequently, whenever you use a scaled memory addressing register or use the sum of two registers to point to memory, you automatically add 1 cycle and 1 byte -to that instruction. This is not to say that you shouldn’t use index -registers when they’re needed, but if you find yourself using them -inside key loops, you should see if it’s possible to move the index +to that instruction. This is not to say that you shouldn't use index +registers when they're needed, but if you find yourself using them +inside key loops, you should see if it's possible to move the index calculation outside the loop as, for example, in a loop like this: LoopTop: @@ -87,7 +87,7 @@ You could change this to the following for greater performance: jnz LoopTop shr ebx,1 ;ebx*2/2 -I’ll end this chapter with two more quirks of 32-bit addressing. First, +I'll end this chapter with two more quirks of 32-bit addressing. First, as with 16-bit addressing, addressing that uses EBP as a base register both accesses the SS segment by default and always has a displacement of at least 1 byte. This reflects the common use of EBP to address a stack @@ -97,12 +97,12 @@ address non-stack memory. Lastly, as I mentioned, ESP cannot be scaled. In fact, ESP cannot be an index register; it must be a base register. Ironically, however, ESP is the one register that cannot be used to address memory without the -presence of an SIB byte, even if it’s used without an index register. +presence of an SIB byte, even if it's used without an index register. This is an outcome of the way in which the SIB byte extends the -capabilities of the Mod-R/M byte, and there’s nothing to be done about -it, but it’s at least worth noting that ESP-based, non-indexed +capabilities of the Mod-R/M byte, and there's nothing to be done about +it, but it's at least worth noting that ESP-based, non-indexed addressing makes for instructions that are a byte larger than other -non-indexed addressing (but not any slower; there’s no 1-cycle penalty +non-indexed addressing (but not any slower; there's no 1-cycle penalty for using ESP as a base register) on the 486. ------------------------ --------------------------------- -------------------- diff --git a/14-01.md b/14-01.md index b1b4a00..d39c61d 100644 --- a/14-01.md +++ b/14-01.md @@ -10,10 +10,10 @@ Chapter 14\ When you seem to be stumped, stop for a minute and *think.* All the information you need may be right in front of your nose if you just look -at things a little differently. Here’s a case in point: +at things a little differently. Here's a case in point: When I was in college, I used to stay around campus for the summer. Oh, -I’d take a course or two, but mostly it was an excuse to hang out and +I'd take a course or two, but mostly it was an excuse to hang out and have fun. In that spirit, my girlfriend, Adrian (*not* my future wife, partly for reasons that will soon become apparent), bussed in to spend a week, sharing a less-than-elegant \$150 per month apartment with me and, @@ -29,57 +29,57 @@ so we thought)—had never been cleaned. By which I mean that I know for a certainty that *we* had never cleaned it, but I suspect that it had in fact not been cleaned since the Late Jurassic, or possibly earlier. Our feet tended to stick to it; had the apartment suddenly turned -upside-down, I think we’d all have been hanging from the ceiling. +upside-down, I think we'd all have been hanging from the ceiling. One day, my roommate and I returned from a pick-up basketball game. Adrian, having been left to her own devices for a couple of hours, had -apparently kept herself busy. “Notice anything?” she asked, with an edge +apparently kept herself busy. "Notice anything?" she asked, with an edge to her voice that suggested we had damned well better. -“Uh, you cooked dinner?” I guessed. “Washed the dishes? Had your hair -done?” My roommate was equally without a clue. +"Uh, you cooked dinner?" I guessed. "Washed the dishes? Had your hair +done?" My roommate was equally without a clue. -She stamped her foot (really; the only time I’ve ever seen it happen), -and said, “No, you jerks! The kitchen floor! Look at the floor! I -cleaned it!” +She stamped her foot (really; the only time I've ever seen it happen), +and said, "No, you jerks! The kitchen floor! Look at the floor! I +cleaned it!" The floor really did look amazing. It was actually all white; the black lines had been grooves filled with dirt. We assured her that it looked -terrific, it just wasn’t that obvious until you knew to look for it; -anyone would tell you that it wasn’t the kind of thing that jumped out +terrific, it just wasn't that obvious until you knew to look for it; +anyone would tell you that it wasn't the kind of thing that jumped out at you, but it really was great, no kidding. We had almost smoothed things over, when a friend walked in, looked around with a start, and -said, “Hey! Did you guys put in a new floor?” +said, "Hey! Did you guys put in a new floor?" As I said, sometimes everything you need to know is right in front of your nose. Which brings us to Boyer-Moore string searching. ### String Searching Refresher {#Heading3} -I’ve discussed string searching earlier in this book, in Chapters 5 and +I've discussed string searching earlier in this book, in Chapters 5 and 9. You may want to refer back to these chapters for some background on -string searching in general. I’m also going to use some of the code from -that chapter as part of this chapter’s test suite. For further +string searching in general. I'm also going to use some of the code from +that chapter as part of this chapter's test suite. For further 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 -should be “j \>= 0,” unless I’m missing something.) +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 a particular sequence of bytes (the pattern) within another sequence of 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 +found or the buffer is exhausted. There's even a nifty string +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. -Typically, the buffer will contain a wide variety of bytes. Let’s assume +We have some important information that we're not yet using, though. +Typically, the buffer will contain a wide variety of bytes. Let's assume that the buffer contains text, in which case there will be dozens of -different characters; and although the distribution of characters won’t +different characters; and although the distribution of characters won't 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 @@ -89,37 +89,37 @@ 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. -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 +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 information you need may be under your nose. ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Formally, you don’t know a blessed thing about the search buffer, but experience, common sense, and your knowledge of the application give you a great deal of useful, if somewhat imprecise, information.* + ![](images/i.jpg) *Formally, you don't know a blessed thing about the search buffer, but experience, common sense, and your knowledge of the application give you 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 +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 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** +lower probabilities of matching than others. In a normal buffer, ‘T' +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 +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 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.** -At this point, we’re pretty much searching at the speed of **REPNZ +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 +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? Actually, yes, we can. diff --git a/14-02.md b/14-02.md index 8be48d7..ede43b4 100644 --- a/14-02.md +++ b/14-02.md @@ -5,7 +5,7 @@ ### The Boyer-Moore Algorithm {#Heading4} All our *a priori* knowledge of string searching is stated above, but -there’s another sort of knowledge—knowledge that’s generated +there's another sort of knowledge—knowledge that's generated dynamically. As we search through the buffer, we acquire information each time we check for a match. One sort of information that we acquire is based on partial matches; we can often skip ahead after partial @@ -14,23 +14,23 @@ already implicitly done a comparison of the partially matched buffer characters with all possible pattern start locations that overlap those partially-matched bytes. -If that makes your head hurt, it should—and don’t worry. This line of +If that makes your head hurt, it should—and don't worry. This line of thinking, which is the basis of the Knuth-Morris-Pratt algorithm and half the basis of the Boyer-Moore algorithm, is what gives Boyer-Moore its reputation for inscrutability. That reputation is well deserved for -this aspect (which I will not discuss further in this book), but there’s -another part of Boyer-Moore that’s easily understood, easily +this aspect (which I will not discuss further in this book), but there's +another part of Boyer-Moore that's easily understood, easily implemented, and highly effective. -Consider this: We’re searching for the pattern “ABC,” beginning the -search at the start (offset 0) of a buffer containing “ABZABC.” We match -on ‘A,’ we match on ‘B,’ and we mismatch on ‘C’; the buffer contains a -‘Z’ in this position. What have we learned? Why, we’ve learned not only -that the pattern doesn’t match the buffer starting at offset 0, but also -that it can’t possibly match starting at offset 1 or offset 2, either! -After all, there’s a ‘Z’ in the buffer at offset 2; since the pattern -doesn’t contain a single ‘Z,’ there’s no way that the pattern can match -starting at *any* location from which it would span the ‘Z’ at offset 2. +Consider this: We're searching for the pattern "ABC," beginning the +search at the start (offset 0) of a buffer containing "ABZABC." We match +on ‘A,' we match on ‘B,' and we mismatch on ‘C'; the buffer contains a +‘Z' in this position. What have we learned? Why, we've learned not only +that the pattern doesn't match the buffer starting at offset 0, but also +that it can't possibly match starting at offset 1 or offset 2, either! +After all, there's a ‘Z' in the buffer at offset 2; since the pattern +doesn't contain a single ‘Z,' there's no way that the pattern can match +starting at *any* location from which it would span the ‘Z' at offset 2. We can just skip straight from offset 0 to offset 3 and continue, saving ourselves two comparisons. @@ -41,8 +41,8 @@ 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? -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 +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 compared to the corresponding bytes in the other set. @@ -51,25 +51,25 @@ compared to the corresponding bytes in the other set. ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- We learn nothing new from a mismatch on the leftmost character, except -that the pattern can’t match starting at that location. A mismatch on +that the pattern can't match starting at that location. A mismatch on the rightmost character, however, tells us about the possibilities of the pattern matching starting at every buffer location from which the pattern spans the mismatch location. If the mismatched character in the -buffer doesn’t appear in the pattern, then we’ve just eliminated not one +buffer doesn't appear in the pattern, then we've just eliminated not one potential match, but as many potential matches as there are characters -in the pattern; that’s how many locations there are in the buffer that +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 +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 -check every byte in the buffer, but Boyer-Moore doesn’t. +check every byte in the buffer, but Boyer-Moore doesn't. Figure 14.1 illustrates the operation of a Boyer-Moore search when the rightcharacter of the search pattern (which is the first character -that’s compared at each location because we’re comparing backwards) +that's compared at each location because we're comparing backwards) mismatches with a buffer character that appears nowhere in the pattern. Figure 14.2 illustrates the operation of a partial match when the -mismatch occurs with a character that’s not a pattern member. In this +mismatch occurs with a character that's not a pattern member. In this case, we can only skip ahead past the mismatch location, resulting in an advance of fewer bytes than the pattern length, and potentially as little as the same single byte distance by which the standard search @@ -79,7 +79,7 @@ approach advances. **Figure 14.1**  *Mismatch on first character checked.* What if the mismatch occurs with a buffer character that *does* occur in -the pattern? Then we can’t skip past the mismatch location, but we can +the pattern? Then we can't skip past the mismatch location, but we can skip to whatever location aligns the rightmost occurrence of that character in the pattern with the mismatch location, as shown in Figure 14.3. @@ -91,9 +91,9 @@ a mismatch, we see what we can learn from the buffer character that failed to match the pattern. Imagine that we move the pattern to the right across the mismatch location until we find a start location that the mismatch does not eliminate as a possible match for the pattern. If -the mismatch character doesn’t appear in the pattern, the pattern can +the mismatch character doesn't appear in the pattern, the pattern can move clear past the mismatch location. Otherwise, the pattern moves -until a matching pattern byte lies atop the mismatch. That’s all there +until a matching pattern byte lies atop the mismatch. That's all there is to it! ![](images/14-02.jpg)\ diff --git a/14-03.md b/14-03.md index 70f1b57..7444981 100644 --- a/14-03.md +++ b/14-03.md @@ -10,7 +10,7 @@ modified version of the text of this chapter) shows that this 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 +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 @@ -18,17 +18,17 @@ point. * * * * * -“g;” +"g;" -“Yogi” +"Yogi" -“igoY” +"igoY" -“Adrian” +"Adrian" -“Conclusion” +"Conclusion" -“You don’t know what you know” +"You don't know what you know" * * * * * @@ -140,14 +140,14 @@ Table 14.1 Comparison of searching techniques. * * * * * -The entry “Standard Boyer-Moore in ASM” in Table 14.1 refers to +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 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: -Architecture is destiny. Has a nice ring, doesn’t it? +Architecture is destiny. Has a nice ring, doesn't it? ------------------------ --------------------------------- -------------------- [Previous](14-02.html) [Table of Contents](index.html) [Next](14-04.html) diff --git a/14-04.md b/14-04.md index fd74a3d..9f0ae3e 100644 --- a/14-04.md +++ b/14-04.md @@ -30,13 +30,13 @@ /* Create the table of distances by which to skip ahead on mismatches for every possible byte value */ /* Initialize all skips to the pattern length; this is the skip - distance for bytes that don’t appear in the pattern */ + distance for bytes that don't appear in the pattern */ for (i = 0; i < 256; i++) SkipTable[i] = PatternLength; /*Set the skip values for the bytes that do appear in the pattern to the distance from the byte location to the end of the pattern. When there are multiple instances of the same byte, - the rightmost instance’s skip value is used. Note that the - rightmost byte of the pattern isn’t entered in the skip table; + the rightmost instance's skip value is used. Note that the + rightmost byte of the pattern isn't entered in the skip table; if we get that value for a mismatch, we know for sure that the right end of the pattern has already passed the mismatch location, so this is not a relevant byte for skipping purposes */ @@ -61,27 +61,27 @@ /* Compare the pattern and the buffer location, searching from high memory toward low (right to left) */ while (*WorkingPatternPtr— == *WorkingBufferPtr—) { - /* If we’ve matched the entire pattern, it’s a match */ + /* If we've matched the entire pattern, it's a match */ if (-CompCount == 0) /* Return a pointer to the start of the match location */ return(BufferPtr - PatternLength + 1); } - /* It’s a mismatch; let’s see what we can learn from it */ + /* It's a mismatch; let's see what we can learn from it */ WorkingBufferPtr++; /* point back to the mismatch location */ /* # of bytes that did match */ DistanceMatched = BufferPtr - WorkingBufferPtr; - /*If, based on the mismatch character, we can’t even skip ahead + /*If, based on the mismatch character, we can't even skip ahead as far as where we started this particular comparison, then just advance by 1 to the next potential match; otherwise, skip ahead from the mismatch location by the skip distance for the mismatch character */ if (SkipTable[*WorkingBufferPtr] <= DistanceMatched) - Skip = 1; /* skip doesn’t do any good, advance by 1 */ + Skip = 1; /* skip doesn't do any good, advance by 1 */ else /* Use skip value, accounting for distance covered by the partial match */ Skip = SkipTable[*WorkingBufferPtr] - DistanceMatched; - /* If skipping ahead would exhaust the buffer, we’re done + /* If skipping ahead would exhaust the buffer, we're done without a match */ if (Skip >= BufferLength) return(NULL); /* Skip ahead and perform the next comparison */ @@ -113,48 +113,48 @@ int Handle; unsigned int WorkingLength; - printf(“File to search:”); + printf("File to search:"); gets(Filename); - printf(“Pattern for which to search:”); + printf("Pattern for which to search:"); gets(Pattern); if ( (Handle = open(Filename, O_RDONLY | O_BINARY)) == -1 ) { - printf(“Can’t open file: %s\n”, Filename); exit(1); + printf("Can't open file: %s\n", Filename); exit(1); } /* Get memory in which to buffer the data */ if ( (TestBuffer=(unsigned char *)malloc(BUFFER_SIZE+1)) == NULL) { - printf(“Can’t get enough memory\n”); exit(1); + printf("Can't get enough memory\n"); exit(1); } /* Process a BUFFER_SIZE chunk */ if ( (int)(WorkingLength = read(Handle, TestBuffer, BUFFER_SIZE)) == -1 ) { - printf(“Error reading file %s\n”, Filename); exit(1); + printf("Error reading file %s\n", Filename); exit(1); } TestBuffer[WorkingLength] = 0; /* 0-terminate buffer for printf */ /* Search for the pattern and report the results */ if ((MatchPtr = FindString(TestBuffer, WorkingLength, Pattern, (unsigned int) strlen(Pattern))) == NULL) { - /* Pattern wasn’t found */ - printf(“\“%s\” not found\n”, Pattern); + /* Pattern wasn't found */ + printf("\"%s\" not found\n", Pattern); } else { /* Pattern was found. Zero-terminate TempBuffer; strncpy - won’t do it if DISPLAY_LENGTH characters are copied */ + won't do it if DISPLAY_LENGTH characters are copied */ TempBuffer[DISPLAY_LENGTH] = 0; - printf(“\“%s\” found. Next %d characters at match:\n\”%s\“\n”, + printf("\"%s\" found. Next %d characters at match:\n\"%s\"\n", Pattern, DISPLAY_LENGTH, strncpy(TempBuffer, MatchPtr, DISPLAY_LENGTH)); } exit(0); } -Well, architecture carries a lot of weight, but it sure as heck isn’t +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. +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 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 +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 first-comparison mismatches for the brute-force approach. The results in diff --git a/14-05.md b/14-05.md index b3c3ec8..b17d552 100644 --- a/14-05.md +++ b/14-05.md @@ -28,14 +28,14 @@ public _FindString _FindString proc near cld - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to our stack frame - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di sub sp,256*2 ;allocate space for SkipTable ; Create the table of distances by which to skip ahead on mismatches ; for every possible byte value. First, initialize all skips to the - ; pattern length; this is the skip distance for bytes that don’t + ; pattern length; this is the skip distance for bytes that don't ; appear in the pattern. mov ax,[bp+PatternLength] and ax,ax ;return an instant match if the pattern is @@ -57,8 +57,8 @@ ; Set the skip values for the bytes that do appear in the pattern to ; the distance from the byte location to the end of the pattern. ; When there are multiple instances of the same byte, the rightmost - ; instance’s skip value is used. Note that the rightmost byte of the - ; pattern isn’t entered in the skip table; if we get that value for + ; instance's skip value is used. Note that the rightmost byte of the + ; pattern isn't entered in the skip table; if we get that value for ; a mismatch, we know for sure that the right end of the pattern has ; already passed the mismatch location, so this is not a relevant byte ; for skipping purposes. @@ -85,7 +85,7 @@ mov cx,[bp+BufferLength] ;# of match locations to check SearchLoop: mov si,sp ;point SI to SkipTable - ; Skip through until there’s a match for the rightmost pattern byte. + ; Skip through until there's a match for the rightmost pattern byte. QuickSearchLoop: mov bl,[di] ;rightmost buffer byte at this location cmp dl,bl ;does it match the rightmost pattern byte? @@ -114,14 +114,14 @@ mov si,[bp+PatternPtr] ;point to next-to-rightmost bytes dec di ; of buffer location and pattern repz cmpsb ;compare the rest of the pattern - jz Match ;that’s it; we’ve found a match - ; It’s a mismatch; let’s see what we can learn from it. + jz Match ;that's it; we've found a match + ; It's a mismatch; let's see what we can learn from it. inc di ;compensate for 1-byte overrun of REPZ CMPSB; ; point to mismatch location in buffer ; # of bytes that did match. mov si,[bp+BufferPtr] sub si,di - ; If, based on the mismatch character, we can’t even skip ahead as far + ; If, based on the mismatch character, we can't even skip ahead as far ; as where we started this particular comparison, then just advance by ; 1 to the next potential match; otherwise, skip ahead from this ; comparison location by the skip distance for the mismatch character, @@ -131,14 +131,14 @@ add bx,bx ;prepare for word look-up add bx,sp ;SP points to SkipTable mov cx,[bx] ;get the skip value for this mismatch - mov ax,1 ;assume we’ll just advance to the next + mov ax,1 ;assume we'll just advance to the next ; potential match location sub cx,si ;is the skip far enough to be worth taking? jna MoveAhead ;no, go with the default advance of 1 mov ax,cx ;yes; this is the distance to skip ahead from ; the last potential match location checked MoveAhead: - ; Skip ahead and perform the next comparison, if there’s any buffer + ; Skip ahead and perform the next comparison, if there's any buffer ; left to check. mov di,[bp+BufferPtr] add di,ax ;BufferPtr += Skip; @@ -158,9 +158,9 @@ Done: cld ;restore default direction flag add sp,256*2 ;deallocate space for SkipTable - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _FindString endp end diff --git a/14-06.md b/14-06.md index 69dc458..1f74e36 100644 --- a/14-06.md +++ b/14-06.md @@ -9,12 +9,12 @@ good as or better than—sometimes *much* better than—brute-force 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 -you’re implementing a big-name algorithm; you know more than it does. +Know your data and use your smarts. Don't stop thinking just because +you're implementing a big-name algorithm; you know more than it does. ### Further Optimization of Boyer-Moore {#Heading6} -We can do substantially better yet than Listing 14.3 if we’re willing to +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, @@ -35,7 +35,7 @@ about 60 percent faster than Listing 14.3. ; Requires that the pattern be no longer than 255 bytes, and that ; there be a match for the pattern somewhere in the buffer (ie., a ; copy of the pattern should be placed as a sentinel at the end of - ; the buffer if the pattern isn’t already known to be in the buffer). + ; the buffer if the pattern isn't already known to be in the buffer). ; Tested with TASM. ; C near-callable as: ; unsigned char * FindString(unsigned char * BufferPtr, @@ -58,14 +58,14 @@ about 60 percent faster than Listing 14.3. public _FindString _FindString proc near cld - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to our stack frame - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di sub sp,256 ;allocate space for SkipTable ; Create the table of distances by which to skip ahead on mismatches ; for every possible byte value. First, initialize all skips to the - ; pattern length; this is the skip distance for bytes that don’t + ; pattern length; this is the skip distance for bytes that don't ; appear in the pattern. mov di,ds mov es,di ;ES=DS=SS @@ -106,7 +106,7 @@ about 60 percent faster than Listing 14.3. mov bx,sp ;point to SkipTable for XLAT SearchLoop: sub ah,ah ;used to convert AL to a word - ; Skip through until there’s a match for the first pattern byte. + ; Skip through until there's a match for the first pattern byte. QuickSearchLoop: ; See if we have a match at the first buffer location. REPT 8 ;unroll loop 8 times to reduce branching @@ -132,21 +132,21 @@ about 60 percent faster than Listing 14.3. dec di ;point to next destination byte to compare (SI ; points to next-to-rightmost source byte) repz cmpsb ;compare the rest of the pattern - jz Match ;that’s it; we’ve found a match - ; It’s a mismatch; let’s see what we can learn from it. + jz Match ;that's it; we've found a match + ; It's a mismatch; let's see what we can learn from it. inc di ;compensate for 1-byte overrun of REPZ CMPSB; ; point to mismatch location in buffer ; # of bytes that did match. mov si,[bp+BufferPtr] sub si,di - ; If, based on the mismatch character, we can’t even skip ahead as far + ; If, based on the mismatch character, we can't even skip ahead as far ; as where we started this particular comparison, then just advance by ; 1 to the next potential match; otherwise, skip ahead from this ; comparison location by the skip distance for the mismatch character, ; less the distance covered by the partial match. mov al,[di] ;get the value of the mismatch byte in buffer xlat ;get the skip value for this mismatch - mov cx,1 ;assume we’ll just advance to the next + mov cx,1 ;assume we'll just advance to the next ; potential match location sub ax,si ;is the skip far enough to be worth taking? jna MoveAhead ;no, go with the default advance of 1 @@ -167,9 +167,9 @@ about 60 percent faster than Listing 14.3. Done: cld ;restore default direction flag add sp,256 ;deallocate space for SkipTable - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _FindString endp end @@ -182,15 +182,15 @@ it as a parameter. ### Know What You Know {#Heading7} -Here we’ve turned up our nose at a repeated string instruction, we’ve -gone against the grain by comparing backward, and yet we’ve speeded up +Here we've turned up our nose at a repeated string instruction, we've +gone against the grain by comparing backward, and yet we've speeded up our code quite a bit. All this without any restrictions or special requirements (excluding Listing 14.4)—and without any new information. Everything we needed was sitting there all along; we just needed to think to look at it. -As Yogi Berra might put it, “You don’t know what you know until you know -it.” +As Yogi Berra might put it, "You don't know what you know until you know +it." ------------------------ --------------------------------- -------------------- [Previous](14-05.html) [Table of Contents](index.html) [Next](15-01.html) diff --git a/15-01.md b/15-01.md index 8f3ec92..a0e1834 100644 --- a/15-01.md +++ b/15-01.md @@ -11,9 +11,9 @@ Chapter 15\ After 21 years, this story still makes me wince. Oh, the humiliations I suffer for your enlightenment.... -It wasn’t until ninth grade that I had my first real girlfriend. Okay, +It wasn't until ninth grade that I had my first real girlfriend. Okay, maybe I was a little socially challenged as a kid, but hey, show me a -good programmer who wasn’t; it goes with the territory. Her name was +good programmer who wasn't; it goes with the territory. Her name was Jeannie Schweigert, and she was about four feet tall, pretty enough, and female—and willing to go out with me, which made her approximately as attractive as Cheryl Tiegs, in my book. @@ -21,13 +21,13 @@ attractive as Cheryl Tiegs, in my book. Jeannie and I hung out together at school, and went to basketball games and a few parties together, but somehow the two of us were never alone. Being 14, neither of us could drive, so her parents tended to end up -chauffeuring us. That’s a next-to-ideal arrangement, I now realize, +chauffeuring us. That's a next-to-ideal arrangement, I now realize, having a daughter of my own (ideal being exiling all males between the ages of 12 and 18 to Tasmania), but at the time, it drove me nuts. You see...ahem...I had never actually kissed Jeannie—or anyone, for that matter, unless you count maiden aunts and the like—and I was dying to. At the same time, I was terrified at the prospect. What if I turned out -to be no good at it? It wasn’t as if I could go to Kisses ‘R’ Us and +to be no good at it? It wasn't as if I could go to Kisses ‘R' Us and take lessons. My long-awaited opportunity finally came after a basketball game. For a @@ -46,27 +46,27 @@ try, though, I did manage to get the hang of this kissing business, and eventually even went on to have a child. (Not with Jeannie, I might add; the mind boggles at the mess I could have made of *that* with her.) As it turns out, none of that stuff is particularly difficult; in fact, -it’s kind of enjoyable, wink, wink, say no more. +it's kind of enjoyable, wink, wink, say no more. -When you’re dealing with something new, a little knowledge goes a long +When you're dealing with something new, a little knowledge goes a long way. When it comes to kissing, we have to fumble along the learning curve on our own, but there are all sorts of resources to help speed up the learning process when it comes to programming. The basic mechanisms of programming—searches, sorts, parsing, and the like—are well-understood and superbly well-documented. Treat yourself to a book -like *Algorithms,* by Robert Sedgewick (Addison Wesley), or Knuth’s *The +like *Algorithms,* by Robert Sedgewick (Addison Wesley), or Knuth's *The Art of Computer Programming* series (also from Addison Wesley; and where was Knuth with *The Art of Kissing* when I needed him?), or practically anything by Jon Bentley, and when you tackle a new area, give yourself a -head start. There’s still plenty of room for inventiveness and +head start. There's still plenty of room for inventiveness and creativity on your part, but why not apply that energy on top of the -knowledge that’s already been gained, instead of reinventing the wheel? +knowledge that's already been gained, instead of reinventing the wheel? I know, reinventing the wheel is just the kind of challenge programmers love—but can you really afford to waste the time? And do you honestly -think that you’re so smart that you can out-think Knuth, who’s spent a +think that you're so smart that you can out-think Knuth, who's spent a lifetime at this stuff and happens to be a genius? -Maybe you can—but I sure can’t. For example, consider the evolution of +Maybe you can—but I sure can't. For example, consider the evolution of my understanding of linked lists. ### Linked Lists {#Heading3} @@ -85,12 +85,12 @@ nodes; and an inability to backtrack, unless doubly-linked lists are used. Unfortunately, doubly linked lists need more memory, as well as processing time to maintain the backward links. -Linked lists aren’t very good for most types of sorts. Insertion and +Linked lists aren't very good for most types of sorts. Insertion and bubble sorts work fine, but more sophisticated sorts depend on efficient -random access, which linked lists don’t provide. Likewise, you wouldn’t +random access, which linked lists don't provide. Likewise, you wouldn't want to do a binary search on a linked list. On the other hand, linked lists are ideal for applications where nothing more than sequential -access is needed to data that’s always sorted or nearly sorted. +access is needed to data that's always sorted or nearly sorted. Consider a polygon fill function, for example. Polygon edges are added to the active edge list in x-sorted order, and tend to stay pretty @@ -100,15 +100,15 @@ best. Moreover, linked lists are straightforward to implement, and with linked lists an arbitrary number of polygon edges can be handled with no fuss. All in all, linked lists work beautifully for filling polygons. For an example of the use of linked lists in polygon filling, see my -column in the May 1991 issue of *Dr. Dobb’s Journal.* Be warned, though, +column in the May 1991 issue of *Dr. Dobb's Journal.* Be warned, though, that none of the following optimizations are to be found in that column. You see, that column was my first heavy-duty use of linked lists, and -they seemed so simple that I didn’t even open Sedgewick or Knuth. For -hashing or Boyer-Moore searching, sure, I’d have done my homework first; +they seemed so simple that I didn't even open Sedgewick or Knuth. For +hashing or Boyer-Moore searching, sure, I'd have done my homework first; but linked lists seemed too obvious to bother. I was much more concerned with the polygon-related aspects of the implementation, and, in truth, I -gave the linked list implementation not a moment’s thought before I +gave the linked list implementation not a moment's thought before I began coding. Heck, I had handled *much* tougher programming problems in the past; surely it would be faster to figure this one out on my own than to look it up. @@ -125,18 +125,18 @@ 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 listings in this chapter.) Easy enough—unless the preceding node happens -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 +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 +(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** -structure—but that’s an ugly and potentially dangerous trick, and we’ll +structure—but that's an ugly and potentially dangerous trick, and we'll see a better approach next.) ![](images/15-01.jpg)\ diff --git a/15-02.md b/15-02.md index 3e5d059..a7e1d2a 100644 --- a/15-02.md +++ b/15-02.md @@ -9,7 +9,7 @@ the head-of-list pointer is required. Returns the same pointer that was passed in. */ - #include “llist.h” + #include "llist.h" struct LinkNode *DeleteNodeAfter(struct LinkNode *NodeToDeleteAfter) { NodeToDeleteAfter->NextNode = @@ -41,7 +41,7 @@ indicated node. List is headed by a head-of-list pointer; if the pointer to the node to delete after points to the head-of-list pointer, special handling is performed. */ - #include “llist.h” + #include "llist.h" struct LinkNode *DeleteNodeAfter(struct LinkNode **HeadOfListPtr, struct LinkNode *NodeToDeleteAfter) { @@ -56,7 +56,7 @@ return(NodeToDeleteAfter); } -However, it is true that if you’re going to store a variety of types of +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 *every* structure, and the same linked list code can handle all of the @@ -65,18 +65,18 @@ This is a less than elegant approach, but it works. C++ can handle data 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** -field of the node preceding the node to be deleted, and it’s impossible +operation as "delete the *next* node," rather than "delete this node," +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 interest—and that makes the problem of having to special-case the head pointer all the more acute. -Similar problems with the head pointer crop up when you’re inserting -nodes, and in fact in all link manipulation code. It’s easy to end up +Similar problems with the head pointer crop up when you're inserting +nodes, and in fact in all link manipulation code. It's easy to end up working with either pointers to pointers or lots of special-case code, -and while those approaches work, they’re inelegant and inefficient. +and while those approaches work, they're inelegant and inefficient. ### Dummies and Sentinels {#Heading4} @@ -84,13 +84,13 @@ A far better approach is to use a *dummy node* for the head of the list, as shown in Figure 15.2. I invented this one for myself the next time I encountered linked lists, while designing a seed fill function for MetaWindows, back during my tenure at Metagraphics Corp. But I could -have learned it by spending five minutes with Sedgewick’s book. +have learned it by spending five minutes with Sedgewick's book. ![](images/15-02.jpg)\ **Figure 15.2**  *Using a dummy head and tail node with a linked list.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *The next-node pointer of the head node, which points to the first real node, is the only part of the head node that’s actually used. This way the same code works on the head node as on the rest of the list, so there are no special cases.* + ![](images/i.jpg) *The next-node pointer of the head node, which points to the first real node, is the only part of the head node that's actually used. This way the same code works on the head node as on the rest of the list, so there are no special cases.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Likewise, there should be a separate node for the tail of the list, so @@ -120,7 +120,7 @@ value has to perform two tests in the inner loop, as shown in Listing NULL pointer if no such value was found. Assumes the list is terminated with a tail node pointing to itself as the next node. */ #include - #include “llist.h” + #include "llist.h" struct LinkNode *FindNodeBeforeValueNotLess( struct LinkNode *HeadOfListNode, int SearchValue) { @@ -140,10 +140,10 @@ value has to perform two tests in the inner loop, as shown in Listing Suppose, however, that we make the tail node a *sentinel* by giving it a value that is guaranteed to terminate the search, as shown in Figure 15.4. The list in Figure 15.4 has a sentinel with a value field of -32,767; since we’re working with integers, that’s the highest possible +32,767; since we're working with integers, that's the highest possible search value, and is guaranteed to satisfy any search that comes down the pike. The success or failure of the search can then be determined -outside the loop, if necessary, by checking for the tail node’s special +outside the loop, if necessary, by checking for the tail node's special pointer—but the inside of the loop is streamlined to just one test, as shown in Listing 15.5. Not all linked lists lend themselves to sentinels, but the performance benefits are considerable for those lend diff --git a/15-03.md b/15-03.md index e59e339..3506a7c 100644 --- a/15-03.md +++ b/15-03.md @@ -12,7 +12,7 @@ containing the largest possible Value field setting and pointing to itself as the next node. */ #include - #include “llist.h” + #include "llist.h" struct LinkNode *FindNodeBeforeValueNotLess( struct LinkNode *HeadOfListNode, int SearchValue) { @@ -35,19 +35,19 @@ One minor but elegant refinement yet remains: Use a single node as both the head *and* the tail of the list. We can do this by connecting the last node back to the first through the head/tail node in a circular fashion, as shown in Figure 15.5. This head/tail node can also, of -course, be a sentinel; when it’s necessary to check for the end of the +course, be a sentinel; when it's necessary to check for the end of the list explicitly, that can be done by comparing the current node pointer -to the head pointer. If they’re equal, you’re at the head/tail node. +to the head pointer. If they're equal, you're at the head/tail node. Why am I so fond of this circular list architecture? For one thing, it saves a node, and most of my linked list programming has been done in -severely memory-constrained environments. Mostly, though, it’s just so -*neat;* with this setup, there’s not a single node or inner-loop +severely memory-constrained environments. Mostly, though, it's just so +*neat;* with this setup, there's not a single node or inner-loop instruction wasted. Perfect economy of programming, if you ask me. I must admit that I racked my brains for quite a while to come up with the circular list, simple as it may seem. Shortly after coming up with -it, I happened to look in Sedgewick’s book, only to find my nifty +it, I happened to look in Sedgewick's book, only to find my nifty optimization described plain as day; and a little while after *that,* I came across a thread in the algorithms/computer.sci topic on BIX that described it in considerable detail. Folks, the information is out @@ -65,7 +65,7 @@ illustrates the use of the linked-list functions in Listings 15.1 and Contrast Figure 15.5 with Figure 15.1, and Listings 15.1, 15.5, 15.6, and 15.7 with Listings 15.3 and 15.4. Yes, linked lists are simple, but -not so simple that a little knowledge doesn’t make a substantial +not so simple that a little knowledge doesn't make a substantial difference. Make it a habit to read Knuth or Sedgewick or the like before you write a single line of code. @@ -82,7 +82,7 @@ before you write a single line of code. #include #include #include - #include “llist.h” + #include "llist.h" /* Initializes an empty linked list of LinkNode structures, consisting of a single head/tail/sentinel node, and returns a pointer to the list. Returns NULL for failure. */ @@ -94,7 +94,7 @@ before you write a single line of code. return(NULL); Sentinel->NextNode = Sentinel; Sentinel->Value = SENTINEL; - strcpy(Sentinel->Text, “*** sentinel ***”); + strcpy(Sentinel->Text, "*** sentinel ***"); return(Sentinel); } diff --git a/15-04.md b/15-04.md index ccf2261..5f04fe1 100644 --- a/15-04.md +++ b/15-04.md @@ -72,7 +72,7 @@ #include #include #include - #include “llist.h” + #include "llist.h" void main() { int Done = 0, Char, TempValue; @@ -80,60 +80,60 @@ char TempBuffer[MAX_TEXT_LENGTH+3]; if ((ListPtr = InitLinkedList()) == NULL) { - printf(“Out of memory\n”); + printf("Out of memory\n"); exit(1); } while (!Done) { - printf(“\nA=add; D=delete; F=find; L=list all; Q=quit\n>”); + printf("\nA=add; D=delete; F=find; L=list all; Q=quit\n>"); Char = toupper(getche()); - printf(“\n”); + printf("\n"); switch (Char) { case 'A': /* add a node */ if ((TempPtr = malloc(sizeof(struct LinkNode))) == NULL) { - printf(“Out of memory\n ); + printf("Out of memory\n ); exit(1); } - printf(“Node value: ”); - scanf(“%d”, &TempPtr->Value); + printf("Node value: "); + scanf("%d", &TempPtr->Value); if ((FindNodeBeforeValue(ListPtr,TempPtr->Value))!=NULL) - { printf(“*** value already in list; try again ***\n”); + { printf("*** value already in list; try again ***\n"); free(TempPtr); - } else {printf(“Node text: ”); + } else {printf("Node text: "); TempBuffer[0] = MAX_TEXT_LENGTH; cgets(TempBuffer); strcpy(TempPtr->Text, &TempBuffer[2]); InsertNodeSorted(ListPtr, TempPtr); - printf(“\n”); + printf("\n"); } break; case 'D': /* delete a node */ - printf(“Value field of node to delete: ”); - scanf(“%d”, &TempValue); + printf("Value field of node to delete: "); + scanf("%d", &TempValue); if ((TempPtr = FindNodeBeforeValue(ListPtr, TempValue)) != NULL) { TempPtr2 = TempPtr->NextNode; /* -> node to delete */ DeleteNodeAfter(TempPtr); /* delete it */ free(TempPtr2); /* free its memory */ } else { - printf(“*** no such value field in list ***\n”) + printf("*** no such value field in list ***\n") break; case 'F': /* find a node */ - printf(“Value field of node to find: ”); - scanf(“%d”, &TempValue); + printf("Value field of node to find: "); + scanf("%d", &TempValue); if ((TempPtr = FindNodeBeforeValue(ListPtr, TempValue)) != NULL) - printf(“Value: %d\nText: %s\n”, + printf("Value: %d\nText: %s\n", TempPtr->NextNode->Value, TempPtr->NextNode->Text); else - printf(“*** no such value field in list ***\n”); + printf("*** no such value field in list ***\n"); break; case 'L': /* list all nodes */ TempPtr = ListPtr->NextNode; /* point to first node */ if (TempPtr == ListPtr) { /* empty if at sentinel */ - printf(“*** List is empty ***\n”); + printf("*** List is empty ***\n"); } else { - do {printf(“Value: %d\n Text: %s\n”, TempPtr->Value, + do {printf("Value: %d\n Text: %s\n", TempPtr->Value, TempPtr->Text); TempPtr = TempPtr->NextNode; } while (TempPtr != ListPtr); @@ -150,8 +150,8 @@ ### Hi/Lo in 24 Bytes {#Heading6} -In one of my *PC TECHNIQUES* “Pushing the Envelope” columns, I passed -along one of David Stafford’s fiendish programming puzzles: Write a +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**. 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. @@ -160,16 +160,16 @@ 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 -in 24 bytes or less—*combined*.” +"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. Yes, a 24-byte hi/lo function is possible, anatomically improbable as it -might seem. Which I guess goes to show that when one of David’s puzzles -seems less than impossible, odds are you’re missing something. Listing -15.9 is David’s 24-byte solution, from which a lot may be learned if one +might seem. Which I guess goes to show that when one of David's puzzles +seems less than impossible, odds are you're missing something. Listing +15.9 is David's 24-byte solution, from which a lot may be learned if one reads closely enough. **LISTING 15.9 L15-9.ASM** @@ -203,9 +203,9 @@ reads closely enough. ret Before I end this chapter, let me say that I get a lot of feedback from -my readers, and it’s much appreciated. Keep those cards, letters, and +my readers, and it's much appreciated. Keep those cards, letters, and email messages coming. And if any of you know Jeannie Schweigert, have -her drop me a line and let me know how she’s doing these days.... +her drop me a line and let me know how she's doing these days.... ------------------------ --------------------------------- -------------------- [Previous](15-03.html) [Table of Contents](index.html) [Next](16-01.html) diff --git a/16-01.md b/16-01.md index 1d20054..f21330f 100644 --- a/16-01.md +++ b/16-01.md @@ -3,7 +3,7 @@ ------------------------ --------------------------------- -------------------- Chapter 16\ - There Ain’t No Such Thing as the Fastest Code {#Heading1} + There Ain't No Such Thing as the Fastest Code {#Heading1} ---------------------------------------------- ### Lessons Learned in the Pursuit of the Ultimate Word Counter {#Heading2} @@ -34,15 +34,15 @@ importance to buyers: **10.**  Windows development cycle automation -Is something missing here? You bet your maximum *gluteus* something’s +Is something missing here? You bet your maximum *gluteus* something's missing—nowhere on that list is there so much as one word about how fast -the compiled code runs! I’m not saying that performance is everything, -but optimization isn’t even down there at number 10, below online help! +the compiled code runs! I'm not saying that performance is everything, +but optimization isn't even down there at number 10, below online help! Ye gods and little fishes! We are talking here about people who would take a bus from LA to New York instead of a plane because it had a cleaner bathroom; who would choose a painting from a Holiday Inn over a Matisse because it had a fancier frame; who would buy a Yugo instead -of—well, hell, anything—because it had a nice owner’s manual and +of—well, hell, anything—because it had a nice owner's manual and particularly attractive keys. We are talking about people who are focusing on means, and have forgotten about ends. We are talking about people with no programming souls. @@ -53,7 +53,7 @@ What are we to make of this? At the very least, we can safely guess that very few corporate buyers ever enter optimization contests. Most of my readers do, however; in fact, far more than I thought ever would, but that gladdens me to no end. I issued my first optimization challenge in -a “Pushing the Envelope” column in *PC TECHNIQUES* back in 1991, and was +a "Pushing the Envelope" column in *PC TECHNIQUES* back in 1991, and was deluged by respondents who, one might also gather, do not live by *PC Week*. @@ -64,11 +64,11 @@ optimization issues along the way. David did all his coding in Pascal, pointing out that while an assembly language version would probably be faster, his Pascal utility worked properly and was fast enough for him. -It wasn’t, however, fast enough for me. The logical starting place for -speeding up word counting would be David’s original Pascal code, but I’m +It wasn't, however, fast enough for me. The logical starting place for +speeding up word counting would be David's original Pascal code, but I'm much more comfortable with C, so Listing 16.1 is a loose approximation -of David’s word count program, translated to C. I left out a few -details, such as handling comment blocks, partly because I don’t use +of David's word count program, translated to C. I left out a few +details, such as handling comment blocks, partly because I don't use such blocks myself, and partly so we can focus on optimizing the core word-counting code. As Table 16.1 indicates, Listing 16.1 counts the words in a 104,448-word file in 4.6 seconds. The file was stored on a @@ -128,22 +128,22 @@ Table 16.1 Word count timings. char *Buffer, CharFlag = 0, PredCharFlag, *BufferPtr, Ch; if (argc != 2) { - printf(“usage: wc \n”); + printf("usage: wc \n"); exit(1); } if ((Buffer = malloc(BUFFER_SIZE)) == NULL) { - printf(“Can’t allocate adequate memory\n”); + printf("Can't allocate adequate memory\n"); exit(1); } if ((Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1) { - printf(“Can’t open file %s\n”, argv[1]); + printf("Can't open file %s\n", argv[1]); exit(1); } if ((FileSize = filelength(Handle)) == -1) { - printf(“Error sizing file %s\n”, argv[1]); + printf("Error sizing file %s\n", argv[1]); exit(1); } @@ -152,7 +152,7 @@ Table 16.1 Word count timings. /* Get the next chunk */ FileSize -= (BlockSize = min(FileSize, BUFFER_SIZE)); if (read(Handle, Buffer, BlockSize) == -1) { - printf(“Error reading file %s\n”, argv[1]); + printf("Error reading file %s\n", argv[1]); exit(1); } /* Count words in the chunk */ @@ -162,10 +162,10 @@ Table 16.1 Word count timings. Ch = *BufferPtr++ & 0x7F; /* strip high bit, which some word processors set as an internal flag */ - CharFlag = ((Ch >= ‘a’) && (Ch <= ‘z’)) || - ((Ch >= ‘A’) && (Ch <= ‘Z’)) || - ((Ch >= ‘0’) && (Ch <= ‘9’)) || - (Ch == ‘\’’); + CharFlag = ((Ch >= ‘a') && (Ch <= ‘z')) || + ((Ch >= ‘A') && (Ch <= ‘Z')) || + ((Ch >= ‘0') && (Ch <= ‘9')) || + (Ch == ‘\''); if ((!CharFlag) && PredCharFlag) { WordCo u nt++; } @@ -176,7 +176,7 @@ Table 16.1 Word count timings. if (CharFlag) { WordCount++; } - printf(“\nTotal words in file: %lu\n”, WordCount); + printf("\nTotal words in file: %lu\n", WordCount); return(0); } diff --git a/16-02.md b/16-02.md index 629d066..86f7549 100644 --- a/16-02.md +++ b/16-02.md @@ -36,22 +36,22 @@ generates. char *Buffer, CharFlag = 0; if (argc != 2) { - printf(“usage: wc \n”); + printf("usage: wc \n"); exit(1); } if ((Buffer = malloc(BUFFER_SIZE)) == NULL) { - printf(“Can’t allocate adequate memory\n”); + printf("Can't allocate adequate memory\n"); exit(1); } if ((Handle = open(argv[1], O_RDONLY | O_BINARY)) == -1) { - printf(“Can’t open file %s\n”, argv[1]); + printf("Can't open file %s\n", argv[1]); exit(1); } if ((FileSize = filelength(Handle)) == -1) { - printf(“Error sizing file %s\n”, argv[1]); + printf("Error sizing file %s\n", argv[1]); exit(1); } @@ -59,7 +59,7 @@ generates. while (FileSize > 0) { FileSize -= (BlockSize = min(FileSize, BUFFER_SIZE)); if (read(Handle, Buffer, BlockSize) == -1) { - printf(“Error reading file %s\n”, argv[1]); + printf("Error reading file %s\n", argv[1]); exit(1); } ScanBuffer(Buffer, BlockSize, &CharFlag, &WordCount); @@ -69,7 +69,7 @@ generates. if (CharFlag) { WordCount++; } - printf(“\nTotal words in file: %lu\n”, WordCount); + printf("\nTotal words in file: %lu\n", WordCount); return(0); } @@ -98,9 +98,9 @@ generates. .code public _ScanBuffer _ScanBuffer proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;set up local stack frame - push si ;preserve caller’s register vars + push si ;preserve caller's register vars push di mov si,[bp+Buffer] ;point to buffer to scan @@ -116,19 +116,19 @@ generates. and al,7fh ;strip high bit for word processors ; that set it as an internal flag mov bl,1 ;assume this is a char; CharFlag = 1; - cmp al,‘a’ ;it is a char if between a and z + cmp al,‘a' ;it is a char if between a and z jb CheckAZ - cmp al,‘z’ + cmp al,‘z' jna IsAChar CheckAZ: - cmp al,‘A’ ;it is a char if between A and Z + cmp al,‘A' ;it is a char if between A and Z jb Check09 - cmp al,‘Z’ + cmp al,‘Z' jna IsAChar Check09: - cmp al,‘0’ ;it is a char if between 0 and 9 + cmp al,‘0' ;it is a char if between 0 and 9 jb CheckApostrophe - cmp al,‘9’ + cmp al,‘9' jna IsAChar CheckApostrophe: cmp al,27h ;it is a char if an apostrophe @@ -149,9 +149,9 @@ generates. mov [bx],cx ;set new word count mov [bx+2],dx - pop di ;restore caller’s register vars + pop di ;restore caller's register vars pop si - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _ScanBuffer endp end @@ -160,10 +160,10 @@ generates. We could rearrange the tests in light of the nature of the data being scanned; for example, we could perform the tests more efficiently by -taking advantage of the knowledge that if a byte is less than ‘0,’ it’s +taking advantage of the knowledge that if a byte is less than ‘0,' it's either an apostrophe or not a character at all. However, that sort of fine-tuning is typically good for speedups of only 10 to 20 percent, and -I’ve intentionally refrained from implementing this in Listing 16.3 to +I've intentionally refrained from implementing this in Listing 16.3 to avoid pointing you down the wrong path; what we need is a different tack altogether. Ponder this. What we *really* want to know is nothing more than whether a byte is a character, not what sort of character it is. diff --git a/16-03.md b/16-03.md index 8562aad..88a5cfe 100644 --- a/16-03.md +++ b/16-03.md @@ -45,9 +45,9 @@ .code public _ScanBuffer _ScanBuffer proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;set up local stack frame - push si ;preserve caller’s register vars + push si ;preserve caller's register vars push di mov si,[bp+Buffer] ;point to buffer to scan @@ -62,10 +62,10 @@ and al,al ;ZF=0 if last byte was a char, ; ZF=1 if not lodsb ;get the next byte - ;***doesn’t change flags*** + ;***doesn't change flags*** xlat ;look up its char/not status - ;***doesn’t change flags*** - jz ScanLoopBottom ;don’t count a word if last byte was + ;***doesn't change flags*** + jz ScanLoopBottom ;don't count a word if last byte was ; not a character and al,al ;last byte was a character; is the ; current byte a character? @@ -80,9 +80,9 @@ mov [bx],di ;set new word count mov [bx+2],dx - pop di ;restore caller’s register vars + pop di ;restore caller's register vars pop si - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret align 2 @@ -104,7 +104,7 @@ the byte in a table, all with just two instruction bytes. 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 +**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** . @@ -113,20 +113,20 @@ of the loop to increment the word count in the case where a word is actually found, with a duplicate of the loop-bottom code placed after the code that increments the word count, to avoid an extra branch back into the loop; this replaces the more intuitive approach of jumping -around the incrementing code to the loop bottom when a word isn’t found. +around the incrementing code to the loop bottom when a word isn't found. Although this incurs a branch every time a word is found, a word is typically found only once every 5 or 6 bytes; on average, then, a branch is saved about two-thirds of the time. This is an excellent example of -how understanding the nature of the data you’re processing allows you to -optimize in ways the compiler can’t. *Know your data!* +how understanding the nature of the data you're processing allows you to +optimize in ways the compiler can't. *Know your data!* So, gosh, Listing 16.4 is the best word-counting code in the universe, -right? Not hardly. If there’s one thing my years of toil in this vale of -silicon have taught me, it’s that there’s never a lack of potential for +right? Not hardly. If there's one thing my years of toil in this vale of +silicon have taught me, it's that there's never a lack of potential for further optimization. *Never!* Off the top of my head, I can think of at least three ways to speed up Listing 16.4; and, since Turbo Profiler reports that even in Listing 16.4, 88 percent of the time is spent -scanning the buffer (as opposed to reading the file), there’s potential +scanning the buffer (as opposed to reading the file), there's potential for those further optimizations to improve performance significantly. (However, it is true that when access is performed to a hard rather than RAM disk, disk access jumps to about half of overall execution time.) @@ -149,14 +149,14 @@ No listings were to be longer than 200 lines. No complete programs were to be accepted; submissions had to be plug-compatible with Listing 16.4. (This was to encourage people not to waste time optimizing outside the inner loop.) Finally, the code had to produce the same results as -Listing 16.4; I didn’t want to see functions that approximated the word +Listing 16.4; I didn't want to see functions that approximated the word count by dividing the number of characters by six instead of counting actual words! So how did the entrants in this particular challenge stack up? More than one claimed a speed-up over my assembly word-counting code of more than three times. On top of the three-times speedup over the original C code -that I had already realized, we’re almost up to an order of magnitude +that I had already realized, we're almost up to an order of magnitude faster. You are, of course, entitled to your own opinion, but *I* consider an order of magnitude to be significant. diff --git a/16-04.md b/16-04.md index 44ad918..a0a393c 100644 --- a/16-04.md +++ b/16-04.md @@ -2,33 +2,33 @@ [Previous](16-03.html) [Table of Contents](index.html) [Next](16-05.html) ------------------------ --------------------------------- -------------------- -Truth to tell, I didn’t expect a three-times speedup; around two times +Truth to tell, I didn't expect a three-times speedup; around two times was what I had in mind. Which just goes to show that any code can be -made faster than you’d expect, if you think about it long enough and +made faster than you'd expect, if you think about it long enough and from many different perspectives. (The most potent word-counting technique seems to be a 64K lookup table that allows handling two bytes simultaneously. This is not the sort of technique one comes up with by brute-force optimization.) Thinking (or, worse yet, boasting) that your code is the fastest possible is rollescating on a tightrope in a -hurricane; you’re due for a fall, if you catch my drift. Case in point: -Terje Mathisen’s word-counting program. +hurricane; you're due for a fall, if you catch my drift. Case in point: +Terje Mathisen's word-counting program. #### Blinding Yourself to a Better Approach {#Heading6} Not so long ago, Terje Mathisen, who I introduced earlier in this book, wrote a very fast word-counting program, and posted it on Bix. When I -say it was fast, I mean *fast;* this code was optimized like nobody’s -business. We’re talking top-quality code here. +say it was fast, I mean *fast;* this code was optimized like nobody's +business. We're talking top-quality code here. When the topic of optimizing came up in one of the Bix conferences, -Terje’s program was mentioned, and he posted the following message: “I +Terje's program was mentioned, and he posted the following message: "I challenge BIXens (and especially **mabrash!**) to speed it up -significantly. I would consider 5 percent a good result.” The clear -implication was, “That code is as fast as it can possibly be.” +significantly. I would consider 5 percent a good result." The clear +implication was, "That code is as fast as it can possibly be." -Naturally, it wasn’t; there ain’t no such thing as the fastest code -(TANSTATFC? I agree, it doesn’t have the ring of TANSTAAFL). I pored -over Terje’s 386 native-mode code, and found the critical inner loop, +Naturally, it wasn't; there ain't no such thing as the fastest code +(TANSTATFC? I agree, it doesn't have the ring of TANSTAAFL). I pored +over Terje's 386 native-mode code, and found the critical inner loop, which was indeed as tight as one could imagine, consisting of just a few 386 native-mode instructions. However, one of the instructions was this: @@ -49,19 +49,19 @@ enough, by good fortune, to speed up the whole program by 5 percent. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ (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 +*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?) #### Watch Out for Luggable Assumptions! {#Heading7} The first lesson to be learned here is not to lug assumptions that may 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 +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 -trying to speed up his code, but he didn’t really see what it did; +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 without overloading after about 20 instructions, but they can be a major @@ -73,10 +73,10 @@ counting is not the key to happiness, riches, and wondrous performance. After getting my 5 percent speedup, I mentioned to Terje the possibility of using a 64K lookup table. (This predated the arrival of entries for the optimization contest.) He said that he had considered it, but it -didn’t seem to him to be worthwhile. He couldn’t shake the thought, +didn't seem to him to be worthwhile. He couldn't shake the thought, though, and started to poke around, and one day, *voila,* he posted a new version of his word count program, WC50, that was *much* faster than -the old version. I don’t have exact numbers, but Terje’s preliminary +the old version. I don't have exact numbers, but Terje's preliminary estimate was 80 percent faster, and word counting—*including* disk cache access time—proceeds at more than 3 MB per second on a 33 MHz 486. Even allowing for the speed of the 486, those are very impressive numbers @@ -87,20 +87,20 @@ barrier that Terje faced was that he *thought* he had the fastest code possible. Once he opened up the possibility that there were faster approaches, and looked beyond the specific approach that he had so carefully optimized, he was able to come up with code that was a *lot* -faster. Consider the incongruity of Terje’s willingness to consider a 5 +faster. Consider the incongruity of Terje's willingness to consider a 5 percent speedup significant in light of his later near-doubling of performance. ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Don’t get stuck in the rut of instruction-by-instruction optimization. It’s useful in key loops, but very often, a change in approach will work far greater wonders than any amount of cycle counting can.* + ![](images/i.jpg) *Don't get stuck in the rut of instruction-by-instruction optimization. It's useful in key loops, but very often, a change in approach will work far greater wonders than any amount of cycle counting can.* ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -By the way, Terje’s WC50 program is a full-fledged counting program; it +By the way, Terje's WC50 program is a full-fledged counting program; it counts characters, words, and lines, can handle multiple files, and lets you specify the characters that separate words, should you so desire. Source code is provided as part of the archive WC50 comes in. All in -all, it’s a nice piece of work, and you might want to take a look at it -if you’re interested in really fast assembly code. I wouldn’t call it +all, it's a nice piece of work, and you might want to take a look at it +if you're interested in really fast assembly code. I wouldn't call it the *fastest* word-counting code, though, because I would of course never be so foolish as to call *anything* the fastest. @@ -109,13 +109,13 @@ never be so foolish as to call *anything* the fastest. As it happened, the challenge I issued to my *PC TECHNIQUES* readers was a smashing success, with dozens of good entries. I certainly enjoyed it, even though I did have to look at a *lot* of tricky assembly code that I -didn’t write—hard work under the best of circumstances. It was worth the +didn't write—hard work under the best of circumstances. It was worth the trouble, though. The winning entry was an astonishing example of what assembly language can do in the right hands; on my 386, it was *four times* faster at word counting than the nice, tight assembly code I provided as a starting point—and about 13 times faster than the original C implementation. Attention, high-level language chauvinists: Is the -speedup getting significant yet? Okay, maybe word counting isn’t the +speedup getting significant yet? Okay, maybe word counting isn't the most critical application, but how would you like to have that kind of improvement in your compression software, or in your real-time games—or in Windows graphics? @@ -128,7 +128,7 @@ more than that he is obviously an extremely good assembly language programmer, was a close third, as shown in Table 16.2, which precedes Listing 16.5. Those three were out ahead of the pack; the fourth-place entry, good as it was (twice as fast as my original code), was twice as -slow as David’s winning entry, so you can see that David, Dave, and Mick +slow as David's winning entry, so you can see that David, Dave, and Mick attained a rarefied level of optimization indeed. ------------------------ --------------------------------- -------------------- diff --git a/16-05.md b/16-05.md index 6664bac..a53a334 100644 --- a/16-05.md +++ b/16-05.md @@ -104,7 +104,7 @@ Table 16.2 The top four word-counting entries. in-a-word/not-in-a-word status. The count register is masked to remove the high bit and the count of words remains in the count register. - Sound complicated? You’re right! But it’s fast! + Sound complicated? You're right! But it's fast! The beauty of this method is that no jumps are required, the operations are fast, it requires only one table and the process can @@ -153,7 +153,7 @@ Table 16.2 The top four word-counting entries. mov di,[bp+CharFlag] mov bh,[di] ;bh = old CharFlag mov bl,[si] ;bl = character - add bh,‘A’-1 ;make bh into character + add bh,‘A'-1 ;make bh into character add bx,bx ;prepare to index mov al,es:[bx] cbw ;get hi bit in ah (then bh) diff --git a/16-06.md b/16-06.md index da80e0c..9c160b4 100644 --- a/16-06.md +++ b/16-06.md @@ -5,17 +5,17 @@ ### Levels of Optimization {#Heading9} Three levels of optimization were evident in the word-counting entries I -received in response to my challenge. I’d briefly describe them as -“fine-tuning,” “new perspective,” and “table-driven state machine.” The +received in response to my challenge. I'd briefly describe them as +"fine-tuning," "new perspective," and "table-driven state machine." The latter categories produce faster code, but, by the same token, they are harder to design, harder to implement, and more difficult to understand, -so they’re suitable for only the most demanding applications. (Heck, I -don’t even guarantee that David Stafford’s entry works perfectly, +so they're suitable for only the most demanding applications. (Heck, I +don't even guarantee that David Stafford's entry works perfectly, although, knowing him, it probably does; the more complex and cryptic the code, the greater the chance for obscure bugs.) ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Remember, optimize only when needed, and stop when further optimization will not be noticed. Optimization that’s not perceptible to the user is like buying Telly Savalas a comb; it’s not going to do any harm, but it’s nonetheless a waste of time.* + ![](images/i.jpg) *Remember, optimize only when needed, and stop when further optimization will not be noticed. Optimization that's not perceptible to the user is like buying Telly Savalas a comb; it's not going to do any harm, but it's nonetheless a waste of time.* ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- #### Optimization Level 1: Good Code {#Heading10} @@ -28,7 +28,7 @@ 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 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 +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 @@ -44,7 +44,7 @@ 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 the number of repetitions of the *unrolled* loop and count down only once each time through the unrolled loop. In order to handle repetition -counts that aren’t exact multiples of the unrolling factor, you must +counts that aren't exact multiples of the unrolling factor, you must enter the loop by branching into the middle of it to perform whatever fraction of the number of unrolled repetitions is required to make the whole thing come out right. Listing 16.5 (QSCAN3.ASM) illustrates this @@ -63,7 +63,7 @@ eliminate it entirely. The most straightforward way to reduce such branching is to employ two loops. One loop is used to look for the end of a word when the last byte was a non-separator, and one loop is used to look for the start of a word when the last byte was a separator. This -way, it’s no longer necessary to maintain a flag to indicate the state +way, it's no longer necessary to maintain a flag to indicate the state of the last byte; that state is implied by whichever loop is currently executing. This considerably simplifies and streamlines the inner loop code. @@ -71,11 +71,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** . -Together, these optimizations made Willem’s code nearly twice as fast as +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, -and it’s also fairly compact and reasonably easy to understand. In +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 a reasonable cost in complexity and time. This level of optimization is diff --git a/16-07.md b/16-07.md index 8a636ad..d62692b 100644 --- a/16-07.md +++ b/16-07.md @@ -119,17 +119,17 @@ 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 -nothing more than a doubling up of tests, it’s a little more than that, +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 counting is really nothing more than a state machine that transitions -from the “in word” state to the “not in word” state and back, counting a +from the "in word" state to the "not in word" state and back, counting a word on one but not both of those transitions. Willem says, in effect, -“We’re in a word; if the next two bytes are non-separators, then we’re -still in a word, else we’re not in a word, so count and change to the -appropriate state.” That’s really quite different from saying, as I -originally did, “If the last byte was a non-separator, then if the -current byte is a separator, then count a word.” Willem has moved away +"We're in a word; if the next two bytes are non-separators, then we're +still in a word, else we're not in a word, so count and change to the +appropriate state." That's really quite different from saying, as I +originally did, "If the last byte was a non-separator, then if the +current byte is a separator, then count a word." Willem has moved away from the all-in-one approach, splitting the code up into state-specific chunks that are more efficient because each does only the work required in a particular state. @@ -140,27 +140,27 @@ the word), rather than waiting for a separator following a non-separator (at the end of the word). My friend Dan Illowsky describes the thought process leading to this approach thusly: -*“I try to code as closely as possible to the real world nature of those +*"I try to code as closely as possible to the real world nature of those things my program models. It seems somehow wrong to me to count the end of a word as you do when you look for a transition from a word to a 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 eliminated all branches used for detecting word edges; the inner loop of his code is shown in Listing 16.7. As John explains it: -*“My next shot was to get rid of all the branches in the loop. To do +*"My next shot was to get rid of all the branches in the loop. To do that, I reached back to my college hardware courses. I noticed that we were really looking at an edge triggered device we want to count each -time the I’m a character state goes from one to zero. Remembering that +time the I'm a character state goes from one to zero. Remembering that XOR on two single-bit values will always return whether the bits are different or the same, I implemented a transition counter. The counter -triggers every time a word begins or ends.”* +triggers every time a word begins or ends."* ------------------------ --------------------------------- -------------------- [Previous](16-06.html) [Table of Contents](index.html) [Next](16-08.html) diff --git a/16-08.md b/16-08.md index 835e715..6bfb2c8 100644 --- a/16-08.md +++ b/16-08.md @@ -6,13 +6,13 @@ ScanLoop: lodsw ;get the next 2 bytes (AL = first, AH = 2nd) - xlat ;look up first’s char/not status - xor dl,al ;see if there’s a new char/not status + xlat ;look up first's char/not status + xor dl,al ;see if there's a new char/not status add di,dx ;we add 1 for each char/not transition mov dl,al mov al,ah ;look at the second byte xlat ;look up its char/not status - xor dl,al ;see if there’s a new char/not status + xor dl,al ;see if there's a new char/not status add di,dx ;we add 1 for each char/not transition mov dl,al dec dx @@ -20,10 +20,10 @@ 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 +John's approach makes it clear that word-counting is nothing more than a fairly simple state machine. The interesting part, of course, is building the fastest state machine. @@ -40,21 +40,21 @@ In the case of word counting, level 3 means building a table-driven state machine dedicated to processing a buffer of bytes into a count of words with a minimum of branching. This level of optimization strips away many of the abstractions we usually use in coding, such as loops, -tests, and named variables—look back to Listing 16.5, and you’ll see -what I mean. Only a few people reached this level, and I don’t think any -of them did it without long, hard thinking; David Stafford’s final entry +tests, and named variables—look back to Listing 16.5, and you'll see +what I mean. Only a few people reached this level, and I don't think any +of them did it without long, hard thinking; David Stafford's final entry (that is, the one I present as Listing 16.5) was *at least* the fifth entry he sent me. The key concept at level 3 is the use of a massive (64K) lookup table that processes byte sequences directly into word-count actions. With -such a table, it’s possible to look up the appropriate action for two -bytes simultaneously in just a few instructions; next, I’m going to look -at the inspired and highly unusual way that David’s code, shown in +such a table, it's possible to look up the appropriate action for two +bytes simultaneously in just a few instructions; next, I'm going to look +at the inspired and highly unusual way that David's code, shown in Listing 16.5, does exactly that. (Before assembling Listing 16.5, you must run the C code in Listing 16.8, to generate an include file defining the 64K lookup table. When you assemble Listing 16.5, TASM will -report a “location counter overflow” warning; ignore it.) +report a "location counter overflow" warning; ignore it.) **LISTING 16.8 MAKETAB.C** @@ -63,7 +63,7 @@ report a “location counter overflow” warning; ignore it.) #include #include - #define ChType( c ) (((c) & 0x7f) == ‘\’’ || isalnum((c) & 0x7f)) + #define ChType( c ) (((c) & 0x7f) == ‘\'' || isalnum((c) & 0x7f)) int NoCarry[ 4 ] = { 0, 0x80, 1, 0x80 }; int Carry[ 4 ] = { 1, 0x81, 1, 0x80 }; @@ -71,9 +71,9 @@ report a “location counter overflow” warning; ignore it.) void main( void ) { int ahChar, alChar, i; - FILE *t = fopen( “QSCAN3.INC”, “wt” ); + FILE *t = fopen( "QSCAN3.INC", "wt" ); - printf( “Building table. Please wait...” ); + printf( "Building table. Please wait..." ); for( ahChar = 0; ahChar < 128; ahChar++ ) { @@ -81,10 +81,10 @@ report a “location counter overflow” warning; ignore it.) { i = ChType( alChar ) * 2 + ChType( ahChar ); - if( alChar % 8 == 0 ) fprintf( t, “\ndb %02Xh”, NoCarry[ i ] ); - else fprintf( t, “,%02Xh”, NoCarry[ i ] ); + if( alChar % 8 == 0 ) fprintf( t, "\ndb %02Xh", NoCarry[ i ] ); + else fprintf( t, ",%02Xh", NoCarry[ i ] ); - fprintf( t, “,%02Xh”, Carry[ i ] ); + fprintf( t, ",%02Xh", Carry[ i ] ); } } @@ -92,7 +92,7 @@ report a “location counter overflow” warning; ignore it.) } -David’s approach is simplicity itself, although his implementation +David's approach is simplicity itself, although his implementation arguably is not. Consider any three sequential bytes in the buffer. Those three bytes define two potential places where a word might be counted, as shown in Figure 16.1. Given the separator/non-separator @@ -102,11 +102,11 @@ there is a non-separator followed by a separator. Note that a maximum of one word can be counted per three-byte sequence. The trick, then, is to identify the separator/not statuses of each set -of three bytes and turn them into a 1 (count word) or 0 (don’t count +of three bytes and turn them into a 1 (count word) or 0 (don't count word), as quickly as possible. Assuming that the separator/not status for the first byte is in the Carry flag, this is easily accomplished by a lookup in a 64K table, based on the Carry flag and the other two -bytes, as shown in Figure 16.2. (Remember that we’re counting 7-bit +bytes, as shown in Figure 16.2. (Remember that we're counting 7-bit 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, @@ -122,7 +122,7 @@ One detail remains to be attended to: setting the Carry flag for next time if the last byte was a non-separator. David does this in a bizarre and incredibly effective way: He presets the high bit of the count, and sets the high bit in the lookup table for those entries looked up by -non-separators. When a non-separator’s lookup entry is added to the +non-separators. When a non-separator's lookup entry is added to the count, it will produce a carry, as desired. The high bit of the count is masked off before being added to the total count, so David is essentially using different parts of the count variables for different @@ -131,13 +131,13 @@ purposes (counting, and setting the Carry flag). ![](images/16-02.jpg)\ **Figure 16.2**  *Looking up a word count status.* -There are a number of other interesting details in David’s code, +There are a number of other interesting details in David's code, including the unrolling of the loop 64 times, so that 256 bytes in a row are processed without a single branch. Unfortunately, I lack the space -to discuss Listing 16.5 any further. Perhaps that’s not so unfortunate, -after all; I’d hate to deny you the pleasure of discovering the wonders +to discuss Listing 16.5 any further. Perhaps that's not so unfortunate, +after all; I'd hate to deny you the pleasure of discovering the wonders of this rather remarkable code yourself. I will say one more thing, -though. The cycle count for David’s inner loop is 6.5 cycles per byte +though. The cycle count for David's inner loop is 6.5 cycles per byte processed, and the actual measured time for his routine, overhead and all, is 7.9 cycles/byte. The original C code clocked in at around 100 cycles/byte. @@ -146,24 +146,24 @@ Enough said, I trust. #### Enough Word Counting Already! {#Heading13} -Before I finish up this chapter, I’d like to mention that Terje -Mathisen’s WC word-counting program, which I’ve mentioned previously and -which is available, with source, on Bix, is in the ballpark with David’s -code for performance. What’s more, Terje’s program handles 8-bit ASCII, +Before I finish up this chapter, I'd like to mention that Terje +Mathisen's WC word-counting program, which I've mentioned previously and +which is available, with source, on Bix, is in the ballpark with David's +code for performance. What's more, Terje's program handles 8-bit ASCII, counts lines as well as words, and supports user-definable separator -sets. It’s wonderful code, well worth a look; it also happens to be a +sets. It's wonderful code, well worth a look; it also happens to be a great word-counting utility. By the way, Terje builds his 64K table on the fly, at program initialization; this allows for customized tables, -shrinks the size of the EXE, and, according to Terje’s calculations, +shrinks the size of the EXE, and, according to Terje's calculations, takes less time than loading the table off disk as part of the EXE. So, has David written the fastest possible word-counting code? Well, maybe—but I have a letter from Terry Holmes, of San Rafael, California, that calculates the theoretical maximum performance of native 386 word-counting code at 5.5 cycles/byte, which would be significantly -faster than David’s code. Terry, alas, didn’t bother to implement his -design, but maybe I’ll take a shot at it someday. It’d be fun, for -sure—but jeez, I’ve got *real* work to do! +faster than David's code. Terry, alas, didn't bother to implement his +design, but maybe I'll take a shot at it someday. It'd be fun, for +sure—but jeez, I've got *real* work to do! ------------------------ --------------------------------- -------------------- [Previous](16-07.html) [Table of Contents](index.html) [Next](17-01.html) diff --git a/17-01.md b/17-01.md index d37b45d..87b6d26 100644 --- a/17-01.md +++ b/17-01.md @@ -8,52 +8,52 @@ Chapter 17\ ### The Triumph of Algorithmic Optimization in a Cellular Automata Game {#Heading2} -I’ve spent a lot of my life discussing assembly language optimization, +I've spent a lot of my life discussing assembly language optimization, which I consider to be an important and underappreciated topic. However, -I’d like to take this opportunity to point out that there is much, much +I'd like to take this opportunity to point out that there is much, much more to optimization than assembly language. Assembly is essential for -absolute maximum performance, but it’s not the only ingredient; +absolute maximum performance, but it's not the only ingredient; necessary but not sufficient, if you catch my drift—and not even -necessary, if you’re looking for improved but not maximum performance. -You’ve heard it a thousand times: Optimize your algorithm first. Devise +necessary, if you're looking for improved but not maximum performance. +You've heard it a thousand times: Optimize your algorithm first. Devise new approaches. Or, as Knuth said, *Premature optimization is the root of all evil.* This is, of course, old hat, stuff you know like the back of your hand. Or is it? As Jeff Duntemann pointed out to me the other day, performance -programmers are made, not born. While I’m merrily gallivanting around in +programmers are made, not born. While I'm merrily gallivanting around in this book optimizing 486 pipelining and turning simple tasks into horribly complicated and terrifyingly fast state machines, many of you -are still developing your basic optimization skills. I don’t want to +are still developing your basic optimization skills. I don't want to shortchange those of you in the latter category, so in this chapter, -we’ll discuss some high-level language optimizations that can be applied -by mere mortals within a reasonable period of time. We’re going to +we'll discuss some high-level language optimizations that can be applied +by mere mortals within a reasonable period of time. We're going to examine a complete optimization process, from start to finish, and what -we will find is that it’s possible to get a 50-times speed-up without -using *one byte of assembly!* It’s all a matter of perspective—how you +we will find is that it's possible to get a 50-times speed-up without +using *one byte of assembly!* It's all a matter of perspective—how you look at your code and data. -### Conway’s Game {#Heading3} +### Conway's Game {#Heading3} -The program that we’re going to optimize is Conway’s famous Game of -Life, long-ago favorite of the hackers at MIT’s AI Lab. If you’ve never +The program that we're going to optimize is Conway's famous Game of +Life, long-ago favorite of the hackers at MIT's AI Lab. If you've never seen it, let me assure you: Life is *neat,* and more than a little hypnotic. Fractals have been the hot graphics topic in recent years, but for eye-catching dazzle, Life is hard to beat. Of course, eye-catching dazzle requires real-time performance—lots of -pixels help too—and there’s the rub. When there are, say, 40,000 cells +pixels help too—and there's the rub. When there are, say, 40,000 cells to process and display, a simple, straightforward implementation just -doesn’t cut it, even on a 33 MHz 486. Happily, though, there are many, +doesn't cut it, even on a 33 MHz 486. Happily, though, there are many, many ways to speed up Life, and they illustrate a variety of important optimization principles, as this chapter will show. -First, I’ll describe the ground rules of Life, implement a very +First, I'll describe the ground rules of Life, implement a very straightforward version in C++, and then speed that version up by about eight times without using any drastically different approaches or any assembly. This may be a little tame for some of you, but be patient; for -after that, we’ll haul out the big guns and move into the 30 to 40 times -speed-up range. Then in the next chapter, I’ll show you how several +after that, we'll haul out the big guns and move into the 30 to 40 times +speed-up range. Then in the next chapter, I'll show you how several programmers *really* floored it in taking me up on my second Optimization Challenge, which involved the Game of Life. @@ -68,25 +68,25 @@ according to the following rules: - If a cell is on and has either two or three neighbors that are on in the current generation, it stays on; otherwise, the cell turns off. -- If a cell is off and has exactly three “on” neighbors in the current - generation, it turns on; otherwise, it stays off. That’s all the +- If a cell is off and has exactly three "on" neighbors in the current + generation, it turns on; otherwise, it stays off. That's all the rules there are—but they give rise to an astonishing variety of forms, including patterns that spin, march across the screen, and explode. -It’s only a little more complicated to implement the Game of Life than +It's only a little more complicated to implement the Game of Life than it is to describe it. Listing 17.1, together with the display functions -in Listing 17.2, is a C++ implementation of the Game of Life, and it’s -very straightforward. A cellmap is an object that’s accessible through +in Listing 17.2, is a C++ implementation of the Game of Life, and it's +very straightforward. A cellmap is an object that's accessible through member functions to set, clear, and test cell states, and through a member function to calculate the next generation. Calculating the next generation involves nothing more than using the other member functions to set each cell to the appropriate state, given the number of -neighboring on-cells and the cell’s current state. The only complication -is that it’s necessary to place the next generation’s cells in another +neighboring on-cells and the cell's current state. The only complication +is that it's necessary to place the next generation's cells in another cellmap, and then copy the final result back to the original cellmap. -This keeps us from corrupting the current generation’s cellmap before -we’re done using it to calculate the next generation. +This keeps us from corrupting the current generation's cellmap before +we're done using it to calculate the next generation. All in all, Listing 17.1 is a clean, compact, and elegant implementation of the Game of Life. Were it not that the code is as slow as molasses, diff --git a/17-02.md b/17-02.md index 11d38be..02e9718 100644 --- a/17-02.md +++ b/17-02.md @@ -65,12 +65,12 @@ cellmap next_map(cellmap_height, cellmap_width); // Get the seed; seed randomly if 0 entered - cout << “Seed (0 for random seed): ”; + cout << "Seed (0 for random seed): "; cin >> seed; if (seed == 0) seed = (unsigned) time(NULL); // Randomly initialize the initial cell map - cout << “Initializing...”; + cout << "Initializing..."; srand(seed); init_length = (cellmap_height * cellmap_width) / 2; do { @@ -84,11 +84,11 @@ // Keep recalculating and redisplaying generations until a key // is pressed - show_text(0, MSG_LINE, “Generation: ”); + show_text(0, MSG_LINE, "Generation: "); start_bios_time = _bios_timeofday(_TIME_GETCLOCK, &bios_time); do { generation++; - sprintf(gen_text, “%10lu”, generation); + sprintf(gen_text, "%10lu", generation); show_text(1, GENERATION_LINE, gen_text); // Recalculate and draw the next generation current_map.next_generation(next_map); @@ -104,8 +104,8 @@ } while (!kbhit()); getch(); // clear keypress exit_display_mode(); - cout << “Total generations: ” << generation << “\nSeed: ” << - seed << “\n”; + cout << "Total generations: " << generation << "\nSeed: " << + seed << "\n"; } /* cellmap constructor. */ @@ -125,7 +125,7 @@ delete[] cells; } - /* Copies one cellmap’s cells to another cellmap. Both cellmaps are + /* Copies one cellmap's cells to another cellmap. Both cellmaps are assumed to be the same size. */ void cellmap::copy_cells(cellmap &sourcemap) { diff --git a/17-03.md b/17-03.md index c2b4c31..9925aa6 100644 --- a/17-03.md +++ b/17-03.md @@ -16,12 +16,12 @@ optimizing to do. 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 +I'll tell you one place where I thought there was considerable 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 +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 generation. @@ -111,34 +111,34 @@ 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* 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 +*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().** ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *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 knowing the nature of your data, and is a potent optimization technique that will be extremely useful a little later in this chapter.* + ![](images/i.jpg) *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 knowing the nature of your data, and is a potent optimization technique that will be extremely useful a little later in this chapter.* ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### 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 address that aspect of the program directly. Once you know where you need to optimize, the one word to keep in mind -isn’t assembly, it’s...plastics. No, actually, it’s *abstraction.* +isn't assembly, it's...plastics. No, actually, it's *abstraction.* Well-written C and especially C++ programs are highly abstract models. For example, Listing 17.1 essentially creates a new programming language in which cells are tangible things, with built-in manipulation -instructions. Given the cellmap member functions, you don’t even need to +instructions. Given the cellmap member functions, you don't even need to know the cell storage format! This is a wonderful thing, in general; it saves programming time and bugs, and frees you to work on the -application’s needs, rather than implementation details. +application's needs, rather than implementation details. ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ![](images/i.jpg) *However, if you never look beneath the surface of the abstract model at the implementation details, you have no idea of what the true performance cost of various operations* *is, and, without that, you have largely surrendered control over performance.* @@ -148,10 +148,10 @@ Having said that, let me hasten to add that algorithmic improvements can make a big difference even when working at a purely abstract level. For a large unordered data set, a high-level Quicksort will beat the pants off the best-implemented insertion sort you can imagine. Still, you can -optimize your algorithm from here ’til doomsday, and if you have a fast -algorithm running on top of a highly abstract programming model, you’ll +optimize your algorithm from here 'til doomsday, and if you have a fast +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 +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. In fact, given the nature of cell storage, the eight neighbors are in a diff --git a/17-04.md b/17-04.md index 8b68894..2f1d039 100644 --- a/17-04.md +++ b/17-04.md @@ -2,43 +2,43 @@ [Previous](17-03.html) [Table of Contents](index.html) [Next](17-05.html) ------------------------ --------------------------------- -------------------- -There’s a kicker here, though, and that’s the counting of neighbors for +There's a kicker here, though, and that's the counting of neighbors for cells at the edge of the cellmap. When cellmap wrapping is enabled (so that the cellmap becomes essentially a toroid, with each edge joined seamlessly to the opposite edge, as opposed to having a border of -off-cells), neighbors that reside on the other edge of the cellmap can’t +off-cells), neighbors that reside on the other edge of the cellmap can't be accessed by the standard fixed offset, as shown in Figure 17.1. So, in general, we could improve performance by hard-wiring our -neighbor-counting for the bit-per-cell cellmap format, but it seems we’d +neighbor-counting for the bit-per-cell cellmap format, but it seems we'd need a lot of conditional code to handle wrapping, and that would slow things back down again. ![](images/17-01.jpg)\ **Figure 17.1**  *Edge-wrapping complications.* -When a problem doesn’t lend itself well to optimization, make it a +When a problem doesn't lend itself well to optimization, make it a practice to see if you can change the problem definition to one that -allows for greater efficiency. In this case, we’ll change the problem by +allows for greater efficiency. In this case, we'll change the problem by putting padding bytes around the edge of the cellmap, and duplicating each edge of the cellmap in the padding bytes at the opposite side, as shown in Figure 17.2. That way, a hard-wired neighbor count will find exactly what it should—the opposite edge—without any special code at all. -But doesn’t that extra copying of the edges take time? Sure, but only a +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 +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* 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 code significantly larger—but Listing 17.3 is 3.6 times faster than -Listing 17.1, as shown in Table 17.1. We’re up to about 10 generations +Listing 17.1, as shown in Table 17.1. We're up to about 10 generations per second on a 486; not where we want to be, but it is a vast improvement. ![](images/17-02.jpg)\ - **Figure 17.2**  *The “padding cells” solution.* + **Figure 17.2**  *The "padding cells" solution.* **LISTING 17.3 L17-3.CPP** @@ -79,7 +79,7 @@ improvement. memset(cells, 0, length_in_bytes); // clear all cells, to start } - /* Copies one cellmap’s cells to another cellmap. If wrapping is + /* Copies one cellmap's cells to another cellmap. If wrapping is enabled, copies edge (wrap) bytes into opposite padding bytes in source first, so that the padding bytes off each edge have the same values as would be found by wrapping around to the opposite diff --git a/17-05.md b/17-05.md index f66d8ef..801283e 100644 --- a/17-05.md +++ b/17-05.md @@ -7,7 +7,7 @@ the member functions to compensate for the padding. Also note that the width now has to be a multiple of eight, to facilitate the process of copying the edges to the opposite padding bytes. We have decreased the generality of our Game of Life implementation in exchange for better -performance. That’s a very common trade-off, as common as trading memory +performance. That's a very common trade-off, as common as trading memory for performance. As a rule, the more general a program is, the slower it is. A corollary is that often (not always, but often), the more heavily optimized a program is, the more complex and the more difficult to @@ -15,17 +15,17 @@ 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 +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 -also have to be altered, and it’s more complex than any of the other +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 +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 -still taking up more than half the time. Maybe now it’s time to go to +**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? Not hardly. @@ -37,7 +37,7 @@ then see if we can find an alternative approach that better fits the application. It would actually have made much more sense if we had looked for a new approach as our first optimization step, but I decided it would be better to cover straightforward C++ optimizations at this -point, and the mind-bending stuff a little later. Right now, let’s look +point, and the mind-bending stuff a little later. Right now, let's look at some C++ optimizations; Listing 17.4 is a C++-optimized version of Listing 17.3. @@ -138,21 +138,21 @@ eliminating all remaining function calls and from-scratch address/mask 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 +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** 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 -with a small cellmap like 48x48.) We’ve sped things up by about eight +with a small cellmap like 48x48.) We've sped things up by about eight times so far; we need to increase our speed another ten times to reach our goal of 200x200 at 18 generations per second on a 20 MHz 386. -It’s undoubtedly possible to improve the performance of Listing 17.4 +It's undoubtedly possible to improve the performance of Listing 17.4 further by fine-tuning the code, but no tremendous improvement is possible that way. ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Once you’ve reached the point of fine-tuning pointer usage and register variables and the like in C or C++, you’ve become compiler-dependent; you therefore might as well go to assembly and get the real McCoy.* + ![](images/i.jpg) *Once you've reached the point of fine-tuning pointer usage and register variables and the like in C or C++, you've become compiler-dependent; you therefore might as well go to assembly and get the real McCoy.* ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------ --------------------------------- -------------------- diff --git a/17-06.md b/17-06.md index ce34978..53cceb0 100644 --- a/17-06.md +++ b/17-06.md @@ -2,10 +2,10 @@ [Previous](17-05.html) [Table of Contents](index.html) [Next](17-07.html) ------------------------ --------------------------------- -------------------- -We’re still not ready for assembly, though; what we need is a new +We're still not ready for assembly, though; what we need is a new perspective that lends itself to vastly better performance in C++. The Life program in the next section is *three to seven times* faster than -Listing 17.4—and it’s still in C++. +Listing 17.4—and it's still in C++. How is this possible? Here are some hints: @@ -19,10 +19,10 @@ How is this possible? Here are some hints: In the previous section, we saw how a C++ program could be sped up about eight times simply by rearranging the data and code in straightforward -ways. Now we’re going to see how right-brain non-linear optimization can +ways. Now we're going to see how right-brain non-linear optimization can speed things up by another four times—and make the code *simpler.* -Now *that’s* Zen code optimization. +Now *that's* Zen code optimization. I have two objectives to achieve in the remainder of this chapter. First, I want to show that optimization consists of many levels, from @@ -43,25 +43,25 @@ optimizations were moving the critical code into a single loop and using pointers rather than member functions whenever possible. In other words, we took what we already knew and made it more efficient. -Now it’s time to re-examine the nature of this programming task from the -ground up, looking for things that we *don’t* yet know. Let’s take a +Now it's time to re-examine the nature of this programming task from the +ground up, looking for things that we *don't* yet know. Let's take a moment to review what the Game of Life consists of. The basic task is -evolving a new generation, and that’s done by looking at the number of -“on” neighbors a cell has and the cell’s own state. If a cell is on, and +evolving a new generation, and that's done by looking at the number of +"on" neighbors a cell has and the cell's own state. If a cell is on, and two or three neighbors are on, then the cell stays on; otherwise, an on-cell is turned off. If a cell is off and exactly three neighbors are -on, then the cell is turned on; otherwise, an off-cell stays off. That’s +on, then the cell is turned on; otherwise, an off-cell stays off. That's all there is to it. As any fool can see, the trick is to arrange things so that we can count neighbors and check the cell state as quickly as possible. Large lookup tables, oddly encoded cellmaps, and lots of -bit-twiddling assembly code spring to mind as possible approaches. Can’t +bit-twiddling assembly code spring to mind as possible approaches. Can't you just feel your adrenaline start to pump? ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Relax. Step back. Try to divine the true nature of the problem. The object is not to count neighbors and check cell states as quickly as possible; that’s just one possible implementation. The object is to determine when a cell’s state must be changed and to change it appropriately, and that’s what we need to do as quickly as possible.* + ![](images/i.jpg) *Relax. Step back. Try to divine the true nature of the problem. The object is not to count neighbors and check cell states as quickly as possible; that's just one possible implementation. The object is to determine when a cell's state must be changed and to change it appropriately, and that's what we need to do as quickly as possible.* ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -What difference does that new perspective make? Let’s approach it this +What difference does that new perspective make? Let's approach it this way. What does a typical cellmap look like? As it happens, after a few generations, the vast majority of cells are off. In fact, the vast majority of cells are not only off but are entirely surrounded by @@ -69,7 +69,7 @@ off-cells. Also, cells change state infrequently; in any given generation after the first few, most cells remain in the same state as in the previous generation. -Do you see where I’m heading? Do you hear a whisper of inspiration from +Do you see where I'm heading? Do you hear a whisper of inspiration from your right brain? The original implementation stored cell states as 1-bits (on), or 0-bits (off). For each generation and for each cell, it counted the states of the eight neighbors, for an average of eight @@ -96,7 +96,7 @@ only one-tenth that of the original approach! #### Acting on What We Know {#Heading10} -Once we’ve changed the cellmap format to store neighbor counts as well +Once we've changed the cellmap format to store neighbor counts as well as states, with a byte for each cell, we can get another performance boost by again examining what we know about our data. I said earlier that most cells are off during any given generation. This means that @@ -106,13 +106,13 @@ 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 -memory for the next byte that’s non-zero.) +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 neighbor-count cell map format and scans for non-zero bytes. On a 20 MHz 386, Listing 17.5 is about 4.5 times faster at calculating generations -(that is, the generation engine is 4.5 times faster; I’m ignoring the +(that is, the generation engine is 4.5 times faster; I'm ignoring the time consumed by drawing and text display) than Listing 17.4, which is no slouch. On a 33 MHz 486, Listing 17.5 is about 3.5 times faster than Listing 17.4. This is true even though Listing 17.5 must be compiled diff --git a/17-07.md b/17-07.md index 92a4afb..afc0da0 100644 --- a/17-07.md +++ b/17-07.md @@ -76,11 +76,11 @@ // Keep recalculating and redisplaying generations until any key // is pressed - show_text(0, MSG_LINE, “Generation: ”); + show_text(0, MSG_LINE, "Generation: "); start_bios_time = _bios_timeofday(_TIME_GETCLOCK, &bios_time); do { generation++; - sprintf(gen_text, “%10lu”, generation); + sprintf(gen_text, "%10lu", generation); show_text(1, GENERATION_LINE, gen_text); // Recalculate and draw the next generation current_map.next_generation(); @@ -95,8 +95,8 @@ getch(); // clear keypress exit_display_mode(); - cout << “Total generations: ” << generation << “\nSeed: ” << - seed << “\n”; + cout << "Total generations: " << generation << "\nSeed: " << + seed << "\n"; } /* cellmap constructor. */ @@ -108,7 +108,7 @@ cells = new unsigned char[length_in_bytes]; // cell storage temp_cells = new unsigned char[length_in_bytes]; // temp cell storage if ( (cells == NULL) || (temp_cells == NULL) ) { - printf(“Out of memory\n”); + printf("Out of memory\n"); exit(1); } memset(cells, 0, length_in_bytes); // clear all cells, to start @@ -229,11 +229,11 @@ cell_ptr++; // advance to the next cell if (++x >= w) goto RowDone; } - // Found a cell that’s either on or has on-neighbors, + // Found a cell that's either on or has on-neighbors, // so see if its state needs to be changed count = *cell_ptr >> 1; // # of neighboring on-cells if (*cell_ptr & 0x01) { - // Cell is on; turn it off if it doesn’t have + // Cell is on; turn it off if it doesn't have // 2 or 3 neighbors if ((count != 2) && (count != 3)) { clear_cell(x, y); @@ -259,14 +259,14 @@ unsigned int x, y, init_length; // Get the seed; seed randomly if 0 entered - cout << “Seed (0 for random seed): ”; + cout << "Seed (0 for random seed): "; cin >> seed; if (seed == 0) seed = (unsigned) time(NULL); // Randomly initialize the initial cell map to 50% on-pixels // (actually generally fewer, because some coordinates will be // randomly selected more than once) - cout << “Initializing...”; + cout << "Initializing..."; srand(seed); init_length = (height * width) / 2; do { diff --git a/17-08.md b/17-08.md index 9c2f15b..4c770f0 100644 --- a/17-08.md +++ b/17-08.md @@ -4,48 +4,48 @@ The large model is actually not necessary for the 96x96 cellmap in Listing 17.5. However, I was actually more interested in seeing a fast -200x200 cellmap, and two 200x200 cellmaps can’t fit in a single segment. +200x200 cellmap, and two 200x200 cellmaps can't fit in a single segment. (This can easily be worked around in assembly language for cellmaps up to a segment in size; beyond that size, cellmap scanning becomes pretty complex, although it can still be efficiently implemented with some clever programming.) -Anyway, using the large model helps illustrate that it’s the data +Anyway, using the large model helps illustrate that it's the data representation and the data processing approach you choose that matter most. Optimization details like memory models and segments and in-line functions and assembly language are important but secondary. Let your mind roam creatively before you start coding. Otherwise, you may find -you’re writing well-tuned slow code, which is by no means the same thing +you're writing well-tuned slow code, which is by no means the same thing as fast code. -Take a close look at Listing 17.5. You will see that it’s quite a bit -simpler than Listing 17.4. To some extent, that’s because I decided to +Take a close look at Listing 17.5. You will see that it's quite a bit +simpler than Listing 17.4. To some extent, that's because I decided to hard-wire the program to wrap around from one edge of the cellmap to the -other (it’s much more interesting that way), but the main reason is that -it’s a lot easier to work with the neighbor-count model. There’s no +other (it's much more interesting that way), but the main reason is that +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 +haven't optimized even that because it's done in a C++ loop; it should 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 large model for large cellmaps. Also, of course, the entire program is -still in C++; note well that there’s not a whit of assembly here. +still in C++; note well that there's not a whit of assembly here. ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *We’ve gotten more than a 30-times speedup simply by removing a little of the abstraction that C++ encourages, and by storing and processing the data in a manner appropriate for the typical nature of the data itself. In other words, we’ve done some linear, left-brained optimization (using pointers and reducing calls) and some non-linear, right-brained optimization (understanding the real problem and listening for the creative whisper of non-obvious solutions).* + ![](images/i.jpg) *We've gotten more than a 30-times speedup simply by removing a little of the abstraction that C++ encourages, and by storing and processing the data in a manner appropriate for the typical nature of the data itself. In other words, we've done some linear, left-brained optimization (using pointers and reducing calls) and some non-linear, right-brained optimization (understanding the real problem and listening for the creative whisper of non-obvious solutions).* ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- No doubt we could get another two to five times improvement with good -assembly code—but that’s dwarfed by a 30-times improvement, so +assembly code—but that's dwarfed by a 30-times improvement, so optimization at a conceptual level *must* come first. #### The Challenge That Ate My Life {#Heading11} The most recent optimization challenge I laid my community of readers was to write the fastest possible Game of Life generation engine. By -“engine” I meant that I didn’t care about time spent in input or output, +"engine" I meant that I didn't care about time spent in input or output, only time consumed by the call to **next-generation.** The time spent updating the cellmap was what I wanted people to concentrate on. @@ -70,7 +70,7 @@ Here are the rules I laid down for the challenge: That was the challenge I put to the readers. Little did I realize the challenge it would lay on *me:* Entries poured in from the four corners of the globe. Some were plain, some were brilliant, some were, well, -berserk. Many didn’t even work. But all had to be gone through, examined +berserk. Many didn't even work. But all had to be gone through, examined for adherence to the rules, read, compiled, linked, run, and judged. I learned a lot—about a lot of things, not the least of which was the process (or maybe the wisdom) of laying down challenges to readers. diff --git a/18-01.md b/18-01.md index 20e6d58..dd3fdff 100644 --- a/18-01.md +++ b/18-01.md @@ -3,7 +3,7 @@ ------------------------ --------------------------------- -------------------- Chapter 18\ - It’s a plain Wonderful Life {#Heading1} + It's a plain Wonderful Life {#Heading1} ---------------------------- ### Optimization beyond the Pale {#Heading2} @@ -18,9 +18,9 @@ inside the cone too—except the pear-shaped kid bringing up the rear, who plodded his way around every single cone on his way to finishing about half a lap behind. When the laggard finally crossed the finish line, the coach named him the winner, to my considerable irritation. After all, -the object was to see who could run the fastest, wasn’t it? +the object was to see who could run the fastest, wasn't it? -Actually, it wasn’t. The object was to see who could run the fastest +Actually, it wasn't. The object was to see who could run the fastest according to the limitations placed upon the contest. This is a crucial distinction, although usually taken for granted. Would it have been legitimate if I had cut across the middle of the field? If I had ridden @@ -32,13 +32,13 @@ Why am I telling you this? First, because it is a useful lesson for programming. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *All programming is performed within limitations, some of which can be bent or changed, but many of which cannot. You cannot change the maximum memory bandwidth of a VGA, or the maximum instruction execution rate of a 486. That is why the stunning 3D demos you see at SIGGRAPH have only passing relevance to everyday life on the desktop. A rule that Intel’s chip designers cannot break is 8086 compatibility, much as I’m sure they’d like to, but of course the flip side is that although RISC chips are technically superior, they command but a small fraction of the market; raw performance is not the arena of competition. Similarly, you will often be unable to change the specifications for the software you implement.* + ![](images/i.jpg) *All programming is performed within limitations, some of which can be bent or changed, but many of which cannot. You cannot change the maximum memory bandwidth of a VGA, or the maximum instruction execution rate of a 486. That is why the stunning 3D demos you see at SIGGRAPH have only passing relevance to everyday life on the desktop. A rule that Intel's chip designers cannot break is 8086 compatibility, much as I'm sure they'd like to, but of course the flip side is that although RISC chips are technically superior, they command but a small fraction of the market; raw performance is not the arena of competition. Similarly, you will often be unable to change the specifications for the software you implement.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### Breaking the Rules {#Heading3} The other reason for the anecdote has to do with the way my second -Optimization Challenge worked itself out. If you’ll recall from the last +Optimization Challenge worked itself out. If you'll recall from the last chapter, the challenge I made to the readers of *PC TECHNIQUES* was to devise the fastest possible version of the Game of Life cellular automata simulation game. I gave an example, laid out the rules, and @@ -49,14 +49,14 @@ that every submitted entry broke the rules. *Every single entry*. The rules clearly stated that submitted code must produce *exactly the same output* as my example implementation under all circumstances in order to be eligible to win. I do not think that there can be any question about -what “exactly the same output” means. It means the same pixels, in the +what "exactly the same output" means. It means the same pixels, in the same colors, at the same places on the screen at the same points in all the Life simulations that the original code was capable of running. Period. And not one of the entries met that standard. Some submitted -listings were more than 400 lines long. Some didn’t display the -generation number at the right side of the screen, didn’t draw the same -pixel colors, or didn’t bother with magnification. Some had bugs. Some -didn’t support all possible cellmap widths and heights up to 200x200, +listings were more than 400 lines long. Some didn't display the +generation number at the right side of the screen, didn't draw the same +pixel colors, or didn't bother with magnification. Some had bugs. Some +didn't support all possible cellmap widths and heights up to 200x200, requiring widths and heights that were specific multiples of a number of cells that lent itself to a particular implementation. @@ -75,16 +75,16 @@ a winner in the true spirit of the contest: raw speed. Two winners, in fact: Peter Klerings, a programmer for Turck GmbH in Munich, Germany, whose entry just plain runs like a bat out of hell, and David Stafford (who was also the winner of my first Optimization Challenge), of Borland -International, whose entry is slightly slower mainly because he didn’t +International, whose entry is slightly slower mainly because he didn't optimize the drawing part of the program, in full accordance with the contest rules, which specifically excluded drawing time from -consideration. Unfortunately, Peter’s generation code and drawing code +consideration. Unfortunately, Peter's generation code and drawing code are so tightly intertwined that it is impossible to separate them, and hence not really possible to figure out whose generation engine is faster. Anyway, at 180 to 200 generations per second, including drawing time, for 200x200 cellmaps (and in the neighborhood of *1000* gps for -96x96 cellmaps, the size of my original implementation), they’re the -fastest submissions I received. They’re both more than an order of +96x96 cellmaps, the size of my original implementation), they're the +fastest submissions I received. They're both more than an order of magnitude faster than my final optimized C++ Life implementation shown in Chapter 17, and more than 300 times faster than my original, perfectly functional Life implementation. Not 300 percent—300 *times*. @@ -95,11 +95,11 @@ true objective of the challenge has been met: pure, breathtaking Notwithstanding, *mea culpa*. The next time I lay a challenge, I will define the rules with scrupulous care. Even so, this was much more than -just another cycle-counting contest. We’re fortunate enough to be privy +just another cycle-counting contest. We're fortunate enough to be privy to a startling demonstration of the power of the best optimizer anyone -has yet devised—you. (That’s the general “you”; I realize that the -specific “you” may or may not be quite up to the optimizing level of the -specific “David Stafford” or “Peter Klerings.”) +has yet devised—you. (That's the general "you"; I realize that the +specific "you" may or may not be quite up to the optimizing level of the +specific "David Stafford" or "Peter Klerings.") Onward to the code. diff --git a/18-02.md b/18-02.md index 21f702d..5c267d1 100644 --- a/18-02.md +++ b/18-02.md @@ -6,18 +6,18 @@ David Stafford won my first Optimization Challenge by means of a huge look-up table and an incredible state machine driven by that table. The -table didn’t cause David’s entry to exceed the line limit because -David’s submission included code to generate the table on the fly as +table didn't cause David's entry to exceed the line limit because +David's submission included code to generate the table on the fly as part of the build process. David has done himself one better this time with his QLIFE program; not only does his build process generate a 64K table, but it also generates virtually all his code, consisting of 17,000-plus lines of assembly language spanning another 64K. What David has done is write the equivalent of a bitblt compiler for the Game of -Life; one might in fact call it a Life compiler. What David’s code +Life; one might in fact call it a Life compiler. What David's code generates is still a general-purpose program; it takes arbitrary seed -values, and can run for an arbitrary number of generations, so it’s not +values, and can run for an arbitrary number of generations, so it's not as if David simply hardwired the instructions to draw each successive -screen. However, it’s a general-purpose program that is exquisitely +screen. However, it's a general-purpose program that is exquisitely tailored to the task it needs to perform. All the pieces of QLIFE are shown in Listings 18.1 through 18.5, as @@ -25,23 +25,23 @@ follows: Listing 18.1 is BUILD.BAT, the batch file used to build QLIFE; Listing 18.2 is LCOMP.C, the program used to generate the assembler code and data file QLIFE.ASM; Listing 18.3 is MAIN.C, the main program for QLIFE; Listing 18.4 is VIDEO.C, the video-related functions, and Listing -18.5 is LIFE.H, the header file. The following sidebar contains David’s -build instructions, exactly as he wrote them. I certainly won’t have -room to discuss all the marvelous intricacies of David’s code; I suggest +18.5 is LIFE.H, the header file. The following sidebar contains David's +build instructions, exactly as he wrote them. I certainly won't have +room to discuss all the marvelous intricacies of David's code; I suggest you look over these listings until you understand them thoroughly (it -took me a day to pick them apart) because there’s a lot of neat stuff in -there, and it’s an approach to performance programming that operates at +took me a day to pick them apart) because there's a lot of neat stuff in +there, and it's an approach to performance programming that operates at a more efficient, tightly integrated level than you may ever see again. One hint: It helps a *lot* to build and run LCOMP.C, redirect its output to QLIFE.ASM, and look at the assembly code in that file. This code is -the entirety of David’s generation engine, and it’s almost impossible to +the entirety of David's generation engine, and it's almost impossible to visualize its operation without actually seeing it. > * * * * * > > How To Build Qlife > -> QLIFE is written for Borland C++, but it shouldn’t be too difficult to +> QLIFE is written for Borland C++, but it shouldn't be too difficult to > convert it to work with Microsoft C++. To build QLIFE, run the > BUILD.BAT batch file with the size of the life grid on the command > line (see below). The command-line options are: @@ -55,13 +55,13 @@ visualize its operation without actually seeing it. > -- ----------- ------------------------------------------------------------- > > These *must* be in uppercase. For example, the minimum you really need -> is “WIDTH 40 HEIGHT 120.” I used “WIDTH 46 HEIGHT 138 NOCOUNTER NODRAW -> GEN 7000” during testing. +> is "WIDTH 40 HEIGHT 120." I used "WIDTH 46 HEIGHT 138 NOCOUNTER NODRAW +> GEN 7000" during testing. > > If you have selected the GEN option, you will have to press a key to > exit QLIFE when it is finished. This is so I could visually compare > the result of N generations under QLIFE with N generations under -> Abrash’s original life program. You should be aware that the program +> Abrash's original life program. You should be aware that the program > from the listing contains a small bug, which may make it appear that > they do not generate identical results. The original program does not > display a cell until it changes, so if a cell is alive on the first @@ -72,7 +72,7 @@ visualize its operation without actually seeing it. > 210x200. > > You *must* have a VGA and at least a 386 to run QLIFE. The 386 -> features that it uses are not integral to the algorithm (they’re a +> features that it uses are not integral to the algorithm (they're a > convenience for the code), so feel free to modify QLIFE to run on > earlier CPUs if you wish. QLIFE works best if you have a large CPU > cache (256K is recommended). diff --git a/18-03.md b/18-03.md index dbce9ef..3f38d54 100644 --- a/18-03.md +++ b/18-03.md @@ -20,7 +20,7 @@ #include #include - #include “life.h” + #include "life.h" #define LIST_LIMIT (46 * 138) // when we need to use es: @@ -29,33 +29,33 @@ void Next1( void ) { - char *Seg = “”; + char *Seg = ""; - if( WIDTH * HEIGHT > LIST_LIMIT ) Seg = “es:”; + if( WIDTH * HEIGHT > LIST_LIMIT ) Seg = "es:"; - printf( “mov bp,%s[si]\n”, Seg ); - printf( “add si,2\n” ); - printf( “mov dh,[bp+1]\n” ); - printf( “and dh,0FEh\n” ); - printf( “jmp dx\n” ); + printf( "mov bp,%s[si]\n", Seg ); + printf( "add si,2\n" ); + printf( "mov dh,[bp+1]\n" ); + printf( "and dh,0FEh\n" ); + printf( "jmp dx\n" ); } void Next2( void ) { - printf( “mov bp,es:[si]\n” ); - printf( “add si,2\n” ); - printf( “mov dh,[bp+1]\n” ); - printf( “or dh,1\n” ); - printf( “jmp dx\n” ); + printf( "mov bp,es:[si]\n" ); + printf( "add si,2\n" ); + printf( "mov dh,[bp+1]\n" ); + printf( "or dh,1\n" ); + printf( "jmp dx\n" ); } void BuildMaps( void ) { unsigned short i, j, Size, x = 0, y, N1, N2, N3, C1, C2, C3; - printf( “_DATA segment ‘DATA’\nalign 2\n” ); - printf( “public _CellMap\n” ); - printf( “_CellMap label word\n” ); + printf( "_DATA segment ‘DATA'\nalign 2\n" ); + printf( "public _CellMap\n" ); + printf( "_CellMap label word\n" ); for( j = 0; j < HEIGHT; j++ ) { @@ -63,48 +63,48 @@ { if( i == 0 || i == WIDTH-1 || j == 0 || j == HEIGHT-1 ) { - printf( “dw 8000h\n” ); + printf( "dw 8000h\n" ); } else { - printf( “dw 0\n” ); + printf( "dw 0\n" ); } } } - printf( “ChangeCell dw 0\n” ); - printf( “_RowColMap label word\n” ); + printf( "ChangeCell dw 0\n" ); + printf( "_RowColMap label word\n" ); for( j = 0; j < HEIGHT; j++ ) { for( i = 0; i < WIDTH; i++ ) { - printf( “dw 0%02x%02xh\n”, j, i * 3 ); + printf( "dw 0%02x%02xh\n", j, i * 3 ); } } if( WIDTH * HEIGHT > LIST_LIMIT ) { - printf( “Change1 dw offset _CHANGE:_ChangeList1\n” ); - printf( “Change2 dw offset _CHANGE:_ChangeList2\n” ); - printf( “ends\n\n” ); - printf( “_CHANGE segment para public ‘FAR_DATA’\n” ); + printf( "Change1 dw offset _CHANGE:_ChangeList1\n" ); + printf( "Change2 dw offset _CHANGE:_ChangeList2\n" ); + printf( "ends\n\n" ); + printf( "_CHANGE segment para public ‘FAR_DATA'\n" ); } else { - printf( “Change1 dw offset DGROUP:_ChangeList1\n” ); - printf( “Change2 dw offset DGROUP:_ChangeList2\n” ); + printf( "Change1 dw offset DGROUP:_ChangeList1\n" ); + printf( "Change2 dw offset DGROUP:_ChangeList2\n" ); } Size = WIDTH * HEIGHT + 1; - printf( “public _ChangeList1\n_ChangeList1 label word\n” ); - printf( “dw %d dup (offset DGROUP:ChangeCell)\n”, Size ); - printf( “public _ChangeList2\n_ChangeList2 label word\n” ); - printf( “dw %d dup (offset DGROUP:ChangeCell)\n”, Size ); - printf( “ends\n\n” ); + printf( "public _ChangeList1\n_ChangeList1 label word\n" ); + printf( "dw %d dup (offset DGROUP:ChangeCell)\n", Size ); + printf( "public _ChangeList2\n_ChangeList2 label word\n" ); + printf( "dw %d dup (offset DGROUP:ChangeCell)\n", Size ); + printf( "ends\n\n" ); - printf( “_LDMAP segment para public ‘FAR_DATA’\n” ); + printf( "_LDMAP segment para public ‘FAR_DATA'\n" ); do { @@ -150,25 +150,25 @@ y |= 0x1000; } - printf( “db 0%02xh\n”, y >> 8 ); + printf( "db 0%02xh\n", y >> 8 ); } while( ++x != 0 ); - printf( “ends\n\n” ); + printf( "ends\n\n" ); } void GetUpAndDown( void ) { - printf( “mov ax,[bp+_RowColMap-_CellMap]\n” ); - printf( “or ah,ah\n” ); - printf( “mov dx,%d\n”, DOWN ); - printf( “mov cx,%d\n”, WRAPUP ); - printf( “jz short D%d\n”, Label ); - printf( “cmp ah,%d\n”, HEIGHT - 1 ); - printf( “mov cx,%d\n”, UP ); - printf( “jb short D%d\n”, Label ); - printf( “mov dx,%d\n”, WRAPDOWN ); - printf( “D%d:\n”, Label ); + printf( "mov ax,[bp+_RowColMap-_CellMap]\n" ); + printf( "or ah,ah\n" ); + printf( "mov dx,%d\n", DOWN ); + printf( "mov cx,%d\n", WRAPUP ); + printf( "jz short D%d\n", Label ); + printf( "cmp ah,%d\n", HEIGHT - 1 ); + printf( "mov cx,%d\n", UP ); + printf( "jb short D%d\n", Label ); + printf( "mov dx,%d\n", WRAPDOWN ); + printf( "D%d:\n", Label ); } void FirstPass( void ) @@ -176,37 +176,37 @@ char *Op; unsigned short UpDown = 0; - printf( “org 0%02x00h\n”, (Edge << 7) + (New << 4) + (Old << 1) ); + printf( "org 0%02x00h\n", (Edge << 7) + (New << 4) + (Old << 1) ); // reset cell - printf( “xor byte ptr [bp+1],0%02xh\n”, (New ^ Old) << 1 ); + printf( "xor byte ptr [bp+1],0%02xh\n", (New ^ Old) << 1 ); // get the screen address and update the display #ifndef NODRAW - printf( “mov al,160\n” ); - printf( “mov bx,[bp+_RowColMap-_CellMap]\n” ); - printf( “mul bh\n” ); - printf( “add ax,ax\n” ); - printf( “mov bh,0\n” ); - printf( “add bx,ax\n” ); // bx = screen offset + printf( "mov al,160\n" ); + printf( "mov bx,[bp+_RowColMap-_CellMap]\n" ); + printf( "mul bh\n" ); + printf( "add ax,ax\n" ); + printf( "mov bh,0\n" ); + printf( "add bx,ax\n" ); // bx = screen offset if( ((New ^ Old) & 6) == 6 ) { - printf( “mov word ptr fs:[bx],0%02x%02xh\n”, + printf( "mov word ptr fs:[bx],0%02x%02xh\n", (New & 2) ? 15 : 0, (New & 4) ? 15 : 0 ); if( (New ^ Old) & 1 ) { - printf( “mov byte ptr fs:[bx+2],%s\n”, - (New & 1) ? “15” : “dl” ); + printf( "mov byte ptr fs:[bx+2],%s\n", + (New & 1) ? "15" : "dl" ); } } else { if( ((New ^ Old) & 3) == 3 ) { - printf( “mov word ptr fs:[bx+1],0%02x%02xh\n”, + printf( "mov word ptr fs:[bx+1],0%02x%02xh\n", (New & 1) ? 15 : 0, (New & 2) ? 15 : 0 ); } @@ -214,21 +214,21 @@ { if( (New ^ Old) & 2 ) { - printf( “mov byte ptr fs:[bx+1],%s\n”, - (New & 2) ? “15” : “dl” ); + printf( "mov byte ptr fs:[bx+1],%s\n", + (New & 2) ? "15" : "dl" ); } if( (New ^ Old) & 1 ) { - printf( “mov byte ptr fs:[bx+2],%s\n”, - (New & 1) ? “15” : “dl” ); + printf( "mov byte ptr fs:[bx+2],%s\n", + (New & 1) ? "15" : "dl" ); } } if( (New ^ Old) & 4 ) { - printf( “mov byte ptr fs:[bx],%s\n”, - (New & 4) ? “15” : “dl” ); + printf( "mov byte ptr fs:[bx],%s\n", + (New & 4) ? "15" : "dl" ); } } #endif @@ -243,83 +243,83 @@ if( (New ^ Old) & 4 ) { - printf( “mov di,%d\n”, WRAPLEFT ); // di = left - printf( “cmp al,0\n” ); - printf( “je short L%d\n”, Label ); - printf( “mov di,%d\n”, LEFT ); - printf( “L%d:\n”, Label ); + printf( "mov di,%d\n", WRAPLEFT ); // di = left + printf( "cmp al,0\n" ); + printf( "je short L%d\n", Label ); + printf( "mov di,%d\n", LEFT ); + printf( "L%d:\n", Label ); - if( New & 4 ) Op = “inc”; - else Op = “dec”; + if( New & 4 ) Op = "inc"; + else Op = "dec"; - printf( “%s word ptr [bp+di]\n”, Op ); - printf( “add di,cx\n” ); - printf( “%s word ptr [bp+di]\n”, Op ); - printf( “sub di,cx\n” ); - printf( “add di,dx\n” ); - printf( “%s word ptr [bp+di]\n”, Op ); + printf( "%s word ptr [bp+di]\n", Op ); + printf( "add di,cx\n" ); + printf( "%s word ptr [bp+di]\n", Op ); + printf( "sub di,cx\n" ); + printf( "add di,dx\n" ); + printf( "%s word ptr [bp+di]\n", Op ); } if( (New ^ Old) & 1 ) { - printf( “mov di,%d\n”, WRAPRIGHT ); // di = right - printf( “cmp al,%d\n”, (WIDTH - 1) * 3 ); - printf( “je short R%d\n”, Label ); - printf( “mov di,%d\n”, RIGHT ); - printf( “R%d:\n”, Label ); + printf( "mov di,%d\n", WRAPRIGHT ); // di = right + printf( "cmp al,%d\n", (WIDTH - 1) * 3 ); + printf( "je short R%d\n", Label ); + printf( "mov di,%d\n", RIGHT ); + printf( "R%d:\n", Label ); - if( New & 1 ) Op = “add”; - else Op = “sub”; + if( New & 1 ) Op = "add"; + else Op = "sub"; - printf( “%s word ptr [bp+di],40h\n”, Op ); - printf( “add di,cx\n” ); - printf( “%s word ptr [bp+di],40h\n”, Op ); - printf( “sub di,cx\n” ); - printf( “add di,dx\n” ); - printf( “%s word ptr [bp+di],40h\n”, Op ); + printf( "%s word ptr [bp+di],40h\n", Op ); + printf( "add di,cx\n" ); + printf( "%s word ptr [bp+di],40h\n", Op ); + printf( "sub di,cx\n" ); + printf( "add di,dx\n" ); + printf( "%s word ptr [bp+di],40h\n", Op ); } - printf( “mov di,cx\n” ); - printf( “add word ptr [bp+di],%d\n”, UpDown ); - printf( “mov di,dx\n” ); - printf( “add word ptr [bp+di],%d\n”, UpDown ); + printf( "mov di,cx\n" ); + printf( "add word ptr [bp+di],%d\n", UpDown ); + printf( "mov di,dx\n" ); + printf( "add word ptr [bp+di],%d\n", UpDown ); - printf( “mov dl,0\n” ); + printf( "mov dl,0\n" ); } else { if( (New ^ Old) & 4 ) { - if( New & 4 ) Op = “inc”; - else Op = “dec”; + if( New & 4 ) Op = "inc"; + else Op = "dec"; - printf( “%s byte ptr [bp+%d]\n”, Op, LEFT ); - printf( “%s byte ptr [bp+%d]\n”, Op, UPPERLEFT ); - printf( “%s byte ptr [bp+%d]\n”, Op, LOWERLEFT ); + printf( "%s byte ptr [bp+%d]\n", Op, LEFT ); + printf( "%s byte ptr [bp+%d]\n", Op, UPPERLEFT ); + printf( "%s byte ptr [bp+%d]\n", Op, LOWERLEFT ); } if( (New ^ Old) & 1 ) { - if( New & 1 ) Op = “add”; - else Op = “sub”; + if( New & 1 ) Op = "add"; + else Op = "sub"; - printf( “%s word ptr [bp+%d],40h\n”, Op, RIGHT ); - printf( “%s word ptr [bp+%d],40h\n”, Op, UPPERRIGHT ); - printf( “%s word ptr [bp+%d],40h\n”, Op, LOWERRIGHT ); + printf( "%s word ptr [bp+%d],40h\n", Op, RIGHT ); + printf( "%s word ptr [bp+%d],40h\n", Op, UPPERRIGHT ); + printf( "%s word ptr [bp+%d],40h\n", Op, LOWERRIGHT ); } if( abs( UpDown ) > 1 ) { - printf( “add word ptr [bp+%d],%d\n”, UP, UpDown ); - printf( “add word ptr [bp+%d],%d\n”, DOWN, UpDown ); + printf( "add word ptr [bp+%d],%d\n", UP, UpDown ); + printf( "add word ptr [bp+%d],%d\n", DOWN, UpDown ); } else { - if( UpDown == 1 ) Op = “inc”; - else Op = “dec”; + if( UpDown == 1 ) Op = "inc"; + else Op = "dec"; - printf( “%s byte ptr [bp+%d]\n”, Op, UP ); - printf( “%s byte ptr [bp+%d]\n”, Op, DOWN ); + printf( "%s byte ptr [bp+%d]\n", Op, UP ); + printf( "%s byte ptr [bp+%d]\n", Op, DOWN ); } } @@ -328,29 +328,29 @@ void Test( char *Offset, char *Str ) { - printf( “mov bx,[bp+%s]\n”, Offset ); - printf( “cmp bh,[bx]\n” ); - printf( “jnz short FIX_%s%d\n”, Str, Label ); - printf( “%s%d:\n”, Str, Label ); + printf( "mov bx,[bp+%s]\n", Offset ); + printf( "cmp bh,[bx]\n" ); + printf( "jnz short FIX_%s%d\n", Str, Label ); + printf( "%s%d:\n", Str, Label ); } void Fix( char *Offset, char *Str, int JumpBack ) { - printf( “FIX_%s%d:\n”, Str, Label ); - printf( “mov bh,[bx]\n” ); - printf( “mov [bp+%s],bx\n”, Offset ); + printf( "FIX_%s%d:\n", Str, Label ); + printf( "mov bh,[bx]\n" ); + printf( "mov [bp+%s],bx\n", Offset ); - if( *Offset != ‘0’ ) printf( “lea ax,[bp+%s]\n”, Offset ); - else printf( “mov ax,bp\n” ); + if( *Offset != ‘0' ) printf( "lea ax,[bp+%s]\n", Offset ); + else printf( "mov ax,bp\n" ); - printf( “stosw\n” ); + printf( "stosw\n" ); - if( JumpBack ) printf( “jmp short %s%d\n”, Str, Label ); + if( JumpBack ) printf( "jmp short %s%d\n", Str, Label ); } void SecondPass( void ) { - printf( “org 0%02x00h\n”, + printf( "org 0%02x00h\n", (Edge << 7) + (New << 4) + (Old << 1) + 1 ); if( Edge ) @@ -358,109 +358,109 @@ // finished with second pass if( New == 7 && Old == 0 ) { - printf( “cmp bp,offset DGROUP:ChangeCell\n” ); - printf( “jne short NotEnd\n” ); - printf( “mov word ptr es:[di],offset DGROUP:ChangeCell\n” ); - printf( “pop di si bp ds\n” ); - printf( “mov ChangeCell,0\n” ); - printf( “retf\n” ); - printf( “NotEnd:\n” ); + printf( "cmp bp,offset DGROUP:ChangeCell\n" ); + printf( "jne short NotEnd\n" ); + printf( "mov word ptr es:[di],offset DGROUP:ChangeCell\n" ); + printf( "pop di si bp ds\n" ); + printf( "mov ChangeCell,0\n" ); + printf( "retf\n" ); + printf( "NotEnd:\n" ); } GetUpAndDown(); // ah = row, al = col, cx = up, dx = down - printf( “push si\n” ); - printf( “mov si,%d\n”, WRAPLEFT ); // si = left - printf( “cmp al,0\n” ); - printf( “je short L%d\n”, Label ); - printf( “mov si,%d\n”, LEFT ); - printf( “L%d:\n”, Label ); + printf( "push si\n" ); + printf( "mov si,%d\n", WRAPLEFT ); // si = left + printf( "cmp al,0\n" ); + printf( "je short L%d\n", Label ); + printf( "mov si,%d\n", LEFT ); + printf( "L%d:\n", Label ); - Test( “si”, “LEFT” ); - printf( “add si,cx\n” ); - Test( “si”, “UPPERLEFT” ); - printf( “sub si,cx\n” ); - printf( “add si,dx\n” ); - Test( “si”, “LOWERLEFT” ); + Test( "si", "LEFT" ); + printf( "add si,cx\n" ); + Test( "si", "UPPERLEFT" ); + printf( "sub si,cx\n" ); + printf( "add si,dx\n" ); + Test( "si", "LOWERLEFT" ); - printf( “mov si,cx\n” ); - Test( “si”, “UP” ); - printf( “mov si,dx\n” ); - Test( “si”, “DOWN” ); + printf( "mov si,cx\n" ); + Test( "si", "UP" ); + printf( "mov si,dx\n" ); + Test( "si", "DOWN" ); - printf( “cmp byte ptr [bp+_RowColMap-_CellMap],%d\n”, + printf( "cmp byte ptr [bp+_RowColMap-_CellMap],%d\n", (WIDTH - 1) * 3 ); - printf( “mov si,%d\n”, WRAPRIGHT ); // si = right - printf( “je short R%d\n”, Label ); - printf( “mov si,%d\n”, RIGHT ); - printf( “R%d:\n”, Label ); + printf( "mov si,%d\n", WRAPRIGHT ); // si = right + printf( "je short R%d\n", Label ); + printf( "mov si,%d\n", RIGHT ); + printf( "R%d:\n", Label ); - Test( “si”, “RIGHT” ); - printf( “add si,cx\n” ); - Test( “si”, “UPPERRIGHT” ); - printf( “sub si,cx\n” ); - printf( “add si,dx\n” ); - Test( “si”, “LOWERRIGHT” ); + Test( "si", "RIGHT" ); + printf( "add si,cx\n" ); + Test( "si", "UPPERRIGHT" ); + printf( "sub si,cx\n" ); + printf( "add si,dx\n" ); + Test( "si", "LOWERRIGHT" ); } else { - Test( itoa( LEFT, Buf, 10 ), “LEFT” ); - Test( itoa( UPPERLEFT, Buf, 10 ), “UPPERLEFT” ); - Test( itoa( LOWERLEFT, Buf, 10 ), “LOWERLEFT” ); - Test( itoa( UP, Buf, 10 ), “UP” ); - Test( itoa( DOWN, Buf, 10 ), “DOWN” ); - Test( itoa( RIGHT, Buf, 10 ), “RIGHT” ); - Test( itoa( UPPERRIGHT, Buf, 10 ), “UPPERRIGHT” ); - Test( itoa( LOWERRIGHT, Buf, 10 ), “LOWERRIGHT” ); + Test( itoa( LEFT, Buf, 10 ), "LEFT" ); + Test( itoa( UPPERLEFT, Buf, 10 ), "UPPERLEFT" ); + Test( itoa( LOWERLEFT, Buf, 10 ), "LOWERLEFT" ); + Test( itoa( UP, Buf, 10 ), "UP" ); + Test( itoa( DOWN, Buf, 10 ), "DOWN" ); + Test( itoa( RIGHT, Buf, 10 ), "RIGHT" ); + Test( itoa( UPPERRIGHT, Buf, 10 ), "UPPERRIGHT" ); + Test( itoa( LOWERRIGHT, Buf, 10 ), "LOWERRIGHT" ); } - if( New == Old ) Test( “0”, “CENTER” ); + if( New == Old ) Test( "0", "CENTER" ); - if( Edge ) printf( “pop si\n” “mov dl,0\n” ); + if( Edge ) printf( "pop si\n" "mov dl,0\n" ); Next2(); if( Edge ) { - Fix( “si”, “LEFT”, 1 ); - Fix( “si”, “UPPERLEFT”, 1 ); - Fix( “si”, “LOWERLEFT”, 1 ); - Fix( “si”, “UP”, 1 ); - Fix( “si”, “DOWN”, 1 ); - Fix( “si”, “RIGHT”, 1 ); - Fix( “si”, “UPPERRIGHT”, 1 ); - Fix( “si”, “LOWERRIGHT”, New == Old ); + Fix( "si", "LEFT", 1 ); + Fix( "si", "UPPERLEFT", 1 ); + Fix( "si", "LOWERLEFT", 1 ); + Fix( "si", "UP", 1 ); + Fix( "si", "DOWN", 1 ); + Fix( "si", "RIGHT", 1 ); + Fix( "si", "UPPERRIGHT", 1 ); + Fix( "si", "LOWERRIGHT", New == Old ); } else { - Fix( itoa( LEFT, Buf, 10 ), “LEFT”, 1 ); - Fix( itoa( UPPERLEFT, Buf, 10 ), “UPPERLEFT”, 1 ); - Fix( itoa( LOWERLEFT, Buf, 10 ), “LOWERLEFT”, 1 ); - Fix( itoa( UP, Buf, 10 ), “UP”, 1 ); - Fix( itoa( DOWN, Buf, 10 ), “DOWN”, 1 ); - Fix( itoa( RIGHT, Buf, 10 ), “RIGHT”, 1 ); - Fix( itoa( UPPERRIGHT, Buf, 10 ), “UPPERRIGHT”, 1 ); - Fix( itoa( LOWERRIGHT, Buf, 10 ), “LOWERRIGHT”, New == Old ); + Fix( itoa( LEFT, Buf, 10 ), "LEFT", 1 ); + Fix( itoa( UPPERLEFT, Buf, 10 ), "UPPERLEFT", 1 ); + Fix( itoa( LOWERLEFT, Buf, 10 ), "LOWERLEFT", 1 ); + Fix( itoa( UP, Buf, 10 ), "UP", 1 ); + Fix( itoa( DOWN, Buf, 10 ), "DOWN", 1 ); + Fix( itoa( RIGHT, Buf, 10 ), "RIGHT", 1 ); + Fix( itoa( UPPERRIGHT, Buf, 10 ), "UPPERRIGHT", 1 ); + Fix( itoa( LOWERRIGHT, Buf, 10 ), "LOWERRIGHT", New == Old ); } - if( New == Old ) Fix( “0”, “CENTER”, 0 ); + if( New == Old ) Fix( "0", "CENTER", 0 ); - if( Edge ) printf( “pop si\n” “mov dl,0\n” ); + if( Edge ) printf( "pop si\n" "mov dl,0\n" ); Next2(); } void main( void ) { - char *Seg = “ds”; + char *Seg = "ds"; BuildMaps(); - printf( “DGROUP group _DATA\n” ); - printf( “LIFE segment ‘CODE’\n” ); - printf( “assume cs:LIFE,ds:DGROUP,ss:DGROUP,es:NOTHING\n” ); - printf( “.386C\n” “public _NextGen\n\n” ); + printf( "DGROUP group _DATA\n" ); + printf( "LIFE segment ‘CODE'\n" ); + printf( "assume cs:LIFE,ds:DGROUP,ss:DGROUP,es:NOTHING\n" ); + printf( ".386C\n" "public _NextGen\n\n" ); for( Edge = 0; Edge <= 1; Edge++ ) { @@ -475,34 +475,34 @@ } // finished with first pass - printf( “org 0\n” ); - printf( “mov si,Change1\n” ); - printf( “mov di,Change2\n” ); - printf( “mov Change1,di\n” ); - printf( “mov Change2,si\n” ); - printf( “mov ChangeCell,0F000h\n” ); - printf( “mov ax,seg _LDMAP\n” ); - printf( “mov ds,ax\n” ); + printf( "org 0\n" ); + printf( "mov si,Change1\n" ); + printf( "mov di,Change2\n" ); + printf( "mov Change1,di\n" ); + printf( "mov Change2,si\n" ); + printf( "mov ChangeCell,0F000h\n" ); + printf( "mov ax,seg _LDMAP\n" ); + printf( "mov ds,ax\n" ); Next2(); // entry point - printf( “_NextGen: push ds bp si di\n” “cld\n” ); + printf( "_NextGen: push ds bp si di\n" "cld\n" ); - if( WIDTH * HEIGHT > LIST_LIMIT ) Seg = “seg _CHANGE”; + if( WIDTH * HEIGHT > LIST_LIMIT ) Seg = "seg _CHANGE"; - printf( “mov ax,%s\n”, Seg ); - printf( “mov es,ax\n” ); + printf( "mov ax,%s\n", Seg ); + printf( "mov es,ax\n" ); #ifndef NODRAW - printf( “mov ax,0A000h\n” ); - printf( “mov fs,ax\n” ); + printf( "mov ax,0A000h\n" ); + printf( "mov fs,ax\n" ); #endif - printf( “mov si,Change1\n” ); - printf( “mov dl,0\n” ); + printf( "mov si,Change1\n" ); + printf( "mov dl,0\n" ); Next1(); - printf( “LIFE ends\nend\n” ); + printf( "LIFE ends\nend\n" ); } ------------------------ --------------------------------- -------------------- diff --git a/18-04.md b/18-04.md index d9d6ad4..5dec62e 100644 --- a/18-04.md +++ b/18-04.md @@ -14,7 +14,7 @@ #include #include #include - #include “life.h” + #include "life.h" // functions in VIDEO.C void enter_display_mode( void ); @@ -51,14 +51,14 @@ long start_time, end_time; unsigned int seed; - printf( “Seed (0 for random seed): ” ); - scanf( “%d”, &seed ); + printf( "Seed (0 for random seed): " ); + scanf( "%d", &seed ); if( seed == 0 ) seed = (unsigned) time(NULL); srand( seed ); #ifndef NODRAW enter_display_mode(); - show_text( 0, 10, “Generation:” ); + show_text( 0, 10, "Generation:" ); #endif InitCellmap(); // randomly initialize cell map @@ -71,7 +71,7 @@ generation++; #ifndef NOCOUNTER - sprintf( gen_text, “%10lu”, generation ); + sprintf( gen_text, "%10lu", generation ); show_text( 0, 12, gen_text ); #endif } @@ -89,9 +89,9 @@ exit_display_mode(); #endif - printf( “Total generations: %ld\nSeed: %u\n”, generation, seed ); - printf( “%ld ticks\n”, end_time ); - printf( “Time: %f generations/second\n”, + printf( "Total generations: %ld\nSeed: %u\n", generation, seed ); + printf( "%ld ticks\n", end_time ); + printf( "Time: %f generations/second\n", (double)generation / (double)end_time * 18.2 ); } @@ -167,37 +167,37 @@ a *change list;* that is, a list of pointers to cells that change in the current generation. Only those cells and their neighbors need to be checked or touched in any way in order to create the next generation, saving a great many instructions and also a great many cache misses due -to the fact that cellmaps are too big to fit into the 486’s internal +to the fact that cellmaps are too big to fit into the 486's internal cache. During a given generation, David runs down the list of cells that changed from the previous generation to make the changes for this generation, and in the process generates the change list for the next generation. -That’s the overall approach, but this being David Stafford, it’s not -that simple, of course. I’ll let him tell you how his implementation -works in his own words. (I’ve edited David’s text a bit, and added my +That's the overall approach, but this being David Stafford, it's not +that simple, of course. I'll let him tell you how his implementation +works in his own words. (I've edited David's text a bit, and added my own comments in square brackets, so blame me for any errors.) -“Each three cells in the life grid are packed into two bytes, as shown +"Each three cells in the life grid are packed into two bytes, as shown in Figure 18.1. So, it is convenient if the width of the cell array is -an even multiple of three. There’s nothing in the algorithm that +an even multiple of three. There's nothing in the algorithm that prevents it from supporting any arbitrary size, but the code is a bit simpler this way. So if you want a 200x200 grid, I recommend just using a 201x200 grid, and be happy with the extra free column. Otherwise the edge wrapping code gets more complex. -“Since every cell has from zero to eight neighbors, you may be wondering +"Since every cell has from zero to eight neighbors, you may be wondering how I can manage to keep track of them with only three bits. Each cell really has only a maximum of seven neighbors since we only need to keep track of neighbors *outside* of the current cell word. That is, if cell -‘B’ changes state then we don’t need to reflect this in the neighbor -counts of cells ‘A’ and ‘C.’ Updating is made a little faster. [In other +‘B' changes state then we don't need to reflect this in the neighbor +counts of cells ‘A' and ‘C.' Updating is made a little faster. [In other words, when David picks up a word representing three cells, each of the three cells has at least one of the other cells in that word as a neighbor, and the state of that neighbor is stored right in that word, as shown in Figure 18.1. Therefore, the neighbor count for a given cell never needs to reflect more than seven neighbors, because at least one -of the eight neighbors’ states is already encoded in the word.] +of the eight neighbors' states is already encoded in the word.] ![](images/18-01.jpg)\ **Figure 18.1**  *Cell triplet storage.* diff --git a/18-05.md b/18-05.md index 106ef6a..cc8f36b 100644 --- a/18-05.md +++ b/18-05.md @@ -2,21 +2,21 @@ [Previous](18-04.html) [Table of Contents](index.html) [Next](19-01.html) ------------------------ --------------------------------- -------------------- -“The basic idea is to maintain a ‘change list.’ This is an array of +"The basic idea is to maintain a ‘change list.' This is an array of pointers into the cell array. Each change list element points to a word -which changes in the next generation. This way we don’t have to waste +which changes in the next generation. This way we don't have to waste time scanning every cell since most of them do not change. Two passes are made through the change list. The first pass updates the cell display on the screen, sets the life/death status of each cell for this new generation, and updates the neighbor counts for the adjacent cells. There are some efficiencies gained by using cell triplets rather than -individual cells since we usually don’t need to set all eight neighbors. +individual cells since we usually don't need to set all eight neighbors. [Again, the neighbor counts for cells in the same word are implied by the states of those cells.] The second pass sets the next-generation states for the cells and their neighbors, and in the process builds the change list for the next generation. -“Processing each word is a little complex but very fast. A 64K block of +"Processing each word is a little complex but very fast. A 64K block of code exists with routines on each 256-byte boundary. Generally speaking, the entry point corresponds to the high byte of the cell word. This byte contains the life/death values and a bit to indicate if this is an edge @@ -27,31 +27,31 @@ to that address. [Therefore, there are 128 possible jump targets on the first pass, and 128 more on the second, all on 256-byte boundaries and all keyed off the high 7 bits of the cell triplet state; because bit 8 of the jump index is 0 on the first pass and 1 on the second, there is -no conflict. The lower bit isn’t needed for other purposes because only +no conflict. The lower bit isn't needed for other purposes because only the edge flag bit and the six life/death state bits matter for jumping -into David’s state machine. The other nine bits, the bits used for the +into David's state machine. The other nine bits, the bits used for the neighbor counts, are used only in the next step.] -“Determining which changes must be made to a cell triplet is easy and -surprisingly quick. There’s no counting! Instead, I use a 64K lookup +"Determining which changes must be made to a cell triplet is easy and +surprisingly quick. There's no counting! Instead, I use a 64K lookup 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 -done.” [This step is as important as it is obscure. David has a 64K +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 -for the next generation. In other words, David’s table is constructed so +for the next generation. In other words, David's table is constructed so that the edge flag bit, the life/death states, and the three neighbor count fields form an index to a byte describing the next generation state for that triplet. In practice, only the next generation field of the cell changes. Then, if another change to a nearby cell tries to -nudge that cell into changing again, David’s code sees that the desired +nudge that cell into changing again, David's code sees that the desired state is already set, and does not add that cell to the change list again.] -Segment usage in David’s assembly code is summarized in Listing 18.6. +Segment usage in David's assembly code is summarized in Listing 18.6. **LISTING 18.6 QLIFE Assembly Segment Usage** @@ -62,12 +62,12 @@ Segment usage in David’s assembly code is summarized in Listing 18.6. FS : Video segment GS : Unused -#### A Layperson’s Overview of QLIFE {#Heading6} +#### A Layperson's Overview of QLIFE {#Heading6} -Most likely, you’re scratching your head right now in bemusement. I -don’t blame you; I felt the same way myself at first. It’s actually +Most likely, you're scratching your head right now in bemusement. I +don't blame you; I felt the same way myself at first. It's actually pretty simple, though, once you have the hang of it. Basically, David -runs down the change list, visiting every cell that’s due to change in +runs down the change list, visiting every cell that's due to change in this generation, setting it to the new state, drawing it in the new state, and adjusting the counts of all its neighbors. David has a separate assembly routine for every possible change of state for a cell @@ -83,8 +83,8 @@ David looks at that triplet and all affected neighbors to see which will change in the next generation. He tests for this condition by using each potentially changed cell triplet word as an index into the aforementioned lookup table of new states. If the current state matches -the appropriate state for the next generation, then there’s nothing to -do and the cell is not added to the change list. If the states don’t +the appropriate state for the next generation, then there's nothing to +do and the cell is not added to the change list. If the states don't match, then the cell is added to the change list, and the appropriate state for the next generation is set in the cell triplet. David checks the minimum possible number of cells for change by branching to code @@ -101,18 +101,18 @@ the desired result—just three instructions: These suffice to select the proper, minimum-work code to process the next cell triplet that has changed, and all potentially affected -neighbors. For all the size of David’s code, it has an astonishing +neighbors. For all the size of David's code, it has an astonishing economy of effort, as execution glides through the change list without a wasted instruction. -Alas, I don’t have the room to discuss Peter Klerings’ equally -remarkable Life implementation here. I’ll close this chapter with a +Alas, I don't have the room to discuss Peter Klerings' equally +remarkable Life implementation here. I'll close this chapter with a quote from Terje Mathisen, one of the finest optimizers it has ever been -my pleasure to meet, who, after looking over David’s and Peter’s -entries, said, “This has been an eye-opening experience for me. I -honestly thought I had the fastest possible approach.” TANSTATFC. +my pleasure to meet, who, after looking over David's and Peter's +entries, said, "This has been an eye-opening experience for me. I +honestly thought I had the fastest possible approach." TANSTATFC. -There Ain’t No Such Thing As the Fastest Code. +There Ain't No Such Thing As the Fastest Code. ------------------------ --------------------------------- -------------------- [Previous](18-04.html) [Table of Contents](index.html) [Next](19-01.html) diff --git a/19-01.md b/19-01.md index 8a7998d..c0307aa 100644 --- a/19-01.md +++ b/19-01.md @@ -9,10 +9,10 @@ Chapter 19\ ### Learning a Whole Different Set of Optimization Rules {#Heading2} I can still remember the day I did my first 8088 programming. I had just -moved over from the distantly related Z80, so the 8088 wasn’t totally +moved over from the distantly related Z80, so the 8088 wasn't totally alien, but it was nonetheless an incredibly exciting processor. The -8088’s instruction set was vastly more powerful and varied than the -Z80’s, and as someone who thrives on puzzles of all sorts, from +8088's instruction set was vastly more powerful and varied than the +Z80's, and as someone who thrives on puzzles of all sorts, from crosswords to Freecell to jigsaws to assembly language optimization, I was delighted to find that the 8088 made the optimization universe an order of magnitude more complicated—and correspondingly more @@ -20,7 +20,7 @@ interesting. Well, the years went by and the Z80 just died, and 8088 optimization got ever more complex and intriguing as I discovered the hazards of the -8088’s cycle-eaters. By the time 1989 rolled around, I had written *Zen +8088's cycle-eaters. By the time 1989 rolled around, I had written *Zen of Assembly Language*, in which I described all that I had learned about the 8088 and concluded that 8088 optimization was a black art of infinite subtlety. Unfortunately, by that time the 286 was the standard, @@ -45,43 +45,43 @@ hazards, and life was good again. The Pentium has two execution pipelines and enough rules and exceptions to those rules to bring joy to the heart of the hardest-core assembly junkie. For a change, Intel documented most of the Pentium optimization rules and spread the word -about them, so we don’t have to go through as much spelunking of the -Pentium as with its predecessors. They’ve done this, I suspect, largely -because more than any previous x86 processor, the Pentium’s performance +about them, so we don't have to go through as much spelunking of the +Pentium as with its predecessors. They've done this, I suspect, largely +because more than any previous x86 processor, the Pentium's performance is highly dependent on properly optimized code. In the worst case, where the second execution pipe is dormant most of -the time, the Pentium won’t perform all that much better than a 486 at +the time, the Pentium won't perform all that much better than a 486 at the same clock speed. In the best case, where the second pipe is heavily -used and the Pentium’s other advantages (such as branch prediction, +used and the Pentium's other advantages (such as branch prediction, write-back cache, 64-bit full speed external bus, and dual 8K caches) can kick in, the Pentium can be more than twice as fast as a 486. In a critical inner loop, hand optimization can double or even triple -performance over 486-optimized code—and that’s on top of the sorts of +performance over 486-optimized code—and that's on top of the sorts of algorithmic and design optimizations that are routinely performed on any processor. Good compilers can make a big difference on the Pentium, too, -but there are some gotchas there, to which I’ll return later. +but there are some gotchas there, to which I'll return later. -It’s been a long time coming, but hard-core, big-payoff assembly +It's been a long time coming, but hard-core, big-payoff assembly language optimization is back in style, and for the rest of this book -I’ll be delving into the Byzantine wonders of the Pentium. In this -chapter, I’ll do a quick overview, then cover a variety of smaller -Pentium optimization topics. In the next chapter, I’ll tackle the +I'll be delving into the Byzantine wonders of the Pentium. In this +chapter, I'll do a quick overview, then cover a variety of smaller +Pentium optimization topics. In the next chapter, I'll tackle the 900-pound gorilla of Pentium optimization: superscalar (dual execution -pipe) programming. Trust me, this’ll be fun. +pipe) programming. Trust me, this'll be fun. Listen, do you want to know a secret? This lead-in has been brought to -you with the help of “classic rock”—another way of saying “music Baby +you with the help of "classic rock"—another way of saying "music Baby Boomers listened to back when they cared more about music than 401Ks and -regular flossing.” There are so many of us Boomers that our music, even -the worst of it, will never go away. When we’re 90 years old, propped up +regular flossing." There are so many of us Boomers that our music, even +the worst of it, will never go away. When we're 90 years old, propped up in our Kraftmatic adjustable beds and surfing the 5,000-channel information superhighway from one infomercial to the next, the sound system in the retirement community will be piping in a Muzak version of -“Louie, Louie,” while on the holovid Country Joe McDonald and the Fish +"Louie, Louie," while on the holovid Country Joe McDonald and the Fish pitch Preparation H. I can hardly wait. -Gimme a “P”.... +Gimme a "P".... ### The Pentium: An Overview {#Heading4} @@ -89,7 +89,7 @@ Architecturally, the Pentium is vastly different in many ways from the 486, but most of those differences are transparent to programmers. After all, the whole idea behind the Pentium is that it runs the same code as previous x86 processors, but faster; otherwise, Intel could have made a -faster, cheaper RISC processor. Still, knowledge of the Pentium’s +faster, cheaper RISC processor. Still, knowledge of the Pentium's architecture is useful for understanding exactly how code will perform, and a few of the architectural differences are most decidedly *not* transparent to performance programmers. @@ -99,7 +99,7 @@ second stripped-down 486 EU, on a single chip. The first EU is referred to as the U execution pipe, or *U-pipe;* the second, more limited one is called the *V-pipe*. The two pipes are capable of executing instructions simultaneously, have separate write buffers, and can even access the -data cache simultaneously (although with certain limitations that I’ll +data cache simultaneously (although with certain limitations that I'll 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 @@ -108,12 +108,12 @@ those for the 486, although some instructions—notably **MUL**, the repeated string instructions, and some of the shifts and rotates—have gotten faster. -My first thought upon hearing of the Pentium’s dual pipes was to wonder +My first thought upon hearing of the Pentium's dual pipes was to wonder how often the prefetch queue stalls for lack of instruction bytes, given that the demand for instruction bytes can be twice that of the 486. The answer is: rarely indeed, and then only because the code is not in the internal cache. The 486 has a single 8K cache that stores both code and -data, and prefetching can stall if data fetching doesn’t allow time for +data, and prefetching can stall if data fetching doesn't allow time for prefetching to occur (although this rarely happens in practice). ------------------------ --------------------------------- -------------------- diff --git a/19-02.md b/19-02.md index edc2f58..4fd72af 100644 --- a/19-02.md +++ b/19-02.md @@ -3,17 +3,17 @@ ------------------------ --------------------------------- -------------------- ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *The Pentium, on the other hand, has two separate 8K caches, one for code and one for data, so code prefetches can never collide with data fetches; the prefetch queue can stall only when the code being fetched isn’t in the internal code cache.* + ![](images/i.jpg) *The Pentium, on the other hand, has two separate 8K caches, one for code and one for data, so code prefetches can never collide with data fetches; the prefetch queue can stall only when the code being fetched isn't in the internal code cache.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ (And yes, self-modifying code still works; as with all Pentium changes, the dual caches introduce no incompatibilities with 386/486 code.) Also, -because the code and data caches are separate, code can’t be driven out +because the code and data caches are separate, code can't be driven out of the cache in a tight loop that accesses a lot of data, unlike the -486. In addition, the Pentium expands the 486’s 32-byte prefetch queue +486. In addition, the Pentium expands the 486's 32-byte prefetch queue to 128 bytes. In conjunction with the branch prediction feature (described next), which allows the Pentium to prefetch properly at most -branches, this larger prefetch queue means that the Pentium’s two pipes +branches, this larger prefetch queue means that the Pentium's two pipes should be better fed than those of any previous x86 processor. #### Crossing Cache Lines {#Heading5} @@ -22,12 +22,12 @@ There are three other characteristics of the Pentium that make for a healthy supply of instruction bytes. One is that the Pentium can prefetch instructions across cache lines. Unlike the 486, where there is a 3-cycle penalty for branching to an instruction that spans a cache -line, there’s no such penalty on the Pentium. The second is that the +line, there's no such penalty on the Pentium. The second is that the cache line size (the number of bytes fetched from the external cache or main memory on a cache miss) on the Pentium is 32 bytes, twice the size -of the 486’s cache line, so a cache miss causes a longer run of +of the 486's cache line, so a cache miss causes a longer run of instructions to be placed in the cache than on the 486. The third is -that the Pentium’s external bus is twice as wide as the 486’s, at 64 +that the Pentium's external bus is twice as wide as the 486's, at 64 bits, and runs twice as fast, at 66 MHz, so the Pentium can fetch both instruction and data bytes from the external cache four times as fast as the 486. @@ -40,14 +40,14 @@ The upshot of all this is that at the same clock speed, with code and data that are mostly in the internal caches, the Pentium maxes out somewhere around twice as fast as a 486. (When the caches are missed a lot, the Pentium can get as much as three to four times faster, due to -the superior external bus and bigger caches.) Most of this won’t affect -how you program, but it is useful to know that you don’t have to worry -about instruction fetching. It’s also useful to know the sizes of the +the superior external bus and bigger caches.) Most of this won't affect +how you program, but it is useful to know that you don't have to worry +about instruction fetching. It's also useful to know the sizes of the caches because a high cache hit rate is crucial to Pentium performance. Cache misses are vastly slower than cache hits (anywhere from two to 50 or more times as slow, depending on the speed of the external cache and -whether the external cache misses as well), and the Pentium can’t use -the V-pipe on code that hasn’t already been executed out of the cache at +whether the external cache misses as well), and the Pentium can't use +the V-pipe on code that hasn't already been executed out of the cache at least once. This means that it is *very* important to get the working sets of critical loops to fit in the internal caches. @@ -55,27 +55,27 @@ One change in the Pentium that you definitely do have to worry about is superscalar execution. Utilization of the V-pipe can range from near zero percent to 100 percent, depending on the code being executed, and careful rearrangement of code can have amazing effects. Maxing out -V-pipe use is not a trivial task; I’ll spend all of the next chapter +V-pipe use is not a trivial task; I'll spend all of the next chapter discussing it so as to have time to cover it properly. In the meantime, two good references for superscalar programming and other Pentium -information are Intel’s *Pentium Processor User’s Manual: Volume 3: +information are Intel's *Pentium Processor User's Manual: Volume 3: Architecture and Programming Manual* (ISBN 1-55512-195-0; Intel order -number 241430-001), and the article “Optimizing Pentium Code” by Mike -Schmidt, in *Dr. Dobb’s Journal* for January 1994. +number 241430-001), and the article "Optimizing Pentium Code" by Mike +Schmidt, in *Dr. Dobb's Journal* for January 1994. #### Cache Organization {#Heading6} -There are two other interesting changes in the Pentium’s cache +There are two other interesting changes in the Pentium's cache organization. First, the cache is two-way set-associative, whereas the -486 is four-way set-associative. The details of this don’t matter, but +486 is four-way set-associative. The details of this don't matter, but simply put, this, combined with the 32-byte cache line size, means that the Pentium has somewhat coarser granularity in both space and time than the 486 in terms of packing bytes into the cache, although the total -cache space is now bigger. There’s nothing you can do about this, but it -may make it a little harder to get a loop’s working set into the cache. +cache space is now bigger. There's nothing you can do about this, but it +may make it a little harder to get a loop's working set into the cache. Second, the internal cache can now be configured (by the BIOS or OS; you -won’t have to worry about it) for write-back rather than write-through -operation. This means that writes to the internal data cache don’t +won't have to worry about it) for write-back rather than write-through +operation. This means that writes to the internal data cache don't necessarily get propagated to the external bus until other demands for cache space force the data out of the cache, making repeated writes to memory variables such as loop counters cheaper on average than on the @@ -84,9 +84,9 @@ memory variables such as loop counters cheaper on average than on the As a final note on Pentium architecture for this chapter, the pipeline stalls (what Intel calls AGIs, for *Address Generation Interlocks*) that I discussed earlier in this book (see Chapter 12) are still present in -the Pentium. In fact, they’re there in spades on the Pentium; the two +the Pentium. In fact, they're there in spades on the Pentium; the two pipelines mean that an AGI can now slow down execution of an instruction -that’s *three* instructions away from the AGI (because four instructions +that's *three* instructions away from the AGI (because four instructions can execute in two cycles). So, for example, the code sequence add edx,4 ;U-pipe cycle 1 @@ -110,7 +110,7 @@ AGI. Rearranging the code like makes it functionally identical, but cuts the cycles to 2—a 50 percent improvement. Clearly, avoiding AGIs becomes a much more challenging and -rewarding game in a superscalar world, one to which I’ll return in the +rewarding game in a superscalar world, one to which I'll return in the next chapter. ------------------------ --------------------------------- -------------------- diff --git a/19-03.md b/19-03.md index 6b8b4e3..ddb462e 100644 --- a/19-03.md +++ b/19-03.md @@ -4,9 +4,9 @@ ### Faster Addressing and More {#Heading7} -I’ll spend the rest of this chapter covering a variety of Pentium +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 +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 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 @@ -16,12 +16,12 @@ 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 +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 +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 @@ -35,14 +35,14 @@ boundary, as in mov eax,[ebx] costs three cycles. On the other hand, as noted above, branch targets -can now span cache lines with impunity, so on the Pentium there’s no +can now span cache lines with impunity, so on the Pentium there's no good argument for the paragraph (that is, 16-byte) alignment that Intel recommends for 486 jump targets. The 32-byte alignment might make for slightly more efficient Pentium cache usage, but would make code much bigger overall. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *In fact, given that most jump targets aren’t in performance-critical code, it’s hard to make a compelling argument for aligning branch targets even on the 486. I’d say that no alignment (except possibly where you know a branch target lies in a key loop), or at most dword alignment (for the 386) is plenty, and can shrink code size considerably.* + ![](images/i.jpg) *In fact, given that most jump targets aren't in performance-critical code, it's hard to make a compelling argument for aligning branch targets even on the 486. I'd say that no alignment (except possibly where you know a branch target lies in a key loop), or at most dword alignment (for the 386) is plenty, and can shrink code size considerably.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Instruction prefixes are awfully expensive; avoid them if you can. @@ -54,9 +54,9 @@ cycle and shuts down the V-pipe for that cycle, effectively costing as much as two normal instructions (although prefix cycles can overlap with previous multicycle instructions, or AGIs, as on the 486). This means that using 32-bit addressing or 32-bit operands in a 16-bit segment, or -vice versa, makes for bigger code that’s significantly slower. So, for +vice versa, makes for bigger code that's significantly slower. So, for example, you should generally avoid 16-bit variables (shorts, in C) in -32-bit code, although if using 32-bit variables where they’re not needed +32-bit code, although if using 32-bit variables where they're not needed makes your data space get a lot bigger, you may want to stick with shorts, especially since longs use the cache less efficiently than shorts. The trade-off depends on the amount of data and the number of @@ -72,21 +72,21 @@ 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*]** instruction (which normally takes 3 cycles) 5, 10, or more cycles -slower. Most programmers will never use **LOCK** on purpose—it’s -primarily an operating system instruction—but there’s a hidden gotcha +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 with a memory operand. ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) ***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 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 you possibly can.* + ![](images/i.jpg) ***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 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 you possibly can.* ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -As with the 486, don’t use **ENTER** or **LEAVE**, which are slower than +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 +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 +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 Pentium may have to shut down the V-pipe for a cycle to avoid potential diff --git a/19-04.md b/19-04.md index dbacbdd..1893893 100644 --- a/19-04.md +++ b/19-04.md @@ -15,30 +15,30 @@ or V-pipe, respectively)—1 or 2 cycles more than a branch and 3 or 4 cycles more than a fall-through on the 486. ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Branch prediction is unprecedented in the x86, and fundamentally alters the nature of pedal-to-the-metal optimization, for the simple reason that it renders unrolled loops largely obsolete. Rare indeed is the loop that can’t afford to spare even 1 or 0 (yes, zero!) cycles per iteration for loop counting, and that’s how low the cost can go for maintaining a loop on the Pentium.* + ![](images/i.jpg) *Branch prediction is unprecedented in the x86, and fundamentally alters the nature of pedal-to-the-metal optimization, for the simple reason that it renders unrolled loops largely obsolete. Rare indeed is the loop that can't afford to spare even 1 or 0 (yes, zero!) cycles per iteration for loop counting, and that's how low the cost can go for maintaining a loop on the Pentium.* ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Also, unrolled loops are bigger than normal loops, so there are extra (and expensive) cache misses the first time through the loop if the -entire loop isn’t already in the cache; then, too, an unrolled loop will +entire loop isn't already in the cache; then, too, an unrolled loop will shoulder other code out of the internal and external caches. If in a critical loop you absolutely need the time taken by the loop control instructions, or if you need an extra register that can be freed by -unrolling a loop, then by all means unroll the loop. Don’t expect the +unrolling a loop, then by all means unroll the loop. Don't expect the sort of speed-up you get from this on the 486 or especially the 386, though, and watch out for the cache effects. You may well wonder exactly *when* the Pentium correctly predicts branching. Alas, this is one area that Intel has declined to document, beyond saying that you should endeavor to fall through branches when you -have a choice. That’s good advice on every other x86 processor, anyway, -so it’s well worth following. Also, it’s a pretty safe bet that in a +have a choice. That's good advice on every other x86 processor, anyway, +so it's well worth following. Also, it's a pretty safe bet that in a tight loop, the Pentium will start guessing the right branch direction at the bottom of the loop pretty quickly, so you can treat loop branches as one-cycle instructions. -It’s an equally safe bet that it’s a bad move to have in a loop a -conditional branch that goes both ways on a random basis; it’s hard to +It's an equally safe bet that it's a bad move to have in a loop a +conditional branch that goes both ways on a random basis; it's hard to see how the Pentium could consistently predict such branches correctly, and mispredicted branches are more expensive than they might appear to be. Not only does a mispredicted branch take 4 or 5 cycles, but the @@ -47,11 +47,11 @@ time—3 times as many as the 486 can execute during its branch time—so correct branch prediction (or eliminating branch instructions, if possible) is very important in inner loops. Note that on the 486 you can count on a branch to take 1 cycle when it falls through, but on the -Pentium you can’t be sure whether it will take 1 or either 4 or 5 cycles +Pentium you can't be sure whether it will take 1 or either 4 or 5 cycles on any given iteration. ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *As things currently stand, branch prediction is an annoyance for assembly language optimization because it’s impossible to be certain exactly how code will perform until you measure it, and even then it’s difficult to be sure exactly where the cycles went. All I can say is try to fall through branches if possible, and try to be consistent in your branching if not.* + ![](images/i.jpg) *As things currently stand, branch prediction is an annoyance for assembly language optimization because it's impossible to be certain exactly how code will perform until you measure it, and even then it's difficult to be sure exactly where the cycles went. All I can say is try to fall through branches if possible, and try to be consistent in your branching if not.* ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### Miscellaneous Pentium Topics {#Heading9} @@ -59,24 +59,24 @@ 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 +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, +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. #### 486 versus Pentium Optimization {#Heading10} -Many Pentium optimizations help, or at least don’t hurt, on the 486. +Many Pentium optimizations help, or at least don't hurt, on the 486. Many, but not all—and many *do* hurt on the 386. As I discuss various Pentium optimizations, I will attempt to note the effects on the 486 as well, but doing this in complete detail would double the sizes of these -discussions and make them hard to follow. In general, I’d recommend +discussions and make them hard to follow. In general, I'd recommend reserving Pentium optimization for your most critical code, and even -there, it’s a good idea to have at least two code paths, one for the 386 -and one for the 486/Pentium. It’s also a good idea to time your code on -a 486 before and after Pentium-optimizing it, to make sure you haven’t +there, it's a good idea to have at least two code paths, one for the 386 +and one for the 486/Pentium. It's also a good idea to time your code on +a 486 before and after Pentium-optimizing it, to make sure you haven't hurt performance on what will be, after all, by far the most important processor over the next couple of years. @@ -88,12 +88,12 @@ on the fastest hardware, Pentium optimization can make your code #### Going Superscalar {#Heading11} -In the next chapter, we’ll look into the single biggest element of -Pentium performance, cranking up the Pentium’s second execution pipe. +In the next chapter, we'll look into the single biggest element of +Pentium performance, cranking up the Pentium's second execution pipe. This is the area in which compiler technology is most touted for the Pentium, the two thoughts apparently being that (1) most existing code is in C, so recompiling to use the second pipe better is an automatic -win, and (2) it’s so complicated to optimize Pentium code that only a +win, and (2) it's so complicated to optimize Pentium code that only a compiler can do it well. The first point is a reasonable one, but it does suffer from one flaw for large programs, in that Pentium-optimized code is larger than 486- or 386-optimized code, for reasons that will @@ -106,15 +106,15 @@ but can be less beneficial than expected or even slower in some cases. What makes more sense is enabling Pentium optimization *only* for key code. Better yet, you could hand-tune the most important code—and yes, you can absolutely do a better job with a small, critical loop than any -PC compiler I’ve ever seen, or expect to see. Sure, you keep hearing how +PC compiler I've ever seen, or expect to see. Sure, you keep hearing how great each new compiler generation is, and compilers certainly have -improved; but they play by the same rules we do, and we’re more flexible -and know more about what we’re doing—and now we have the wonderfully +improved; but they play by the same rules we do, and we're more flexible +and know more about what we're doing—and now we have the wonderfully complex and powerful Pentium upon which to loose our carbon-based optimizers. A compiler that generates better code than a good assembly programmer? -That’ll be the day. +That'll be the day. ------------------------ --------------------------------- -------------------- [Previous](19-03.html) [Table of Contents](index.html) [Next](20-01.html) diff --git a/20-01.md b/20-01.md index f85598c..91e3818 100644 --- a/20-01.md +++ b/20-01.md @@ -6,7 +6,7 @@ Chapter 20\ Pentium Rules {#Heading1} -------------- -### How Your Carbon-Based Optimizer Can Put the “Super” in Superscalar {#Heading2} +### How Your Carbon-Based Optimizer Can Put the "Super" in Superscalar {#Heading2} At the 1983 West Coast Computer Faire, my friend Dan Illowsky, Andy Greenberg (co-author of Wizardry, at that time the best-selling computer @@ -16,69 +16,69 @@ category at the time was educational software, and one of the hottest new educational software companies was Spinnaker Software. Andy used Spinnaker as an example of a company that had been aimed at a good market and started up properly, and was succeeding as a result. Dan -didn’t buy this; his point was that Spinnaker had been given a bundle of +didn't buy this; his point was that Spinnaker had been given a bundle of money to get off the ground, and was growing only by spending a lot of -that money in order to move its products. “Heck,” said Dan, “I could get +that money in order to move its products. "Heck," said Dan, "I could get that kind of market share too if I gave away a fifty-dollar bill with -each of my games.” +each of my games." Remember, this was a time when a program, two diskette drives (for duplicating disks), and a couple of ads were enough to start a company, and, in fact, Dan built a very successful game company out of not much -more than that. (I’ll never forget coming to visit one day and finding +more than that. (I'll never forget coming to visit one day and finding his apartment stuffed literally to the walls and ceiling with boxes of diskettes and game packages; he had left a narrow path to the computer so his wife and his mother could get in there to duplicate disks.) Back then, the field was wide open, with just about every competent programmer thinking of striking out on his or her own to try to make their fortune, and Dan and Andy and I were no exceptions. In short, we -were having a perfectly normal conversation, and Dan’s comment was both +were having a perfectly normal conversation, and Dan's comment was both appropriate, and, in retrospect, accurate. Appropriate, save for one thing: We were having this conversation while walking through a low-rent section of Market Street in San Francisco at night. A bum sitting against a nearby building overheard Dan, and rose up, shouting in a quavering voice loud enough to wake the dead, -“Fifty-dollar bill! Fifty-dollar bill! He’s giving away fifty-dollar -bills!” We ignored him; undaunted, he followed us for a good half mile, -stopping every few feet to bellow “fifty-dollar bill!” No one else +"Fifty-dollar bill! Fifty-dollar bill! He's giving away fifty-dollar +bills!" We ignored him; undaunted, he followed us for a good half mile, +stopping every few feet to bellow "fifty-dollar bill!" No one else seemed to notice, and no one hassled us, but I was mighty happy to get to the sanctuary of the Fairmont Hotel and slip inside. -The point is, most actions aren’t inherently good or bad; it’s all a -matter of context. If Dan had uttered the words “fifty-dollar bill” on -the West Coast Faire’s show floor, no one would have batted an eye. If +The point is, most actions aren't inherently good or bad; it's all a +matter of context. If Dan had uttered the words "fifty-dollar bill" on +the West Coast Faire's show floor, no one would have batted an eye. If he had said it in a slightly worse part of town than he did, we might have learned just how fast the three of us could run. -Similarly, there’s no such thing as inherently fast code, only fast code +Similarly, there's no such thing as inherently fast code, only fast code in context. At the moment, the context is the Pentium, and the truth is that a sizable number of the x86 optimization tricks that you and I have learned over the past ten years are obsolete on the Pentium. True, the Pentium contains what amounts to about one-and-a-half 486s, but, as -we’ll see shortly, that doesn’t mean that optimized Pentium code looks +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 +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 expensive on the 486.) So discard your x86 preconceptions as we delve into superscalar optimization for this one-of-a-kind processor. ### An Instruction in Every Pipe {#Heading3} -In the last chapter, we took a quick tour of the Pentium’s architecture, -and started to look into the Pentium’s optimization rules. Now we’re -ready to get to the key rules, those having to do with the Pentium’s +In the last chapter, we took a quick tour of the Pentium's architecture, +and started to look into the Pentium's optimization rules. Now we're +ready to get to the key rules, those having to do with the Pentium's most unique and powerful feature, the ability to execute more than one instruction per cycle. This is known as *superscalar execution*, and has heretofore been the sole province of fast RISC CPUs. The Pentium has two integer execution units, called the *U-pipe* and the *V-pipe*, which can execute two separate instructions simultaneously, potentially doubling performance—but only under the proper conditions. (There is also a -separate floating-point execution unit that I won’t have the space to +separate floating-point execution unit that I won't have the space to cover in this book.) Your job, as a performance programmer, is to understand the conditions needed for superscalar performance and make -sure they’re met, and that’s what this and the next chapters are all +sure they're met, and that's what this and the next chapters are all about. The two pipes are not independent processors housed in a single chip; @@ -92,7 +92,7 @@ take the next two waiting instructions and execute both at once, rather than one after the other. The U-pipe is the more capable of the two pipes, able to execute any -instruction in the Pentium’s instruction set. (A number of instructions +instruction in the Pentium's instruction set. (A number of instructions actually use both pipes at once. Logically, though, you can think of such instructions as U-pipe instructions, and of the Pentium optimization model as one in which the U-pipe is able to execute all @@ -105,22 +105,22 @@ simple instructions such as **MOV** and **ADD**, but unable to handle even **ADC** or **SBB**. ![](images/20-01.jpg)\ - **Figure 20.1**  *The Pentium’s two pipes.* + **Figure 20.1**  *The Pentium's two pipes.* Getting two instructions executing simultaneously in the two pipes is trickier than it sounds, not only because the V-pipe can handle only a -relatively small subset of the Pentium’s instruction set, but also +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**. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *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**.* + ![](images/i.jpg) *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**.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Trickier yet, register contention can shut down the V-pipe on any given cycle, and Address Generation Interlocks (AGIs) can stall either pipe at -any time, as we’ll see in the next chapter. +any time, as we'll see in the next chapter. The key to Pentium optimization is to view execution as a stream of instructions going through the U- and V-pipes, and to eliminate, as much diff --git a/20-02.md b/20-02.md index cf91b6c..ada0762 100644 --- a/20-02.md +++ b/20-02.md @@ -8,7 +8,7 @@ Any instruction can go through the U-pipe, and, for practical purposes, the U-pipe is always executing instructions. (The exceptions are when the U-pipe execution unit is waiting for instruction or data bytes after a cache miss, and when a U-pipe instruction finishes before a paired -V-pipe instruction, as I’ll discuss below.) Only the instructions shown +V-pipe instruction, as I'll discuss below.) Only the instructions shown 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 @@ -26,7 +26,7 @@ finished (that is, once neither the U- nor V-pipe is executing anything), the Pentium sends the next instruction through the U-pipe. If the instruction after the one in the U-pipe is an instruction the V-pipe can handle, if the instruction in the U-pipe is pairable, and if -register contention doesn’t occur, then the V-pipe starts executing that +register contention doesn't occur, then the V-pipe starts executing that instruction, as shown in Figure 20.2. Otherwise, the second instruction waits until the first instruction is done, then executes in the U-pipe, possibly pairing with the next instruction in line if all pairing @@ -70,7 +70,7 @@ conditions are met. JMP/CALL near (1 cycle if predicted correctly; 3 cycles otherwise) - † Can’t execute in V-pipe if address contains a displacement + † Can't execute in V-pipe if address contains a displacement **Table 20.1 Instructions that can execute in the V-pipe.** @@ -131,7 +131,7 @@ in Figure 20.3—a full cycle *faster* than **PUSH [*mem*]**, which takes ROL/ROR/RCL/RCR reg,1 (1 cycle) - † Can’t pair if address contains a displacement + † Can't pair if address contains a displacement †† Includes shift-by-1 forms of instructions **Table 20.2 Instructions that, when executed in the U-pipe, allow @@ -141,13 +141,13 @@ V-pipe.** * * * * * ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *A fundamental rule of Pentium optimization is that it pays to break complex instructions into equivalent simple instructions, then shuffle the simple instructions for maximum use of the V-pipe. This is true partly because most of the pairable instructions are simple instructions, and partly because breaking instructions into pieces allows more freedom to rearrange code to avoid the AGIs and register contention I’ll discuss in the next chapter.* + ![](images/i.jpg) *A fundamental rule of Pentium optimization is that it pays to break complex instructions into equivalent simple instructions, then shuffle the simple instructions for maximum use of the V-pipe. This is true partly because most of the pairable instructions are simple instructions, and partly because breaking instructions into pieces allows more freedom to rearrange code to avoid the AGIs and register contention I'll discuss in the next chapter.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ![](images/20-02.jpg)\ **Figure 20.2**  *Instruction flow through the two pipes.* -One downside of this “RISCification” (turning complex instructions into +One downside of this "RISCification" (turning complex instructions into simple, RISC-like ones) of Pentium-optimized code is that it makes for substantially larger code. For example, @@ -174,11 +174,11 @@ versus the equivalent: 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) *It’s not unusual for Pentium optimization to approximately double both performance and code size at the same time. In an important loop, go for performance and ignore the size, but on a program-wide basis, the size bears watching.* + ![](images/i.jpg) *It's not unusual for Pentium optimization to approximately double both performance and code size at the same time. In an important loop, go for performance and ignore the size, but on a program-wide basis, the size bears watching.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------ --------------------------------- -------------------- diff --git a/20-03.md b/20-03.md index 6748960..daec678 100644 --- a/20-03.md +++ b/20-03.md @@ -21,12 +21,12 @@ 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 4 cycles of work, or 67 percent utilization. Even though these instructions pair, then, this sequence fails to make maximum use of the -Pentium’s horsepower. +Pentium's horsepower. The key here is that when two instructions pair, both execution units are tied up until both instructions have finished (which means at least for the amount of time required for the longer of the two to execute, -plus possibly some extra cycles for pairable instructions that can’t +plus possibly some extra cycles for pairable instructions that can't fully overlap, as described below). The logical conclusion would seem to be that we should strive to pair instructions of the same lengths, but that is often not correct. @@ -38,35 +38,35 @@ that is often not correct. ![](images/20-04.jpg)\ **Figure 20.4**  *Lockstep execution and idle time in the V-pipe.* -Here’s why. The Pentium is fully capable of handling instructions that +Here's why. The Pentium is fully capable of handling instructions that use memory operands in either pipe, or, if necessary, in both pipes at once. Each pipe has its own write FIFO, which buffers the last few writes and takes care of writing the data out while the Pentium continues processing. The Pentium also has a write-back internal data -cache, so data that is frequently changed doesn’t have to be written to +cache, so data that is frequently changed doesn't have to be written to external memory (which is much slower than the cache) very often. This combination means that unless you write large blocks of data at a high -speed, the Pentium should be able to keep up with both pipes’ memory +speed, the Pentium should be able to keep up with both pipes' memory writes without stalling execution. -The Pentium is also designed to satisfy both pipes’ needs for reading +The Pentium is also designed to satisfy both pipes' needs for reading memory operands with little waiting. The data cache is constructed so that both pipes can read from the cache *on the same cycle*. This feat is accomplished by organizing the data cache as eight-banked memory, as shown in Figure 20.5, with each 32-byte cache line consisting of 8 dwords, 1 in each bank. The banks are independent of one another, so as -long as the desired data is in the cache and the U- and V-pipes don’t +long as the desired data is in the cache and the U- and V-pipes don't try to read from the same bank on the same cycle, both pipes can read memory operands on the same cycle. (If there is a cache bank collision, the V-pipe instruction stalls for one cycle.) -Normally, you won’t pay close attention to which of the eight dword -banks your paired memory accesses fall in—that’s just too much work—but +Normally, you won't pay close attention to which of the eight dword +banks your paired memory accesses fall in—that's just too much work—but you might want to watch out for simultaneously read addresses that have the same values for address ![](images/20-05.jpg)\ - **Figure 20.5**  *The Pentium’s eight bank data cache.* + **Figure 20.5**  *The Pentium's eight bank data cache.* bits 2, 3, and 4 (fall in the same bank) in tight loops, and you should also avoid sequences like @@ -89,15 +89,15 @@ instructions; by contrast, the obvious way of loading BX mov bx,[esi] -takes 1.5 to two cycles because the size prefix can’t pair, as described +takes 1.5 to two cycles because the size prefix can't pair, as described below. This is yet another example of how different Pentium optimization -can be from everything we’ve learned about its predecessors. +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 -memory operand. (I’ll call these *complex memory instructions*. They’re +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 +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 instruction, the pipe with the faster instruction will sit idle for part @@ -105,7 +105,7 @@ of the time, wasting cycles. You might think that if both pipes execute complex instructions of the same length, then neither would lie idle, but that turns out to not always be the case. Two two-cycle instructions (instructions with register destination operands) can indeed pair and -execute in two cycles, so it’s okay to pair two instructions such as +execute in two cycles, so it's okay to pair two instructions such as these: add esi,[SourceSkip] ;U-pipe cycles 1 and 2 diff --git a/20-04.md b/20-04.md index 01fcf88..ccf370d 100644 --- a/20-04.md +++ b/20-04.md @@ -7,8 +7,8 @@ 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 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 +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 instructions take 5 cycles in all to execute, and 4 cycles of idle @@ -27,12 +27,12 @@ interleave them, as shown in Figure 20.7, which accomplishes the same task in 3 cycles, with no idle cycles whatsoever. Figure 20.7 is a good example of what optimized Pentium code generally looks like: mostly one-cycle instructions, mixed together so that at least two operations -are in progress at once. It’s not the easiest code to read or write, but -it’s the only way to get both pipes running at capacity. +are in progress at once. It's not the easiest code to read or write, but +it's the only way to get both pipes running at capacity. ### Superscalar Notes {#Heading6} -You may well ask why it’s necessary to interleave operations, as is done +You may well ask why it's necessary to interleave operations, as is done in Figure 20.7. It seems simpler just to turn and [ebx],al @@ -45,26 +45,26 @@ into 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, -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 +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 -instructions, as written, can’t possibly pair, so the sequence takes the +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 +[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. -What we’ve just seen is the read-after-write form of the superscalar -hazard known as *register contention*. I’ll return to the subject of +What we've just seen is the read-after-write form of the superscalar +hazard known as *register contention*. I'll return to the subject of register contention in the next chapter; in the remainder of this -chapter I’d like to cover a few short items about superscalar execution. +chapter I'd like to cover a few short items about superscalar execution. #### Register Starvation {#Heading7} The above examples should make it pretty clear that effective -superscalar programming puts a lot of strain on the Pentium’s relatively +superscalar programming puts a lot of strain on the Pentium's relatively small register set. There are only seven general-purpose registers (I strongly suggest using EBP in critical loops), and it does not help to have to sacrifice one of those registers for temporary storage on each @@ -73,15 +73,15 @@ those handy CISC memory instructions to do all that stuff without using any extra registers. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *More problematic still is that for maximum pairing, you’ll typically have two operations proceeding at once, one in each pipe, and trying to keep two operations in registers at once is difficult indeed. There’s not much to be done about this, other than clever and Spartan register usage, but be aware that it’s a major element of Pentium performance programming.* + ![](images/i.jpg) *More problematic still is that for maximum pairing, you'll typically have two operations proceeding at once, one in each pipe, and trying to keep two operations in registers at once is difficult indeed. There's not much to be done about this, other than clever and Spartan register usage, but be aware that it's a major element of Pentium performance programming.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Also be aware that prefixes of every sort, with the sole exception of the 0FH prefix on non-short conditional jumps, always execute in the -U-pipe, and that Intel’s documentation indicates that no pairing can -happen while a prefix byte executes. (As I’ll discuss in the next -chapter, my experiments indicate that this rule doesn’t always apply to -multiple-cycle instructions, but you still won’t go far wrong by +U-pipe, and that Intel's documentation indicates that no pairing can +happen while a prefix byte executes. (As I'll discuss in the next +chapter, my experiments indicate that this rule doesn't always apply to +multiple-cycle instructions, but you still won't go far wrong by assuming that the above rule is correct and trying to eliminate prefix bytes.) A prefix byte takes one cycle to execute; after that cycle, the actual prefixed instruction itself will go through the U-pipe, and if it @@ -99,16 +99,16 @@ not only pairable but also exactly 1 byte long, a category that includes 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 +occur only when the cache working set for the code you're timing is on the order of 8K or more—an awful lot of code to try to optimize. It should be excruciatingly clear by this point that you *must* time -your Pentium-optimized code if you’re to have any hope of knowing if +your Pentium-optimized code if you're to have any hope of knowing if your optimizations are working as well as you think they are; there are just too many details involved for you to be sure your optimizations are working properly without checking. My most basic optimization rule has always been to grab the Zen timer and *measure actual performance*—and -nowhere is this more true than on the Pentium. Don’t believe it until +nowhere is this more true than on the Pentium. Don't believe it until you measure it! ![](images/20-08.jpg)\ diff --git a/21-01.md b/21-01.md index ce87167..5cd6594 100644 --- a/21-01.md +++ b/21-01.md @@ -3,45 +3,45 @@ ------------------------ --------------------------------- -------------------- Chapter 21\ - Unleashing the Pentium’s V-Pipe {#Heading1} + Unleashing the Pentium's V-Pipe {#Heading1} -------------------------------- ### Focusing on Keeping Both Pentium Pipes Full {#Heading2} The other day, my daughter suggested that we each draw the prettiest -picture we could, then see whose was prettier. I won’t comment on who +picture we could, then see whose was prettier. I won't comment on who won, except to note that apparently a bolt of lightning zipping toward a moose with antlers that bear an unfortunate resemblance to a propeller -beanie isn’t going to win me any scholarships to art school, if you +beanie isn't going to win me any scholarships to art school, if you catch my drift. Anyway, my drawing happened to feature the word -“chartreuse” (because it rhymed with “moose” and “Zeus”—hence the +"chartreuse" (because it rhymed with "moose" and "Zeus"—hence the lightning; more than that I am not at liberty to divulge), and she wanted to know if the moose was actually chartreuse. I had to admit that -I didn’t know, so we went to the dictionary, whereupon we learned that +I didn't know, so we went to the dictionary, whereupon we learned that chartreuse is a pale apple-green color. Then she brought up the Windows Control Panel, pointed to the selection of predefined colors, and asked, -“Which of those is chartreuse?”—and I realized that I *still* didn’t +"Which of those is chartreuse?"—and I realized that I *still* didn't know. Some things can be described perfectly with words, but others just have to be experienced. Color is one such category, and Pentium optimization -is another. I’ve spent the last two chapters detailing the rules for -Pentium optimization, and I’ll spend half of this one doing so, as well. -That’s good; without understanding the fundamentals, we have no chance -of optimizing well. It’s not enough, though. We also need to look at a -real-world example of Pentium optimization in action, and we’ll do that +is another. I've spent the last two chapters detailing the rules for +Pentium optimization, and I'll spend half of this one doing so, as well. +That's good; without understanding the fundamentals, we have no chance +of optimizing well. It's not enough, though. We also need to look at a +real-world example of Pentium optimization in action, and we'll do that later in this chapter; after which, you should go out and do some Pentium optimization on your own. Optimization is one of those things that you can learn a lot about from reading, but ultimately it has to sink into your pores as you do it—especially Pentium optimization because the Pentium is perhaps the most complex (and rewarding) chip to -optimize for that I’ve ever seen. +optimize for that I've ever seen. In the last chapter, we explored the dual-execution-pipe nature of the Pentium, and learned which instructions could pair (execute -simultaneously) in which pipes. Now we’re ready to look at AGIs and +simultaneously) in which pipes. Now we're ready to look at AGIs and register contention—two hazards that can prevent otherwise properly -written code from taking full advantage of the Pentium’s two pipes, and +written code from taking full advantage of the Pentium's two pipes, and can thereby keep your code from pushing the Pentium to maximum performance. @@ -50,9 +50,9 @@ performance. The Pentium is advertised as having a five-stage pipeline for each of its execution units. All this means is that at any given time, up to five instructions are in various stages of execution in each pipe; this -overlapping of execution is done for speed, so each instruction doesn’t +overlapping of execution is done for speed, so each instruction doesn't have to wait until the previous one has finished. The only way that the -Pentium’s pipelining directly affects the way you program is in the +Pentium's pipelining directly affects the way you program is in the areas of AGIs and register dependencies. AGIs are *Address Generation Interlocks*, a fancy way of saying that if @@ -75,25 +75,25 @@ a cycle, you cannot use that register to address memory during either that cycle or the next cycle. If you try to do this, the Pentium will simply stall the instruction that tries to use that register to address memory until two cycles after the register was modified. This was true -on the 486 as well, but the Pentium’s new twist is that since more than +on the 486 as well, but the Pentium's new twist is that since more than one instruction can execute in a single cycle, an AGI can stall an -instruction that’s as many as three instructions away from the changing +instruction that's as many as three instructions away from the changing of the addressing register, as shown in Figure 21.1, and an AGI can also cause a stall that costs as many as three instructions, as shown in Figure 21.2. This means that AGIs are both much easier to cause and potentially more expensive than on the 486, and you must keep a sharp -eye out for them. It also means that it’s often worth calculating a +eye out for them. It also means that it's often worth calculating a memory pointer several instructions ahead of its actual use. Unfortunately, this tends to extend the lifetimes of pointer registers -to span a greater number of instructions, making the Pentium’s +to span a greater number of instructions, making the Pentium's relatively small register set seem even smaller. ![](images/21-01.jpg)\ **Figure 21.1**  *An AGI can stall up to three instructions later.* -As an example of a sort of AGI that’s new to the Pentium, consider the +As an example of a sort of AGI that's new to the Pentium, consider the following test for a NULL pointer, followed by the use of the pointer if -it’s not NULL: +it's not NULL: push ebx ;U-pipe cycle 1 mov ebx,[Ptr] ;V-pipe cycle 1 @@ -107,11 +107,11 @@ it’s not NULL: 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 +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 -Pentium doesn’t know that, so it has to insert the AGI. +use **TEST EBX,EBX** instead of **AND; TEST** can't modify EBX, so no +AGI occurs. Sure, **AND EBX,EBX** doesn't modify EBX either, but the +Pentium doesn't know that, so it has to insert the AGI. ![](images/21-02.jpg)\ **Figure 21.2**  *An AGI can cost as many as 3 cycles.* diff --git a/21-02.md b/21-02.md index e323c71..16ba385 100644 --- a/21-02.md +++ b/21-02.md @@ -4,7 +4,7 @@ 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**, -are special-cased so you don’t have to worry about AGIs. However, if you +are special-cased so you don't have to worry about AGIs. However, if you explicitly modify ESP with this instruction sub esp,100h @@ -28,7 +28,7 @@ addressing displacement, such as 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 sub eax,eax ;U-pipe 1 @@ -45,26 +45,26 @@ faster, and six bytes smaller than this sequence: 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 +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, -this is yet another reason why you should always measure your code’s +this is yet another reason why you should always measure your code's actual performance. ### Register Contention {#Heading4} Finally, we come to the last major component of superscalar optimization: register contention. The basic premise here is simple: You -can’t use the same register in two inherently sequential ways in a -single cycle. For example, you can’t execute +can't use the same register in two inherently sequential ways in a +single cycle. For example, you can't execute inc eax ;U-pipe cycle 1 ;V-pipe idle cycle 1 ; due to dependency 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, +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 @@ -131,16 +131,16 @@ pairable U-pipe instruction, as illustrated by this sequence: dec ecx ;U-pipe cycle 2 jnz LoopTop ;V-pipe cycle 2 -Branches can’t pair in the U-pipe; a branch that executes in the U-pipe +Branches can't pair in the U-pipe; a branch that executes in the U-pipe runs alone, with the V-pipe idle. If a call or jump is correctly -predicted by the Pentium’s branch prediction circuitry (as discussed in +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** -can’t pair. +can't pair. -### Who’s in First? {#Heading6} +### Who's in First? {#Heading6} One of the trickiest things about superscalar optimization is that a given instruction stream can execute at a different speed depending on @@ -158,7 +158,7 @@ even though we only added 25 percent more cycles: dec ecx ;V-pipe cycle 2 jnz LoopTop ;U-pipe cycle 3 ;V-pipe idle cycle 3 - ; because JNZ can’t + ; because JNZ can't ; pair in the U-pipe ------------------------ --------------------------------- -------------------- diff --git a/21-03.md b/21-03.md index 1eaf70a..0ad094c 100644 --- a/21-03.md +++ b/21-03.md @@ -2,14 +2,14 @@ [Previous](21-02.html) [Table of Contents](index.html) [Next](21-04.html) ------------------------ --------------------------------- -------------------- -It’s actually not hard to figure out which instructions go through which -pipes; just back up until you find an instruction that can’t pair or can +It's actually not hard to figure out which instructions go through which +pipes; just back up until you find an instruction that can't pair or can only go through the U-pipe, and work forward from there, given the knowledge that that instruction executes in the U-pipe. The easiest thing to look for is branches. All branch target instructions execute in 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 +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 instructions not listed in Table 20.1 in the last chapter are likewise @@ -17,7 +17,7 @@ U-pipe markers. ### Pentium Optimization in Action {#Heading7} -Now, let’s take a look at one of the simplest, tightest pieces of code +Now, let's take a look at one of the simplest, tightest pieces of code imaginable, and see what our new Pentium perspective reveals. Listing 21.1 shows a loop implementing the TCP/IP checksum, a 16-bit checksum that wraps carries around to the low bit so that the result is @@ -26,8 +26,8 @@ data regardless of the endian characteristics of the machines on which those blocks are generated and received. (Thanks to fellow performance enthusiast Terje Mathisen for suggesting this checksum as fertile ground for Pentium optimization, in the ibm.pc/fast.code forum on Bix.) The -loop in Listing 21.1 consists of exactly five instructions; it’s hard to -imagine that there’s a lot of performance to be wrung from this snippet, +loop in Listing 21.1 consists of exactly five instructions; it's hard to +imagine that there's a lot of performance to be wrung from this snippet, right? **LISTING 21.1 L21-1.ASM** @@ -63,8 +63,8 @@ right? 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 +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 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]**. @@ -78,7 +78,7 @@ 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 -doesn’t cost anything, certainly not the one full cycle in each pipe +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 diff --git a/21-04.md b/21-04.md index cea8bcb..3c2ff4c 100644 --- a/21-04.md +++ b/21-04.md @@ -4,14 +4,14 @@ 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? +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 +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, +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 +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 @@ -28,7 +28,7 @@ same cache data bank, as discussed in the last chapter). sub eax,eax ;initialize the checksum mov dx,[esi] ;first word to checksum - dec ecx ;we’ll do 1 checksum outside the loop + dec ecx ;we'll do 1 checksum outside the loop jz short ckloopend ;only 1 checksum to do add esi,2 ;point to the next word to checksum @@ -50,7 +50,7 @@ Listing 21.3 is a more sophisticated attempt to speed up the checksum calculation. Here we see a hallmark of Pentium optimization: two operations (the checksumming of the current and next pair of words) interleaved together to allow both pipes to run at near maximum -capacity. Another hallmark that’s apparent in Listing 21.3 is that +capacity. Another hallmark that's apparent in Listing 21.3 is that Pentium-optimized code tends to use more registers and require more instructions than 486-optimized code. Again, note the careful mixing of byte-sized reads to avoid AGIs, register contention, and cache bank @@ -69,7 +69,7 @@ placement of **ADD ESI,4** to avoid an AGI. sub eax,eax ;initialize the checksum sub edx,edx ;prepare for later ORing - shr ecx,1 ;we’ll do two words per loop + shr ecx,1 ;we'll do two words per loop jnc short ckloopsetup ;even number of words mov ax,[esi] ;do the odd word jz short ckloopdone ;no more words to checksum @@ -109,14 +109,14 @@ placement of **ADD ESI,4** to avoid an AGI. The checksum loop in Listing 21.3 takes longer than the loop in Listing 21.2, at 6 cycles versus 4 cycles for Listing 21.2—but Listing 21.3 does -two checksum operations in those 6 cycles, so we’ve cut the time per +two checksum operations in those 6 cycles, so we've cut the time per checksum addition from 4 to 3 cycles. You might think that this small an -improvement doesn’t justify the additional complexity of Listing 21.3, +improvement doesn't justify the additional complexity of Listing 21.3, but it is a one-third speedup, well worth it if this is a critical -loop—and, in general, if it isn’t critical, there’s no point in -hand-tuning it. That’s why I haven’t bothered to try to optimize the -non-inner-loop code in Listing 21.3; it’s only executed once per -checksum, so it’s unlikely that a cycle or two saved there would make +loop—and, in general, if it isn't critical, there's no point in +hand-tuning it. That's why I haven't bothered to try to optimize the +non-inner-loop code in Listing 21.3; it's only executed once per +checksum, so it's unlikely that a cycle or two saved there would make any real-world difference. Listing 21.3 could be made a bit faster yet with some loop unrolling, @@ -127,12 +127,12 @@ time to eliminate both the word prefix of Listing 21.1 and the multiple byte-sized accesses of Listing 21.3. An obvious drawback to this is the considerable complexity needed to ensure that the dword accesses are dword-aligned (remember that unaligned dword accesses cost three cycles -each), and to handle buffer lengths that aren’t dword multiples. I’ve +each), and to handle buffer lengths that aren't dword multiples. I've handled these problems by requiring that the buffer be dword-aligned and a dword multiple in length, which is of course not always the case in the real world. However, the point of these listings is to illustrate Pentium optimization—dword issues, being non-inner-loop stuff, are -solvable details that aren’t germane to the main focus. In any case, the +solvable details that aren't germane to the main focus. In any case, the complexity and assumptions are well justified by the performance of this code: three cycles per loop, or 1.5 cycles per checksummed word, more than three times the speed of the original code. Again, note that the diff --git a/21-05.md b/21-05.md index e5829f7..f7b2866 100644 --- a/21-05.md +++ b/21-05.md @@ -13,10 +13,10 @@ ; in length, and length > 0. sub eax,eax ;initialize the checksum - shr ecx,1 ;we’ll do two words per loop + shr ecx,1 ;we'll do two words per loop mov edx,[esi] ;preload the first dword add esi,4 ;point to the next dword - dec ecx ;we’ll do 1 checksum outside the loop + dec ecx ;we'll do 1 checksum outside the loop jz short ckloopend ;only 1 checksum to do ckloop: @@ -37,7 +37,7 @@ Listing 21.5 improves upon Listing 21.4 by processing 2 dwords per loop, thereby bringing the time per checksummed word down to exactly 1 cycle. -Listing 21.5 basically does nothing but unroll Listing 21.4’s loop one +Listing 21.5 basically does nothing but unroll Listing 21.4's loop one time, demonstrating that the venerable optimization technique of loop unrolling still has some life left in it on the Pentium. The cost for this is, as usual, increased code size and complexity, and the use of @@ -54,7 +54,7 @@ more registers. ; in length, and length > 0. sub eax,eax ;initialize the checksum - shr ecx,2 ;we’ll do two dwords per loop + shr ecx,2 ;we'll do two dwords per loop jnc short noodddword ;is there an odd dword in buffer? mov eax,[esi] ;checksum the odd dword jz short ckloopdone ;no, done @@ -62,7 +62,7 @@ more registers. noodddword: mov edx,[esi] ;preload the first dword mov ebx,[esi+4] ;preload the second dword - dec ecx ;we’ll do 1 checksum outside the loop + dec ecx ;we'll do 1 checksum outside the loop jz short ckloopend ;only 1 checksum to do add esi,8 ;point to the next dword @@ -87,30 +87,30 @@ more registers. adc eax,0 Listing 21.5 is undeniably intricate code, and not the sort of thing one -would choose to write as a matter of course. On the other hand, it’s +would choose to write as a matter of course. On the other hand, it's five times as fast as the tight, seemingly-speedy loop in Listing 21.1 (and six times as fast as Listing 21.1 would have been if the prefix -byte had behaved as expected). That’s an awful lot of speed to wring out +byte had behaved as expected). That's an awful lot of speed to wring out of a five-instruction loop, and the TCP/IP checksum is, in fact, used by network software, an area in which a five-times speedup might make a 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 +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 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 +Hand-optimization for the Pentium isn't simple, and requires careful measurement to check the efficacy of your optimizations, so reserve it for when you really, really need it—but when you need it, you need it *bad*. #### A Quick Note on the 386 and 486 {#Heading8} -I’ve mentioned that Pentium-optimized code does fine on the 486, but not +I've mentioned that Pentium-optimized code does fine on the 486, but not always so well on the 386. On a 486, Listing 21.1 runs at 9 cycles per checksummed word, and Listing 21.5 runs at 2.5 cycles per checksummed word, a healthy 3.6-times speedup. On a 386, Listing 21.1 runs at 22 diff --git a/22-01.md b/22-01.md index 46093db..2310406 100644 --- a/22-01.md +++ b/22-01.md @@ -6,7 +6,7 @@ Chapter 22\ Zenning and the Flexible Mind {#Heading1} ------------------------------ -### Taking a Spin through What You’ve Learned {#Heading2} +### Taking a Spin through What You've Learned {#Heading2} And so we come to the end of our journey; for now, at least. What follows is a modest bit of optimization, one which originally served to @@ -16,40 +16,40 @@ learn how to apply the flexible mind—unconventional, broadly integrative thinking—to approaching high-level optimization at the algorithmic and program design levels. You, of course, need no such reassurance, having just spent 21 chapters learning about the flexible mind in many guises, -but I think you’ll find this example instructive nonetheless. Try to +but I think you'll find this example instructive nonetheless. Try to stay ahead as the level of optimization rises from instruction elimination to instruction substitution to more creative solutions that -involve broader understanding and redesign. We’ll start out by +involve broader understanding and redesign. We'll start out by compacting individual instructions and bits of code, but by the end -we’ll come up with a solution that involves the very structure of the +we'll come up with a solution that involves the very structure of the subroutine, with each instruction carefully integrated into a remarkably -compact whole. It’s a neat example of how optimization operates at many -levels, some much less determininstic than others—and besides, it’s just +compact whole. It's a neat example of how optimization operates at many +levels, some much less determininstic than others—and besides, it's just plain fun. Enjoy! ### Zenning {#Heading3} -In Jeff Duntemann’s excellent book *Borland Pascal From Square One* -(Random House, 1993), there’s a small assembly subroutine that’s +In Jeff Duntemann's excellent book *Borland Pascal From Square One* +(Random House, 1993), there's a small assembly subroutine that's designed to be called from a Turbo Pascal program in order to fill the screen or a systemscreen buffer with a specified character/attribute pair in text mode. This subroutine involves only 21 instructions and works perfectly well; however, with what we know, we can compact the subroutine tremendously and speed it up a bit as well. To coin a verb, -we can “Zen” this already-tight assembly code to an astonishing degree. -In the process, I hope you’ll get a feel for how advanced your assembly +we can "Zen" this already-tight assembly code to an astonishing degree. +In the process, I hope you'll get a feel for how advanced your assembly skills have become. -Jeff’s original code follows as Listing 22.1 (with some text converted +Jeff's original code follows as Listing 22.1 (with some text converted to lowercase in order to match the style of this book), but the comments are mine. **LISTING 22.1 L22-1.ASM** - OnStack struc ;data that’s stored on the stack after PUSH BP - OldBP dw ? ;caller’s BP + OnStack struc ;data that's stored on the stack after PUSH BP + OldBP dw ? ;caller's BP RetAddr dw ? ;return address Filler dw ? ;character to fill the buffer with Attrib dw ? ;attribute to fill the buffer with @@ -60,7 +60,7 @@ are mine. OnStack ends ; ClearS proc near - push bp ;save caller’s BP + push bp ;save caller's BP mov bp,sp ;point to stack frame cmp word ptr [bp].BufSeg,0 ;skip the fill if a null jne Start ; pointer is passed @@ -79,32 +79,32 @@ are mine. mov cx,[bp].BufSize ;load CX with buffer size rep stosw ;fill the buffer Bye:mov sp,bp ;restore original stack pointer - pop bp ; and caller’s BP + pop bp ; and caller's BP ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack 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 -that one repeated string instruction, and there’s no way to improve on +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 +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. +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 +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 -on to some serious optimizing. We’ll look first for cases where we know +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 +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 +For example, there's no need to load any register, whether segment or general, through BX; we can eliminate two instructions by loading ES and DI directly as shown in Listing 22.2. diff --git a/22-02.md b/22-02.md index 327397c..04c83f1 100644 --- a/22-02.md +++ b/22-02.md @@ -5,7 +5,7 @@ **LISTING 22.2 L22-2.ASM** ClearS proc near - push bp ;save caller’s BP + push bp ;save caller's BP mov bp,sp ;point to stack frame cmp word ptr [bp].BufSeg,0 ;skip the fill if a null jne Start ; pointer is passed @@ -22,15 +22,15 @@ mov cx,[bp].BufSize ;load CX with buffer size rep stosw ;fill the buffer Bye: - pop bp ;restore caller’s BP + pop bp ;restore caller's BP ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack ClearS endp -(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 +(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**.) -Okay, loading ES and DI directly saves another four bytes. We’ve +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 next? @@ -40,7 +40,7 @@ loading ES and DI as shown in Listing 22.3. **LISTING 22.3 L22-3.ASM** ClearS proc near - push bp ;save caller’s BP + push bp ;save caller's BP mov bp,sp ;point to stack frame cmp word ptr [bp].BufSeg,0 ;skip the fill if a null jne Start ; pointer is passed @@ -57,11 +57,11 @@ loading ES and DI as shown in Listing 22.3. mov cx,[bp].BufSize ;load CX with buffer size rep stosw ;fill the buffer Bye: - pop bp ;restore caller’s BP + pop bp ;restore caller's BP ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack ClearS endp -That’s good for another three bytes. We’re down to 43 bytes, and +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 @@ -71,7 +71,7 @@ values as shown in Listing 22.4. **LISTING 22.4 L22-4.ASM** ClearS proc near - push bp ;save caller’s BP + push bp ;save caller's BP mov bp,sp ;point to stack frame cmp word ptr [bp].BufSeg,0 ;skip the fill if a null jne Start ; pointer is passed @@ -88,13 +88,13 @@ values as shown in Listing 22.4. mov cx,[bp].BufSize ;load CX with buffer size rep stosw ;fill the buffer Bye: - pop bp ;restore caller’s BP + pop bp ;restore caller's BP ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack ClearS endp -Now we’re down to 40 bytes—more than 20 percent smaller than the -original code. That’s pretty much it for simple instruction -optimizations. Now let’s look for instruction optimizations. +Now we're down to 40 bytes—more than 20 percent smaller than the +original code. That's pretty much it for simple instruction +optimizations. Now let's look for instruction optimizations. It seems strange to load a word value into AX and then throw away AL. Likewise, it seems strange to load a word value into BX and then throw @@ -102,10 +102,10 @@ away BH. However, those steps are necessary because the two modified word values are ORed into a single character/attribute word value that is then used to fill the target buffer. -Let’s step back and see what this code really *does*, though. All it +Let's step back and see what this code really *does*, though. All it does in the end is load one byte addressed relative to BP into AH and another byte addressed relative to BP into AL. Heck, we can just do that -directly! Presto—we’ve saved another 6 bytes, and turned two word-sized +directly! Presto—we've saved another 6 bytes, and turned two word-sized memory accesses into byte-sized memory accesses as well. Listing 22.5 shows the new code. diff --git a/22-03.md b/22-03.md index 9b6e8c2..b8321ee 100644 --- a/22-03.md +++ b/22-03.md @@ -5,7 +5,7 @@ **LISTING 22.5 L22-5.ASM** ClearS proc near - push bp ;save caller’s BP + push bp ;save caller's BP mov bp,sp ;point to stack frame cmp word ptr [bp].BufSeg,0 ;skip the fill if a null jne Start ; pointer is passed @@ -18,77 +18,77 @@ mov cx,[bp].BufSize ;load CX with buffer size rep stosw ;fill the buffer Bye: - pop bp ;restore caller’s BP + pop bp ;restore caller's BP ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack ClearS endp (We could get rid of yet another instruction by having the calling code pack both the attribute and the fill value into the same word, but -that’s not part of the specification for this particular routine.) +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 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 +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 it later without having to reload it from memory. This technique is shown in Listing 22.6. **LISTING 22.6 L22-6.ASM** ClearS proc near - push bp ;save caller’s BP + push bp ;save caller's BP mov bp,sp ;point to stack frame les di,dword ptr [bp].BufOfs ;load ES:DI with target buffer;segment:offset mov ax,es ;put segment where we can test it or ax,di ;is it a null pointer? - je Bye ;yes, so we’re done + je Bye ;yes, so we're done Start: cld ;make STOSW count up mov ah,byte ptr [bp].Attrib[1];load AH with attribute mov al,byte ptr [bp].Filler ;load AL with fill char mov cx,[bp].BufSize ;load CX with buffer size rep stosw ;fill the buffer Bye: - pop bp ;restore caller’s BP + pop bp ;restore caller's BP ret EndMrk-RetAddr-2 ;return, clearing the parms from the stack ClearS endp -Well. Now we’re down to 28 bytes, having reduced the size of this +Well. Now we're down to 28 bytes, having reduced the size of this subroutine by nearly 50 percent. Only 13 instructions remain. 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. +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? 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 +so that the parameters can be accessed. Mind you, there's nothing wrong 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 -to improve the code if we don’t bend the rules a bit. +a high-level language would. That means that we aren't going to be able +to improve the code if we don't bend the rules a bit. -Let’s think...the parameters are sitting on the stack, and most of our +Let's think...the parameters are sitting on the stack, and most of our instruction bytes are being used to read bytes off the stack with BP-based addressing...we need a more efficient way to address the 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 +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**. -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 +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 removing them from the stack, thereby neatly avoiding the need to -discard them when it’s time to return. +discard them when it's time to return. With that problem dealt with, Listing 22.7 shows the Zenned version of **ClearS**. @@ -105,15 +105,15 @@ With that problem dealt with, Listing 22.7 shows the Zenned version of pop es ;get the segment of the buffer origin mov bx,es ;put the segment where we can test it or bx,di ;null pointer? - je Bye ;yes, so we’re done + je Bye ;yes, so we're done cld ;make STOSW count up rep stosw ;do the string store Bye: jmp dx ;return to the calling code ClearS endp -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 +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 run a bit faster too, given that there are far fewer instruction bytes diff --git a/23-01.md b/23-01.md index c57cfa4..49b72fc 100644 --- a/23-01.md +++ b/23-01.md @@ -21,10 +21,10 @@ outperform the standard VGA, and indeed, it is becoming hard to find a plain vanilla VGA anymore—but there is no standard for accelerators, and every accelerator contains a true-blue VGA at its core. -What that means is that if you write your programs for the VGA, you’ll +What that means is that if you write your programs for the VGA, you'll have the largest possible market for your software. In order for graphics-based software to succeed, however, it must perform well. -Wringing the best performance from the VGA is no simple task, and it’s +Wringing the best performance from the VGA is no simple task, and it's *impossible* unless you really understand how the VGA works—unless you have the internals down cold. This book is about PC graphics at many levels, but high performance is the foundation for all that is to come, @@ -32,8 +32,8 @@ so it is with the inner workings of the VGA that we will begin our exploration of PC graphics. The first eight chapters of Part II is a guided tour of the heart of the -VGA; after you’ve absorbed what we’ll cover in this and the next seven -chapters, you’ll have the foundation for understanding just about +VGA; after you've absorbed what we'll cover in this and the next seven +chapters, you'll have the foundation for understanding just about everything the VGA can do, including the fabled Mode X and more. As you read through these first chapters, please keep in mind that the *really* exciting stuff—animation, 3-D, blurry-fast lines and circles and @@ -41,23 +41,23 @@ polygons—has to wait until we have the fundamentals out of the way. So hold on and follow along, and before you know it the fireworks will be well underway. -We’ll start our exploration with a quick overview of the VGA, and then -we’ll dive right in and get a taste of what the VGA can do. +We'll start our exploration with a quick overview of the VGA, and then +we'll dive right in and get a taste of what the VGA can do. ### The VGA {#Heading3} The VGA is the baseline adapter for modern IBM PC compatibles, present in virtually every PC sold today or in the last several years. (Note that the VGA is often nothing more than a chip on a motherboard, with -some memory, a DAC, and maybe a couple of glue chips; nonetheless, I’ll +some memory, a DAC, and maybe a couple of glue chips; nonetheless, I'll refer to it as an adapter from now on for simplicity.) It guarantees that every PC is capable of documented resolutions up to 640x480 (with 16 possible colors per pixel) and 320x200 (with 256 colors per pixel), as well as undocumented—but nonetheless thoroughly standard—resolutions -up to 360x480 in 256-color mode, as we’ll see in Chapters 31-34 and +up to 360x480 in 256-color mode, as we'll see in Chapters 31-34 and 47-49. In order for a video adapter to claim VGA compatibility, it must support all the features and code discussed in this book (with a very -few minor exceptions that I’ll note)—and my experience is that just +few minor exceptions that I'll note)—and my experience is that just about 100 percent of the video hardware currently shipping or shipped since 1990 is in fact VGA compatible. Therefore, VGA code will run on nearly all of the 50,000,000 or so PC compatibles out there, with the @@ -65,61 +65,61 @@ exceptions being almost entirely obsolete machines from the 1980s. This makes good VGA code and VGA programming expertise valuable commodities indeed. -Right off the bat, I’d like to make one thing perfectly clear: The VGA +Right off the bat, I'd like to make one thing perfectly clear: The VGA is hard—sometimes *very* hard—to program for good performance. Hard, but -not impossible—and that’s why I like this odd board. It’s a throwback to +not impossible—and that's why I like this odd board. It's a throwback to an earlier generation of micros, when inventive coding and a solid understanding of the hardware were the best tools for improving performance. Increasingly, faster processors and powerful coprocessors are seen as the solution to the sluggish software produced by high-level -languages and layers of interface and driver code, and that’s surely a +languages and layers of interface and driver code, and that's surely a valid approach. However, there are tens of millions of VGAs installed right now, in machines ranging from 6-MHz 286s to 90-MHz Pentiums. -What’s more, because the VGAs are generally 8- or at best 16-bit +What's more, because the VGAs are generally 8- or at best 16-bit devices, and because of display memory wait states, a faster processor -isn’t as much of a help as you’d expect. The upshot is that only a +isn't as much of a help as you'd expect. The upshot is that only a seasoned performance programmer who understands the VGA through and through can drive the board to its fullest potential. -Throughout this book, I’ll explore the VGA by selecting a specific +Throughout this book, I'll explore the VGA by selecting a specific algorithm or feature and implementing code to support it on the VGA, examining aspects of the VGA architecture as they become relevant. -You’ll get to see VGA features in context, where they are more -comprehensible than in IBM’s somewhat arcane documentation, and you’ll +You'll get to see VGA features in context, where they are more +comprehensible than in IBM's somewhat arcane documentation, and you'll get working code to use or to modify to meet your needs. -The prime directive of VGA programming is that there’s rarely just one +The prime directive of VGA programming is that there's rarely just one way to program the VGA for a given purpose. Once you understand the -tools the VGA provides, you’ll be able to combine them to generate the +tools the VGA provides, you'll be able to combine them to generate the particular synergy your application needs. My VGA routines are not -intended to be taken as gospel, or to show “best” implementations, but +intended to be taken as gospel, or to show "best" implementations, but rather to start you down the road to understanding the VGA. -Let’s begin. +Let's begin. ### An Introduction to VGA Programming {#Heading4} -Most discussions of the VGA start out with a traditional “Here’s a block -diagram of the VGA” approach, with lists of registers and statistics. -I’ll get to that eventually, but you can find it in IBM’s VGA -documentation and several other books. Besides, it’s numbing to read +Most discussions of the VGA start out with a traditional "Here's a block +diagram of the VGA" approach, with lists of registers and statistics. +I'll get to that eventually, but you can find it in IBM's VGA +documentation and several other books. Besides, it's numbing to read specifications and explanations, and the VGA is an exciting adapter, the kind that makes you want to get your hands dirty probing under the hood, -to write some nifty code just to see what the board can do. What’s more, -the best way to understand the VGA is to see it work, so let’s jump -right into a sample of the VGA in action, getting a feel for the VGA’s +to write some nifty code just to see what the board can do. What's more, +the best way to understand the VGA is to see it work, so let's jump +right into a sample of the VGA in action, getting a feel for the VGA's architecture in the process. Listing 23.1 is a sample VGA program that pans around an animated -16-color medium-resolution (640x350) playfield. There’s a lot packed -into this code; I’m going to focus on the VGA-specific aspects so we -don’t get sidetracked. I’m not going to explain how the ball is -animated, for example; we’ll get to animation starting in Chapter 42. +16-color medium-resolution (640x350) playfield. There's a lot packed +into this code; I'm going to focus on the VGA-specific aspects so we +don't get sidetracked. I'm not going to explain how the ball is +animated, for example; we'll get to animation starting in Chapter 42. What I will do is cover each of the VGA features used in this program—the virtual screen, vertical and horizontal panning, color plane manipulation, multi-plane block copying, and page flipping—at a conceptual level, letting the code itself demonstrate the implementation -details. We’ll return to many of these concepts in more depth later in +details. We'll return to many of these concepts in more depth later in this book. ------------------------ --------------------------------- -------------------- diff --git a/23-02.md b/23-02.md index 5c5ab78..7dfe0d5 100644 --- a/23-02.md +++ b/23-02.md @@ -4,7 +4,7 @@ ### At the Core {#Heading5} -A little background is necessary before we’re ready to examine Listing +A little background is necessary before we're ready to examine Listing 23.1. The VGA is built around four functional blocks, named the CRT Controller (CRTC), the Sequence Controller (SC), the Attribute Controller (AC), and the Graphics Controller (GC). The single-chip VGA @@ -23,8 +23,8 @@ makes it necessary for you to remember which registers are in which blocks. Most VGA registers are addressed as *internally indexed* registers. The -internal address of the register is written to a given block’s Index -register, and then the data for that register is written to the block’s +internal address of the register is written to a given block's Index +register, and then the data for that register is written to the block's Data register. For example, GC register 8, the Bit Mask register, is set to 0FFH by writing 8 to port 3CEH, the GC Index register, and then writing 0FFH to port 3CFH, the GC Data register. Internal indexing makes @@ -128,7 +128,7 @@ 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 -to the AC do work in the EGA, but not in the VGA, so they shouldn’t be +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 1 register at any time to force the AC to Index mode. @@ -137,11 +137,11 @@ 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 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, 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 until it is reprogrammed. This means that in cases where you are setting the same internal register repeatedly, you can set the Index register to point to that internal register once, then write to the Data register multiple times. For example, the Bit Mask register (GC register 8) is often set repeatedly inside a loop when drawing lines. The standard code for this is:* + ![](images/i.jpg) *A speed tip: The setting of each chip's Index register remains the same until it is reprogrammed. This means that in cases where you are setting the same internal register repeatedly, you can set the Index register to point to that internal register once, then write to the Data register multiple times. For example, the Bit Mask register (GC register 8) is often set repeatedly inside a loop when drawing lines. The standard code for this is:* ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- MOV DX,03CEH ;point to GC Index register diff --git a/23-03.md b/23-03.md index a1fbdc7..01a94dc 100644 --- a/23-03.md +++ b/23-03.md @@ -4,7 +4,7 @@ #### Linear Planes and True VGA Modes {#Heading6} -The VGA’s memory is organized as four 64K planes. Each of these planes +The VGA's memory is organized as four 64K planes. Each of these planes is a linear bitmap; that is, each byte from a given plane controls eight adjacent pixels on the screen, the next byte controls the next eight pixels, and so on to the end of the scan line. The next byte then @@ -13,46 +13,46 @@ end of the screen. The VGA adds a powerful twist to linear addressing; the logical width of the screen in VGA memory need not be the same as the physical width of -the display. The programmer is free to define all or part of the VGA’s +the display. The programmer is free to define all or part of the VGA's large memory map as a logical screen of up to 4,080 pixels in width, and then use the physical screen as a window onto any part of the logical -screen. What’s more, a virtual screen can have any logical height up to +screen. What's more, a virtual screen can have any logical height up to the capacity of VGA memory. Such a virtual screen could be used to store a spreadsheet or a CAD/CAM drawing, for instance. As we will see shortly, the VGA provides excellent hardware for moving around the -virtual screen; taken together, the virtual screen and the VGA’s smooth +virtual screen; taken together, the virtual screen and the VGA's smooth panning capabilities can generate very impressive effects. All four linear planes are addressed in the same 64K memory space starting at A000:0000. Consequently, there are four bytes at any given address in VGA memory. The VGA provides special hardware to assist the CPU in manipulating all four planes, in parallel, with a single memory -access, so that the programmer doesn’t have to spend a great deal of +access, so that the programmer doesn't have to spend a great deal of time switching between planes. Astute use of this VGA hardware allows VGA software to as much as quadruple performance by processing the data for all the planes in parallel. Each memory plane provides one bit of data for each pixel. The bits for a given pixel from each of the four planes are combined into a nibble -that serves as an address into the VGA’s palette RAM, which maps the one +that serves as an address into the VGA's palette RAM, which maps the one of 16 colors selected by display memory into any one of 64 colors, as shown in Figure 23.1. All sixty-four mappings for all 16 colors are -independently programmable. (We’ll discuss the VGA’s color capabilities +independently programmable. (We'll discuss the VGA's color capabilities in detail starting in Chapter 33.) The VGA BIOS supports several graphics modes (modes 4, 5, and 6) in which VGA memory appears not to be organized as four linear planes. These modes exist for CGA compatibility only, and are not true VGA graphics modes; use them when you need CGA-type operation and ignore -them the rest of the time. The VGA’s special features are most powerful +them the rest of the time. The VGA's special features are most powerful in true VGA modes, and it is on the 16-color true-VGA modes (modes 0DH (320x200), 0EH (640x200), 10H (640x350), and 12H (640x480)) that I will concentrate in this part of the book. There is also a 256-color mode, mode 13H, that appears to be a single linear plane, but, as we will see -in Chapters 31-34 and 47-49 of this book, that’s a polite fiction—and +in Chapters 31-34 and 47-49 of this book, that's a polite fiction—and discarding that fiction gives us an opportunity to unleash the power of -the VGA’s hardware for vastly better performance. VGA text modes, which -feature soft fonts, are another matter entirely, upon which we’ll touch +the VGA's hardware for vastly better performance. VGA text modes, which +feature soft fonts, are another matter entirely, upon which we'll touch from time to time. ![](images/23-01.jpg)\ @@ -60,7 +60,7 @@ from time to time. With that background out of the way, we can get on to the sample VGA program shown in Listing 23.1. I suggest you run the program before -continuing, since the explanations will mean far more to you if you’ve +continuing, since the explanations will mean far more to you if you've seen the features in action. **LISTING 23.1 L23-1.ASM** diff --git a/23-04.md b/23-04.md index d6dcae8..c52b064 100644 --- a/23-04.md +++ b/23-04.md @@ -4,7 +4,7 @@ #### Smooth Panning {#Heading7} -The first thing you’ll notice upon running the sample program is the +The first thing you'll notice upon running the sample program is the remarkable smoothness with which the display pans from side-to-side and up-and-down. That the display can pan at all is made possible by two VGA features: 256K of display memory and the virtual screen capability. Even @@ -73,7 +73,7 @@ register, AC register 13H, working in conjunction with the start address. Up to 7 pixels worth of single pixel panning of the displayed image to the left is performed by increasing the Horizontal Pel Panning register from 0 to 7. This exhausts the range of motion possible via the -Horizontal Pel Panning register; the next pixel’s worth of smooth +Horizontal Pel Panning register; the next pixel's worth of smooth panning is accomplished by incrementing the start address by one and resetting the Horizontal Pel Panning register to 0. Smooth horizontal panning should be viewed as a series of fine adjustments in the 8-pixel @@ -82,7 +82,7 @@ range between coarse byte-sized adjustments. A horizontal panning oddity: Alone among VGA modes, text mode (in most cases) has 9 dots per character clock. Smooth panning in this mode requires cycling the Horizontal Pel Panning register through the values -8, 0, 1, 2, 3, 4, 5, 6, and 7. 8 is the “no panning” setting. +8, 0, 1, 2, 3, 4, 5, 6, and 7. 8 is the "no panning" setting. There is one annoying quirk about programming the AC. When the AC Index register is set, only the lower five bits are used as the internal diff --git a/23-05.md b/23-05.md index b171c87..dcf0f41 100644 --- a/23-05.md +++ b/23-05.md @@ -24,14 +24,14 @@ writes, so old and new images can mix on the screen, producing unwanted color effects as, say, three planes from the old image mix with one plane from the new image. The sample program solves this by ensuring that the memory written to is zeroed. A better way to set all planes at -once is provided by the set/reset capabilities of the VGA, which I’ll +once is provided by the set/reset capabilities of the VGA, which I'll cover in Chapter 25. The sample program writes the image of the colored ball to VGA memory by enabling one plane at a time and writing the image of the ball for that plane. Each image is written to the same VGA addresses; only the destination plane, selected by the Map Mask register, is different. You -might think of the ball’s image as consisting of four colored overlays, +might think of the ball's image as consisting of four colored overlays, which together make up a multicolored image. The sample program writes a blank image to VGA memory by enabling all planes and writing a block of zero bytes; the zero bytes are written to all four VGA planes @@ -48,11 +48,11 @@ 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 slow. -The solution is to take advantage of the VGA’s write mode 1, which is +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 -addressed byte from all four planes into the VGA’s four internal +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 irrelevant. @@ -61,7 +61,7 @@ The sample program uses write mode 1 to copy the images that were previously drawn to the high end of VGA memory into a desired area of display memory, all in a single block copy operation. This is an excellent way to keep the number of reads, writes, and OUTs required to -manipulate the VGA’s display memory low enough to allow real-time +manipulate the VGA's display memory low enough to allow real-time drawing. The Map Mask register can still mask out planes in write mode 1. All @@ -72,12 +72,12 @@ The animated images appear to move a bit jerkily because they are byte-aligned and so must move a minimum of 8 pixels horizontally. This is easily solved by storing rotated versions of all images in VGA memory, and then in each instance drawing the correct rotation for the -pixel alignment at which the image is to be drawn; we’ll see this +pixel alignment at which the image is to be drawn; we'll see this technique in action in Chapter 49. -Don’t worry if you’re not catching everything in this chapter on the +Don't worry if you're not catching everything in this chapter on the first pass; the VGA is a complicated beast, and learning about it is an -iterative process. We’ll be going over these features again, in +iterative process. We'll be going over these features again, in different contexts, over the course of the rest of this book. #### Page Flipping {#Heading9} @@ -92,7 +92,7 @@ display memory is displayed while another is being modified. The modifications never affect an area of memory as it is providing video data, so no undesirable side effects occur. Once the modification is complete, the modified buffer is selected for display, causing the -screen to change to the new image in a single frame’s time, typically +screen to change to the new image in a single frame's time, typically 1/60th or 1/70th of a second. The other buffer is then available for modification. @@ -103,14 +103,14 @@ are needed. The sample program uses two 672x384 virtual pages, each 32,256 bytes long, one starting at A000:0000 and the other starting at A000:7E00. Flipping between the pages is as simple as setting the start address registers to point to one display area or the other—but, as it -turns out, that’s not as simple as it sounds. +turns out, that's not as simple as it sounds. The timing of the switch between pages is critical to achieving flicker-free animation. It is essential that the program never be modifying an area of display memory as that memory is providing video data. Achieving this is surprisingly complicated on the VGA, however. -The problem is as follows. The start address is latched by the VGA’s +The problem is as follows. The start address is latched by the VGA's internal circuitry exactly once per frame, typically (but not always on all clones) at the start of the vertical sync pulse. The vertical sync status is, in fact, available as bit 3 of the Input Status 0 register, @@ -118,14 +118,14 @@ addressable at 3BAH (in monochrome modes) or 3DAH (color). Unfortunately, by the time the vertical sync status is observed by a program, the start address for the next frame has already been latched, having happened the instant the vertical sync pulse began. That means -that it’s no good to wait for vertical sync to begin, then set the new -start address; if we did that, we’d have to wait until the *next* -vertical sync pulse to start drawing, because the page wouldn’t flip +that it's no good to wait for vertical sync to begin, then set the new +start address; if we did that, we'd have to wait until the *next* +vertical sync pulse to start drawing, because the page wouldn't flip until then. Clearly, what we want is to set the new start address, then wait for the start of the vertical sync pulse, at which point we can be sure the page -has flipped. However, we can’t just set the start address and wait, +has flipped. However, we can't just set the start address and wait, because we might have the extreme misfortune to set one of the start address registers before the start of vertical sync and the other after, resulting in mismatched halves of the start address and a nasty jump of diff --git a/23-06.md b/23-06.md index 4fa2d59..38be055 100644 --- a/23-06.md +++ b/23-06.md @@ -5,23 +5,23 @@ One possible solution to this problem is to pick a second page start address that has a 0 value for the lower byte, so only the Start Address High register ever needs to be set, but in the sample program in Listing -23.1 I’ve gone for generality and always set both bytes. To avoid +23.1 I've gone for generality and always set both bytes. To avoid mismatched start address bytes, the sample program waits for pixel data to be displayed, as indicated by the Display Enable status; this tells -us we’re somewhere in the displayed portion of the frame, far enough +us we're somewhere in the displayed portion of the frame, far enough away from vertical sync so we can be sure the new start address will get used at the next vertical sync. Once the Display Enable status is observed, the program sets the new start address, waits for vertical sync to happen, sets the new pel panning state, and then continues -drawing. Don’t worry about the details right now; page flipping will +drawing. Don't worry about the details right now; page flipping will come up again, at considerably greater length, in later chapters. ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *As an interesting side note, be aware that if you run DOS software under a multitasking environment such as Windows NT, timeslicing delays can make mismatched start address bytes or mismatched start address and pel panning settings much more likely, for the graphics code can be interrupted at any time. This is also possible, although much less likely, under non-multitasking environments such as DOS, because strategically placed interrupts can cause the same sorts of problems there. For maximum safety, you should disable interrupts around the key portions of your page-flipping code, although here we run into the problem that if interrupts are disabled from the time we start looking for Display Enable until we set the Pel Panning register, they will be off for far too long, and keyboard, mouse, and network events will potentially be lost. Also, disabling interrupts won’t help in true multitasking environments, which never let a program hog the entire CPU. This is one reason that pel panning, although indubitably flashy, isn’t widely used and should be reserved for only those cases where it’s absolutely necessary.* + ![](images/i.jpg) *As an interesting side note, be aware that if you run DOS software under a multitasking environment such as Windows NT, timeslicing delays can make mismatched start address bytes or mismatched start address and pel panning settings much more likely, for the graphics code can be interrupted at any time. This is also possible, although much less likely, under non-multitasking environments such as DOS, because strategically placed interrupts can cause the same sorts of problems there. For maximum safety, you should disable interrupts around the key portions of your page-flipping code, although here we run into the problem that if interrupts are disabled from the time we start looking for Display Enable until we set the Pel Panning register, they will be off for far too long, and keyboard, mouse, and network events will potentially be lost. Also, disabling interrupts won't help in true multitasking environments, which never let a program hog the entire CPU. This is one reason that pel panning, although indubitably flashy, isn't widely used and should be reserved for only those cases where it's absolutely necessary.* ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Waiting for the sync pulse has the side effect of causing program -execution to synchronize to the VGA’s frame rate of 60 or 70 frames per +execution to synchronize to the VGA's frame rate of 60 or 70 frames per second, depending on the display mode. This synchronization has the useful consequence of causing the program to execute at the same speed on any CPU that can draw fast enough to complete the drawing in a single @@ -29,7 +29,7 @@ frame; the program just idles for the rest of each frame that it finishes before the VGA is finished displaying the previous frame. An important point illustrated by the sample program is that while the -VGA’s display memory is far larger and more versatile than is the case +VGA's display memory is far larger and more versatile than is the case with earlier adapters, it is nonetheless a limited resource and must be used judiciously. The sample program uses VGA memory to store two 672x384 virtual pages, leaving only 1024 bytes free to store images. In @@ -44,19 +44,19 @@ line for **MEDRES\_VIDEO\_MODE**. ### The Hazards of VGA Clones {#Heading10} -Earlier, I said that any VGA that doesn’t support the features and -functionality covered in this book can’t properly be called VGA +Earlier, I said that any VGA that doesn't support the features and +functionality covered in this book can't properly be called VGA compatible. I also noted that there are some exceptions, however, and -we’ve just come to the most prominent one. You see, all VGAs really -*are* compatible with the IBM VGA’s functionality when it comes to +we've just come to the most prominent one. You see, all VGAs really +*are* compatible with the IBM VGA's functionality when it comes to drawing pixels into display memory; all the write modes and read modes and set/reset capabilities and everything else involved with manipulating display memory really does work in the same way on all VGAs -and VGA clones. That compatibility isn’t as airtight when it comes to +and VGA clones. That compatibility isn't as airtight when it comes to scanning pixels out of display memory and onto the screen in certain infrequently-used ways, however. -The areas of incompatibility of which I’m aware are illustrated by the +The areas of incompatibility of which I'm aware are illustrated by the sample program, and may in fact have caused you to see some glitches when you ran Listing 23.1. The problem, which arises only on certain VGAs, is that some settings of the Row Offset register cause some pixels @@ -66,20 +66,20 @@ experience, only VRAM (Video RAM)-based VGAs exhibit this problem, no doubt due to the way that pixel data is fetched from VRAM in large blocks.) Panning and large virtual bitmaps can be made to work reliably, by careful selection of virtual bitmap sizes and start addresses, but -it’s difficult; that’s one of the reasons that most commercial software +it's difficult; that's one of the reasons that most commercial software does not use these features, although a number of games do. The upshot -is that if you’re going to use oversized virtual bitmaps and pan around +is that if you're going to use oversized virtual bitmaps and pan around them, you should take great care to test your software on a wide variety of VRAM- and DRAM-based VGAs. ### Just the Beginning {#Heading11} That pretty well covers the important points of the sample VGA program -in Listing 23.1. There are many VGA features we didn’t even touch on, +in Listing 23.1. There are many VGA features we didn't even touch on, but the object was to give you a feel for the variety of features available on the VGA, to convey the flexibility and complexity of the -VGA’s resources, and in general to give you an initial sense of what VGA -programming is like. Starting with the next chapter, we’ll begin to +VGA's resources, and in general to give you an initial sense of what VGA +programming is like. Starting with the next chapter, we'll begin to explore the VGA systematically, on a more detailed basis. ### The Macro Assembler {#Heading12} @@ -90,23 +90,23 @@ good development environment, but I believe that often the best code written in assembly. This is especially true of graphics code for the x86 family, given segments, the string instructions, and the asymmetric and limited register set, and for real-time programming of a complex -board like the VGA, there’s really no other choice for the lowest-level +board like the VGA, there's really no other choice for the lowest-level code. -Before I’m deluged with protests from C devotees, let me add that the +Before I'm deluged with protests from C devotees, let me add that the majority of my productive work is done in C; no programmer is immune to the laws of time, and C is simply a more time-efficient environment in which to develop, particularly when working in a programming team. In -this book, however, we’re after the *sine qua non* of PC -graphics—performance—and we can’t get there from here without a fair +this book, however, we're after the *sine qua non* of PC +graphics—performance—and we can't get there from here without a fair amount of assembly language. Now that we know what the VGA looks like in broad strokes and have a sense of what VGA programming is like, we can start looking at specific -areas in depth. In the next chapter, we’ll take a look at the hardware +areas in depth. In the next chapter, we'll take a look at the hardware assistance the VGA provides the CPU during display memory access. There are four latches and four ALUs in those chips, along with some useful -masks and comparators, and it’s that hardware that’s the difference +masks and comparators, and it's that hardware that's the difference between sluggish performance and making the VGA get up and dance. ------------------------ --------------------------------- -------------------- diff --git a/24-01.md b/24-01.md index 1c9e905..455bb17 100644 --- a/24-01.md +++ b/24-01.md @@ -12,15 +12,15 @@ This heading refers to the ability of the VGA chip to manipulate up to four bytes of display memory at once. In particular, the VGA provides four ALUs (Arithmetic Logic Units) to assist the CPU during display memory writes, and this hardware is a tremendous resource in the task of -manipulating the VGA’s sizable frame buffer. The ALUs are actually only +manipulating the VGA's sizable frame buffer. The ALUs are actually only one part of the surprisingly complex data flow architecture of the VGA, -but since they’re involved in almost all memory access operations, -they’re a good place to begin. +but since they're involved in almost all memory access operations, +they're a good place to begin. ### VGA Programming: ALUs and Latches {#Heading3} -I’m going to begin our detailed tour of the VGA at the heart of the flow -of data through the VGA: the four ALUs built into the VGA’s Graphics +I'm going to begin our detailed tour of the VGA at the heart of the flow +of data through the VGA: the four ALUs built into the VGA's Graphics Controller (GC) circuitry. The ALUs (one for each display memory plane) are capable of ORing, ANDing, and XORing CPU data and display memory data together, as well as masking off some or all of the bits in the @@ -42,7 +42,7 @@ performance would slow to a crawl. Figure 24.1 is a simplified depiction of data flow around the ALUs. Each ALU has a matching latch, which holds the byte read from the corresponding plane during the last CPU read from display memory, even -if that particular plane wasn’t the plane that the CPU actually read on +if that particular plane wasn't the plane that the CPU actually read on the last read access. (Only one byte can be read by the CPU with a single display memory read; the plane supplying the byte is selected by the Read Map register. However, the bytes at the specified address in @@ -52,7 +52,7 @@ those four bytes are stored in their respective latches.) Each ALU logically combines the byte written by the CPU and the byte stored in the matching latch, according to the settings of bits 3 and 4 of the Data Rotate register (and the Bit Mask register as well, which -I’ll cover next time), and then writes the result to display memory. It +I'll cover next time), and then writes the result to display memory. It is most important to understand that neither ALU operand comes directly from display memory. The temptation is to think of the ALUs as combining CPU data and the contents of the display memory address being written @@ -82,7 +82,7 @@ The latches can also be used to draw a fairly complex area fill pattern, with a different bit pattern used to fill each plane. The mechanism for this is as follows: First, generate the desired pattern across all planes at any display memory address. Generating the pattern requires a -separate write operation for each plane, so that each plane’s byte will +separate write operation for each plane, so that each plane's byte will be unique. Next, read that memory address to store the pattern in the latches. The contents of the latches can now be written to memory any number of times by using either write mode 1 or the bit mask, since they diff --git a/24-02.md b/24-02.md index 3c735ba..d35638c 100644 --- a/24-02.md +++ b/24-02.md @@ -4,14 +4,14 @@ **LISTING 24.1 L24-1.ASM** - ; Program to illustrate operation of ALUs and latches of the VGA’s + ; Program to illustrate operation of ALUs and latches of the VGA's ; Graphics Controller. Draws a variety of patterns against ; a horizontally striped background, using each of the 4 available ; logical functions (data unmodified, AND, OR, XOR) in turn to combine ; the images with the background. ; By Michael Abrash. ; - stack segment para stack ‘STACK’ + stack segment para stack ‘STACK' db 512 dup(?) stack ends ; @@ -34,23 +34,23 @@ ; register index GC_MODE equ 5 ;GC mode register index ; - dseg segment para common ‘DATA’ + dseg segment para common ‘DATA' ; ; String used to label logical functions. ; LabelString label byte - db ‘UNMODIFIED AND OR XOR ’ + db ‘UNMODIFIED AND OR XOR ' LABEL_STRING_LENGTH equ $-LabelString ; ; Strings used to label fill patterns. ; - FillPatternFF db ‘Fill Pattern: 0FFh’ + FillPatternFF db ‘Fill Pattern: 0FFh' FILL_PATTERN_FF_LENGTH equ $ - FillPatternFF - FillPattern00 db ‘Fill Pattern: 000h’ + FillPattern00 db ‘Fill Pattern: 000h' FILL_PATTERN_00_LENGTH equ $ - FillPattern00 - FillPatternVert db ‘Fill Pattern: Vertical Bar’ + FillPatternVert db ‘Fill Pattern: Vertical Bar' FILL_PATTERN_VERT_LENGTH equ $ - FillPatternVert - FillPatternHorz db ‘Fill Pattern: Horizontal Bar’ + FillPatternHorz db ‘Fill Pattern: Horizontal Bar' FILL_PATTERN_HORZ_LENGTH equ $ - FillPatternHorz ; dseg ends @@ -77,7 +77,7 @@ int 10h endm ; - cseg segment para public ‘CODE’ + cseg segment para public ‘CODE' assume cs:cseg, ds:dseg start proc near mov ax,dseg @@ -139,15 +139,15 @@ ; Label the screen. ; push ds - pop es ;strings we’ll display are passed to BIOS + pop es ;strings we'll display are passed to BIOS ; by pointing ES:BP to them ; - ; Label the logical functions, using the VGA BIOS’s + ; Label the logical functions, using the VGA BIOS's ; write string function. ; TEXT_UP LabelString, LABEL_STRING_LENGTH, 24, 0 ; - ; Label the fill patterns, using the VGA BIOS’s + ; Label the fill patterns, using the VGA BIOS's ; write string function. ; TEXT_UP FillPatternFF, FILL_PATTERN_FF_LENGTH, 3, 42 @@ -155,7 +155,7 @@ TEXT_UP FillPatternVert, FILL_PATTERN_VERT_LENGTH, 15, 42 TEXT_UP FillPatternHorz, FILL_PATTERN_HORZ_LENGTH, 21, 42 ; - ; Wait until a key’s been hit to reset screen mode & exit. + ; Wait until a key's been hit to reset screen mode & exit. ; WaitForKey: mov ah,1 @@ -195,7 +195,7 @@ mov cx,WIDTH ColumnLoop: mov ah,es:[di] ;load display memory contents into - ; GC latches (we don’t actually care + ; GC latches (we don't actually care ; about value read into AH) stosb ;write pattern, which is logically ; combined with latch contents for each @@ -225,7 +225,7 @@ dec ax ;0ffh fill (smaller to do word than byte DEC) mov cx,si ;width to fill HBLoop1: - mov bl,es:[di] ;load latches (don’t care about value) + mov bl,es:[di] ;load latches (don't care about value) stosb ;write solid pattern, through ALUs loop HBLoop1 add di,SCREEN_WIDTH_IN_BYTES - VERTICAL_BOX_WIDTH_IN_BYTES diff --git a/24-03.md b/24-03.md index dcff27d..72c7718 100644 --- a/24-03.md +++ b/24-03.md @@ -11,7 +11,7 @@ place CPU data into display memory, replacing the previous contents entirely. It may occur to you that there is no need to latch display memory at all when the data unmodified function is selected. In the sample program, that is true, but if the bit mask is being used, the -latches must be loaded even for the data unmodified function, as I’ll +latches must be loaded even for the data unmodified function, as I'll discuss in the next chapter. Logical functions 1 through 3 cause the CPU data to be ANDed, ORed, and @@ -19,11 +19,11 @@ XORed with the latched data, respectively. Of these, XOR is the most useful, since exclusive-ORing is a traditional way to perform animation. The uses of the AND and OR logical functions are less obvious. AND can be used to mask a blank area into display memory, or to mask off those -portions of a drawing operation that don’t overlap an existing display +portions of a drawing operation that don't overlap an existing display memory image. OR could conceivably be used to force an image into -display memory over an existing image. To be honest, I haven’t +display memory over an existing image. To be honest, I haven't encountered any particularly valuable applications for AND and OR, but -they’re the sort of building-block features that could come in handy in +they're the sort of building-block features that could come in handy in just the right context, so keep them in mind. ### Notes on the ALU/Latch Demo Program {#Heading4} @@ -33,7 +33,7 @@ their default condition before the BIOS is called to output text or draw pixels. The VGA BIOS does not guarantee that it will set most VGA registers except on mode sets, and there are so many compatible BIOSes around that the code of the IBM BIOS is not a reliable guide. For -instance, when the BIOS is called to draw text, it’s likely that the +instance, when the BIOS is called to draw text, it's likely that the result will be illegible if the Bit Mask register is not in its default state. Similarly, a mode set should generally be performed before exiting a program that tinkers with VGA settings. @@ -64,11 +64,11 @@ 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 read to load the latches and then write to modify display memory without -affecting any CPU registers, as we’ll see later on. +affecting any CPU registers, as we'll see later on. All text in the sample program is drawn by VGA BIOS function 13H, the -write string function. This function is also present in the AT’s BIOS, -but not in the XT’s or PC’s, and as a result is rarely used; the +write string function. This function is also present in the AT's BIOS, +but not in the XT's or PC's, and as a result is rarely used; the function is always available if a VGA is installed, however. Text drawn with this function is relatively slow. If speed is important, a program can draw text directly into display memory much faster in any given @@ -77,24 +77,24 @@ 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 -program may seem strange, but it’s a convenient way to save a byte of +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. +a word value that's composed of two independent immediate byte values. The obvious way to implement this would be with MOV DL,VALUE1 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, +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 +values with MASM's **OR** operator (or the **ADD** operator), both halves of DX can be loaded with a single instruction, as in 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 +(Note, though, that in 32-bit protected mode, there's a size and 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 @@ -103,7 +103,7 @@ 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 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 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 the register.* + ![](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 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 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 the register.* ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- The latches and ALUs are central to high-performance VGA code, since @@ -112,7 +112,7 @@ 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 -the barrel shifter, can assist the ALUs in controlling data, as we’ll +the barrel shifter, can assist the ALUs in controlling data, as we'll see in the next few chapters. ------------------------ --------------------------------- -------------------- diff --git a/25-01.md b/25-01.md index b7dd2f5..8b9d474 100644 --- a/25-01.md +++ b/25-01.md @@ -9,7 +9,7 @@ Chapter 25\ ### The Barrel Shifter, Bit Mask, and Set/Reset Mechanisms {#Heading2} In the last chapter, we examined a simplified model of data flow within -the GC portion of the VGA, featuring the latches and ALUs. Now we’re +the GC portion of the VGA, featuring the latches and ALUs. Now we're ready to expand that model to include the barrel shifter, bit mask, and the set/reset capabilities, leaving only the write modes to be explored over the next few chapters. @@ -17,9 +17,9 @@ over the next few chapters. ### VGA Data Rotation {#Heading3} Figure 25.1 shows an expanded model of GC data flow, featuring the -barrel shifter and bit mask circuitry. Let’s look at the barrel shifter +barrel shifter and bit mask circuitry. Let's look at the barrel shifter first. A barrel shifter is circuitry capable of shifting—or rotating, in -the VGA’s case—data an arbitrary number of bits in a single operation, +the VGA's case—data an arbitrary number of bits in a single operation, as opposed to being able to shift only one bit position at a time. The barrel shifter in the VGA can rotate incoming CPU data up to seven bits to the right (toward the least significant bit), with bit 0 wrapping @@ -39,9 +39,9 @@ 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 rotation: one to set the GC Index register, and one to set the Data -Rotate register. However, with careful programming it’s sometimes +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 @@ -50,16 +50,16 @@ rotate *latched* data, then there would be all sorts of useful applications for rotation, but, sadly, only CPU data can be rotated. The drawing of bit-mapped text is one use for the barrel shifter, and -I’ll demonstrate that application below. In general, though, don’t knock +I'll demonstrate that application below. In general, though, don't knock yourself out trying to figure out how to work data rotation into your -programs—it just isn’t all that useful in most cases. +programs—it just isn't all that useful in most cases. ### The Bit Mask {#Heading4} The VGA has bit mask circuitry for each of the four memory planes. The four bit masks operate in parallel and are all driven by the same mask -data for each operation, so they’re generally referred to in the -singular, as “the bit mask.” Figure 25.2 illustrates the operation of +data for each operation, so they're generally referred to in the +singular, as "the bit mask." Figure 25.2 illustrates the operation of one bit of the bit mask for one plane. This circuitry occurs eight times in the bit mask for a given plane, once for each bit of the byte written to display memory. Briefly, the bit mask determines on a bit-by-bit @@ -74,9 +74,9 @@ given bit of the Bit Mask register is 1, then the corresponding bit of data from the ALUs is written to display memory for all four planes, while if that bit is 0, then the corresponding bit of data from the latches for the four planes is written to display memory unchanged. (In -write mode 3, the actual bit mask that’s applied to data written to +write mode 3, the actual bit mask that's applied to data written to display memory is the logical AND of the contents of the Bit Mask -register and the data written by the CPU, as we’ll see in Chapter 26.) +register and the data written by the CPU, as we'll see in Chapter 26.) The most common use of the bit mask is to allow updating of selected bits within a display memory byte. This works as follows: The display @@ -93,7 +93,7 @@ capabilities of the GC to draw bitmapped text at any screen location. The BIOS only draws characters on character boundaries; in 640x480 graphics mode the default font is drawn on byte boundaries horizontally and every 16 scan lines vertically. However, with direct bitmapped text -drawing of the sort used in Listing 25.1, it’s possible to draw any font +drawing of the sort used in Listing 25.1, it's possible to draw any font of any size anywhere on the screen (and a lot faster than via DOS or the BIOS, as well). diff --git a/25-02.md b/25-02.md index 23450e2..1a99423 100644 --- a/25-02.md +++ b/25-02.md @@ -6,11 +6,11 @@ ; Program to illustrate operation of data rotate and bit mask ; features of Graphics Controller. Draws 8x8 character at - ; specified location, using VGA’s 8x8 ROM font. Designed + ; specified location, using VGA's 8x8 ROM font. Designed ; for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h. ; By Michael Abrash. ; - stack segment para stack ‘STACK’ + stack segment para stack ‘STACK' db 512 dup(?) stack ends ; @@ -25,13 +25,13 @@ ; register index GC_BIT_MASK equ 8 ;GC bit mask register index ; - dseg segment para common ‘DATA’ + dseg segment para common ‘DATA' TEST_TEXT_ROW equ 69 ;row to display test text at TEST_TEXT_COL equ 17 ;column to display test text at TEST_TEXT_WIDTH equ 8 ;width of a character in pixels TestString label byte - db ‘Hello, world!’,0 ;test string to print. + db ‘Hello, world!',0 ;test string to print. FontPointer dd ? ;font offset dseg ends ; @@ -43,7 +43,7 @@ out dx,ax endm ; - cseg segment para public ‘CODE’ + cseg segment para public ‘CODE' assume cs:cseg, ds:dseg start proc near mov ax,dseg @@ -106,7 +106,7 @@ ; BX = row to draw text character at ; CX = column to draw text character at ; - ; Forces ALU function to “move”. + ; Forces ALU function to "move". ; DrawChar proc near push ax @@ -237,14 +237,14 @@ The bit mask can be used for much more than bit-aligned fonts. For example, the bit mask is useful for fast pixel drawing, such as that -performed when drawing lines, as we’ll see in Chapter 35. It’s also +performed when drawing lines, as we'll see in Chapter 35. It's also useful for drawing the edges of primitives, such as filled polygons, that potentially involve modifying some but not all of the pixels controlled by a single byte of display memory. Basically, the bit mask is handy whenever only *some* of the eight pixels in a byte of display memory need to be changed, because it allows -full use of the VGA’s four-way parallel processing capabilities for the +full use of the VGA's four-way parallel processing capabilities for the pixels that are to be drawn, without interfering with the pixels that are to be left unchanged. The alternative would be plane-by-plane processing, which from a performance perspective would be undesirable diff --git a/25-03.md b/25-03.md index 119e258..52fbf01 100644 --- a/25-03.md +++ b/25-03.md @@ -2,22 +2,22 @@ [Previous](25-02.html) [Table of Contents](index.html) [Next](25-04.html) ------------------------ --------------------------------- -------------------- -It’s worth pointing out again that the bit mask operates on the data in +It's worth pointing out again that the bit mask operates on the data in the latches, not on the data in display memory. This makes the bit mask a flexible resource that with a little imagination can be used for some interesting purposes. For example, you could fill the latches with a solid background color (by writing the color somewhere in display memory, then reading that location to load the latches), and then use -the Bit Mask register (or write mode 3, as we’ll see later) as a mask +the Bit Mask register (or write mode 3, as we'll see later) as a mask through which to draw a foreground color stencilled into the background color *without* reading display memory first. This only works for writing whole bytes at a time (clipped bytes require the use of the bit -mask; unfortunately, we’re already using it for stencilling in this +mask; unfortunately, we're already using it for stencilling in this case), but it completely eliminates reading display memory and does foreground-plus-background drawing in one blurry-fast pass. ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *This last-described example is a good illustration of how I’d suggest you approach the VGA: As a rich collection of hardware resources that can profitably be combined in some non-obvious ways. Don’t let yourself be limited by the obvious applications for the latches, bit mask, write modes, read modes, map mask, ALUs, and set/reset circuitry. Instead, try to imagine how they could work together to perform whatever task you happen to need done at any given time. I’ve made my code as much as four times faster by doing this, as the discussion of Mode X in Chapters 47-49 demonstrates.* + ![](images/i.jpg) *This last-described example is a good illustration of how I'd suggest you approach the VGA: As a rich collection of hardware resources that can profitably be combined in some non-obvious ways. Don't let yourself be limited by the obvious applications for the latches, bit mask, write modes, read modes, map mask, ALUs, and set/reset circuitry. Instead, try to imagine how they could work together to perform whatever task you happen to need done at any given time. I've made my code as much as four times faster by doing this, as the discussion of Mode X in Chapters 47-49 demonstrates.* ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- The example code in Listing 25.1 is designed to illustrate the use of @@ -29,9 +29,9 @@ byte (to write the font byte), rather than four (to read display memory and write the font byte to each of the two bytes the character spans). Likewise, non-aligned text drawing could be streamlined to one display memory access per byte by having the CPU rotate and combine the font -data directly, rather than setting up the VGA’s hardware to do it. +data directly, rather than setting up the VGA's hardware to do it. (Listing 25.1 was designed to illustrate VGA data rotation and bit -masking rather than the fastest way to draw text. We’ll see faster +masking rather than the fastest way to draw text. We'll see faster text-drawing code soon.) One excellent rule of thumb is to minimize display memory accesses of all types, especially reads, which tend to be considerably slower than writes. Also, in Listing 25.1 it would be @@ -39,14 +39,14 @@ faster to use a table lookup to calculate the bit masks for the two halves of each character rather than the shifts used in the example. For another (and more complex) example of drawing bit-mapped text on the -VGA, see John Cockerham’s article, “Pixel Alignment of EGA Fonts,” *PC -Tech Journal*, January, 1987. Parenthetically, I’d like to pass along -John’s comment about the VGA: “When programming the VGA, *everything* is -complex.” +VGA, see John Cockerham's article, "Pixel Alignment of EGA Fonts," *PC +Tech Journal*, January, 1987. Parenthetically, I'd like to pass along +John's comment about the VGA: "When programming the VGA, *everything* is +complex." -He’s got a point there. +He's got a point there. -### The VGA’s Set/Reset Circuitry {#Heading5} +### The VGA's Set/Reset Circuitry {#Heading5} At last we come to the final aspect of data flow through the GC on write mode 0 writes: the set/reset circuitry. Figure 25.3 shows data flow on a diff --git a/25-04.md b/25-04.md index 9c8c9be..19618bd 100644 --- a/25-04.md +++ b/25-04.md @@ -8,7 +8,7 @@ ; to memory that already contains data. ; By Michael Abrash. ; - stack segment para stack ‘STACK’ + stack segment para stack ‘STACK' db 512 dup(?) stack ends ; diff --git a/25-06.md b/25-06.md index e2f5a59..c36e6aa 100644 --- a/25-06.md +++ b/25-06.md @@ -6,7 +6,7 @@ There is no clearly defined role for the set/reset circuitry, as there is for, say, the bit mask. In many cases, set/reset is largely interchangeable with CPU data, particularly with CPU data written in write mode 2 (write mode 2 operates similarly to the set/reset -circuitry, as we’ll see in Chapter 27). The most powerful use of +circuitry, as we'll see in Chapter 27). The most powerful use of set/reset, in my experience, is in applications such as the example of Listing 25.4, where it is used to force the value written to certain planes while the CPU data is written to other planes. In general, @@ -14,7 +14,7 @@ though, think of set/reset as one more tool you have at your disposal in getting the VGA to do what you need done, in this case a tool that lets you force all bits in each plane to either zero or one, or pass CPU data through unchanged, on each write to display memory. As tools go, -set/reset is a handy one, and it’ll pop up often in this book. +set/reset is a handy one, and it'll pop up often in this book. ### Notes on Set/Reset {#Heading8} @@ -24,7 +24,7 @@ register provides the primary drawing color in write mode 3, as discussed in the next chapter. ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Be aware that because set/reset directly replaces CPU data, it does not necessarily have to force an entire display memory byte to 0 or 0FFH, even when set/reset is replacing CPU data for all planes. For example, if the Bit Mask register is set to 80H, the set/reset circuitry can only modify bit 7 of the destination byte in each plane, since the other seven bits will come from the latches for each plane. Similarly, the set/reset value for each plane can be modified by that plane’s ALU. Once again, this illustrates that set/reset merely replaces the CPU data for selected planes; the set/reset value is then processed in exactly the same way that CPU data normally is.* + ![](images/i.jpg) *Be aware that because set/reset directly replaces CPU data, it does not necessarily have to force an entire display memory byte to 0 or 0FFH, even when set/reset is replacing CPU data for all planes. For example, if the Bit Mask register is set to 80H, the set/reset circuitry can only modify bit 7 of the destination byte in each plane, since the other seven bits will come from the latches for each plane. Similarly, the set/reset value for each plane can be modified by that plane's ALU. Once again, this illustrates that set/reset merely replaces the CPU data for selected planes; the set/reset value is then processed in exactly the same way that CPU data normally is.* ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### A Brief Note on Word OUTs {#Heading9} @@ -32,18 +32,18 @@ discussed in the next chapter. 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 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 +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: 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 +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 -since I’ve heard of them causing any problems. +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 +since I've heard of them causing any problems. ------------------------ --------------------------------- -------------------- [Previous](25-05.html) [Table of Contents](index.html) [Next](26-01.html) diff --git a/26-01.md b/26-01.md index 5d0598f..755d2fa 100644 --- a/26-01.md +++ b/26-01.md @@ -8,8 +8,8 @@ Chapter 26\ ### The Write Mode That Grows on You {#Heading2} -Over the last three chapters, we’ve covered the VGA’s write path from -stem to stern—with one exception. Thus far, we’ve only looked at how +Over the last three chapters, we've covered the VGA's write path from +stem to stern—with one exception. Thus far, we've only looked at how writes work in write mode 0, the straightforward, workhorse mode in which each byte that the CPU writes to display memory fans out across the four planes. (Actually, we also took a quick look at write mode 1, @@ -17,9 +17,9 @@ in which the latches are always copied unmodified, but since exactly the same result can be achieved by setting the Bit Mask register to 0 in write mode 0, write mode 1 is of little real significance.) -Write mode 0 is a very useful mode, but some of VGA’s most interesting +Write mode 0 is a very useful mode, but some of VGA's most interesting capabilities involve the two write modes that we have yet to examine: -write mode 1, and, especially, write mode 3. We’ll get to write mode 1 +write mode 1, and, especially, write mode 3. We'll get to write mode 1 in the next chapter, but right now I want to focus on write mode 3, which can be confusing at first, but turns out to be quite a bit more powerful than one might initially think. @@ -36,25 +36,25 @@ 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, write mode 3 was used more than write mode 0 overall, excluding simple pixel copying. So write mode 3 is well worth using, but to use it you -must first understand it. Here’s how it works. +must first understand it. Here's how it works. In write mode 3, set/reset is automatically enabled for all four planes (the Enable Set/Reset register is ignored). The CPU data byte is rotated and then ANDed with the contents of the Bit Mask register, and the result of this operation is used as the contents of the Bit Mask register alone would normally be used. (If this is Greek to you, have a -look back at Chapters 23 through 25. There’s no way to understand write -mode 3 without understanding the rest of the VGA’s write data path +look back at Chapters 23 through 25. There's no way to understand write +mode 3 without understanding the rest of the VGA's write data path first.) -That’s what write mode 3 does—but what is it *for?* It turns out that +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 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. -(As we’ll see at the end of this chapter, write mode 3 is potentially +(As we'll see at the end of this chapter, write mode 3 is potentially terrific for opaque text—text drawn with the character box filled in with a solid color—as well.) diff --git a/26-02.md b/26-02.md index 04e34e3..0b0f19e 100644 --- a/26-02.md +++ b/26-02.md @@ -6,14 +6,14 @@ ; Program to illustrate operation of write mode 3 of the VGA. ; Draws 8x8 characters at arbitrary locations without disturbing - ; the background, using VGA’s 8x8 ROM font. Designed + ; the background, using VGA's 8x8 ROM font. Designed ; for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h. ; Runs only on VGAs (in Models 50 & up and IBM Display Adapter ; and 100% compatibles). ; Assembled with MASM ; By Michael Abrash ; - stack segment para stack ‘STACK’ + stack segment para stack ‘STACK' db 512 dup(?) stack ends ; @@ -33,16 +33,16 @@ GC_MODE equ 5 ;GC Mode register GC_BIT_MASK equ 8 ;GC bit mask register index ; - dseg segment para common ‘DATA’ + dseg segment para common ‘DATA' TEST_TEXT_ROW equ 69 ;row to display test text at TEST_TEXT_COL equ 17 ;column to display test text at TEST_TEXT_WIDTH equ 8 ;width of a character in pixels TestString label byte - db ‘Hello, world!’,0 ;test string to print. + db ‘Hello, world!',0 ;test string to print. FontPointer dd ? ;font offset dseg ends ; - cseg segment para public ‘CODE’ + cseg segment para public ‘CODE' assume cs:cseg, ds:dseg start proc near mov ax,dseg @@ -77,7 +77,7 @@ mov di,0 mov cx,8000h ;fill all 32k words mov ax,0ffffh ;because of set/reset, the value - ; written actually doesn’t matter + ; written actually doesn't matter rep stosw ;fill with blue ; ; Set driver to use the 8x8 font. @@ -131,7 +131,7 @@ ; BX = row to draw text character at ; CX = column to draw text character at ; - ; Forces ALU function to “move”. + ; Forces ALU function to "move". ; Forces write mode 3. ; DrawChar proc near @@ -205,7 +205,7 @@ ; ; Set up the GC rotation. In write mode 3, this is the rotation ; of CPU data before it is ANDed with the Bit Mask register to - ; form the bit mask. Force the ALU function to “move”. Uses the + ; form the bit mask. Force the ALU function to "move". Uses the ; readability of VGA registers to leave reserved bits unchanged. ; mov dx,GC_INDEX diff --git a/26-03.md b/26-03.md index a587b98..6327c4e 100644 --- a/26-03.md +++ b/26-03.md @@ -10,13 +10,13 @@ Rotate register is set to rotate the CPU data to pixel-align it, since 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 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 combines pieces of two adjacent characters into one byte—whenever possible for efficiency, the CPU generally has to do extra work to prepare the data so the VGA’s rotator can handle it.* + ![](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 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 combines pieces of two adjacent characters into one byte—whenever possible for efficiency, the CPU generally has to do extra work to prepare the data so the VGA's rotator can handle it.* ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- At the same time that the Data Rotate register is set, the Bit Mask register is set to allow the CPU to modify only that portion of the display memory byte accessed that the pixel-aligned character falls in, -so that other characters and/or graphics data won’t be wiped out. The +so that other characters and/or graphics data won't be wiped out. The result of ANDing the rotated CPU data byte with the contents of the Bit Mask register is a bit mask that allows only the bits equal to 1 in the original character pattern (rotated and masked to provide pixel @@ -29,12 +29,12 @@ that falls in the currently addressed byte. The color of the pixels set by the CPU is determined by the contents of the Set/Reset register. Whew. It sounds complex, but given an understanding of what the data -rotator, set/reset, and the bit mask do, it’s not that bad. One good way +rotator, set/reset, and the bit mask do, it's not that bad. One good way to make sense of it is to refer to the original text-drawing program in Listing 25.1 back in Chapter 25, and then see how Listing 26.1 differs from that program. -It’s worth noting that the results generated by Listing 26.1 could have +It's worth noting that the results generated by Listing 26.1 could have been accomplished without write mode 3. Write mode 0 could have been used instead, but at a significant performance cost. Instead of letting write mode 3 rotate the CPU data and AND it with the contents of the Bit @@ -52,7 +52,7 @@ the left portion of each bit-aligned character (the portion of each 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 +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 be those required to set the bit mask and data rotation. This technique could well outperform single-character bit-mapped text drivers such as @@ -68,14 +68,14 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into ; Program to illustrate high-speed text-drawing operation of ; write mode 3 of the VGA. ; Draws a string of 8x14 characters at arbitrary locations - ; without disturbing the background, using VGA’s 8x14 ROM font. + ; without disturbing the background, using VGA's 8x14 ROM font. ; Designed for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h. ; Runs only on VGAs (in Models 50 & up and IBM Display Adapter ; and 100% compatibles). ; Assembled with MASM ; By Michael Abrash ; - stack segment para stack ‘STACK’ + stack segment para stack ‘STACK' db 512 dup(?) stack ends ; @@ -95,16 +95,16 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into GC_MODE equ 5 ;GC Mode register GC_BIT_MASK equ 8 ;GC bit mask register index ; - dseg segment para common ‘DATA’ + dseg segment para common ‘DATA' TEST_TEXT_ROW equ 69 ;row to display test text at TEST_TEXT_COL equ 17 ;column to display test text at TEST_TEXT_COLOR equ 0fh ;high intensity white TestString label byte - db ‘Hello, world!’,0 ;test string to print. + db ‘Hello, world!',0 ;test string to print. FontPointer dd ? ;font offset dseg ends ; - cseg segment para public ‘CODE’ + cseg segment para public ‘CODE' assume cs:cseg, ds:dseg start proc near mov ax,dseg @@ -139,7 +139,7 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into mov di,0 mov cx,8000h ;fill all 32k words mov ax,0ffffh ;because of set/reset, the value - ; written actually doesn’t matter + ; written actually doesn't matter rep stosw ;fill with blue ; ; Set driver to use the 8x14 font. @@ -183,7 +183,7 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into ; CX = column to start string at ; DS:SI = string to draw ; - ; Forces ALU function to “move”. + ; Forces ALU function to "move". ; Forces write mode 3. ; DrawString proc near @@ -242,7 +242,7 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into ; ; Set up the GC rotation. In write mode 3, this is the rotation ; of CPU data before it is ANDed with the Bit Mask register to - ; form the bit mask. Force the ALU function to “move”. Uses the + ; form the bit mask. Force the ALU function to "move". Uses the ; readability of VGA registers to leave reserved bits unchanged. ; mov dx,GC_INDEX @@ -384,11 +384,11 @@ along with the tables used to alter the 8x14 and 8x16 ROM fonts into cseg ends end start -In this chapter, I’ve tried to give you a feel for how write mode 3 +In this chapter, I've tried to give you a feel for how write mode 3 works and what it might be used for, rather than providing polished, -optimized, plug-it-in-and-go code. Like the rest of the VGA’s write +optimized, plug-it-in-and-go code. Like the rest of the VGA's write path, write mode 3 is a resource that can be used in a remarkable -variety of ways, and I don’t want to lock you into thinking of it as +variety of ways, and I don't want to lock you into thinking of it as useful in just one context. Instead, you should take the time to thoroughly understand what write mode 3 does, and then, when you do VGA programming, think about how write mode 3 can best be applied to the @@ -402,7 +402,7 @@ single operation. Similarly, Listing 26.1 is designed to illustrate write mode 3 and its interaction with the rest of the VGA as a contrast to Listing 25.1 in Chapter 25, rather than for maximum speed, and it could be made -considerably more efficient. If we were going for performance, we’d have +considerably more efficient. If we were going for performance, we'd have the CPU not only rotate the bytes into position, but also do the masking by ANDing in software. Even more significantly, we would have the CPU combine adjacent characters into complete, rotated bytes whenever @@ -417,15 +417,15 @@ background (text box) color, set/reset could be set to the foreground (text) color, and write mode 3 could then be used to turn monochrome text bytes written by the CPU into characters on the screen with just one write per byte. There are complications, such as drawing partial -bytes, and rotating the bytes to align the characters, which we’ll -revisit later on in Chapter 55, while we’re working through the details +bytes, and rotating the bytes to align the characters, which we'll +revisit later on in Chapter 55, while we're working through the details of the X-Sharp library. Nonetheless, the performance benefit of this approach can be a speedup of as much as four times—all thanks to the decidedly quirky but surprisingly powerful and flexible write mode 3. ### A Note on Preserving Register Bits {#Heading4} -If you take a quick look, you’ll see that the code in Listing 26.1 uses +If you take a quick look, you'll see that the code in Listing 26.1 uses the readable register feature of the VGA to preserve reserved bits and bits other than those being modified. Older adapters such as the CGA and EGA had few readable registers, so it was necessary to set all bits in a diff --git a/27-01.md b/27-01.md index 72c178c..bba7c74 100644 --- a/27-01.md +++ b/27-01.md @@ -10,7 +10,7 @@ Chapter 27\ In the last chapter, we learned about the markedly peculiar write mode 3 of the VGA, after having spent three chapters learning the ins and outs -of the VGA’s data path in write mode 0, touching on write mode 1 as well +of the VGA's data path in write mode 0, touching on write mode 1 as well in Chapter 23. In all, the VGA supports four write modes—write modes 0, 1, 2, and 3—and read modes 0 and 1 as well. Which leaves two burning questions: What is write mode 2, and how the heck do you *read* VGA @@ -21,13 +21,13 @@ particularly if you followed the description of set/reset in Chapter 25. Reading VGA memory, on the other hand, can be stranger than you could ever imagine. -Let’s start with the easy stuff, write mode 2, and save the read modes +Let's start with the easy stuff, write mode 2, and save the read modes for the next chapter. ### Write Mode 2 and Set/Reset {#Heading3} -Remember how set/reset works? Good, because that’s pretty much how write -mode 2 works. (You *don’t* remember? Well, I’ll provide a brief +Remember how set/reset works? Good, because that's pretty much how write +mode 2 works. (You *don't* remember? Well, I'll provide a brief refresher, but I suggest that you go back through Chapters 23 through 25 and come up to speed on the VGA.) @@ -56,12 +56,12 @@ CPU-side input); likewise, bit 1 of the CPU byte is expanded to a byte for plane 1, bit 2 is expanded for plane 2, and bit 3 is expanded for plane 3. -It’s possible that you understand write mode 2 thoroughly at this point; +It's possible that you understand write mode 2 thoroughly at this point; nonetheless, I suspect that some additional explanation of an admittedly -non-obvious mode wouldn’t hurt. Let’s follow the CPU byte through the +non-obvious mode wouldn't hurt. Let's follow the CPU byte through the VGA in write mode 2, step by step. -#### A Byte’s Progress in Write Mode 2 {#Heading4} +#### A Byte's Progress in Write Mode 2 {#Heading4} Figure 27.1 shows the write mode 2 data path. The CPU byte comes into the VGA and is split into four separate bits, one for each plane. Bits @@ -74,12 +74,12 @@ the CPU byte remains unused in write mode 2 to this day. The bit of the CPU byte sent to each plane is expanded to a 0 or 0FFH byte, depending on whether the bit is 0 or 1, respectively. The byte for -each plane then becomes the CPU-side input to the respective plane’s +each plane then becomes the CPU-side input to the respective plane's ALU. From this point on, the write mode 2 data path is identical to the write mode 0 data path. As discussed in earlier articles, the latch byte for each plane is the other ALU input, and the ALU either ANDs, ORs, or XORs the two bytes together or simply passes the CPU-side byte through. -The byte generated by each plane’s ALU then goes through the bit mask +The byte generated by each plane's ALU then goes through the bit mask circuitry, which selects on a bit-by-bit basis between the ALU byte and the latch byte. Finally, the byte from the bit mask circuitry for each plane is written to that plane if the corresponding bit in the Map Mask @@ -89,7 +89,7 @@ register is set to 1. **Figure 27.1**  *VGA data flow in write mode 2.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *It’s worth noting two differences between write mode 2 and write mode 0, the standard write mode of the VGA. First, rotation of the CPU data byte does not take place in write mode 2. Second, the Set/Reset and Enable Set/Reset registers have no effect in write mode 2.* + ![](images/i.jpg) *It's worth noting two differences between write mode 2 and write mode 0, the standard write mode of the VGA. First, rotation of the CPU data byte does not take place in write mode 2. Second, the Set/Reset and Enable Set/Reset registers have no effect in write mode 2.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Now that we understand the mechanics of write mode 2, we can step back @@ -98,10 +98,10 @@ byte as a single pixel in one of 16 colors. Next imagine that nibble turned sideways and written across the four planes, one bit to a plane. Finally, expand each of the bits to a byte, as shown in Figure 27.2, so that 8 pixels are drawn in the color selected by bits 3-0 of the CPU -byte. Within the constraints of the VGA’s data paths, that’s exactly +byte. Within the constraints of the VGA's data paths, that's exactly what write mode 2 does. -By “the constraints of the VGA’s data paths,” I mean the ALUs, the bit +By "the constraints of the VGA's data paths," I mean the ALUs, the bit mask, and the map mask. As Figure 27.1 indicates, the ALUs can modify the color written by the CPU, the map mask can prevent the CPU from altering selected planes, and the bit mask can prevent the CPU from @@ -113,7 +113,7 @@ These are not really constraints at all, of course, but rather features of the VGA; I simply want to make it clear that the use of write mode 2 to set 8 pixels to a given color is a rather simple special case among the many possible ways in which write mode 2 can be used to feed data -into the VGA’s data path. +into the VGA's data path. Write mode 2 is selected by setting bits 1 and 0 of the Graphics Mode register (Graphics Controller register 5) to 1 and 0, respectively. diff --git a/27-02.md b/27-02.md index 064b0e7..0186101 100644 --- a/27-02.md +++ b/27-02.md @@ -4,7 +4,7 @@ #### Copying Chunky Bitmaps to VGA Memory Using Write Mode 2 {#Heading5} -Let’s take a look at two examples of write mode 2 in action. Listing +Let's take a look at two examples of write mode 2 in action. Listing 27.1 presents a program that uses write mode 2 to copy a graphics image in chunky format to the VGA. In chunky format adjacent bits in a single byte make up each pixel: mode 4 of the CGA, EGA, and VGA is a @@ -25,7 +25,7 @@ discussed in Chapter 25, the bit mask makes it possible to narrow the effect of the CPU write down to a single pixel. Given the above, conversion of a chunky 4-bit-per-pixel bitmap to the -VGA’s planar format in write mode 2 is trivial. First, the Bit Mask +VGA's planar format in write mode 2 is trivial. First, the Bit Mask register is set to allow only the VGA display memory bits corresponding to the leftmost chunky pixel of the two stored in the first chunky bitmap byte to be modified. Next, the destination byte in display memory @@ -45,14 +45,14 @@ image. **LISTING 27.1 L27-1.ASM** ; Program to illustrate one use of write mode 2 of the VGA and EGA by - ; animating the image of an “A” drawn by copying it from a chunky + ; animating the image of an "A" drawn by copying it from a chunky ; bit-map in system memory to a planar bit-map in VGA or EGA memory. ; ; Assemble with MASM or TASM ; ; By Michael Abrash ; - Stack segment para stack ‘STACK’ + Stack segment para stack ‘STACK' db 512 dup(0) Stack ends @@ -64,15 +64,15 @@ image. GRAPHICS_MODE equ 5 ;index of Graphics Mode reg BIT_MASKequ 8 ;index of Bit Mask reg - Data segment para common ‘DATA’ + Data segment para common ‘DATA' ; - ; Current location of “A” as it is animated across the screen. + ; Current location of "A" as it is animated across the screen. ; CurrentX dw ? CurrentY dw ? RemainingLength dw ? ; - ; Chunky bit-map image of a yellow “A” on a bright blue background + ; Chunky bit-map image of a yellow "A" on a bright blue background ; AImage label byte dw 13, 13 ;width, height in pixels @@ -91,7 +91,7 @@ image. db 000h, 000h, 000h, 000h, 000h, 000h, 000h Data ends - Code segment para public ‘CODE’ + Code segment para public ‘CODE' assume cs:Code, ds:Data Start proc near mov ax,Data @@ -105,7 +105,7 @@ image. mov [CurrentY],200 mov [RemainingLength],600 ;move 600 times ; - ; Animate, repeating RemainingLength times. It’s unnecessary to erase + ; Animate, repeating RemainingLength times. It's unnecessary to erase ; the old image, since the one pixel of blank fringe around the image ; erases the part of the old image not overlapped by the new image. ; @@ -113,10 +113,10 @@ image. mov bx,[CurrentX] mov cx,[CurrentY] mov si,offset AImage - call DrawFromChunkyBitmap ;draw the “A” image + call DrawFromChunkyBitmap ;draw the "A" image inc [CurrentX] ;move one pixel to the right - mov cx,0 ;delay so we don’t move the + mov cx,0 ;delay so we don't move the DelayLoop: ; image too fast; adjust as ; needed loop DelayLoop @@ -207,7 +207,7 @@ image. inc dx ; to the Bit Mask register RowLoop: - push ax ;preserve the left column’s bit mask + push ax ;preserve the left column's bit mask push cx ;preserve the width push di ;preserve the destination offset @@ -251,7 +251,7 @@ image. CheckMoreScanLines: pop di ;get back the destination offset pop cx ;get back the width - pop ax ;get back the left column’s bit mask + pop ax ;get back the left column's bit mask add di,SCREEN_WIDTH_IN_BYTES ;point to the start of the next scan ; line of the image diff --git a/27-03.md b/27-03.md index 91912b3..7fcbc21 100644 --- a/27-03.md +++ b/27-03.md @@ -2,13 +2,13 @@ [Previous](27-02.html) [Table of Contents](index.html) [Next](27-04.html) ------------------------ --------------------------------- -------------------- -“That’s an interesting application of write mode 2,” you may well say, -“but is it really useful?” While the ability to convert chunky bitmaps +"That's an interesting application of write mode 2," you may well say, +"but is it really useful?" While the ability to convert chunky bitmaps into VGA bitmaps does have its uses, Listing 27.1 is primarily intended to illustrate the mechanics of write mode 2. ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *For performance, it’s best to store 16-color bitmaps in pre-separated four-plane format in system memory, and copy one plane at a time to the screen. Ideally, such bitmaps should be copied one scan line at a time, with all four planes completed for one scan line before moving on to the next. I say this because when entire images are copied one plane at a time, nasty transient color effects can occur as one plane becomes visibly changed before other planes have been modified.* + ![](images/i.jpg) *For performance, it's best to store 16-color bitmaps in pre-separated four-plane format in system memory, and copy one plane at a time to the screen. Ideally, such bitmaps should be copied one scan line at a time, with all four planes completed for one scan line before moving on to the next. I say this because when entire images are copied one plane at a time, nasty transient color effects can occur as one plane becomes visibly changed before other planes have been modified.* ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- #### Drawing Color-Patterned Lines Using Write Mode 2 {#Heading6} @@ -17,14 +17,14 @@ A more serviceable use of write mode 2 is shown in the program presented in Listing 27.2. The program draws multicolored horizontal, vertical, and diagonal lines, basing the color patterns on passed color tables. 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 +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 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 -guarantee that data already in display memory wouldn’t interfere with +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. @@ -47,7 +47,7 @@ the CPU byte in write mode 2 to select the color in which to draw. ; ; By Michael Abrash ; - Stack segment para stack ‘STACK’ + Stack segment para stack ‘STACK' db 512 dup(0) Stack ends @@ -59,7 +59,7 @@ the CPU byte in write mode 2 to select the color in which to draw. GRAPHICS_MODE equ 5 ;index of Graphics Mode reg BIT_MASK equ 8 ;index of Bit Mask reg - Data segment para common ‘DATA’ + Data segment para common ‘DATA' Pattern0 db 16 db 0, 1, 2, 3, 4, 5, 6, 7, 8 db 9, 10, 11, 12, 13, 14, 15 @@ -71,7 +71,7 @@ the CPU byte in write mode 2 to select the color in which to draw. db 1, 1, 1, 2, 2, 2, 4, 4, 4 Data ends - Code segment para public ‘CODE’ + Code segment para public ‘CODE' assume cs:Code, ds:Data Start proc near mov ax,Data diff --git a/27-04.md b/27-04.md index 15fde33..2076ba3 100644 --- a/27-04.md +++ b/27-04.md @@ -25,18 +25,18 @@ set/reset is enabled for some but not all planes. ### Mode 13H—320x200 with 256 Colors {#Heading8} -I’m going to take a minute—and I do mean a minute—to discuss the -programming model for mode 13H, the VGA’s 320x200 256-color mode. -Frankly, there’s just not much to it, especially compared to the -convoluted 16-color model that we’ve explored over the last five +I'm going to take a minute—and I do mean a minute—to discuss the +programming model for mode 13H, the VGA's 320x200 256-color mode. +Frankly, there's just not much to it, especially compared to the +convoluted 16-color model that we've explored over the last five chapters. Mode 13H offers the simplest programming model in the history of PC graphics: A linear bitmap starting at A000:0000, consisting of 64,000 bytes, each controlling one pixel. The byte at offset 0 controls the upper left pixel on the screen, the byte at offset 319 controls the upper right pixel on the screen, the byte at offset 320 controls the second pixel down at the left of the screen, and the byte at offset -63,999 controls the lower right pixel on the screen. That’s all there is -to it; it’s so simple that I’m not going to spend any time on a demo +63,999 controls the lower right pixel on the screen. That's all there is +to it; it's so simple that I'm not going to spend any time on a demo program, especially given that some of the listings later in this book, such as the antialiasing code in Chapter F on the companion CD-ROM, use mode 13H. @@ -46,39 +46,39 @@ mode 13H. A while back, I got an interesting letter from Phil Coleman, of La Jolla, who wrote: -“Suppose I have the EGA in mode 10H (640x350 16-color graphics). I would +"Suppose I have the EGA in mode 10H (640x350 16-color graphics). I would like to preserve some or all of the image while I temporarily switch to -text mode 3 to give my user a ‘Help’ screen. Naturally memory is scarce -so I’d rather not make a copy of the video buffer at A000H to ‘remember’ +text mode 3 to give my user a ‘Help' screen. Naturally memory is scarce +so I'd rather not make a copy of the video buffer at A000H to ‘remember' the image while I digress to the Help text. The EGA BIOS says that the screen memory will not be cleared on a mode set if bit 7 of AL is set. Yet if I try that, it is clear that writing text into the B800H buffer trashes much more than the 4K bytes of a text page; when I switch back -to mode 10H, “ghosts” appear in the form of bands of colored dots. (When +to mode 10H, "ghosts" appear in the form of bands of colored dots. (When in text mode, I do make a copy of the 4K buffer at B800H before showing the help; and I restore the 4K before switching back to mode 10H.) Is there a way to preserve the graphics image while I switch to text -mode?’’ +mode?'' -“A corollary to this question is: Where does the 64/128/256K of EGA -memory ‘hide’ when the EGA is in text mode? Some I guess is used to +"A corollary to this question is: Where does the 64/128/256K of EGA +memory ‘hide' when the EGA is in text mode? Some I guess is used to store character sets, but what happens to the rest? Or rather, how can I -protect it?” +protect it?" Those are good questions. Alas, answering them in full would require -extensive explanation that would have little general application, so I’m +extensive explanation that would have little general application, so I'm not going to do that. However, the issue of how to go to text mode and back without losing the graphics image certainly rates a short -discussion, complete with some working code. That’s especially true +discussion, complete with some working code. That's especially true given that both the discussion and the code apply just as well to the -VGA as to the EGA (with a few differences in mode 12H, the VGA’s +VGA as to the EGA (with a few differences in mode 12H, the VGA's highmode, as noted below). Phil is indeed correct in his observation that setting bit 7 of AL instructs the BIOS not to clear display memory on mode sets, and he is also correct in surmising that a font is loaded when going to text mode. The normal mode 10H bitmap occupies the first 28,000 bytes of each of -the VGA’s four planes. (The mode 12H bitmap takes up the first 38,400 +the VGA's four planes. (The mode 12H bitmap takes up the first 38,400 bytes of each plane.) The normal mode 3 character/attribute memory map resides in the first 4000 bytes of planes 0 and 1 (the blue and green planes in mode 10H). The standard font in mode 3 is stored in the first @@ -91,18 +91,18 @@ bytes—the first 4000 bytes of planes 0 and 1 and the first 8K bytes of plane 2—when going from mode 10H or mode 12H to mode 3, to be restored on returning to graphics mode. -That’s hardly all there is to the matter of going from text to graphics +That's hardly all there is to the matter of going from text to graphics and back without bitmap corruption, though. One interesting point is that the mode 10H bitmap can be relocated to A000:8000 simply by doing a mode set to mode 10H and setting the start address (programmed at CRT Controller registers 0CH and 0DH) to 8000H. You can then access display memory starting at A800:8000 instead of the normal A000:0000, with the resultant display exactly like that of normal mode 10H. There are BIOS -issues, since the BIOS doesn’t automatically access display memory at +issues, since the BIOS doesn't automatically access display memory at the new start address, but if your program does all its drawing directly -without the help of the BIOS, that’s no problem. +without the help of the BIOS, that's no problem. -The mode 12H bitmap can’t start at A000:8000, because it’s so long that +The mode 12H bitmap can't start at A000:8000, because it's so long that it would run off the end of display memory. However, the mode 12H bitmap can be relocated to, say, A000:6000, where it would fit without conflicting with the default font or the normal text mode memory map, @@ -110,7 +110,7 @@ although it would overlap two of the upper pages available for use (but rarely used) by text-mode programs. At any rate, once the graphics mode bitmap is relocated, flipping to -text mode and back becomes painless. The memory used by mode 3 doesn’t +text mode and back becomes painless. The memory used by mode 3 doesn't overlap the relocated mode 10H bitmap at all (unless additional portions of font memory are loaded), so all you need do is set bit 7 of AL on mode sets in order to flip back and forth between the two modes. diff --git a/27-05.md b/27-05.md index b97b29d..fee442e 100644 --- a/27-05.md +++ b/27-05.md @@ -3,7 +3,7 @@ ------------------------ --------------------------------- -------------------- Another interesting point about flipping from graphics to text and back -is that the standard mode 3 character/attribute map doesn’t actually +is that the standard mode 3 character/attribute map doesn't actually take up every byte of the first 4000 bytes of planes 0 and 1. The standard mode 3 character/attribute map actually only takes up every even byte of the first 4000 in each plane; the odd bytes are left @@ -21,10 +21,10 @@ single block. Explaining why only every other byte of planes 0 and 1 is used in text mode and why characters and attributes appear to be contiguous bytes when they are actually in different planes is a large part of the -explanation I’m not going to go into now. One bit of fallout from this, +explanation I'm not going to go into now. One bit of fallout from this, however, is that if you flip to text mode and preserve the graphics -bitmap using the mechanism illustrated in Listing 27.3, you shouldn’t -write to any text page other than page 0 (that is, don’t write to any +bitmap using the mechanism illustrated in Listing 27.3, you shouldn't +write to any text page other than page 0 (that is, don't write to any offset in display memory above 3999 in text mode) or alter the Page Select bit in the Miscellaneous Output register (3C2H) while in text mode. In order to allow completely unfettered access to text pages, it @@ -37,10 +37,10 @@ saved, up to a maximum of all 64K of plane 2. In the worst case, a full 128K would have to be saved in order to preserve all the memory potentially used by text mode. -As I said, Phil Coleman’s question is an interesting one, and I’ve only +As I said, Phil Coleman's question is an interesting one, and I've only touched on the intriguing possibilities arising from the various configurations of display memory in VGA graphics and text modes. Right -now, though, we’ve still got the basics of the remarkably complex (but +now, though, we've still got the basics of the remarkably complex (but rewarding!) VGA to cover. **LISTING 27.3 L27-3.ASM** @@ -52,7 +52,7 @@ rewarding!) VGA to cover. ; ; By Michael Abrash ; - Stack segment para stack ‘STACK’ + Stack segment para stack ‘STACK' db 512 dup(0) Stack ends @@ -63,19 +63,19 @@ rewarding!) VGA to cover. GC_INDEX equ 3ceh ;Graphics Controller Index register READ_MAP equ 4 ;index of Read Map register - Data segment para common ‘DATA’ + Data segment para common ‘DATA' GStrikeAnyKeyMsg0 label byte - db 0dh, 0ah, ‘Graphics mode’, 0dh, 0ah - db ‘Strike any key to continue...’, 0dh, 0ah, ‘$’ + db 0dh, 0ah, ‘Graphics mode', 0dh, 0ah + db ‘Strike any key to continue...', 0dh, 0ah, ‘$' GStrikeAnyKeyMsg1 label byte - db 0dh, 0ah, ‘Graphics mode again’, 0dh, 0ah - db ‘Strike any key to continue...’, 0dh, 0ah, ‘$’ + db 0dh, 0ah, ‘Graphics mode again', 0dh, 0ah + db ‘Strike any key to continue...', 0dh, 0ah, ‘$' TStrikeAnyKeyMsg label byte - db 0dh, 0ah, ‘Text mode’, 0dh, 0ah - db ‘Strike any key to continue...’, 0dh, 0ah, ‘$’ + db 0dh, 0ah, ‘Text mode', 0dh, 0ah + db ‘Strike any key to continue...', 0dh, 0ah, ‘$' Plane2Save db 2000h dup (?) ;save area for plane 2 data ; where font gets loaded @@ -84,7 +84,7 @@ rewarding!) VGA to cover. ; data in text mode Data ends - Code segment para public ‘CODE’ + Code segment para public ‘CODE' assume cs:Code, ds:Data Start proc near mov ax,10h @@ -116,7 +116,7 @@ rewarding!) VGA to cover. shl ah,1 loop FillBitMap ; - ; Put up “strike any key” message. + ; Put up "strike any key" message. ; mov ax,Data mov ds,ax @@ -162,13 +162,13 @@ rewarding!) VGA to cover. mov cx,4000/2 ;length of one text screen in words rep movsw ; - ; Fill the text mode screen with dots and put up “strike any key” + ; Fill the text mode screen with dots and put up "strike any key" ; message. ; mov ax,TEXT_SEGMENT mov es,ax sub di,di - mov al,‘.’ ;fill character + mov al,‘.' ;fill character mov ah,7 ;fill attribute mov cx,4000/2 ;length of one text screen in words rep stosw @@ -217,7 +217,7 @@ rewarding!) VGA to cover. mov cx,2000h/2 ;restore 8K (length of default font) rep movsw ; - ; Put up “strike any key” message. + ; Put up "strike any key" message. ; mov ax,Data mov ds,ax diff --git a/28-01.md b/28-01.md index 792ba06..3a8894c 100644 --- a/28-01.md +++ b/28-01.md @@ -6,24 +6,24 @@ Chapter 28\ Reading VGA Memory {#Heading1} ------------------- -### Read Modes 0 and 1, and the Color Don’t Care Register {#Heading2} +### Read Modes 0 and 1, and the Color Don't Care Register {#Heading2} -Well, it’s taken five chapters, but we’ve finally covered the data write -path and all four write modes of the VGA. Now it’s time to tackle the -VGA’s two read modes. While the read modes aren’t as complex as the -write modes, they’re nothing to sneeze at. In particular, read mode 1 +Well, it's taken five chapters, but we've finally covered the data write +path and all four write modes of the VGA. Now it's time to tackle the +VGA's two read modes. While the read modes aren't as complex as the +write modes, they're nothing to sneeze at. In particular, read mode 1 (also known as color compare mode) is rather unusual and not at all intuitive. -You may well ask, isn’t *anything* about programming the VGA +You may well ask, isn't *anything* about programming the VGA straightforward? Well...no. But then, clearing up the mysteries of VGA -programming is what this part of the book is all about, so let’s get +programming is what this part of the book is all about, so let's get started. ### Read Mode 0 {#Heading3} Read mode 0 is actually relatively uncomplicated, given that you -understand the four-plane nature of the VGA. (If you don’t understand +understand the four-plane nature of the VGA. (If you don't understand the four-plane nature of the VGA, I strongly urge you to read Chapters 23-27 before continuing with this chapter.) Read mode 0, the read mode counterpart of write mode 0, lets you read from one (and only one) plane @@ -52,14 +52,14 @@ a time, so there are only four possible settings of the Read Map register: 0, 1, 2, or 3, to select reads from plane 0, 1, 2, or 3. In write mode 0, by contrast (in fact, in any write mode), any or all planes may be written to at once, since the byte written by the CPU can -“fan out” to multiple planes. Consequently, there are not four but +"fan out" to multiple planes. Consequently, there are not four but sixteen possible settings of the Map Mask register. The setting of the Map Mask register to write only to plane 0 is 1; to write only to plane 1 is 2; to write only to plane 2 is 4; and to write only to plane 3 is 8. As you can see, the settings of the Read Map and Map Mask registers for -accessing a given plane don’t match. The code in Listing 28.1 +accessing a given plane don't match. The code in Listing 28.1 illustrates this. Listing 28.1 simply copies a sixteen-color image from system memory to VGA memory, one plane at a time, then animates by repeatedly copying the image back to system memory, again one plane at a diff --git a/28-03.md b/28-03.md index db0eb7d..9478deb 100644 --- a/28-03.md +++ b/28-03.md @@ -3,38 +3,38 @@ ------------------------ --------------------------------- -------------------- By the way, the code in Listing 28.1 is intended only to illustrate read -mode 0, and is, in general, a poor way to perform animation, since it’s -slow and tends to flicker. Later in this book, we’ll take a look at some +mode 0, and is, in general, a poor way to perform animation, since it's +slow and tends to flicker. Later in this book, we'll take a look at some far better VGA animation techniques. -As you’d expect, neither the read mode nor the setting of the Read Map +As you'd expect, neither the read mode nor the setting of the Read Map register affects CPU *writes* to VGA memory in any way. ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *An important point regarding reading VGA memory involves the VGA’s latches. (Remember that each of the four latches stores a byte for one plane; on CPU writes, the latches can provide some or all of the data written to display memory, allowing fast copying and efficient pixel masking.) Whenever the CPU reads a given address in VGA memory, each of the four latches is loaded with the contents of the byte at that address in its respective plane. Even though the CPU only receives data from one plane in read mode 0, all four planes are always read, and the values read are stored in the latches. This is true in read mode 1 as well. In short, whenever the CPU reads VGA memory in any read mode, all four planes are read and all four latches are always loaded.* + ![](images/i.jpg) *An important point regarding reading VGA memory involves the VGA's latches. (Remember that each of the four latches stores a byte for one plane; on CPU writes, the latches can provide some or all of the data written to display memory, allowing fast copying and efficient pixel masking.) Whenever the CPU reads a given address in VGA memory, each of the four latches is loaded with the contents of the byte at that address in its respective plane. Even though the CPU only receives data from one plane in read mode 0, all four planes are always read, and the values read are stored in the latches. This is true in read mode 1 as well. In short, whenever the CPU reads VGA memory in any read mode, all four planes are read and all four latches are always loaded.* ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### Read Mode 1 {#Heading4} -Read mode 0 is the workhorse read mode, but it’s got an annoying +Read mode 0 is the workhorse read mode, but it's got an annoying limitation: Whenever you want to determine the color of a given pixel in read mode 0, you have to perform four VGA memory reads, one for each -plane, and then interpret the four bytes you’ve read as eight 16-color -pixels. That’s a lot of programming. The code is also likely to run +plane, and then interpret the four bytes you've read as eight 16-color +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 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 +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 in the neighborhood of 10 486/66 cycles.) Read mode 1, also known as *color compare mode*, provides special hardware assistance for determining whether a pixel is a given color. With a single read mode 1 read, you can determine whether each of up to eight pixels is a specific color, and you can even specify any or all -planes as “don’t care” planes in the pixel color comparison. +planes as "don't care" planes in the pixel color comparison. Read mode 1 is selected by setting bit 3 of the Graphics Mode register (Graphics Controller register 5) to 1. In its simplest form, read mode 1 @@ -44,7 +44,7 @@ address to the color value in bits 3-0 of the Color Compare register position of each pixel that matches the color in the Color Compare register and a 0 for each pixel that does not match. -That’s certainly interesting, but what’s read mode 1 good for? One +That's certainly interesting, but what's read mode 1 good for? One obvious application is in implementing flood-fill algorithms, since read mode 1 makes it easy to tell when a given byte contains a pixel of a boundary color. Another application is in detecting on-screen object diff --git a/28-04.md b/28-04.md index 9abb2f0..3fb7e7e 100644 --- a/28-04.md +++ b/28-04.md @@ -166,55 +166,55 @@ code ends end Start -### When all Planes “Don’t Care” {#Heading5} +### When all Planes "Don't Care" {#Heading5} -Still and all, there aren’t all that many uses for basic color compare +Still and all, there aren't all that many uses for basic color compare operations. There is, however, a genuinely odd application of read mode -1 that’s worth knowing about; but in order to understand that, we must -first look at the “don’t care” aspect of color compare operation. +1 that's worth knowing about; but in order to understand that, we must +first look at the "don't care" aspect of color compare operation. As described earlier, during read mode 1 reads the color stored in the Color Compare register is compared to each of the 8 pixels at a given -address in VGA memory. But—and it’s a big but—any plane for which the -corresponding bit in the Color Don’t Care register is a 0 is always +address in VGA memory. But—and it's a big but—any plane for which the +corresponding bit in the Color Don't Care register is a 0 is always considered a color compare match, regardless of the values of that -plane’s bits in the pixels and in the Color Compare register. +plane's bits in the pixels and in the Color Compare register. -Let’s look at this another way. A given pixel is controlled by four -bits, one in each plane. Normally (when the Color Don’t Care register is +Let's look at this another way. A given pixel is controlled by four +bits, one in each plane. Normally (when the Color Don't Care register is 0FH), the color in the Color Compare register is compared to the four bits of each pixel; bit 0 of the Color Compare register is compared to the plane 0 bit of each pixel, bit 1 of the Color Compare register is compared to the plane 1 bit of each pixel, and so on. That is, when the -lower four bits of the Color Don’t Care register are all set to 1, then +lower four bits of the Color Don't Care register are all set to 1, then all four bits of a given pixel must match the Color Compare register in order for a read mode 1 read to return a 1 for that pixel to the CPU. -However, if any bit of the Color Don’t Care register is 0, then the +However, if any bit of the Color Don't Care register is 0, then the corresponding bit of each pixel is unconditionally considered to match the corresponding bit of the Color Compare register. You might think of -the Color Don’t Care register as selecting exactly which planes should +the Color Don't Care register as selecting exactly which planes should matter in a given read mode 1 read. At the extreme, if all bits of the -Color Don’t Care register are 0, then read mode 1 reads will always +Color Don't Care register are 0, then read mode 1 reads will always return 0FFH, since all planes are considered to match all bits of all pixels. -Now, we’re all prone to using tools the “right” way—that is, in the way -in which they were intended to be used. By that token, the Color Don’t +Now, we're all prone to using tools the "right" way—that is, in the way +in which they were intended to be used. By that token, the Color Don't Care register is clearly intended to mask one or more planes out of a -color comparison, and as such, has limited use. However, the Color Don’t -Care register becomes far more interesting in exactly the “extreme” case -described above, where all planes become “don’t care” planes. +color comparison, and as such, has limited use. However, the Color Don't +Care register becomes far more interesting in exactly the "extreme" case +described above, where all planes become "don't care" planes. -Why? Well, as I’ve said, when all planes are “don’t care” planes, read +Why? Well, as I've said, when all planes are "don't care" planes, read mode 1 reads always return 0FFH. Now, when you AND any value with 0FFH, -the value remains unchanged, and that can be awfully handy when you’re +the value remains unchanged, and that can be awfully handy when you're using the bit mask to modify selected pixels in VGA memory. Recall that you must always read VGA memory to load the latches before writing to -VGA memory when you’re using the bit mask. Traditionally, two separate +VGA memory when you're using the bit mask. Traditionally, two separate 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 +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 write VGA memory, since ANDing any value with 0FFH leaves that value diff --git a/28-05.md b/28-05.md index 04b1287..d6520ed 100644 --- a/28-05.md +++ b/28-05.md @@ -3,8 +3,8 @@ ------------------------ --------------------------------- -------------------- 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 +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 in AL, unmodified, to the VGA, where it is used to generate the bit @@ -126,24 +126,24 @@ excellent pixel- and line-drawing code. code ends end Start -I hope I’ve given you a good feel for what color compare mode is and -what it might be used for. Color compare mode isn’t particularly easy to -understand, but it’s not that complicated in actual operation, and it’s +I hope I've given you a good feel for what color compare mode is and +what it might be used for. Color compare mode isn't particularly easy to +understand, but it's not that complicated in actual operation, and it's certainly useful at times; take some time to study the sample code and perform a few experiments of your own, and you may well find useful applications for color compare mode in your graphics code. A final note: The Read Map register has no effect in read mode 1, and -the Color Compare and Color Don’t Care registers have no effect either +the Color Compare and Color Don't Care registers have no effect either in read mode 0 or when writing to VGA memory. And with that, by gosh, -we’re actually done with the basics of accessing VGA memory! +we're actually done with the basics of accessing VGA memory! Not to worry—that still leaves us a slew of interesting VGA topics, including smooth panning and scrolling, the split screen, color -selection, page flipping, and Mode X. And that’s not to mention actual -uses to which the VGA’s hardware can be put, including lines, circles, -polygons, and my personal favorite, animation. We’ve covered a lot of -challenging and rewarding ground—and we’ve only just begun. +selection, page flipping, and Mode X. And that's not to mention actual +uses to which the VGA's hardware can be put, including lines, circles, +polygons, and my personal favorite, animation. We've covered a lot of +challenging and rewarding ground—and we've only just begun. ------------------------ --------------------------------- -------------------- [Previous](28-04.html) [Table of Contents](index.html) [Next](29-01.html) diff --git a/29-01.md b/29-01.md index 0bcac82..6ddb510 100644 --- a/29-01.md +++ b/29-01.md @@ -8,32 +8,32 @@ Chapter 29\ ### Useful Nuggets from the VGA Zen File {#Heading2} -There are a number of VGA graphics topics that aren’t quite involved +There are a number of VGA graphics topics that aren't quite involved enough to warrant their own chapters, yet still cause a fair amount of programmer headscratching—and thus deserve treatment somewhere in this -book. This is the place, and during the course of this chapter we’ll +book. This is the place, and during the course of this chapter we'll touch on saving and restoring 16-color EGA and VGA screens, the 16-out-of-64 colors issue, and techniques involved in reading and writing VGA control registers. -That’s a lot of ground to cover, so let’s get started! +That's a lot of ground to cover, so let's get started! ### Saving and Restoring EGA and VGA Screens {#Heading3} The memory architectures of EGAs and VGAs are similar enough to treat both together in this regard. The basic principle for saving EGA and VGA 16-color graphics screens is astonishingly simple: Write each plane to -disk separately. Let’s take a look at how this works in the EGA’s hi-res +disk separately. Let's take a look at how this works in the EGA's hi-res mode 10H, which provides 16 colors at 640x350. All we need do is enable reads from plane 0 and write the 28,000 bytes of plane 0 that are displayed in mode 10H to disk, then enable reads from plane 1 and write the displayed portion of that plane to disk, and -so on for planes 2 and 3. The result is a file that’s 112,000 (28,000 \* +so on for planes 2 and 3. The result is a file that's 112,000 (28,000 \* 4) bytes long, with the planes stored as four distinct 28,000-byte blocks, as shown in Figure 29.1. -The program shown later on in Listing 29.1 does just what I’ve described +The program shown later on in Listing 29.1 does just what I've described here, putting the screen into mode 10H, putting up some bittext so there is something to save, and creating the 112K file SNAPSHOT.SCR, which contains the visible portion of the mode 10H frame buffer. @@ -41,7 +41,7 @@ contains the visible portion of the mode 10H frame buffer. ![](images/29-01.jpg)\ **Figure 29.1**  *Saving EGA/VGA display memory.* -The only part of Listing 29.1 that’s even remotely tricky is the use of +The only part of Listing 29.1 that's even remotely tricky is the use of the Read Map register (Graphics Controller register 4) to make each of the four planes of display memory readable in turn. The same code is used to write 28,000 bytes of display memory to disk four times, and @@ -52,7 +52,7 @@ back to Figure 29.1; you may also want to reread Chapter 28 to brush up on the operation of the Read Map register in particular and reading EGA and VGA memory in general.) -Of course, we’ll want the ability to restore what we’ve saved, and +Of course, we'll want the ability to restore what we've saved, and Listing 29.2 does this. Listing 29.2 reverses the action of Listing 29.1, selecting mode 10H and then loading 28,000 bytes from SNAPSHOT.SCR into each plane of display memory. The Map Mask register (Sequence @@ -73,20 +73,20 @@ by Listing 29.1 back into the mode 10H frame buffer. DISPLAYED_SCREEN_SIZE equ (640/8)*350 ;# of displayed bytes per plane in a ; hi-res graphics screen ; - stack segment para stack ‘STACK’ + stack segment para stack ‘STACK' db 512 dup (?) stack ends ; - Data segment word ‘DATA’ - SampleText db ‘This is bit-mapped text, drawn in hi-res ’ - db ‘EGA graphics mode 10h.’, 0dh, 0ah, 0ah - db ‘Saving the screen (including this text)...’ - db 0dh, 0ah, ‘$’ - Filename db ‘SNAPSHOT.SCR’,0 ;name of file we’re saving to - ErrMsg1 db ‘*** Couldn’t open SNAPSHOT.SCR ***’,0dh,0ah,‘$’ - ErrMsg2 db ‘*** Error writing to SNAPSHOT.SCR ***’,0dh,0ah,‘$’ - WaitKeyMsg db 0dh, 0ah, ‘Done. Press any key to end...’,0dh,0ah,‘$’ - Handle dw ? ;handle of file we’re saving to + Data segment word ‘DATA' + SampleText db ‘This is bit-mapped text, drawn in hi-res ' + db ‘EGA graphics mode 10h.', 0dh, 0ah, 0ah + db ‘Saving the screen (including this text)...' + db 0dh, 0ah, ‘$' + Filename db ‘SNAPSHOT.SCR',0 ;name of file we're saving to + ErrMsg1 db ‘*** Couldn't open SNAPSHOT.SCR ***',0dh,0ah,‘$' + ErrMsg2 db ‘*** Error writing to SNAPSHOT.SCR ***',0dh,0ah,‘$' + WaitKeyMsg db 0dh, 0ah, ‘Done. Press any key to end...',0dh,0ah,‘$' + Handle dw ? ;handle of file we're saving to Plane db ? ;plane being read Data ends ; @@ -102,7 +102,7 @@ by Listing 29.1 back into the mode 10H frame buffer. ; hi-res graphics mode int 10h ;BIOS video interrupt ; - ; Put up some text, so the screen isn’t empty. + ; Put up some text, so the screen isn't empty. ; mov ah,9 ;DOS print string function mov dx,offset SampleText @@ -121,7 +121,7 @@ by Listing 29.1 back into the mode 10H frame buffer. sub cx,cx ;make it a normal file int 21h mov [Handle],ax ;save the handle - jnc SaveTheScreen ;we’re ready to save if no error + jnc SaveTheScreen ;we're ready to save if no error mov ah,9 ;DOS print string function mov dx,offset ErrMsg1 int 21h ;notify of the error diff --git a/29-02.md b/29-02.md index ae08936..25db739 100644 --- a/29-02.md +++ b/29-02.md @@ -13,16 +13,16 @@ DISPLAYED_SCREEN_SIZE equ (640/8)*350 ;# of displayed bytes per plane in a ; hi-res graphics screen ; - stack segment para stack ‘STACK’ + stack segment para stack ‘STACK' db 512 dup (?) stack ends ; - Data segment word ‘DATA’ - Filename db ‘SNAPSHOT.SCR’,0 ;name of file we’re restoring from - ErrMsg1 db ‘*** Couldn’‘t open SNAPSHOT.SCR ***’,0dh,0ah,‘$’ - ErrMsg2 db ‘*** Error reading from SNAPSHOT.SCR ***’,0dh,0ah,‘$’ - WaitKeyMsg db 0dh, 0ah, ‘Done. Press any key to end...’,0dh,0ah,‘$’ - Handle dw ? ;handle of file we’re restoring from + Data segment word ‘DATA' + Filename db ‘SNAPSHOT.SCR',0 ;name of file we're restoring from + ErrMsg1 db ‘*** Couldn'‘t open SNAPSHOT.SCR ***',0dh,0ah,‘$' + ErrMsg2 db ‘*** Error reading from SNAPSHOT.SCR ***',0dh,0ah,‘$' + WaitKeyMsg db 0dh, 0ah, ‘Done. Press any key to end...',0dh,0ah,‘$' + Handle dw ? ;handle of file we're restoring from Plane db ? ;plane being written Data ends ; @@ -45,7 +45,7 @@ sub al,al ;open for reading int 21h mov [Handle],ax ;save the handle - jnc RestoreTheScreen ;we’re ready to restore if no error + jnc RestoreTheScreen ;we're ready to restore if no error mov ah,9 ;DOS print string function mov dx,offset ErrMsg1 int 21h ;notify of the error @@ -131,11 +131,11 @@ Chapter 28 provides a detailed explanation of the differences between the Read Map and Map Mask registers. Screen saving and restoring is pretty simple, eh? There are a few -caveats, of course, but nothing serious. First, the adapter’s registers +caveats, of course, but nothing serious. First, the adapter's registers must be programmed properly in order for screen saving and restoring to -work. For screen saving, you must be in read mode 0; if you’re in color -compare mode, there’s no telling what bit pattern you’ll save, but it -certainly won’t be the desired screen image. For screen restoring, you +work. For screen saving, you must be in read mode 0; if you're in color +compare mode, there's no telling what bit pattern you'll save, but it +certainly won't be the desired screen image. For screen restoring, you must be in write mode 0, with the Bit Mask register set to 0FFH and Data Rotate register set to 0 (no data rotation and the logical function set to pass the data through unchanged). diff --git a/29-03.md b/29-03.md index 9fafd2a..8a058ff 100644 --- a/29-03.md +++ b/29-03.md @@ -3,27 +3,27 @@ ------------------------ --------------------------------- -------------------- ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *While these requirements are no problem if you’re simply calling a subroutine in order to save an image from your program, they pose a considerable problem if you’re designing a hot-key operated TSR that can capture a screen image at any time. With the EGA specifically, there’s never any way to tell what state the registers are currently in, since the registers aren’t readable. (More on this issue later in this chapter.) As a result, any TSR that sets the Bit Mask to 0FFH, the Data Rotate register to 0, and so on runs the risk of interfering with the drawing code of the program that’s already running.* + ![](images/i.jpg) *While these requirements are no problem if you're simply calling a subroutine in order to save an image from your program, they pose a considerable problem if you're designing a hot-key operated TSR that can capture a screen image at any time. With the EGA specifically, there's never any way to tell what state the registers are currently in, since the registers aren't readable. (More on this issue later in this chapter.) As a result, any TSR that sets the Bit Mask to 0FFH, the Data Rotate register to 0, and so on runs the risk of interfering with the drawing code of the program that's already running.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -What’s the solution? Frankly, the solution is to get VGA-specific. A TSR +What's the solution? Frankly, the solution is to get VGA-specific. A TSR designed for the VGA can simply read out and save the state of the registers of interest, program those registers as needed, save the -screen image, and restore the original settings. From a programmer’s +screen image, and restore the original settings. From a programmer's perspective, readable registers are certainly near the top of the list of things to like about the VGA! The remaining installed base of EGAs is steadily dwindling, and you may be able to ignore it as a market today, -as you couldn’t even a year or two ago. +as you couldn't even a year or two ago. If you are going to write a hi-res VGA version of the screen capture -program, be sure to account for the increased size of the VGA’s mode 12H +program, be sure to account for the increased size of the VGA's mode 12H bit map. The mode 12H (640x480) screen uses 37.5K per plane of display memory, so for mode 12H the displayed screen size equate in Listings 29.1 and 29.2 should be changed to: DISPLAYED_SCREEN_SIZEequ(640/8)*480 -Similarly, if you’re capturing a graphics screen that starts at an +Similarly, if you're capturing a graphics screen that starts at an offset other than 0 in the segment at A000H, you must change the memory offset used by the disk functions to match. You can, if you so desire, read the start offset of the display memory providing the information @@ -39,21 +39,21 @@ read back as a linear block of memory, just like a normal array. While Listings 29.1 and 29.2 are written in assembly, the principles they illustrate apply equally well to high-level languages. In fact, -there’s no need for any assembly at all when saving an EGA/VGA screen, -as long as the high-level language you’re using can perform direct port +there's no need for any assembly at all when saving an EGA/VGA screen, +as long as the high-level language you're using can perform direct port I/O to set up the adapter and can read and write display memory directly. ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *One tip if you’re saving and restoring the screen from a high-level language on an EGA, though: After you’ve completed the save or restore operation, be sure to put any registers that you’ve changed back to their default settings. Some high-level languages (and the BIOS as well) assume that various registers are left in a certain state, so on the EGA it’s safest to leave the registers in their most likely state. On the VGA, of course, you can just read the registers out before you change them, then put them back the way you found them when you’re done.* + ![](images/i.jpg) *One tip if you're saving and restoring the screen from a high-level language on an EGA, though: After you've completed the save or restore operation, be sure to put any registers that you've changed back to their default settings. Some high-level languages (and the BIOS as well) assume that various registers are left in a certain state, so on the EGA it's safest to leave the registers in their most likely state. On the VGA, of course, you can just read the registers out before you change them, then put them back the way you found them when you're done.* ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### 16 Colors out of 64 {#Heading4} How does one produce the 64 colors from which the 16 colors displayed by -the EGA can be chosen? The answer is simple enough: There’s a BIOS +the EGA can be chosen? The answer is simple enough: There's a BIOS function that lets you select the mapping of the 16 possible pixel -values to the 64 possible colors. Let’s lay out a bit of background +values to the 64 possible colors. Let's lay out a bit of background before proceeding, however. The EGA sends pixel information to the monitor on 6 pins. This means @@ -63,27 +63,27 @@ modes Enhanced Color Displaymonitors ignore two of the signals. As a result, in CGA-compatible modes (modes 4, 5, 6, and the 200-scan-line versions of modes 0, 1, 2, and 3) you can select from only 16 colors (although the colors can still be remapped, as described below). If -you’re not hooked up to a monitor capable of displaying 350 scan lines +you're not hooked up to a monitor capable of displaying 350 scan lines (such as the old IBM Color Display), you can never select from more than 16 colors, since those monitors only accept four input signals. For now, -we’ll assume we’re in one of the 350-scan line color modes, a group +we'll assume we're in one of the 350-scan line color modes, a group which includes mode 10H and the 350-scan-line versions of modes 0, 1, 2, and 3. Each pixel comes out of memory (or, in text mode, out of the attribute-handling portion of the EGA) as a 4-bit value, denoting 1 of 16 possible colors. In graphics modes, the 4-bit pixel value is made up -of one bit from each plane, with 8 pixels’ worth of data stored at any +of one bit from each plane, with 8 pixels' worth of data stored at any given byte address in display memory. Normally, we think of the 4-bit -value of a pixel as being that pixel’s color, so a pixel value of 0 is -black, a pixel value of 1 is blue, and so on, as if that’s a built-in +value of a pixel as being that pixel's color, so a pixel value of 0 is +black, a pixel value of 1 is blue, and so on, as if that's a built-in feature of the EGA. Actually, though, the correspondence of pixel values to color is absolutely arbitrary, depending solely on how the colorportion of the EGA containing the palette registers is programmed. If you cared to have color 0 be bright red and color 1 be black, that could easily be -arranged, as could a mapping in which all 16 colors were yellow. What’s +arranged, as could a mapping in which all 16 colors were yellow. What's more, these mappings affect text-mode characters as readily as they do graphics-mode pixels, so you could map text attribute 0 to white and text attribute 15 to black to produce a black on white display, if you @@ -103,8 +103,8 @@ lower six bits of the corresponding palette register, as shown in Figure 29.3. Secondary red, green, and blue are less-intense versions of red, green, and blue, although their exact effects vary from monitor to monitor. The best way to figure out what the 64 colors look like on your -monitor is to see them, and that’s just what the program in Listing -29.3, which we’ll discuss shortly, lets you do. +monitor is to see them, and that's just what the program in Listing +29.3, which we'll discuss shortly, lets you do. ![](images/29-02.jpg)\ **Figure 29.2**  *Color translation via the palette registers.* diff --git a/29-04.md b/29-04.md index 83e034f..2907736 100644 --- a/29-04.md +++ b/29-04.md @@ -2,13 +2,13 @@ [Previous](29-03.html) [Table of Contents](index.html) [Next](29-05.html) ------------------------ --------------------------------- -------------------- -How does one go about setting the palette registers? Well, it’s +How does one go about setting the palette registers? Well, it's certainly possible to set the palette registers directly by addressing them at registers 0 through 0FH of the Attribute Controller. However, setting the palette registers is a bit tricky—bit 5 of the Attribute Controller Index register must be 0 while the palette registers are -written to, and glitches can occur if the updating doesn’t take place -during the blanking interval—and besides, it turns out that there’s no +written to, and glitches can occur if the updating doesn't take place +during the blanking interval—and besides, it turns out that there's no need at all to go straight to the hardware on this one. Conveniently, the EGA BIOS provides us with video function 10H, which supports setting either any one palette register or all 16 palette registers (and the @@ -21,7 +21,7 @@ to. If AL is 1 (subfunction 1), then BH contains the value to set the overscan (border) color to. Finally, if AL is 2 (subfunction 2), then ES:DX points to a 17-byte array containing the values to set palette registers 0-15 and the overscan register to. (For completeness, although -it’s unrelated to the palette registers, there is one more subfunction +it's unrelated to the palette registers, there is one more subfunction of video function 10H. If AL = 3 (subfunction 3), bit 0 of BL is set to 1 to cause bit 7 of text attributes to select blinking, or set to 0 to cause bit 7 of text attributes to select highreverse video.) @@ -44,16 +44,16 @@ produce high-intensity colors. Palette register 6 is set to 14H to produce brown, rather than the yellow that the expected value of 6H would produce.) -When you run Listing 29.3, you’ll see that the whole screen changes +When you run Listing 29.3, you'll see that the whole screen changes color as each new color set is selected. This occurs because most of the pixels on the screen have a value of 0, selecting the background color -stored in palette register 0, and we’re reprogramming palette register 0 +stored in palette register 0, and we're reprogramming palette register 0 right along with the other 15 palette registers. -It’s important to understand that in Listing 29.3 the contents of +It's important to understand that in Listing 29.3 the contents of display memory are never changed after initialization. The only change is the mapping from the 4-bit pixel data coming out of display memory to -the 6-bit data going to the monitor. For this reason, it’s technically +the 6-bit data going to the monitor. For this reason, it's technically inaccurate to speak of bits in display memory as representing colors; more accurately, they represent attributes in the range 0-15, which are mapped to colors 0-3FH by the palette registers. @@ -61,7 +61,7 @@ mapped to colors 0-3FH by the palette registers. **LISTING 29.3 L29-3.ASM** ; Program to illustrate the color mapping capabilities of the - ; EGA’s palette registers. + ; EGA's palette registers. ; VGA_SEGMENT equ 0a000h SC_INDEX equ 3c4h ;Sequence Controller Index register @@ -70,16 +70,16 @@ mapped to colors 0-3FH by the palette registers. TOP_BAR equ BAR_HEIGHT*6 ;start the bars down a bit to ; leave room for text ; - stack segment para stack ‘STACK’ + stack segment para stack ‘STACK' db 512 dup (?) stack ends ; - Data segment word ‘DATA’ - KeyMsg db ‘Press any key to see the next color set. ’ - db ‘There are 64 color sets in all.’ + Data segment word ‘DATA' + KeyMsg db ‘Press any key to see the next color set. ' + db ‘There are 64 color sets in all.' db 0dh, 0ah, 0ah, 0ah, 0ah - db 13 dup (‘ ’), ‘Attribute’ - db 38 dup (‘ ’), ‘Color$’ + db 13 dup (‘ '), ‘Attribute' + db 38 dup (‘ '), ‘Color$' ; ; Used to label the attributes of the color bars. ; @@ -87,27 +87,27 @@ mapped to colors 0-3FH by the palette registers. x= 0 rept 16 if x lt 10 - db ‘0’, x+‘0’, ‘h’, 0ah, 8, 8, 8 + db ‘0', x+‘0', ‘h', 0ah, 8, 8, 8 else - db ‘0’, x+‘A’-10, ‘h’, 0ah, 8, 8, 8 + db ‘0', x+‘A'-10, ‘h', 0ah, 8, 8, 8 endif x= x+1 endm - db ‘$’ + db ‘$' ; ; Used to label the colors of the color bars. (Color values are ; filled in on the fly.) ; ColorNumberslabelbyte rept 16 - db ‘000h’, 0ah, 8, 8, 8, 8 + db ‘000h', 0ah, 8, 8, 8, 8 endm COLOR_ENTRY_LENGTHequ($-ColorNumbers)/16 - db ‘$’ + db ‘$' ; CurrentColordb? ; - ; Space for the array of 16 colors we’ll pass to the BIOS, plus + ; Space for the array of 16 colors we'll pass to the BIOS, plus ; an overscan setting of black. ; ColorTable db 16 dup (?), 0 @@ -133,9 +133,9 @@ mapped to colors 0-3FH by the palette registers. int 21h ; ; Put up the color bars, one in each of the 16 possible pixel values - ; (which we’ll call attributes). + ; (which we'll call attributes). ; - mov cx,16 ;we’ll put up 16 color bars + mov cx,16 ;we'll put up 16 color bars sub al,al ;start with attribute 0 BarLoop: push ax @@ -259,10 +259,10 @@ mapped to colors 0-3FH by the palette registers. BinToHexDigit proc near cmp al,9 ja IsHex - add al,‘0’ + add al,‘0' ret IsHex: - add al,‘A’-10 + add al,‘A'-10 ret BinToHexDigit endp ; @@ -281,7 +281,7 @@ mapped to colors 0-3FH by the palette registers. mov al,[CurrentColor] ;start with the current color mov bx,offset ColorNumbers+1 ;build color number text string on the fly - mov cx,16 ;we’ve got 16 colors to do + mov cx,16 ;we've got 16 colors to do ColorNumberLoop: pus hax;save the color # and al,3fh;limit to 6-bit color values diff --git a/29-05.md b/29-05.md index 26f9cb4..c3f582e 100644 --- a/29-05.md +++ b/29-05.md @@ -4,16 +4,16 @@ ### Overscan {#Heading5} -While we’re at it, I’m going to touch on overscan. Overscan is the color +While we're at it, I'm going to touch on overscan. Overscan is the color of the border of the display, the rectangular area around the edge of -the monitor that’s outside the region displaying active video data but +the monitor that's outside the region displaying active video data but inside the blanking area. The overscan (or border) color can be programmed to any of the 64 possible colors by either setting Attribute Controller register 11H directly or calling video function 10H, subfunction 1. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *On ECD-compatible monitors, however, there’s too little scan time to display a proper border when the EGA is in 350-scan-line mode, so overscan should always be 0 (black) unless you’re in 200-scanmode. Note, though, that a VGA can easily display a border on a VGA-compatible monitor, and VGAs are in fact programmed at mode set for an 8-pixel-wide border in all modes; all you need do is set the overscan color on any VGA to see the border.* + ![](images/i.jpg) *On ECD-compatible monitors, however, there's too little scan time to display a proper border when the EGA is in 350-scan-line mode, so overscan should always be 0 (black) unless you're in 200-scanmode. Note, though, that a VGA can easily display a border on a VGA-compatible monitor, and VGAs are in fact programmed at mode set for an 8-pixel-wide border in all modes; all you need do is set the overscan color on any VGA to see the border.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ### A Bonus Blanker {#Heading6} @@ -44,16 +44,16 @@ screen blanking. int 21h endm ; - stack segment para stack ‘STACK’ + stack segment para stack ‘STACK' db512 dup (?) stack ends ; - Data segment word ‘DATA’ - SampleText db ‘This is bit-mapped text, drawn in hi-res ’ - db ‘EGA graphics mode 10h.’, 0dh, 0ah, 0ah - db ‘Press any key to blank the screen, then ’ - db ‘any key to unblank it,’, 0dh, 0ah - db ‘then any key to end.$’ + Data segment word ‘DATA' + SampleText db ‘This is bit-mapped text, drawn in hi-res ' + db ‘EGA graphics mode 10h.', 0dh, 0ah, 0ah + db ‘Press any key to blank the screen, then ' + db ‘any key to unblank it,', 0dh, 0ah + db ‘then any key to end.$' Data ends ; Code segment @@ -68,7 +68,7 @@ screen blanking. ; hi-res graphics mode int 10h ;BIOS video interrupt ; - ; Put up some text, so the screen isn’t empty. + ; Put up some text, so the screen isn't empty. ; mov ah,9 ;DOS print string function mov dx,offset SampleText diff --git a/29-06.md b/29-06.md index c582216..0cb2d01 100644 --- a/29-06.md +++ b/29-06.md @@ -2,27 +2,27 @@ [Previous](29-05.html) [Table of Contents](index.html) [Next](30-01.html) ------------------------ --------------------------------- -------------------- -Does that do it for color selection? Yes and no. For the EGA, we’ve +Does that do it for color selection? Yes and no. For the EGA, we've covered the whole of color selection—but not so for the VGA. The VGA can -emulate everything we’ve discussed, but actually performs one 4-bit to +emulate everything we've discussed, but actually performs one 4-bit to 8-bit translation (except in 256-color modes, where all 256 colors are simultaneously available), followed by yet another translation, this one -8-bit to 18-bit. What’s more, the VGA has the ability to flip instantly -through as many as 16 16-color sets. The VGA’s color selection +8-bit to 18-bit. What's more, the VGA has the ability to flip instantly +through as many as 16 16-color sets. The VGA's color selection capabilities, which are supported by another set of BIOS functions, can -be used to produce stunning color effects, as we’ll see when we cover +be used to produce stunning color effects, as we'll see when we cover them starting in Chapter 33. ### Modifying VGA Registers {#Heading7} EGA registers are not readable. VGA registers are readable. This revelation will not come as news to most of you, but many programmers -still insist on setting entire VGA registers even when they’re modifying +still insist on setting entire VGA registers even when they're modifying only selected bits, as if they were programming the EGA. This comes to mind because I recently received a query inquiring why write mode 1 (in which the contents of the latches are copied directly to display memory) -didn’t work in Mode X. (I’ll go into Mode X in detail later in this -book.) Actually, write mode 1 does work in Mode X; it didn’t work when +didn't work in Mode X. (I'll go into Mode X in detail later in this +book.) Actually, write mode 1 does work in Mode X; it didn't work when this particular correspondent enabled it because he did so by writing the value 01H to the Graphics Mode register. As it happens, the write mode field is only one of several fields in that register, as shown in @@ -45,29 +45,29 @@ setting the VGA to write mode 1, do this: out dx,al ;set write mode 1 This approach is more of a nuisance than simply setting the whole -register, but it’s safer. It’s also slower; for cases where you must set +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 slower than memory accesses on 386 and 486 machines. -Astute readers may wonder why I didn’t put a delay sequence, such as +Astute readers may wonder why I didn't put a delay sequence, such as **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 -a 286 or faster CPU can access a port. My answer is that while I can’t -guarantee that a delay isn’t needed, I’ve never found a VGA that +a 286 or faster CPU can access a port. My answer is that while I can't +guarantee that a delay isn't needed, I've never found a VGA that required one; I suspect that the delay specification has more to do with motherboard chips such as the timer, the interrupt controller, and the -like, and I sure hate to waste the delay time if it’s not necessary. -However, I’ve never been able to find anyone with the definitive word on +like, and I sure hate to waste the delay time if it's not necessary. +However, I've never been able to find anyone with the definitive word on whether delays might ever be needed when accessing VGAs, so if you know the gospel truth, or if you know of a VGA/processor combo that does require delays, please let me know by contacting me through the -publisher. You’d be doing a favor for a whole generation of graphics -programmers who aren’t sure whether they’re skating on thin ice without +publisher. You'd be doing a favor for a whole generation of graphics +programmers who aren't sure whether they're skating on thin ice without those legendary delays. ![](images/29-04.jpg)\ diff --git a/30-01.md b/30-01.md index 1fb52d6..177966e 100644 --- a/30-01.md +++ b/30-01.md @@ -13,18 +13,18 @@ one—displayed above the other on the screen—is one of the more intriguing capabilities of the VGA and EGA. The split screen feature can be used for popups (including popups that slide smoothly onto the screen), or simply to display two separate portions of display memory on -a single screen. While it’s possible to accomplish the same effects +a single screen. While it's possible to accomplish the same effects purely in software without using the split screen, software solutions tend to be slow and hard to implement. By contrast, the basic operation of the split screen is fairly simple, once you grasp the various coding tricks required to pull it off, and -understand the limitations and pitfalls—like the fact that the EGA’s +understand the limitations and pitfalls—like the fact that the EGA's split screen implementation is a little buggy. Furthermore, panning with the split screen enabled is not as simple as it might seem. All in all, we do have some ground to cover. -Let’s start with the basic operation of the split screen. +Let's start with the basic operation of the split screen. ### How the Split Screen Works {#Heading3} @@ -45,7 +45,7 @@ start address registers, and the whole process starts over. The net effect: The contents of display memory starting at offset zero are displayed starting at the scan line following the specified split -screen start scan line, as shown in Figure 30.1. It’s important to +screen start scan line, as shown in Figure 30.1. It's important to understand that the scan line that matches the split screen scan line is *not* part of the split screen; the split screen starts on the *following* scan line. So, for example, if the split screen scan line is @@ -62,7 +62,7 @@ at least one scan line short. **Figure 30.1**  *Display memory and the split screen.* So, where is the split screen start scan line stored? The answer varies -a bit, depending on whether you’re talking about the EGA or the VGA. On +a bit, depending on whether you're talking about the EGA or the VGA. On the EGA, the split screen start scan line is a 9-bit value, with bits 7-0 stored in the Line Compare register (CRTC register 18H) and bit 8 stored in bit 4 of the Overflow register (CRTC register 7). Other bits @@ -81,9 +81,9 @@ Scan Line register (CRTC register 9). Turning the split screen on involves nothing more than setting all bits of the split screen start scan line to the scan line after which you -want the split screen to start appearing. (Of course, you’ll probably +want the split screen to start appearing. (Of course, you'll probably want to change the start address before using the split screen; -otherwise, you’ll just end up displaying the memory at offset zero +otherwise, you'll just end up displaying the memory at offset zero *twice:* once in the normal screen and once in the split screen.) Turning off the split screen is a simple matter of setting the split screen start scan line to a value equal to or greater than the last scan @@ -107,11 +107,11 @@ Listing 30.1 then slides the split screen up from the bottom of the screen, one scan line at a time. The split screen slides halfway up the screen, bounces down a quarter of the screen, advances another half-screen, drops another quarter-screen, and finally slides all the -way up to the top. If you’ve never seen the split screen in action, you +way up to the top. If you've never seen the split screen in action, you should run Listing 30.1; the smooth overlapping of the split screen on top of the normal display is a striking effect. -Listing 30.1 isn’t done just yet, however. After a keypress, Listing +Listing 30.1 isn't done just yet, however. After a keypress, Listing 30.1 demonstrates how to turn the split screen off (by setting all bits of the split screen start scan line to 1). After another keypress, Listing 30.1 shows that the split screen can never cover the whole @@ -119,10 +119,10 @@ screen, by setting the start address to 0 and then flipping back and forth between the normal screen and the split screen with a split screen start scan line setting of zero. Both the normal screen and the split screen display the same text, but the split screen displays it one scan -line lower, because the split screen doesn’t start until *after* the +line lower, because the split screen doesn't start until *after* the first scan line, and that produces a jittering effect as the program switches the split screen on and off. (On the EGA, the split screen may -display *two* scan lines lower, for reasons I’ll discuss shortly.) +display *two* scan lines lower, for reasons I'll discuss shortly.) ------------------------ --------------------------------- -------------------- [Previous](29-06.html) [Table of Contents](index.html) [Next](30-02.html) diff --git a/30-03.md b/30-03.md index e6aabf7..ddd3694 100644 --- a/30-03.md +++ b/30-03.md @@ -2,7 +2,7 @@ [Previous](30-02.html) [Table of Contents](index.html) [Next](30-04.html) ------------------------ --------------------------------- -------------------- -#### VGA and EGA Split-Screen Operation Don’t Mix {#Heading5} +#### VGA and EGA Split-Screen Operation Don't Mix {#Heading5} 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 @@ -16,19 +16,19 @@ 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. -The VGA version of Listing 30.1 won’t work on an EGA, because EGA -registers aren’t readable. The EGA version of Listing 30.1 won’t work on +The VGA version of Listing 30.1 won't work on an EGA, because EGA +registers aren't readable. The EGA version of Listing 30.1 won't work on a VGA, both because VGA monitors require different vertical settings -than EGA monitors and because the EGA version doesn’t set bit 9 of the +than EGA monitors and because the EGA version doesn't set bit 9 of the split screen start scan line. In short, there is no way that I know of to support both VGA and EGA split screens with common code; separate drivers are required. This is one of the reasons that split screens are so rarely used in PC programming. -By the way, Listing 30.1 operates in mode 10H because that’s the -highest-resolution mode the VGA and EGA share. That’s not the only mode +By the way, Listing 30.1 operates in mode 10H because that's the +highest-resolution mode the VGA and EGA share. That's not the only mode the split screen works in, however. In fact, it works in *all* modes, as -we’ll see later. +we'll see later. ### Setting the Split-Screen-Related Registers {#Heading6} @@ -50,24 +50,24 @@ because the changed screen can appear *before* the new split screen start scan line is set. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Remember, the split screen start scan line is spread out over two or three registers. What if the incompletely-changed value matches the current scan line after you’ve set one register but before you’ve set the rest? For one frame, you’ll see the split screen in a wrong place—possibly a very wrong place—resulting in jumping and flicker.* + ![](images/i.jpg) *Remember, the split screen start scan line is spread out over two or three registers. What if the incompletely-changed value matches the current scan line after you've set one register but before you've set the rest? For one frame, you'll see the split screen in a wrong place—possibly a very wrong place—resulting in jumping and flicker.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ The solution is simple: Set the split screen start scan line at a time -when it can’t possibly match the currently displayed scan line. The easy -way to do that is to set it when there isn’t any currently displayed -scan line—during vertical non-display time. One safe time that’s easy to +when it can't possibly match the currently displayed scan line. The easy +way to do that is to set it when there isn't any currently displayed +scan line—during vertical non-display time. One safe time that's easy to find is the start of the vertical sync pulse, which is typically pretty -near the middle of vertical non-display time, and that’s the approach -I’ve followed in Listing 30.1. I’ve also disabled interrupts during the -period when the split screen registers are being set. This isn’t -absolutely necessary, but if it’s not done, there’s the possibility that +near the middle of vertical non-display time, and that's the approach +I've followed in Listing 30.1. I've also disabled interrupts during the +period when the split screen registers are being set. This isn't +absolutely necessary, but if it's not done, there's the possibility that an interrupt will occur between register sets and delay the later register sets until display time, again causing flicker. One interesting effect of setting the split screen registers at the start of vertical sync is that it has the effect of synchronizing the -program to the display adapter’s frame rate. No matter how fast the +program to the display adapter's frame rate. No matter how fast the computer running Listing 30.1 may be, the split screen will move at a maximum rate of once per frame. This is handy for regulating execution speed over a wide variety of hardware performance ranges; however, be @@ -77,7 +77,7 @@ modes support 60 Hz frame rates. ### The Problem with the EGA Split Screen {#Heading7} -I mentioned earlier that the EGA’s split screen is a little buggy. How? +I mentioned earlier that the EGA's split screen is a little buggy. How? you may well ask, particularly given that Listing 30.1 illustrates that the EGA split screen seems pretty functional. @@ -87,8 +87,8 @@ twice. In other words, the first line of split screen display memory, and only the first line, is replicated one unnecessary time, pushing all the other lines down by one. -That’s not a fatal bug, of course. In fact, if the first few scan lines -are identical, it’s not even noticeable. The EGA’s split-screen bug can +That's not a fatal bug, of course. In fact, if the first few scan lines +are identical, it's not even noticeable. The EGA's split-screen bug can produce visible distortion given certain patterns, however, so you should try to make the top few lines identical (if possible) when designing split-screen images that might be displayed on EGAs, and you @@ -96,13 +96,13 @@ should in any case check how your split-screens look on both VGAs and EGAs. ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *I have an important caution here: Don’t count on the EGA’s split-screen bug; that is, don’t rely on the first scan line being doubled when you design your split screens. IBM designed and made the original EGA, but a lot of companies cloned it, and there’s no guarantee that all EGA clones copy the bug. It is a certainty, at least, that the VGA didn’t copy it.* + ![](images/i.jpg) *I have an important caution here: Don't count on the EGA's split-screen bug; that is, don't rely on the first scan line being doubled when you design your split screens. IBM designed and made the original EGA, but a lot of companies cloned it, and there's no guarantee that all EGA clones copy the bug. It is a certainty, at least, that the VGA didn't copy it.* ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -There’s another respect in which the EGA is inferior to the VGA when it -comes to the split screen, and that’s in the area of panning when the -split screen is on. This isn’t a bug—it’s just one of the many areas in -which the VGA’s designers learned from the shortcomings of the EGA and +There's another respect in which the EGA is inferior to the VGA when it +comes to the split screen, and that's in the area of panning when the +split screen is on. This isn't a bug—it's just one of the many areas in +which the VGA's designers learned from the shortcomings of the EGA and went the EGA one better. ------------------------ --------------------------------- -------------------- diff --git a/30-04.md b/30-04.md index 5ab6d52..31c58a1 100644 --- a/30-04.md +++ b/30-04.md @@ -9,37 +9,37 @@ horizontal panning. Smooth horizontal panning consists of two parts: byte-by-byte (8-pixel) panning by changing the start address and pixel-by-pixel intrabyte panning by setting the Pel Panning register (AC register 13H) to adjust alignment by 0 to 7 pixels. (IBM prefers its own -jargon and uses the word “pel” instead of “pixel” in much of their -documentation, hence “pel panning.” Then there’s DASD, a.k.a. Direct +jargon and uses the word "pel" instead of "pixel" in much of their +documentation, hence "pel panning." Then there's DASD, a.k.a. Direct Access Storage Device—IBM-speak for hard disk.) -Horizontal smooth panning works just fine, although I’ve always harbored +Horizontal smooth panning works just fine, although I've always harbored some doubts that any one horizontal-smooth-panning approach works -properly on all display board clones. (More on this later.) There’s a +properly on all display board clones. (More on this later.) There's a catch when using horizontal smooth panning with the split screen up, -though, and it’s a serious catch: You can’t byte-pan the split screen +though, and it's a serious catch: You can't byte-pan the split screen (which always starts at offset zero, no matter what the setting of the start address registers)—but you *can* pel-pan the split screen. Put another way, when the normal portion of the screen is horizontally smooth-panned, the split screen portion moves a pixel at a time until -it’s time to move to the next byte, then jumps back to the start of the +it's time to move to the next byte, then jumps back to the start of the current byte. As the top part of the screen moves smoothly about, the split screen will move and jump, move and jump, over and over. Believe -me, it’s not a pretty sight. +me, it's not a pretty sight. ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *What’s to be done? On the EGA, nothing. Unless you’re willing to have your users’ eyes doing the jitterbug, don’t use horizontal smooth scrolling while the split screen is up. Byte panning is fine—just don’t change the Pel Panning register from its default setting.* + ![](images/i.jpg) *What's to be done? On the EGA, nothing. Unless you're willing to have your users' eyes doing the jitterbug, don't use horizontal smooth scrolling while the split screen is up. Byte panning is fine—just don't change the Pel Panning register from its default setting.* ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- On the VGA, there is recourse. A VGA-only bit, bit 5 of the AC Mode Control register (AC register 10H), turns off pel panning in the split screen. In other words, when this bit is set to 1, pel panning is reset to zero before the first line of the split screen, and remains zero -until the end of the frame. This doesn’t allow you to pan the split -screen horizontally, mind you—there’s no way to do that—but it does let +until the end of the frame. This doesn't allow you to pan the split +screen horizontally, mind you—there's no way to do that—but it does let you pan the normal screen while the split screen stays rock-solid. This -can be used to produce an attractive “streaming tape” effect in the +can be used to produce an attractive "streaming tape" effect in the normal screen while the split screen is used to display non-moving information. @@ -54,14 +54,14 @@ memory to leave room for the split screen starting at offset zero, turns on the split screen, and fills in the normal screen and split screen with distinctive patterns. Next, Listing 30.2 pans the normal screen horizontally without setting bit 5 of the AC Mode Control register to 1. -As you’d expect, the split screen jerks about quite horribly. After a +As you'd expect, the split screen jerks about quite horribly. After a 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 +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 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, +panning suppression in the split screen won't work in this version, however, because the EGA lacks the capability to support that feature. When the EGA version runs, the split screen simply jerks back and forth during both panning sessions. diff --git a/30-06.md b/30-06.md index a76e0e4..986a45d 100644 --- a/30-06.md +++ b/30-06.md @@ -28,57 +28,57 @@ reading does not affect the state of the AC index/data toggle. Listing setting the start address registers (CRTC registers 0CH and 0DH) has its complications. As with the split screen registers, the start address registers must be set together and without interruption at a time when -there’s no chance of a partial setting being used for a frame. However, -it’s a little more difficult to know when that might be the case with +there's no chance of a partial setting being used for a frame. However, +it's a little more difficult to know when that might be the case with the start address registers than it was with the split screen registers, -because it’s not clear when the start address is used. +because it's not clear when the start address is used. -You see, the start address is loaded into the EGA’s or VGA’s internal +You see, the start address is loaded into the EGA's or VGA's internal display memory pointer once per frame. The internal pointer is then advanced, byte-by-byte and line-by-line, until the end of the frame (with a possible resetting to zero if the split screen line is reached), -and is then reloaded for the next frame. That’s straightforward enough; +and is then reloaded for the next frame. That's straightforward enough; the real question is, *Exactly when is the start address loaded?* -In his excellent book *Programmer’s Guide to PC Video Systems* +In his excellent book *Programmer's Guide to PC Video Systems* (Microsoft Press) Richard Wilton says that the start address is loaded at the start of the vertical sync pulse. (Wilton calls it vertical retrace, which can also be taken to mean vertical non-display time, but -given that he’s testing the vertical sync status bit in the Input Status +given that he's testing the vertical sync status bit in the Input Status 0 register, I assume he means that the start address is loaded at the start of vertical sync.) Consequently, he waits until the *end* of the vertical sync pulse to set the start address registers, confident that -the start address won’t take effect until the next frame. +the start address won't take effect until the next frame. -I’m sure Richard is right when it comes to the real McCoy IBM VGA and -EGA, but I’m less confident that every clone out there loads the start +I'm sure Richard is right when it comes to the real McCoy IBM VGA and +EGA, but I'm less confident that every clone out there loads the start address at the start of vertical sync. ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *For that very reason, I generally advise people not to use horizontal smooth panning unless they can test their software on all the makes of display adapter it might run on. I’ve used Richard’s approach in Listings 30.1 and 30.2, and so far as I’ve seen it works fine, but be aware that there are potential, albeit unproven, hazards to relying on the setting of the start address registers to occur at a specific time in the frame.* + ![](images/i.jpg) *For that very reason, I generally advise people not to use horizontal smooth panning unless they can test their software on all the makes of display adapter it might run on. I've used Richard's approach in Listings 30.1 and 30.2, and so far as I've seen it works fine, but be aware that there are potential, albeit unproven, hazards to relying on the setting of the start address registers to occur at a specific time in the frame.* ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- The interaction of the start address registers and the Pel Panning register is worthy of note. After waiting for the end of vertical sync to set the start address in Listing 30.2, I wait for the start of the -*next* vertical sync to set the Pel Panning register. That’s because the -start address doesn’t take effect until the start of the next frame, but +*next* vertical sync to set the Pel Panning register. That's because the +start address doesn't take effect until the start of the next frame, but the pel panning setting takes effect at the start of the next line; if -we set the pel panning at the same time we set the start address, we’d +we set the pel panning at the same time we set the start address, we'd get a whole frame with the old start address and the new pel panning settings mixed together, causing the screen to jump. As with the split -screen registers, it’s safest to set the Pel Panning register during -non-display time. For maximum reliability, we’d have interrupts off from +screen registers, it's safest to set the Pel Panning register during +non-display time. For maximum reliability, we'd have interrupts off from the time we set the start address registers to the time we change the -pel planning setting, to make sure an interrupt doesn’t come in and +pel planning setting, to make sure an interrupt doesn't come in and cause us to miss the start of a vertical sync and thus get a mismatched pel panning/start address pair for a frame, although for modularity I -haven’t done this in Listing 30.2. (Also, doing so would require +haven't done this in Listing 30.2. (Also, doing so would require disabling interrupts for much too long a time.) What if you wanted to pan faster? Well, you could of course just move two pixels at a time rather than one; I assure you no one will ever -notice when you’re panning at a rate of 10 or more times per second. +notice when you're panning at a rate of 10 or more times per second. ------------------------ --------------------------------- -------------------- [Previous](30-05.html) [Table of Contents](index.html) [Next](30-07.html) diff --git a/30-07.md b/30-07.md index 1790012..37f7be2 100644 --- a/30-07.md +++ b/30-07.md @@ -4,34 +4,34 @@ ### Split Screens in Other Modes {#Heading11} -So far we’ve only discussed the split screen in mode 10H. What about +So far we've only discussed the split screen in mode 10H. What about other modes? Generally, the split screen works in any mode; the basic rule is that when a scan line on the screen matches the split screen -scan line, the internal display memory pointer is reset to zero. I’ve +scan line, the internal display memory pointer is reset to zero. I've found this to be true even in oddball modes, such as line-doubled CGA modes and the 320x200 256-color mode (which is really a 320x400 mode with each line repeated. For split-screen purposes, the VGA and EGA seem to count purely in scan lines, not in rows or doubled scan lines or the like. However, I have run into small anomalies in those modes on clones, -and I haven’t tested all modes (nor, lord knows, all clones!) so be +and I haven't tested all modes (nor, lord knows, all clones!) so be careful when using the split screen in modes other than modes 0DH-12H, and test your code on a variety of hardware. Come to think of it, I warn you about the hazards of running fancy VGA -code on clones pretty often, don’t I? Ah, well—just one of the hazards +code on clones pretty often, don't I? Ah, well—just one of the hazards of the diversity and competition of the PC market! It is a fact of life, -though—if you’re a commercial developer and don’t test your video code -on at least half a dozen VGAs, you’re living dangerously. +though—if you're a commercial developer and don't test your video code +on at least half a dozen VGAs, you're living dangerously. What of the split screen in text mode? It works fine; in fact, it not only resets the internal memory pointer to zero, but also resets the -text scan line counter—which marks which line within the font you’re +text scan line counter—which marks which line within the font you're on—to zero, so the split screen starts out with a full row of text. -There’s only one trick with text mode: When split screen pel panning +There's only one trick with text mode: When split screen pel panning suppression is on, the pel panning setting is forced to 0 for the rest -of the frame. Unfortunately, 0 is *not* the “no-panning” setting for +of the frame. Unfortunately, 0 is *not* the "no-panning" setting for 9-dot-wide text; 8 is. The result is that when you turn on split screen -pel panning suppression, the text in the split screen won’t pan with the +pel panning suppression, the text in the split screen won't pan with the normal screen, as intended, but will also display the undesirable characteristic of moving one pixel to the left. Whether this causes any noticeable on-screen effects depends on the text displayed by a @@ -40,17 +40,17 @@ split screen has a border of blanks on the left side. ### How Safe? {#Heading12} -So, how safe *is* it to use the split screen? My opinion is that it’s -perfectly safe, although I’d welcome input from people with extensive +So, how safe *is* it to use the split screen? My opinion is that it's +perfectly safe, although I'd welcome input from people with extensive split screen experience—and the effects are striking enough that the split screen is well worth using in certain applications. -I’m a little more leery of horizontal smooth scrolling, with or without -the split screen. Still, the Wilton book doesn’t advise any particular -caution, and I haven’t heard any horror stories from the field lately, +I'm a little more leery of horizontal smooth scrolling, with or without +the split screen. Still, the Wilton book doesn't advise any particular +caution, and I haven't heard any horror stories from the field lately, so the clone manufacturers must finally have gotten it right. (I vividly -remember some early clones years back that *didn’t* quite get it right.) -So, on balance, I’d say to use horizontal smooth scrolling if you really +remember some early clones years back that *didn't* quite get it right.) +So, on balance, I'd say to use horizontal smooth scrolling if you really need it; on the other hand, in fast animation you can often get away with byte scrolling, which is easier, faster, and safer. (I recently saw a game that scrolled as smoothly as you could ever want. It was only by diff --git a/31-01.md b/31-01.md index dd397e1..6217a09 100644 --- a/31-01.md +++ b/31-01.md @@ -17,53 +17,53 @@ are by no means a standard, and differences between seemingly identical modes from different manufacturers can be vexing.) More colors can often compensate for less resolution, but the resolution difference between the 640x480 16-color mode and the 320x200 256-color mode is so great -that many programmers must regretfully decide that they simply can’t +that many programmers must regretfully decide that they simply can't afford to use the 256-color mode. -If there’s one thing we’ve learned about the VGA, however, it’s that -there’s *never* just one way to do things. With the VGA, alternatives -always exist for the clever programmer, and that’s more true than you +If there's one thing we've learned about the VGA, however, it's that +there's *never* just one way to do things. With the VGA, alternatives +always exist for the clever programmer, and that's more true than you might imagine with 256-color mode. Not only is there a high 256-color resolution, there are *lots* of higher 256-color resolutions, going all -the way up to 360x480—and that’s with the vanilla IBM VGA! +the way up to 360x480—and that's with the vanilla IBM VGA! -In this chapter, I’m going to focus on one of my favorite 256-color +In this chapter, I'm going to focus on one of my favorite 256-color modes, which provides 320x400 resolution and two graphics pages and can -be set up with very little reof the VGA. In the next chapter, I’ll +be set up with very little reof the VGA. In the next chapter, I'll discuss higher-resolution 256-color modes, and starting in Chapter 47, -I’ll cover the high-performance “Mode X” 256-color programming that many +I'll cover the high-performance "Mode X" 256-color programming that many games use. -So. Let’s get started. +So. Let's get started. ### Why 320x200? Only IBM Knows for Sure {#Heading3} -The first question, of course, is, “How can it be possible to get higher -256-color resolutions out of the VGA?” After all, there were no unused +The first question, of course, is, "How can it be possible to get higher +256-color resolutions out of the VGA?" After all, there were no unused higher resolutions to be found in the CGA, Hercules card, or EGA. -The answer is another question: “Why did IBM *not* use the -higher-resolution 256-color modes of the VGA?” The VGA is easily capable +The answer is another question: "Why did IBM *not* use the +higher-resolution 256-color modes of the VGA?" The VGA is easily capable of twice the 200-scan-line vertical resolution of mode 13H, the 256-color mode, and IBM clearly made a decision not to support a higher-resolution 256-color mode. In fact, mode 13H *does* display 400 scan lines, but each row of pixels is displayed on two successive scan lines, resulting in an effective resolution of 320x200. This is the same -scan-doubling approach used by the VGA to convert the CGA’s +scan-doubling approach used by the VGA to convert the CGA's 200-scan-line modes to 400 scan lines; however, the resolution of the CGA has long been fixed at 200 scan lines, so IBM had no choice with the CGA modes but to scan-double the lines. Mode 13H has no such historical -limitation—it’s the first 256-color mode ever offered by IBM, if you -don’t count the late and unlamented Professional Graphics Controller +limitation—it's the first 256-color mode ever offered by IBM, if you +don't count the late and unlamented Professional Graphics Controller (PGC). Why, then, would IBM choose to limit the resolution of mode 13H? -There’s no way to know, but one good guess is that IBM wanted a standard +There's no way to know, but one good guess is that IBM wanted a standard 256-color mode across all PS/2 computers (for which the VGA was originally created), and mode 13H is the highest-resolution 256-color mode that could fill the bill. You see, each 256-color pixel requires one byte of display memory, so a 320x200 256-color mode requires 64,000 -bytes of display memory. That’s no problem for the VGA, which has 256K -of display memory, but it’s a stretch for the MCGA of the Model 30, +bytes of display memory. That's no problem for the VGA, which has 256K +of display memory, but it's a stretch for the MCGA of the Model 30, since the MCGA comes with only 64K. On the other hand, the smaller display memory size of the MCGA also @@ -71,9 +71,9 @@ limits the number of colors supported in 640x480 mode to 2, rather than the 16 supported by the VGA. In this case, though, IBM simply created two modes and made both available on the VGA: mode 11H for 640x480 2-color graphics and mode 12H for 640x480 16-color graphics. The same -could have been done for 256-color graphics—but wasn’t. Why? I don’t -know. Maybe IBM just didn’t like the odd aspect ratio of a 320x400 -graphics mode. Maybe they didn’t want to have to worry about how to map +could have been done for 256-color graphics—but wasn't. Why? I don't +know. Maybe IBM just didn't like the odd aspect ratio of a 320x400 +graphics mode. Maybe they didn't want to have to worry about how to map in more than 64K of display memory. Heck, maybe they made a mistake in designing the chip. Whatever the reason, mode 13H is really a 400-scan-line mode masquerading as a 200-scan-line mode, and we can @@ -81,12 +81,12 @@ readily end that masquerade. ### 320x400 256-Color Mode {#Heading4} -Okay, what’s so great about 320x400 256-color mode? Two things: easy, +Okay, what's so great about 320x400 256-color mode? Two things: easy, safe mode sets and page flipping. As I said above, mode 13H is really a 320x400 mode, albeit with each line doubled to produce an effective resolution of 320x200. That means -that we don’t need to change any display timings, widths, or heights in +that we don't need to change any display timings, widths, or heights in order to tweak mode 13H into 320x400 mode—and that makes 320x400 a safe choice. Basically, 320x400 mode differs from mode 13H only in the settings of *mode* bits, which are sure to be consistent from one VGA @@ -105,19 +105,19 @@ supported. Each 320x400 256-color mode requires 128,000 bytes of display memory, so we can just barely manage two pages in 320x400 mode, one starting at offset 0 in display memory and the other starting at offset 8000H. Those two pages are the largest pair of pages that can fit in the -VGA’s 256K, though, and the higher-resolution 256-color modes, which use +VGA's 256K, though, and the higher-resolution 256-color modes, which use still larger bitmaps (areas of display memory that control pixels on the -screen), can’t support two pages at all. As we’ve seen in earlier +screen), can't support two pages at all. As we've seen in earlier chapters and will see again in this book, paging is very useful for off-screen construction of images and fast, smooth animation. -That’s why I like 320x400 256-color mode. The next step is to understand -how display memory is organized in 320x400 mode, and that’s not so +That's why I like 320x400 256-color mode. The next step is to understand +how display memory is organized in 320x400 mode, and that's not so simple. #### Display Memory Organization in 320x400 Mode {#Heading5} -First, let’s look at why display memory must be organized differently in +First, let's look at why display memory must be organized differently in 320x400 256-color mode than in mode 13H. The designers of the VGA intentionally limited the maximum size of the bitmap in mode 13H to 64K, thereby limiting resolution to 320x200. This was accomplished *in diff --git a/31-02.md b/31-02.md index 72254d9..08a065f 100644 --- a/31-02.md +++ b/31-02.md @@ -2,12 +2,12 @@ [Previous](31-01.html) [Table of Contents](index.html) [Next](31-03.html) ------------------------ --------------------------------- -------------------- -That’s a shame, because mode 13H has the simplest bitmap organization of +That's a shame, because mode 13H has the simplest bitmap organization of any mode—one long, linear bitmap, with each byte controlling one pixel. -We can’t have that organization, though, so we’ll have to find an +We can't have that organization, though, so we'll have to find an acceptable substitute if we want to use a higher 256-color resolution. -We’re talking about the VGA, so of course there are actually *several* +We're talking about the VGA, so of course there are actually *several* bitmap organizations that let us use higher 256-color resolutions than mode 13H. The one I like best is shown in Figure 31.1. Each byte controls one 256-color pixel. Pixel 0 is at address 0 in plane 0, pixel @@ -15,10 +15,10 @@ controls one 256-color pixel. Pixel 0 is at address 0 in plane 0, pixel 3 is at address 0 in plane 3, pixel 4 is at address 1 in plane 0, and so on. -Let’s look at this another way. Ideally, we’d like one long bitmap, with -each pixel at the address that’s just after the address of the pixel to -the left. Well, that’s true in this case too, *if* you consider the -number of the plane that the pixel is in to be part of the pixel’s +Let's look at this another way. Ideally, we'd like one long bitmap, with +each pixel at the address that's just after the address of the pixel to +the left. Well, that's true in this case too, *if* you consider the +number of the plane that the pixel is in to be part of the pixel's address. View the pixel numbers on the screen as increasing from left to right and from the end of one scan line to the start of the next. Then the pixel number, n, of the pixel at display memory address *address* in @@ -43,30 +43,30 @@ Basically, the full address of the pixel, its pixel number, is broken into two components: the display memory address and the plane. By the way, because 320x400 mode has a significantly different memory -organization from mode 13H, the BIOS text routines won’t work in 320x400 -mode. If you want to draw text in 320x400 mode, you’ll have to look up a +organization from mode 13H, the BIOS text routines won't work in 320x400 +mode. If you want to draw text in 320x400 mode, you'll have to look up a font in the BIOS ROM and draw the text yourself. Likewise, the BIOS read -pixel and write pixel routines won’t work in 320x400 mode, but that’s no -problem because I’ll provide equivalent routines in the next section. +pixel and write pixel routines won't work in 320x400 mode, but that's no +problem because I'll provide equivalent routines in the next section. -Our next task is to convert standard mode 13H into 320x400 mode. That’s +Our next task is to convert standard mode 13H into 320x400 mode. That's accomplished by undoing some of the mode bits that are set up especially for mode 13H, so that from a programming perspective the VGA reverts to 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 +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 discuss next. #### Reading and Writing Pixels {#Heading6} The basic graphics functions in any mode are functions to read and write single pixels. Any more complex function can be built on these -primitives, although that’s rarely the speediest solution. What’s more, +primitives, although that's rarely the speediest solution. What's more, once you understand the operation of the read and write pixel functions, -you’ve got all the knowledge you need to create functions that perform -more complex graphics functions. Consequently, we’ll start our +you've got all the knowledge you need to create functions that perform +more complex graphics functions. Consequently, we'll start our exploration of 320x400 mode with pixel-at-a-time line drawing. Listing 31.1 draws 8 multicolored octagons in turn, drawing a new one on diff --git a/31-04.md b/31-04.md index f204915..0772df3 100644 --- a/31-04.md +++ b/31-04.md @@ -6,16 +6,16 @@ The interesting aspects of Listing 31.1 are three. First, the **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 -memory as well. It’s necessary to clear display memory even after a mode +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** subroutine, which draws a colored pixel at any *x,y* addressable -location on the screen. Although it may not be obvious because I’ve +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 -remarkably simple. First, the pixel’s display memory address is +remarkably simple. First, the pixel's display memory address is calculated as *address*=(*y* \* (SCREEN\_WIDTH / 4)) + (*x* / 4) @@ -25,7 +25,7 @@ which might be more recognizable as: *address*=((*y* \* SCREEN\_WIDTH) + *x*) / 4 (There are 4 pixels at each display memory address in 320x400 mode, -hence the division by 4.) Then the pixel’s plane is calculated as +hence the division by 4.) Then the pixel's plane is calculated as *plane*=*x* and 3 @@ -33,24 +33,24 @@ which is equivalent to: *plane*=*x* modulo 4 -The pixel’s color is then written to the addressed byte in the addressed -plane. That’s all there is to it! +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 register. Of course, that difference merely reflects a fundamental -difference in the operation of the two registers. (If that’s Greek to +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. How does 320x400 256-color mode stack up as regards performance? As it turns out, the programming model of 320x400 mode is actually pretty good for pixel drawing, pretty much on a par with the model of mode 13H. When -you run Listing 31.1, you’ll no doubt notice that the lines are drawn +you run Listing 31.1, you'll no doubt notice that the lines are drawn quite rapidly. (In fact, the drawing could be considerably faster still with a dedicated line-drawing subroutine, which would avoid the multiplication associated with each pixel in Listing 31.1.) @@ -74,20 +74,20 @@ 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 -requires. If you’re really into performance, you could store your images +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** 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, 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 +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 high-performance programming in 256-color modes other than mode 13H -(“non-chain 4” modes) in Chapter 47. +("non-chain 4" modes) in Chapter 47. -It’s all a bit complicated, but as I say, you should be able to design +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 +whatever graphics function you need. If you're not all that concerned with speed, **WritePixel** and **ReadPixel** should meet your needs. ### Two 256-Color Pages {#Heading7} @@ -100,7 +100,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 50f83e9..28bb3ab 100644 --- a/31-05.md +++ b/31-05.md @@ -281,7 +281,7 @@ 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, 4000H, 8000H, and 0C000H in display memory. For another, having only half as many pixels per screen can as much as double drawing speeds; -that’s one reason that many games run at 320x200, and even then often +that's one reason that many games run at 320x200, and even then often limit the active display drawing area to only a portion of the screen. ------------------------ --------------------------------- -------------------- diff --git a/32-01.md b/32-01.md index 773e884..e6c5f53 100644 --- a/32-01.md +++ b/32-01.md @@ -11,31 +11,31 @@ Chapter 32\ In the last chapter, we learned how to coax 320x400 256-color resolution out of a standard VGA. At the time, I noted that the VGA was actually capable of supporting 256-color resolutions as high as 360x480, but -didn’t pursue the topic further, preferring to concentrate on the +didn't pursue the topic further, preferring to concentrate on the versatile and easy-to-set 320x400 256-color mode instead. Some time back I was sent a particularly useful item from John Bridges, a longtime correspondent and an excellent programmer. It was a complete mode set routine for 360x480 256-color mode that he has placed into the -public domain. In addition, John wrote, “I also have a couple of +public domain. In addition, John wrote, "I also have a couple of freeware (free, but not public domain) utilities out there, including PICEM, which displays PIC, PCX, and GIF images not only in 360x480x256 but also in 640x350x256, 640x400x256, 640x480x256, and 800x600x256 on -SuperVGAs.” +SuperVGAs." -In this chapter, I’m going to combine John’s mode set code with +In this chapter, I'm going to combine John's mode set code with appropriately modified versions of the dot-plot code from Chapter 31 and -the line-drawing code that we’ll develop in Chapter 35. Together, those +the line-drawing code that we'll develop in Chapter 35. Together, those routines will make a pretty nifty demo of the capabilities of 360x480 256-color mode. -### Extended 256-Color Modes: What’s Not to Like? {#Heading3} +### Extended 256-Color Modes: What's Not to Like? {#Heading3} When last we left 256-color programming, we had found that the standard 256-color mode, mode 13H, which officially offers 320x200 resolution, actually displays 400, not 200, scan lines, with line-doubling used to reduce the effective resolution to 320x200. By tweaking a few of the -VGA’s mode registers, we converted mode 13H to a true 320x400 256-color +VGA's mode registers, we converted mode 13H to a true 320x400 256-color mode. As an added bonus, that 320x400 mode supports two graphics pages, a distinct improvement over the single graphics page supported by mode 13H. (We also learned how to get *four* graphics pages at 320x200 @@ -43,7 +43,7 @@ resolution, should that be needed.) I particularly like 320x400 256-color mode for two reasons: It supports two-page graphics, which is very important for animation applications; -and it doesn’t require changing any of the monitor timing +and it doesn't require changing any of the monitor timing characteristics of the VGA. The mode bits that we changed to produce 320x400 256-color mode are pretty much guaranteed to be the same from one VGA to another, but the monitor-oriented registers are less certain @@ -54,7 +54,7 @@ All in all, those are good arguments for 320x400 256-color mode. However, the counter-argument seems compelling as well—nothing beats higher resolution for producing striking graphics. Given that, and given that John Bridges was kind enough to make his mode set code available, -I’m going to look at 360x480 256mode next. However, bear in mind that +I'm going to look at 360x480 256mode next. However, bear in mind that the drawbacks of this mode are the flip side of the strengths of 320x400 256-color mode: Only one graphics page, and direct setting of the monitorregisters. Also, this mode has a peculiar and unique aspect @@ -64,52 +64,52 @@ resolution and sometimes-jagged drawing; on the other hand, the resolution is better in both directions than in mode 13H, and mode 13H itself has an odd aspect ratio, so it seems a bit petty to complain. -The single graphics page isn’t a drawback if you don’t need page -flipping, of course, so there’s not much to worry about there: If you -need page flipping, don’t use this mode. The direct setting of the +The single graphics page isn't a drawback if you don't need page +flipping, of course, so there's not much to worry about there: If you +need page flipping, don't use this mode. The direct setting of the monitor-oriented registers is another matter altogether. -I don’t know how likely this code is to produce problems with clone VGAs +I don't know how likely this code is to produce problems with clone VGAs in general; however, I did find that I had to put an older Video Seven -VRAM VGA into “pure” mode—where it treats the VRAMs as DRAMs and exactly +VRAM VGA into "pure" mode—where it treats the VRAMs as DRAMs and exactly emulates a plain-vanilla IBM VGA—before 360x480 256-color mode would work properly. Now, that particular problem was due to an inherent -characteristic of VRAMs, and shouldn’t occur on Video Seven’s Fastwrite +characteristic of VRAMs, and shouldn't occur on Video Seven's Fastwrite adapter or any other VGA clone. Nonetheless, 360x480 256-color mode is a good deal different from any standard VGA mode, and while the code in this chapter runs perfectly well on all other VGAs in my experience, I -can’t guarantee its functionality on any particular VGA/monitor +can't guarantee its functionality on any particular VGA/monitor combination, unlike 320x400 256-color mode. Mind you, 360x480 256-color mode *should* work on all VGAs—there are just too many variables involved for me to be certain. Feedback from readers with broad 360x480 256-color experience is welcome. The above notwithstanding, 360x480 256-color mode offers 64 times as -many colors and nearly three times as many pixels as IBM’s original CGA +many colors and nearly three times as many pixels as IBM's original CGA color graphics mode, making startlingly realistic effects possible. No mode of the VGA (at least no mode that I know of!), documented or undocumented, offers a better combination of resolution and color; even 320x400 256-color mode has 26 percent fewer pixels. -In other words, 360x480 256-color mode is worth considering—so let’s +In other words, 360x480 256-color mode is worth considering—so let's have a look. ### 360x480 256-Color Mode {#Heading4} -I’m going to start by showing you 360x480 256-color mode in action, -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. +I'm going to start by showing you 360x480 256-color mode in action, +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 -the specified location. (This last function isn’t actually used in the +the specified location. (This last function isn't actually used in the example program in this chapter, but is included for completeness.) -Listing 32.2 contains an adaptation of some C linecode I’ll be -presenting shortly in Chapter 35. If you’re reading this book in serial -fashion and haven’t gotten there yet, simply take it on faith. If you +Listing 32.2 contains an adaptation of some C linecode I'll be +presenting shortly in Chapter 35. If you're reading this book in serial +fashion and haven't gotten there yet, simply take it on faith. If you really *really* need to know how the line-draw code works right *now*, by all means make a short forward call to Chapter 35 and digest it. The line-draw code presented below has been altered to select 360x480 diff --git a/32-02.md b/32-02.md index b427d1b..a954ccb 100644 --- a/32-02.md +++ b/32-02.md @@ -22,10 +22,10 @@ READ_MAP equ 4 ;Read Map register index in GC SCREEN_WIDTH equ 360 ;# of pixels across screen WORD_OUTS_OK equ 1 ;set to 0 to assemble for - ; computers that can’t handle + ; computers that can't handle ; word outs to indexed VGA registers ; - _DATAsegmentpublic byte ‘DATA’ + _DATAsegmentpublic byte ‘DATA' ; ; 360x480 256-color mode CRT Controller register settings. ; (Courtesy of John Bridges.) @@ -65,7 +65,7 @@ endif endm ; - _TEXTsegment byte public ‘CODE’ + _TEXTsegment byte public ‘CODE' assumecs:_TEXT, ds:_DATA ; ; Sets up 360x480 256-color mode. @@ -136,7 +136,7 @@ ; public _Draw360x480Dot _Draw360x480Dotprocnear - push bp ;preserve caller’s BP + push bp ;preserve caller's BP mov bp,sp ;point to stack frame push si ;preserve C register vars push di @@ -150,7 +150,7 @@ mov di,[bp+DrawX] ;get the X coordinate shr di,1 ;there are 4 pixels at each address shr di,1 ; so divide the X coordinate by 4 - add di,ax ;point to the pixel’s address + add di,ax ;point to the pixel's address mov cl,byte ptr [bp+DrawX] ;get the X coordinate again and cl,3 ;get the plane # of the pixel mov ah,1 @@ -164,7 +164,7 @@ stosb ;draw the pixel pop di ;restore C register vars pop si - pop bp ;restore caller’s BP + pop bp ;restore caller's BP ret _Draw360x480Dotendp ; @@ -184,7 +184,7 @@ ; public _Read360x480Dot _Read360x480Dotprocnear - push bp ;preserve caller’s BP + push bp ;preserve caller's BP mov bp,sp ;point to stack frame push si ;preserve C register vars push di @@ -198,7 +198,7 @@ mov si,[bp+DrawX] ;get the X coordinate shr si,1 ;there are 4 pixels at each address shr si,1 ; so divide the X coordinate by 4 - add si,ax ;point to the pixel’s address + add si,ax ;point to the pixel's address mov ah,byte ptr [bp+DrawX] ;get the X coordinate again and ah,3 ;get the plane # of the pixel @@ -210,7 +210,7 @@ sub ah,ah ;make the return value a word for C pop di ;restore C register vars pop si - pop bp ;restore caller’s BP + pop bp ;restore caller's BP ret _Read360x480Dot endp _TEX Tends diff --git a/32-03.md b/32-03.md index 30deac5..0b5eba1 100644 --- a/32-03.md +++ b/32-03.md @@ -48,7 +48,7 @@ /* Draw the line */ Draw360x480Dot(X0, Y0, Color); /* draw the first pixel */ while ( DeltaX-- ) { - /* See if it’s time to advance the Y coordinate */ + /* See if it's time to advance the Y coordinate */ if ( ErrorTerm >= 0 ) { /* Advance the Y coordinate & adjust the error term back down */ @@ -85,7 +85,7 @@ Draw360x480Dot(X0, Y0, Color);/* draw the first pixel */ while ( DeltaY-- ) { - /* See if it’s time to advance the X coordinate */ + /* See if it's time to advance the X coordinate */ if ( ErrorTerm >= 0 ) { /* Advance the X coordinate & adjust the error term back down */ @@ -198,19 +198,19 @@ VectorsUp(X_MAX * 3 / 4, Y_MAX * 3 / 4, X_MAX / 4, Y_MAX / 4, 4); /* Wait for the enter key to be pressed */ - scanf(“%c”, &temp); + scanf("%c", &temp); /* Back to text mode */ _AX = TEXT_MODE; geninterrupt(BIOS_VIDEO_INT); } -The first thing you’ll notice when you run this code is that the speed +The first thing you'll notice when you run this code is that the speed of 360x480 256-color mode is pretty good, especially considering that most of the program is im-plemented in C. ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Drawing in 360x480 256-color mode can sometimes actually be faster than in the 16-color modes, because the byte-per-pixel display memory organization of 256-color mode eliminates the need to read display memory before writing to it in order to isolate individual pixels coexisting within a single byte. In addition, 360x480 256-color mode is a variant of Mode X, which we’ll encounter in detail in Chapter 47, and supports all the high-performance features of Mode X.* + ![](images/i.jpg) *Drawing in 360x480 256-color mode can sometimes actually be faster than in the 16-color modes, because the byte-per-pixel display memory organization of 256-color mode eliminates the need to read display memory before writing to it in order to isolate individual pixels coexisting within a single byte. In addition, 360x480 256-color mode is a variant of Mode X, which we'll encounter in detail in Chapter 47, and supports all the high-performance features of Mode X.* ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------ --------------------------------- -------------------- diff --git a/32-04.md b/32-04.md index 2ab4e12..1f68ec6 100644 --- a/32-04.md +++ b/32-04.md @@ -2,7 +2,7 @@ [Previous](32-03.html) [Table of Contents](index.html) [Next](32-05.html) ------------------------ --------------------------------- -------------------- -The second thing you’ll notice is that exquisite shading effects are +The second thing you'll notice is that exquisite shading effects are possible in 360x480 256-color mode; adjacent lines blend together remarkably smoothly, even with the default palette. The VGA allows you to select your 256 colors from a palette of 256K, so you could, if you @@ -20,34 +20,34 @@ pixels as the entire mode 13H screen; in all, 360x480 256-color mode has 2.7 times as many pixels as mode 13H! As mentioned above, the resolution is unevenly distributed, with vertical resolution matching that of mode 12H but horizontal resolution barely exceeding that of mode 13H—but -resolution is hot stuff, no matter how it’s laid out, and 360x480 -256-color mode has the highest 256-color resolution you’re ever likely +resolution is hot stuff, no matter how it's laid out, and 360x480 +256-color mode has the highest 256-color resolution you're ever likely to see on a standard VGA. (SuperVGAs are quite another matter—but when -you *require* a SuperVGA you’re automatically excluding what might be a +you *require* a SuperVGA you're automatically excluding what might be a significant chunk of the market for your code.) -Now that we’ve seen the wonders of which our new mode is capable, let’s +Now that we've seen the wonders of which our new mode is capable, let's take the time to understand how it works. ### How 360x480 256-Color Mode Works {#Heading5} -In describing 360x480 256-color mode, I’m going to assume that you’re +In describing 360x480 256-color mode, I'm going to assume that you're familiar with the discussion of 320x400 256-color mode in the last chapter. If not, go back to that chapter and read it; the two modes have -a great deal in common, and I’m not going to bore you by repeating +a great deal in common, and I'm not going to bore you by repeating myself when the goods are just a few page flips (the paper kind) away. 360x480 256-color mode is essentially 320x400 256-color mode, but -stretched in both dimensions. Let’s look at the vertical stretching -first, since that’s the simpler of the two. +stretched in both dimensions. Let's look at the vertical stretching +first, since that's the simpler of the two. #### 480 Scan Lines per Screen: A Little Slower, But No Big Deal {#Heading6} -There’s nothing unusual about 480 scan lines; standard modes 11H and 12H +There's nothing unusual about 480 scan lines; standard modes 11H and 12H support that vertical resolution. The number of scan lines has nothing to do with either the number of colors or the horizontal resolution, so converting 320x400 256mode to 320x480 256-color mode is a simple matter -of reprogramming the VGA’s vertical control registers—which control the +of reprogramming the VGA's vertical control registers—which control the scan lines displayed, the vertical sync pulse, vertical blanking, and the total number of scan lines—to the 480-scansettings, and setting the polarities of the horizontal and vertical sync pulses to tell the @@ -60,31 +60,31 @@ rate slows to 60 Hz. (VGA monitors always scan at the same rate horizontally; that is, the distance across the screen covered by the electron beam in a given period of time is the same in all modes. Consequently, adding extra lines per frame requires extra time.) 60 Hz -isn’t *bad*—that’s the only refresh rate the EGA ever supported, and the +isn't *bad*—that's the only refresh rate the EGA ever supported, and the EGA was the industry standard in its time—but it does tend to flicker a little more and so is a little harder on the eyes than 70 Hz. #### 360 Pixels per Scan Line: No Mean Feat {#Heading7} Converting from 320 to 360 pixels per scan line is more difficult than -converting from 400 to 480 scan lines per screen. None of the VGA’s +converting from 400 to 480 scan lines per screen. None of the VGA's graphics modes supports 360 pixels across the screen, or anything like it; the standard choices are 320 and 640 pixels across. However, the VGA *does* support the horizontal resolution we seek—360 pixels—in 40-column *text* mode. Unfortunately, the register settings that select those horizontal -resolutions aren’t directly transferable to graphics mode. Text modes +resolutions aren't directly transferable to graphics mode. Text modes display 9 dots (the width of one character) for each time information is fetched from display memory, while graphics modes display just 4 or 8 -dots per display memory fetch. (Although it’s a bit confusing, it’s +dots per display memory fetch. (Although it's a bit confusing, it's standard terminology to refer to the interval required for one display -memory fetch as a “character,” and I’ll follow that terminology from now +memory fetch as a "character," and I'll follow that terminology from now on.) Consequently, both modes display either 40 or 80 characters per scan line; the only difference is that text modes display more pixels -per character. Given that graphics modes *can’t* display 9 dots per -character (there’s only enough information for eight 16pixels or four -256-color pixels in each memory fetch, and that’s that), we’d seem to be +per character. Given that graphics modes *can't* display 9 dots per +character (there's only enough information for eight 16pixels or four +256-color pixels in each memory fetch, and that's that), we'd seem to be at an impasse. ------------------------ --------------------------------- -------------------- diff --git a/32-05.md b/32-05.md index 848b9e1..4c68e3a 100644 --- a/32-05.md +++ b/32-05.md @@ -13,20 +13,20 @@ clocks on board, and one of those clocks is just sufficiently faster than the other clock so that an extra 80 (or 40) pixels can be displayed on each scan line. -In other words, there’s a slow clock (about 25 MHz) that’s usually used +In other words, there's a slow clock (about 25 MHz) that's usually used in graphics modes to get 640 (or 320) pixels on the screen during each -scan line, and a second, fast clock (about 28 MHz) that’s usually used +scan line, and a second, fast clock (about 28 MHz) that's usually used in text modes to crank out 720 (or 360) pixels per scan line. In particular, 320x400 256-color mode uses the 25 MHz clock. -I’ll bet that you can see where I’m headed: We can switch from the 25 +I'll bet that you can see where I'm headed: We can switch from the 25 MHz clock to the 28 MHz clock in 320x480 256mode in order to get more -pixels. It takes two clocks to produce one 256-color pixel, so we’ll get +pixels. It takes two clocks to produce one 256-color pixel, so we'll get 40 rather than 80 extra pixels by doing this, bringing our horizontal resolution to the desired 360 pixels. -Switching horizontal resolutions sounds easy, doesn’t it? Alas, it’s -not. There’s no standard VGA mode that uses the 28 MHz clock to draw 8 +Switching horizontal resolutions sounds easy, doesn't it? Alas, it's +not. There's no standard VGA mode that uses the 28 MHz clock to draw 8 rather than 9 dots per character, so the timing parameters have to be calculated from scratch. John Bridges has already done that for us, but I want you to appreciate that producing this mode took some work. The @@ -38,7 +38,7 @@ next, and the clock speed all have to be altered in order to set up does all that, and sets up the registers that control vertical resolution, as well. -Once all that’s done, the VGA is in 360x480 mode, awaiting our every +Once all that's done, the VGA is in 360x480 mode, awaiting our every high-resolution 256-color graphics whim. #### Accessing Display Memory in 360x480 256-Color Mode {#Heading8} @@ -80,25 +80,25 @@ The other programming difference between the two modes is that the area of display memory mapped to the screen is longer in 360x480 256-color mode, which is only common sense given that there are more pixels in that mode. The exact amount of memory required in 360x480 256-color mode -is 360 times 480 = 172,800 bytes. That’s more than half of the VGA’s 256 -Kb memory complement, so page-flipping is out; however, there’s no -reason you couldn’t use that extra memory to create a virtual screen +is 360 times 480 = 172,800 bytes. That's more than half of the VGA's 256 +Kb memory complement, so page-flipping is out; however, there's no +reason you couldn't use that extra memory to create a virtual screen larger than 360x480, around which you could then scroll, if you wish. -That’s really all there is to drawing in 360x480 256-color mode. From a +That's really all there is to drawing in 360x480 256-color mode. From a programming perspective, this mode is no more complicated than 320x400 256-color mode once the mode set is completed, and should be capable of -good performance given some clever coding. It’s not particular +good performance given some clever coding. It's not particular straightforward to implement bitblt, block move, or fast line-drawing code for any of the extended 256-color modes, but it can be done—and -it’s worth the trouble. Even the small taste we’ve gotten of the +it's worth the trouble. Even the small taste we've gotten of the capabilities of these modes shows that they put the traditional CGA, EGA, and generally even VGA modes to shame. ![](images/32-01.jpg)\ **Figure 32.1**  *Pixel organization in 360x480 256-color mode.* -There’s more and better to come, though; in later chapters, we’ll return +There's more and better to come, though; in later chapters, we'll return to high-resolution 256-color programming in a big way, by exploring the tremendous potential of these modes for real time 2-D and 3-D animation. diff --git a/33-01.md b/33-01.md index c17659c..621b079 100644 --- a/33-01.md +++ b/33-01.md @@ -8,49 +8,49 @@ Chapter 33\ ### The Basics of VGA Color Generation {#Heading2} -Kevin Mangis wants to know about the VGA’s 4-bit to 8-bit to 18-bit +Kevin Mangis wants to know about the VGA's 4-bit to 8-bit to 18-bit color translation. Mansur Loloyan would like to find out how to generate a look-up table containing 256 colors and how to change the default color palette. And surely they are only the tip of the iceberg; hordes of screaming programmers from every corner of the planet are no doubt tearing the place up looking for a discussion of VGA color, and venting -their frustration at my mailbox. *Let’s have it*, they’ve said, clearly +their frustration at my mailbox. *Let's have it*, they've said, clearly and in considerable numbers. As Eurythmics might say, who is this humble writer to disagree? -On the other hand, I hope you all know what you’re getting into. To +On the other hand, I hope you all know what you're getting into. To paraphrase Yogi, the VGA is smarter (and more confusing) than the -average board. There’s the basic 8-bit to 18-bit translation, there’s -the EGA-compatible 4-bit to 6-bit translation, there’s the 2- or 4-bit -color paging register that’s used to pad 6- or 4-bit pixel values out to -8 bits, and then there’s 256-color mode. Fear not, it will all make +average board. There's the basic 8-bit to 18-bit translation, there's +the EGA-compatible 4-bit to 6-bit translation, there's the 2- or 4-bit +color paging register that's used to pad 6- or 4-bit pixel values out to +8 bits, and then there's 256-color mode. Fear not, it will all make sense in the end, but it may take us a couple of additional chapters to -get there—so let’s get started. +get there—so let's get started. -Before we begin, though, I must refer you to Michael Covington’s -excellent article, “Color Vision and the VGA,” in the June/July 1990 +Before we begin, though, I must refer you to Michael Covington's +excellent article, "Color Vision and the VGA," in the June/July 1990 issue of *PC TECHNIQUES*. Michael, one of the most brilliant people it has ever been my pleasure to meet, is an expert in many areas I know nothing about, including linguistics and artificial intelligence. Add to that list the topic of color perception, for his article superbly describes the mechanisms by which we perceive color and ties that -information to the VGA’s capabilities. After reading Michael’s article, -you’ll understand what colors the VGA is capable of generating, and why. +information to the VGA's capabilities. After reading Michael's article, +you'll understand what colors the VGA is capable of generating, and why. -Our topic in this chapter complements Michael’s article nicely. Where he -focused on color perception, we’ll focus on color generation; that is, +Our topic in this chapter complements Michael's article nicely. Where he +focused on color perception, we'll focus on color generation; that is, the ways in which the VGA can be programmed to generate those colors -that lie within its capabilities. To find out why a VGA can’t generate -as pure a red as an LED, read Michael’s article. If you want to find out -how to flip between 16 different sets of 16 colors, though, don’t touch +that lie within its capabilities. To find out why a VGA can't generate +as pure a red as an LED, read Michael's article. If you want to find out +how to flip between 16 different sets of 16 colors, though, don't touch that dial! -I would be remiss if I didn’t point you in the direction of two more -articles, these in the July 1990 issue of *Dr. Dobb’s Journal*. “Super -VGA Programming,” by Chris Howard, provides a good deal of useful -information about SuperVGA chipsets, modes, and programming. “Circles -and the Digital Differential Analyzer,” by Tim Paterson, is a good -article about fast circle drawing, a topic we’ll tackle soon. All in +I would be remiss if I didn't point you in the direction of two more +articles, these in the July 1990 issue of *Dr. Dobb's Journal*. "Super +VGA Programming," by Chris Howard, provides a good deal of useful +information about SuperVGA chipsets, modes, and programming. "Circles +and the Digital Differential Analyzer," by Tim Paterson, is a good +article about fast circle drawing, a topic we'll tackle soon. All in all, the dog days of 1990 were good times for graphics. ### VGA Color Basics {#Heading3} @@ -58,7 +58,7 @@ all, the dog days of 1990 were good times for graphics. Briefly put, the VGA color translation circuitry takes in one 4- or 8-bit pixel value at a time and translates it into three 6-bit values, one each of red, green, and blue, that are converted to corresponding -analog levels and sent to the monitor. Seems simple enough, doesn’t it? +analog levels and sent to the monitor. Seems simple enough, doesn't it? Unfortunately, nothing is ever that simple on the VGA, and color translation is no exception. @@ -79,9 +79,9 @@ sent out by the palette RAM. (The operation of the palette RAM was described back in Chapter 29.) The process is much the same in text mode, except that in text mode each -4-bit pixel value is generated based on the character’s font pattern and -attribute. In 256-color mode, which we’ll get to eventually, the palette -RAM is not a factor from the programmer’s perspective and should be left +4-bit pixel value is generated based on the character's font pattern and +attribute. In 256-color mode, which we'll get to eventually, the palette +RAM is not a factor from the programmer's perspective and should be left alone. #### The DAC {#Heading5} @@ -92,24 +92,24 @@ sent to the DAC (Digital/Analog Converter). The DAC performs an 8-bit to 18-bit conversion in much the same manner as the palette RAM, converts the 18-bit result to analog red, green, and blue signals (6 bits for each signal), and sends the three analog signals to the monitor. The DAC -is a separate chip, external to the VGA chip, but it’s an integral part +is a separate chip, external to the VGA chip, but it's an integral part of the VGA standard and is present on every VGA. ![](images/33-01.jpg)\ **Figure 33.1**  *The VGA color generation path.* -(I’d like to take a moment to point out that you can’t speak of “color” +(I'd like to take a moment to point out that you can't speak of "color" at any point in the color translation process until the output stage of the DAC. The 4-bit pixel values in memory, 6-bit values in the palette RAM, and 8-bit values sent to the DAC are all attributes, not colors, -because they’re subject to translation by a later stage. For example, a -pixel with a 4-bit value of 0 isn’t black, it’s attribute 0. It will be -translated to 3FH if palette RAM register 0 is set to 3FH, but that’s +because they're subject to translation by a later stage. For example, a +pixel with a 4-bit value of 0 isn't black, it's attribute 0. It will be +translated to 3FH if palette RAM register 0 is set to 3FH, but that's not the color white, just another attribute. The value 3FH coming into -the DAC isn’t white either, and if the value stored in DAC register 63 +the DAC isn't white either, and if the value stored in DAC register 63 is red=7, green=0, and blue=0, the actual *color* displayed for that -pixel that was 0 in display memory will be dim red. It isn’t color until -the DAC says it’s color.) +pixel that was 0 in display memory will be dim red. It isn't color until +the DAC says it's color.) The DAC contains 256 18-bit storage registers, used to translate one of 256 possible 8-bit values into one of 256K (262,144, to be precise) diff --git a/33-02.md b/33-02.md index ca2d751..ee039e1 100644 --- a/33-02.md +++ b/33-02.md @@ -4,8 +4,8 @@ #### Color Paging with the Color Select Register {#Heading6} -“Wait a minute,” you say bemusedly. “Aren’t you missing some bits -between the palette RAM and the DAC?” Indeed I am. The palette RAM puts +"Wait a minute," you say bemusedly. "Aren't you missing some bits +between the palette RAM and the DAC?" Indeed I am. The palette RAM puts out 6 bits at a time, and the DAC takes in 8 bits at a time. The two missing bits—bits 6 and 7 going into the DAC—are supplied by bits 2 and 3 of the Color Select register (Attribute Controller register 14H). This @@ -15,34 +15,34 @@ of 64 attributes. Normally, those 64 attributes look up colors from registers 0 through 63 in the DAC, because bits 2 and 3 of the Color Select register are both zero. By changing the Color Select register, however, one of three other 64 color sets can be selected instantly. -I’ll refer to the process of flipping through color sets in this manner +I'll refer to the process of flipping through color sets in this manner as *color paging*. -That’s interesting, but frankly it seems somewhat half-baked; why bother +That's interesting, but frankly it seems somewhat half-baked; why bother expanding 16 attributes to 64 attributes before looking up the colors in -the DAC? What we’d *really* like is to map the 16 attributes straight +the DAC? What we'd *really* like is to map the 16 attributes straight through the palette RAM without changing them and supply the upper *4* bits going to the DAC from a register, giving us 16 color pages. As it happens, all we have to do to make that happen is set bit 7 of the -Attribute Controller Mode register (register 10H) to 1. Once that’s +Attribute Controller Mode register (register 10H) to 1. Once that's done, bits 0 through 3 of the Color Select register go straight to bits 4 through 7 of the DAC, and only bits 3 through 0 coming out of the palette RAM are used; bits 4 and 5 from the palette RAM are ignored. In this mode, the palette RAM effectively contains 4-bit, rather than -6-bit, registers, but that’s no problem because the palette RAM will be +6-bit, registers, but that's no problem because the palette RAM will be programmed to pass pixel values through unchanged by having register 0 set to 0, register 1 set to 1, and so on, a configuration in which the upper two bits of all the palette RAM registers are the same (zero) and -therefore irrelevant. As a matter of fact, you’ll generally want to set +therefore irrelevant. As a matter of fact, you'll generally want to set the palette RAM to this pass-through state when working with VGA color, -whether you’re using color paging or not. +whether you're using color paging or not. Why is it a good idea to set the palette RAM to a pass-through state? -It’s a good idea because the palette RAM is programmed by the BIOS to +It's a good idea because the palette RAM is programmed by the BIOS to EGA-compatible settings and the first 64 DAC registers are programmed to emulate the 64 colors that an EGA can display during mode sets for 16-color modes. This is done for compatibility with EGA programs, and -it’s useless if you’re going to tinker with the VGA’s colors. As a VGA +it's useless if you're going to tinker with the VGA's colors. As a VGA programmer, you want to take a 4-bit pixel value and turn it into an 18-bit RGB value; you can do that without any help from the palette RAM, and setting the palette RAM to pass-through values effectively takes it @@ -52,16 +52,16 @@ that I know of for VGA-only color programming. #### 256-Color Mode {#Heading7} -So far I’ve spoken only of 16-color modes; what of 256-color modes? +So far I've spoken only of 16-color modes; what of 256-color modes? -The rule in 256-color modes is: *Don’t tinker with the VGA palette*. +The rule in 256-color modes is: *Don't tinker with the VGA palette*. Period. You can select any colors you want by reprogramming the DAC, and -there’s no guarantee as to what will happen if you mess around with the -palette RAM. There’s no benefit that I know of to changing the palette -RAM in 256-color mode, and the effect may vary from VGA to VGA. So don’t -do it unless you know something I don’t. +there's no guarantee as to what will happen if you mess around with the +palette RAM. There's no benefit that I know of to changing the palette +RAM in 256-color mode, and the effect may vary from VGA to VGA. So don't +do it unless you know something I don't. -On the other hand, feel free to alter the DAC settings to your heart’s +On the other hand, feel free to alter the DAC settings to your heart's content in 256-color mode, all the more so because this is the only mode in which all 256 DAC settings can be displayed simultaneously. By the way, the Color Select register and bit 7 of the Attribute Controller @@ -77,8 +77,8 @@ interrupt 10H, function 10H. I strongly recommend using the BIOS interrupt; a clone BIOS may mask incompatibilities with genuine IBM silicon. Such incompatibilities could include anything from flicker to trashing the palette RAM; or they may not exist at all, but why find out -the hard way? My policy is to use the BIOS unless there’s a clear reason -not to do so, and there’s no such reason that I know of in this case. +the hard way? My policy is to use the BIOS unless there's a clear reason +not to do so, and there's no such reason that I know of in this case. When programming specifically for the VGA, the palette RAM needs to be loaded only once, to store the pass-through values 0 through 15 in @@ -91,7 +91,7 @@ bytes pointed to by ES:DX, with ES:DX pointing to the value for register ES:DX+16, which points to the overscan value. The palette RAM registers store 6 bits each, so only the lower 6 bits of each of the first 16 bytes in the 17-byte block are significant. (The Overscan register, -which specifies what’s displayed between the area of the screen that’s +which specifies what's displayed between the area of the screen that's controlled by the values in display memory and the blanked region at the edges of the screen, is an 8-bit register, however.) @@ -100,7 +100,7 @@ Alternatively, any one palette RAM register can be set via subfunction 0 BL contains the number of the palette RAM register to set and the lower 6 bits of BH contain the value to which to set that register. -Having said that, let’s leave the palette RAM behind (presumably in a +Having said that, let's leave the palette RAM behind (presumably in a pass-through state) and move on to the DAC, which is the right place to do color translation on the VGA. diff --git a/33-03.md b/33-03.md index 9f9b4aa..bdf81b9 100644 --- a/33-03.md +++ b/33-03.md @@ -8,19 +8,19 @@ Like the palette RAM, the DAC registers can be set either directly or through the BIOS. Again, the BIOS should be used whenever possible, but there are a few complications here. My experience is that varying degrees of flicker and screen bounce occur on many VGAs when a large -block of DAC registers is set through the BIOS. That’s not a problem +block of DAC registers is set through the BIOS. That's not a problem when the DAC is loaded just once and then left that way, as is the case -in Listing 33.1, which we’ll get to shortly, but it can be a serious -problem when the color set is changed rapidly (“cycled”) to produce +in Listing 33.1, which we'll get to shortly, but it can be a serious +problem when the color set is changed rapidly ("cycled") to produce on-screen effects such as rippling colors. My (limited) experience is -that it’s necessary to program the DAC directly in order to cycle colors +that it's necessary to program the DAC directly in order to cycle colors cleanly, although input from readers who have worked extensively with VGA color is welcome. At any rate, the code in this chapter will use the BIOS to set the DAC, -so I’ll describe the BIOS DAC-setting functions next. Later, I’ll +so I'll describe the BIOS DAC-setting functions next. Later, I'll briefly describe how to set both the palette RAM and DAC registers -directly, and I’ll return to the topic in detail in an upcoming chapter +directly, and I'll return to the topic in detail in an upcoming chapter when we discuss color cycling. An individual DAC register can be set by interrupt 10H, function 10H @@ -39,15 +39,15 @@ consists of three bytes; the first byte is a 6-bit red component, the second byte is a 6-bit green component, and the third byte is a 6-bit blue component, as illustrated by Listing 33.1. -### If You Can’t Call the BIOS, Who Ya Gonna Call? {#Heading10} +### If You Can't Call the BIOS, Who Ya Gonna Call? {#Heading10} Although the palette RAM and DAC registers should be set through the BIOS whenever possible, there are times when the BIOS is not the best choice or even a choice at all; for example, a protected-mode program may not have access to the BIOS. Also, as mentioned earlier, it may be necessary to program the DAC directly when performing color cycling. -Therefore, I’ll briefly describe how to set the palette RAM and DAC -registers directly; in Chapter A on the companion CD-ROM I’ll discuss +Therefore, I'll briefly describe how to set the palette RAM and DAC +registers directly; in Chapter A on the companion CD-ROM I'll discuss programming the DAC directly in more detail. The palette RAM registers are Attribute Controller registers 0 through @@ -59,8 +59,8 @@ to be loaded. Do *not* set bit 5 of the Index register to 1, as you normally would, but rather set bit 5 to 0. Setting bit 5 to 0 allows values to be written to the palette RAM registers, but it also causes the screen to blank, so you should wait for the start of vertical -retrace before loading palette RAM registers if you don’t want the -screen to flicker. (Do you see why it’s easier to go through the BIOS?) +retrace before loading palette RAM registers if you don't want the +screen to flicker. (Do you see why it's easier to go through the BIOS?) Then, write the desired register value to 3C0H, which has now toggled to become the Attribute Controller Data register. Write any desired number of additional register number/register data pairs to 3C0H, then write @@ -69,7 +69,7 @@ of additional register number/register data pairs to 3C0H, then write The process of loading the palette RAM registers depends heavily on the proper sequence being followed; if the Attribute Controller Index register or index/data toggle data gets changed in the middle of the -loading process, you’ll probably end up with a hideous display, or no +loading process, you'll probably end up with a hideous display, or no display at all. Consequently, for maximum safety you may want to disable interrupts while you load the palette RAM, to prevent any sort of interference from a TSR or the like that alters the state of the @@ -80,7 +80,7 @@ set to the DAC Write Index register at 3C8H, then writing three bytes—the 6-bit red component, the 6-bit green component, and the 6-bit blue component, in that order—to the DAC Data register at 3C9H. The DAC Write Index register then autoincrements, so if you write another -three-byte RGB value to the DAC Data register, it’ll go to the next DAC +three-byte RGB value to the DAC Data register, it'll go to the next DAC register, and so on indefinitely; you can set all 256 registers by sending 256\*3 = 768 bytes to the DAC Data Register. @@ -88,13 +88,13 @@ Loading the DAC is just as sequence-dependent and potentially susceptible to interference as is loading the palette, so my personal inclination is to go through the whole process of disabling interrupts, loading the DAC Write Index, and writing a three-byte RGB value -separately for each DAC register; although that doesn’t take advantage +separately for each DAC register; although that doesn't take advantage of the autoincrementing feature, it seems to me to be least susceptible to outside influences. (It would be even better to disable interrupts -for the entire duration of DAC register loading, but that’s much too +for the entire duration of DAC register loading, but that's much too long a time to leave interrupts off.) However, I have no hard evidence to offer in support of my conservative approach to setting the DAC, just -an uneasy feeling, so I’d be most interested in hearing from any +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 @@ -107,12 +107,12 @@ 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 -motherboard chips, but I don’t know how applicable it is when accessing +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. In the meantime, if you can use the BIOS to set the DAC, do so; then you -won’t have to worry about the real and potential complications of +won't have to worry about the real and potential complications of setting the DAC directly. ### An Example of Setting the DAC {#Heading11} @@ -120,12 +120,12 @@ setting the DAC directly. This chapter has gotten about as big as a chapter really ought to be; the VGA color saga will continue in the next few. Quickly, then, Listing 33.1 is a simple example of setting the DAC that gives you a taste of -the spectacular effects that color translation makes possible. There’s +the spectacular effects that color translation makes possible. There's nothing particularly complex about Listing 33.1; it just selects 256-color mode, fills the screen with one-pixel-wide concentric diamonds drawn with sequential attributes, and sets the DAC to produce a smooth gradient of each of the three primary colors and of a mix of red and -blue. Run the program; I suspect you’ll be surprised at the stunning +blue. Run the program; I suspect you'll be surprised at the stunning display this short program produces. Clever color manipulation is perhaps the easiest way to produce truly eye-catching effects on the PC. diff --git a/33-04.md b/33-04.md index e878802..9e95837 100644 --- a/33-04.md +++ b/33-04.md @@ -190,20 +190,20 @@ lines, as discussed in Chapter 42. Finally, note that the border of the screen turns green when Listing 33.1 is run. Listing 33.1 reprograms DAC register 0 to green, and the border attribute (in the Overscan register) happens to be 0, so the -border comes out green even though we haven’t touched the Overscan +border comes out green even though we haven't touched the Overscan register. Normally, attribute 0 is black, causing the border to vanish, but the border is an 8-bit attribute that has to pass through the DAC -just like any other pixel value, and it’s just as subject to DAC color +just like any other pixel value, and it's just as subject to DAC color translation as the pixels controlled by display memory. However, the border color is not affected by the palette RAM or by the Color Select register. In this chapter, we traced the surprisingly complex path by which the VGA turns a pixel value into RGB analog signals headed for the monitor. -In the next chapter and Chapter A on the companion CD-ROM, we’ll look at -some more code that plays with VGA color. We’ll explore in more detail +In the next chapter and Chapter A on the companion CD-ROM, we'll look at +some more code that plays with VGA color. We'll explore in more detail the process of reading and writing the palette RAM and DAC registers, -and we’ll observe color paging and cycling in action. +and we'll observe color paging and cycling in action. ------------------------ --------------------------------- -------------------- [Previous](33-03.html) [Table of Contents](index.html) [Next](34-01.html) diff --git a/34-01.md b/34-01.md index cc3770b..240a26e 100644 --- a/34-01.md +++ b/34-01.md @@ -12,21 +12,21 @@ Sometimes, strange as it may seem, the harder you try, the less you accomplish. Brute force is fine when it suffices, but it does not always suffice, and when it does not, finesse and alternative approaches are called for. Such is the case with rapidly cycling through colors by -repeatedly loading the VGA’s Digital to Analog Converter (DAC). No -matter how much you optimize your code, you just can’t reliably load the +repeatedly loading the VGA's Digital to Analog Converter (DAC). No +matter how much you optimize your code, you just can't reliably load the whole DAC cleanly in a single frame, so you had best find other ways to -use the DAC to cycle colors. What’s more, BIOS support for DAC loading -is so inconsistent that it’s unusable for color cycling; direct loading -through the I/O ports is the only way to go. We’ll see why next, as we +use the DAC to cycle colors. What's more, BIOS support for DAC loading +is so inconsistent that it's unusable for color cycling; direct loading +through the I/O ports is the only way to go. We'll see why next, as we explore color cycling, and then finish up this chapter and this section by cleaning up some odds and ends about VGA color. -There’s a lot to be said about loading the DAC, so let’s dive right in +There's a lot to be said about loading the DAC, so let's dive right in and see where the complications lie. ### Color Cycling {#Heading3} -As we’ve learned in past chapters, the VGA’s DAC contains 256 storage +As we've learned in past chapters, the VGA's DAC contains 256 storage locations, each holding one 18-bit value representing an RGB color triplet organized as 6 bits per primary color. Each and every pixel generated by the VGA is fed into the DAC as an 8-bit value (refer to @@ -36,11 +36,11 @@ to look up one of the 256 values stored in the DAC. The looked-up value is then converted to analog red, green, and blue signals and sent to the monitor to form one pixel. -That’s straightforward enough, and we’ve produced some pretty impressive +That's straightforward enough, and we've produced some pretty impressive color effects by loading the DAC once and then playing with the 8-bit path into the DAC. Now, however, we want to generate color effects by dynamically changing the values stored in the DAC in real time, a -technique that I’ll call *color cycling*. The potential of color cycling +technique that I'll call *color cycling*. The potential of color cycling should be obvious: Smooth motion can easily be simulated by altering the colors in an appropriate pattern, and all sorts of changing color effects can be produced without altering a single bit of display memory. @@ -49,13 +49,13 @@ For example, a sunset can be made to color and darken by altering the DAC locations containing the colors used to draw the sunset, or a river can be made to appear to flow by cycling through the colors used to draw the river. Another use for color cycling is in providing more realistic -displays for applications like realtime 3-D games, where the VGA’s 256 +displays for applications like realtime 3-D games, where the VGA's 256 simultaneous colors can be made to seem like many more by changing the DAC settings from frame to frame to match the changing color demands of the rendered scene. Which leaves only one question: How do we load the DAC smoothly in realtime? -Actually, so far as I know, you can’t. At least you can’t load the +Actually, so far as I know, you can't. At least you can't load the *entire* DAC—all 256 locations—frame after frame without producing distressing on-screen effects on at least some computers. In non-256 color modes, it is indeed possible to load the DAC quickly enough to @@ -66,14 +66,14 @@ stored within the DAC in all modes other than 256 color mode, as discussed in Chapter A on the companion CD-ROM) can be used in non-256 color modes to produce many of the same effects as color cycling and is considerably simpler and more reliable then color cycling, so color -paging is generally superior to color cycling whenever it’s available. +paging is generally superior to color cycling whenever it's available. In short, color cycling is really the method of choice for dynamic color effects only in 256-color mode—but, regrettably, color cycling is at its -least reliable and capable in that mode, as we’ll see next. +least reliable and capable in that mode, as we'll see next. ### The Heart of the Problem {#Heading4} -Here’s the problem with loading the entire DAC repeatedly: The DAC +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 @@ -89,11 +89,11 @@ As it happens, the DAC should only be loaded during vertical blanking; that is, the time between the end of displaying the bottom border and the start of displaying the top border, when no video information at all is being sent to the screen by the DAC. Otherwise, small dots of snow -appear on the screen, and while an occasional dot of this sort wouldn’t +appear on the screen, and while an occasional dot of this sort wouldn't be a problem, the constant DAC loading required by color cycling would produce a veritable snowstorm on the screen. By the way, I do mean -“border,” not “frame buffer”; the overscan pixels pass through the DAC -just like the pixels controlled by the frame buffer, so you can’t even +"border," not "frame buffer"; the overscan pixels pass through the DAC +just like the pixels controlled by the frame buffer, so you can't even load the DAC while the border color is being displayed without getting snow. @@ -102,17 +102,17 @@ leading edge of the vertical sync pulse is easy to detect via bit 3 of the Input Status 1 register at 3DAH; when bit 3 is 1, the vertical sync pulse is active. Conveniently, the vertical sync pulse starts partway through but not too far into vertical blanking, so it serves as a handy -way to tell when it’s safe to load the DAC without producing snow on the +way to tell when it's safe to load the DAC without producing snow on the screen. So we wait for the start of the vertical sync pulse, then begin to load -the DAC. There’s a catch, though. On many computers—Pentiums, 486s, and +the DAC. There's a catch, though. On many computers—Pentiums, 486s, and 386s sometimes, 286s most of the time, and 8088s all the time—there just -isn’t enough time between the start of the vertical sync pulse and the -end of vertical blanking to load all 256 DAC locations. That’s the crux -of the problem with the DAC, and shortly we’ll get to a tool that will +isn't enough time between the start of the vertical sync pulse and the +end of vertical blanking to load all 256 DAC locations. That's the crux +of the problem with the DAC, and shortly we'll get to a tool that will let you explore for yourself the extent of the problem on computers in -which you’re interested. First, though, we must address *another* DAC +which you're interested. First, though, we must address *another* DAC loading problem: the BIOS. ------------------------ --------------------------------- -------------------- diff --git a/34-02.md b/34-02.md index 31b594a..4482432 100644 --- a/34-02.md +++ b/34-02.md @@ -12,7 +12,7 @@ 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 -calling the BIOS once rather than 256 times. At any rate, we’d like to +calling the BIOS once rather than 256 times. At any rate, we'd like to use one or the other of the BIOS functions for color cycling, because we know that whenever possible, one should use a BIOS function in preference to accessing hardware directly, in the interests of avoiding @@ -20,13 +20,13 @@ compatibility problems. In the case of color cycling, however, it is emphatically *not* possible to use either of the BIOS functions, for they have problems. Serious problems. -The difficulty is this: IBM’s BIOS specification describes exactly how +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 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 +vendors. IBM's spec does *not*, however, describe whether vertical retrace should be waited for before loading the DAC, nor does it mention whether video should be left enabled while loading the DAC, leaving cloners to choose whatever approach they desire—and, alas, every VGA @@ -34,16 +34,16 @@ cloner seems to have selected a different approach. I tested four clone VGAs from different manufacturers, some in a 20 MHz 386 machine and some in a 10 MHz 286 machine. Two of the four waited for -vertical retrace before loading the DAC; two didn’t. Two of the four +vertical retrace before loading the DAC; two didn't. Two of the four blanked the display while loading the DAC, resulting in flickering bars across the screen. One showed speckled pixels spattered across the top of the screen while the DAC was being loaded. Also, not one was able to load all 256 DAC locations without showing *some* sort of garbage on the -screen for at least one frame, but that’s not the BIOS’s fault; it’s a +screen for at least one frame, but that's not the BIOS's fault; it's a problem endemic to the VGA. ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *These findings lead me inexorably to the conclusion that the BIOS should not be used to load the DAC dynamically. That is, if you’re loading the DAC just once in preparation for a graphics session—sort of a DAC mode set—by all means load by way of the BIOS. No one will care that some garbage is displayed for a single frame; heck, I have boards that bounce and flicker and show garbage every time I do a mode set, and the amount of garbage produced by loading the DAC once is far less noticeable. If, however, you intend to load the DAC repeatedly for color cycling, avoid the BIOS DAC load functions like the plague. They will bring you only heartache.* + ![](images/i.jpg) *These findings lead me inexorably to the conclusion that the BIOS should not be used to load the DAC dynamically. That is, if you're loading the DAC just once in preparation for a graphics session—sort of a DAC mode set—by all means load by way of the BIOS. No one will care that some garbage is displayed for a single frame; heck, I have boards that bounce and flicker and show garbage every time I do a mode set, and the amount of garbage produced by loading the DAC once is far less noticeable. If, however, you intend to load the DAC repeatedly for color cycling, avoid the BIOS DAC load functions like the plague. They will bring you only heartache.* ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- As but one example of the unsuitability of the BIOS DAC-loading @@ -51,20 +51,20 @@ functions for color cycling, imagine that you want to cycle all 256 colors 70 times a second, which is once per frame. In order to accomplish that, you would normally wait for the start of the vertical sync signal (marking the end of the frame), then call the BIOS to load -the DAC. On some boards—boards with BIOSes that don’t wait for vertical +the DAC. On some boards—boards with BIOSes that don't wait for vertical sync before loading the DAC—that will work pretty well; you will, in fact, load the DAC once a frame. On other boards, however, it will work very poorly indeed; your program will wait for the start of vertical sync, and then the BIOS will wait for the start of the next vertical sync, with the result being that the DAC gets loaded only once every -*two* frames. Sadly, there’s no way, short of actually profiling the +*two* frames. Sadly, there's no way, short of actually profiling the performance of BIOS DAC loads, for you to know which sort of BIOS is installed in a particular computer, so unless you can always control the -brand of VGA your software will run on, you really can’t afford to color +brand of VGA your software will run on, you really can't afford to color cycle by calling the BIOS. Which is not to say that loading the DAC directly is a picnic either, as -we’ll see next. +we'll see next. #### Loading the DAC Directly {#Heading6} @@ -83,7 +83,7 @@ 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. -So what’s the drawback? Well, imagine that as you’re loading the DAC, an +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) activates and writes to the DAC; you could end up with quite a mess on the screen, especially when your program resumes and continues writing @@ -94,16 +94,16 @@ on the other hand, you set the index for each DAC location separately, you can disable interrupts 256 times, once as each DAC location is loaded, without problems. -As I commented in the last chapter, I don’t have any gruesome tale to +As I commented in the last chapter, I don't have any gruesome tale to relate that mandates taking the slower but safer road and setting the index for each DAC location separately while interrupts are disabled. -I’m merely hypothesizing as to what ghastly mishaps *could*. happen. -However, it’s been my experience that anything that can happen on the PC +I'm merely hypothesizing as to what ghastly mishaps *could*. happen. +However, it's been my experience that anything that can happen on the PC *does* happen eventually; there are just too dang many PCs out there for -it to be otherwise. However, load the DAC any way you like; just don’t -blame me if you get a call from someone who’s claims that your program +it to be otherwise. However, load the DAC any way you like; just don't +blame me if you get a call from someone who's claims that your program sometimes turns their screen into something resembling month-old yogurt. -It’s not really your fault, of course—but try explaining that to *them!* +It's not really your fault, of course—but try explaining that to *them!* ------------------------ --------------------------------- -------------------- [Previous](34-01.html) [Table of Contents](index.html) [Next](34-03.html) diff --git a/34-03.md b/34-03.md index 8b3fd07..fe897a3 100644 --- a/34-03.md +++ b/34-03.md @@ -4,22 +4,22 @@ ### A Test Program for Color Cycling {#Heading7} -Anyway, the choice of how to load the DAC is yours. Given that I’m not -providing you with any hard-and-fast rules (mainly because there don’t +Anyway, the choice of how to load the DAC is yours. Given that I'm not +providing you with any hard-and-fast rules (mainly because there don't seem to be any), what you need is a tool so that you can experiment with -various DAC-loading approaches for yourself, and that’s exactly what -you’ll find in Listing 34.1. +various DAC-loading approaches for yourself, and that's exactly what +you'll find in Listing 34.1. Listing 34.1 draws a band of vertical lines, each one pixel wide, across 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 +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 kept constant in order to keep the background and border colors from -changing, but **CYCLE\_SIZE** locations are *loaded*, and it’s the -number of locations we can load without problems that we’re interested +changing, but **CYCLE\_SIZE** locations are *loaded*, and it's the +number of locations we can load without problems that we're interested in.) **LISTING 34.1 L34-1.ASM** diff --git a/34-04.md b/34-04.md index ac8489c..d8dc787 100644 --- a/34-04.md +++ b/34-04.md @@ -18,7 +18,7 @@ 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** -is 0, the program doesn’t wait before loading. The effect of setting or +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 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 @@ -36,7 +36,7 @@ program loads the DAC non-stop. 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 +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 @@ -62,31 +62,31 @@ 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 +don't have enough data points to make reliable suggestions, and I don't want you acting on my comments and running into trouble down the pike. You now have a versatile tool with which to probe the limitations of various DAC-loading approaches; use it to perform your own tests on a sampling of the slowest hardware configurations you expect your programs to run on, then leave a generous safety margin. -One thing’s for sure, though—you’re not going to be able to cycle all +One thing's for sure, though—you're not going to be able to cycle all 256 DAC locations cleanly once per frame on a reliable basis across the -current generation of PCs. That’s why I said at the outset that brute -force isn’t appropriate to the task of color cycling. That doesn’t mean -that color cycling can’t be used, just that subtler approaches must be -employed. Let’s look at some of those alternatives. +current generation of PCs. That's why I said at the outset that brute +force isn't appropriate to the task of color cycling. That doesn't mean +that color cycling can't be used, just that subtler approaches must be +employed. Let's look at some of those alternatives. ### Color Cycling Approaches that Work {#Heading8} -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 +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 -computer. Color cycling looks stunningly smooth, doesn’t it? And this is +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 saturations, and you could get something that looks for all the world diff --git a/34-05.md b/34-05.md index 6aa49cc..850d09f 100644 --- a/34-05.md +++ b/34-05.md @@ -4,25 +4,25 @@ Yet another and somewhat odder workaround is that of using only 128 DAC locations and page flipping. (Page flipping in 256-color modes involves -using the VGA’s undocumented 256-color modes; see Chapters 31, 43, and -47 for details.) In this mode of operation, you’d first display page 0, -which is drawn entirely with colors 0-127. Then you’d draw page 1 to +using the VGA's undocumented 256-color modes; see Chapters 31, 43, and +47 for details.) In this mode of operation, you'd first display page 0, +which is drawn entirely with colors 0-127. Then you'd draw page 1 to look just like page 0, except that colors 128-255 are used instead. -You’d load DAC locations 128-255 with the next cycle settings for the -128 colors you’re using, then you’d switch to display the second page +You'd load DAC locations 128-255 with the next cycle settings for the +128 colors you're using, then you'd switch to display the second page with the new colors. Then you could modify page 0 as needed, drawing in colors 0-127, load DAC locations 0-127 with the next color cycle settings, and flip back to page 0. The idea is that you modify only those DAC locations that are not used to display any pixels on the current screen. The advantage of this is -*not*, as you might think, that you don’t generate garbage on the screen +*not*, as you might think, that you don't generate garbage on the screen when modifying undisplayed DAC locations; in fact, you do, for a spot of interference will show up if you set a DAC location, displayed or not, during display time. No, you still have to wait for vertical sync and load only during vertical blanking before loading the DAC when page flipping with 128 colors; the advantage is that since none of the DAC -locations you’re modifying is currently displayed, you can spread the +locations you're modifying is currently displayed, you can spread the loading out over two or more vertical blanking periods—however long it takes. If you did this without the 128-color page flipping, you might get odd on-screen effects as some of the colors changed after one frame, @@ -36,16 +36,16 @@ on-screen colors by cycling colors *as a frame is displayed*, so that the color set changes from scan line to scan line down the screen. The possibilities are endless. However, were I to be writing 256-color -software that used color cycling, I’d find out how many colors could be +software that used color cycling, I'd find out how many colors could be cycled after the start of vertical sync on the slowest computer I -expected the software to run on, I’d lop off at least 10 percent for a -safety margin, and I’d structure my program so that no color cycling set +expected the software to run on, I'd lop off at least 10 percent for a +safety margin, and I'd structure my program so that no color cycling set exceeded that size, interleaving several color cycling sets if necessary. -That’s what *I’d* do. Don’t let yourself be held back by my limited +That's what *I'd* do. Don't let yourself be held back by my limited imagination, though! Color cycling may be the most complicated of all -the color control techniques, but it’s also the most powerful. +the color control techniques, but it's also the most powerful. ### Odds and Ends {#Heading9} @@ -54,7 +54,7 @@ loading the DAC, the Write Index register wraps back from 255 to 0, and likewise when you load a block of registers through the BIOS. So far as I know, this is a characteristic of the hardware, and should be consistent; also, Richard Wilton documents this behavior for the BIOS in -the VGA bible, *Programmer’s Guide to PC Video Systems, Second Edition* +the VGA bible, *Programmer's Guide to PC Video Systems, Second Edition* (Microsoft Press), so you should be able to count on it. Not that I see that DAC index wrapping is especially useful, but it never hurts to understand exactly how your resources behave, and I never know when one @@ -63,7 +63,7 @@ quirk. #### The DAC Mask {#Heading10} -There’s one register in the DAC that I haven’t mentioned yet, the DAC +There's one register in the DAC that I haven't mentioned yet, the DAC Mask register at 03C6H. The operation of this register is simple but powerful; it can mask off any or all of the 8 bits of pixel information coming into the DAC from the VGA. Whenever a bit of the DAC Mask @@ -83,7 +83,7 @@ The DAC can be read directly, via the DAC Read Index register at 3C7H and the DAC Data register at 3C9H, in much the same way as it can be written directly by way of the DAC Write Index register—complete with autoincrementing the DAC Read Index register after every three reads. -Everything I’ve said about writing to the DAC applies to reading from +Everything I've said about writing to the DAC applies to reading from the DAC. In fact, reading from the DAC can even cause snow, just as loading the DAC does, so it should ideally be performed during vertical blanking. @@ -113,12 +113,12 @@ very much symmetric with setting the DAC. And so, at long last, we come to the end of our discussion of color control on the VGA. If it has been more complex than anyone might have -imagined, it has also been most rewarding. There’s as much obscure but +imagined, it has also been most rewarding. There's as much obscure but very real potential in color control as there is anywhere on the VGA, -which is to say that there’s a very great deal of potential indeed. Put +which is to say that there's a very great deal of potential indeed. Put color cycling or color paging together with the page flipping and image -drawing techniques explored elsewhere in this book, and you’ll leave the -audience gasping and wondering “How the heck did they *do* that?” +drawing techniques explored elsewhere in this book, and you'll leave the +audience gasping and wondering "How the heck did they *do* that?" ------------------------ --------------------------------- -------------------- [Previous](34-04.html) [Table of Contents](index.html) [Next](35-01.html) diff --git a/35-01.md b/35-01.md index df1ab9a..ac284e0 100644 --- a/35-01.md +++ b/35-01.md @@ -6,7 +6,7 @@ Chapter 35\ Bresenham Is Fast, and Fast Is Good {#Heading1} ------------------------------------ -### Implementing and Optimizing Bresenham’s Line-Drawing Algorithm {#Heading2} +### Implementing and Optimizing Bresenham's Line-Drawing Algorithm {#Heading2} For all the complexity of graphics design and programming, surprisingly few primitive functions lie at the heart of most graphics software. @@ -19,22 +19,22 @@ graphics applications today, notably CAD/CAM and computer-aided engineering. Probably the best-known formula for drawing lines on a computer display -is called Bresenham’s line-drawing algorithm. (We have to be specific -here because there is also a less-well-known Bresenham’s circle-drawing -algorithm.) In this chapter, I’ll present two implementations for the -EGA and VGA of Bresenham’s line-drawing algorithm, which provides decent +is called Bresenham's line-drawing algorithm. (We have to be specific +here because there is also a less-well-known Bresenham's circle-drawing +algorithm.) In this chapter, I'll present two implementations for the +EGA and VGA of Bresenham's line-drawing algorithm, which provides decent line quality and excellent drawing speed. The first implementation is in rather plain C, with the second in -not-so-plain assembly, and they’re both pretty good code. The assembly +not-so-plain assembly, and they're both pretty good code. The assembly implementation is damned good code, in fact, but if you want to know -whether it’s the fastest Bresenham’s implementation possible, I must -tell you that it isn’t. First of all, the code could be sped up a bit by +whether it's the fastest Bresenham's implementation possible, I must +tell you that it isn't. First of all, the code could be sped up a bit by shuffling and combining the various error-term manipulations, but that results in *truly* cryptic code. I wanted you to be able to relate the original algorithm to the final code, so I skipped those optimizations. Also, write mode 3, which is unique to the VGA, could be used for -considerably faster drawing. I’ve described write mode 3 in earlier +considerably faster drawing. I've described write mode 3 in earlier chapters, and I strongly recommend its use in VGA-only line drawing. Second, horizontal, vertical, and diagonal lines could be special-cased, @@ -43,27 +43,27 @@ very rapidly. (This is especially true of horizontal lines, which can be drawn 8 pixels at a time.) Third, run-length slice line drawing could be used to significantly -reduce the number of calculations required per pixel, as I’ll +reduce the number of calculations required per pixel, as I'll demonstrate in the next two chapters. Finally, unrolled loops and/or duplicated code could be used to eliminate most of the branches in the final assembly implementation, and because x86 processors are notoriously slow at branching, that would -make quite a difference in overall performance. If you’re interested in +make quite a difference in overall performance. If you're interested in unrolled loops and similar assembly techniques, I refer you to the first part of this book. -That brings us neatly to my final point: Even if I didn’t know that +That brings us neatly to my final point: Even if I didn't know that there were further optimizations to be made to my line-drawing -implementation, I’d *assume* that there were. As I’m sure the +implementation, I'd *assume* that there were. As I'm sure the experienced assembly programmers among you know, there are dozens of ways to tackle any problem in assembly, and someone else always seems to -have come up with a trick that never occurred to you. I’ve incorporated -a suggestion made by Jim Mackraz in the code in this chapter, and I’d be +have come up with a trick that never occurred to you. I've incorporated +a suggestion made by Jim Mackraz in the code in this chapter, and I'd be most interested in hearing of any other tricks or tips you may have. Notwithstanding, the line-drawing implementation in Listing 35.3 is -plenty fast enough for most purposes, so let’s get the discussion +plenty fast enough for most purposes, so let's get the discussion underway. ### The Task at Hand {#Heading3} @@ -78,21 +78,21 @@ the true line it represents. Second, to be usable, a line-drawing function must be *fast*. Minicomputers and mainframes generally have hardware that performs line drawing, but most microcomputers offer no such assistance. True, nowadays graphics accelerators such as the S3 and -ATI chips have line drawing hardware, but some other accelerators don’t; +ATI chips have line drawing hardware, but some other accelerators don't; when drawing lines on the latter sort of chip, when drawing on the CGA, EGA, and VGA, and when drawing sorts of lines not supported by -line-drawing hardware as well, the PC’s CPU must draw lines on its own, +line-drawing hardware as well, the PC's CPU must draw lines on its own, and, as many users of graphics-oriented software know, that can be a slow process indeed. Line drawing quality and speed derive from two factors: The algorithm used to draw the line and the implementation of that algorithm. The -first implementation (written in Borland C++) that I’ll be presenting in +first implementation (written in Borland C++) that I'll be presenting in this chapter illustrates the workings of the algorithm and draws lines at a good rate. The second implementation, written in assembly language and callable directly from Borland C++, draws lines at extremely high speed, on the order of three to six times faster than the C version. -Between them, the two implementations illuminate Bresenham’s +Between them, the two implementations illuminate Bresenham's line-drawing algorithm and provide high-performance line-drawing capability. @@ -111,15 +111,15 @@ high resolution, a large color palette, and/or floating-point hardware. These techniques tend to perform poorly and produce less visually impressive results on all but the best-endowed PCs. -Bresenham’s line-drawing algorithm, on the other hand, is uniquely +Bresenham's line-drawing algorithm, on the other hand, is uniquely suited to microcomputer implementation in that it requires no floating-point operations, no divides, and no multiplies inside the line-drawing loop. Moreover, it can be implemented with surprisingly little code. -### Bresenham’s Line-Drawing Algorithm {#Heading4} +### Bresenham's Line-Drawing Algorithm {#Heading4} -The key to grasping Bresenham’s algorithm is to understand that when +The key to grasping Bresenham's algorithm is to understand that when drawing an approximation of a line on a finite-resolution display, each pixel drawn will lie either exactly on the true line or to one side or the other of the true line. The amount by which the pixel actually drawn diff --git a/35-02.md b/35-02.md index 89dcab9..e5c8ca9 100644 --- a/35-02.md +++ b/35-02.md @@ -2,13 +2,13 @@ [Previous](35-01.html) [Table of Contents](index.html) [Next](35-03.html) ------------------------ --------------------------------- -------------------- -Let’s examine the case of drawing a line where the horizontal, or X +Let's examine the case of drawing a line where the horizontal, or X length of the line is greater than the vertical, or Y length, and both lengths are greater than 0. For example, suppose we are drawing a line from (0,0) to (5,2), as shown in Figure 35.2. Note that Figure 35.2 shows the upper-left-hand corner of the screen as (0,0), rather than placing (0,0) at its more traditional lower-left-hand corner location. -Due to the way in which the PC’s graphics are mapped to memory, it is +Due to the way in which the PC's graphics are mapped to memory, it is simpler to work within this framework, although a translation of Y from increasing downward to increasing upward could be effected easily enough by simply subtracting the Y coordinate from the screen height minus 1; @@ -19,7 +19,7 @@ In Figure 35.2, the endpoints of the line fall exactly on displayed pixels. However, no other part of the line squarely intersects the center of a pixel, meaning that all other pixels will have to be plotted as approximations of the line. The approach to approximation that -Bresenham’s algorithm takes is to move exactly 1 pixel along the major +Bresenham's algorithm takes is to move exactly 1 pixel along the major dimension of the line each time a new pixel is drawn, while moving 1 pixel along the minor dimension each time the line moves more than halfway between pixels along the minor dimension. @@ -32,9 +32,9 @@ accompany those X coordinates. ![](images/35-02.jpg)\ **Figure 35.2**  *Drawing between two pixel endpoints.* -It’s easy enough to select the Y coordinates by eye in Figure 35.2. The +It's easy enough to select the Y coordinates by eye in Figure 35.2. The appropriate Y coordinates are 0, 0, 1, 1, 2, 2, based on the Y -coordinate closest to the line for each X coordinate. Bresenham’s +coordinate closest to the line for each X coordinate. Bresenham's algorithm makes the same selections, based on the same criterion. The manner in which it does this is by keeping a running record of the error of the line—that is, how far from the true line the current Y coordinate @@ -44,7 +44,7 @@ true line to the extent that the adjacent Y coordinate would be closer to the line, then the current Y coordinate is changed to that adjacent Y coordinate. -Let’s take a moment to follow the steps Bresenham’s algorithm would go +Let's take a moment to follow the steps Bresenham's algorithm would go through in drawing the line in Figure 35.3. The initial pixel is drawn at (0,0), the starting point of the line. At this point the error of the line is 0. @@ -66,11 +66,11 @@ adjustment of one pixel in the current Y coordinate. The running error of the pixel actually drawn at this point is C minus D. ![](images/35-03.jpg)\ - **Figure 35.3**  *The error term in Bresenham’s algorithm.* + **Figure 35.3**  *The error term in Bresenham's algorithm.* The fourth pixel has an X coordinate of 3. The running error at this point is E minus D; since this is less than 1/2, the current Y -coordinate doesn’t change. The fourth pixel is drawn at (3,1). +coordinate doesn't change. The fourth pixel is drawn at (3,1). The fifth pixel has an X coordinate of 4. The running error at this point is F minus D; since this is greater than 1/2, the current Y @@ -81,11 +81,11 @@ point is G minus F. Finally, the sixth pixel is the end point of the line. This pixel has an X coordinate of 5. The running error at this point is G minus G, or 0, indicating that this point is squarely on the true line, as of course it -should be given that it’s the end point, so the current Y coordinate +should be given that it's the end point, so the current Y coordinate remains the same. The end point of the line is drawn at (5,2), and the line is complete. -That’s really all there is to Bresenham’s algorithm. The algorithm is a +That's really all there is to Bresenham's algorithm. The algorithm is a process of drawing a pixel at each possible coordinate along the major dimension of the line, each with the closest possible coordinate along the minor dimension. The running error is used to keep track of when the @@ -97,10 +97,10 @@ octants in which lines could be drawn, as we will see in the C implementation. The above discussion summarizes the nature rather than the exact -mechanism of Bresenham’s line-drawing algorithm. I’ll provide a brief +mechanism of Bresenham's line-drawing algorithm. I'll provide a brief seat-of-the-pants discussion of the algorithm in action when we get to the C implementation of the algorithm; for a full mathematical -treatment, I refer you to pages 433-436 of Foley and Van Dam’s +treatment, I refer you to pages 433-436 of Foley and Van Dam's *Fundamentals of Interactive Computer Graphics* (Addison-Wesley, 1982), or pages 72-78 of the second edition of that book, which was published under the name *Computer Graphics: Principles and Practice* @@ -110,26 +110,26 @@ code for drawing lines in one of the eight possible octants. #### Strengths and Weaknesses {#Heading5} -The overwhelming strength of Bresenham’s line-drawing algorithm is +The overwhelming strength of Bresenham's line-drawing algorithm is speed. With no divides, no floating-point operations, and no need for -variables that won’t fit in 16 bits, it is perfectly suited for PCs. +variables that won't fit in 16 bits, it is perfectly suited for PCs. -The weakness of Bresenham’s algorithm is that it produces relatively +The weakness of Bresenham's algorithm is that it produces relatively low-quality lines by comparison with most other line-drawing algorithms. -In particular, lines generated with Bresenham’s algorithm can tend to +In particular, lines generated with Bresenham's algorithm can tend to look a little jagged. On the PC, however, jagged lines are an inevitable consequence of relatively low resolution and a small color set, so lines -drawn with Bresenham’s algorithm don’t look all that much different from +drawn with Bresenham's algorithm don't look all that much different from lines drawn in other ways. Besides, in most applications, users are far more interested in the overall picture than in the primitive elements from which that picture is built. As a general rule, any collection of pixels that trend from point A to point B in a straight fashion is -accepted by the eye as a line. Bresenham’s algorithm is successfully +accepted by the eye as a line. Bresenham's algorithm is successfully used by many current PC programs, and by the standard of this wide acceptance the algorithm is certainly good enough. Then, too, users hate waiting for their computer to finish drawing. By -any standard of drawing performance, Bresenham’s algorithm excels. +any standard of drawing performance, Bresenham's algorithm excels. ------------------------ --------------------------------- -------------------- [Previous](35-01.html) [Table of Contents](index.html) [Next](35-03.html) diff --git a/35-03.md b/35-03.md index dd1edfd..c518097 100644 --- a/35-03.md +++ b/35-03.md @@ -4,15 +4,15 @@ ### An Implementation in C {#Heading6} -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 +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**. **LISTING 35.1 L35-1.C** /* - * C implementation of Bresenham’s line drawing algorithm + * C implementation of Bresenham's line drawing algorithm * for the EGA and VGA. Works in modes 0xE, 0xF, 0x10, and 0x12. * * Compiled with Borland C++ @@ -54,18 +54,18 @@ Listing 35.2 is a sample program to demonstrate the use of **EVGALine**. PixelBytePtr = MK_FP(EVGA_SCREEN_SEGMENT, ( Y0 * EVGA_SCREEN_WIDTH_IN_BYTES ) + ( X0 / 8 )); - /* Generate a mask with a 1 bit in the pixel’s position within the + /* Generate a mask with a 1 bit in the pixel's position within the screen byte */ PixelMask = 0x80 >> ( X0 & 0x07 ); - /* Set up the Graphics Controller’s Bit Mask register to allow + /* Set up the Graphics Controller's Bit Mask register to allow only the bit corresponding to the pixel being drawn to be modified */ outportb(GC_INDEX, BIT_MASK_INDEX); outportb(GC_DATA, PixelMask); /* Draw the pixel. Because of the operation of the set/reset - feature of the EGA/VGA, the value written doesn’t matter. + feature of the EGA/VGA, the value written doesn't matter. The screen byte is ORed in order to perform a read to latch the display memory, then perform a write in order to modify it. */ *PixelBytePtr |= 0xFE; @@ -92,7 +92,7 @@ Listing 35.2 is a sample program to demonstrate the use of **EVGALine**. /* Draw the line */ EVGADot(X0, Y0); /* draw the first pixel */ while ( DeltaX— ) { - /* See if it’s time to advance the Y coordinate */ + /* See if it's time to advance the Y coordinate */ if ( ErrorTerm >= 0 ) { /* Advance the Y coordinate & adjust the error term back down */ @@ -127,7 +127,7 @@ Listing 35.2 is a sample program to demonstrate the use of **EVGALine**. EVGADot(X0, Y0); /* draw the first pixel */ while ( DeltaY— ) { - /* See if it’s time to advance the X coordinate */ + /* See if it's time to advance the X coordinate */ if ( ErrorTerm >= 0 ) { /* Advance the X coordinate & adjust the error term back down */ diff --git a/35-04.md b/35-04.md index 228326d..4633754 100644 --- a/35-04.md +++ b/35-04.md @@ -81,7 +81,7 @@ Y_MAX / 4, 4); /* Wait for the enter key to be pressed */ - scanf(“%c”, &temp); + scanf("%c", &temp); /* Return back to text mode */ _AX = TEXT_MODE; @@ -91,8 +91,8 @@ #### Looking at EVGALine {#Heading7} 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 +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 @@ -105,7 +105,7 @@ Set/Reset and Set/Reset registers in this way causes the remainder 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 +eight possible line orientations among which a Bresenham's algorithm implementation must distinguish. (In interpreting Figure 35.4, assume that lines radiate outward from the center of the figure, falling into one of eight octants delineated by the horizontal and vertical axes and @@ -144,7 +144,7 @@ coordinate changes on every pixel and the X coordinate changes only when the running error dictates, since Y is the major axis. ![](images/35-04.jpg)\ - **Figure 35.4**  *Bresenham’s eight possible line orientations.* + **Figure 35.4**  *Bresenham's eight possible line orientations.* ------------------------ --------------------------------- -------------------- [Previous](35-03.html) [Table of Contents](index.html) [Next](35-05.html) diff --git a/35-05.md b/35-05.md index 16918e8..01c73a7 100644 --- a/35-05.md +++ b/35-05.md @@ -34,7 +34,7 @@ Enable Set/Reset and Set/Reset registers for each pixel: While modularity would improve, speed would suffer markedly. ![](images/35-05.jpg)\ - **Figure 35.5**  *EVGALine’s decision logic.* + **Figure 35.5**  *EVGALine's decision logic.* #### Drawing Each Line {#Heading8} @@ -73,7 +73,7 @@ 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 -same color, line-drawing performance is improved by setting the VGA’s +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**. @@ -93,14 +93,14 @@ 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 bit 7 of the byte at offset 1, and so on. Further, it assumes that the -graphics adapter’s hardware is configured such that setting the Bit Mask +graphics adapter's hardware is configured such that setting the Bit Mask register to allow modification of only the bit controlling the pixel of interest and then ORing a value of 0FEH with display memory will draw that pixel correctly without affecting any other dots. (Note that 0FEH is used rather than 0FFH or 0 because some optimizing compilers turn ORs with the latter values into simpler operations or optimize them away -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 +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 @@ -117,10 +117,10 @@ the one that controls the pixel to be drawn will be left unmodified. Finally, 0FEH is ORed with the display memory byte controlling the pixel 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 +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 +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 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** diff --git a/35-06.md b/35-06.md index 6f1e283..076a0a2 100644 --- a/35-06.md +++ b/35-06.md @@ -5,7 +5,7 @@ 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 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). +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 itself is only five lines long. @@ -25,7 +25,7 @@ hardware-dependent **outportb** lines in **EVGALine** itself. **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 @@ -48,7 +48,7 @@ adjusted from one pixel to the next, rather than recalculated from scratch. These enhancements are not incorporated into the code in Listing 35.1 -for a couple of reasons. One reason is that it’s important that the +for a couple of reasons. One reason is that it's important that the workings of the algorithm be clearly visible in the code, for learning purposes. Once the implementation is understood, rewriting it for improved performance would certainly be a worthwhile exercise. Another @@ -60,9 +60,9 @@ times the speed? Given which, a high-speed assembly language version of **EVGALine** would seem to be a logical next step. -### Bresenham’s Algorithm in Assembly {#Heading11} +### Bresenham's Algorithm in Assembly {#Heading11} -Listing 35.3 is a high-performance implementation of Bresenham’s +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 with the same parameters. Either of the two can be linked to any program diff --git a/35-07.md b/35-07.md index fddb168..af41e96 100644 --- a/35-07.md +++ b/35-07.md @@ -4,7 +4,7 @@ **LISTING 35.3 L35-3.ASM** - ; Fast assembler implementation of Bresenham’s line-drawing algorithm + ; Fast assembler implementation of Bresenham's line-drawing algorithm ; for the EGA and VGA. Works in modes 0Eh, 0Fh, 10h, and 12h. ; Borland C++ near-callable. ; Bit mask accumulation technique when |DeltaX| >= |DeltaY| @@ -74,7 +74,7 @@ local MoveToNextByte, ResetBitMaskAccumulator mov cx,bx ;# of pixels in line jcxz Line1End ;done if there are no more pixels - ; (there’s always at least the one pixel + ; (there's always at least the one pixel ; at the start location) shl si,1 ;DeltaY * 2 mov bp,si ;error term @@ -87,7 +87,7 @@ ; for the initial pixel LineLoop: ; - ; See if it’s time to advance the Y coordinate yet. + ; See if it's time to advance the Y coordinate yet. ; and bp,bp ;see if error term is negative js MoveXCoord ;yes, stay at the same Y coordinate @@ -101,7 +101,7 @@ ;load latches and write pixels, with bit mask ; preserving other latched bits. Because ; set/reset is enabled for all planes, the - ; value written actually doesn’t matter + ; value written actually doesn't matter add di,EVGA_SCREEN_WIDTH_IN_BYTES ;increment Y coordinate add bp,si ;adjust error term back down ; @@ -113,7 +113,7 @@ else ror ah,1 ;move pixel mask 1 pixel to the right endif - jnc ResetBitMaskAccumulator ;didn’t wrap to next byte + jnc ResetBitMaskAccumulator ;didn't wrap to next byte jmp short MoveToNextByte ;did wrap to next byte ; ; Move pixel mask one pixel (either right or left, depending @@ -134,7 +134,7 @@ ;load latches and write pixels, with bit mask ; preserving other latched bits. Because ; set/reset is enabled for all planes, the - ; value written actually doesn’t matter + ; value written actually doesn't matter MoveToNextByte: if MOVE_LEFT dec di ;next pixel is in byte to left @@ -156,7 +156,7 @@ ;load latches and write pixels, with bit mask ; preserving other latched bits. Because ; set/reset is enabled for all planes, the - ; value written actually doesn’t matter + ; value written actually doesn't matter endm ; @@ -190,10 +190,10 @@ ;load latches and write pixel, with bit mask ; preserving other latched bits. Because ; set/reset is enabled for all planes, the - ; value written actually doesn’t matter + ; value written actually doesn't matter LineLoop: ; - ; See if it’s time to advance the X coordinate yet. + ; See if it's time to advance the X coordinate yet. ; and bp,bp ;see if error term is negative jns ETermAction ;no, advance X coordinate @@ -225,7 +225,7 @@ ;load latches and write pixel, with bit mask ; preserving other latched bits. Because ; set/reset is enabled for all planes, the - ; value written actually doesn’t matter + ; value written actually doesn't matter ; loop LineLoop Line2End: @@ -270,9 +270,9 @@ mov ax,[bp+Y0] ;line Y end, used later in ;calculating the start address sub si,ax ;calculate DeltaY - jns CalcStartAddress ;if positive, we’re set + jns CalcStartAddress ;if positive, we're set ; - ; DeltaY is negative — swap coordinates so we’re always working + ; DeltaY is negative — swap coordinates so we're always working ; with a positive DeltaY. ; mov ax,[bp+Y1] ;set line start to Y1, for use @@ -385,7 +385,7 @@ which is well-commented, should speak for itself. 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 +and lamented *Programmer's Journal*. Jim's suggestion was that when 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. @@ -393,16 +393,16 @@ 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 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 +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 access. All too many PC programmers fall into the high-level-language trap of thinking that a good algorithm guarantees good performance. Not so: As -our two implementations of Bresenham’s algorithm graphically illustrate +our two implementations of Bresenham's algorithm graphically illustrate (pun not originally intended, but allowed to stand once recognized), truly great PC code requires both a good algorithm *and* a good assembly -implementation. In Listing 35.3, we’ve got y-oh-my, isn’t it fun? +implementation. In Listing 35.3, we've got y-oh-my, isn't it fun? ------------------------ --------------------------------- -------------------- [Previous](35-06.html) [Table of Contents](index.html) [Next](36-01.html) diff --git a/36-01.md b/36-01.md index e76f493..6237124 100644 --- a/36-01.md +++ b/36-01.md @@ -10,44 +10,44 @@ Chapter 36\ Years ago, I worked at a company that asked me to write blazingly fast line-drawing code for an AutoCAD driver. I implemented the basic -Bresenham’s line-drawing algorithm; streamlined it as much as possible; +Bresenham's line-drawing algorithm; streamlined it as much as possible; special-cased horizontal, diagonal, and vertical lines; broke out separate, optimized routines for lines in each octant; and massively unrolled the loops. When I was done, I had line drawing down to a mere five or six instructions per pixel, and I handed the code over to the AutoCAD driver person, content in the knowledge that I had pushed the -theoretical limits of the Bresenham’s algorithm on the 80x86 +theoretical limits of the Bresenham's algorithm on the 80x86 architecture, and that this was as fast as line drawing could get on a PC. That feeling lasted for about a week, until Dave Miller, who these days is a Windows display-driver whiz at Engenious Solutions, casually -mentioned Bresenham’s faster run-length slice line-drawing algorithm. +mentioned Bresenham's faster run-length slice line-drawing algorithm. -Remember Bill Murray’s safety tip in *Ghostbusters*? It goes something +Remember Bill Murray's safety tip in *Ghostbusters*? It goes something like this. Harold Ramis tells the Ghostbusters not to cross the beams of -the antighost guns. “Why?” Murray asks. +the antighost guns. "Why?" Murray asks. -“It would be bad,” Ramis says. +"It would be bad," Ramis says. -Murray says, “I’m fuzzy on the whole good/bad thing. What exactly do you -mean by ‘bad’?” It turns out that what Ramis means by bad is basically +Murray says, "I'm fuzzy on the whole good/bad thing. What exactly do you +mean by ‘bad'?" It turns out that what Ramis means by bad is basically the destruction of the universe. -“Important safety tip,” Murray comments dryly. +"Important safety tip," Murray comments dryly. I learned two important safety tips from my line-drawing experience; neither involves the possible destruction of the universe, so far as I know, but they are nonetheless worth keeping in mind. First, never, -never, never think you’ve written the fastest possible code. Odds are, -you haven’t. Run your code past another good programmer, and he or she -will probably say, “But why don’t you do this?” and you’ll realize that +never, never think you've written the fastest possible code. Odds are, +you haven't. Run your code past another good programmer, and he or she +will probably say, "But why don't you do this?" and you'll realize that you could indeed do that, and your code would then be faster. Or relax and come back to your code later, and you may well see another, faster approach. There are a million ways to implement code for any task, and you can almost always find a faster way if you need to. Second, when performance matters, never have your code perform the same -calculation more than once. This sounds obvious, but it’s astonishing -how often it’s ignored. For example, consider this snippet of code: +calculation more than once. This sounds obvious, but it's astonishing +how often it's ignored. For example, consider this snippet of code: for (i=0; i YEnd) { Temp = YStart; @@ -85,7 +85,7 @@ balanced.* /* Point to the bitmap address first pixel to draw */ ScreenPtr = MK_FP(SCREEN_SEGMENT, YStart * SCREEN_WIDTH + XStart); - /* Figure out whether we’re going left or right, and how far we’re + /* Figure out whether we're going left or right, and how far we're going horizontally */ if ((XDelta = XEnd - XStart) < 0) { @@ -96,7 +96,7 @@ balanced.* { XAdvance = 1; } - /* Figure out how far we’re going vertically */ + /* Figure out how far we're going vertically */ YDelta = YEnd - YStart; /* Special-case horizontal, vertical, and diagonal lines, for speed @@ -158,16 +158,16 @@ balanced.* InitialPixelCount = (WholeStep / 2) + 1; FinalPixelCount = InitialPixelCount; - /* If the basic run length is even and there’s no fractional + /* If the basic run length is even and there's no fractional advance, we have one pixel that could go to either the initial - or last partial run, which we’ll arbitrarily allocate to the + or last partial run, which we'll arbitrarily allocate to the last run */ if ((AdjUp == 0) && ((WholeStep & 0x01) == 0)) { InitialPixelCount--; } - /* If there’re an odd number of pixels per run, we have 1 pixel that can’t - be allocated to either the initial or last partial run, so we’ll add 0.5 + /* If there're an odd number of pixels per run, we have 1 pixel that can't + be allocated to either the initial or last partial run, so we'll add 0.5 to error term so this pixel will be handled by the normal full-run loop */ if ((WholeStep & 0x01) != 0) { @@ -186,7 +186,7 @@ balanced.* RunLength++; ErrorTerm -= AdjDown; /* reset the error term */ } - /* Draw this scan line’s run */ + /* Draw this scan line's run */ DrawHorizontalRun(&ScreenPtr, XAdvance, RunLength, Color); } /* Draw the final run of pixels */ @@ -218,16 +218,16 @@ balanced.* InitialPixelCount = (WholeStep / 2) + 1; FinalPixelCount = InitialPixelCount; - /* If the basic run length is even and there’s no fractional advance, we + /* If the basic run length is even and there's no fractional advance, we have 1 pixel that could go to either the initial or last partial run, - which we’ll arbitrarily allocate to the last run */ + which we'll arbitrarily allocate to the last run */ if ((AdjUp == 0) && ((WholeStep & 0x01) == 0)) { InitialPixelCount--; } /* If there are an odd number of pixels per run, we have one pixel - that can’t be allocated to either the initial or last partial - run, so we’ll add 0.5 to the error term so this pixel will be + that can't be allocated to either the initial or last partial + run, so we'll add 0.5 to the error term so this pixel will be handled by the normal full-run loop */ if ((WholeStep & 0x01) != 0) { @@ -247,7 +247,7 @@ balanced.* RunLength++; ErrorTerm -= AdjDown; /* reset the error term */ } - /* Draw this scan line’s run */ + /* Draw this scan line's run */ DrawVerticalRun(&ScreenPtr, XAdvance, RunLength, Color); } /* Draw the final run of pixels */ diff --git a/36-04.md b/36-04.md index f63f639..7633ff0 100644 --- a/36-04.md +++ b/36-04.md @@ -2,11 +2,11 @@ [Previous](36-03.html) [Table of Contents](index.html) [Next](37-01.html) ------------------------ --------------------------------- -------------------- -Notwithstanding that it’s not optimized, Listing 36.1 is reasonably +Notwithstanding that it's not optimized, Listing 36.1 is reasonably fast. If you run Listing 36.2 (a sample line-drawing program that you can use to test-drive Listing 36.1), you may be as surprised as I was at how quickly the screen fills with vectors, considering that Listing 36.1 -is entirely in C and has some redundant divides. Or perhaps you won’t be +is entirely in C and has some redundant divides. Or perhaps you won't be surprised—in which case I suggest you *not* miss the next chapter. **LISTING 36.2 L36-2.C** diff --git a/37-01.md b/37-01.md index 518a17c..5aab610 100644 --- a/37-01.md +++ b/37-01.md @@ -12,24 +12,24 @@ As I write this, the wife, the kid, and I are in the throes of yet another lightning-quick transcontinental move, this time to Redmond, Washington, to work for You Know Who. Moving is never fun, but what makes it worse for us is the pets. Getting them into kennels and to the -airport is hard; there’s always the possibility that they might not be +airport is hard; there's always the possibility that they might not be allowed to fly because of the weather; and, worst of all, they might not -make it. Animals don’t usually end up injured or dead, but it does +make it. Animals don't usually end up injured or dead, but it does happen. In a (not notably successful) effort to cheer me up about the prospect of shipping my animals, a friend told me the following story, which he -swears actually happened to a friend of his. I don’t know—to me, it has +swears actually happened to a friend of his. I don't know—to me, it has the ring of an urban legend, which is to say it makes a good story, but -you can never track down the person it really happened to; it’s always a -friend of a friend. But maybe it is true, and anyway, it’s a good story. +you can never track down the person it really happened to; it's always a +friend of a friend. But maybe it is true, and anyway, it's a good story. This friend of a friend (henceforth referred to as FOF), worked in an air-freight terminal. Consequently, he handled a lot of animals, which was fine by him, because he liked animals; in fact, he had quite a few cats at home. You can imagine his dismay when, one day, he took a kennel off the plane to find that the cat it carried was quite thoroughly dead. -(No, it wasn’t resting, nor pining for the fjords; this cat was bloody +(No, it wasn't resting, nor pining for the fjords; this cat was bloody *deceased*.) FOF knew how upset the owner would be, and came up with a plan to make @@ -38,29 +38,29 @@ markings. He would substitute that cat, and since all cats treat all humans with equal disdain, the owner would never know the difference, and would never suffer the trauma of the loss of her cat. So FOF drove home, got his cat, put it in the kennel, and waited for the owner to -show up—at which point, she took one look at the kennel and said, “This -isn’t my cat. My cat is dead.” +show up—at which point, she took one look at the kennel and said, "This +isn't my cat. My cat is dead." As it turned out, she had shipped her recently deceased feline home to be buried. History does not record how our FOF dug himself out of this one. -Okay, but what’s the point? The point is, if it isn’t broken, don’t fix -it. And if it is broken, maybe that’s all right, too. Which brings us, +Okay, but what's the point? The point is, if it isn't broken, don't fix +it. And if it is broken, maybe that's all right, too. Which brings us, neat as a pin, to the topic of drawing lines in a serious hurry. ### Fast Run-Length Slice Line Drawing {#Heading3} In the last chapter, we examined the principles of run-length slice line drawing, which draws lines a run at a time rather than a pixel at a -time, a run being a series of pixels along the major (longer) axis. It’s +time, a run being a series of pixels along the major (longer) axis. It's time to turn theory into useful practice by developing a fast assembly -version. Listing 37.1 is the assembly version, in a form that’s +version. Listing 37.1 is the assembly version, in a form that's plug-compatible with the C code from the previous chapter. **LISTING 37.1 L37-1.ASM** - ; Fast run-length slice line drawing implementation for mode 0x13, the VGA’s + ; Fast run-length slice line drawing implementation for mode 0x13, the VGA's ; 320x200 256-color mode. ; Draws a line between the specified endpoints in color Color. ; C near-callable as: @@ -93,13 +93,13 @@ plug-compatible with the C code from the previous chapter. public _LineDraw _LineDraw proc near cld - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to our stack frame sub sp, LOCAL_SIZE ;allocate space for local variables push si ;preserve C register variables push di - push ds ;preserve caller’s DS - ; We’ll draw top to bottom, to reduce the number of cases we have to handle, + push ds ;preserve caller's DS + ; We'll draw top to bottom, to reduce the number of cases we have to handle, ; and to make lines between the same endpoints always draw the same pixels. mov ax,[bp].YStart cmp ax,[bp].YEnd @@ -117,10 +117,10 @@ plug-compatible with the C code from the previous chapter. mov di,si add di,ax ;DI = YStart * SCREEN_WIDTH + XStart ; = offset of initial pixel - ; Figure out how far we’re going vertically (guaranteed to be positive). + ; Figure out how far we're going vertically (guaranteed to be positive). mov cx,[bp].YEnd sub cx,[bp].YStart ;CX = YDelta - ; Figure out whether we’re going left or right, and how far we’re going + ; Figure out whether we're going left or right, and how far we're going ; horizontally. In the process, special-case vertical lines, for speed and ; to avoid nasty boundary conditions and division by 0. mov dx,[bp].XEnd @@ -229,11 +229,11 @@ plug-compatible with the C code from the previous chapter. ; (may be adjusted later). This is also the ; final run pixel count push cx ;remember final run pixel count for later - ; If the basic run length is even and there’s no fractional advance, we have + ; If the basic run length is even and there's no fractional advance, we have ; one pixel that could go to either the initial or last partial run, which - ; we’ll arbitrarily allocate to the last run. - ; If there is an odd number of pixels per run, we have one pixel that can’t - ; be allocated to either the initial or last partial run, so we’ll add 0.5 to + ; we'll arbitrarily allocate to the last run. + ; If there is an odd number of pixels per run, we have one pixel that can't + ; be allocated to either the initial or last partial run, so we'll add 0.5 to ; the error term so this pixel will be handled by the normal full-run loop. add dx,si ;assume odd length, add YDelta to error term ; (add 0.5 of a pixel to the error term) @@ -241,7 +241,7 @@ plug-compatible with the C code from the previous chapter. jnz XMajorAdjustDone ;no, already did work for odd case, all set sub dx,si ;length is even, undo odd stuff we just did and bx,bx ;is the adjust up equal to 0? - jnz XMajorAdjustDone ;no (don’t need to check for odd length, + jnz XMajorAdjustDone ;no (don't need to check for odd length, ; because of the above test) dec cx ;both conditions met; make initial run 1 ; shorter @@ -267,7 +267,7 @@ plug-compatible with the C code from the previous chapter. inc cx ;one extra pixel in run sub dx,[bp].AdjDown ;reset the error term XMajorNoExtra: - rep stosb ;draw this scan line’s run + rep stosb ;draw this scan line's run add di,SCREEN_WIDTH ;advance along the minor axis (Y) XMajorFullRunsOddEntry: ;enter loop here if there is an odd number ; of full runs @@ -277,7 +277,7 @@ plug-compatible with the C code from the previous chapter. inc cx ;one extra pixel in run sub dx,[bp].AdjDown ;reset the error term XMajorNoExtra2: - rep stosb ;draw this scan line’s run + rep stosb ;draw this scan line's run add di,SCREEN_WIDTH ;advance along the minor axis (Y) dec si @@ -323,18 +323,18 @@ plug-compatible with the C code from the previous chapter. ; (may be adjusted later) push cx ;remember final run pixel count for later - ; If the basic run length is even and there’s no fractional advance, we have + ; If the basic run length is even and there's no fractional advance, we have ; one pixel that could go to either the initial or last partial run, which - ; we’ll arbitrarily allocate to the last run. - ; If there is an odd number of pixels per run, we have one pixel that can’t - ; be allocated to either the initial or last partial run, so we’ll add 0.5 to + ; we'll arbitrarily allocate to the last run. + ; If there is an odd number of pixels per run, we have one pixel that can't + ; be allocated to either the initial or last partial run, so we'll add 0.5 to ; the error term so this pixel will be handled by the normal full-run loop. add dx,si ;assume odd length, add XDelta to error term test al,1 ;is run length even? jnz YMajorAdjustDone ;no, already did work for odd case, all set sub dx,si ;length is even, undo odd stuff we just did and bx,bx ;is the adjust up equal to 0? - jnz YMajorAdjustDone ;no (don’t need to check for odd length, + jnz YMajorAdjustDone ;no (don't need to check for odd length, ; because of the above test) dec cx ;both conditions met; make initial run 1 ; shorter @@ -400,11 +400,11 @@ plug-compatible with the C code from the previous chapter. dec cx jnz YMajorLastLoop Done: - pop ds ;restore caller’s DS + pop ds ;restore caller's DS pop di pop si ;restore C register variables mov sp,bp ;deallocate local variables - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _LineDraw endp end diff --git a/37-02.md b/37-02.md index 4f43901..8df2003 100644 --- a/37-02.md +++ b/37-02.md @@ -6,21 +6,21 @@ Your first question is likely to be the following: Just how fast is Listing 37.1? Is it optimized to the hilt or just pretty fast? The quick -answer is: It’s *fast*. Listing 37.1 draws lines at a rate of nearly 1 +answer is: It's *fast*. Listing 37.1 draws lines at a rate of nearly 1 million pixels per second on my 486/33, and is capable of still faster -drawing, as I’ll discuss shortly. (The heavily optimized AutoCAD +drawing, as I'll discuss shortly. (The heavily optimized AutoCAD line-drawing code that I mentioned in the last chapter drew 150,000 pixels per second on an EGA in a 386/16, and I thought I had died and gone to Heaven. Such is progress.) The full answer is a more complicated -one, and ties in to the principle that if it is broken, maybe that’s +one, and ties in to the principle that if it is broken, maybe that's okay—and to the principle of looking before you leap, also known as 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 +converted the last chapter's C code into assembly. Then I streamlined 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 +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 them, for they were bound to be a lot more interesting than a basic C-to-assembly port. @@ -28,7 +28,7 @@ C-to-assembly port. Ego intervened at this point, however. I wanted to know how much of a speed-up I had already gotten, so I timed the performance of the C code and compared it to the assembly code. To my horror, I found that I had -not gotten even a two-times improvement! I couldn’t understand how that +not gotten even a two-times improvement! I couldn't understand how that could be—the C code was decidedly unoptimized—until I hit on the idea of measuring the maximum memory speed of the VGA to which I was drawing. @@ -48,13 +48,13 @@ system (nondisplay) memory, I found that the assembly code was actually four times as fast as the C code. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *In fact, Listing 37.1 draws VGA lines at about 92 percent of the maximum possible rate in my system—that is, it draws very nearly as fast as the VGA hardware will allow. All the optimization in the world would get me less than 10 percent faster line drawing—and only if I eliminated all overhead, an unlikely proposition at best. The code isn’t fully optimized, but so what?* + ![](images/i.jpg) *In fact, Listing 37.1 draws VGA lines at about 92 percent of the maximum possible rate in my system—that is, it draws very nearly as fast as the VGA hardware will allow. All the optimization in the world would get me less than 10 percent faster line drawing—and only if I eliminated all overhead, an unlikely proposition at best. The code isn't fully optimized, but so what?* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -Now it’s true that faster line-drawing code would likely be more +Now it's true that faster line-drawing code would likely be more beneficial on faster VGAs, especially local-bus VGAs, and in slower -systems. For that reason, I’ll list a variety of potential optimizations -to Listing 37.1. On the other hand, it’s also true that Listing 37.1 is +systems. For that reason, I'll list a variety of potential optimizations +to Listing 37.1. On the other hand, it's also true that Listing 37.1 is capable of drawing lines at a rate of 2.2 million pixels per second on a 486/ 33, given fast enough VGA memory, so it should be able to drive almost any non-local-bus VGA at nearly full speed. In short, Listing @@ -96,19 +96,19 @@ One weakness of Listing 37.1 is that for lines with slopes between 0.5 and 2, the average run length is less than two, rendering run-length slicing ineffective. This can be remedied by viewing lines in that range as being composed of diagonal, rather than horizontal or vertical runs. -I haven’t space to take this idea any further in this book, but it’s not +I haven't space to take this idea any further in this book, but it's not very complicated, and it guarantees a minimum run length of 2, which renders run drawing considerably more efficient, and makes techniques such as unrolling the inner run-drawing loops more attractive. Finally, be aware that run-length slice drawing is best for long lines, -because it has more and slower setup than a standard Bresenham’s line +because it has more and slower setup than a standard Bresenham's line draw, including a divide. Run-length slice is great for 100-pixel lines, -but not necessarily for 20-pixel lines, and it’s a sure thing that it’s +but not necessarily for 20-pixel lines, and it's a sure thing that it's not terrific for 3-pixel lines. Both approaches will work, but if -line-drawing performance is critical, whether you’ll want to use -run-length slice or standard Bresenham’s depends on the typical lengths -of the lines you’ll be drawing. For lines of widely varying lengths, you +line-drawing performance is critical, whether you'll want to use +run-length slice or standard Bresenham's depends on the typical lengths +of the lines you'll be drawing. For lines of widely varying lengths, you might want to implement both approaches, and choose the best one for each line, depending on the line length—assuming, of course, that your display memory is fast enough and your application demanding enough to @@ -116,7 +116,7 @@ make that level of optimization worthwhile. If your code looks broken from a performance perspective, think before you fix it; that particular cat may be dead for a perfectly good reason. -I’ll say it again: *Profile before you optimize*. +I'll say it again: *Profile before you optimize*. ------------------------ --------------------------------- -------------------- [Previous](37-01.html) [Table of Contents](index.html) [Next](38-01.html) diff --git a/38-01.md b/38-01.md index 0de226c..63b4461 100644 --- a/38-01.md +++ b/38-01.md @@ -8,26 +8,26 @@ Chapter 38\ ### Drawing Polygons Efficiently and Quickly {#Heading2} -*“Give me but one firm spot on which to stand, and I will move the -Earth.”* +*"Give me but one firm spot on which to stand, and I will move the +Earth."* —Archimedes -Were Archimedes alive today, he might say, “Give me but one fast -polygon-fill routine on which to call, and I will draw the Earth.” +Were Archimedes alive today, he might say, "Give me but one fast +polygon-fill routine on which to call, and I will draw the Earth." Programmers often think of pixel drawing as being the basic graphics primitive, but filled polygons are equally fundamental and far more useful. Filled polygons can be used for constructs as diverse as a single pixel or a 3-D surface, and virtually everything in between. -I’ll spend some time in this chapter and the next several developing +I'll spend some time in this chapter and the next several developing routines to draw filled polygons and building more sophisticated graphics operations atop those routines. Once we have that foundation, -I’ll get into 2-D manipulation and animation of polygon-based entities -as preface to an exploration of 3-D graphics. You can’t get there from -here without laying some groundwork, though, so in this chapter I’ll -begin with the basics of filling a polygon. In the next chapter, we’ll -see how to draw a polygon considerably faster. That’s my general +I'll get into 2-D manipulation and animation of polygon-based entities +as preface to an exploration of 3-D graphics. You can't get there from +here without laying some groundwork, though, so in this chapter I'll +begin with the basics of filling a polygon. In the next chapter, we'll +see how to draw a polygon considerably faster. That's my general approach for this sort of topic: High-level exploration of a graphics topic first, followed by a speedy hardware-specific implementation for the IBM PC/VGA combination, the most widely used graphics system around. @@ -41,12 +41,12 @@ And slow computer graphics is scarcely worth the bother. A polygon is simply a shape formed by lines laid end to end to form a continuous, closed path. A polygon is filled by setting all pixels -within the polygon’s boundaries to a color or pattern. For now, we’ll +within the polygon's boundaries to a color or pattern. For now, we'll work only with polygons filled with solid colors. You can divide polygons into three categories: convex, nonconvex, and -complex, as shown in Figure 38.1. Convex polygons include what you’d -normally think of as “convex” and more; as far as we’re concerned, a +complex, as shown in Figure 38.1. Convex polygons include what you'd +normally think of as "convex" and more; as far as we're concerned, a convex polygon is one for which any horizontal line drawn through the polygon encounters the right edge exactly once and the left edge exactly once, excluding horizontal and zero-length edge segments. Put another @@ -55,7 +55,7 @@ direction from up to down, or vice-versa. Also, the right and left edges of a convex polygon may not cross one another, although they may touch so long as the right edge never crosses over to the left side of the left edge. (Check out the second polygon drawn in Listing 38.3, which -certainly isn’t convex in the normal sense.) The boundaries of nonconvex +certainly isn't convex in the normal sense.) The boundaries of nonconvex polygons, on the other hand, can go in whatever directions they please, so long as they never cross. Complex polygons can have any boundaries you might imagine, which makes for interesting problems in deciding @@ -67,17 +67,17 @@ naming.) Why bother to distinguish between convex, nonconvex, and complex polygons? Easy: performance, especially when it comes to filling convex -polygons. We’re going to start with filled convex polygons; they’re +polygons. We're going to start with filled convex polygons; they're widely useful and will serve well to introduce some of the subtler complexities of polygon drawing, not the least of which is the slippery -concept of “inside.” +concept of "inside." #### Which Side Is Inside? {#Heading4} The basic principle of polygon filling is decomposing each polygon into a series of horizontal lines, one for each horizontal row of pixels, or -scan line, within the polygon (a process I’ll call *scan conversion*), -and drawing the horizontal lines. I’ll refer to the entire process as +scan line, within the polygon (a process I'll call *scan conversion*), +and drawing the horizontal lines. I'll refer to the entire process as rasterization. Rasterization of convex polygons is easily done by starting at the top of the polygon and tracing down the left and right sides, one scan line (one vertical pixel) at a time, filling the extent @@ -90,18 +90,18 @@ simple approach is inadequate for nonconvex polygons. **Figure 38.1**  *Convex, nonconvex, and complex polygons.* There are a couple of complications, however. The lesser complication is -how to rasterize the polygon efficiently, given that it’s difficult to +how to rasterize the polygon efficiently, given that it's difficult to write fast code that simultaneously traces two edges and fills the space between them. The solution is to decouple the process of scan-converting the polygon into a list of horizontal lines from that of drawing the horizontal lines. One device-independent routine can trace along the two edges and build a list of the beginning and end coordinates of the polygon on each raster line. Then a second, device-specific, routine can -draw from the list after the entire polygon has been scanned. We’ll see +draw from the list after the entire polygon has been scanned. We'll see this in action shortly. The second, greater complication arises because the definition of which -pixels are “within” a polygon is a more complicated matter than you +pixels are "within" a polygon is a more complicated matter than you might imagine. You might think that scan-converting an edge of a polygon is analogous to drawing a line from one vertex to the next, but this is not so. A line by itself is a one-dimensional construct, and as such is @@ -112,10 +112,10 @@ polygon, we want to draw the pixels within the polygon, but a standard vertex-to-vertex line-drawing algorithm will draw many pixels outside the polygon, as shown in Figure 38.2. -It’s no crime to use standard lines to trace out a polygon, rather than +It's no crime to use standard lines to trace out a polygon, rather than drawing only interior pixels. In fact, there are certain advantages: For example, the edges of a filled polygon will match the edges of the same -polygon drawn unfilled. Such polygons will look pretty much as they’re +polygon drawn unfilled. Such polygons will look pretty much as they're supposed to, and all drawing on raster displays is, after all, only an approximation of an ideal. @@ -123,14 +123,14 @@ approximation of an ideal. **Figure 38.2**  *Drawing polygons with standard line-drawing algorithms.* -There’s one great drawback to tracing polygons with standard lines, -however: Adjacent polygons won’t fit together properly, as shown in +There's one great drawback to tracing polygons with standard lines, +however: Adjacent polygons won't fit together properly, as shown in Figure 38.3. If you use six equilateral triangles to make a hexagon, for example, the edges of the triangles will overlap when traced with standard lines, and more recently drawn triangles will wipe out portions of their predecessors. Worse still, odd color effects will show up along the polygon boundaries if XOR drawing is used. Consequently, filling out -to the boundary lines just won’t do for drawing images composed of +to the boundary lines just won't do for drawing images composed of fitted-together polygons. And because fitting polygons together is exactly what I have in mind, we need a different approach. diff --git a/38-02.md b/38-02.md index cd7c64f..663d0dc 100644 --- a/38-02.md +++ b/38-02.md @@ -8,21 +8,21 @@ How, then, do you fit polygons together? *Very* carefully. First, the line-tracing algorithm must be adjusted so that it selects only those pixels that are truly inside the polygon. This basically requires shifting a standard line-drawing algorithm horizontally by one -half-pixel toward the polygon’s interior. That leaves the issue of how +half-pixel toward the polygon's interior. That leaves the issue of how to handle points that are exactly on the boundary, and points that lie at vertices, so that those points are drawn once and only once. To deal -with that, we’re going to adopt the following rules: +with that, we're going to adopt the following rules: - Points located exactly on nonhorizontal edges are drawn only if the interior of the polygon is directly to the right (left edges are - drawn, right edges aren’t). + drawn, right edges aren't). ![](images/38-03.jpg)\ **Figure 38.3**  *The adjacent polygons problem.* - Points located exactly on horizontal edges are drawn only if the interior of the polygon is directly below them (horizontal top edges - are drawn, horizontal bottom edges aren’t). + are drawn, horizontal bottom edges aren't). - A vertex is drawn only if all lines ending at that point meet the above conditions (no right or bottom edges end at that point). @@ -38,10 +38,10 @@ once—just what we need in order to be able to fit filled polygons together seamlessly. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *This sort of non-overlapping polygon filling isn’t ideal for all purposes. Polygons are skewed toward the top and left edges, which not only introduces drawing error relative to the ideal polygon but also means that a filled polygon won’t match the same polygon drawn unfilled. Narrow wedges and one-pixel-wide polygons will show up spottily. All in all, the choice of polygon-filling approach depends entirely on the ways in which the filled polygons must be used.* + ![](images/i.jpg) *This sort of non-overlapping polygon filling isn't ideal for all purposes. Polygons are skewed toward the top and left edges, which not only introduces drawing error relative to the ideal polygon but also means that a filled polygon won't match the same polygon drawn unfilled. Narrow wedges and one-pixel-wide polygons will show up spottily. All in all, the choice of polygon-filling approach depends entirely on the ways in which the filled polygons must be used.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -For our purposes, nonoverlapping polygons are the way to go, so let’s +For our purposes, nonoverlapping polygons are the way to go, so let's have at them. ### Filling Non-Overlapping Convex Polygons {#Heading6} @@ -53,19 +53,19 @@ 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 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 +listings. Here are the listings; we'll pick up discussion on the other side. **LISTING 38.1 L38-1.C** /* Color-fills a convex polygon. All vertices are offset by (XOffset, - YOffset). “Convex” means that every horizontal line drawn through + YOffset). "Convex" means that every horizontal line drawn through the polygon at any point would cross exactly two active edges (neither horizontal lines nor zero-length edges count as active edges; both are acceptable anywhere in the polygon), and that the - right & left edges never cross. (It’s OK for them to touch, though, + right & left edges never cross. (It's OK for them to touch, though, so long as the right edge never crosses over to the left of the - left edge.) Nonconvex polygons won’t be drawn properly. Returns 1 + left edge.) Nonconvex polygons won't be drawn properly. Returns 1 for success, 0 if memory allocation failed. */ #include @@ -75,7 +75,7 @@ side. #else /* MSC */ #include #endif - #include “polygon.h” + #include "polygon.h" /* Advances the index by one vertex forward through the vertex list, wrapping at the end of the list */ @@ -171,20 +171,20 @@ side. } /* Set the # of scan lines in the polygon, skipping the bottom edge - and also skipping the top vertex if the top isn’t flat because + and also skipping the top vertex if the top isn't flat because in that case the top vertex has a right edge component, and set the top scan line to draw, which is likewise the second line of the polygon unless the top is flat */ if ((WorkingHLineList.Length = MaxPoint_Y - MinPoint_Y - 1 + TopIsFlat) <= 0) - return(1); /* there’s nothing to draw, so we’re done */ + return(1); /* there's nothing to draw, so we're done */ WorkingHLineList.YStart = YOffset + MinPoint_Y + 1 - TopIsFlat; /* Get memory in which to store the line list we generate */ if ((WorkingHLineList.HLinePtr = (struct HLine *) (malloc(sizeof(struct HLine) * WorkingHLineList.Length))) == NULL) - return(0); /* couldn’t get memory for the line list */ + return(0); /* couldn't get memory for the line list */ /* Scan the left edge and store the boundary points in the list */ /* Initial pointer for storing scan converted left-edge coords */ @@ -192,8 +192,8 @@ side. /* Start from the top of the left edge */ PreviousIndex = CurrentIndex = MinIndexL; /* Skip the first point of the first line unless the top is flat; - if the top isn’t flat, the top vertex is exactly on a right - edge and isn’t drawn */ + if the top isn't flat, the top vertex is exactly on a right + edge and isn't drawn */ SkipFirst = TopIsFlat ? 0 : 1; /* Scan convert each line in the left edge from top to bottom */ do { @@ -226,7 +226,7 @@ side. /* Draw the line list representing the scan converted polygon */ DrawHorizontalLineList(&WorkingHLineList, Color); - /* Release the line list’s memory and we’re successfully done */ + /* Release the line list's memory and we're successfully done */ free(WorkingHLineList.HLinePtr); return(1); } @@ -235,7 +235,7 @@ side. point at (X2,Y2). This avoids overlapping the end of one line with the start of the next, and causes the bottom scan line of the polygon not to be drawn. If SkipFirst != 0, the point at (X1,Y1) - isn’t drawn. For each scan line, the pixel closest to the scanned + isn't drawn. For each scan line, the pixel closest to the scanned line without being to the left of the scanned line is chosen. */ static void ScanEdge(int X1, int Y1, int X2, int Y2, int SetXStart, int SkipFirst, struct HLine **EdgePointPtr) @@ -263,18 +263,18 @@ side. WorkingEdgePointPtr->XEnd = X1 + (int)(ceil((Y-Y1) * InverseSlope)); } - *EdgePointPtr = WorkingEdgePointPtr; /* advance caller’s ptr */ + *EdgePointPtr = WorkingEdgePointPtr; /* advance caller's ptr */ } **LISTING 38.2 L38-2.C** /* Draws all pixels in the list of horizontal lines passed in, in - mode 13h, the VGA’s 320x200 256-color mode. Uses a slow pixel-by- + mode 13h, the VGA's 320x200 256-color mode. Uses a slow pixel-by- pixel approach, which does have the virtue of being easily ported to any environment. */ #include - #include “polygon.h” + #include "polygon.h" #define SCREEN_WIDTH 320 #define SCREEN_SEGMENT 0xA000 diff --git a/38-03.md b/38-03.md index 78286e7..8003567 100644 --- a/38-03.md +++ b/38-03.md @@ -10,7 +10,7 @@ #include #include - #include “polygon.h” + #include "polygon.h" /* Draws the polygon described by the point list PointList in color Color with all vertices offset by (X,Y) */ diff --git a/38-04.md b/38-04.md index 0d69ff8..8c60aba 100644 --- a/38-04.md +++ b/38-04.md @@ -2,22 +2,22 @@ [Previous](38-03.html) [Table of Contents](index.html) [Next](39-01.html) ------------------------ --------------------------------- -------------------- -Listing 38.2 isn’t particularly interesting; it merely draws each +Listing 38.2 isn't particularly interesting; it merely draws each horizontal line in the passed-in list in the simplest possible way, one -pixel at a time. (No, that doesn’t make the pixel the fundamental -primitive; in the next chapter I’ll replace Listing 38.2 with a much -faster version that doesn’t bother with individual pixels at all.) +pixel at a time. (No, that doesn't make the pixel the fundamental +primitive; in the next chapter I'll replace Listing 38.2 with a much +faster version that doesn't bother with individual pixels at all.) Listing 38.1 is where the action is in this chapter. Our goal is to scan out the left and right edges of each polygon so that all points inside and no points outside the polygon are drawn, and so that all points located exactly on the boundary are drawn only if they are not on right -or bottom edges. That’s precisely what Listing 38.1 does. Here’s how: +or bottom edges. That's precisely what Listing 38.1 does. Here's how: Listing 38.1 first finds the top and bottom of the polygon, then works out from the top point to find the two ends of the top edge. If the ends are at different locations, the top is flat, which has two implications. -First, it’s easy to find the starting vertices and directions through +First, it's easy to find the starting vertices and directions through the vertex list for the left and right edges. (To scan-convert them properly, we must first determine which edge is which.) Second, the top scan line of the polygon should be drawn without the rightmost pixel, @@ -26,9 +26,9 @@ the top scan line is part of a right edge. If, on the other hand, the ends of the top edge are at the same location, the top is pointed. In that case, the top scan line of the -polygon isn’t drawn; it’s part of the right-edge line that starts at the -top vertex. (It’s part of a left-edge line, too, but the right edge -overrides.) When the top isn’t flat, it’s more difficult to tell in +polygon isn't drawn; it's part of the right-edge line that starts at the +top vertex. (It's part of a left-edge line, too, but the right edge +overrides.) When the top isn't flat, it's more difficult to tell in which direction through the vertex list the right and left edges go, because both edges start at the top vertex. The solution is to compare the slopes from the top vertex to the ends of the two lines coming out @@ -41,26 +41,26 @@ the slope-based equation: 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 +corresponding scan line in the list we'll pass to **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, +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 twice; two lines come into every vertex, but we want to scan-convert each vertex only once. Second, not scan-converting the last point of each line causes the bottom scan line of the polygon not to be drawn, as required by our rules. The first scan line of the polygon is also -skipped if the top isn’t flat. +skipped if the top isn't flat. Now we need to scan-convert the right edge into the ending X coordinate fields of the line list. This is performed in the same manner as for the left edge, except that every line in the right edge is moved one pixel to the left before being scan-converted. Why? We want the nearest point to the left of but not on the right edge, so that the right edge itself -isn’t drawn. As it happens, drawing the nearest point on or to the right +isn't drawn. As it happens, drawing the nearest point on or to the right of a line moved one pixel to the left is exactly the same as drawing the 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. +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. @@ -76,11 +76,11 @@ location. Horizontal line segments are fine anywhere in a polygon, too. Basically, Listing 38.1 scan-converts between active edges (the edges that define the extent of the polygon on each scan line) and both horizontal and zero-length lines are non-active; neither advances to -another scan line, so they don’t affect the edges being scanned. +another scan line, so they don't affect the edges being scanned. -I’ve limited this chapter’s code to merely demonstrating the principles +I've limited this chapter's code to merely demonstrating the principles of filling convex polygons, and the listings given are by no means fast. -In the next chapter, we’ll spice things up by eliminating the floating +In the next chapter, we'll spice things up by eliminating the floating point calculations and pixel-at-a-time drawing and tossing a little assembly language into the mix. diff --git a/39-01.md b/39-01.md index 5902bc8..3194f9c 100644 --- a/39-01.md +++ b/39-01.md @@ -9,21 +9,21 @@ Chapter 39\ ### Filling Polygons in a Hurry {#Heading2} In the previous chapter, we explored the surprisingly intricate process -of filling convex polygons. Now we’re going to fill them an order of +of filling convex polygons. Now we're going to fill them an order of magnitude or so faster. -Two thoughts may occur to some of you at this point: “Oh, no, he’s not -going to get into assembly language and device-dependent code, is he?” -and, “Why bother with polygon filling—or, indeed, any drawing -primitives—anyway? Isn’t that what GUIs and third-party libraries are -for?” +Two thoughts may occur to some of you at this point: "Oh, no, he's not +going to get into assembly language and device-dependent code, is he?" +and, "Why bother with polygon filling—or, indeed, any drawing +primitives—anyway? Isn't that what GUIs and third-party libraries are +for?" -To which I answer, “Well, yes, I am,” and, “If you have to ask, you’ve -missed the magic of microcomputer programming.” Actually, both questions -ask the same thing, and that is: “Why should I, as a programmer, have -any idea how my program actually works?” +To which I answer, "Well, yes, I am," and, "If you have to ask, you've +missed the magic of microcomputer programming." Actually, both questions +ask the same thing, and that is: "Why should I, as a programmer, have +any idea how my program actually works?" -Put that way, it sounds a little different, doesn’t it? +Put that way, it sounds a little different, doesn't it? GUIs, reusable code, portable code written entirely in high-level languages, and object-oriented programming are all the rage now, and @@ -38,10 +38,10 @@ create quickly and reliably programs that will be easy for new users to pick up, so software becomes easier to both produce and learn. This is, without question, a Good Thing. -The “black box” approach does not, however, necessarily cause the +The "black box" approach does not, however, necessarily cause the software itself to become faster, smaller, or more innovative; quite the -opposite, I suspect. I’ll reserve judgement on whether that is a good -thing or not, but I’ll make a prediction: In the short run, the +opposite, I suspect. I'll reserve judgement on whether that is a good +thing or not, but I'll make a prediction: In the short run, the aforementioned techniques will lead to noticeably larger, slower programs, as programmers understand less and less of what the key parts of their programs do and rely increasingly on general-purpose code @@ -60,13 +60,13 @@ in various ways components written by other people, and will cease to be the all-inclusively creative, mindbendingly complex pursuit it is today. (Using legally certified black boxes is, by the way, one direction in which the patent lawyers are leading us; legal considerations may be the -final nail in the coffin of homegrown code.) For now, though, it’s still +final nail in the coffin of homegrown code.) For now, though, it's still within your power, as a PC programmer, to understand and even control every single thing that happens on a computer if you so desire, to realize any vision you may have. Take advantage of this unique window of opportunity to create some magic! -Neither does it hurt to understand what’s involved in drawing, say, a +Neither does it hurt to understand what's involved in drawing, say, a filled polygon, even if you are using a GUI. You will better understand the performance implications of the available GUI functions, and you will be able to fill in any gaps in the functions provided. You may even @@ -76,13 +76,13 @@ screen; for instance, you can do this under Windows by using the WinG library available from Microsoft. You will also be able to understand why various quirks exist, and will be able to put them to good use. For example, the X Window System follows the polygon drawing rules described -in the previous chapter (although it’s not obvious from the X Window -System documentation); if you understood the previous chapter’s -discussion, you’re in good shape to use polygons under X. +in the previous chapter (although it's not obvious from the X Window +System documentation); if you understood the previous chapter's +discussion, you're in good shape to use polygons under X. In short, even though doing so runs counter to current trends, it helps -to understand how things work, especially when they’re very visible -parts of the software you develop. That said, let’s learn more about +to understand how things work, especially when they're very visible +parts of the software you develop. That said, let's learn more about filling convex polygons. ### Fast Convex Polygon Filling {#Heading3} @@ -92,8 +92,8 @@ chapter, the implementation we came up with met all of our functional requirements. In particular, it met stringent rules that guaranteed that polygons would never overlap or have gaps at shared edges, an important consideration when building polygon-based images. Unfortunately, the -implementation was also slow as molasses. In this chapter we’ll work up -polygon-filling code that’s fast enough to be truly usable. +implementation was also slow as molasses. In this chapter we'll work up +polygon-filling code that's fast enough to be truly usable. Our original polygon filling code involved three major tasks, each performed by a separate function: @@ -105,7 +105,7 @@ performed by a separate function: - Characterizing the polygon and coordinating the tracing and drawing (**FillConvexPolygon** ). -The amount of time that the previous chapter’s sample program spent in +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 @@ -113,30 +113,30 @@ minuscule), so we have our choice of where to begin optimizing. #### Fast Drawing {#Heading4} -Let’s start with drawing, which is easily sped up. The previous -chapter’s code used a double-nested loop that called a draw-pixel -function to plot each pixel in the polygon individually. That’s a +Let's start with drawing, which is easily sped up. The previous +chapter's code used a double-nested loop that called a draw-pixel +function to plot each pixel in the polygon individually. That's a ridiculous approach in a graphics mode that offers linearly mapped -memory, as does VGA mode 13H, the mode in which we’re working. At the +memory, as does VGA mode 13H, the mode in which we're working. At the very least, we could point a far pointer to the left edge of each polygon scan line, then draw each pixel in that scan line in quick 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 +**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 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 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 +virtually anything, it's sometimes much simpler just to use a little assembly code and be done with it. ------------------------ --------------------------------- -------------------- diff --git a/39-02.md b/39-02.md index f489700..e537f80 100644 --- a/39-02.md +++ b/39-02.md @@ -4,10 +4,10 @@ At any rate, Listing 39.1 for this chapter shows a version 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, +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 -38’s draw-pixel-based code, despite the fact that Listing 39.1 requires +38's draw-pixel-based code, despite the fact that Listing 39.1 requires a large (in this case, the Compact) data model. Listing 39.1 works fine with Borland C++, but may not work with other compilers, for it relies on the aforementioned interaction between memset and the selected memory @@ -145,7 +145,7 @@ Table 39.1 Polygon fill performance. **LISTING 39.1 L39-1.C** /* Draws all pixels in the list of horizontal lines passed in, in - mode 13h, the VGA’s 320x200 256-color mode. Uses memset to fill + mode 13h, the VGA's 320x200 256-color mode. Uses memset to fill each line, which is much faster than using DrawPixel but requires that a large data model (compact, large, or huge) be in use when running in real mode or 286 protected mode. @@ -153,7 +153,7 @@ Table 39.1 Polygon fill performance. #include #include - #include “polygon.h” + #include "polygon.h" #define SCREEN_WIDTH 320 #define SCREEN_SEGMENT 0xA000 @@ -184,7 +184,7 @@ Table 39.1 Polygon fill performance. } } -At this point, I’d like to mention that benchmarks are notoriously +At this point, I'd like to mention that benchmarks are notoriously unreliable; the results in Table 39.1 are accurate *only* for the test program, and only when running on a particular system. Results could be vastly different if smaller, larger, or more complex polygons were @@ -197,29 +197,29 @@ which code is superior. Anyway, Listing 39.1 has the desired effect of vastly improving drawing time. There are cycles yet to be had in the drawing code, but as tracing -polygon edges now takes 92 percent of the polygon filling time, it’s +polygon edges now takes 92 percent of the polygon filling time, it's 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 still be slower than the alternatives; besides, why require a math -coprocessor when you don’t have to?) Both integer and fixed-point +coprocessor when you don't have to?) Both integer and fixed-point calculations are fast. In many cases, fixed-point is faster, but integer -calculations have one tremendous virtue: They’re completely accurate. +calculations have one tremendous virtue: They're completely accurate. The tiny imprecision inherent in either fixed or floating-point calculations can result in occasional pixels being one position off from their proper location. This is no great tragedy, but after going to so -much trouble to ensure that polygons don’t overlap at common edges, why +much trouble to ensure that polygons don't overlap at common edges, why not get it exactly right? In fact, when I tested out the integer edge tracing code by comparing an 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, +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** function returned a coordinate that was one too large. diff --git a/39-03.md b/39-03.md index b9991ff..55ae6a4 100644 --- a/39-03.md +++ b/39-03.md @@ -11,7 +11,7 @@ pixels advanced per scan line is the same as the ratio of the X delta of the edge to the Y delta. Listing 39.2 is more complex than the original floating point implementation, but not painfully so. In return for that complexity, Listing 39.2 is more than 80 times faster at scanning -edges—and, as just mentioned, it’s actually more accurate than the +edges—and, as just mentioned, it's actually more accurate than the floating point code. Ya gotta love that integer arithmetic. @@ -19,14 +19,14 @@ Ya gotta love that integer arithmetic. **LISTING 39.2 L39-2.C** /* Scan converts an edge from (X1,Y1) to (X2,Y2), not including the - point at (X2,Y2). If SkipFirst == 1, the point at (X1,Y1) isn’t + point at (X2,Y2). If SkipFirst == 1, the point at (X1,Y1) isn't drawn; if SkipFirst == 0, it is. For each scan line, the pixel closest to the scanned edge without being to the left of the scanned edge is chosen. Uses an all-integer approach for speed and precision. */ #include - #include “polygon.h” + #include "polygon.h" void ScanEdge(int X1, int Y1, int X2, int Y2, int SetXStart, int SkipFirst, struct HLine **EdgePointPtr) @@ -78,7 +78,7 @@ Ya gotta love that integer arithmetic. else ErrorTerm = -Height + 1; /* going right->left */ if (SkipFirst) { /* skip the first point if so indicated */ - /* Determine whether it’s time for the X coord to advance */ + /* Determine whether it's time for the X coord to advance */ if ((ErrorTerm += Width) > 0) { X1 += AdvanceAmt; /* move 1 pixel to the left or right */ ErrorTerm -= Height; /* advance ErrorTerm to next point */ @@ -91,7 +91,7 @@ Ya gotta love that integer arithmetic. WorkingEdgePointPtr->XStart = X1; else WorkingEdgePointPtr->XEnd = X1; - /* Determine whether it’s time for the X coord to advance */ + /* Determine whether it's time for the X coord to advance */ if ((ErrorTerm += Width) > 0) { X1 += AdvanceAmt; /* move 1 pixel to the left or right */ ErrorTerm -= Height; /* advance ErrorTerm to correspond */ @@ -109,7 +109,7 @@ Ya gotta love that integer arithmetic. ErrorTerm = -Height + 1; /* going right->left */ if (SkipFirst) { /* skip the first point if so indicated */ X1 += XMajorAdvanceAmt; /* move X minimum distance */ - /* Determine whether it’s time for X to advance one extra */ + /* Determine whether it's time for X to advance one extra */ if ((ErrorTerm += ErrorTermAdvance) > 0) { X1 += AdvanceAmt; /* move X one more */ ErrorTerm -= Height; /* advance ErrorTerm to correspond */ @@ -123,7 +123,7 @@ Ya gotta love that integer arithmetic. else WorkingEdgePointPtr->XEnd = X1; X1 += XMajorAdvanceAmt; /* move X minimum distance */ - /* Determine whether it’s time for X to advance one extra */ + /* Determine whether it's time for X to advance one extra */ if ((ErrorTerm += ErrorTermAdvance) > 0) { X1 += AdvanceAmt; /* move X one more */ ErrorTerm -= Height; /* advance ErrorTerm to correspond */ @@ -131,21 +131,21 @@ Ya gotta love that integer arithmetic. } } - *EdgePointPtr = WorkingEdgePointPtr; /* advance caller’s ptr */ + *EdgePointPtr = WorkingEdgePointPtr; /* advance caller's ptr */ } ### The Finishing Touch: Assembly Language {#Heading6} 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 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 +sharp stick, but just barely. There's more to these timing results than meets that eye, though. Display memory generally responds much more slowly than system memory, especially in 386 and 486 systems. That means that much of the time taken by Listing 39.3 is actually spent waiting diff --git a/39-04.md b/39-04.md index b822d8d..6611afa 100644 --- a/39-04.md +++ b/39-04.md @@ -10,14 +10,14 @@ version of **DrawHorizontalLineList** becomes almost three times as fast as the C code. ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *There is a lesson here. An optimization has no fixed payoff; its value fluctuates according to the context in which it is used. There’s relatively little benefit to further optimizing code that already spends half its time waiting for display memory; no matter how good your optimizations, you’ll get only a two-times speedup at best, and generally much less than that. There is, on the other hand, potential for tremendous improvement when drawing to system memory, so if that’s where most of your drawing will occur, optimizations such as Listing 39.3 are well worth the effort.* + ![](images/i.jpg) *There is a lesson here. An optimization has no fixed payoff; its value fluctuates according to the context in which it is used. There's relatively little benefit to further optimizing code that already spends half its time waiting for display memory; no matter how good your optimizations, you'll get only a two-times speedup at best, and generally much less than that. There is, on the other hand, potential for tremendous improvement when drawing to system memory, so if that's where most of your drawing will occur, optimizations such as Listing 39.3 are well worth the effort.* *Know the environments in which your code will run, and know where the cycles go in those environments.* ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- **LISTING 39.3 L39-3.ASM** ; Draws all pixels in the list of horizontal lines passed in, in - ; mode 13h, the VGA’s 320x200 256-color mode. Uses REP STOS to fill + ; mode 13h, the VGA's 320x200 256-color mode. Uses REP STOS to fill ; each line. ; C near-callable as: ; void DrawHorizontalLineList(struct HLineList * HLineListPtr, @@ -49,9 +49,9 @@ as the C code. public _DrawHorizontalLineList align 2 _DrawHorizontalLineList proc - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to our stack frame - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di cld ;make string instructions inc pointers @@ -67,7 +67,7 @@ as the C code. ; for the first (top) horizontal line mov si,[si+Lngth] ;# of scan lines to draw and si,si ;are there any lines to draw? - jz FillDone ;no, so we’re done + jz FillDone ;no, so we're done mov al,byte ptr [bp+Color];color with which to fill mov ah,al ;duplicate color for STOSW FillLoop: @@ -86,7 +86,7 @@ as the C code. MainFill: shr cx,1 ;# of words in fill rep stosw ;fill as many words as possible - adc cx,cx ;1 if there’s an odd trailing byte to + adc cx,cx ;1 if there's an odd trailing byte to ; do, 0 otherwise rep stosb ;fill any odd trailing byte LineFillDone: @@ -95,16 +95,16 @@ as the C code. dec si ;count off lines to fill jnz FillLoop FillDone: - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _DrawHorizontalLineList endp end #### Maximizing REP STOS {#Heading7} -Listing 39.3 doesn’t take the easy way out and use **REP STOSB** to fill +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 @@ -113,7 +113,7 @@ word accesses to even addresses, so Listing 39.3 makes sure that all 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 +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 @@ -127,12 +127,12 @@ 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 -coordination routine, hasn’t even been converted to assembly language, -so that could be sped up as well. I haven’t bothered with these +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** 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 +simply isn't great enough to justify the effort. Part of the value of a profiler is being able to tell when to stop optimizing; with Listings 39.3 and 39.4 in use, more than two-thirds of the time taken to draw polygons is spent waiting for display memory, so optimization is pretty @@ -144,7 +144,7 @@ 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 +**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 support for other graphics systems, such as the 8514/A, the XGA, or, for diff --git a/39-05.md b/39-05.md index ccf6d0c..3f157bf 100644 --- a/39-05.md +++ b/39-05.md @@ -5,7 +5,7 @@ **LISTING 39.4 L39-4.ASM** ; Scan converts an edge from (X1,Y1) to (X2,Y2), not including the - ; point at (X2,Y2). If SkipFirst == 1, the point at (X1,Y1) isn’t + ; point at (X2,Y2). If SkipFirst == 1, the point at (X1,Y1) isn't ; drawn; if SkipFirst == 0, it is. For each scan line, the pixel ; closest to the scanned edge without being to the left of the scanned ; edge is chosen. Uses an all-integer approach for speed & precision. @@ -46,10 +46,10 @@ public _ScanEdge align 2 _ScanEdge proc - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to our stack frame sub sp,LOCAL_SIZE ;allocate space for local variables - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di mov di,[bp+EdgePointPtr] mov di,[di] ;point to the HLine array @@ -64,11 +64,11 @@ jle ToScanEdgeExit ;guard against 0-length & horz edges mov [bp+Height],bx ;Height = Y2 - Y1 sub cx,cx ;assume ErrorTerm starts at 0 (true if - ; we’re moving right as we draw) + ; we're moving right as we draw) mov dx,1 ;assume AdvanceAmt = 1 (move right) mov ax,[bp+X2] sub ax,[bp+X1] ;DeltaX = X2 - X1 - jz IsVertical ;it’s a vertical edge--special case it + jz IsVertical ;it's a vertical edge--special case it jns SetAdvanceAmt ;DeltaX >= 0 mov cx,1 ;DeltaX < 0 (move left as we draw) sub cx,bx ;ErrorTerm = -Height + 1 @@ -78,10 +78,10 @@ mov [bp+AdvanceAmt],dx ; Figure out whether the edge is diagonal, X-major (more horizontal), ; or Y-major (more vertical) and handle appropriately. - cmp ax,bx ;if Width==Height, it’s a diagonal edge - jz IsDiagonal ;it’s a diagonal edge--special case - jb YMajor ;it’s a Y-major (more vertical) edge - ;it’s an X-major (more horz) edge + cmp ax,bx ;if Width==Height, it's a diagonal edge + jz IsDiagonal ;it's a diagonal edge--special case + jb YMajor ;it's a Y-major (more vertical) edge + ;it's an X-major (more horz) edge sub dx,dx ;prepare DX:AX (Width) for division div bx ;Width/Height ;DX = error term advance per scan line @@ -161,12 +161,12 @@ sub di,XEnd ;no, point back to the XStart field UpdateHLinePtr: mov bx,[bp+EdgePointPtr] ;point to pointer to HLine array - mov [bx],di ;update caller’s HLine array pointer + mov [bx],di ;update caller's HLine array pointer ScanEdgeExit: - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si mov sp,bp ;deallocate local variables - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _ScanEdge endp end diff --git a/40-01.md b/40-01.md index 80e2365..9ca3052 100644 --- a/40-01.md +++ b/40-01.md @@ -8,28 +8,28 @@ Chapter 40\ ### Dealing with Irregular Polygonal Areas {#Heading2} -Every so often, my daughter asks me to sing her to sleep. (If you’ve +Every so often, my daughter asks me to sing her to sleep. (If you've ever heard me sing, this may cause you concern about either her hearing or her judgement, but love knows no bounds.) As any parent is well aware, singing a young child to sleep can easily take several hours, or until sunrise, whichever comes last. One night, running low on -children’s songs, I switched to a Beatles medley, and at long last her -breathing became slow and regular. At the end, I softly sang “A Hard -Day’s Night,” then quietly stood up to leave. As I tiptoed out, she -said, in a voice not even faintly tinged with sleep, “Dad, what do they -mean, ‘working like a dog’? Chasing a stick? That doesn’t make sense; -people don’t chase sticks.” +children's songs, I switched to a Beatles medley, and at long last her +breathing became slow and regular. At the end, I softly sang "A Hard +Day's Night," then quietly stood up to leave. As I tiptoed out, she +said, in a voice not even faintly tinged with sleep, "Dad, what do they +mean, ‘working like a dog'? Chasing a stick? That doesn't make sense; +people don't chase sticks." That led us into a discussion of idioms, which made about as much sense to her as an explanation of quantum mechanics. Finally, I fell back on my standard explanation of the Universe, which is that a lot of the time -it simply doesn’t make sense. +it simply doesn't make sense. As a general principle, that explanation holds up remarkably well. (In fact, having just done my taxes, I think Earth is actually run by blob-creatures from the planet Mrxx, who are helplessly doubled over -with laughter at the ridiculous things they can make us do. “Let’s make -them get Social Security numbers for their pets next year!” they’re +with laughter at the ridiculous things they can make us do. "Let's make +them get Social Security numbers for their pets next year!" they're saying right now, gasping for breath.) Occasionally, however, one has the rare pleasure of finding a corner of the Universe that makes sense, where everything fits together as if preordained. @@ -42,12 +42,12 @@ In Chapter 38, I described three types of polygons: convex, nonconvex, and complex. *The RenderMan Companion*, a terrific book by Steve Upstill (Addison-Wesley, 1990) has an intuitive definition of *convex*: If a rubber band stretched around a polygon touches all vertices in the order -they’re defined, then the polygon is convex. If a polygon has -intersecting edges, it’s complex. If a polygon doesn’t have intersecting -edges but isn’t convex, it’s nonconvex. Nonconvex is a special case of -complex, and convex is a special case of nonconvex. (Which, I’m well +they're defined, then the polygon is convex. If a polygon has +intersecting edges, it's complex. If a polygon doesn't have intersecting +edges but isn't convex, it's nonconvex. Nonconvex is a special case of +complex, and convex is a special case of nonconvex. (Which, I'm well aware, makes nonconvex a lousy name—noncomplex would have been -better—but I’m following X Window System nomenclature here.) +better—but I'm following X Window System nomenclature here.) The reason for distinguishing between these three types of polygons is that the more specialized types can be filled with markedly faster @@ -57,24 +57,24 @@ require less sorting, because edges never cross. Convex polygons can be filled fastest of all by simply scanning the two sides of the polygon, as we saw in Chapter 39. -Before we dive into complex polygon filling, I’d like to point out that -the code in this chapter, like all polygon filling code I’ve ever seen, +Before we dive into complex polygon filling, I'd like to point out that +the code in this chapter, like all polygon filling code I've ever seen, requires that the caller describe the type of the polygon to be filled. -Often, however, the caller doesn’t know what type of polygon it’s +Often, however, the caller doesn't know what type of polygon it's passing, or specifies complex for simplicity, because that will work for all polygons; in such a case, the polygon filler will use the slow complex-fill code even if the polygon is, in fact, a convex polygon. In -Chapter 41, I’ll discuss one way to improve this situation. +Chapter 41, I'll discuss one way to improve this situation. #### Active Edges {#Heading4} The basic premise of filling a complex polygon is that for a given scan -line, we determine all intersections between the polygon’s edges and +line, we determine all intersections between the polygon's edges and that scan line and then fill the spans between the intersections, as -shown in Figure 40.1. (Section 3.6 of Foley and van Dam’s *Computer +shown in Figure 40.1. (Section 3.6 of Foley and van Dam's *Computer Graphics*, Second Edition provides an overview of this and other aspects of polygon filling.) There are several rules that might be used to -determine which spans are drawn and which aren’t; we’ll use the odd/even +determine which spans are drawn and which aren't; we'll use the odd/even rule, which specifies that drawing turns on after odd-numbered intersections (first, third, and so on) and off after even-numbered intersections. @@ -83,9 +83,9 @@ The question then becomes how can we most efficiently determine which edges cross each scan line and where? As it happens, there is a great deal of coherence from one scan line to the next in a polygon edge list, because each edge starts at a given Y coordinate and continues unbroken -until it ends. In other words, edges don’t leap about and stop and start +until it ends. In other words, edges don't leap about and stop and start randomly; the X coordinate of an edge at one scan line is a consistent -delta from that edge’s X coordinate at the last scan line, and that is +delta from that edge's X coordinate at the last scan line, and that is consistent for the length of the line. ![](images/40-01.jpg)\ @@ -95,7 +95,7 @@ This allows us to reduce the number of edges that must be checked for intersection; on any given scan line, we only need to check for intersections with the currently active edges—edges that start on that scan line, plus all edges that start on earlier (above) scan lines and -haven’t ended yet—as shown in Figure 40.2. This suggests that we can +haven't ended yet—as shown in Figure 40.2. This suggests that we can proceed from the top scan line of the polygon to the bottom, keeping a running list of currently active edges—called the *active edge table* (AET)—with the edges sorted in order of ascending X coordinate of @@ -110,7 +110,7 @@ First, we must add to the AET any edges that start on the current scan line, making sure to keep the AET X-sorted for efficient odd/even scanning. Second, we must remove edges that end on the current scan line. Third, we must advance the X coordinates of active edges with the -same sort of error term-based, Bresenham’s-like approach we used for +same sort of error term-based, Bresenham's-like approach we used for convex polygons, again ensuring that the AET is X-sorted after advancing the edges. diff --git a/40-02.md b/40-02.md index 45cba44..5dcd28b 100644 --- a/40-02.md +++ b/40-02.md @@ -2,11 +2,11 @@ [Previous](40-01.html) [Table of Contents](index.html) [Next](40-03.html) ------------------------ --------------------------------- -------------------- -Advancing the X coordinates is easy. For each edge, we’ll store the -current X coordinate and all required error term information, and we’ll -use that to advance the edge one scan line at a time; then, we’ll resort +Advancing the X coordinates is easy. For each edge, we'll store the +current X coordinate and all required error term information, and we'll +use that to advance the edge one scan line at a time; then, we'll resort the AET by X coordinate as needed. Removing edges as they end is also -easy; we’ll just count down the length of each active edge on each scan +easy; we'll just count down the length of each active edge on each scan line and remove an edge when its count reaches zero. Adding edges as their tops are encountered is a tad more complex. While there are a number of ways to do this, one particularly efficient approach is to @@ -14,7 +14,7 @@ start out by putting all the edges of the polygon, sorted by increasing Y coordinate, into a single list, called the *global edge table* (GET). Then, as each scan line is encountered, all edges at the start of the GET that begin on the current scan line are moved to the AET; because -the GET is Y-sorted, there’s no need to search the entire GET. For still +the GET is Y-sorted, there's no need to search the entire GET. For still greater efficiency, edges in the GET that share common Y coordinates can be sorted by increasing X coordinate; this ensures that no more than one pass through the AET per scan line is ever needed when adding new edges @@ -22,15 +22,15 @@ from the GET in such a way as to keep the AET sorted in ascending X order. What form should the GET and AET take? Linked lists of edge structures, -as shown in Figure 40.3. With linked lists, all that’s required to move +as shown in Figure 40.3. With linked lists, all that's required to move edges from the GET to the AET as they become active, sort the AET, and remove edges that have been fully drawn is the exchanging of a few pointers. -In summary, we’ll initially store all the polygon edges in +In summary, we'll initially store all the polygon edges in Y-primary/X-secondary sort order in the GET, complete with initial X and Y coordinates, error terms and error term adjustments, lengths, and -directions of X movement for each edge. Once the GET is built, we’ll do +directions of X movement for each edge. Once the GET is built, we'll do the following: **1.**  Set the current Y coordinate to the Y coordinate of the first @@ -51,13 +51,13 @@ edges in the AET by one scan line. **6.**  Advance the current Y coordinate by one scan line. -**7.**  If either the AET or GET isn’t empty, go to step 2. +**7.**  If either the AET or GET isn't empty, go to step 2. ![](images/40-03.jpg)\ **Figure 40.3**  *The global and active edge tables as linked lists.* -That’s really all there is to it. Compare Listing 40.1 to the fast -convex polygon filling code from Chapter 39, and you’ll see that, +That's really all there is to it. Compare Listing 40.1 to the fast +convex polygon filling code from Chapter 39, and you'll see that, contrary to expectation, complex polygon filling is indeed one of the more sane and sensible corners of the universe. @@ -69,10 +69,10 @@ more sane and sensible corners of the universe. by (XOffset, YOffset). Returns 1 for success, 0 if memory allocation failed. All C code tested with Borland C++. If the polygon shape is known in advance, speedier processing may be - enabled by specifying the shape as follows: “convex” - a rubber band + enabled by specifying the shape as follows: "convex" - a rubber band stretched around the polygon would touch every vertex in order; - “nonconvex” - the polygon is not self-intersecting, but need not be - convex; “complex” - the polygon may be self-intersecting, or, indeed, + "nonconvex" - the polygon is not self-intersecting, but need not be + convex; "complex" - the polygon may be self-intersecting, or, indeed, any sort of polygon at all. Complex will work for all polygons; convex is fastest. Undefined results will occur if convex is specified for a nonconvex or complex polygon. @@ -89,7 +89,7 @@ more sane and sensible corners of the universe. #else /* MSC */ #include #endif - #include “polygon.h” + #include "polygon.h" #define SWAP(a,b) {temp = a; a = b; b = temp;} @@ -136,7 +136,7 @@ more sane and sensible corners of the universe. if ((EdgeTableBuffer = (struct EdgeState *) (malloc(sizeof(struct EdgeState) * VertexList->Length))) == NULL) - return(0); /* couldn’’t get memory for the edge table */ + return(0); /* couldn''t get memory for the edge table */ /* Build the global edge table */ BuildGET(VertexList, EdgeTableBuffer, XOffset, YOffset); /* Scan down through the polygon edges, one scan line at a time, @@ -150,7 +150,7 @@ more sane and sensible corners of the universe. XSortAET(); /* resort on X */ CurrentY++; /* advance to the next scan line */ } - /* Release the memory we’ve allocated and we’re done */ + /* Release the memory we've allocated and we're done */ free(EdgeTableBuffer); return(1); } @@ -190,9 +190,9 @@ more sane and sensible corners of the universe. SWAP(StartX, EndX); SWAP(StartY, EndY); } - /* Skip if this can’t ever be an active edge (has 0 height) */ + /* Skip if this can't ever be an active edge (has 0 height) */ if ((DeltaY = EndY - StartY) != 0) { - /* Allocate space for this edge’s info, and fill in the + /* Allocate space for this edge's info, and fill in the structure */ NewEdgePtr = NextFreeEdgeStruc++; NewEdgePtr->XDirection = /* direction in which X moves */ @@ -276,9 +276,9 @@ more sane and sensible corners of the universe. /* This edge is finished, so remove it from the AET */ *CurrentEdgePtr = CurrentEdge->NextEdge; } else { - /* Advance the edge’s X coordinate by minimum move */ + /* Advance the edge's X coordinate by minimum move */ CurrentEdge->X += CurrentEdge->WholePixelXMove; - /* Determine whether it’s time for X to advance one extra */ + /* Determine whether it's time for X to advance one extra */ if ((CurrentEdge->ErrorTerm += CurrentEdge->ErrorTermAdjUp) > 0) { CurrentEdge->X += CurrentEdge->XDirection; @@ -296,7 +296,7 @@ more sane and sensible corners of the universe. int CurrentX; /* The GET is Y sorted. Any edges that start at the desired Y - coordinate will be first in the GET, so we’ll move edges from + coordinate will be first in the GET, so we'll move edges from the GET to AET until the first edge left in the GET is no longer at the desired Y coordinate. Also, the GET is X sorted within each Y coordinate, so each successive edge we add to the AET is diff --git a/40-03.md b/40-03.md index 14c9c4f..603ff6f 100644 --- a/40-03.md +++ b/40-03.md @@ -27,11 +27,11 @@ various sorts. /* Draws all pixels in the horizontal line segment passed in, from (LeftX,Y) to (RightX,Y), in the specified color in mode 13h, the - VGA’s 320x200 256-color mode. Both LeftX and RightX are drawn. No + VGA's 320x200 256-color mode. Both LeftX and RightX are drawn. No drawing will take place if LeftX > RightX. */ #include - #include “polygon.h” + #include "polygon.h" #define SCREEN_WIDTH 320 #define SCREEN_SEGMENT 0xA000 @@ -103,7 +103,7 @@ various sorts. #include #include - #include “polygon.h” + #include "polygon.h" #define DRAW_POLYGON(PointList,Color,Shape,X,Y) \ Polygon.Length = sizeof(PointList)/sizeof(struct Point); \ diff --git a/40-04.md b/40-04.md index 34fb276..a2642c7 100644 --- a/40-04.md +++ b/40-04.md @@ -37,7 +37,7 @@ connect to its endpoints. #### Performance Considerations {#Heading7} -How fast is Listing 40.1? When drawing triangles on a 20-MHz 386, it’s +How fast is Listing 40.1? When drawing triangles on a 20-MHz 386, it's less than one-fifth the speed of the fast convex polygon fill code. However, most of that time is spent drawing individual pixels; when Listing 40.2 is replaced with the fast assembly line segment drawing @@ -46,10 +46,10 @@ about half as fast as the fast convex fill code. Even after conversion 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 -improve performance, and the maximum additional improvement that’s +Consequently, there's no single place in which it's possible to greatly +improve performance, and the maximum additional improvement that's possible looks to be a good deal less than two times; for that reason, -and because of space limitations, I’m not going to convert the rest of +and because of space limitations, I'm not going to convert the rest of the code to assembly. However, when filling a polygon with a great many edges, and especially one with a great many active edges at one time, relatively more time would be spent traversing the linked lists. In such @@ -60,7 +60,7 @@ list processing) could pay off reasonably well. ; Draws all pixels in the horizontal line segment passed in, from ; (LeftX,Y) to (RightX,Y), in the specified color in mode 13h, the - ; VGA’s 320x200 256-color mode. No drawing will take place if + ; VGA's 320x200 256-color mode. No drawing will take place if ; LeftX > RightX. Tested with TASM ; C near-callable as: ; void DrawHorizontalLineSeg(Y, LeftX, RightX, Color); @@ -81,9 +81,9 @@ list processing) could pay off reasonably well. public _DrawHorizontalLineSeg align 2 _DrawHorizontalLineSeg proc - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to our stack frame - push di ;preserve caller’s register variable + push di ;preserve caller's register variable cld ;make string instructions inc pointers mov ax,SCREEN_SEGMENT mov es,ax ;point ES to display memory @@ -102,8 +102,8 @@ list processing) could pay off reasonably well. adc cx,cx rep stosb ;draw the odd byte, if any DrawDone: - pop di ;restore caller’s register variable - pop bp ;restore caller’s stack frame + pop di ;restore caller's register variable + pop bp ;restore caller's stack frame ret _DrawHorizontalLineSeg endp end @@ -112,7 +112,7 @@ The algorithm used to X-sort the AET is an interesting performance consideration. Listing 40.1 uses a bubble sort, usually a poor choice for performance. However, bubble sorts perform well when the data are already almost sorted, and because of the X coherence of edges from one -scan line to the next, that’s generally the case with the AET. An +scan line to the next, that's generally the case with the AET. An insertion sort might be somewhat faster, depending on the state of the AET when any particular sort occurs, but a bubble sort will generally do just fine. diff --git a/40-05.md b/40-05.md index 096d2e9..e5c441f 100644 --- a/40-05.md +++ b/40-05.md @@ -6,56 +6,56 @@ An insertion sort that scans backward through the AET from the current edge rather than forward from the start of the AET could be quite a bit faster, because edges rarely move more than one or two positions through the AET. However, scanning backward requires a doubly linked list, -rather than the singly linked list used in Listing 40.1. I’ve chosen to +rather than the singly linked list used in Listing 40.1. I've chosen to use a singly linked list partly to minimize memory requirements (double-linking requires an extra pointer field) and partly because supporting back links would complicate the code a good bit. The main reason, though, is that the potential rewards for the complications of -back links and insertion sorting aren’t great enough; profiling a +back links and insertion sorting aren't great enough; profiling a variety of polygons reveals that less than ten percent of total time is spent sorting the AET. ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *The potential 1 to 5 percent speedup gained by optimizing AET sorting just isn’t worth it in any but the most demanding application—a good example of the need to keep an overall perspective when comparing the theoretical characteristics of various approaches.* + ![](images/i.jpg) *The potential 1 to 5 percent speedup gained by optimizing AET sorting just isn't worth it in any but the most demanding application—a good example of the need to keep an overall perspective when comparing the theoretical characteristics of various approaches.* ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### Nonconvex Polygons {#Heading8} Nonconvex polygons can be filled somewhat faster than complex polygons. Because edges never cross or switch positions with other edges once -they’re in the AET, the AET for a nonconvex polygon needs to be sorted +they're in the AET, the AET for a nonconvex polygon needs to be sorted only when new edges are added. In order for this to work, though, edges must be added to the AET in strict left-to-right order. Complications arise when dealing with two edges that start at the same point, because slopes must be compared to determine which edge is leftmost. This is certainly doable, but because of space limitations and limited -performance returns, I haven’t implemented this in Listing 40.1. +performance returns, I haven't implemented this in Listing 40.1. #### Details, Details {#Heading9} -Every so often, a programming demon that I’d thought I’d forever laid to +Every so often, a programming demon that I'd thought I'd forever laid to rest arises to haunt me once again. A minor example of this—an imp, if -you will—is the use of “ = ” when I mean “ == ,” which I’ve done all too -often in the past, and am sure I’ll do again. That’s minor deviltry, +you will—is the use of " = " when I mean " == ," which I've done all too +often in the past, and am sure I'll do again. That's minor deviltry, though, compared to the considerably greater evils of one of my personal scourges, of which I was recently reminded anew: too-close attention to detail. Not seeing the forest for the trees. Looking low when I should have looked high. Missing the big picture, if you catch my drift. -Thoreau said it best: “Our life is frittered away by detail....Simplify, -simplify.” That quote sprang to mind when I received a letter a while +Thoreau said it best: "Our life is frittered away by detail....Simplify, +simplify." That quote sprang to mind when I received a letter a while back from Anton Treuenfels of Fridley, Minnesota, thanking me for clarifying the principles of filling adjacent convex polygons in my -ongoing writings on graphics programming. (You’ll find this material in +ongoing writings on graphics programming. (You'll find this material in the previous two chapters.) Anton then went on to describe his own method for filling convex polygons. -Anton’s approach had its virtues and drawbacks, foremost among the +Anton's approach had its virtues and drawbacks, foremost among the virtues being a simplicity Thoreau would have admired. For instance, in writing my polygon-filling code, I had spent quite some time trying to figure out the best way to identify which edge was the left edge and which the right, finally settling on comparing the slopes of the edges -if the top of the polygon wasn’t flat, and comparing the starting points +if the top of the polygon wasn't flat, and comparing the starting points of the edges if the top was flat. Anton simplified this tremendously by not bothering to figure out ahead of time which was the right edge of the polygon and which the left, instead scanning out the two edges in @@ -65,10 +65,10 @@ the fill, so that filling started at the leftmost edge. This is a little slower than my approach (although the difference is almost surely negligible), but it also makes quite a bit of code go away. -What that example, and others like it in Anton’s letter, did was kick my -mind into a mode that it hadn’t—but should have—been in when I wrote the -code, a mode in which I began to wonder, “How else can I simplify this -code?”; what you might call Occam’s Razor mode. You see, I created the +What that example, and others like it in Anton's letter, did was kick my +mind into a mode that it hadn't—but should have—been in when I wrote the +code, a mode in which I began to wonder, "How else can I simplify this +code?"; what you might call Occam's Razor mode. You see, I created the convex polygon-drawing code by first writing pseudocode, then writing C code, and finally writing assembly code, and once the pseudocode was finished, I stopped thinking about the interactions of the various @@ -81,16 +81,16 @@ their code from a variety of perspectives; the next chapter shows just how much difference thinking about the big picture can make. May my embarrassment be your enlightenment. -The point is not whether, in the final analysis, my code or Anton’s code +The point is not whether, in the final analysis, my code or Anton's code is better; both have their advantages. The point is that I was programming with half a deck because I was so fixated on the details of a single type of implementation; I ended up with relatively hard-to-write, complex code, and missed out on many potentially useful -optimizations by being so focused. It’s a big world out there, and there +optimizations by being so focused. It's a big world out there, and there are many subtle approaches to any problem, so relax and keep the big picture in mind as you implement your programs. Your code will likely be not only better, but also simpler. And whenever you see me walking -across hot coals in this book or elsewhere when there’s an easier way to +across hot coals in this book or elsewhere when there's an easier way to go, please, let me know! Thanks, Anton. diff --git a/41-01.md b/41-01.md index 80185b6..dc5eb73 100644 --- a/41-01.md +++ b/41-01.md @@ -8,10 +8,10 @@ Chapter 41\ ### Names Do Matter when You Conceptualize a Data Structure {#Heading2} -After I wrote the columns on polygons in *Dr. Dobb’s Journal* that +After I wrote the columns on polygons in *Dr. Dobb's Journal* that became Chapters 38-40, long-time reader Bill Huber wrote to take me to task—and a well-deserved kick in the fanny it was, I might add—for my -use of non-standard polygon terminology in those columns. Unix’s +use of non-standard polygon terminology in those columns. Unix's X-Window System (XWS) defines three categories of polygons: complex, nonconvex, and convex. These three categories, each a specialized subset of the preceding category, not-so-coincidentally map quite nicely to @@ -19,35 +19,35 @@ three increasingly fast polygon filling techniques. Therefore, I used the XWS names to describe the sorts of polygons that can be drawn with each of the polygon filling techniques. -The problem is that those names don’t accurately describe all the sorts +The problem is that those names don't accurately describe all the sorts of polygons that the techniques are capable of drawing. Convex polygons are those for which no interior angle is greater than 180 degrees. The -“convex” drawing approach described in the previous few chapters +"convex" drawing approach described in the previous few chapters actually handles a number of polygons that are not convex; in fact, it can draw any polygon through which no horizontal line can be drawn that intersects the boundary more than twice. (In other words, the boundary reverses the Y direction exactly twice, disregarding polygons that have -degenerated into horizontal lines, which I’m going to ignore.) +degenerated into horizontal lines, which I'm going to ignore.) Bill was kind enough to send me the pages out of *Computational Geometry, An Introduction* (Springer-Verlag, 1988) that describe the -correct terminology; such polygons are, in fact, “monotone with respect -to a vertical line” (which unfortunately makes a rather long -**\#define** variable). Actually, to be a tad more precise, I’d call -them “monotone with respect to a vertical line and simple,” where -“simple” means “not self-intersecting.” Similarly, the polygon type I -called “nonconvex” is actually “simple,” and I suppose what I called -“complex” should be referred to as “nonsimple,” or maybe just “none of -the above.” +correct terminology; such polygons are, in fact, "monotone with respect +to a vertical line" (which unfortunately makes a rather long +**\#define** variable). Actually, to be a tad more precise, I'd call +them "monotone with respect to a vertical line and simple," where +"simple" means "not self-intersecting." Similarly, the polygon type I +called "nonconvex" is actually "simple," and I suppose what I called +"complex" should be referred to as "nonsimple," or maybe just "none of +the above." ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *This may seem like nit-picking, but actually, it isn’t; what it’s really about is the tremendous importance of having a shared language. In one of his books, Richard Feynman describes having developed his own mathematical framework, complete with his own notation and terminology, in high school. When he got to college and started working with other people who were at his level, he suddenly understood that people can’t share ideas effectively unless they speak the same language; otherwise, they waste a great deal of time on misunderstandings and explanation.* + ![](images/i.jpg) *This may seem like nit-picking, but actually, it isn't; what it's really about is the tremendous importance of having a shared language. In one of his books, Richard Feynman describes having developed his own mathematical framework, complete with his own notation and terminology, in high school. When he got to college and started working with other people who were at his level, he suddenly understood that people can't share ideas effectively unless they speak the same language; otherwise, they waste a great deal of time on misunderstandings and explanation.* ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -Or, as Bill Huber put it, “You are free to adopt your own terminology +Or, as Bill Huber put it, "You are free to adopt your own terminology when it suits your purposes well. But you risk losing or confusing those who could be among your most astute readers—those who already have been -trained in the same or a related field.” Ditto. Likewise. *D’accord*. +trained in the same or a related field." Ditto. Likewise. *D'accord*. And *mea culpa* ; I shall endeavor to watch my language in the future. ### Nomenclature in Action {#Heading3} @@ -58,16 +58,16 @@ When I was writing about polygons in my column in *DDJ*, a nonfunctional method for identifying such polygons—checking for exactly two X direction changes and two Y direction changes around the perimeter of the polygon—crept into the column by accident. That method, as I noted -in a later column, does not work. (That’s why you won’t find it in this +in a later column, does not work. (That's why you won't find it in this book.) Still, a fast method of checking for convex polygons would be highly desirable, because such polygons can be drawn with the fast code from Chapter 39, rather than the relatively slow, general-purpose code from Chapter 40. -Now consider Bill’s point that we’re not limited to drawing convex -polygons in our “convex fill” code, but can actually handle any simple -polygon that’s monotone with respect to a vertical line. Additionally, -consider Anton Treuenfels’s point, made back in Chapter 40, that life +Now consider Bill's point that we're not limited to drawing convex +polygons in our "convex fill" code, but can actually handle any simple +polygon that's monotone with respect to a vertical line. Additionally, +consider Anton Treuenfels's point, made back in Chapter 40, that life gets simpler if we stop worrying about which edge of a polygon is the left edge and which is the right, and instead just scan out each raster line starting at whichever edge is left-most. Now, what do we have? @@ -75,12 +75,12 @@ line starting at whichever edge is left-most. Now, what do we have? What we have is an approach passed along by Jim Kent, of Autodesk Animator fame. If we modify the low-level code to check which edge is left-most on each scan line and start drawing there, as just described, -then we can handle any polygon that’s monotone with respect to a -vertical line regardless of whether the edges cross. (I’ll call this -“monotone-vertical” from now on; if anyone wants to correct that +then we can handle any polygon that's monotone with respect to a +vertical line regardless of whether the edges cross. (I'll call this +"monotone-vertical" from now on; if anyone wants to correct that terminology, jump right in.) In other words, we can then handle nonsimple polygons that are monotone-vertical; self-intersection is no -longer a problem. We just scan around the polygon’s perimeter looking +longer a problem. We just scan around the polygon's perimeter looking for exactly two direction reversals along the Y axis only, and if that proves to be the case, we can handle the polygon at high speed. Figure 41.1 shows polygons that can be drawn by a monotone-vertical capable @@ -90,10 +90,10 @@ test whether a polygon is appropriately monotone. **LISTING 41.1 L41-1.C** /* Returns 1 if polygon described by passed-in vertex list is monotone with - respect to a vertical line, 0 otherwise. Doesn’t matter if polygon is simple + respect to a vertical line, 0 otherwise. Doesn't matter if polygon is simple (non-self-intersecting) or not. Tested with Borland C++ in small model. */ - #include “polygon.h” + #include "polygon.h" #define SIGNUM(a) ((a>0)?1:((a<0)?-1:0)) @@ -103,7 +103,7 @@ test whether a polygon is appropriately monotone. int NumYReversals = 0; struct Point *VertexPtr = VertexList->PointPtr; - /* Three or fewer points can’t make a non-vertical-monotone polygon */ + /* Three or fewer points can't make a non-vertical-monotone polygon */ if ((Length=VertexList->Length) < 4) return(1); /* Scan to the first non-horizontal edge */ @@ -117,7 +117,7 @@ test whether a polygon is appropriately monotone. if (i == (Length-1)) return(1); /* polygon is a flat line */ /* Now count Y reversals. Might miss one reversal, at the last vertex, but - because reversal counts must be even, being off by one isn’t a problem */ + because reversal counts must be even, being off by one isn't a problem */ do { if ((DeltaYSign = SIGNUM(VertexPtr[i].Y - VertexPtr[i+1].Y)) != 0) { @@ -129,7 +129,7 @@ test whether a polygon is appropriately monotone. } } } while (i++ < (Length-1)); - return(1); /* it’s a vertical-monotone polygon */ + return(1); /* it's a vertical-monotone polygon */ } ------------------------ --------------------------------- -------------------- diff --git a/41-02.md b/41-02.md index 28a5a9b..c1473ee 100644 --- a/41-02.md +++ b/41-02.md @@ -16,13 +16,13 @@ from Chapter 39) remains the same, and so is not shown again here. **LISTING 41.2 L41-2.C** /* Color-fills a convex polygon. All vertices are offset by (XOffset, YOffset). - “Convex” means “monotone with respect to a vertical line”; that is, every + "Convex" means "monotone with respect to a vertical line"; that is, every horizontal line drawn through the polygon at any point would cross exactly two active edges (neither horizontal lines nor zero-length edges count as active edges; both are acceptable anywhere in the polygon). Right & left edges may cross (polygons may be nonsimple). Polygons that are not convex according to - this definition won’t be drawn properly. (Yes, “convex” is a lousy name for - this type of polygon, but it’s convenient; use “monotone-vertical” if it makes + this definition won't be drawn properly. (Yes, "convex" is a lousy name for + this type of polygon, but it's convenient; use "monotone-vertical" if it makes you happier!) ******************************************************************* NOTE: the low-level drawing routine, DrawHorizontalLineList, must be able to @@ -31,14 +31,14 @@ from Chapter 39) remains the same, and so is not shown again here. highest coordinate to draw). In both respects, this differs from low-level drawing routines presented in earlier columns; changes are necessary to make it possible to draw nonsimple monotone-vertical polygons; that in turn makes it - possible to use Jim Kent’s test for monotone-vertical polygons. + possible to use Jim Kent's test for monotone-vertical polygons. ******************************************************************* Returns 1 for success, 0 if memory allocation failed */ #include #include #include - #include “polygon.h” + #include "polygon.h" /* Advances the index by one vertex forward through the vertex list, wrapping at the end of the list */ @@ -86,14 +86,14 @@ from Chapter 39) remains the same, and so is not shown again here. /* Set the # of scan lines in the polygon, skipping the bottom edge */ if ((WorkingHLineList.Length = MaxPoint_Y - MinPoint_Y) <= 0) - return(1); /* there’s nothing to draw, so we’re done */ + return(1); /* there's nothing to draw, so we're done */ WorkingHLineList.YStart = YOffset + MinPoint_Y; /* Get memory in which to store the line list we generate */ if ((WorkingHLineList.HLinePtr = (struct HLine *) (malloc(sizeof(struct HLine) * WorkingHLineList.Length))) == NULL) - return(0); /* couldn’t get memory for the line list */ + return(0); /* couldn't get memory for the line list */ /* Scan the first edge and store the boundary points in the list */ /* Initial pointer for storing scan converted first-edge coords */ @@ -126,7 +126,7 @@ from Chapter 39) remains the same, and so is not shown again here. /* Draw the line list representing the scan converted polygon */ DrawHorizontalLineList(&WorkingHLineList, Color); - /* Release the line list’s memory and we’re successfully done */ + /* Release the line list's memory and we're successfully done */ free(WorkingHLineList.HLinePtr); return(1); } diff --git a/41-03.md b/41-03.md index 317d9b3..b256de4 100644 --- a/41-03.md +++ b/41-03.md @@ -4,7 +4,7 @@ **LISTING 41.3 L41-3.ASM** - ; Draws all pixels in list of horizontal lines passed in, in mode 13h, VGA’s + ; Draws all pixels in list of horizontal lines passed in, in mode 13h, VGA's ; 320x200 256-color mode. Uses REP STOS to fill each line. ; ****************************************************************** ; NOTE: is able to reverse the X coords for a scan line, if necessary, to make @@ -40,9 +40,9 @@ public _DrawHorizontalLineList align 2 _DrawHorizontalLineList proc - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to our stack frame - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di cld ;make string instructions inc pointers @@ -57,14 +57,14 @@ ; for the first (top) horizontal line mov si,[si+Lngth] ;# of scan lines to draw and si,si ;are there any lines to draw? - jz FillDone ;no, so we’re done + jz FillDone ;no, so we're done mov al,byte ptr [bp+Color] ;color with which to fill mov ah,al ;duplicate color for STOSW FillLoop: mov di,[bx+XStart] ;left edge of fill on this line mov cx,[bx+XEnd] ;right edge of fill cmp di,cx ;is XStart > XEnd? - jle NoSwap ;no, we’re all set + jle NoSwap ;no, we're all set xchg di,cx ;yes, so swap edges NoSwap: sub cx,di ;width of fill on this line @@ -79,7 +79,7 @@ MainFill: shr cx,1 ;# of words in fill rep stosw ;fill as many words as possible - adc cx,cx ;1 if there’s an odd trailing byte to + adc cx,cx ;1 if there's an odd trailing byte to ; do, 0 otherwise rep stosb ;fill any odd trailing byte LineFillDone: @@ -88,17 +88,17 @@ dec si ;count off lines to fill jnz FillLoop FillDone: - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _DrawHorizontalLineList endp end -Listing 41.4 is almost identical to Listing 40.1 from Chapter 40. I’ve +Listing 41.4 is almost identical to Listing 40.1 from Chapter 40. I've modified Listing 40.1 to employ the vertical-monotone detection test -we’ve been talking about and use the fast vertical-monotone drawing code -whenever possible; that’s what Listing 41.4 is. Note well that Listing +we've been talking about and use the fast vertical-monotone drawing code +whenever possible; that's what Listing 41.4 is. Note well that Listing 40.5 from Chapter 40 is also required in order for this code to link. Listing 41.5 is an appropriately updated version of the POLYGON.H header file. diff --git a/41-04.md b/41-04.md index 08461d8..94c196a 100644 --- a/41-04.md +++ b/41-04.md @@ -11,10 +11,10 @@ failed. All C code tested with Borland C++. If the polygon shape is known in advance, speedier processing may be - enabled by specifying the shape as follows: “convex” - a rubber band + enabled by specifying the shape as follows: "convex" - a rubber band stretched around the polygon would touch every vertex in order; - ”nonconvex” - the polygon is not self-intersecting, but need not be - convex; “complex” - the polygon may be self-intersecting, or, indeed, + "nonconvex" - the polygon is not self-intersecting, but need not be + convex; "complex" - the polygon may be self-intersecting, or, indeed, any sort of polygon at all. Complex will work for all polygons; convex is fastest. Undefined results will occur if convex is specified for a nonconvex or complex polygon. @@ -32,7 +32,7 @@ #else /* MSC */ #include #endif - #include “polygon.h” + #include "polygon.h" #define SWAP(a,b) {temp = a; a = b; b = temp;} @@ -84,7 +84,7 @@ if ((EdgeTableBuffer = (struct EdgeState *) (malloc(sizeof(struct EdgeState) * VertexList->Length))) == NULL) - return(0); /* couldn’t get memory for the edge table */ + return(0); /* couldn't get memory for the edge table */ /* Build the global edge table */ BuildGET(VertexList, EdgeTableBuffer, XOffset, YOffset); /* Scan down through the polygon edges, one scan line at a time, @@ -98,7 +98,7 @@ XSortAET(); /* resort on X */ CurrentY++; /* advance to the next scan line */ } - /* Release the memory we’ve allocated and we’re done */ + /* Release the memory we've allocated and we're done */ free(EdgeTableBuffer); return(1); } @@ -138,9 +138,9 @@ SWAP(StartX, EndX); SWAP(StartY, EndY); } - /* Skip if this can’t ever be an active edge (has 0 height) */ + /* Skip if this can't ever be an active edge (has 0 height) */ if ((DeltaY = EndY - StartY) != 0) { - /* Allocate space for this edge’s info, and fill in the + /* Allocate space for this edge's info, and fill in the structure */ NewEdgePtr = NextFreeEdgeStruc++; NewEdgePtr->XDirection = /* direction in which X moves */ @@ -224,9 +224,9 @@ /* This edge is finished, so remove it from the AET */ *CurrentEdgePtr = CurrentEdge->NextEdge; } else { - /* Advance the edge’s X coordinate by minimum move */ + /* Advance the edge's X coordinate by minimum move */ CurrentEdge->X += CurrentEdge->WholePixelXMove; - /* Determine whether it’s time for X to advance one extra */ + /* Determine whether it's time for X to advance one extra */ if ((CurrentEdge->ErrorTerm += CurrentEdge->ErrorTermAdjUp) > 0) { CurrentEdge->X += CurrentEdge->XDirection; @@ -244,7 +244,7 @@ int CurrentX; /* The GET is Y sorted. Any edges that start at the desired Y - coordinate will be first in the GET, so we’ll move edges from + coordinate will be first in the GET, so we'll move edges from the GET to AET until the first edge left in the GET is no longer at the desired Y coordinate. Also, the GET is X sorted within each Y coordinate, so each successive edge we add to the AET is @@ -331,11 +331,11 @@ Is monotone-vertical polygon detection worth all this trouble? Under the right circumstances, you bet. In a situation where a great many polygons -are being drawn, and the application either doesn’t know whether they’re +are being drawn, and the application either doesn't know whether they're monotone-vertical or has no way to tell the polygon filler that they are, performance can be increased considerably if most polygons are, in fact, monotone-vertical. This potential performance advantage is helped -along by the surprising fact that Jim’s test for monotone-vertical +along by the surprising fact that Jim's test for monotone-vertical status is simpler and faster than my original, nonfunctional test for convexity. diff --git a/42-01.md b/42-01.md index 810982f..11175d0 100644 --- a/42-01.md +++ b/42-01.md @@ -3,24 +3,24 @@ ------------------------ --------------------------------- -------------------- Chapter 42\ - Wu’ed in Haste; Fried, Stewed at Leisure {#Heading1} + Wu'ed in Haste; Fried, Stewed at Leisure {#Heading1} ----------------------------------------- -### Fast Antialiased Lines Using Wu’s Algorithm {#Heading2} +### Fast Antialiased Lines Using Wu's Algorithm {#Heading2} The thought first popped into my head as I unenthusiastically picked -through the salad bar at a local “family” restaurant, trying to decide +through the salad bar at a local "family" restaurant, trying to decide whether the meatballs, the fried clams, or the lasagna was likely to shorten my life the least. I decided on the chicken in mystery sauce. -The thought recurred when my daughter asked, “Dad, is that fried -chicken?” +The thought recurred when my daughter asked, "Dad, is that fried +chicken?" -“I don’t think so,” I said. “I think it’s stewed chicken.” +"I don't think so," I said. "I think it's stewed chicken." -“It looks like fried chicken.” +"It looks like fried chicken." -“Maybe it’s fried, stewed chicken,” my wife volunteered hopefully. I +"Maybe it's fried, stewed chicken," my wife volunteered hopefully. I took a bite. It was, indeed, fried, stewed chicken. I can now, unhesitatingly and without reservation, recommend that you avoid fried, stewed chicken at all costs. @@ -32,15 +32,15 @@ Schumacher, is *appropriate technology*. For a family on a budget, with a small child, tired of staring at each other over the kitchen table, this was a perfect place to eat. It was cheap, it had greasy food and ice cream, no one cared if children dropped things or talked loudly or -walked around, and, most important of all, it wasn’t home. So what if +walked around, and, most important of all, it wasn't home. So what if the food was lousy? Good food was a luxury, a bonus; everything on the above list was necessary. A family restaurant was the appropriate dining-out technology, given the parameters within which we had to work. When I read through SIGGRAPH proceedings and other state-of-the-art -computer-graphics material, all too often I feel like I’m dining at a +computer-graphics material, all too often I feel like I'm dining at a four-star restaurant with two-year-old triplets and an empty wallet. -We’re talking incredibly inappropriate technology for PC graphics here. +We're talking incredibly inappropriate technology for PC graphics here. Sure, I say to myself as I read about an antialiasing technique, that sounds wonderful—if I had 24-bpp color, and dedicated hardware to do the processing, and all day to wait to generate one image. Yes, I think, @@ -50,31 +50,31 @@ Graphics* is riveting, but, alas, pretty much useless on PCs. When an x86 has to do all the work, speed becomes the overriding parameter, especially for real-time graphics. -Literature that’s applicable to fast PC graphics is hard enough to find, -but what we’d really like is above-average image quality combined with -terrific speed, and there’s almost no literature of that sort around. +Literature that's applicable to fast PC graphics is hard enough to find, +but what we'd really like is above-average image quality combined with +terrific speed, and there's almost no literature of that sort around. There is some, however, and you folks are right on top of it. For example, alert reader Michael Chaplin, of San Diego, wrote to suggest that I might enjoy the line-antialiasing algorithm presented in Xiaolin -Wu’s article, “An Efficient Antialiasing Technique,” in the July 1991 +Wu's article, "An Efficient Antialiasing Technique," in the July 1991 issue of *Computer Graphics*. Michael was dead-on right. This is a great algorithm, combining excellent antialiased line quality with speed -that’s close to that of non-antialiased Bresenham’s line drawing. This +that's close to that of non-antialiased Bresenham's line drawing. This is the sort of algorithm that makes you want to go out and write a wire-frame animation program, just so you can see how good those smooth lines look in motion. Wu antialiasing is a wonderful example of what can be accomplished on inexpensive, mass-market hardware with the proper -programming perspective. In short, it’s a splendid example of +programming perspective. In short, it's a splendid example of appropriate technology for PCs. ### Wu Antialiasing {#Heading3} -Antialiasing, as we’ve been discussing for the past few chapters, is the +Antialiasing, as we've been discussing for the past few chapters, is the process of smoothing lines and edges so that they appear less jagged. Antialiasing is partly an aesthetic issue, because it makes images more -attractive. It’s also partly an accuracy issue, because it makes it +attractive. It's also partly an accuracy issue, because it makes it possible to position and draw images with effectively more precision -than the resolution of the display. Finally, it’s partly a flat-out +than the resolution of the display. Finally, it's partly a flat-out necessity, to avoid the horrible, crawling, jagged edges of temporal aliasing when performing animation. @@ -83,8 +83,8 @@ the algorithm steps one pixel unit at a time along the major (longer) axis of a line, it draws the two pixels bracketing the line along the minor axis at each point. Each of the two bracketing pixels is drawn with a weighted fraction of the full intensity of the drawing color, -with the weighting for each pixel equal to one minus the pixel’s -distance along the minor axis from the ideal line. Yes, it’s a mouthful, +with the weighting for each pixel equal to one minus the pixel's +distance along the minor axis from the ideal line. Yes, it's a mouthful, but Figure 42.1 illustrates the concept. The intensities of the two pixels that bracket the line are selected so @@ -100,7 +100,7 @@ jagged collection of pixel pairs, but as a smooth line centered on the ideal line. Thus, by weighting the bracketing pixels properly at each step, we can readily produce what looks like a smooth line at precisely the right location, rather than the jagged pattern of line segments that -non-antialiased line-drawing algorithms such as Bresenham’s (see +non-antialiased line-drawing algorithms such as Bresenham's (see Chapters 35, 36, and 37) trace out. ![](images/42-01.jpg)\ @@ -114,7 +114,7 @@ calculations involve only a few shifts, XORs, and adds; for all practical purposes, tracing and weighting are rolled into one step—and a very fast step it is. How fast is it? On a 33-MHz 486 with a fast VGA, a good but not maxed-out assembly implementation of Wu antialiasing draws -a more than respectable 5,000 150-pixel-long vectors per second. That’s +a more than respectable 5,000 150-pixel-long vectors per second. That's especially impressive considering that about 1,500,000 actual pixels are drawn per second, meaning that Wu antialiasing is drawing at around 50 percent of the maximum memory bandwidth—half the fastest theoretically diff --git a/42-02.md b/42-02.md index 4ebc2e1..44462b3 100644 --- a/42-02.md +++ b/42-02.md @@ -12,7 +12,7 @@ a simple, fixed-point algorithm. The move along the minor axis with respect to a one-pixel move along the major axis (the line slope for lines with slopes less than 1, 1/slope for lines with slopes greater than 1) is calculated with a single integer divide. This value, called -the “error adjust,” is stored as a fixed-point fraction, in 0.16 format +the "error adjust," is stored as a fixed-point fraction, in 0.16 format (that is, all bits are fractional, and the decimal point is just to the left of bit 15). An error accumulator, also in 0.16 format, is initialized to 0. Then the first pixel is drawn; no weighting is needed, @@ -21,7 +21,7 @@ because the line intersects its endpoints exactly. Now the error adjust is added to the error accumulator. The error accumulator indicates how far between pixels the line has progressed along the minor axis at any given step; when the error accumulator turns -over, it’s time to advance one pixel along the minor axis. At each step +over, it's time to advance one pixel along the minor axis. At each step along the line, the major-axis coordinate advances by one pixel. The two bracketing pixels to draw are simply the two pixels nearest the line along the minor axis. For instance, if X is the current major-axis @@ -36,7 +36,7 @@ So far, nothing special; but now we come to the true wonder of Wu antialiasing. We know which pair of pixels to draw at each step along the line, but we also need to generate the two proper intensities, which must be inversely proportional to distance from the ideal line and sum -to 1, and that’s a potentially time-consuming operation. Let’s assume, +to 1, and that's a potentially time-consuming operation. Let's assume, however, that the number of possible intensity levels to be used for weighting is the value NumLevels = 2^n^ for some integer n, with the minimum weighting (0 percent intensity) being the value 2^n^ -1, and the @@ -47,18 +47,18 @@ shown in Figure 42.2. Better yet, 2^n^-1 minus the intensity of the first pixel selects the intensity of the other pixel in the pair, because the intensities of the two pixels must sum to 1; as it happens, this result can be obtained simply by flipping the n least-significant -bits of the first pixel’s value. All this works because what the error -accumulator accumulates is precisely the ideal line’s current distance +bits of the first pixel's value. All this works because what the error +accumulator accumulates is precisely the ideal line's current distance between the two bracketing pixels. ![](images/42-02.jpg)\ **Figure 42.2**  *Wu intensity calculations.* The intensity calculations take longer to describe than they do to -perform. All that’s involved is a shift of the error accumulator to +perform. All that's involved is a shift of the error accumulator to right-justify the desired intensity weighting bits, and then an XOR to -flip the least-significant n bits of the first pixel’s value in order to -generate the second pixel’s value. Listing 42.1 illustrates just how +flip the least-significant n bits of the first pixel's value in order to +generate the second pixel's value. Listing 42.1 illustrates just how efficient Wu antialiasing is; the intensity calculations take only three statements, and the entire Wu line-drawing loop is only nine statements long. Of course, a single C statement can hide a great deal of diff --git a/42-03.md b/42-03.md index cd8d32b..fdd490f 100644 --- a/42-03.md +++ b/42-03.md @@ -5,11 +5,11 @@ ### Sample Wu Antialiasing {#Heading5} 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 +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. +**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 a426002..effac8e 100644 --- a/42-04.md +++ b/42-04.md @@ -71,16 +71,16 @@ } while (--DeltaX); } -Listing 42.1 isn’t particularly fast, because it calls **DrawPixel()** +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 256-color mode on SuperVGAs built around the Tseng Labs ET4000 chip with -at least 512K of display memory installed. It’s well worth checking out +at least 512K of display memory installed. It's well worth checking out Wu antialiasing at 640x480. Although antialiased lines look much -smoother than normal lines at 320x200 resolution, they’re far from -perfect, because the pixels are so big that the eye can’t blend them +smoother than normal lines at 320x200 resolution, they're far from +perfect, because the pixels are so big that the eye can't blend them properly. At 640x480, however, Wu-antialiased lines look fabulous; from a couple of feet away, they look as straight and smooth as if they were drawn with a ruler. @@ -136,10 +136,10 @@ says that 32 intensities are enough; on my system, eight and even four levels looked pretty good. I found that gamma correction, which gives linearly spaced intensity steps, improved antialiasing quality significantly. Fortunately, we can program the palette with -gamma-corrected values, so our drawing code doesn’t have to do any extra +gamma-corrected values, so our drawing code doesn't have to do any extra work. -Listing 42.1 isn’t very fast, so I implemented Wu antialiasing in +Listing 42.1 isn't very fast, so I implemented Wu antialiasing in assembly, hard-coded for mode 13H. The implementation is shown in full in Listing 42.6. High-speed graphics code and fast VGAs go together like peanut butter and jelly, which is to say very well indeed; the assembly diff --git a/42-05.md b/42-05.md index 4ab3d9c..73a9201 100644 --- a/42-05.md +++ b/42-05.md @@ -276,21 +276,21 @@ #### Notes on Wu Antialiasing {#Heading6} -Wu antialiasing can be applied to any curve for which it’s possible to +Wu antialiasing can be applied to any curve for which it's possible to calculate at each step the positions and intensities of two bracketing pixels, although the implementation will generally be nowhere near as -efficient as it is for lines. However, Wu’s article in *Computer +efficient as it is for lines. However, Wu's article in *Computer Graphics* does describe an efficient algorithm for drawing antialiased circles. Wu also describes a technique for antialiasing solids, such as -filled circles and polygons. Wu’s approach biases the edges of filled +filled circles and polygons. Wu's approach biases the edges of filled objects outward. Although this is no good for adjacent polygons of the -sort used in rendering, it’s certainly possible to design a more -accurate polygon-antialiasing approach around Wu’s basic weighting +sort used in rendering, it's certainly possible to design a more +accurate polygon-antialiasing approach around Wu's basic weighting technique. The results would not be quite so good as more sophisticated antialiasing techniques, but they would be much faster. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *In general, the results obtained by Wu antialiasing are only so-so, by theoretical measures. Wu antialiasing amounts to a simple box filter placed over a fixed-point step approximation of a line, and that process introduces a good deal of deviation from the ideal. On the other hand, Wu notes that even a 10 percent error in intensity doesn’t lead to noticeable loss of image quality, and for Wu-antialiased lines up to 1K pixels in length, the error is under 10 percent. If it looks good, it is good—and it looks good.* + ![](images/i.jpg) *In general, the results obtained by Wu antialiasing are only so-so, by theoretical measures. Wu antialiasing amounts to a simple box filter placed over a fixed-point step approximation of a line, and that process introduces a good deal of deviation from the ideal. On the other hand, Wu notes that even a 10 percent error in intensity doesn't lead to noticeable loss of image quality, and for Wu-antialiased lines up to 1K pixels in length, the error is under 10 percent. If it looks good, it is good—and it looks good.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- With a 16-bit error accumulator, fixed-point inaccuracy becomes a @@ -300,13 +300,13 @@ lines of any practical length. In the listings, I have chosen to truncate, rather than round, the error-adjust value. This increases the intensity error of the line but -guarantees that fixed-point inaccuracy won’t cause the minor axis to +guarantees that fixed-point inaccuracy won't cause the minor axis to advance past the endpoint. Overrunning the endpoint would result in the -drawing of pixels outside the line’s bounding box, and potentially even +drawing of pixels outside the line's bounding box, and potentially even in an attempt to access pixels off the edge of the bitmap. -Finally, I should mention that, as published, Wu’s algorithm draws lines -symmetrically, from both ends at once. I haven’t done this for a number +Finally, I should mention that, as published, Wu's algorithm draws lines +symmetrically, from both ends at once. I haven't done this for a number of reasons, not least of which is that symmetric drawing is an inefficient way to draw lines that span banks on banked Super-VGAs. Banking aside, however, symmetric drawing is potentially faster, because diff --git a/43-01.md b/43-01.md index 6dfbd88..ee68926 100644 --- a/43-01.md +++ b/43-01.md @@ -8,7 +8,7 @@ Chapter 43\ ### A Simple and Extremely Fast Animation Method for Limited Color {#Heading2} -When it comes to computers, my first love is animation. There’s nothing +When it comes to computers, my first love is animation. There's nothing quite like the satisfaction of fooling the eye and creating a miniature reality simply by rearranging a few bytes of display memory. What makes animation particularly interesting is that it has to happen fast (as @@ -18,28 +18,28 @@ constraints make animation the toughest graphics challenge—and also the most rewarding. It pains me to hear industry pundits rag on the PC when it comes to -animation. Okay, I’ll grant you that the PC isn’t a Silicon Graphics +animation. Okay, I'll grant you that the PC isn't a Silicon Graphics workstation and never will be, but then neither is anything else on the market. The VGA offers good resolution and color, and while the hardware -wasn’t *designed* for animation, that doesn’t mean we can’t put it to +wasn't *designed* for animation, that doesn't mean we can't put it to work in that capacity. One lesson that any good PC graphics or assembly -programmer learns quickly is that it’s what the PC’s hardware *can* -do—not what it was intended to do—that’s important. (By the way, if I +programmer learns quickly is that it's what the PC's hardware *can* +do—not what it was intended to do—that's important. (By the way, if I were to pick one aspect of the PC to dump on, it would be sound, not -animation. The PC’s sound circuity really is lousy, and it’s hard to +animation. The PC's sound circuity really is lousy, and it's hard to understand why that should be, given that a cheap sound chip—which even the almost-forgotten PC*jr*had—would have changed everything. I guess -IBM figured “serious” computer users would be put off by a computer that +IBM figured "serious" computer users would be put off by a computer that could make fun noises.) -Anyway, my point is that the PC’s animation capabilities are pretty -good. There’s a trick, though: You can only push the VGA to its +Anyway, my point is that the PC's animation capabilities are pretty +good. There's a trick, though: You can only push the VGA to its animation limits by stretching your mind a bit and using some unorthodox approaches to animation. In fact, stretching your mind is the key to -producing good code for *any* task on the PC—that’s the topic of the -first part of this book. For most software, however, it’s not fatal if -your code isn’t excellent—there’s slow but functional software all over -the place. When it comes to VGA animation, though, you won’t get to +producing good code for *any* task on the PC—that's the topic of the +first part of this book. For most software, however, it's not fatal if +your code isn't excellent—there's slow but functional software all over +the place. When it comes to VGA animation, though, you won't get to first base without a clever approach. So, what clever approaches do I have in mind? All sorts. The resources @@ -49,20 +49,20 @@ effective animation. For example, refer back to Chapter 23 for an example of page flipping. Or look at the July 1986 issue of *PC Tech Journal*, which describes the basic block-move animation technique, or the August 1987 issue of *PC Tech Journal*, which shows a -software-sprite scheme built around the EGA’s vertical interrupt and the +software-sprite scheme built around the EGA's vertical interrupt and the AND-OR image drawing technique. Or look over the rest of this book, which contains dozens of tips and tricks that can be applied to animation, including Mode X-based techniques starting in Chapter 47 that are the basis for many commercial games. -This chapter adds yet another sort of animation to the list. We’re going +This chapter adds yet another sort of animation to the list. We're going 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 -other animation techniques, the techniques I’ll show you in this chapter +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. As with any technique on the PC, there are tradeoffs involved with @@ -70,10 +70,10 @@ bit-plane animation. While bit-plane animation is extremely attractive as far as performance and visual quality are concerned, it is somewhat limited. Bit-plane animation supports only four colors plus the background color at any one time, each image must consist of only one of -the four colors, and it’s preferable that images of the same color not +the four colors, and it's preferable that images of the same color not intersect. -It doesn’t much matter if bit-plane animation isn’t perfect for all +It doesn't much matter if bit-plane animation isn't perfect for all applications, though. The real point of showing you bit-plane animation is to bring home the reality that the VGA is a complex adapter with many resources, and that you can do remarkable things if you understand those @@ -86,14 +86,14 @@ The underlying principle of bit-plane animation is extremely simple. The VGA has four separate bit planes in modes 0DH, 0EH, 10H, and 12H. Plane 0 normally contains data for the blue component of pixel color, plane 1 normally contains green pixel data, plane 2 red pixel data, and plane 3 -intensity pixel data—but we’re going to mix that up a bit in a moment, -so we’ll simply refer to them as planes 0, 1, 2, and 3 from now on. +intensity pixel data—but we're going to mix that up a bit in a moment, +so we'll simply refer to them as planes 0, 1, 2, and 3 from now on. Each bit plane can be written to independently. The contents of the four bit planes are used to generate pixels, with the four bits that control the color of each pixel coming from the four planes. However, the bits from the planes go through a look-up stage on the way to becoming -pixels—they’re used to look up a 6-bit color from one of the sixteen +pixels—they're used to look up a 6-bit color from one of the sixteen palette registers. Figure 43.1 shows how the bits from the four planes feed into the palette registers to select the color of each pixel. (On the VGA specifically, the output of the palette registers goes to the @@ -102,18 +102,18 @@ and also Chapter A on the companion CD-ROM.) Take a good look at Figure 43.1. Any light bulbs going on over your head yet? If not, consider this. The general problem with VGA animation is -that it’s complex and time-consuming to manipulate images that span the -four planes (as most do), and that it’s hard to avoid interference +that it's complex and time-consuming to manipulate images that span the +four planes (as most do), and that it's hard to avoid interference problems when images intersect, since those images share the same bits in display memory. Since the four bit planes can be written to and read from independently, it should be apparent that if we could come up with a way to display images from each plane independently of whatever images are stored in the other planes, we would have four sets of images that we could manipulate very easily. There would be no interference effects -between images in different planes, because images in one plane wouldn’t -share bits with images in another plane. What’s more, since all the bits +between images in different planes, because images in one plane wouldn't +share bits with images in another plane. What's more, since all the bits for a given image would reside in a single plane, we could do away with -the cumbersome programming of the VGA’s complex hardware that is needed +the cumbersome programming of the VGA's complex hardware that is needed to manipulate images that span multiple planes. ![](images/43-01.jpg)\ @@ -123,7 +123,7 @@ All in all, it would be a good deal if we could store each image in a single plane, as shown in Figure 43.2. However, a problem arises when images in different planes overlap, as shown in Figure 43.3. The combined bits from overlapping images generate new colors, so the -overlapping parts of the images don’t look like they belong to either of +overlapping parts of the images don't look like they belong to either of the two images. What we really want, of course, is for one of the images to appear to be in front of the other. It would be better yet if the rearward image showed through any transparent (that is, diff --git a/43-02.md b/43-02.md index 525f9cb..21e4770 100644 --- a/43-02.md +++ b/43-02.md @@ -23,7 +23,7 @@ us transparency, where rearward images would show through holes within 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 +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 to. @@ -34,7 +34,7 @@ the highest precedence determines the color. The palette RAM settings for the colors described above are summarized in Table 43.1. Remember that the 4-bit values coming from display memory select which -palette register provides the actual pixel color. Given that, it’s easy +palette register provides the actual pixel color. Given that, it's easy to see that the rightmost 1-bit of the four bits coming from display memory in Table 43.1 selects the pixel color. If the bit from plane 0 is 1, then the color is red, no matter what the other bits are, as shown in @@ -161,11 +161,11 @@ Table 43.1 Palette RAM settings for bit-plane animation. ![](images/43-04.jpg)\ **Figure 43.4**  *How pixel precedence works.* -Seems almost too easy, doesn’t it? Nonetheless, it works beautifully, as -we’ll see very shortly. First, though, I’d like to point out that -there’s nothing sacred about plane 0 having precedence. We could +Seems almost too easy, doesn't it? Nonetheless, it works beautifully, as +we'll see very shortly. First, though, I'd like to point out that +there's nothing sacred about plane 0 having precedence. We could rearrange the palette register settings so that any plane had the -highest precedence, followed by the other planes in any order. I’ve +highest precedence, followed by the other planes in any order. I've chosen to make plane 0 the highest precedence only because it seems simplest to think of plane 0 as appearing in front of plane 1, which is in front of plane 2, which is in front of plane 3. diff --git a/43-03.md b/43-03.md index 5b50415..2963328 100644 --- a/43-03.md +++ b/43-03.md @@ -18,7 +18,7 @@ ; SLOWDOWN equ 10000 ; - ; Plane selects for the four colors we’re using. + ; Plane selects for the four colors we're using. ; RED equ 01h GREEN equ 02h @@ -34,15 +34,15 @@ SCREEN_WIDTH equ 80 ;# of bytes across screen SCREEN_HEIGHT equ 350 ;# of scan lines on screen WORD_OUTS_OK equ 1 ;set to 0 to assemble for - ; computers that can’t + ; computers that can't ; handle word outs to ; indexed VGA regs ; - stack segment para stack ‘STACK’ + stack segment para stack ‘STACK' db 512 dup (?) stack ends ; - ; Complete info about one object that we’re animating. + ; Complete info about one object that we're animating. ; ObjectStructure struc Delay dw ? ;used to delay for n passes @@ -72,7 +72,7 @@ ; word-aligned) ObjectStructure ends ; - Data segment word ‘DATA’ + Data segment word ‘DATA' ; ; Palette settings to give plane 0 precedence, followed by ; planes 1, 2, and 3. Plane 3 has the lowest precedence (is @@ -98,9 +98,9 @@ db 000h ;border color=black ; ; Image of a hollow square. - ; There’s an 8-pixel-wide blank border around all edges + ; There's an 8-pixel-wide blank border around all edges ; so that the image erases the old version of itself as - ; it’s moved and redrawn. + ; it's moved and redrawn. ; Square label byte dw 48,6 ;height in pixels, width in bytes @@ -147,9 +147,9 @@ ; ; Image of a hollow diamond with a smaller diamond in the ; middle. - ; There’s an 8-pixel-wide blank border around all edges + ; There's an 8-pixel-wide blank border around all edges ; so that the image erases the old version of itself as - ; it’s moved and redrawn. + ; it's moved and redrawn. ; Diamond label byte dw 48,6 ;height in pixels, width in bytes @@ -301,15 +301,15 @@ mov bx,offset ObjectList ;point to the first ; object in the list ; - ; For each object, see if it’s time to move and draw that + ; For each object, see if it's time to move and draw that ; object. ; ObjectLoop: ; - ; See if it’s time to move this object. + ; See if it's time to move this object. ; dec [bx+Delay] ;count down delay - jnz DoNextObject ;still delaying-don’t move + jnz DoNextObject ;still delaying-don't move mov ax,[bx+BaseDelay] mov [bx+Delay],ax ;reset delay for next time ; @@ -354,7 +354,7 @@ ; above, only one plane will be affected. ; mov si,[bx+Image] ;point to the - ; object’s image + ; object's image ; info call DrawObject ; @@ -374,7 +374,7 @@ loop DelayLoop endif ; - ; If a key’s been pressed, we’re done, otherwise animate + ; If a key's been pressed, we're done, otherwise animate ; again. ; CheckKey: @@ -436,7 +436,7 @@ ; solid cross line) BackdropRowLoop: mov cx,SCREEN_WIDTH/2 - rep stosw ;draw this scan line’s bit + rep stosw ;draw this scan line's bit ; of all the vertical lines ; on the screen dec dx diff --git a/43-04.md b/43-04.md index 40b4d15..0e16fa9 100644 --- a/43-04.md +++ b/43-04.md @@ -2,14 +2,14 @@ [Previous](43-03.html) [Table of Contents](index.html) [Next](43-05.html) ------------------------ --------------------------------- -------------------- -For those of you who haven’t experienced the frustrations of animation -programming on a PC, there’s a *whole* lot of animation going on in -Listing 43.1. What’s more, the animation is virtually flicker-free, +For those of you who haven't experienced the frustrations of animation +programming on a PC, there's a *whole* lot of animation going on in +Listing 43.1. What's more, the animation is virtually flicker-free, partly thanks to bit-plane animation and partly because images are never really erased but rather are simply overwritten. (The principle behind the animation is that of redrawing each image with a blank fringe around it when it moves, so that the blank fringe erases the part of the old -image that the new image doesn’t overwrite. For details on this sort of +image that the new image doesn't overwrite. For details on this sort of animation, see the above-mentioned *PC Tech Journal* July 1986 article.) Better yet, the red images take precedence over the green images, which take precedence over the blue images, which take precedence over the @@ -26,9 +26,9 @@ 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 -43.1 to slow things down to a reasonable pace. (It’s not a very good +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 +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 substantial delay (although it does look rather interesting at warp @@ -36,7 +36,7 @@ speed). We should all have such problems, eh? In fact, we could easily increase the number of animated images past 20 on that old AT, and well 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 +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 @@ -46,7 +46,7 @@ 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** 10H), function 10H, subfunction 2. -Bit-plane animation does have inherent limitations, which we’ll get to +Bit-plane animation does have inherent limitations, which we'll get to in a second. One limitation that is *not* inherent to bit-plane animation but simply a shortcoming of Listing 43.1 is somewhat choppy horizontal motion. In the interests of both clarity and keeping Listing @@ -57,7 +57,7 @@ support rotation. Unfortunately, it also meant that the smallest possible horizontal movement was 8 pixels (1 byte of display memory), which is far enough to be noticeable at certain speeds. The situation is, however, easily correctable with the additional rotations and code. -We’ll see an implementation of fully rotated images (in this case for +We'll see an implementation of fully rotated images (in this case for Mode X, but the principles generalize nicely) in Chapter 49. Vertically, where there is no byte-alignment issue, the images move 4 or 6 pixels at a times, resulting in considerably smoother animation. @@ -67,13 +67,13 @@ to support for internal animation, where the appearance of a given image changes over time to suggest that the image is an active entity. For example, propellers could whirl, jaws could snap, and jets could flare. Bit-plane animation with bit-aligned images and internal animation can -look truly spectacular. It’s a sight worth seeing, particularly for -those who doubt the PC’s worth when it comes to animation. +look truly spectacular. It's a sight worth seeing, particularly for +those who doubt the PC's worth when it comes to animation. ### Limitations of Bit-Plane Animation {#Heading6} -As I’ve said, bit-plane animation is not perfect. For starters, -bit-plane animation can only be used in the VGA’s planar modes, modes +As I've said, bit-plane animation is not perfect. For starters, +bit-plane animation can only be used in the VGA's planar modes, modes 0DH, 0EH, 10H, and 12H. Also, the reprogramming of the palette registers that provides image precedence also reduces the available color set from the normal 16 colors to just 5 (one color per plane plus the background @@ -91,7 +91,7 @@ plane 3 for bit-plane animation while using planes 0-2 for normal of the 8-color images. If we wanted the plane 3 images to be yellow, we could set up the palette registers as shown in Table 43.2. -As you can see, the color yellow is displayed whenever a pixel’s bit +As you can see, the color yellow is displayed whenever a pixel's bit from plane 3 is 1. This gives the images from plane 3 precedence, while leaving us with the 8 normal low-intensity colors for images drawn across the other 3 planes, as shown in Figure 43.5. Of course, this diff --git a/43-05.md b/43-05.md index 44bd759..88b2d14 100644 --- a/43-05.md +++ b/43-05.md @@ -2,7 +2,7 @@ [Previous](43-04.html) [Table of Contents](index.html) [Next](43-06.html) ------------------------ --------------------------------- -------------------- -Another limitation of bit-plane animation is that it’s best if images +Another limitation of bit-plane animation is that it's best if images stored in the same plane never cross each other. Why? Because when images do cross, the blank fringe @@ -85,20 +85,20 @@ Table 43.2 Palette RAM settings for two-plane animation. * * * * * around each image can temporarily erase the overlapped parts of the -other image or images, resulting in momentary flicker. While that’s not +other image or images, resulting in momentary flicker. While that's not fatal, it certainly detracts from the rock-solid animation effect of bit-plane animation. Not allowing images in the same plane to overlap is actually less of a limitation than it seems. Run Listing 43.1 again. Unless you were -looking for it, you’d never notice that images of the same color almost -never overlap—there’s plenty of action to distract the eye, and the +looking for it, you'd never notice that images of the same color almost +never overlap—there's plenty of action to distract the eye, and the trajectories of images of the same color are arranged so that they have a full range of motion without running into each other. The only exception is the chain of green images, which occasionally doubles back on itself when it bounces directly into a corner and reverses direction. Here, however, the images are moving so quickly that the brief moment -during which one image’s fringe blanks a portion of another image is +during which one image's fringe blanks a portion of another image is noticeable only upon close inspection, and not particularly unaesthetic even then. @@ -107,7 +107,7 @@ even then. When a technique has such tremendous visual and performance advantages as does bit-plane animation, it behooves you to design your animation -software so that the limitations of the animation technique don’t get in +software so that the limitations of the animation technique don't get in the way. For example, you might design a shooting gallery game with all the images in a given plane marching along in step in a continuous band. The images could never overlap, so bit-plane animation would produce @@ -124,7 +124,7 @@ memory. If the CRT controller scans memory faster than the CPU can modify that memory, then the CRT controller can scan out the bytes of display memory that have been already been changed, pass the point in the image that the CPU is currently drawing, and start scanning out -bytes that haven’t yet been changed. The result: Mismatched upper and +bytes that haven't yet been changed. The result: Mismatched upper and lower portions of the image. ------------------------ --------------------------------- -------------------- diff --git a/43-06.md b/43-06.md index ad15dfa..23a1c53 100644 --- a/43-06.md +++ b/43-06.md @@ -9,16 +9,16 @@ top and bottom parts of the image, as the CRT controller scans out first unchanged bytes and then changed bytes. Basically, shear will occasionally occur unless the CPU and CRT proceed at exactly the same rate, which is most unlikely. Shear is more noticeable when there are -fewer but larger images, since it’s more apparent when a larger screen -area is sheared, and because it’s easier to spot one out of three large +fewer but larger images, since it's more apparent when a larger screen +area is sheared, and because it's easier to spot one out of three large images momentarily shearing than one out of twenty small images. -Image shear isn’t terrible—I’ve written and sold several games in which -images occasionally shear, and I’ve never heard anyone complain—but +Image shear isn't terrible—I've written and sold several games in which +images occasionally shear, and I've never heard anyone complain—but neither is it ideal. One solution is page flipping, in which drawing is done to a non-displayed page of display memory while another page of display memory is shown on the screen. (We saw page flipping back in -Chapter 23, we’ll see it again in the next chapter, and we’ll use it +Chapter 23, we'll see it again in the next chapter, and we'll use it heavily starting in Chapter 47.) When the drawing is finished, the newly-drawn part of display memory is made the displayed page, so that the new screen becomes visible all at once, with no shearing or flicker. @@ -33,18 +33,18 @@ few drawbacks to page flipping, however. Page flipping requires two display memory buffers, one to draw in and one to display at any given time. Unfortunately, in mode 12H there just -isn’t enough memory for two buffers, so page flipping is not an option +isn't enough memory for two buffers, so page flipping is not an option in that mode. Also, page flipping requires that you keep the contents of both buffers up to date, which can require a good deal of extra drawing. -Finally, page flipping requires that you wait until you’re sure the page +Finally, page flipping requires that you wait until you're sure the page has flipped before you start drawing to the other page. Otherwise, you -could end up modifying a page while it’s still being displayed, +could end up modifying a page while it's still being displayed, defeating the whole purpose of page flipping. Waiting for pages to flip -takes time and can slow overall performance significantly. What’s more, -it’s sometimes difficult to be sure when the page has flipped, since not +takes time and can slow overall performance significantly. What's more, +it's sometimes difficult to be sure when the page has flipped, since not all VGA clones implement the display adapter status bits and page flip timing identically. @@ -57,11 +57,11 @@ to implement and perhaps a bit less reliable on some computers. Bit-plane animation is neat stuff. Heck, good animation of *any* sort is fun, and the PC is as good a place as any (well, almost any) to make -people’s jaws drop. (Certainly it’s the place to go if you want to make -a *lot* of jaws drop.) Don’t let anyone tell you that you can’t do good +people's jaws drop. (Certainly it's the place to go if you want to make +a *lot* of jaws drop.) Don't let anyone tell you that you can't do good animation on the PC. You can—*if* you stretch your mind to find ways to bring the full power of the VGA to bear on your applications. Bit-plane -animation isn’t for every task; neither are page flipping, +animation isn't for every task; neither are page flipping, exclusive-ORing, pixel panning, or any of the many other animation techniques you have available. One or more tricks from that grab-bag should give you what you need, though, and the bigger your grab-bag, the diff --git a/44-01.md b/44-01.md index 6b24ef7..e291e51 100644 --- a/44-01.md +++ b/44-01.md @@ -8,23 +8,23 @@ Chapter 44\ ### 640x480 Page Flipped Animation in 64K...Almost {#Heading2} -Almost doesn’t count, they say—at least in horseshoes and maybe a few +Almost doesn't count, they say—at least in horseshoes and maybe a few other things. This is especially true in digital circles, where if you need 12 MB of hard disk to install something and you only have 10 MB -left (a situation that seems to be some sort of eternal law) you’re +left (a situation that seems to be some sort of eternal law) you're stuck. -And that’s only infuriating until you dredge up the gumption to go in +And that's only infuriating until you dredge up the gumption to go in there and free up some space. How would you feel if you were up against -an “almost-but-not-quite” kind of a wall that couldn’t be breached by +an "almost-but-not-quite" kind of a wall that couldn't be breached by freeing up something elsewhere? Suppose you were within a few KB of implementing a wonderful VGA animation scheme that provided lots of screen space, square pixels, smooth motion and more than adequate speed—but all the memory you have is all there is? What would you do? -Scream a little. Or throw something that won’t break easily. Then you +Scream a little. Or throw something that won't break easily. Then you sit down and let your right brain do what it was designed to do. Sure -enough, there’s a way, and in this chapter I’ll explain how a little VGA +enough, there's a way, and in this chapter I'll explain how a little VGA secret called *page splitting* can save the day for page flipped animation in 640x480 mode. But to do that, I have to lay a little groundwork first. Or maybe a lot of groundwork. @@ -39,7 +39,7 @@ interprets the successive images as a single object in motion over time. The fact that the world is an analog realm and the images displayed on a computer screen consist of discrete pixels updated at a maximum rate of about 70 Hz is irrelevant; your eye can interpret both real-world images -and pixel patterns on the screen as objects in motion, and that’s that. +and pixel patterns on the screen as objects in motion, and that's that. One of the key problems of computer animation is that it takes time to redraw a screen, time during which the bitmap controlling the screen is @@ -63,13 +63,13 @@ these requirements are met by the program presented in Listings 44.1 and The listings taken together form a sample animation program, in which a single object bounces endlessly off other objects, with instructions and -a count of bounces displayed at the bottom of the screen. I’ll discuss +a count of bounces displayed at the bottom of the screen. I'll discuss various aspects of Listings 44.1 and 44.2 during the balance of this article. The listings are too complex and involve too much VGA and animation knowledge for for me to discuss it all in exhaustive detail -(and I’ve covered a lot of this stuff earlier in the book); instead, -I’ll cover the major elements, leaving it to you to explore the finer -points—and, hope, to experiment with and expand on the code I’ll +(and I've covered a lot of this stuff earlier in the book); instead, +I'll cover the major elements, leaving it to you to explore the finer +points—and, hope, to experiment with and expand on the code I'll provide. ------------------------ --------------------------------- -------------------- diff --git a/44-02.md b/44-02.md index 2a0a050..73b409f 100644 --- a/44-02.md +++ b/44-02.md @@ -220,14 +220,14 @@ DrawRect(0,1,3,SPLIT-LINES-1,15,SPLIT-START-OFFSET,SCREEN-SEG); DrawRect(SCREEN-PIXWIDTH-4,1,SCREEN-PIXWIDTH-1,SPLIT-LINES-1,15, SPLIT-START-OFFSET,SCREEN-SEG); - TextUp(“This is the split screen area...”,8,8,SPLIT-START-OFFSET, + TextUp("This is the split screen area...",8,8,SPLIT-START-OFFSET, SCREEN-SEG); - TextUp(“Bounces: ”,272,64,SPLIT-START-OFFSET,SCREEN-SEG); - TextUp(“\033: nudge left”,520,78,SPLIT-START-OFFSET,SCREEN-SEG); - TextUp(“\032: nudge right”,520,90,SPLIT-START-OFFSET,SCREEN-SEG); - TextUp(“\031: nudge down”,520,102,SPLIT-START-OFFSET,SCREEN-SEG); - TextUp(“\030: nudge up”,520,114,SPLIT-START-OFFSET,SCREEN-SEG); - TextUp(“Esc to end”,520,126,SPLIT-START-OFFSET,SCREEN-SEG); + TextUp("Bounces: ",272,64,SPLIT-START-OFFSET,SCREEN-SEG); + TextUp("\033: nudge left",520,78,SPLIT-START-OFFSET,SCREEN-SEG); + TextUp("\032: nudge right",520,90,SPLIT-START-OFFSET,SCREEN-SEG); + TextUp("\031: nudge down",520,102,SPLIT-START-OFFSET,SCREEN-SEG); + TextUp("\030: nudge up",520,114,SPLIT-START-OFFSET,SCREEN-SEG); + TextUp("Esc to end",520,126,SPLIT-START-OFFSET,SCREEN-SEG); } /* Turn on the split screen at the desired line (minus 1 because the @@ -281,7 +281,7 @@ } /* Update the bounce count display; turn over at 10000 */ if (++BounceCount >= 10000) { - TextUp(“0 ”,344,64,SPLIT-START-OFFSET,SCREEN-SEG); + TextUp("0 ",344,64,SPLIT-START-OFFSET,SCREEN-SEG); BounceCount = 0; } else { ShowBounceCount(); diff --git a/44-04.md b/44-04.md index 0bcb3b2..6eae941 100644 --- a/44-04.md +++ b/44-04.md @@ -4,7 +4,7 @@ Listing 44.1 is written in C. It could equally well have been written in assembly language, and would then have been somewhat faster. However, -wanted to make the point (as I’ve made again and again) that assembly +wanted to make the point (as I've made again and again) that assembly language, and, indeed, optimization in general, is needed only in the most critical portions of any program, and then only when the program would otherwise be too slow. Only in a highly performance-sensitive @@ -17,23 +17,23 @@ wait for the page to flip. #### Write Mode 3 {#Heading5 align="center"} -It’s possible to update the bitmap very efficiently on the VGA, because +It's possible to update the bitmap very efficiently on the VGA, because the VGA can draw up to 8 pixels at once, and because the VGA provides a number of hardware features to speed up drawing. This article makes considerable use of one particularly unusual hardware feature, write -mode 3. We discussed write mode 3 back in Chapter 26, but we’ve covered -a lot of ground since then—so I’m going to run through a quick refresher +mode 3. We discussed write mode 3 back in Chapter 26, but we've covered +a lot of ground since then—so I'm going to run through a quick refresher on write mode 3. -Some background: In the standard VGA’s high-resolution mode, mode 12H -(640x480 with 16 colors, the mode in which this chapter’s sample program +Some background: In the standard VGA's high-resolution mode, mode 12H +(640x480 with 16 colors, the mode in which this chapter's sample program runs), each byte of display memory controls 8 adjacent pixels on the screen. (The color of each pixel is, in turn, controlled by 4 bits spread across the four VGA memory planes, but we need not concern ourselves with that here.) Now, there will often be times when we want to change some but not all of the pixels controlled by a particular byte of display memory. This is not easily done, for there is no way to write -half a byte, or two bits, or such to memory; it’s the whole byte or none +half a byte, or two bits, or such to memory; it's the whole byte or none of it at all. You might think that using AND and OR to manipulate individual bits @@ -60,8 +60,8 @@ register; write mode 3 does that automatically.) Then, to draw individual pixels within a single byte, simply read display memory, and then write a byte to display memory with 1-bits where you want the color to be drawn and 0-bits where you want the current bitmap contents to be -preserved. (Note well that *the data actually read by the CPU doesn’t -matter;* the read operation latches all four planes’ data, as described +preserved. (Note well that *the data actually read by the CPU doesn't +matter;* the read operation latches all four planes' data, as described way back in Chapter 24.) So, for example, if write mode 3 is enabled and the Set/Reset register is set to 1 (blue), then the following sequence of operations: @@ -85,12 +85,12 @@ memory location in a single operation, as in: mov al,0f0h xchg es:[0],al -Again, the actual value that’s read is irrelevant. In general, the +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 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 -color, it’s not necessary to read before writing, because none of the +color, it's not necessary to read before writing, because none of the information in display memory at that byte needs to be preserved; a simple write of 0FFH (to draw all bits) will set all 8 pixels to the set/reset color: @@ -100,7 +100,7 @@ set/reset color: mov byte ptr es:[di],0ffh ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *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 written, in addition to the read and write of display memory, and **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 masking than the other write modes.* + ![](images/i.jpg) *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 written, in addition to the read and write of display memory, and **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 masking than the other write modes.* ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- In short, write mode 3 is a good choice for single-color drawing that @@ -111,19 +111,19 @@ drawing, in keeping with our desire for speedy screen updates. #### Drawing Text {#Heading6 align="center"} -We’ll need text in the sample application; is that also a good use for +We'll need text in the sample application; is that also a good use for write mode 3? Sometimes it is, but not in this particular case. Each character in a font is represented by a pattern of bits, with 1-bits representing character pixels and 0-bits representing background -pixels. Since we’ll be using the 8x8 font stored in the BIOS ROM (a +pixels. Since we'll be using the 8x8 font stored in the BIOS ROM (a pointer to which can be obtained by calling a BIOS service, as illustrated by Listing 44.2), each character is exactly 8 bits, or 1 -byte wide. We’ll further insist that characters be placed on byte +byte wide. We'll further insist that characters be placed on byte boundaries (that is, with their left edges only at pixels with X coordinates that are multiples of 8); this means that the character bytes in the font are automatically aligned with display memory, and no -rotation or clipping of characters is needed. Finally, we’ll draw all +rotation or clipping of characters is needed. Finally, we'll draw all text in white. ------------------------ --------------------------------- -------------------- diff --git a/44-05.md b/44-05.md index 9262061..d30942c 100644 --- a/44-05.md +++ b/44-05.md @@ -4,7 +4,7 @@ Given the above assumptions, drawing text is easy; we simply copy each byte of each character to the appropriate location in display memory, -and *voila*, we’re done. Text copying is done in write mode 0, in which +and *voila*, we're done. Text copying is done in write mode 0, in which the byte written to display memory is copied to all four planes at once; hence, 1-bits turn into white (color value 0FH, with 1-bits in all four planes), and 0-bits turn into black (color value 0). This is faster than @@ -13,29 +13,29 @@ memory (or at least preloading the latches with the background color), while the write mode 0 approach requires only a write to display memory. ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Is write mode 0 always the best way to do text? Not at all. The write mode 0 approach described above draws both foreground and background pixels within the character box, forcing the background pixels to black at the same time that it forces the foreground pixels to white. If you want to draw transparent text (that is, draw only the character pixels, not the surrounding background box), write mode 3 is ideal. Also, matters get far more complicated if characters that aren’t 8 pixels wide are drawn, or if characters are drawn starting at arbitrary pixel locations, without the multiple-of-8 column restriction, so that rotation and masking are required. Lastly, the Map Mask register can be used to draw text in colors other than white—but only if the background is black. Otherwise, the data remaining in the planes protected by the Map Mask will remain and can interfere with the colors of the text being drawn.* + ![](images/i.jpg) *Is write mode 0 always the best way to do text? Not at all. The write mode 0 approach described above draws both foreground and background pixels within the character box, forcing the background pixels to black at the same time that it forces the foreground pixels to white. If you want to draw transparent text (that is, draw only the character pixels, not the surrounding background box), write mode 3 is ideal. Also, matters get far more complicated if characters that aren't 8 pixels wide are drawn, or if characters are drawn starting at arbitrary pixel locations, without the multiple-of-8 column restriction, so that rotation and masking are required. Lastly, the Map Mask register can be used to draw text in colors other than white—but only if the background is black. Otherwise, the data remaining in the planes protected by the Map Mask will remain and can interfere with the colors of the text being drawn.* ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -I’m not going to delve any deeper into the considerable issues of +I'm not going to delve any deeper into the considerable issues of drawing VGA text; I just want to sensitize you to the existence of approaches other than the ones used in Listings 44.1 and 44.2. On the -VGA, the rule is: If there’s something you want to do, there probably +VGA, the rule is: If there's something you want to do, there probably are 10 ways to do it, each with unique strengths and weaknesses. Your mission, should you decide to accept it, is to figure out which one is best for your particular application. #### Page Flipping {#Heading7 align="center"} -Now that we know how to update the screen reasonably quickly, it’s time +Now that we know how to update the screen reasonably quickly, it's time to get on to the fun stuff. Page flipping answers the second requirement -for animation, by keeping bitmap changes off the screen until they’re +for animation, by keeping bitmap changes off the screen until they're complete. In other words, page flipping guarantees that partially updated bitmaps are never seen. How is it possible to update a bitmap without seeing the changes as -they’re made? Easy—with page flipping, there are *two* bitmaps; the +they're made? Easy—with page flipping, there are *two* bitmaps; the program shows you one bitmap while it updates the other. Conceptually, -it’s that simple. In practice, unfortunately, it’s not so simple, +it's that simple. In practice, unfortunately, it's not so simple, because of the design of the VGA. To understand why that is, we must look at how the VGA turns bytes in display memory into pixels on the screen. @@ -44,7 +44,7 @@ The VGA bitmap is a linear 64 K block of memory. (True, most adapters nowadays are SuperVGAs with more than 256 K of display memory, but every make of SuperVGA has its own way of letting you access that extra memory, so going beyond standard VGA is a daunting and difficult task. -Also, it’s hard to manipulate the large frame buffers of SuperVGA modes +Also, it's hard to manipulate the large frame buffers of SuperVGA modes fast enough for real-time animation.) Normally, the VGA picks up the first byte of memory (the byte at offset 0) and displays the corresponding 8 pixels on the screen, then picks up the byte at offset 1 @@ -57,8 +57,8 @@ next frame starts. So, for example, in mode 10H (640x350, 16 colors), a large enough bitmap to store a complete screen of information can be stored at display memory offsets 0 through 27,999, and *another* full bitmap could be stored at offsets 28,000 through 55,999, as shown in -Figure 44.1. (I’m discussing 640x350 mode at the moment for good reason; -we’ll get to 640x480 shortly.) When the Start Address registers are set +Figure 44.1. (I'm discussing 640x350 mode at the moment for good reason; +we'll get to 640x480 shortly.) When the Start Address registers are set to 0, the first bitmap (or page) is displayed; when they are set to 28,000, the second bitmap is displayed. Page flipped animation can be performed by displaying page 0 and drawing to page 1, then setting the diff --git a/44-06.md b/44-06.md index e16719d..c26c994 100644 --- a/44-06.md +++ b/44-06.md @@ -4,28 +4,28 @@ #### Knowing When to Flip {#Heading8 align="center"} -There’s a hitch, though, and that hitch is knowing exactly when it is -that the page has flipped. The page doesn’t flip the instant that you +There's a hitch, though, and that hitch is knowing exactly when it is +that the page has flipped. The page doesn't flip the instant that you set the Start Address registers. The VGA loads the starting offset from the Start Address registers once before starting each frame, then pays those registers no nevermind until the next frame comes around. This means that you can set the Start Address registers whenever you want—but -the page actually being displayed doesn’t change until after the VGA +the page actually being displayed doesn't change until after the VGA loads that new offset in preparation for the next frame. The potential problem should be obvious. Suppose that page 1 is being -displayed, and you’re updating page 0. You finish drawing to page 0, set +displayed, and you're updating page 0. You finish drawing to page 0, set the Start Address registers to 0 to switch to displaying page 0, and start updating page 1, which is no longer displayed. Or is it? If the VGA was in the middle of the current frame, displaying page 1, when you set the Start Address registers, then page 1 is going to be displayed for the rest of the frame, no matter what you do with the Start Address registers. If you start updating page 1 right away, any changes you make -may well show up on the screen, because page 0 hasn’t yet flipped to +may well show up on the screen, because page 0 hasn't yet flipped to being displayed in place of page 1—and that defeats the whole purpose of page flipping. -To avoid this problem, it is mandatory that you wait until you’re sure +To avoid this problem, it is mandatory that you wait until you're sure the page has flipped. The Start Address registers are, according to my tests, loaded at the start of the Vertical Sync signal, although that may not be the case with all VGA clones. The Vertical Sync status is @@ -42,14 +42,14 @@ Start Address High value for one page would be mixed with the Start Address Low value for the other page, and, depending on the start address values, the whole screen could appear to shift any number of pixels for a single, horrible frame. *This must never happen!* The -solution is to set the Start Address registers when you’re certain +solution is to set the Start Address registers when you're certain Vertical Sync is not about to start. The easiest way to know that is to check for the Display Enable status (bit 0 of the Input Status 1 register) being active; that means that bitmap-controlled pixels are being scanned onto the screen, and, since Vertical Sync happens in the middle of the vertical non-display portion of the frame, Vertical Sync can never be anywhere nearby if Display Enable is active. (Note that one -good alternative is to set up both pages with a start address that’s a +good alternative is to set up both pages with a start address that's a multiple of 256, and just change the Start Address High register and wait for Vertical Sync, with no Display Enable wait required.) @@ -69,20 +69,20 @@ farther each time and the like) if updates are happening too slowly. #### Enter the Split Screen {#Heading9} -So far, I’ve discussed page flipping in 640x350 mode. There’s a reason +So far, I've discussed page flipping in 640x350 mode. There's a reason for that: 640x350 is the highest-resolution standard mode in which -there’s enough display memory for two full pages on a standard VGA. It’s +there's enough display memory for two full pages on a standard VGA. It's possible to program the VGA to a non-standard 640x400 mode and still -have two full pages, but that’s pretty much the limit. One 640x480 page -takes 38,400 bytes of display memory, and clearly there isn’t enough +have two full pages, but that's pretty much the limit. One 640x480 page +takes 38,400 bytes of display memory, and clearly there isn't enough room in 64 K of display memory for two of *those* monster pages. And yet, 640x480 is a wonderful mode in many ways. It offers a 1:1 aspect ratio (square pixels), and it provides by far the best resolution -of any 16-color mode. Surely there’s *some* way to bring the visual +of any 16-color mode. Surely there's *some* way to bring the visual appeal of page flipping to this mode? -Surely there is—but it’s an odd solution indeed. The VGA has a feature, +Surely there is—but it's an odd solution indeed. The VGA has a feature, known as the *split screen,* that allows you to force the offset from which the VGA fetches video data back to 0 after any desired scan line. For example, you can program the VGA to scan through display memory as @@ -119,12 +119,12 @@ flipping gives you the best combination of square pixels and high resolution possible on a standard VGA. So. Is VGA animation worth all the fuss? *Mais oui.* Run the sample -program; if you’ve never seen aggressive VGA animation before, you’ll be +program; if you've never seen aggressive VGA animation before, you'll be amazed at how smooth it can be. Not every square millimeter of every animated screen must be in constant motion. Most graphics screens need a little quiet space to display scores, coordinates, file names, or (if -all else fails) company logos. If you don’t tell the user he’s/she’s -only getting 339 scan lines of animation, he’ll/she’ll probably never +all else fails) company logos. If you don't tell the user he's/she's +only getting 339 scan lines of animation, he'll/she'll probably never know. ------------------------ --------------------------------- -------------------- diff --git a/45-01.md b/45-01.md index 23185a6..45a022c 100644 --- a/45-01.md +++ b/45-01.md @@ -11,31 +11,31 @@ Chapter 45\ We brought our pets with us when we moved to Seattle. At about the same time, our Golden Retriever, Sam, observed his third birthday. Sam is relatively intelligent, in the sense that he is clearly smarter than a -banana slug, although if he were in the same room with Jeff Duntemann’s -dog Mr. Byte, there’s a reasonable chance that he would mistake Mr. Byte +banana slug, although if he were in the same room with Jeff Duntemann's +dog Mr. Byte, there's a reasonable chance that he would mistake Mr. Byte for something edible (a category that includes rocks, socks, and a surprising number of things too disgusting to mention), and Jeff would have to find a new source of things to write about. -But that’s not important now. What is important is that—and I am not +But that's not important now. What is important is that—and I am not making this up—this morning I managed to find the one pair of socks Sam -hadn’t chewed holes in. And what’s even more important is that after we +hadn't chewed holes in. And what's even more important is that after we moved and Sam turned three, he calmed down amazingly. We had been waiting for this magic transformation since Sam turned one, the age at which most puppies turn into normal dogs who lie around a lot, waking up -to eat their Science Diet (motto, “The dog food that costs more than the -average neurosurgeon makes in a year”) before licking themselves in +to eat their Science Diet (motto, "The dog food that costs more than the +average neurosurgeon makes in a year") before licking themselves in embarrassing places and going back to sleep. When Sam turned one and -remained hopelessly out of control we said, “Goldens take two years to -calm down,” as if we had a clue. When he turned two and remained -undeniably Sam we said, “Any day now.” By the time he turned three, we +remained hopelessly out of control we said, "Goldens take two years to +calm down," as if we had a clue. When he turned two and remained +undeniably Sam we said, "Any day now." By the time he turned three, we were reduced to figuring that it was only about seven more years until he expired, at which point we might be able to take all the fur he had shed in his lifetime and weave ourselves some clothes without holes in them, or quite possibly a house. But miracle of miracles, we moved, and Sam instantly turned into the dog -we thought we’d gotten when we forked over \$500—calm, sweet, and +we thought we'd gotten when we forked over \$500—calm, sweet, and obedient. Weeks went by, and Sam was, if anything, better than ever. Clearly, the change was permanent. @@ -43,22 +43,22 @@ And then we took Sam to the vet for his annual check-up and found that he had an ear infection. Thanks to the wonders of modern animal medicine, a \$5 bottle of liquid restored his health in just two days. And with his health, we got, as a bonus, the old Sam. You see, Sam -hadn’t changed. He was just tired from being sick. Now he once again +hadn't changed. He was just tired from being sick. Now he once again joyously knocks down any stranger who makes the mistake of glancing in his direction, and will, quite possibly, be booked any day now on suspicion of homicide by licking. ### Plus ça Change {#Heading3} -Okay, you give up. What exactly does this have to do with graphics? I’m +Okay, you give up. What exactly does this have to do with graphics? I'm glad you asked. The lesson to be learned from Sam, The Dog With A Brain -The Size Of A Walnut, is that while things may *look* like they’ve -changed, in fact they often haven’t. Take VGA performance. If you buy a -486 with a SuperVGA, you’ll get performance that knocks your socks off, +The Size Of A Walnut, is that while things may *look* like they've +changed, in fact they often haven't. Take VGA performance. If you buy a +486 with a SuperVGA, you'll get performance that knocks your socks off, especially if you run Windows. Things are liable to be so fast that -you’ll figure the SuperVGA has to deserve some of the credit. Well, -maybe it does if it’s a local-bus VGA. But maybe it doesn’t, even if it -is local bus—and it certainly doesn’t if it’s an ISA bus VGA, because no +you'll figure the SuperVGA has to deserve some of the credit. Well, +maybe it does if it's a local-bus VGA. But maybe it doesn't, even if it +is local bus—and it certainly doesn't if it's an ISA bus VGA, because no ISA bus VGA can run faster than about 300 nanoseconds per access, and VGAs capable of that speed have been common for at least a couple of years now. @@ -66,22 +66,22 @@ years now. Your 486 VGA system is fast almost entirely because it has a 486 in it. (486 systems with graphics accelerators such as the ATI Ultra or Diamond Stealth are another story altogether.) Underneath it all, the VGA is -still painfully slow—and if you have an old VGA or IBM’s original PS/2 -motherboard VGA, it’s incredibly slow. The fastest ISA-bus VGA around is +still painfully slow—and if you have an old VGA or IBM's original PS/2 +motherboard VGA, it's incredibly slow. The fastest ISA-bus VGA around is two to twenty times slower than system memory, and the slowest VGA around is as much as 100 times slower. In the old days, the rule was, -“Display memory is slow, and should be avoided.” Nowadays, the rule is, -“Display memory is not quite so slow, but should still be avoided.” +"Display memory is slow, and should be avoided." Nowadays, the rule is, +"Display memory is not quite so slow, but should still be avoided." -So, as I say, sometimes things don’t change. Of course, sometimes they +So, as I say, sometimes things don't change. Of course, sometimes they do change. For example, in just 49 dog years, I fully expect to own at least one pair of underwear without a single hole in it. Which brings -us, deus ex machina and the creek don’t rise, to yet another animation +us, deus ex machina and the creek don't rise, to yet another animation method: dirty-rectangle animation. ### VGA Access Times {#Heading4} -Actually, before we get to dirty rectangles, I’d like to take you +Actually, before we get to dirty rectangles, I'd like to take you through a quick refresher on VGA memory and I/O access times. I want to do this partly because the slow access times of the VGA make dirty-rectangle animation particularly attractive, and partly as a @@ -91,7 +91,7 @@ performance tests I recently ran. Table 45.1 shows the results of the aforementioned I/O performance tests, as run on two 486/33 SuperVGA systems under the Phar Lap 386|DOS-Extender. (The systems and VGAs are unnamed because this is a -not-very-scientific spot test, and I don’t want to unfairly malign, say, +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 @@ -104,7 +104,7 @@ 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 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, +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 VGAs are as much as 100 times slower than normal memory accesses, and diff --git a/45-02.md b/45-02.md index c784449..8855e03 100644 --- a/45-02.md +++ b/45-02.md @@ -5,15 +5,15 @@ Of course, VGA display memory has its own performance problems. The fastest ISA bus VGA can, at best, support sustained write times of about 10 cycles per word-sized write on a 486/33; 15 or 20 cycles is more -common, even for relatively fast SuperVGAs; the worst case I’ve seen is +common, even for relatively fast SuperVGAs; the worst case I've seen is 65 cycles per byte. However, intermittent writes, mixed with a lot of register and cache-only code, can effectively execute in one cycle, -thanks to the caching design of many VGAs and the 486’s 4-deep write +thanks to the caching design of many VGAs and the 486's 4-deep write buffer, which stores pending writes while the CPU continues executing instructions. Display memory reads tend to take longer, because -coprocessing isn’t possible—one microsecond is a reasonable rule of -thumb for VGA reads, although there’s considerable variation. So VGA -memory tends not to be as bad as VGA I/O, but lord knows it isn’t +coprocessing isn't possible—one microsecond is a reasonable rule of +thumb for VGA reads, although there's considerable variation. So VGA +memory tends not to be as bad as VGA I/O, but lord knows it isn't *good*. * * * * * @@ -102,8 +102,8 @@ bit-mask register. When you must use the bit mask, arrange drawing so that you can set the bit mask once, then do a lot of drawing with that mask. For example, draw a whole edge at once, then the middle, then the other edge, rather than setting the bit mask several times on each scan -line to draw the edge and middle bytes together. Don’t read from display -memory if you don’t have to. Write each pixel once and only once. +line to draw the edge and middle bytes together. Don't read from display +memory if you don't have to. Write each pixel once and only once. It is indeed a strange concept: The key to fast graphics is staying away from the graphics adapter as much as possible. @@ -111,12 +111,12 @@ from the graphics adapter as much as possible. ### Dirty-Rectangle Animation {#Heading5} The relative slowness of VGA hardware is part of the appeal of the -technique that I call “dirty-rectangle” animation, in which a complete +technique that I call "dirty-rectangle" animation, in which a complete copy of the contents of display memory is maintained in offscreen system (nondisplay) memory. All drawing is done to this system buffer. As offscreen drawing is done, a list is maintained of the bounding rectangles for the drawn-to areas; these are the *dirty rectangles*, -“dirty” in the sense that that have been altered and no longer match the +"dirty" in the sense that that have been altered and no longer match the contents of the screen. After all drawing for a frame is completed, all the dirty rectangles for that frame are copied to the screen in a burst, and then the cycle of off-screen drawing begins again. @@ -124,8 +124,8 @@ and then the cycle of off-screen drawing begins again. Why, exactly, would we want to go through all this complication, rather than simply drawing to the screen in the first place? The reason is visual quality. If we were to do all our drawing directly to the screen, -there’d be a lot of flicker as objects were erased and then redrawn. -Similarly, overlapped drawing done with the painter’s algorithm (in +there'd be a lot of flicker as objects were erased and then redrawn. +Similarly, overlapped drawing done with the painter's algorithm (in which farther objects are drawn first, so that nearer objects obscure them) would flicker as farther objects were visible for short periods. With dirty-rectangle animation, only the finished pixels for any given @@ -144,21 +144,21 @@ solves these problems. Well, then, if we want good visual quality, why not use page flipping? For one thing, not all adapters and all modes support page flipping. The -CGA and MCGA don’t, and neither do the VGA’s 640x480 16-color or 320x200 +CGA and MCGA don't, and neither do the VGA's 640x480 16-color or 320x200 256-color modes, or many SuperVGA modes. In contrast, *all* adapters support dirty-rectangle animation. Another advantage of dirty-rectangle -animation is that it’s generally faster. While it may seem strange that +animation is that it's generally faster. While it may seem strange that it would be faster to draw off-screen and then copy the result to the screen, that is often the case, because dirty-rectangle animation -usually reduces the number of times the VGA’s hardware needs to be +usually reduces the number of times the VGA's hardware needs to be touched, especially in 256-color modes. This reduction comes about because when dirty rectangles are erased, -it’s done in system memory, not in display memory, and since most +it's done in system memory, not in display memory, and since most objects move a good deal less than their full width (that is, the new and old positions overlap), display memory is written to fewer times than with page flipping. (In 16-color modes, this is not necessarily the -case, because of the parallelism obtained from the VGA’s planar +case, because of the parallelism obtained from the VGA's planar hardware.) Also, read/modify/write operations are performed in fast system memory rather than slow display memory, so display memory rarely needs to be read. This is particularly good because display memory is diff --git a/45-03.md b/45-03.md index 91b54cd..3d04783 100644 --- a/45-03.md +++ b/45-03.md @@ -6,29 +6,29 @@ Also, page flipping wastes a good deal of time waiting for the page to flip at the end of the frame. Dirty-rectangle animation never needs to wait for anything because partially drawn images are never present in display memory. Actually, in one sense, partially drawn images are -sometimes present because it’s possible for a rectangle to be partially +sometimes present because it's possible for a rectangle to be partially drawn when the scanning raster beam reaches that part of the screen. This causes the rectangle to appear partially drawn for one frame, -producing a phenomenon I call “shearing.” Fortunately, shearing tends +producing a phenomenon I call "shearing." Fortunately, shearing tends not to be particularly distracting, especially for fairly small images, but it can be a problem when copying large areas. This is one area in which dirty-rectangle animation falls short of page flipping, because page flipping has perfect display quality, never showing anything other than a completely finished frame. Similarly, dirty-rectangle copying may -take two or more frame times to finish, so even if shearing doesn’t -happen, it’s still possible to have the images in the various dirty +take two or more frame times to finish, so even if shearing doesn't +happen, it's still possible to have the images in the various dirty rectangles show up non-simultaneously. In my experience, this latter phenomenon is not a serious problem, but do be aware of it. ### Dirty Rectangles in Action {#Heading7} Listing 45.1 demonstrates dirty-rectangle animation. This is a very -simple implementation, in several respects. For one thing, it’s written +simple implementation, in several respects. For one thing, it's written entirely in C, and animation fairly cries out for assembly language. For another thing, it uses far pointers, which C often handles with less -than optimal efficiency, especially because I haven’t used library +than optimal efficiency, especially because I haven't used library functions to copy and fill memory. (I did this so the code would work in -any memory model.) Also, Listing 45.1 doesn’t attempt to coalesce +any memory model.) Also, Listing 45.1 doesn't attempt to coalesce rectangles so as to perform a minimum number of display-memory accesses; instead, it copies each dirty rectangle to the screen, even if it overlaps with another rectangle, so some pixels are copied multiple @@ -37,7 +37,7 @@ on my 486/33, 10 11x11 images animate at a very respectable clip. **LISTING 45.1 L45-1.C** - /* Sample simple dirty-rectangle animation program. Doesn’t attempt to coalesce + /* Sample simple dirty-rectangle animation program. Doesn't attempt to coalesce rectangles to minimize display memory accesses. Not even vaguely optimized! Tested with Borland C++ in the small model. */ @@ -75,7 +75,7 @@ on my 486/33, 10 11x11 images animate at a very respectable clip. /* If set to 1, ignore dirty rectangle list and copy the whole screen. */ int DrawWholeScreen = 0; - /* Pixels for image we’ll animate */ + /* Pixels for image we'll animate */ #define IMAGE_WIDTH 11 #define IMAGE_HEIGHT 11 char ImagePixels[] = { @@ -95,7 +95,7 @@ on my 486/33, 10 11x11 images animate at a very respectable clip. #define NUM_ENTITIES 10 Entity Entities[NUM_ENTITIES]; - /* pointer to system buffer into which we’ll draw */ + /* pointer to system buffer into which we'll draw */ char far *SystemBufferPtr; /* pointer to screen */ @@ -111,10 +111,10 @@ on my 486/33, 10 11x11 images animate at a very respectable clip. unsigned int TempCount; char far *TempPtr; union REGS regs; - /* Allocate memory for the system buffer into which we’ll draw */ + /* Allocate memory for the system buffer into which we'll draw */ if (!(SystemBufferPtr = farmalloc((unsigned int)SCREEN_WIDTH* SCREEN_HEIGHT))) { - printf("Couldn’t get memory\n"); + printf("Couldn't get memory\n"); exit(1); } /* Clear the system buffer */ @@ -125,7 +125,7 @@ on my 486/33, 10 11x11 images animate at a very respectable clip. /* Point to the screen */ ScreenPtr = MK_FP(SCREEN_SEGMENT, 0); - /* Set up the entities we’ll animate, at random locations */ + /* Set up the entities we'll animate, at random locations */ randomize(); for (i = 0; i < NUM_ENTITIES; i++) { Entities[i].X = random(SCREEN_WIDTH - IMAGE_WIDTH); @@ -284,7 +284,7 @@ on my 486/33, 10 11x11 images animate at a very respectable clip. /* Point to the destination in the system buffer */ RowPtr = SystemBufferPtr + (Entities[i].Y*SCREEN_WIDTH) + Entities[i].X; - /* Clear the entity’s rectangle */ + /* Clear the entity's rectangle */ for (j = 0; j < IMAGE_HEIGHT; j++) { /* Clear a row */ for (k = 0, TempPtr = RowPtr; k < IMAGE_WIDTH; k++) { diff --git a/45-04.md b/45-04.md index 05f2431..bb7964c 100644 --- a/45-04.md +++ b/45-04.md @@ -2,11 +2,11 @@ [Previous](45-03.html) [Table of Contents](index.html) [Next](45-05.html) ------------------------ --------------------------------- -------------------- -One point I’d like to make is that although the system-memory buffer in +One point I'd like to make is that although the system-memory buffer in Listing 45.1 has exactly the same dimensions as the screen bitmap, -that’s not a requirement, and there are some good reasons not to make +that's not a requirement, and there are some good reasons not to make the two the same size. For example, if the system buffer is bigger than -the area displayed on the screen, it’s possible to pan the visible area +the area displayed on the screen, it's possible to pan the visible area around the system buffer. Or, alternatively, the system buffer can be just the size of a desired window, representing a window into a larger, virtual buffer. We could then draw the desired portion of the virtual @@ -18,31 +18,31 @@ location. ![](images/i.jpg) *Another argument in favor of a small viewing window is that it restricts the amount of display memory actually drawn to. Restricting the display memory used for animation reduces the total number of display-memory accesses, which in turn boosts overall performance; it also improves the performance and appearance of panning, in which the whole window has to be redrawn or copied.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -If you keep a close watch, you’ll notice that many high-performance +If you keep a close watch, you'll notice that many high-performance animation games similarly restrict their full-featured animation area to -a relatively small region. Often, it’s hard to tell that this is the +a relatively small region. Often, it's hard to tell that this is the case, because the animation region is surrounded by flashy digitized graphics and by items such as scoreboards and status screens, but look -closely and see if the animation region in your favorite game isn’t +closely and see if the animation region in your favorite game isn't smaller than you thought. ### Hi-Res VGA Page Flipping {#Heading8} On a standard VGA, hi-res mode is mode 12H, which offers 640x480 -resolution with 16 colors. That’s a nice mode, with plenty of pixels, +resolution with 16 colors. That's a nice mode, with plenty of pixels, and square ones at that, but it lacks one thing—page flipping. The problem is that the mode 12H bitmap is 150 K in size, and the standard VGA has only 256 K total, too little memory for two of those monster mode 12H pages. With only one page, flipping is obviously out of the -question, and without page flipping, top-flight, hi-res animation can’t -be implemented. The standard fallback is to use the EGA’s hi-res mode, +question, and without page flipping, top-flight, hi-res animation can't +be implemented. The standard fallback is to use the EGA's hi-res mode, mode 10H (640x350, 16 colors) for page flipping, but this mode is less than ideal for a couple of reasons: It offers sharply lower vertical -resolution, and it’s lousy for handling scaled-up CGA graphics, because +resolution, and it's lousy for handling scaled-up CGA graphics, because the vertical resolution is a fractional multiple—1.75 times, to be exact—of that of the CGA. CGA resolution may not seem important these days, but many images were originally created for the CGA, as were many -graphics packages and games, and it’s at least convenient to be able to +graphics packages and games, and it's at least convenient to be able to handle CGA graphics easily. Then, too, 640x350 is also a poor multiple of the 200 scan lines of the popular 320x200 256-color mode 13H of the VGA. @@ -51,7 +51,7 @@ There are a couple of interesting, if imperfect, solutions to the problem of hi-res page flipping. One is to use the split screen to enable page flipping only in the top two-thirds of the screen; see the previous chapter for details, and for details on the mechanics of page -flipping generally. This doesn’t address the CGA problem, but it does +flipping generally. This doesn't address the CGA problem, but it does yield square pixels and a full 640x480 screen resolution, although not all those pixels are flippable and thus animatable. @@ -70,14 +70,14 @@ The key to 640x400 mode is understanding that on a VGA, mode 10H (640x350) is, at heart, a 400-scan-line mode. What I mean by that is that in mode 10H, the Vertical Total register, which controls the total number of scan lines, both displayed and nondisplayed, is set to 447, -exactly the same as in the VGA’s text modes, which do in fact support +exactly the same as in the VGA's text modes, which do in fact support 400 scan lines. A properly sized and centered display is achieved in mode 10H by setting the polarity of the sync pulses to tell the monitor to scan vertically at a faster rate (to make fewer lines fill the screen), by starting the overscan after 350 lines, and by setting the vertical sync and blanking pulses appropriately for the faster vertical -scanning rate. Changing those settings is all that’s required to turn -mode 10H into a 640x400 mode, and that’s easy to do, as illustrated by +scanning rate. Changing those settings is all that's required to turn +mode 10H into a 640x400 mode, and that's easy to do, as illustrated by Listing 45.2, which provides mode set code for 640x400 mode. **LISTING 45.2 L45-2.C** diff --git a/45-05.md b/45-05.md index bef9ba3..130e517 100644 --- a/45-05.md +++ b/45-05.md @@ -80,7 +80,7 @@ flipping. int i; for (i=0; i<30; i++) { - /* Wait until we’re not in vertical sync, so we can catch leading edge */ + /* Wait until we're not in vertical sync, so we can catch leading edge */ while ((inp(INPUT_STATUS_1) & 0x08) != 0) ; /* Wait until we are in vertical sync */ while ((inp(INPUT_STATUS_1) & 0x08) == 0) ; @@ -89,22 +89,22 @@ flipping. After I described 640x400 mode in a magazine article, Bill Lindley, of Mesa, Arizona, wrote me to suggest that when programming the VGA to a -nonstandard mode of this sort, it’s a good idea to tell the BIOS about +nonstandard mode of this sort, it's a good idea to tell the BIOS about the new screen size, for a couple of reasons. For one thing, pop-up -utilities often use the BIOS variables; Bill’s memory-resident screen +utilities often use the BIOS variables; Bill's memory-resident screen printer, EGAD Screen Print, determines the number of scan lines to print -by multiplying the BIOS “number of text rows” variable times the -“character height” variable. For another, the BIOS itself may do a poor +by multiplying the BIOS "number of text rows" variable times the +"character height" variable. For another, the BIOS itself may do a poor job of displaying text if not given proper information; the active text area may not match the screen dimensions, or an inappropriate graphics -font may be used. (Of course, the BIOS isn’t going to be able to display +font may be used. (Of course, the BIOS isn't going to be able to display text anyway in highly nonstandard modes such as Mode X, but it will do fine in slightly nonstandard modes such as 640x400 16-color mode.) In the case of the 640x400 16-color model described a little earlier, Bill suggests that the code in Listing 45.4 be called immediately after -putting the VGA into that mode to tell the BIOS that we’re working with +putting the VGA into that mode to tell the BIOS that we're working with 25 rows of 16-pixel-high text. I think this is an excellent suggestion; -it can’t hurt, and may save you from getting aggravating tech support +it can't hurt, and may save you from getting aggravating tech support calls down the road. **LISTING 45.4 L45-4.C** diff --git a/45-06.md b/45-06.md index df63ca8..9e99822 100644 --- a/45-06.md +++ b/45-06.md @@ -2,25 +2,25 @@ [Previous](45-05.html) [Table of Contents](index.html) [Next](46-01.html) ------------------------ --------------------------------- -------------------- -The 640x400 mode I’ve described here isn’t exactly earthshaking, but it -can come in handy for page flipping and CGA emulation, and I’m sure that +The 640x400 mode I've described here isn't exactly earthshaking, but it +can come in handy for page flipping and CGA emulation, and I'm sure that some of you will find it useful at one time or another. ### Another Interesting Twist on Page Flipping {#Heading9} -I’ve spent a fair amount of time exploring various ways to do animation. +I've spent a fair amount of time exploring various ways to do animation. I thought I had pegged all the possible ways to do animation: exclusive-ORing; simply drawing and erasing objects; drawing objects -with a blank fringe to erase them at their old locations as they’re +with a blank fringe to erase them at their old locations as they're drawn; page flipping; and, finally, drawing to local memory and copying -the dirty (modified) rectangles to the screen, as I’ve discussed in this +the dirty (modified) rectangles to the screen, as I've discussed in this chapter. To my surprise, someone threw me an interesting and useful twist on animation not long ago, which turned out to be a cross between page flipping and dirty-rectangle animation. That someone was Serge Mathieu of Concepteva Inc., in Rosemere, Quebec, who informed me that he designs -everything “from a game *point de vue*.” +everything "from a game *point de vue*." In normal page flipping, you display one page while you update the other page. Then you display the new page while you update the other. This @@ -28,8 +28,8 @@ works fine, but the need to keep two pages current can make for a lot of bookkeeping and possibly extra drawing, especially in applications where only some of the objects are redrawn each time. -Serge didn’t care to do all that bookkeeping in his animation -applications, so he came up with the following approach, which I’ve +Serge didn't care to do all that bookkeeping in his animation +applications, so he came up with the following approach, which I've reworded, amplified, and slightly modified in the summary here: **1.**  Set the start address to display page 0. @@ -38,32 +38,32 @@ reworded, amplified, and slightly modified in the summary here: **3.**  Set the start address to display page 1 (the newly drawn page), then wait for the leading edge of vertical sync, at which point the page -has flipped and it’s safe to modify page 0. +has flipped and it's safe to modify page 0. **4.**  Copy, via the latches, from page 1 to page 0 the areas that changed from the previous screen to the current one. **5.**  Set the start address to display page 0, which is now identical to page 1, then wait for the leading edge of vertical sync, at which -point the page has flipped and it’s safe to modify page 1. +point the page has flipped and it's safe to modify page 1. **6.**  Go to step 2. -The great benefit of Serge’s approach is that the only page that is ever +The great benefit of Serge's approach is that the only page that is ever actually drawn to (as opposed to being block-copied to) is page 1. Only one page needs to be maintained, and the complications of maintaining -two separate pages vanish entirely. The performance of Serge’s approach +two separate pages vanish entirely. The performance of Serge's approach may be better or worse than standard page flipping, depending on whether a lot of extra work is required to maintain two pages or not. My guess -is that Serge’s approach will usually be slower, owing to the +is that Serge's approach will usually be slower, owing to the considerable amount of display-memory copying involved, and also to the -double page-flip per frame. There’s no doubt, however, that Serge’s +double page-flip per frame. There's no doubt, however, that Serge's approach is simpler, and the resultant display quality is every bit as -good as standard page flipping. Given page flipping’s fair degree of +good as standard page flipping. Given page flipping's fair degree of complication, this approach is a valuable tool, especially for less-experienced animation programmers. -An interesting variation on Serge’s approach doesn’t page flip nor wait +An interesting variation on Serge's approach doesn't page flip nor wait for vertical sync: **1.**  Set the start address to display page 0. @@ -77,25 +77,25 @@ screen to the current one from page 1 to page 0. This approach totally eliminates page flipping, which can consume a great deal of time. The downside is that images may shear for one frame -if they’re only partially copied when the raster beam reaches them. This +if they're only partially copied when the raster beam reaches them. This approach is basically a standard dirty-rectangle approach, except that the drawing buffer is stored in display memory, rather than in system memory. Whether this technique is faster than drawing to system memory -depends on whether the benefit you get from the VGA’s hardware, such as +depends on whether the benefit you get from the VGA's hardware, such as the Bit Mask, the ALUs, and especially the latches (for copying the dirty rectangles) is sufficient to outweigh the extra display-memory accesses involved in drawing and copying, since display memory is notoriously slow. -Finally, I’d like to point out that in any scheme that involves changing +Finally, I'd like to point out that in any scheme that involves changing the display-memory start address, a clever trick can potentially reduce -the time spent waiting for pages to flip. Normally, it’s necessary to +the time spent waiting for pages to flip. Normally, it's necessary to wait for display enable to be active, then set the two start address registers, and finally wait for vertical sync to be active, so that you know the new start address has taken effect. The start-address registers must never be set around the time vertical sync is active (the new start address is accepted at either the start or end of vertical sync on the -EGAs and VGAs I’m familiar with), because it would then be possible to +EGAs and VGAs I'm familiar with), because it would then be possible to load a half-changed start address (one register loaded, the other not yet loaded), and the screen would jump for a frame. Avoiding this condition is the motivation for waiting for display enable, because @@ -112,11 +112,11 @@ mismatched); page flipping will often involve less waiting, because display enable becomes inactive long before vertical sync becomes active. Using the above approach reclaims all the time between the end of display enable and the start of vertical sync for doing useful work. -(The steps I’ve given for Serge’s animation approach assume that the -single-byte approach is in use; that’s why display enable is never +(The steps I've given for Serge's animation approach assume that the +single-byte approach is in use; that's why display enable is never waited for.) -In the next chapter, I’ll return to the original dirty-rectangle +In the next chapter, I'll return to the original dirty-rectangle algorithm presented in this chapter, and goose it a little with some assembly, so that we can see what dirty-rectangle animation is really made of. (Probably not dog hair....) diff --git a/46-01.md b/46-01.md index 0dbb699..152263b 100644 --- a/46-01.md +++ b/46-01.md @@ -11,19 +11,19 @@ Chapter 46\ Programming is, by and large, a linear process. One statement or instruction follows another, in predictable sequences, with tiny building blocks strung together to make thinking, which is, of course, A -Good Thing. Still, it’s important to keep in mind that there’s a large -chunk of the human mind that doesn’t work in a linear fashion. +Good Thing. Still, it's important to keep in mind that there's a large +chunk of the human mind that doesn't work in a linear fashion. -I’ve written elsewhere about the virtues of +I've written elsewhere about the virtues of nonlinear/right-brain/lateral/what-have-you thinking in solving tough programming problems, such as debugging or optimization, but it bears repeating. The mind can be an awesome pattern-matching and extrapolation tool, if you let it. For example, the other day was grinding my way through a particularly difficult bit of debugging. The code had been -written by someone else, and, to my mind, there’s nothing worse than -debugging someone else’s code; there’s always the nasty feeling that you -don’t quite know what’s going on. The overall operation of this code -wouldn’t come clear in my head, no matter how long stared at it, leaving +written by someone else, and, to my mind, there's nothing worse than +debugging someone else's code; there's always the nasty feeling that you +don't quite know what's going on. The overall operation of this code +wouldn't come clear in my head, no matter how long stared at it, leaving me with a rising sense of frustration and a determination not to quit until got this bug. @@ -40,31 +40,31 @@ head, and the work went quickly and easily. Obviously, my mind needed a break from linear, left-brain, push-it-out thinking, so it could do the sort of integrating work it does so -well—but that it’s rarely willing to do under conscious control. It was +well—but that it's rarely willing to do under conscious control. It was exactly this sort of thinking had in mind when titled my 1989 optimization book of *Zen of Assembly Language.* (Although must admit -that few people seem to have gotten the connection, and I’ve had to -field a lot of questions about whether I’m a Zen disciple. I’m -not—actually, I’m more of a Dave Barry disciple. If you don’t know who -Dave Barry is, you should; he’s good for your right brain.) Give your -mind a break once in a while, and I’ll bet you’ll find you’re more +that few people seem to have gotten the connection, and I've had to +field a lot of questions about whether I'm a Zen disciple. I'm +not—actually, I'm more of a Dave Barry disciple. If you don't know who +Dave Barry is, you should; he's good for your right brain.) Give your +mind a break once in a while, and I'll bet you'll find you're more productive. -We’re strange thinking machines, but we’re the best ones yet invented, -and it’s worth learning how to tap our full potential. And with that, -it’s back to dirty-rectangle animation. +We're strange thinking machines, but we're the best ones yet invented, +and it's worth learning how to tap our full potential. And with that, +it's back to dirty-rectangle animation. #### Dirty-Rectangle Animation, Continued {#Heading3} In the last chapter, Introduced the idea of dirty-rectangle animation. -This technique is an alternative to page flipping that’s capable of +This technique is an alternative to page flipping that's capable of producing animation of very high visual quality, without any help at all from video hardware, and without the need for any extra, nondisplayed video memory. This makes dirty-rectangle animation more widely usable -than page flipping, because many adapters don’t support page flipping. +than page flipping, because many adapters don't support page flipping. Dirty-rectangle animation also tends to be simpler to implement than -page flipping, because there’s only one bitmap to keep track of. A final -advantage of dirty-rectangle animation is that it’s potentially somewhat +page flipping, because there's only one bitmap to keep track of. A final +advantage of dirty-rectangle animation is that it's potentially somewhat faster than page flipping, because display-memory accesses can theoretically be reduced to exactly one access for each pixel that changes from one frame to the next. @@ -72,20 +72,20 @@ changes from one frame to the next. The speed advantage of dirty-rectangle animation was entirely theoretical in the previous chapter, because the implementation was completely in C, and because no attempt was made to minimize display -memory accesses. The visual quality of Chapter 45’s animation was also -less than ideal, for reasons we’ll explore shortly. The code in Listings -46.1 and 46.2 addresses the shortcomings of Chapter 45’s code. +memory accesses. The visual quality of Chapter 45's animation was also +less than ideal, for reasons we'll explore shortly. The code in Listings +46.1 and 46.2 addresses the shortcomings of Chapter 45's code. Listing 46.2 implements the low-level drawing routines in assembly language, which boosts performance a good deal. For maximum performance, it would be worthwhile to convert more of Listing 46.1 into assembly, so -a call isn’t required for each animated image, and overall performance +a call isn't required for each animated image, and overall performance could be improved by streamlining the C code, but Listing 46.2 goes a long way toward boosting animation speed. This program now supports snappy animation of 15 images (as opposed to 10 for the software presented in the last chapter), and the images are now two pixels wider. That level of performance is all the more impressive considering that -for this chapter I’ve converted the code from using rectangular images +for this chapter I've converted the code from using rectangular images to using masked images. ------------------------ --------------------------------- -------------------- diff --git a/46-02.md b/46-02.md index acc467d..308336a 100644 --- a/46-02.md +++ b/46-02.md @@ -46,7 +46,7 @@ /* If set to 1, ignore dirty rectangle list and copy the whole screen. */ int DrawWholeScreen = 0; /* pixels and masks for the two internally animated versions of the image - we’ll animate */ + we'll animate */ #define IMAGE-WIDTH 13 #define IMAGE-HEIGHT 11 char ImagePixels0[] = { @@ -108,7 +108,7 @@ /* Animated entities */ #define NUM-ENTITIES 15 Entity Entities[NUM-ENTITIES]; - /* pointer to system buffer into which we’ll draw */ + /* pointer to system buffer into which we'll draw */ char far *SystemBufferPtr; /* pointer to screen */ char far *ScreenPtr; @@ -126,10 +126,10 @@ unsigned int TempCount; char far *TempPtr; union REGS regs; - /* Allocate memory for the system buffer into which we’ll draw */ + /* Allocate memory for the system buffer into which we'll draw */ if (!(SystemBufferPtr = farmalloc((unsigned int)SCREEN-WIDTH* SCREEN-HEIGHT))) { - printf(”Couldn’t get memory\n”); + printf("Couldn't get memory\n"); exit(1); } /* Clear the system buffer */ @@ -139,7 +139,7 @@ } /* Point to the screen */ ScreenPtr = MK-FP(SCREEN-SEGMENT, 0); - /* Set up the entities we’ll animate, at random locations */ + /* Set up the entities we'll animate, at random locations */ randomize(); for (= 0; < NUM-ENTITIES; i++) { Entities[i].X = random(SCREEN-WIDTH - IMAGE-WIDTH); @@ -237,7 +237,7 @@ SCREEN-WIDTH, SCREEN-WIDTH); } else { /* Copy only the dirty rectangles, in the YX-sorted order in which - they’re linked */ + they're linked */ DirtyPtr = DirtyHead.Next; for (= 0; < NumDirtyRectangles; i++) { /* Offset in both system buffer and screen of image */ @@ -303,12 +303,12 @@ (TempPtr->Top < (pEntity->Y + ImageHeight)) && (TempPtr->Bottom > pEntity->Y)) { - /* We’ve found an overlapping rectangle. Calculate the + /* We've found an overlapping rectangle. Calculate the rectangles, if any, remaining after subtracting out the overlapped areas, and add them to the dirty list */ /* Check for a nonoverlapped left portion */ if (TempPtr->Left > pEntity->X) { - /* There’s definitely a nonoverlapped portion at the left; add + /* There's definitely a nonoverlapped portion at the left; add it, but only to at most the top and bottom of the overlapping rect; top and bottom strips are taken care of below */ TempEntity.X = pEntity->X; @@ -320,7 +320,7 @@ } /* Check for a nonoverlapped right portion */ if (TempPtr->Right < (pEntity->X + ImageWidth)) { - /* There’s definitely a nonoverlapped portion at the right; add + /* There's definitely a nonoverlapped portion at the right; add it, but only to at most the top and bottom of the overlapping rect; top and bottom strips are taken care of below */ TempEntity.X = TempPtr->Right; @@ -332,25 +332,25 @@ } /* Check for a nonoverlapped top portion */ if (TempPtr->Top > pEntity->Y) { - /* There’s a top portion that’s not overlapped */ + /* There's a top portion that's not overlapped */ TempEntity.X = pEntity->X; TempEntity.Y = pEntity->Y; AddDirtyRect(&TempEntity, TempPtr->Top - pEntity->Y, ImageWidth); } /* Check for a nonoverlapped bottom portion */ if (TempPtr->Bottom < (pEntity->Y + ImageHeight)) { - /* There’s a bottom portion that’s not overlapped */ + /* There's a bottom portion that's not overlapped */ TempEntity.X = pEntity->X; TempEntity.Y = TempPtr->Bottom; AddDirtyRect(&TempEntity, (pEntity->Y + ImageHeight) - TempPtr->Bottom, ImageWidth); } - /* We’ve added all non-overlapped portions to the dirty list */ + /* We've added all non-overlapped portions to the dirty list */ return; } } #endif /* CHECK-OVERLAP */ - /* There’s no overlap with any existing rectangle, so we can just + /* There's no overlap with any existing rectangle, so we can just add this rectangle as-is */ /* Find the YX-sorted insertion point. Searches will always terminate, because the head/tail rectangle is set to the maximum values */ diff --git a/46-03.md b/46-03.md index ce48a45..36055b4 100644 --- a/46-03.md +++ b/46-03.md @@ -4,18 +4,18 @@ #### Masked Images {#Heading4} -Masked images are rendered by drawing an object’s pixels through a mask; +Masked images are rendered by drawing an object's pixels through a mask; pixels are actually drawn only where the mask specifies that drawing is allowed. This makes it possible to draw nonrectangular objects that -don’t improperly interfere with one another when they overlap. Masked +don't improperly interfere with one another when they overlap. Masked images also make it possible to have transparent areas (windows) within objects. Masked images produce far more realistic animation than do rectangular images, and therefore are more desirable. Unfortunately, masked images are also considerably slower to draw—however, a good assembly language implementation can go a long way toward making masked -images draw rapidly enough, as illustrated by this chapter’s code. +images draw rapidly enough, as illustrated by this chapter's code. (Masked images are also known as *sprites*; some video hardware supports -sprites directly, but on the PC it’s necessary to handle sprites in +sprites directly, but on the PC it's necessary to handle sprites in software.) Masked images make it possible to render scenes so that a given image @@ -24,27 +24,27 @@ so images are displayed in *z-order* (by distance). By consistently drawing images that are supposed to be farther away before drawing nearer images, the nearer images will appear in front of the other images, and because masked images draw only precisely the correct pixels -(as opposed to blank pixels in the bounding rectangle), there’s no +(as opposed to blank pixels in the bounding rectangle), there's no interference between overlapping images to destroy the illusion. -In this chapter, I’ve used the approach of having separate, paired masks +In this chapter, I've used the approach of having separate, paired masks and images. Another, quite different approach to masking is to specify a transparent color for copying, and copy only those pixels that are not the transparent color. This has the advantage of not requiring separate -mask data, so it’s more compact, and the code to implement this is a -little less complex than the full masking I’ve implemented. On the other +mask data, so it's more compact, and the code to implement this is a +little less complex than the full masking I've implemented. On the other hand, the transparent color approach is less flexible because it makes -one color undrawable. Also, with a transparent color, it’s not possible +one color undrawable. Also, with a transparent color, it's not possible to keep the same base image but use different masks, because the mask information is embedded in the image data. #### Internal Animation {#Heading5} -I’ve added another feature essential to producing convincing animation: +I've added another feature essential to producing convincing animation: *internal animation*, which is the process of changing the appearance of a given object over time, as distinguished from changing only the *location* of a given object. Internal animation makes images look -active and alive. I’ve implemented the simplest possible form of +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 @@ -57,10 +57,10 @@ like. #### Dirty-Rectangle Management {#Heading6 align="center"} As mentioned above, dirty-rectangle animation makes it possible to -access display memory a minimum number of times. The previous chapter’s -code didn’t do any of that; instead, it copied all portions of every +access display memory a minimum number of times. The previous chapter's +code didn't do any of that; instead, it copied all portions of every dirty rectangle to the screen, regardless of overlap between rectangles. -The code I’ve presented in this chapter goes to the other extreme, +The code I've presented in this chapter goes to the other extreme, taking great pains never to draw overlapped portions of rectangles more than once. This is accomplished by checking for overlap whenever a rectangle is to be added to the dirty list. When overlap with an @@ -71,7 +71,7 @@ additional overlap is detected. A good deal of code is required to generate a fully nonoverlapped dirty list. Is it worth it? It certainly can be, but in the case of Listing -46.1, probably not. For one thing, you’d need larger, heavily overlapped +46.1, probably not. For one thing, you'd need larger, heavily overlapped objects for this approach to pay off big. Besides, this program is mostly in C, and spends a lot of time doing things other than actually accessing display memory. It also takes a fair amount of time just to @@ -79,7 +79,7 @@ generate the nonoverlapped list; the overhead of all the looping, intersecting, and calling required to generate the list eats up a lot of the benefits of accessing display memory less often. Nonetheless, fully nonoverlapped drawing can be useful under the right circumstances, and -I’ve implemented it in Listing 46.1 so you’ll have something to refer to +I've implemented it in Listing 46.1 so you'll have something to refer to should you decide to go this route. There are a couple of additional techniques you might try if you want to @@ -121,17 +121,17 @@ time to make the animation look wrong. This violated a fundamental rule of animation: *No pixel should ever be displayed in a perceptibly incorrect state*. To correct the problem, -I’ve sorted the dirty rectangles first by Y coordinate, and secondly by +I've sorted the dirty rectangles first by Y coordinate, and secondly by X coordinate. This means the screen updates from to draw a given image should be drawn nearly simultaneously. Run the code from the last -chapter and then this chapter; you’ll see quite a difference in +chapter and then this chapter; you'll see quite a difference in appearance. Avoid the trap of thinking animation is merely a matter of drawing the right pixels, one after another. Animation is the art of drawing *the right pixels at the right times* so that the eye and brain see what you want them to see. Animation is a lot more challenging than merely -cranking out pixels, and it sure as heck isn’t a purely linear process. +cranking out pixels, and it sure as heck isn't a purely linear process. ------------------------ --------------------------------- -------------------- [Previous](46-02.html) [Table of Contents](index.html) [Next](47-01.html) diff --git a/47-01.md b/47-01.md index 2d6a9a7..db013ae 100644 --- a/47-01.md +++ b/47-01.md @@ -6,45 +6,45 @@ Chapter 47\ Mode X: 256-Color VGA Magic {#Heading1} ---------------------------- -### Introducing the VGA’s Undocumented “Animation-Optimal” Mode {#Heading2} +### Introducing the VGA's Undocumented "Animation-Optimal" Mode {#Heading2} At a book signing for my book *Zen of Code Optimization*, an attractive -young woman came up to me, holding my book, and said, “You’re Michael -Abrash, aren’t you?” I confessed that I was, prepared to respond in an +young woman came up to me, holding my book, and said, "You're Michael +Abrash, aren't you?" I confessed that I was, prepared to respond in an appropriately modest yet proud way to the compliments I was sure would -follow. (It was my own book signing, after all.) It didn’t work out +follow. (It was my own book signing, after all.) It didn't work out quite that way, though. The first thing out of her mouth was: -“‘Mode X’ is a stupid name for a graphics mode.” As my jaw started to -drop, she added, “And you didn’t invent the mode, either. My husband did -it before you did.” +"‘Mode X' is a stupid name for a graphics mode." As my jaw started to +drop, she added, "And you didn't invent the mode, either. My husband did +it before you did." And they say there are no groupies in programming! Well. I never claimed that I invented the mode (which is a 320x256-color -mode with some very special properties, as we’ll see shortly). I did +mode with some very special properties, as we'll see shortly). I did discover it independently, but so did other people in the game business, some of them no doubt before I did. The difference is that all those other people held onto this powerful mode as a trade secret, while I -didn’t; instead, I spread the word as broadly as I could in my column in -*Dr. Dobb’s Journal,* on the theory that the more people knew about this +didn't; instead, I spread the word as broadly as I could in my column in +*Dr. Dobb's Journal,* on the theory that the more people knew about this mode, the more valuable it would be. And I succeeded, as evidenced by the fact that this now widely-used mode is universally known by the name -I gave it in *DDJ,* “Mode X.” Neither do I think that’s a bad name; it’s +I gave it in *DDJ,* "Mode X." Neither do I think that's a bad name; it's short, catchy, and easy to remember, and it befits the mystery status of -this mode, which was omitted entirely from IBM’s documentation of the +this mode, which was omitted entirely from IBM's documentation of the VGA. In fact, when all is said and done, Mode X is one of my favorite accomplishments. I remember reading that Charles Schultz, creator of -“Peanuts,” was particularly proud of having introduced the phrase -“security blanket” to the English language. I feel much the same way -about Mode X; it’s now a firmly entrenched part of the computer lexicon, -and how often do any of us get a chance to do that? And that’s not to +"Peanuts," was particularly proud of having introduced the phrase +"security blanket" to the English language. I feel much the same way +about Mode X; it's now a firmly entrenched part of the computer lexicon, +and how often do any of us get a chance to do that? And that's not to mention all the excellent games that would not have been as good without Mode X. -So, in the end, I’m thoroughly pleased with Mode X; the world is a +So, in the end, I'm thoroughly pleased with Mode X; the world is a better place for it, even if it did cost me my one potential female fan. (Contrary to popular belief, the lives of computer columnists and rock stars are not, repeat, *not*, all that similar.) This and the following @@ -55,20 +55,20 @@ and e-mail. Ladies and gentlemen, I give you...Mode X. ### What Makes Mode X Special? {#Heading3} -Consider the strange case of the VGA’s 320x256-color mode—Mode X—which -is undeniably complex to program and isn’t even documented by IBM—but +Consider the strange case of the VGA's 320x256-color mode—Mode X—which +is undeniably complex to program and isn't even documented by IBM—but which is, nonetheless, perhaps the single best mode the VGA has to offer, especially for animation. -We’ve seen the VGA’s undocumented 256-color modes, in Chapters 31 and -32, but now it’s time to delve into the wonders of Mode X itself. (Most -of the performance tips I’ll discuss for this mode also apply to the +We've seen the VGA's undocumented 256-color modes, in Chapters 31 and +32, but now it's time to delve into the wonders of Mode X itself. (Most +of the performance tips I'll discuss for this mode also apply to the other non-standard 256-color modes, however.) Five features set Mode X apart from other VGA modes. First, it has a 1:1 aspect ratio, resulting in equal pixel spacing horizontally and vertically (that is, square pixels). Square pixels make for the most attractive displays, and avoid considerable programming effort that would otherwise be necessary to -adjust graphics primitives and images to match the screen’s pixel +adjust graphics primitives and images to match the screen's pixel spacing. (For example, with square pixels, a circle can be drawn as a circle; otherwise, it must be drawn as an ellipse that corrects for the aspect ratio—a slower and considerably more complicated process.) In @@ -77,9 +77,9 @@ nonsquare 320x200 resolution. Second, Mode X allows page flipping, a prerequisite for the smoothest possible animation. Mode 13H does not allow page flipping, nor does mode -12H, the VGA’s high-resolution 640x480 16-color mode. +12H, the VGA's high-resolution 640x480 16-color mode. -Third, Mode X allows the VGA’s plane-oriented hardware to be used to +Third, Mode X allows the VGA's plane-oriented hardware to be used to process pixels in parallel, improving performance by up to four times over mode 13H. @@ -94,24 +94,24 @@ clones speed up writes more than reads. Fifth, unlike mode 13H, Mode X has plenty of offscreen memory free for image storage. This is particularly effective in conjunction with the -use of the VGA’s latches; together, the latches and the off-screen +use of the VGA's latches; together, the latches and the off-screen memory allow images to be copied to the screen four pixels at a time. -There’s a sixth feature of Mode X that’s *not* so terrific: It’s hard to +There's a sixth feature of Mode X that's *not* so terrific: It's hard to program efficiently. As Chapters 23 through 30 of this book demonstrates, 16-color VGA programming can be demanding. Mode X is often as demanding as 16-color programming, and operates by a set of rules -that turns everything you’ve learned in 16-color mode sideways. +that turns everything you've learned in 16-color mode sideways. Programming Mode X is nothing like programming the nice, flat bitmap of mode 13H, or, for that matter, the flat, linear (albeit banked) bitmap -used by 256-color SuperVGA modes. (I’t’s important to remember that Mode +used by 256-color SuperVGA modes. (I't's important to remember that Mode X works on *all* VGAs, not just SuperVGAs.) Many programmers I talk to -love the flat bitmap model, and think that it’s the ideal organization -for display memory because it’s so straightforward to program. Here, +love the flat bitmap model, and think that it's the ideal organization +for display memory because it's so straightforward to program. Here, however, the complexity of Mode X is opportunity—opportunity for the best combination of performance and appearance the VGA has to offer. If you do 256-color programming, and especially if you use animation, -you’re missing the boat if you’re not using Mode X. +you're missing the boat if you're not using Mode X. Although some developers have taken advantage of Mode X, its use is certainly not universal, being entirely undocumented; only an @@ -120,7 +120,7 @@ exists, and figuring out how to make it perform beyond the write pixel/read pixel level is no mean feat. Little other than my *DDJ* columns has been published about it, although John Bridges has widely distributed his code for a number of undocumented 256-color resolutions, -and I’d like to acknowledge the influence of his code on the mode set +and I'd like to acknowledge the influence of his code on the mode set routine presented in this chapter. ------------------------ --------------------------------- -------------------- diff --git a/47-02.md b/47-02.md index 7ecc849..e8602aa 100644 --- a/47-02.md +++ b/47-02.md @@ -3,14 +3,14 @@ ------------------------ --------------------------------- -------------------- Given the tremendous advantages of Mode X over the documented mode 13H, -I’d very much like to get it into the hands of as many developers as -possible, so I’m going to spend the next few chapters exploring this odd -but worthy mode. I’ll provide mode set code, delineate the bitmap +I'd very much like to get it into the hands of as many developers as +possible, so I'm going to spend the next few chapters exploring this odd +but worthy mode. I'll provide mode set code, delineate the bitmap organization, and show how the basic write pixel and read pixel -operations work. Then, I’ll move on to the magic stuff: rectangle fills, +operations work. Then, I'll move on to the magic stuff: rectangle fills, screen clears, scrolls, image copies, pixel inversion, and, yes, polygon fills (just a different driver for the polygon code), all blurry fast; -hardware raster ops; and page flipping. In the end, I’ll build a working +hardware raster ops; and page flipping. In the end, I'll build a working animation program that shows many of the features of Mode X in action. The mode set code is the logical place to begin. @@ -18,18 +18,18 @@ The mode set code is the logical place to begin. ### Selecting 320x240 256-Color Mode {#Heading4} We could, if we wished, write our own mode set code for Mode X from -scratch—but why bother? Instead, we’ll let the BIOS do most of the work -by having it set up mode 13H, which we’ll then turn into Mode X by +scratch—but why bother? Instead, we'll let the BIOS do most of the work +by having it set up mode 13H, which we'll then turn into Mode X by changing a few registers. Listing 47.1 does exactly that. The code in Listing 47.1 has been around for some time, and the very first version had a bug that serves up an interesting lesson. The -original *DDJ* version made images roll on IBM’s fixed-frequency VGA -monitors, a problem that didn’t come to my attention until the code was +original *DDJ* version made images roll on IBM's fixed-frequency VGA +monitors, a problem that didn't come to my attention until the code was in print and shipped to 100,000 readers. The bug came about this way: The code I modified to make the Mode X mode -set code used the VGA’s 28-MHz clock. Mode X should have used the 25-MHz +set code used the VGA's 28-MHz clock. Mode X should have used the 25-MHz clock, a simple matter of setting bit 2 of the Miscellaneous Output register (3C2H) to 0 instead of 1. @@ -41,23 +41,23 @@ innocuous—until it was distributed broadly and everybody started banging on it. IBM makes only fixed-frequency VGA monitors, which require very specific -frame rates; if they don’t get what you’ve told them to expect, the +frame rates; if they don't get what you've told them to expect, the image rolls. The corrected version is the one shown here as Listing 47.1; it does select the 25-MHz clock, and works just fine on fixed-frequency monitors. -Why didn’t I catch this bug? Neither I nor a single one of my testers +Why didn't I catch this bug? Neither I nor a single one of my testers had a fixed-frequency monitor! This nicely illustrates how difficult it is these days to test code in all the PC-compatible environments in which it might run. The problem is particularly severe for small -developers, who can’t afford to buy every model of every hardware +developers, who can't afford to buy every model of every hardware component from every manufacturer; just imagine trying to test network-aware software in all possible configurations! -When people ask why software isn’t bulletproof; why it crashes or -doesn’t coexist with certain programs; why PC clones aren’t always +When people ask why software isn't bulletproof; why it crashes or +doesn't coexist with certain programs; why PC clones aren't always compatible; why, in short, the myriad irritations of using a PC -exist—this is a big part of the reason. I guess that’s just the price we +exist—this is a big part of the reason. I guess that's just the price we pay for the unfettered creativity and vast choice of the PC market. **LISTING 47.1 L47-1.ASM** @@ -97,9 +97,9 @@ pay for the unfettered creativity and vast choice of the PC market. .code public _Set320x240Mode _Set320x240Mode proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame push si ;preserve C register vars - push di ; (don’t count on BIOS preserving anything) + push di ; (don't count on BIOS preserving anything) mov ax,13h ;let the BIOS set standard 256-color int 10h ; mode (320x200 linear) @@ -146,7 +146,7 @@ pay for the unfettered creativity and vast choice of the PC market. pop di ;restore C register vars pop si - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _Set320x240Mode endp end diff --git a/47-03.md b/47-03.md index 9b24fc6..a9e9ebe 100644 --- a/47-03.md +++ b/47-03.md @@ -3,18 +3,18 @@ ------------------------ --------------------------------- -------------------- After setting up mode 13H, Listing 47.1 alters the vertical counts and -timings to select 480 visible scan lines. (There’s no need to alter any +timings to select 480 visible scan lines. (There's no need to alter any horizontal values, because mode 13H and Mode X both have 320-pixel horizontal resolutions.) The Maximum Scan Line register is programmed to double scan each line (that is, repeat each scan line twice), however, so we get an effective vertical resolution of 240 scan lines. It is, in fact, possible to get 400 or 480 independent scan lines in 256-color mode, as discussed in Chapter 31 and 32; however, 400-scan-line modes -lack square pixels and can’t support simultaneous off-screen memory and +lack square pixels and can't support simultaneous off-screen memory and page flipping. Furthermore, 480-scan-line modes lack page flipping altogether, due to memory constraints. -At the same time, Listing 47.1 programs the VGA’s bitmap to a planar +At the same time, Listing 47.1 programs the VGA's bitmap to a planar organization that is similar to that used by the 16-color modes, and utterly different from the linear bitmap of mode 13H. The bizarre bitmap organization of Mode X is shown in Figure 47.1. The first pixel (the @@ -42,11 +42,11 @@ 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. -That’s all right, though, because most graphics software spends little -time drawing individual pixels. I’ve provided the write and read pixel -routines as basic primitives, and so you’ll understand how the bitmap is +That's all right, though, because most graphics software spends little +time drawing individual pixels. I've provided the write and read pixel +routines as basic primitives, and so you'll understand how the bitmap is organized, but the building blocks of high-performance graphics software -are fills, copies, and bitblts, and it’s there that Mode X shines. +are fills, copies, and bitblts, and it's there that Mode X shines. ![](images/47-01.jpg)\ **Figure 47.1**  *Mode X display memory organization.* @@ -78,30 +78,30 @@ are fills, copies, and bitblts, and it’s there that Mode X shines. .code public _WritePixelX _WritePixelX proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to local stack frame mov ax,SCREEN_WIDTH - mul [bp+Y] ;offset of pixel’s scan line in page + mul [bp+Y] ;offset of pixel's scan line in page mov bx,[bp+X] shr bx,1 shr bx,1 ;X/4 = offset of pixel in scan line add bx,ax ;offset of pixel in page add bx,[bp+PageBase] ;offset of pixel in display memory mov ax,SCREEN_SEG - mov es,ax ;point ES:BX to the pixel’s address + mov es,ax ;point ES:BX to the pixel's address mov cl,byte ptr [bp+X] - and cl,011b ;CL = pixel’s plane + and cl,011b ;CL = pixel's plane mov ax,0100h + MAP_MASK ;AL = index in SC of Map Mask reg - shl ah,cl ;set only the bit for the pixel’s plane to 1 + shl ah,cl ;set only the bit for the pixel's plane to 1 mov dx,SC_INDEX ;set the Map Mask to enable only the - out dx,ax ; pixel’s plane + out dx,ax ; pixel's plane mov al,byte ptr [bp+Color] mov es:[bx],al ;draw the pixel in the desired color - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _WritePixelX endp end @@ -131,29 +131,29 @@ are fills, copies, and bitblts, and it’s there that Mode X shines. .code public _ReadPixelX _ReadPixelX proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to local stack frame mov ax,SCREEN_WIDTH - mul [bp+Y] ;offset of pixel’s scan line in page + mul [bp+Y] ;offset of pixel's scan line in page mov bx,[bp+X] shr bx,1 shr bx,1 ;X/4 = offset of pixel in scan line add bx,ax ;offset of pixel in page add bx,[bp+PageBase] ;offset of pixel in display memory mov ax,SCREEN_SEG - mov es,ax ;point ES:BX to the pixel’s address + mov es,ax ;point ES:BX to the pixel's address mov ah,byte ptr [bp+X] - and ah,011b ;AH = pixel’s plane + and ah,011b ;AH = pixel's plane mov al,READ_MAP ;AL = index in GC of the Read Map reg - mov dx,GC_INDEX ;set the Read Map to read the pixel’s + mov dx,GC_INDEX ;set the Read Map to read the pixel's out dx,ax ; plane - mov al,es:[bx] ;read the pixel’s color + mov al,es:[bx] ;read the pixel's color sub ah,ah ;convert it to an unsigned int - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _ReadPixelX endp end diff --git a/47-04.md b/47-04.md index 4e29bb9..3358abe 100644 --- a/47-04.md +++ b/47-04.md @@ -10,9 +10,9 @@ wrapping back to plane 0. This is the sort of code that stems from a write-pixel line of thinking; it reflects not a whit of the unique perspective that Mode X demands, and although it looks reasonably efficient, it is in fact some of the slowest graphics code you will ever -see. I’ve provided Listing 47.4 partly for illustrative purposes, but -mostly so we’ll have a point of reference for the substantial speed-up -that’s possible with code that’s designed from a Mode X perspective. +see. I've provided Listing 47.4 partly for illustrative purposes, but +mostly so we'll have a point of reference for the substantial speed-up +that's possible with code that's designed from a Mode X perspective. **LISTING 47.4 L47-4.ASM** @@ -47,9 +47,9 @@ that’s possible with code that’s designed from a Mode X perspective. .code public _FillRectangleX _FillRectangleX proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to local stack frame - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di mov ax,SCREEN_WIDTH @@ -62,16 +62,16 @@ that’s possible with code that’s designed from a Mode X perspective. add di,[bp+PageBase] ;offset of first rectangle pixel in ; display memory mov ax,SCREEN_SEG - mov es,ax ;point ES:DI to the first rectangle pixel’s + mov es,ax ;point ES:DI to the first rectangle pixel's ; address mov dx,SC_INDEX ;set the Sequence Controller Index to mov al,MAP_MASK ; point to the Map Mask register out dx,al inc dx ;point DX to the SC Data register mov cl,byte ptr [bp+StartX] - and cl,011b ;CL = first rectangle pixel’s plane + and cl,011b ;CL = first rectangle pixel's plane mov al,01h - shl al,cl ;set only the bit for the pixel’s plane to 1 + shl al,cl ;set only the bit for the pixel's plane to 1 mov ah,byte ptr [bp+Color] ;color with which to fill mov bx,[bp+EndY] sub bx,[bp+StartY] ;BX = height of rectangle @@ -86,7 +86,7 @@ that’s possible with code that’s designed from a Mode X perspective. FillScanLineLoop: out dx,al ;set the plane for this pixel mov es:[di],ah ;draw the pixel - shl al,1 ;adjust the plane mask for the next pixel’s + shl al,1 ;adjust the plane mask for the next pixel's and al,01111b ; bit, modulo 4 jnz AddressSet ;advance address if we turned over from inc di ; plane 3 to plane 0 @@ -100,9 +100,9 @@ that’s possible with code that’s designed from a Mode X perspective. dec bx ;count down scan lines jnz FillRowsLoop FillDone: - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _FillRectangleX endp end @@ -110,16 +110,16 @@ that’s possible with code that’s designed from a Mode X perspective. 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 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 +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 +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 is a problem, the four planes can be cycled through once for each scan line, rather than once for the entire rectangle. @@ -127,7 +127,7 @@ line, rather than once for the entire rectangle. Listing 47.5 is 2.5 times faster than Listing 47.4 at clearing the screen on a 20-MHz cached 386 with a Paradise VGA. Although Listing 47.5 is slightly slower than an equivalent mode 13H fill routine would be, -it’s not grievously so. +it's not grievously so. ------------------------ --------------------------------- -------------------- [Previous](47-03.html) [Table of Contents](index.html) [Next](47-05.html) diff --git a/47-05.md b/47-05.md index 4f5fc77..7914dca 100644 --- a/47-05.md +++ b/47-05.md @@ -46,10 +46,10 @@ .code public _FillRectangleX _FillRectangleX proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to local stack frame sub sp,STACK_FRAME_SIZE ;allocate space for local vars - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di cld @@ -63,7 +63,7 @@ add di,[bp+PageBase] ;offset of first rectangle pixel in ; display memory mov ax,SCREEN_SEG - mov es,ax ;point ES:DI to the first rectangle pixel’s + mov es,ax ;point ES:DI to the first rectangle pixel's mov [bp+StartOffset],di ; address mov dx,SC_INDEX ;set the Sequence Controller Index to mov al,MAP_MASK ; point to the Map Mask register @@ -128,10 +128,10 @@ cmp ah,4 ;have we done all planes? jnz FillPlanesLoop ;continue if any more planes FillDone: - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si mov sp,bp ;discard storage for local variables - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _FillRectangleX endp end diff --git a/47-06.md b/47-06.md index c40d63d..120ea63 100644 --- a/47-06.md +++ b/47-06.md @@ -10,15 +10,15 @@ suffices to make Mode X about as fast as mode 13H. That alone makes Mode X an attractive mode, given its square pixels, page flipping, and offscreen memory, but superior performance would nonetheless be a pleasant addition to that list. Superior performance is indeed possible -in Mode X, although, oddly enough, it comes courtesy of the VGA’s +in Mode X, although, oddly enough, it comes courtesy of the VGA's hardware, which was never designed to be used in 256-color modes. -All of the VGA’s hardware assist features are available in Mode X, +All of the VGA's hardware assist features are available in Mode X, although some are not particularly useful. The VGA hardware feature -that’s truly the key to Mode X performance is the ability to process -four planes’ worth of data in parallel; this includes both the latches +that's truly the key to Mode X performance is the ability to process +four planes' worth of data in parallel; this includes both the latches and the capability to fan data out to any or all planes. For rectangular -fills, we’ll just need to fan the data out to various planes, so I’ll +fills, we'll just need to fan the data out to various planes, so I'll defer a discussion of other hardware features for now. (By the way, the ALUs, bit mask, and most other VGA hardware features are also available in mode 13H—but parallel data processing is not.) @@ -45,7 +45,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 -Listing 47.6 is that any left or right edge that isn’t aligned to a +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, because not all pixels at the address containing the edge are modified. @@ -55,7 +55,7 @@ than Listing 47.5—and also about four times faster than the same rectangle fill in mode 13H. Understanding the bitmap organization and display hardware of Mode X does indeed pay. -Note that the return from Mode X’s parallelism is not always 4x; some +Note that the return from Mode X's parallelism is not always 4x; some adapters lack the underlying memory bandwidth to write data that fast. However, Mode X parallel access should always be faster than mode 13H access; the only question on any given adapter is how *much* faster. diff --git a/47-07.md b/47-07.md index e7e38af..79d28ca 100644 --- a/47-07.md +++ b/47-07.md @@ -39,9 +39,9 @@ .code public _FillRectangleX _FillRectangleX proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to local stack frame - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di cld @@ -54,7 +54,7 @@ add di,[bp+PageBase] ;offset of first rectangle pixel in ; display memory mov ax,SCREEN_SEG ;point ES:DI to the first rectangle - mov es,ax ; pixel’s address + mov es,ax ; pixel's address mov dx,SC_INDEX ;set the Sequence Controller Index to mov al,MAP_MASK ; point to the Map Mask register out dx,al @@ -75,15 +75,15 @@ sub cx,si shr cx,1 shr cx,1 ;# of addresses across rectangle to fill - 1 - jnz MasksSet ;there’s more than one byte to draw - and bh,bl ;there’s only one byte, so combine the left- + jnz MasksSet ;there's more than one byte to draw + and bh,bl ;there's only one byte, so combine the left- ; and right-edge clip masks MasksSet: mov si,[bp+EndY] sub si,[bp+StartY] ;BX = height of rectangle jle FillDone ;skip if 0 or negative height mov ah,byte ptr [bp+Color] ;color with which to fill - mov bp,SCREEN_WIDTH ;stack frame isn’t needed any more + mov bp,SCREEN_WIDTH ;stack frame isn't needed any more sub bp,cx ;distance from end of one scan line to start dec bp ; of next FillRowsLoop: @@ -93,7 +93,7 @@ mov al,ah ;put color in AL stosb ;draw the left edge dec cx ;count off left edge byte - js FillLoopBottom ;that’s the only byte + js FillLoopBottom ;that's the only byte jz DoRightEdge ;there are only two bytes mov al,00fh ;middle addresses are drawn 4 pixels at a pop out dx,al ;set the middle pixel mask to no clip @@ -111,19 +111,19 @@ dec si ;count down scan lines jnz FillRowsLoop FillDone: - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _FillRectangleX endp end Just so you can see Mode X in action, Listing 47.7 is a sample program that selects Mode X and draws a number of rectangles. Listing 47.7 links -to any of the rectangle fill routines I’ve presented. +to any of the rectangle fill routines I've presented. -And now, I hope, you’re beginning to see why I’m so fond of Mode X. In -the next chapter, we’ll continue with Mode X by exploring the wonders +And now, I hope, you're beginning to see why I'm so fond of Mode X. In +the next chapter, we'll continue with Mode X by exploring the wonders that the latches and parallel plane hardware can work on scrolls, copies, blits, and pattern fills. diff --git a/48-01.md b/48-01.md index 234f7c1..307cc0e 100644 --- a/48-01.md +++ b/48-01.md @@ -6,26 +6,26 @@ Chapter 48\ Mode X Marks the Latch {#Heading1} ----------------------- -### The Internals of Animation’s Best Video Display Mode {#Heading2} +### The Internals of Animation's Best Video Display Mode {#Heading2} In the previous chapter, I introduced you to what I call Mode X, an undocumented 320x240 256-color mode of the VGA. Mode X is distinguished from mode 13H, the documented 320x200 256-color VGA mode, in that it supports page flipping, makes off-screen memory available, has square -pixels, and, above all, lets you use the VGA’s hardware to increase +pixels, and, above all, lets you use the VGA's hardware to increase performance by as much as four times. (Of course, those four times come at the cost of more complex and demanding programming, to be sure—but end users care about results, not how hard the code was to write, and Mode X delivers results in a big way.) In the previous chapter we saw -how the VGA’s plane-oriented hardware can be used to speed solid fills. -That’s a nice technique, but now we’re going to move up to the big +how the VGA's plane-oriented hardware can be used to speed solid fills. +That's a nice technique, but now we're going to move up to the big guns—the VGA latches. The VGA has four latches, one for each plane of display memory. Each latch stores exactly one byte, and that byte is always the last byte read from the corresponding plane of display memory, as shown in Figure 48.1. Furthermore, whenever a given address in display memory is read, -all four planes’ bytes at that address are read and stored in the +all four planes' bytes at that address are read and stored in the corresponding latches, regardless of which plane supplied the byte returned to the CPU (as determined by the Read Map register). As with so much else about the VGA, the above will make little sense to VGA @@ -51,7 +51,7 @@ the corresponding plane, so an individual latch can only work with every fourth pixel on the screen; the latch for plane 0 can work with pixels 0, 4, 8..., the latch for plane 1 with pixels 1, 5, 9..., and so on. -The latches aren’t intended for use in 256-color mode—they were designed +The latches aren't intended for use in 256-color mode—they were designed to allow individual bits of display memory to be modified in 16-color mode—but they are nonetheless very useful in Mode X, particularly for patterned fills and screen-to-screen copies, including scrolls. @@ -70,7 +70,7 @@ that each line of the pattern must be loaded into the latches before the corresponding scan line on the screen is filled. Listings 48.1 and 48.2 together demonstrate a variety of fast Mode X four-by-four pattern fills. (The mode set function called by Listing 48.1 is from the -previous chapter’s listings.) +previous chapter's listings.) **LISTING 48.1 L48-1.C** diff --git a/48-02.md b/48-02.md index 233497e..4f19c22 100644 --- a/48-02.md +++ b/48-02.md @@ -53,10 +53,10 @@ .code public _FillPatternX _FillPatternX proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to local stack frame sub sp,STACK_FRAME_SIZE ;allocate space for local vars - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di cld @@ -123,8 +123,8 @@ sub cx,ax shr cx,1 shr cx,1 ;# of addresses across rectangle to fill - 1 - jnz MasksSet ;there’s more than one pixel to draw - and bh,bl ;there’s only one pixel, so combine the left- + jnz MasksSet ;there's more than one pixel to draw + and bh,bl ;there's only one pixel, so combine the left- ; and right-edge clip masks MasksSet: mov ax,[bp+EndY] @@ -141,27 +141,27 @@ FillRowsLoop: mov cx,[bp+RectAddrWidth] ;width across - 1 mov al,es:[si] ;read display memory to latch this scan - ; line’s pattern + ; line's pattern inc si ;point to the next pattern scan line, wrapping jnz short NoWrap ; back to the start of the pattern if - sub si,4 ; we’ve run off the end + sub si,4 ; we've run off the end NoWrap: mov al,bh ;put left-edge clip mask in AL out dx,al ;set the left-edge plane (clip) mask stosb ;draw the left edge (pixels come from latches; - ; value written by CPU doesn’t matter) + ; value written by CPU doesn't matter) dec cx ;count off left edge address - js FillLoopBottom ;that’s the only address + js FillLoopBottom ;that's the only address jz DoRightEdge ;there are only two addresses mov al,00fh ;middle addresses are drawn 4 pixels at a pop out dx,al ;set the middle pixel mask to no clip rep stosb ;draw the middle addresses four pixels apiece - ; (from latches; value written doesn’t matter) + ; (from latches; value written doesn't matter) DoRightEdge: mov al,bl ;put right-edge clip mask in AL out dx,al ;set the right-edge plane (clip) mask stosb ;draw the right edge (from latches; value - ; written doesn’t matter) + ; written doesn't matter) FillLoopBottom: add di,[bp+NextScanOffset] ;point to the start of the next scan ; line of the rectangle @@ -172,10 +172,10 @@ mov al,0ffh ; which selects all bits from the CPU out dx,al ; and none from the latches (the GC ; Index still points to Bit Mask) - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si mov sp,bp ;discard storage for local variables - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _FillPatternX endp end diff --git a/48-03.md b/48-03.md index 843405e..63ba584 100644 --- a/48-03.md +++ b/48-03.md @@ -11,7 +11,7 @@ diagonal stripes, and crosshatches. Furthermore, eight-wide patterns, which are widely used, can be drawn with two passes, one for each half of the pattern. This principle can in fact be extended to patterns of arbitrary multiple-of-four widths. -(Widths that aren’t multiples of four are considerably more difficult to +(Widths that aren't multiples of four are considerably more difficult to handle, because the latches are four pixels wide; one possible solution is expanding such patterns via repetition until they are multiple-of-four widths.) @@ -26,15 +26,15 @@ pattern before each scan line of the actual fill is drawn. Why this double copying process, and why is the pattern stored in that particular area of display memory? -The double copying process is used because it’s the easiest way to load -the latches. Remember, there’s no way to get information directly from +The double copying process is used because it's the easiest way to load +the latches. Remember, there's no way to get information directly from the CPU to the latches; the information must first be written to some location in display memory, because the latches can be loaded *only* from display memory. By writing the pattern to off-screen memory, we -don’t have to worry about interfering with whatever is currently +don't have to worry about interfering with whatever is currently displayed on the screen. -As for why the pattern is stored exactly where it is, that’s part of a +As for why the pattern is stored exactly where it is, that's part of a master memory allocation plan that will come to fruition in the next chapter, when I implement a Mode X animation program. Figure 48.3 shows this master plan; the first two pages of memory (each 76,800 pixels @@ -77,7 +77,7 @@ register to select the corresponding destination plane. Then, copy all pixels in that plane, repeating for all four planes.) ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Although copying through the latches is, in general, a speedy technique, especially on slower VGAs, it’s not always a win. Reading video memory tends to be quite a bit slower than writing, and on a fast VLB or PCI adapter, it can be faster to copy from main memory to display memory than it is to copy from display memory to display memory via the latches.* + ![](images/i.jpg) *Although copying through the latches is, in general, a speedy technique, especially on slower VGAs, it's not always a win. Reading video memory tends to be quite a bit slower than writing, and on a fast VLB or PCI adapter, it can be faster to copy from main memory to display memory than it is to copy from display memory to display memory via the latches.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------ --------------------------------- -------------------- diff --git a/48-04.md b/48-04.md index 04f0b3f..bbad7d4 100644 --- a/48-04.md +++ b/48-04.md @@ -61,10 +61,10 @@ .code public _CopyScreenToScreenX _CopyScreenToScreenX proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to local stack frame sub sp,STACK_FRAME_SIZE ;allocate space for local vars - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di push ds @@ -112,8 +112,8 @@ sub cx,ax shr cx,1 shr cx,1 ;# of addresses across rectangle to copy - 1 - jnz MasksSet ;there’s more than one address to draw - and bh,bl ;there’s only one address, so combine the + jnz MasksSet ;there's more than one address to draw + and bh,bl ;there's only one address, so combine the ; left- and right-edge clip masks MasksSet: mov ax,[bp+SourceEndY] @@ -148,7 +148,7 @@ movsb ;copy the left edge (pixels go through ; latches) dec cx ;count off left edge address - js CopyLoopBottom ;that’s the only address + js CopyLoopBottom ;that's the only address jz DoRightEdge ;there are only two addresses mov al,00fh ;middle addresses are drawn 4 pixels at a pop out dx,al ;set the middle pixel mask to no clip @@ -170,10 +170,10 @@ out dx,al ; and none from the latches (the GC ; Index still points to Bit Mask) pop ds - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si mov sp,bp ;discard storage for local variables - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _CopyScreenToScreenX endp end diff --git a/48-05.md b/48-05.md index 6751858..7d346b7 100644 --- a/48-05.md +++ b/48-05.md @@ -9,16 +9,16 @@ left-to-right copying. Downward scrolls require bottom-to-top copying; likewise, rightward horizontal scrolls require right-to-left copying. As it happens, my intended use for Listing 48.3 is to copy images between off-screen memory and on-screen memory, and to save areas under pop-up -menus and the like, so I don’t really need overlap handling—and I do +menus and the like, so I don't really need overlap handling—and I do really need to keep the complexity of this discussion down. However, you will surely want to add overlap handling if you plan to perform arbitrary scrolling and copying in display memory. Now that we have a fast way to copy images around in display memory, we can draw icons and other images as much as four times faster than in -mode 13H, depending on the speed of the VGA’s display memory. (In case -you’re worried about the nibble-alignment limitation on fast copies, -don’t be; I’ll address that fully in due time, but the secret is to +mode 13H, depending on the speed of the VGA's display memory. (In case +you're worried about the nibble-alignment limitation on fast copies, +don't be; I'll address that fully in due time, but the secret is to store all four possible rotations in off-screen memory, then select the correct one for each copy.) However, before our fast display memory-to-display memory copy routine can do us any good, we must have a @@ -42,7 +42,7 @@ in off-screen memory and copied to the screen via the latches much faster than even the speediest system memory-to-display memory copy routine could manage. -I’m not going to present a routine to perform Mode X copies from display +I'm not going to present a routine to perform Mode X copies from display memory to system memory, but such a routine would be a straightforward inverse of Listing 48.4. @@ -93,10 +93,10 @@ inverse of Listing 48.4. .code public _CopySystemToScreenX _CopySystemToScreenX proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to local stack frame sub sp,STACK_FRAME_SIZE ;allocate space for local vars - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di cld @@ -120,10 +120,10 @@ inverse of Listing 48.4. add di,ax ;offset of first dest rect pixel in page add di,[bp+DestPageBase] ;offset of first dest rect pixel ; in display memory - and cl,011b ;CL = first dest pixel’s plane + and cl,011b ;CL = first dest pixel's plane mov al,11h ;upper nibble comes into play when ; plane wraps from 3 back to 0 - shl al,cl ;set the bit for the first dest pixel’s + shl al,cl ;set the bit for the first dest pixel's mov [bp+LeftMask],al ; plane in each nibble to 1 mov cx,[bp+SourceEndX] ;calculate # of pixels across @@ -145,7 +145,7 @@ inverse of Listing 48.4. CopyScanLineLoop: out dx,al ;set the plane for this pixel movsb ;copy the pixel to the screen - rol al,1 ;set mask for next pixel’s plane + rol al,1 ;set mask for next pixel's plane cmc ;advance destination address only when sbb di,0 ; wrapping from plane 3 to plane 0 ; (else undo INC DI done by MOVSB) @@ -159,31 +159,31 @@ inverse of Listing 48.4. dec bx ;count down scan lines jnz CopyRowsLoop CopyDone: - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si mov sp,bp ;discard storage for local variables - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _CopySystemToScreenX endp end ### Who Was that Masked Image Copier? {#Heading6} -At this point, it’s getting to be time for us to take all the Mode X -tools we’ve developed, together with one more tool—masked image +At this point, it's getting to be time for us to take all the Mode X +tools we've developed, together with one more tool—masked image copying—and the remaining unexplored feature of Mode X, page flipping, -and build an animation application. I hope that when we’re done, you’ll +and build an animation application. I hope that when we're done, you'll agree with me that Mode X is *the* way to animate on the PC. In truth, though, it matters less whether or not *you* think that Mode X -is the best way to animate than whether or not your users think it’s the +is the best way to animate than whether or not your users think it's the best way based on results; end users care only about results, not how you produced them. For my writing, you folks are the end users—and notice how remarkably little you care about how this book gets written and produced. You care that it turned up in the bookstore, and you care -about the contents, but you sure as heck don’t care about how it got -that far from a bin of tree pulp. When you’re a creator, the process -matters. When you’re a buyer, results are everything. All important. +about the contents, but you sure as heck don't care about how it got +that far from a bin of tree pulp. When you're a creator, the process +matters. When you're a buyer, results are everything. All important. *Sine qua non.* The whole enchilada. If you catch my drift. diff --git a/49-01.md b/49-01.md index eddc51b..7ae0a53 100644 --- a/49-01.md +++ b/49-01.md @@ -9,27 +9,27 @@ Chapter 49\ ### How to Make the VGA Really Get up and Dance {#Heading2} Okay—no amusing stories or informative anecdotes to kick off this -chapter; lotta ground to cover, gotta hurry—you’re impatient, I can -smell it. I won’t talk about the time a friend made the mistake of -loudly saying “\$100 bill” during an animated discussion while walking +chapter; lotta ground to cover, gotta hurry—you're impatient, I can +smell it. I won't talk about the time a friend made the mistake of +loudly saying "\$100 bill" during an animated discussion while walking among the bums on Market Street in San Francisco one night, thereby -graphically illustrating that context is everything. I can’t spare a +graphically illustrating that context is everything. I can't spare a word about how my daughter thinks my 11-year-old floppy-disk-based CP/M machine is more powerful than my 386 with its 100-MB hard disk because -the CP/M machine’s word processor loads and runs twice as fast as the -386’s Windows-based word processor, demonstrating that progress is not -the neat exponential curve we’d like to think it is, and that features -and performance are often conflicting notions. And, lord knows, I can’t +the CP/M machine's word processor loads and runs twice as fast as the +386's Windows-based word processor, demonstrating that progress is not +the neat exponential curve we'd like to think it is, and that features +and performance are often conflicting notions. And, lord knows, I can't take the time to discuss the habits of small white dogs, notwithstanding that such dogs seem to be relevant to just about every aspect of -computing, as Jeff Duntemann’s writings make manifest. No lighthearted +computing, as Jeff Duntemann's writings make manifest. No lighthearted fluff for us; we have real work to do, for today we animate with 256 colors in Mode X. ### Masked Copying {#Heading3} -Over the past two chapters, we’ve put together most of the tools needed -to implement animation in the VGA’s undocumented 320x240 256-color Mode +Over the past two chapters, we've put together most of the tools needed +to implement animation in the VGA's undocumented 320x240 256-color Mode X. We now have mode set code, solid and 4x4 pattern fills, system memory-to-display memory block copies, and display memory-to-display memory block copies. The final piece of the puzzle is the ability to @@ -50,21 +50,21 @@ fact, masked copying even makes it possible to have transparent areas within images. Note that another way to achieve this effect is to implement copying -code that supports a transparent color; that is, a color that doesn’t +code that supports a transparent color; that is, a color that doesn't get copied but rather leaves the destination unchanged. Transparent copying makes for more compact images, because no separate mask is needed, and is generally faster in a software-only implementation. However, Mode X supports masked copying but not transparent copying in -hardware, so we’ll use masked copying in this chapter. +hardware, so we'll use masked copying in this chapter. The system memory to display memory masked copy routine in Listing 49.1 implements masked copying in a straightforward fashion. In the main drawing loop, the corresponding mask byte is consulted as each image pixel is encountered, and the image pixel is copied only if the mask -byte is nonzero. As with most of the system-to-display code I’ve -presented, Listing 49.1 is not heavily optimized, because it’s -inherently slow; there’s a better way to go when performance matters, -and that’s to use the VGA’s hardware. +byte is nonzero. As with most of the system-to-display code I've +presented, Listing 49.1 is not heavily optimized, because it's +inherently slow; there's a better way to go when performance matters, +and that's to use the VGA's hardware. **LISTING 49.1 L49-1.ASM** @@ -106,7 +106,7 @@ and that’s to use the VGA’s hardware. DestBitmapWidth dw ? ;# of pixels across dest bitmap (must be multiple of 4) MaskPtr dw ? ;pointer in DS to start of bitmap in which mask ; resides (byte-per-pixel format, just like the source - ; image; 0-bytes mean don’t copy corresponding source + ; image; 0-bytes mean don't copy corresponding source ; pixel, 1-bytes mean do copy) parms ends @@ -118,10 +118,10 @@ and that’s to use the VGA’s hardware. .code public _CopySystemToScreenMaskedX _CopySystemToScreenMaskedX proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to local stack frame sub sp,STACK_FRAME_SIZE ;allocate space for local vars - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di mov ax,SCREEN_SEG ;point ES to display memory @@ -146,10 +146,10 @@ and that’s to use the VGA’s hardware. add di,ax ;offset of first dest rect pixel in page add di,[bp+DestPageBase] ;offset of first dest rect pixel ; in display memory - and cl,011b ;CL = first dest pixel’s plane + and cl,011b ;CL = first dest pixel's plane mov al,11h ;upper nibble comes into play when plane wraps ; from 3 back to 0 - shl al,cl ;set the bit for the first dest pixel’s plane + shl al,cl ;set the bit for the first dest pixel's plane mov [bp+LeftMask],al ; in each nibble to 1 mov ax,[bp+SourceEndX] ;calculate # of pixels across @@ -173,7 +173,7 @@ and that’s to use the VGA’s hardware. push di ;remember the start offset in the dest CopyScanLineLoop: cmp byte ptr [bx],0 ;is this pixel mask-enabled? - jz MaskOff ;no, so don’t draw it + jz MaskOff ;no, so don't draw it ;yes, draw the pixel out dx,al ;set the plane for this pixel mov ah,[si] ;get the pixel from the source @@ -181,7 +181,7 @@ and that’s to use the VGA’s hardware. MaskOff: inc bx ;advance the mask pointer inc si ;advance the source pointer - rol al,1 ;set mask for next pixel’s plane + rol al,1 ;set mask for next pixel's plane adc di,0 ;advance destination address only when ;wrapping from plane 3 to plane 0 loop CopyScanLineLoop @@ -195,10 +195,10 @@ and that’s to use the VGA’s hardware. dec word ptr [bp+RectHeight] ;count down scan lines jnz CopyRowsLoop CopyDone: - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si mov sp,bp ;discard storage for local variables - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _CopySystemToScreenMaskedX endp end diff --git a/49-02.md b/49-02.md index d2344b7..174e3f3 100644 --- a/49-02.md +++ b/49-02.md @@ -4,15 +4,15 @@ #### Faster Masked Copying {#Heading4} -In the previous chapter we saw how the VGA’s latches can be used to copy +In the previous chapter we saw how the VGA's latches can be used to copy four pixels at a time from one area of display memory to another in Mode -X. We’ve further seen that in Mode X the Map Mask register can be used -to select which planes are copied. That’s all we need to know to be able +X. We've further seen that in Mode X the Map Mask register can be used +to select which planes are copied. That's all we need to know to be able to perform fast masked copies; we can store an image in off-screen display memory, and set the Map Mask to the appropriate mask value as up to four pixels at a time are copied. -There’s a slight hitch, though. The latches can only be used when the +There's a slight hitch, though. The latches can only be used when the source and destination left edge coordinates, modulo four, are the same, as explained in the previous chapter. The solution is to copy all four possible alignments of each image to display memory, each properly @@ -20,8 +20,8 @@ positioned for one of the four possible destination-left-edge-modulo-four cases. These aligned images must be accompanied by the four possible alignments of the image mask, stored in system memory. Given all four image and mask alignments, masked copying -is a simple matter of selecting the alignment that’s appropriate for the -destination’s left edge, then setting the Map Mask with the 4-bit mask +is a simple matter of selecting the alignment that's appropriate for the +destination's left edge, then setting the Map Mask with the 4-bit mask corresponding to each four-pixel set as we copy four pixels at a time via the latches. @@ -32,7 +32,7 @@ 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 form. Given all that ready-to-use data, Listing 49.2 selects and works -with the appropriate image-mask pair for the destination’s left edge +with the appropriate image-mask pair for the destination's left edge alignment. **LISTING 49.2 L49-2.ASM** @@ -94,10 +94,10 @@ alignment. .code public _CopyScreenToScreenMaskedX _CopyScreenToScreenMaskedX proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to local stack frame sub sp,STACK_FRAME_SIZE ;allocate space for local vars - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di cld @@ -117,7 +117,7 @@ alignment. shr di,1 ; scan line add di,ax ;offset of first dest rect pixel in page add di,[bp+DestPageBase] ;offset of first dest rect pixel in display - ; memory. now look up the image that’s + ; memory. now look up the image that's ; aligned to match left-edge alignment ; of destination and si,3 ;DestStartX modulo 4 @@ -190,10 +190,10 @@ alignment. mov al,0ffh ; which selects all bits from the CPU out dx,al ; and none from the latches (the GC ; Index still points to Bit Mask) - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si mov sp,bp ;discard storage for local variables - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _CopyScreenToScreenMaskedX endp end diff --git a/49-03.md b/49-03.md index b1c797b..d95e20d 100644 --- a/49-03.md +++ b/49-03.md @@ -7,7 +7,7 @@ generates the four image and mask alignments and fills in the **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 +would be faster if Listing 49.3 were in assembly language, but there's no reason to think that generating aligned images needs to be particularly fast; in such cases, I prefer to use C, for reasons of coding speed, fewer bugs, and maintainability. @@ -27,7 +27,7 @@ coding speed, fewer bugs, and maintainability. #include #include - #include “maskim.h” + #include "maskim.h" extern void CopySystemToScreenX(int, int, int, int, int, int, char *, unsigned int, int, int); @@ -103,7 +103,7 @@ coding speed, fewer bugs, and maintainability. #### Notes on Masked Copying {#Heading5} -Listings 49.1 and 49.2, like all Mode X code I’ve presented, perform no +Listings 49.1 and 49.2, like all Mode X code I've presented, perform no clipping, because clipping code would complicate the listings too much. While clipping can be implemented directly in the low-level Mode X routines (at the beginning of Listing 49.1, for instance), another, @@ -118,11 +118,11 @@ the width of the image and the width of the area of the image to draw. Also, it would be more efficient to make up structures that describe the source and destination bitmaps, with dimensions and coordinates built in, and simply pass pointers to these structures to the low level, -rather than passing many separate parameters, as is now the case. I’ve +rather than passing many separate parameters, as is now the case. I've used separate parameters for simplicity and flexibility. ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Be aware that as nifty as Mode X hardware-assisted masked copying is, whether or not it’s actually faster than software-only masked or 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 display memory writes.)* + ![](images/i.jpg) *Be aware that as nifty as Mode X hardware-assisted masked copying is, whether or not it's actually faster than software-only masked or 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 display memory writes.)* ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------ --------------------------------- -------------------- diff --git a/49-04.md b/49-04.md index 9c40a29..aea63f7 100644 --- a/49-04.md +++ b/49-04.md @@ -4,22 +4,22 @@ ### Animation {#Heading6} -Gosh. There’s just no way I can discuss high-level animation +Gosh. There's just no way I can discuss high-level animation fundamentals in any detail here; I could spend an entire (and entirely separate) book on animation techniques alone. You might want to have a look at Chapters 43 through 46 before attacking the code in this chapter; that will have to do us for the present volume. (I will return to *3-D* animation in the next chapter.) -Basically, I’m going to perform page flipped animation, in which one +Basically, I'm going to perform page flipped animation, in which one page (that is, a bitmap large enough to hold a full screen) of display memory is displayed while another page is drawn to. When the drawing is finished, the newly modified page is displayed, and the other—now invisible—page is drawn to. The process repeats ad infinitum. For further information, some good places to start are *Computer Graphics*, by Foley and van Dam (Addison-Wesley); *Principles of Interactive -Computer Graphics*, by Newman and Sproull (McGraw Hill); and “Real-Time -Animation” by Rahner James (January 1990, *Dr. Dobb’s Journal*). +Computer Graphics*, by Newman and Sproull (McGraw Hill); and "Real-Time +Animation" by Rahner James (January 1990, *Dr. Dobb's Journal*). Some of the code in this chapter was adapted for Mode X from the code in Chapter 44—yet another reason to read that chapter before finishing this @@ -27,7 +27,7 @@ one. ### Mode X Animation in Action {#Heading7} -Listing 49.5 ties together everything I’ve discussed about Mode X so far +Listing 49.5 ties together everything I've discussed about Mode X so far in a compact but surprisingly powerful animation package. Listing 49.5 first uses solid and patterned fills and system-memory-to-screen-memory masked copying to draw a static background containing a mountain, a sun, @@ -58,7 +58,7 @@ the resulting image—add the animation in your imagination. #include #include #include - #include “maskim.h” + #include "maskim.h" #define SCREEN_SEG 0xA000 #define SCREEN_WIDTH 320 @@ -182,7 +182,7 @@ the resulting image—add the animation in your imagination. if (CreateAlignedMaskedImage(&KiteImage, DOWNLOAD_START_OFFSET, KitePixels, KITE_WIDTH, KITE_HEIGHT, KiteMask) == 0) { regset.x.ax = 0x0003; int86(0x10, ®set, ®set); - printf(“Couldn’t get memory\n”); exit(); + printf("Couldn't get memory\n"); exit(); } /* Draw the background to the background page. */ DrawBackground(BG_START_OFFSET); @@ -220,7 +220,7 @@ the resulting image—add the animation in your imagination. } /* Flip to the page into which we just drew. */ ShowPage(PageStartOffsets[DisplayedPage = NonDisplayedPage]); - /* See if it’s time to end. */ + /* See if it's time to end. */ if (kbhit()) { if (getch() == 0x1B) Done = 1; /* Esc to end */ } diff --git a/49-05.md b/49-05.md index 99b9b6e..01fcc59 100644 --- a/49-05.md +++ b/49-05.md @@ -2,16 +2,16 @@ [Previous](49-04.html) [Table of Contents](index.html) [Next](50-01.html) ------------------------ --------------------------------- -------------------- -Here’s something worth noting: The animation is extremely smooth on a 20 +Here's something worth noting: The animation is extremely smooth on a 20 MHz 386. It is somewhat more jerky on an 8 MHz 286, because only 30 frames a second can be processed. If animation looks jerky on your PC, try reducing the number of kites. The kites draw perfectly into the background, with no interference or fringe, thanks to masked copying. In fact, the kites also cross with no -interference (the last-drawn kite is always in front), although that’s +interference (the last-drawn kite is always in front), although that's not readily apparent because they all look the same anyway and are -moving fast. Listing 49.5 isn’t inherently limited to kites; create your +moving fast. Listing 49.5 isn't inherently limited to kites; create your own images and initialize the object list to display a mix of those images and see the full power of Mode X animation. @@ -37,7 +37,7 @@ chapters. .code public _ShowPage _ShowPage proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to local stack frame ; Wait for display enable to be active (status is active low), to be ; sure both halves of the start address will take in the same frame. @@ -63,27 +63,27 @@ chapters. in al,dx test al,08h jz WaitVS ;vertical sync is active high (1 = active) - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _ShowPage endp end ### Works Fast, Looks Great {#Heading8} -We now end our exploration of Mode X, although we’ll use it again +We now end our exploration of Mode X, although we'll use it again shortly for 3-D animation. Mode X admittedly has its complexities; -that’s why I’ve provided a broad and flexible primitive set. Still, so +that's why I've provided a broad and flexible primitive set. Still, so what if it *is* complex? Take a look at Listing 49.5 in action. That sort of colorful, high-performance animation is worth jumping through a few hoops for; drawing 20, or even 10, fair-sized objects at a rate of 60 Hz, with no flicker, interference, or fringe, is no mean accomplishment, even on a 386. -There’s much more we could do with animation in general and with Mode X -in particular, but it’s time to move on to new challenges. In closing, -I’d like to point out that all of the VGA’s hardware features, including +There's much more we could do with animation in general and with Mode X +in particular, but it's time to move on to new challenges. In closing, +I'd like to point out that all of the VGA's hardware features, including the built-in AND, OR, and XOR functions, are available in Mode X, just -as they are in the standard VGA modes. If you understand the VGA’s +as they are in the standard VGA modes. If you understand the VGA's hardware in mode 12H, try applying that knowledge to Mode X; you might be surprised at what you find you can do. diff --git a/50-01.md b/50-01.md index 0f89f14..ff96d37 100644 --- a/50-01.md +++ b/50-01.md @@ -9,7 +9,7 @@ Chapter 50\ ### 3-D Animation Using Mode X {#Heading2} When I first started programming micros, more than 11 years ago now, -there wasn’t much money in it, or visibility, or anything you could call +there wasn't much money in it, or visibility, or anything you could call a promising career. Sometimes, it was a way to accomplish things that would never have gotten done otherwise because minicomputer time cost too much; other times, it paid the rent; mostly, though, it was just for @@ -41,11 +41,11 @@ Times change, and they seem to do so much faster in computer technology than in other parts of the universe. A 486 is capable of decent 3-D animation, owing to its integrated math coprocessor; not in the class of, say, an i860, but pretty good nonetheless. A 386 is less -satisfactory, though; the 387 is no match for the 486’s coprocessor, and +satisfactory, though; the 387 is no match for the 486's coprocessor, and most 386 systems lack coprocessors. However, all is not lost; 32-bit registers and built-in integer multiply and divide hardware make it possible to do some very interesting 3-D animation on a 386 with -fixed-point arithmetic. Actually, it’s possible to do a surprising +fixed-point arithmetic. Actually, it's possible to do a surprising amount of 3-D animation in real mode, and even on lesser x86 processors; in fact, the code in this article will perform real-time 3-D animation (admittedly very simple, but nonetheless real-time and 3-D) on a 286 @@ -54,17 +54,17 @@ floating-point arithmetic. In short, the potential for 3-D animation on the x86 family is considerable. With this chapter, we kick off an exploration of some of the sorts of -3-D animation that can be performed on the x86 family. Mind you, I’m +3-D animation that can be performed on the x86 family. Mind you, I'm talking about real-time 3-D animation, with all calculations and drawing performed on-the-fly. Generating frames ahead of time and playing them -back is an excellent technique, but I’m interested in seeing how far we -can push purely real-time animation. Granted, we’re not going to make it +back is an excellent technique, but I'm interested in seeing how far we +can push purely real-time animation. Granted, we're not going to make it to the level of Terminator 2, but we should have some fun nonetheless. The first few chapters in this final section of the book may seem pretty basic to those of you experienced with 3-D programming, and, at the same time, 3-D neophytes will inevitably be distressed at the amount of -material I skip or skim over. That can’t be helped, but at least -there’ll be working code, the references mentioned later, and some +material I skip or skim over. That can't be helped, but at least +there'll be working code, the references mentioned later, and some explanation; that should be enough to start you on your way with 3-D. Animating in three dimensions is a complex task, so this will be the @@ -72,46 +72,46 @@ largest single section of the book, with later chapters building on earlier ones; and even this first 3-D chapter will rely on polygon fill and page-flip code from earlier chapters. -In a sense, I’ve saved the best for last, because, to my mind, real-time +In a sense, I've saved the best for last, because, to my mind, real-time 3-D animation is one of the most exciting things of any stripe that can -be done with a computer—and because, with today’s hardware, it can in +be done with a computer—and because, with today's hardware, it can in fact be done. Nay, it can be done amazingly well. ### References on 3-D Drawing {#Heading3} There are several good sources for information about 3-D graphics. Foley -and van Dam’s *Computer Graphics: Principles and Practice* (Second +and van Dam's *Computer Graphics: Principles and Practice* (Second Edition, Addison-Wesley, 1990) provides a lengthy discussion of the topic and a great many references for further study. Unfortunately, this book is heavy going at times; a more approachable discussion is provided in *Principles of Interactive Computer Graphics*, by Newman and Sproull -(McGraw-Hill, 1979). Although the latter book lacks the last decade’s +(McGraw-Hill, 1979). Although the latter book lacks the last decade's worth of graphics developments, it nonetheless provides a good overview of basic 3-D techniques, including many of the approaches likely to work well in realtime on a PC. A source that you may or may not find useful is the series of six books on C graphics by Lee Adams, as exemplified by *High-Performance CAD -Graphics in C* (Windcrest/Tab, 1986). (I don’t know if all six books -discuss 3-D graphics, but the four I’ve seen do.) To be honest, this +Graphics in C* (Windcrest/Tab, 1986). (I don't know if all six books +discuss 3-D graphics, but the four I've seen do.) To be honest, this book has a number of problems, including: Relatively little theory and explanation; incomplete and sometimes erroneous discussions of graphics hardware; use of nothing but global variables, with cryptic names like -“array3” and “B21;” and—well, you get the idea. On the other hand, the +"array3" and "B21;" and—well, you get the idea. On the other hand, the book at least touches on a great many aspects of 3-D drawing, and -there’s a lot of C code to back that up. A number of people have spoken -warmly to me of Adams’ books as their introduction to 3-D graphics. I -wouldn’t recommend these books as your only 3-D references, but if -you’re just starting out, you might want to look at one and see if it +there's a lot of C code to back that up. A number of people have spoken +warmly to me of Adams' books as their introduction to 3-D graphics. I +wouldn't recommend these books as your only 3-D references, but if +you're just starting out, you might want to look at one and see if it helps you bridge the gap between the theory and implementation of 3-D graphics. ### The 3-D Drawing Pipeline {#Heading4} -Each 3-D object that we’ll handle will be built out of polygons that +Each 3-D object that we'll handle will be built out of polygons that represent the surface of the object. Figure 50.1 shows the stages a polygon goes through enroute to being drawn on the screen. (For the -present, we’ll avoid complications such as clipping, lighting, and +present, we'll avoid complications such as clipping, lighting, and shading.) First, the polygon is transformed from object space, the coordinate system the object is defined in, to world space, the coordinate system of the 3-D universe. Transformation may involve @@ -130,8 +130,8 @@ axis off to the right. Once in view space, the polygon can be perspective-projected to the screen, with the projected X and Y coordinates of the vertices finally being used to draw the polygon. -That’s really all there is to basic 3-D drawing: transformation from -object space to world space to view space to the screen. Next, we’ll +That's really all there is to basic 3-D drawing: transformation from +object space to world space to view space to the screen. Next, we'll look at the mechanics of transformation. ------------------------ --------------------------------- -------------------- diff --git a/50-02.md b/50-02.md index e819fd8..e933def 100644 --- a/50-02.md +++ b/50-02.md @@ -2,7 +2,7 @@ [Previous](50-01.html) [Table of Contents](index.html) [Next](50-03.html) ------------------------ --------------------------------- -------------------- -One note: I’ll use a purely *right-handed* convention for coordinate +One note: I'll use a purely *right-handed* convention for coordinate systems. Right-handed means that if you hold your right hand with your fingers curled and the thumb sticking out, the thumb points along the Z axis and the fingers point in the direction of rotation from the X axis @@ -29,12 +29,12 @@ Working backward from the final image, we want to take the vertices of a polygon, as transformed into view space, and project them to 2-D coordinates on the screen, which, for projection purposes, is assumed to be centered on and perpendicular to the Z axis in view space, at some -distance from the screen. We’re after visual realism, so we’ll want to +distance from the screen. We're after visual realism, so we'll want to do a perspective projection, in order that farther objects look smaller than nearer objects, and so that the field of view will widen with distance. This is done by scaling the X and Y coordinates of each point proportionately to the Z distance of the point from the viewer, a simple -matter of similar triangles, as shown in Figure 50.3. It doesn’t really +matter of similar triangles, as shown in Figure 50.3. It doesn't really matter how far down the Z axis the screen is assumed to be; what matters is the ratio of the distance of the screen from the viewpoint to the width of the screen. This ratio defines the rate of divergence of the @@ -60,7 +60,7 @@ the object may be located anywhere. #### Rotation {#Heading7} *Rotation* is the process of circularly moving coordinates around the -origin. For our present purposes, it’s necessary only to rotate objects +origin. For our present purposes, it's necessary only to rotate objects about their centers in object space, so as to turn them to the desired attitude before translating them into world space. @@ -77,8 +77,8 @@ matrix can then be used to perform the rotations more efficiently. Second, 3x3 rotation matrices can become the upper-left-hand portions of 4x4 matrices that also perform translation (and scaling as well, but we -won’t need scaling in the near future), as shown in Figure 50.5. A 4x4 -matrix of this sort utilizes homogeneous coordinates; that’s a topic way +won't need scaling in the near future), as shown in Figure 50.5. A 4x4 +matrix of this sort utilizes homogeneous coordinates; that's a topic way beyond this book, but, basically, homogeneous coordinates allow you to handle both rotations and translations with 4x4 matrices, thereby allowing the same code to work with either, and making it possible to @@ -86,9 +86,9 @@ concatenate a long series of rotations and translations into a single matrix that performs the same transformation as the sequence of rotations and transformations. -There’s much more to be said about transformations and the supporting +There's much more to be said about transformations and the supporting matrix math, but, in the interests of getting to working code in this -chapter, I’ll leave that to be discussed as the need arises. +chapter, I'll leave that to be discussed as the need arises. ### A Simple 3-D Example {#Heading8} @@ -96,8 +96,8 @@ At this point, we know enough to be able to put together a simple working 3-D animation example. The example will do nothing more complicated than display a single polygon as it sits in 3-D space, rotating around the Y axis. To make things a little more interesting, -we’ll let the user move the polygon around in space with the arrow keys, -and with the “A” (away), and “T” (toward) keys. The sample program +we'll let the user move the polygon around in space with the arrow keys, +and with the "A" (away), and "T" (toward) keys. The sample program requires two sorts of functionality: The ability to transform and project the polygon from object space onto the screen (3-D functionality), and the ability to draw the projected polygon (complete @@ -109,13 +109,13 @@ functionality). Happily (and not coincidentally), we put together a nice 2-D animation framework back in Chapters 47, 48, and 49, during our exploratory -discussion of Mode X, so we don’t have much to worry about in terms of -non-3-D details. Basically, we’ll use Mode X (320x240, 256 colors), and -we’ll flip between two display pages, drawing to one while the other is +discussion of Mode X, so we don't have much to worry about in terms of +non-3-D details. Basically, we'll use Mode X (320x240, 256 colors), and +we'll flip between two display pages, drawing to one while the other is displayed. One new 2-D element that we need is the ability to clip polygons; while we could avoid this for the moment by restricting the range of motion of the polygon so that it stays fully on the screen, -certainly in the long run we’ll want to be able to handle partially or +certainly in the long run we'll want to be able to handle partially or fully clipped polygons. Listing 50.1 is the low-level code for a Mode X polygon filler that supports clipping. (The high-level polygon fill code is mode independent, and is the same as that presented in Chapters 38, @@ -124,7 +124,7 @@ level, by trimming the Y extent of the scan line list up front, then clipping the X coordinates of each scan line in turn. This is not a particularly fast approach to clipping—ideally, the polygon would be clipped before it was scanned into a line list, avoiding potentially -wasted scanning and eliminating the line-by-line X clipping—but it’s +wasted scanning and eliminating the line-by-line X clipping—but it's much simpler, and, as we shall see, polygon filling performance is the least of our worries at the moment. diff --git a/50-03.md b/50-03.md index 6af3dd0..3d56987 100644 --- a/50-03.md +++ b/50-03.md @@ -5,7 +5,7 @@ **LISTING 50.1 L50-1.ASM** ; Draws all pixels in the list of horizontal lines passed in, in - ; Mode X, the VGA’s undocumented 320x240 256-color mode. Clips to + ; Mode X, the VGA's undocumented 320x240 256-color mode. Clips to ; the rectangle specified by (ClipMinX,ClipMinY),(ClipMaxX,ClipMaxY). ; Draws to the page specified by CurrentPageBase. ; C near-callable as: @@ -50,9 +50,9 @@ public _DrawHorizontalLineList align 2 _DrawHorizontalLineList proc - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to our stack frame - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di cld ;make string instructions inc pointers mov dx,SC_INDEX @@ -66,7 +66,7 @@ mov cx,[si+YStart] ;first scan line to draw mov si,[si+Lngth] ;# of scan lines to draw cmp si,0 ;are there any lines to draw? - jle ToFillDone ;no, so we’re done + jle ToFillDone ;no, so we're done cmp cx,[_ClipMinY] ;clipped at top? jge MinYNotClipped ;no neg cx ;yes, discard however many lines are @@ -122,8 +122,8 @@ sub cx,dx shr cx,1 shr cx,1 ;# of addresses across rectangle to fill - 1 - jnz MasksSet ;there’s more than one byte to draw - and bh,bl ;there’s only one byte, so combine the left + jnz MasksSet ;there's more than one byte to draw + and bh,bl ;there's only one byte, so combine the left ; and right edge clip masks MasksSet: mov dx,SC_INDEX+1 ;already points to the Map Mask reg @@ -133,7 +133,7 @@ mov al,ah ;put color in AL stosb ;draw the left edge dec cx ;count off left edge byte - js FillLoopBottom ;that’s the only byte + js FillLoopBottom ;that's the only byte jz DoRightEdge ;there are only two bytes mov al,00fh ;middle addresses are drawn 4 pixels at a pop out dx,al ;set the middle pixel mask to no clip @@ -154,9 +154,9 @@ dec si ;count down lines jnz FillLoop FillDone: - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret _DrawHorizontalLineList endp end diff --git a/50-04.md b/50-04.md index 9930f88..196ecd0 100644 --- a/50-04.md +++ b/50-04.md @@ -3,11 +3,11 @@ ------------------------ --------------------------------- -------------------- The other 2-D element we need is some way to erase the polygon at its -old location before it’s moved and redrawn. We’ll do that by remembering -the bounding rectangle of the polygon each time it’s drawn, then erasing +old location before it's moved and redrawn. We'll do that by remembering +the bounding rectangle of the polygon each time it's drawn, then erasing by clearing that area with a rectangle fill. -With the 2-D side of the picture well under control, we’re ready to +With the 2-D side of the picture well under control, we're ready to concentrate on the good stuff. Listings 50.2 through 50.5 are the sample 3-D animation program. Listing 50.2 provides matrix multiplication functions in a straightforward fashion. Listing 50.3 transforms, @@ -79,9 +79,9 @@ but it will certainly reduce confusion! represents a transformation from object space through world space to view space), then projects the transformed polygon onto the screen and draws it in color ???Color. Also updates the extent of the - rectangle (EraseRect) that’s used to erase the screen later. + rectangle (EraseRect) that's used to erase the screen later. Tested with Borland C++ in the small model. */ - #include “polygon.h” + #include "polygon.h" void XformAndProjectPoly(double Xform[4][4], struct Point3 * Poly, int PolyLength, int Color) diff --git a/50-06.md b/50-06.md index 3047f20..d0cd0f1 100644 --- a/50-06.md +++ b/50-06.md @@ -14,7 +14,7 @@ #include #include #include - #include “polygon.h” + #include "polygon.h" void main(void); /* Base offset of page to which to draw */ @@ -25,7 +25,7 @@ /* Rectangle specifying extent to be erased in each page */ struct Rect EraseRect[2] = { {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}, {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT} }; - /* Transformation from polygon’s object space to world space. + /* Transformation from polygon's object space to world space. Initially set up to perform no rotation and to move the polygon into world space -140 units away from the origin down the Z axis. Given the viewing point, -140 down the Z axis means 140 units away @@ -95,10 +95,10 @@ switch (getch()) { case 0x1B: /* Esc to exit */ Done = 1; break; - case ‘A’: case ‘a’: /* away (-Z) */ + case ‘A': case ‘a': /* away (-Z) */ PolyWorldXform[2][3] -= 3.0; break; - case ‘T’: /* towards (+Z). Don’t allow to get too */ - case ‘t’: /* close, so Z clipping isn’t needed */ + case ‘T': /* towards (+Z). Don't allow to get too */ + case ‘t': /* close, so Z clipping isn't needed */ if (PolyWorldXform[2][3] < -40.0) PolyWorldXform[2][3] += 3.0; break; case 0: /* extended code */ diff --git a/50-07.md b/50-07.md index 77d05b4..214fbd3 100644 --- a/50-07.md +++ b/50-07.md @@ -4,9 +4,9 @@ #### Notes on the 3-D Animation Example {#Heading9} -The sample program transforms the polygon’s vertices from object space +The sample program transforms the polygon's vertices from object space to world space to view space to the screen, as described earlier. In -this case, world space and view space are congruent—we’re looking right +this case, world space and view space are congruent—we're looking right down the negative Z axis of world space—so the transformation matrix from world to view is the identity matrix; you might want to experiment with changing this matrix to change the viewpoint. The sample program @@ -26,23 +26,23 @@ initially appear to be; more in the near future. The maximum translation along the Z axis is limited to -40; this keeps the polygon from extending past the viewpoint to positive Z coordinates. This would wreak havoc with the projection and 2-D clipping, and would -require 3-D clipping, which is far more complicated than 2-D. We’ll get -to 3-D clipping at some point, but, for now, it’s much simpler just to +require 3-D clipping, which is far more complicated than 2-D. We'll get +to 3-D clipping at some point, but, for now, it's much simpler just to limit all vertices to negative Z coordinates. The polygon does get -mighty close to the viewpoint, though; run the program and use the “T” +mighty close to the viewpoint, though; run the program and use the "T" key to move the polygon as close as possible—the near vertex swinging past provides a striking sense of perspective. The performance of Listing 50.5 is, perhaps, surprisingly good, clocking in at 16 frames per second on a 20 MHz 386 with a VGA of average speed and no 387, although there is, of course, only one polygon being drawn, -rather than the hundreds or thousands we’d ultimately like. What’s far +rather than the hundreds or thousands we'd ultimately like. What's far more interesting is where the execution time goes. Even though the program is working with only one polygon, 73 percent of the time goes for transformation and projection. An additional 7 percent is spent waiting to flip the screen. Only 20 percent of the total time is spent in all other activity—and only 2 percent is spent actually drawing -polygons. Clearly, we’ll want to tackle transformation and projection +polygons. Clearly, we'll want to tackle transformation and projection first when we look to speed things up. (Note, however, that a math coprocessor would considerably decrease the time taken by floating-point calculations.) @@ -55,19 +55,19 @@ putting clipping into the fill code, but, unfortunately, I lack the space to do that here. Finally, observe the jaggies crawling along the edges of the polygon as -it rotates. This is temporal aliasing at its finest! We won’t address +it rotates. This is temporal aliasing at its finest! We won't address antialiasing further, realtime antialiasing being decidedly nontrivial, but this should give you an idea of why antialiasing is so desirable. ### An Ongoing Journey {#Heading10} -In the next chapter, we’ll assign fronts and backs to polygons, and +In the next chapter, we'll assign fronts and backs to polygons, and start drawing only those that are facing the viewer. That will enable us -to handle convex polyhedrons, such as tetrahedrons and cubes. We’ll also +to handle convex polyhedrons, such as tetrahedrons and cubes. We'll also look at interactively controllable rotation, and at more complex rotations than the simple rotation around the Y axis that we did this -time. In time, we’ll use fixed-point arithmetic to speed things up, and -do some shading and texture mapping. The journey has only begun; we’ll +time. In time, we'll use fixed-point arithmetic to speed things up, and +do some shading and texture mapping. The journey has only begun; we'll get to all that and more soon. ------------------------ --------------------------------- -------------------- diff --git a/51-01.md b/51-01.md index 857c07e..70e92c3 100644 --- a/51-01.md +++ b/51-01.md @@ -8,12 +8,12 @@ Chapter 51\ ### Using Backface Removal to Eliminate Hidden Surfaces {#Heading2} -As I’m fond of pointing out, computer animation isn’t a matter of +As I'm fond of pointing out, computer animation isn't a matter of mathematically exact modeling or raw technical prowess, but rather of -fooling the eye and the mind. That’s especially true for 3-D animation, -where we’re not only trying to convince viewers that they’re seeing +fooling the eye and the mind. That's especially true for 3-D animation, +where we're not only trying to convince viewers that they're seeing objects on a screen—when in truth that screen contains no objects at -all, only gaggles of pixels—but we’re also trying to create the illusion +all, only gaggles of pixels—but we're also trying to create the illusion that the objects exist in three-space, possessing four dimensions (counting movement over time as a fourth dimension) of their own. To make this magic happen, we must provide cues for the eye not only to @@ -23,16 +23,16 @@ and rapid and smooth screen updates; the whole deal is considerably more difficult to pull off on a PC than 2-D animation. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *In some senses, however, 3-D animation is easier than 2-D. Because there’s more going on in 3-D animation, the eye and brain tend to make more assumptions, and so are more apt to see what they expect to see, rather than what’s actually there.* + ![](images/i.jpg) *In some senses, however, 3-D animation is easier than 2-D. Because there's more going on in 3-D animation, the eye and brain tend to make more assumptions, and so are more apt to see what they expect to see, rather than what's actually there.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -If you’re piloting a (virtual) ship through a field of thousands of -asteroids at high speed, you’re unlikely to notice if the more distant +If you're piloting a (virtual) ship through a field of thousands of +asteroids at high speed, you're unlikely to notice if the more distant asteroids occasionally seem to go right through each other, or if the -topographic detail on the asteroids’ surfaces sometimes shifts about a -bit. You’ll be busy viewing the asteroids in their primary role, as +topographic detail on the asteroids' surfaces sometimes shifts about a +bit. You'll be busy viewing the asteroids in their primary role, as objects to be navigated around, and the mere presence of topographic -detail will suffice; without being aware of it, you’ll fill in the +detail will suffice; without being aware of it, you'll fill in the blanks. Your mind will see the topography peripherally, recognize it for what it is supposed to be, and, unless the landscape does something really obtrusive such as vanishing altogether or suddenly shooting a @@ -43,21 +43,21 @@ To what extent can you rely on the eye and mind to make up for imperfections in the 3-D animation process? In some areas, hardly at all; for example, jaggies crawling along edges stick out like red flags, and likewise for flicker. In other areas, though, the human perceptual -system is more forgiving than you’d think. Consider this: At the end of +system is more forgiving than you'd think. Consider this: At the end of *Return of the Jedi*, in the battle to end all battles around the Death Star, there is a sequence of about five seconds in which several spaceships are visible in the background. One of those spaceships (and -it’s not very far in the background, either) looks a bit unusual. What +it's not very far in the background, either) looks a bit unusual. What it looks like is a sneaker. In fact, it *is* a sneaker—but unless you -know to look for it, you’ll never notice it, because your mind is busy -making simplifying assumptions about the complex scene it’s seeing—and +know to look for it, you'll never notice it, because your mind is busy +making simplifying assumptions about the complex scene it's seeing—and one of those assumptions is that medium-sized objects floating in space are spaceships, unless proven otherwise. (Thanks to Chris Hecker for -pointing this out. I’d never have noticed the sneaker, myself, without +pointing this out. I'd never have noticed the sneaker, myself, without being tipped off—which is, of course, the whole point.) -If it’s good enough for George Lucas, it’s good enough for us. And with -that, let’s resume our quest for realtime 3-D animation on the PC. +If it's good enough for George Lucas, it's good enough for us. And with +that, let's resume our quest for realtime 3-D animation on the PC. ### One-sided Polygons: Backface Removal {#Heading3} @@ -72,8 +72,8 @@ implemented this pipeline, we were able to watch as a polygon rotated about its Y axis, and were able to move the polygon around in space freely. -One of the drawbacks of the previous chapter’s approach was that the -polygon had two visible sides. Why is that a drawback? It isn’t, +One of the drawbacks of the previous chapter's approach was that the +polygon had two visible sides. Why is that a drawback? It isn't, necessarily, but in our case we want to use polygons to build solid objects with continuous surfaces, and in that context, only one side of a polygon is visible; the other side always faces the inside of the @@ -85,7 +85,7 @@ had the outside face pointing toward the viewer). On average, half the polygons in an object could be instantly rejected by a test of this sort. Such testing of polygon visibility goes by a number of names in the literature, including backplane culling, backface removal, and -assorted variations thereon; I’ll refer to it as *backface removal*. +assorted variations thereon; I'll refer to it as *backface removal*. ------------------------ --------------------------------- -------------------- [Previous](50-07.html) [Table of Contents](index.html) [Next](51-02.html) diff --git a/51-02.md b/51-02.md index 815eefd..d073495 100644 --- a/51-02.md +++ b/51-02.md @@ -2,7 +2,7 @@ [Previous](51-01.html) [Table of Contents](index.html) [Next](51-03.html) ------------------------ --------------------------------- -------------------- -For a single convex polyhedron, removal of polygons that aren’t facing +For a single convex polyhedron, removal of polygons that aren't facing the viewer would solve all hidden surface problems. In a convex polyhedron, any polygon facing the viewer can never be obscured by any other polygon in that polyhedron; this falls out of the definition of a @@ -13,7 +13,7 @@ the viewer, everything will work out properly, with no additional checking for overlap and hidden surfaces needed. Unfortunately, backface removal completely solves the hidden surface -problem for convex polyhedrons *only*, and only if there’s a single +problem for convex polyhedrons *only*, and only if there's a single convex polyhedron involved; when convex polyhedrons overlap, other methods must be used. Nonetheless, backface removal does instantly halve the number of polygons to be handled in rendering any particular scene. @@ -22,20 +22,20 @@ built out of convex polyhedrons. In this chapter, though, we have only one convex polyhedron to deal with, so backface removal alone will do the trick. -Given that I’ve convinced you that backface removal would be a handy +Given that I've convinced you that backface removal would be a handy thing to have, how do we actually do it? A logical approach, often implemented in the PC literature, would be to calculate the plane equation for the plane in which the polygon lies, and see which way the normal (perpendicular) vector to the plane points. That works, but -there’s a more efficient way to calculate the normal to the polygon: as -the cross-product of two of the polygon’s edges. +there's a more efficient way to calculate the normal to the polygon: as +the cross-product of two of the polygon's edges. The cross-product of two vectors is defined as the vector shown in Figure 51.1. One interesting property of the cross-product vector is that it is perpendicular to the plane in which the two original vectors lie. If we take the cross-product of the vectors that form two edges of a polygon, the result will be a vector perpendicular to the polygon; -then, we’ll know that the polygon is visible if and only if the +then, we'll know that the polygon is visible if and only if the cross-product vector points toward the viewer. We need one more thing to make the cross-product approach work, though. The cross-product can actually point either way, depending on which edges of the polygon we @@ -46,22 +46,22 @@ cross-product. ![](images/51-01.jpg)\ **Figure 51.1**  *The cross-product of two vectors.* -We’ll define only convex polygons, with the vertices defined in -clockwise order, as viewed from the outside; that is, if you’re looking +We'll define only convex polygons, with the vertices defined in +clockwise order, as viewed from the outside; that is, if you're looking at the visible side of the polygon, the vertices will appear in the polygon definition in clockwise order. With those assumptions, the cross-product becomes a quick and easy indicator of polygon orientation -with respect to the viewer; we’ll calculate it as the cross-product of +with respect to the viewer; we'll calculate it as the cross-product of the first and last vectors in a polygon, as shown in Figure 51.2, and if -it’s pointing toward the viewer, we’ll know that the polygon is visible. -Actually, we don’t even have to calculate the entire cross-product +it's pointing toward the viewer, we'll know that the polygon is visible. +Actually, we don't even have to calculate the entire cross-product vector, because the Z component alone suffices to tell us which way the polygon is facing: positive Z means visible, negative Z means not. The Z component can be calculated very efficiently, with only two multiplies and a subtraction. The question remains of the proper space in which to perform backface -removal. There’s a temptation to perform it in view space, which is, +removal. There's a temptation to perform it in view space, which is, after all, the space defined with respect to the viewer, but view space is not a good choice. Screen space—the space in which perspective projection has been performed—is the best choice. The purpose of @@ -77,15 +77,15 @@ normal.* Backface removal may also be performed using the polygon vertices in screen coordinates, which are integers. This is less accurate than using the screen space coordinates, which are floating point, but is, by the -same token, faster. In Listing 51.3, which we’ll discuss shortly, +same token, faster. In Listing 51.3, which we'll discuss shortly, backface removal is performed in screen coordinates in the interests of speed. Backface removal, as implemented in Listing 51.3, will not work reliably -if the polygon is not convex, if the vertices don’t appear in clockwise +if the polygon is not convex, if the vertices don't appear in clockwise order, if either the first or last edge in a polygon has zero length, or if the first and last edges are collinear. These latter two points are -the reason it’s preferable to work in screen space rather than screen +the reason it's preferable to work in screen space rather than screen coordinates (which suffer from rounding problems), speed considerations aside. diff --git a/51-03.md b/51-03.md index 72d28d1..7242f98 100644 --- a/51-03.md +++ b/51-03.md @@ -11,7 +11,7 @@ #include #include #include - #include “polygon.h” + #include "polygon.h" #define ROTATION (M_PI / 30.0) /* rotate by 6 degrees at a time */ @@ -26,7 +26,7 @@ static unsigned int PageStartOffsets[2] = {PAGE0_START_OFFSET,PAGE1_START_OFFSET}; int DisplayedPage, NonDisplayedPage; - /* Transformation from cube’s object space to world space. Initially + /* Transformation from cube's object space to world space. Initially set up to perform no rotation and to move the cube into world space -100 units away from the origin down the Z axis. Given the viewing point, -100 down the Z axis means 100 units away in the @@ -114,25 +114,25 @@ switch (getch()) { case 0x1B: /* Esc to exit */ Done = 1; break; - case ‘A’: case ‘a’: /* away (-Z) */ + case ‘A': case ‘a': /* away (-Z) */ CubeWorldXform[2][3] -= 3.0; RecalcXform = 1; break; - case ‘T’: /* towards (+Z). Don’t allow to get too */ - case ‘t’: /* close, so Z clipping isn’t needed */ + case ‘T': /* towards (+Z). Don't allow to get too */ + case ‘t': /* close, so Z clipping isn't needed */ if (CubeWorldXform[2][3] < -40.0) { CubeWorldXform[2][3] += 3.0; RecalcXform = 1; } break; - case ‘4’: /* rotate clockwise around Y */ + case ‘4': /* rotate clockwise around Y */ AppendRotationY(CubeWorldXform, -ROTATION); RecalcXform=1; break; - case ‘6’: /* rotate counterclockwise around Y */ + case ‘6': /* rotate counterclockwise around Y */ AppendRotationY(CubeWorldXform, ROTATION); RecalcXform=1; break; - case ‘8’: /* rotate clockwise around X */ + case ‘8': /* rotate clockwise around X */ AppendRotationX(CubeWorldXform, -ROTATION); RecalcXform=1; break; - case ‘2’: /* rotate counterclockwise around X */ + case ‘2': /* rotate counterclockwise around X */ AppendRotationX(CubeWorldXform, ROTATION); RecalcXform=1; break; case 0: /* extended code */ diff --git a/51-04.md b/51-04.md index 4a2a365..2e46a32 100644 --- a/51-04.md +++ b/51-04.md @@ -8,7 +8,7 @@ perspective projects them to screen space and maps them to screen coordinates, storing the results in the object. */ #include - #include “polygon.h”/ + #include "polygon.h"/ void XformAndProjectPoints(double Xform[4][4], struct Object * ObjectToXform) @@ -44,7 +44,7 @@ /* Draws all visible faces (faces pointing toward the viewer) in the specified object. The object must have previously been transformed and projected, so that the ScreenVertexList array is filled in. */ - #include “polygon.h” + #include "polygon.h" void DrawVisibleFaces(struct Object * ObjectToXform) { @@ -59,7 +59,7 @@ /* Draw each visible face (polygon) of the object in turn */ for (i=0; iNumVerts; - /* Copy over the face’s vertices from the vertex list */ + /* Copy over the face's vertices from the vertex list */ for (j=0, VertNumsPtr=FacePtr->VertNums; j - #include “polygon.h” + #include "polygon.h" /* Concatenate a rotation by Angle around the X axis to the transformation in XformToChange, placing result back in XformToChange. */ diff --git a/51-06.md b/51-06.md index d02cab8..6991d0b 100644 --- a/51-06.md +++ b/51-06.md @@ -91,7 +91,7 @@ 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. @@ -105,7 +105,7 @@ only in their original, untransformed state, but in three other forms as well: transformed to view space, transformed and projected to screen space, and converted to screen coordinates. Earlier, we saw that it can be convenient to store the screen coordinates within the object, so that -if the object hasn’t moved with respect to the viewer, it can be redrawn +if the object hasn't moved with respect to the viewer, it can be redrawn without the need for recalculation, but why bother storing the view and screen space forms of the vertices as well? @@ -115,15 +115,15 @@ by the viewer, you must first know how they look to the viewer, accounting for perspective; screen space provides that information. (So do the final screen coordinates, but with less accuracy, and without any Z information.) The view space vertices are useful for collision and -proximity detection; screen space can’t be used here, because objects +proximity detection; screen space can't be used here, because objects are distorted by the perspective projection into screen space. World space would serve as well as view space for collision detection, but -because it’s possible to transform directly from object space to view -space with a single matrix, it’s often preferable to skip over world -space. It’s not mandatory that vertices be stored for all these +because it's possible to transform directly from object space to view +space with a single matrix, it's often preferable to skip over world +space. It's not mandatory that vertices be stored for all these different spaces, but the coordinates in all those spaces have to be calculated as intermediate steps anyway, so we might as well keep them -around for those occasions when they’re needed. +around for those occasions when they're needed. ------------------------ --------------------------------- -------------------- [Previous](51-05.html) [Table of Contents](index.html) [Next](52-01.html) diff --git a/52-01.md b/52-01.md index 583ce4e..4861d37 100644 --- a/52-01.md +++ b/52-01.md @@ -9,11 +9,11 @@ Chapter 52\ ### The First Iteration of a Generalized 3-D Animation Package {#Heading2} Across the lake from Vermont, a few miles into upstate New York, the -Ausable River has carved out a fairly impressive gorge known as “Ausable -Chasm.” Impressive for the East, anyway; you might think of it as the -poor man’s Grand Canyon. Some time back, I did the tour with my wife and -five-year-old, and it was fun, although I confess that I didn’t loosen -my grip on my daughter’s hand until we were on the bus and headed for +Ausable River has carved out a fairly impressive gorge known as "Ausable +Chasm." Impressive for the East, anyway; you might think of it as the +poor man's Grand Canyon. Some time back, I did the tour with my wife and +five-year-old, and it was fun, although I confess that I didn't loosen +my grip on my daughter's hand until we were on the bus and headed for home; that gorge is deep, and the railings tend to be of the single-bar, rusted-out variety. @@ -21,7 +21,7 @@ New Yorkers can drive straight to this wonder of nature, but Vermonters must take their cars across on the ferry; the alternative is driving three hours around the south end of Lake Champlain. No problem; the ferry ride is an hour well spent on a beautiful lake. Or, rather, no -problem—once you’re on the ferry. Getting to New York is easy, but, as +problem—once you're on the ferry. Getting to New York is easy, but, as we found out, the line of cars waiting to come back from Ausable Chasm gets lengthy about mid-afternoon. The ferry can hold only so many cars, and we wound up spending an unexpected hour exploring the wonders of the @@ -35,41 +35,41 @@ graders to Ausable Chasm. Never mind the potential for trouble with 65 kids loose on a ferry. Never mind what it was like trying to herd that group around a gorge that looks like it was designed to swallow children and small animals without a trace. The hard part was getting back to the -docks and finding they’d have to wait an hour for the next ferry. As my -friend put it, “Let me tell you, an hour is an eternity with 65 sixth -graders screaming the song ‘You Are My Sunshine.’” +docks and finding they'd have to wait an hour for the next ferry. As my +friend put it, "Let me tell you, an hour is an eternity with 65 sixth +graders screaming the song ‘You Are My Sunshine.'" Apart from reminding you how lucky you are to be working in a quiet, air-conditioned room, in front of a gently humming computer, free to -think deep thoughts and eat Cheetos to your heart’s content, this story +think deep thoughts and eat Cheetos to your heart's content, this story provides a useful perspective on the malleable nature of time. An hour -isn’t just an hour—it can be forever, or it can be the wink of an eye. +isn't just an hour—it can be forever, or it can be the wink of an eye. Just think of the last hour you spent working under a deadline; I bet it went past in a flash. Which is not to say, mind you, that I recommend working in a bus full of screaming kids in order to make time pass more slowly; there are quality issues here as well. -In our 3-D animation work so far, we’ve used floating-point arithmetic. +In our 3-D animation work so far, we've used floating-point arithmetic. Floating-point arithmetic—even with a floating-point processor but especially *without* one—is the microcomputer animation equivalent of working in a school bus: It takes forever to do anything, and you just -*know* you’re never going to accomplish as much as you want to. In this -chapter, we’ll address fixed-point arithmetic, which will give us an -instant order-of-magnitude performance boost. We’ll also give our 3-D +*know* you're never going to accomplish as much as you want to. In this +chapter, we'll address fixed-point arithmetic, which will give us an +instant order-of-magnitude performance boost. We'll also give our 3-D animation code a much more powerful and extensible framework, making it easy to add new and different sorts of objects. Taken together, these alterations will let us start to do some really interesting real-time animation. -### This Chapter’s Demo Program {#Heading3} +### This Chapter's Demo Program {#Heading3} Three-dimensional animation is a complicated business, and it takes an astonishing amount of functionality just to get off the launching pad: page flipping, polygon filling, clipping, transformations, list -management, and so forth. I’ve been building toward a critical mass of -animation functionality over the course of this book, and this chapter’s +management, and so forth. I've been building toward a critical mass of +animation functionality over the course of this book, and this chapter's code builds on the code from no fewer than five previous chapters. The -code that’s required in order to link this chapter’s animation demo +code that's required in order to link this chapter's animation demo program is the following: - Listing 50.1 from Chapter 50 (draw clipped line list); @@ -82,7 +82,7 @@ program is the following: must be removed to reflect the switch to typedefs in the animation header file. -As always, all required files are in this chapter’s subdirectory on the +As always, all required files are in this chapter's subdirectory on the CD-ROM. ------------------------ --------------------------------- -------------------- diff --git a/52-02.md b/52-02.md index 4cc7c56..1859ed7 100644 --- a/52-02.md +++ b/52-02.md @@ -9,7 +9,7 @@ #include #include - #include “polygon.h” + #include "polygon.h" /* base offset of page to which to draw */ unsigned int CurrentPageBase = 0; @@ -88,7 +88,7 @@ to retransform the vertices. */ #include - #include “polygon.h” + #include "polygon.h" void XformAndProjectPObject(PObject * ObjectToXform) { diff --git a/52-03.md b/52-03.md index f98d393..8c93515 100644 --- a/52-03.md +++ b/52-03.md @@ -7,7 +7,7 @@ /* Routines to perform incremental rotations around the three axes. */ #include - #include “polygon.h” + #include "polygon.h" /* Concatenate a rotation by Angle around the X axis to transformation in XformToChange, placing the result back into XformToChange. */ @@ -93,7 +93,7 @@ /* Fixed point matrix arithmetic functions. */ - #include “polygon.h” + #include "polygon.h" /* Matrix multiplies Xform by SourceVec, and stores the result in DestVec. Multiplies a 4x4 matrix times a 4x1 matrix; the result is a 4x1 matrix. Cheats @@ -135,7 +135,7 @@ /* Set up basic data that needs to be in fixed point, to avoid data definition hassles. */ - #include “polygon.h” + #include "polygon.h" /* All vertices in the basic cube */ static IntPoint3 IntCubeVerts[NUM_CUBE_VERTS] = { diff --git a/52-04.md b/52-04.md index a856bbb..a97ad1b 100644 --- a/52-04.md +++ b/52-04.md @@ -7,7 +7,7 @@ /* Rotates and moves a polygon-based object around the three axes. Movement is implemented only along the Z axis currently. */ - #include “polygon.h” + #include "polygon.h" void RotateAndMovePObject(PObject * ObjectToMove) { @@ -40,7 +40,7 @@ previously been transformed and projected, so that ScreenVertexList array is filled in. */ - #include “polygon.h” + #include "polygon.h" void DrawPObject(PObject * ObjectToXform) { diff --git a/52-05.md b/52-05.md index a3344ce..36fdf1b 100644 --- a/52-05.md +++ b/52-05.md @@ -8,7 +8,7 @@ #include #include - #include “polygon.h” + #include "polygon.h" #define ROT_6 (M_PI / 30.0) /* rotate 6 degrees at a time */ #define ROT_3 (M_PI / 60.0) /* rotate 3 degrees at a time */ @@ -66,7 +66,7 @@ for (i=0; iDrawFunc = DrawPObject; WorkingCube->RecalcFunc = XformAndProjectPObject; WorkingCube->MoveFunc = RotateAndMovePObject; @@ -107,16 +107,16 @@ WorkingCube->Move.MaxZ = INT_TO_FIXED(InitialMove[i].MaxZ); if ((WorkingCube->XformedVertexList = malloc(NUM_CUBE_VERTS*sizeof(Point3))) == NULL) { - printf(“Couldn't get memory\n”); exit(1); } + printf("Couldn't get memory\n"); exit(1); } if ((WorkingCube->ProjectedVertexList = malloc(NUM_CUBE_VERTS*sizeof(Point3))) == NULL) { - printf(“Couldn't get memory\n”); exit(1); } + printf("Couldn't get memory\n"); exit(1); } if ((WorkingCube->ScreenVertexList = malloc(NUM_CUBE_VERTS*sizeof(Point))) == NULL) { - printf(“Couldn't get memory\n”); exit(1); } + printf("Couldn't get memory\n"); exit(1); } if ((WorkingCube->FaceList = malloc(NUM_CUBE_FACES*sizeof(Face))) == NULL) { - printf(“Couldn't get memory\n”); exit(1); } + printf("Couldn't get memory\n"); exit(1); } /* Initialize the faces */ for (j=0; jFaceList[j].VertNums = VertNumList[j]; diff --git a/52-07.md b/52-07.md index 07727d5..f2eecc1 100644 --- a/52-07.md +++ b/52-07.md @@ -7,8 +7,8 @@ Listings 52.1 through 52.10 shown earlier represent not merely faster animation in library form, but also a nearly complete, extensible, data-driven animation framework. Whereas much of the earlier animation -code I’ve presented in this book was hardwired to demonstrate certain -concepts, this chapter’s code is intended to serve as the basis for a +code I've presented in this book was hardwired to demonstrate certain +concepts, this chapter's code is intended to serve as the basis for a solid animation package. Objects are stored, in their entirety, in customizable structures; new structures can be devised for new sorts of objects. Drawing, preparing for drawing, and moving are all vectored @@ -26,33 +26,33 @@ animation package?) Note that the X-Sharp library as presented in this chapter (and, indeed, in this book) is not a fully complete 3-D library. Movement is supported -only along the Z axis in this chapter’s version, and then in a -non-general fashion. More interesting movement isn’t supported at this +only along the Z axis in this chapter's version, and then in a +non-general fashion. More interesting movement isn't supported at this point because of one of the two missing features in X-Sharp: hidden-surface removal. (The other missing feature is general 3-D clipping.) Without hidden surface removal, nothing can safely overlap. It would actually be easy enough to perform hidden-surface removal by keeping the cubes in different Z bands and drawing them back to front, but this gets into sorting and list issues, and is not a complete -solution—and I’ve crammed as much as will fit into one chapter’s code, +solution—and I've crammed as much as will fit into one chapter's code, anyway. -I’m working toward a goal in this last section of the book, and there +I'm working toward a goal in this last section of the book, and there are many lessons to be learned and stories to be told along the way. So -as X-Sharp grows, you’ll find its evolving implementations in the -chapter subdirectories on the listings diskette. This chapter’s +as X-Sharp grows, you'll find its evolving implementations in the +chapter subdirectories on the listings diskette. This chapter's subdirectory, for example, contains the self-extracting archive file XSHARP14.EXE, (to extract its contents you simply run it as though it -were a program) and the code in that archive is the code I’m speaking of +were a program) and the code in that archive is the code I'm speaking of specifically in this chapter, with all the limitations mentioned above. -Chapter 53’s subdirectory, however, contains the file XSHARP15.EXE, +Chapter 53's subdirectory, however, contains the file XSHARP15.EXE, which is the next step in the evolution of X-Sharp, and it is the -version that I’ll be specifically talking about in that chapter. Later +version that I'll be specifically talking about in that chapter. Later chapters will have their own implementations in their respective chapter subdirectories, in files of the form XSHARPxx.EXE, where xx is an ascending number indicating the version. The final and most recent X-Sharp version will be present in its own subdirectory called XSHARP22. -If you’re intending to use X-Sharp in a real project, use the most +If you're intending to use X-Sharp in a real project, use the most recent version to be sure that you avail yourself of all new features and bug fixes. @@ -60,14 +60,14 @@ and bug fixes. As of the previous chapter, we were at the point where we could rotate, move, and draw a solid cube in real time. Not too shabby...but the code -I’m presenting in this chapter goes a bit further, rotating 12 solid +I'm presenting in this chapter goes a bit further, rotating 12 solid cubes at an update rate of about 15 frames per second (fps) on a 20 MHz -386 with a slow VGA. That’s 12 transformation matrices, 72 polygons, and +386 with a slow VGA. That's 12 transformation matrices, 72 polygons, and 96 vertices being handled in real time; not Star Wars, granted, but a giant step beyond a single cube. Run the program if you get a chance; you may be surprised at just how effective this level of animation is. -I’d like to point out, in case anyone missed it, that this is fully -*general* 3-D. I’m not using any shortcuts or tricks, like prestoring +I'd like to point out, in case anyone missed it, that this is fully +*general* 3-D. I'm not using any shortcuts or tricks, like prestoring coordinates or pregenerating bitmaps; if you were to feed in different rotations or vertices, the animation would change accordingly. diff --git a/52-08.md b/52-08.md index 5765498..db86f25 100644 --- a/52-08.md +++ b/52-08.md @@ -2,7 +2,7 @@ [Previous](52-07.html) [Table of Contents](index.html) [Next](53-01.html) ------------------------ --------------------------------- -------------------- -The keys to the performance increase manifested in this chapter’s code +The keys to the performance increase manifested in this chapter's code are three. The first key is fixed-point arithmetic. In the previous two chapters, we worked with floating-point coordinates and transformation matrices. Those values are now stored as 32-bit fixed-point numbers, in @@ -14,10 +14,10 @@ floating-point emulator operations. Although the speed advantage of fixed-point varies depending on the operation, on the processor, and on whether or not a coprocessor is present, fixed-point multiplication can be as much as 100 times faster than the emulated floating-point -equivalent. (I’d like to take a moment to thank Chris Hecker for his +equivalent. (I'd like to take a moment to thank Chris Hecker for his invaluable input in this area.) -The second performance key is the use of the 386’s native 32-bit +The second performance key is the use of the 386's native 32-bit multiply and divide instructions. C compilers operating in real mode call library routines to perform multiplications and divisions involving 32-bit values, and those library functions are fairly slow, especially @@ -27,7 +27,7 @@ is only for rounding. The third performance key is maintaining and operating on only the relevant portions of transformation matrices and coordinates. The bottom -row of every transformation matrix we’ll use (in this book) is [0 0 0 +row of every transformation matrix we'll use (in this book) is [0 0 0 1], so why bother using or recalculating it when concatenating transforms and transforming points? Likewise for the fourth element of a 3-D vector in homogeneous coordinates, which is always 1. Basically, @@ -42,30 +42,30 @@ optimizations improve the performance of the entire animation—including drawing time and overhead, and not just math—by more than ten times over the code that uses the floating-point emulator. Amazing what one can accomplish with a few dozen lines of assembly and a switch in number -format, isn’t it? Note that no assembly code other than the native 386 +format, isn't it? Note that no assembly code other than the native 386 multiply and divide is used in Listings 52.1 through 52.10, although the -polygon fill code is of course mostly in assembly; we’ve achieved 12 +polygon fill code is of course mostly in assembly; we've achieved 12 cubes animated at 15 fps while doing the 3-D work almost entirely in -Borland C++, and we’re *still* doing sine and cosine via the -floating-point emulator. Happily, we’re still nowhere near the upper +Borland C++, and we're *still* doing sine and cosine via the +floating-point emulator. Happily, we're still nowhere near the upper limit on the animation potential of the PC. #### Drawbacks {#Heading6} -The techniques we’ve used to turbocharge 3-D animation are very -powerful, but there’s a dark side to them as well. Obviously, native 386 -instructions won’t work on 8088 and 286 machines. That’s rectifiable; +The techniques we've used to turbocharge 3-D animation are very +powerful, but there's a dark side to them as well. Obviously, native 386 +instructions won't work on 8088 and 286 machines. That's rectifiable; equivalent multiplication and division routines could be implemented for real mode and performance would still be reasonable. It sure is nice to be able to plug in a 32-bit **IMUL** or **DIV** and be done with it, though. More importantly, 32-bit fixed-point arithmetic has limitations -in range and accuracy. Points outside a 64Kx64Kx64K space can’t be +in range and accuracy. Points outside a 64Kx64Kx64K space can't be handled, imprecision tends to creep in over the course of multiple -matrix concatenations, and it’s quite possible to generate the dreaded +matrix concatenations, and it's quite possible to generate the dreaded divide by 0 interrupt if Z coordinates with absolute values less than one are used. -I don’t have space to discuss these issues in detail, but here are some +I don't have space to discuss these issues in detail, but here are some brief thoughts: The working 64Kx64Kx64K fixed-point space can be paged into a larger virtual space. Imprecision of a pixel or two rarely matters in terms of display quality, and deterioration of concatenated @@ -73,7 +73,7 @@ rotations can be corrected by restoring orthogonality, for example by periodically calculating one row of the matrix as the cross-product of the other two (forcing it to be perpendicular to both). Alternatively, transformations can be calculated from scratch each time an object or -the viewer moves, so there’s no chance for cumulative error. 3-D +the viewer moves, so there's no chance for cumulative error. 3-D clipping with a front clip plane of -1 or less can prevent divide overflow. @@ -84,11 +84,11 @@ wildly biased toward transformation, but sine and cosine are certainly still sucking up cycles. Likewise, the overhead in the calls to **FixedMul()** and **FixedDiv()** is costly. Much of this is correctable with a little carefully crafted assembly language and a lookup table; -I’ll provide that shortly. +I'll provide that shortly. Regardless, with this chapter we have made the critical jump to a usable level of performance and a serviceable general-purpose framework. From -here on out, it’s the fun stuff. +here on out, it's the fun stuff. ------------------------ --------------------------------- -------------------- [Previous](52-07.html) [Table of Contents](index.html) [Next](53-01.html) diff --git a/53-01.md b/53-01.md index 7c69633..ca6b94b 100644 --- a/53-01.md +++ b/53-01.md @@ -8,22 +8,22 @@ Chapter 53\ ### The Naked Truth About Speed in 3-D Animation {#Heading2} -Years ago, this friend of mine—let’s call him Bert—went to Hawaii with +Years ago, this friend of mine—let's call him Bert—went to Hawaii with three other fellows to celebrate their graduation from high school. This was an unchaperoned trip, and they behaved pretty much as responsibly as -you’d expect four teenagers to behave, which is to say, not; there’s a -story about a rental car that, to this day, Bert can’t bring himself to +you'd expect four teenagers to behave, which is to say, not; there's a +story about a rental car that, to this day, Bert can't bring himself to tell. They had a good time, though, save for one thing: no girls. -By and by, they met a group of girls by the pool, but the boys couldn’t +By and by, they met a group of girls by the pool, but the boys couldn't get past the hi-howya-doin stage, so they retired to their hotel room to -plot a better approach. This being the early ’70s, and them being +plot a better approach. This being the early '70s, and them being slightly tipsy teenagers with raging hormones and the effective combined IQ of four eggplants, it took them no time at all to come up with a brilliant plan: streaking. The girls had mentioned their room number, so -the boys piled into the elevator, pushed the button for the girls’ +the boys piled into the elevator, pushed the button for the girls' floor, shucked their clothes as fast as they could, and sprinted to the -girls’ door. They knocked on the door and ran on down the hall. As the +girls' door. They knocked on the door and ran on down the hall. As the girls opened their door, Bert and his crew raced past, toward the elevator, laughing hysterically. @@ -38,11 +38,11 @@ laughter, tears streaming down his face. The universe had blessed him with one of those exceedingly rare moments of perfect timing and execution. -The universe wasn’t done with Bert quite yet, though. He was still +The universe wasn't done with Bert quite yet, though. He was still contorted with laughter—and still quite thoroughly undressed—when the elevator doors opened again. On the lobby. -And with that, we come to this chapter’s topics: raw speed and hidden +And with that, we come to this chapter's topics: raw speed and hidden surfaces. ### Raw Speed, Part 1: Assembly Language {#Heading3} @@ -56,27 +56,27 @@ the user wants fast response. And, in the course of pursuing performance, there are times when a little assembly language goes a long way. -We’re now four chapters into development of the X-Sharp 3-D animation +We're now four chapters into development of the X-Sharp 3-D animation package. In realtime animation, performance is *sine qua non* (Latin for -“Make it fast or find another line of work”), so some judiciously +"Make it fast or find another line of work"), so some judiciously applied assembly language is in order. In the previous chapter, we got up to a serviceable performance level by switching to fixed-point math, 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 +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 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 +Listing 53.1 is the module FIXED.ASM from this chapter's iteration of 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 -math is now down to the point where it’s a fairly minor component of -execution time, representing less than ten percent of the total. It’s +math is now down to the point where it's a fairly minor component of +execution time, representing less than ten percent of the total. It's time to turn our optimization sights elsewhere. ------------------------ --------------------------------- -------------------- diff --git a/53-02.md b/53-02.md index 9f58b9b..27b15eb 100644 --- a/53-02.md +++ b/53-02.md @@ -58,7 +58,7 @@ mov eax,[bp+Dividend] and eax,eax ;positive dividend? jns FDP1 ;yes - inc cx ;mark it’s a negative dividend + inc cx ;mark it's a negative dividend neg eax ;make the dividend positive FDP1: sub edx,edx ;make it a 64-bit dividend, then shift ; left 16 bits so that result will be @@ -70,7 +70,7 @@ mov ebx,dword ptr [bp+Divisor] and ebx,ebx ;positive divisor? jns FDP2 ;yes - dec cx ;mark it’s a negative divisor + dec cx ;mark it's a negative divisor neg ebx ;make divisor positive FDP2: div ebx ;divide shr ebx,1 ;divisor/2, minus 1 if the divisor is @@ -118,7 +118,7 @@ mov bp,sp ;set up local stack frame mov bx,[bp].Angle - and bx,bx ;make sure angle’s between 0 and 2*pi + and bx,bx ;make sure angle's between 0 and 2*pi jns CheckInRange MakePos:;less than 0, so make it positive add bx,360*10 @@ -191,7 +191,7 @@ ; Matrix multiplies Xform by SourceVec, and stores the result in ; DestVec. Multiplies a 4x4 matrix times a 4x1 matrix; the result ; is a 4x1 matrix. Cheats by assuming the W coord is 1 and the - ; bottom row of the matrix is 0 0 0 1, and doesn’t bother to set + ; bottom row of the matrix is 0 0 0 1, and doesn't bother to set ; the W coordinate of the destination. ; C near-callable as: ; void XformVec(Xform WorkingXform, Fixedpoint *SourceVec, @@ -270,7 +270,7 @@ ; Matrix multiplies SourceXform1 by SourceXform2 and stores the ; result in DestXform. Multiplies a 4x4 matrix times a 4x4 matrix; ; the result is a 4x4 matrix. Cheats by assuming the bottom row of - ; each matrix is 0 0 0 1, and doesn’t bother to set the bottom row + ; each matrix is 0 0 0 1, and doesn't bother to set the bottom row ; of the destination. ; C near-callable as: ; void ConcatXforms(Xform SourceXform1, Xform SourceXform2, diff --git a/53-03.md b/53-03.md index eddd161..bf3b065 100644 --- a/53-03.md +++ b/53-03.md @@ -4,16 +4,16 @@ ### Raw Speed, Part II: Look it Up {#Heading4} -It’s a funny thing about Turbo Profiler: Time spent in the Borland C++ -80x87 emulator doesn’t show up directly anywhere that I can see in the +It's a funny thing about Turbo Profiler: Time spent in the Borland C++ +80x87 emulator doesn't show up directly anywhere that I can see in the timing results. The only way to detect it is by way of the line that 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 +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 +fooled me for a while, leading me to think sine and cosine weren't major 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 +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 time by themselves. @@ -22,10 +22,10 @@ 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 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 +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 @@ -40,45 +40,45 @@ Additional optimizations in the area of math could still be made (using fixed-point math, the sine and cosine lookup, and selective assembly optimizations have done a pretty good job already. The bulk of the time taken by X-Sharp is now spent drawing polygons, drawing rectangles (to -erase objects), and waiting for the page to flip. In other words, we’ve +erase objects), and waiting for the page to flip. In other words, we've slain the dragon of 3-D math, or at least wounded it grievously; now -we’re back to the dragon of polygon filling. We’ll address faster +we're back to the dragon of polygon filling. We'll address faster polygon filling soon, but for the moment, we have more than enough horsepower to have some fun with. First, though, we need one more feature: hidden surfaces. #### Hidden Surfaces {#Heading5} -So far, we’ve made a number of simplifying assumptions in order to get +So far, we've made a number of simplifying assumptions in order to get the animation to look good; for example, all objects must currently be -convex polyhedrons. What’s more, right now, objects can never pass -behind or in front of each other. What that means is that it’s time to +convex polyhedrons. What's more, right now, objects can never pass +behind or in front of each other. What that means is that it's time to have a look at hidden surfaces. There are a passel of ways to do hidden surfaces. Way off at one end (the slow end) of the spectrum is Z-buffering, whereby each pixel of -each polygon is checked as it’s drawn to see whether it’s the frontmost +each polygon is checked as it's drawn to see whether it's the frontmost version of the pixel at those coordinates. At the other end is the technique of simply drawing the objects in back-to-front order, so that nearer objects are drawn on top of farther objects. The latter approach, -depth sorting, is the one we’ll take today. (Actually, true depth +depth sorting, is the one we'll take today. (Actually, true depth sorting involves detecting and resolving possible ambiguities when -objects overlap in Z; in this chapter, we’ll simply sort the objects on +objects overlap in Z; in this chapter, we'll simply sort the objects on Z and leave it at that.) This limited version of depth sorting is fast but less than perfect. For -one thing, it doesn’t address the issue of nonconvex objects, so we’ll -have to stick with convex polyhedrons. For another, there’s the question +one thing, it doesn't address the issue of nonconvex objects, so we'll +have to stick with convex polyhedrons. For another, there's the question of what part of each object to use as the sorting key; the nearest point, the center, and the farthest point are all possibilities—and, -whichever point is used, depth sorting doesn’t handle some overlap cases +whichever point is used, depth sorting doesn't handle some overlap cases properly. Figure 53.1 illustrates one case in which back-to-front -sorting doesn’t work, regardless of what point is used as the sorting +sorting doesn't work, regardless of what point is used as the sorting key. For photo-realistic rendering, these are serious problems. For fast -PC-based animation, however, they’re manageable. Choose objects that -aren’t too elongated; arrange their paths of travel so they don’t +PC-based animation, however, they're manageable. Choose objects that +aren't too elongated; arrange their paths of travel so they don't intersect in problematic ways; and, if they do overlap incorrectly, trust that the glitch will be lost in the speed of the animation and the complexity of the screen. @@ -88,17 +88,17 @@ for depth sorting. Objects are now stored in a linked list. The initial, 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 -z-sorted order. Each time the objects are moved, before they’re drawn at +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 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 +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 -point for each object is now transformed along with the vertices. That’s +point for each object is now transformed along with the vertices. That's really all there is to depth sorting—and now we can have objects that overlap in X and Y. ![](images/53-01.jpg)\ - **Figure 53.1**  *Why back-to-front sorting doesn’t always work + **Figure 53.1**  *Why back-to-front sorting doesn't always work properly.* ------------------------ --------------------------------- -------------------- diff --git a/53-04.md b/53-04.md index bf5ef55..e796170 100644 --- a/53-04.md +++ b/53-04.md @@ -6,7 +6,7 @@ /* Object list-related functions. */ #include - #include “polygon.h” + #include "polygon.h" /* Set up the empty object list, with sentinels at both ends to terminate searches */ @@ -90,17 +90,17 @@ FIXED.ASM contains the equate **ROUNDING-ON**. When this equate is 1, the results of multiplications and divisions are rounded to the nearest -fixed-point values; when it’s 0, the results are truncated. The +fixed-point values; when it's 0, the results are truncated. The difference between the results produced by the two approaches is, at -most, 2^-16^; you wouldn’t think that would make much difference, now, +most, 2^-16^; you wouldn't think that would make much difference, now, would you? But it does. When the animation is run with rounding disabled, the cubes start to distort visibly after a few minutes, and -after a few minutes more they look like they’ve been run over. In -contrast, I’ve never seen any significant distortion with rounding on, +after a few minutes more they look like they've been run over. In +contrast, I've never seen any significant distortion with rounding on, even after a half-hour or so. I think the difference with rounding is -not that it’s so much more accurate, but rather that the errors are +not that it's so much more accurate, but rather that the errors are evenly distributed; with truncation, the errors are biased, and biased -errors become very visible when they’re applied to right-angle objects. +errors become very visible when they're applied to right-angle objects. Even with rounding, though, the errors will eventually creep in, and reorthogonalization will become necessary at some point. @@ -115,17 +115,17 @@ rounding for division. #### Having a Ball {#Heading7} -So far in our exploration of 3-D animation, we’ve had nothing to look at -but triangles and cubes. It’s time for something a little more visually +So far in our exploration of 3-D animation, we've had nothing to look at +but triangles and cubes. It's time for something a little more visually appealing, so the demonstration program now features a 72-sided ball. -What’s particularly interesting about this ball is that it’s created by +What's particularly interesting about this ball is that it's created by the GENBALL.C program in the BALL subdirectory of X-Sharp, and both the size of the ball and the number of bands of faces are programmable. GENBALL.C spits out to a file all the arrays of vertices and faces needed to create the ball, ready for inclusion in INITBALL.C. True, if you change the number of bands, you must change the Colors array in -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 +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** @@ -138,7 +138,7 @@ GENBALL.C is the prototype for object definition, and INITBALL.C is the prototype for general-purpose object instantiation. Certainly, it would be nice to someday have an interactive 3-D object editing tool and resource management setup. We have our hands full with the drawing end -of things at the moment, though, and for now it’s enough to be able to +of things at the moment, though, and for now it's enough to be able to create objects in a semiautomated way. ------------------------ --------------------------------- -------------------- diff --git a/54-01.md b/54-01.md index d4276dc..d741115 100644 --- a/54-01.md +++ b/54-01.md @@ -10,34 +10,34 @@ Chapter 54\ At the end of the previous chapter, X-Sharp had just acquired basic hidden-surface capability, and performance had been vastly improved -through the use of fixed-point arithmetic. In this chapter, we’re going +through the use of fixed-point arithmetic. In this chapter, we're going to add quite a bit more: support for 8088 and 80286 PCs, a general color -model, and shading. That’s an awful lot to cover in one chapter -(actually, it’ll spill over into the next chapter), so let’s get to it! +model, and shading. That's an awful lot to cover in one chapter +(actually, it'll spill over into the next chapter), so let's get to it! ### Support for Older Processors {#Heading3} To date, X-Sharp has run on only the 386 and 486, because it uses 32-bit -multiply and divide instructions that sub-386 processors don’t support. -I chose 32-bit instructions for two reasons: They’re much faster for +multiply and divide instructions that sub-386 processors don't support. +I chose 32-bit instructions for two reasons: They're much faster for 16.16 fixed-point arithmetic than any approach that works on the 8088 -and 286; and they’re much easier to implement than any other approach. +and 286; and they're much easier to implement than any other approach. In short, I was after maximum performance, and I was perhaps just a little lazy. 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 +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()**. 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 +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 +8088 instructions makes X-Sharp's fixed-point calculations about 2.5 times slower overall. Since a PC is perhaps 40 times slower than a -486/33, we’re talking about a hundred-times speed difference between the +486/33, we're talking about a hundred-times speed difference between the low end and mainstream. A 486/33 can animate a 72-sided ball, complete with shading (as discussed later), at 60 frames per second (fps), with plenty of cycles to spare; an 8-MHz AT can animate the same ball at @@ -46,7 +46,7 @@ tailored to the available CPU horsepower. The implementation of a 32-bit multiply using 8088 instructions is a 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 +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 @@ -57,15 +57,15 @@ publisher.) In X-Sharp, division is used only to divide either X or Y by Z in the process of projecting from view space to screen space, so the cost of using a 32x16 divide is merely some inaccuracy in calculating screen coordinates, especially when objects get very close to the Z = 0 -plane. This error is not cumulative (that is, it doesn’t carry over to -later frames), and in my experience doesn’t cause noticeable image +plane. This error is not cumulative (that is, it doesn't carry over to +later frames), and in my experience doesn't cause noticeable image degradation; therefore, given the already slow performance of the 8088 -and 286, I’ve opted for performance over precision. +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 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 +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, and no more, as quickly as possible. diff --git a/54-03.md b/54-03.md index 12b91df..d43911a 100644 --- a/54-03.md +++ b/54-03.md @@ -8,14 +8,14 @@ So far, the polygons out of which our animated objects have been built have had colors of fixed intensities. For example, a face of a cube might be blue, or green, or white, but whatever color it is, that color never brightens or dims. Fixed colors are easy to implement, but they -don’t make for very realistic animation. In the real world, the +don't make for very realistic animation. In the real world, the intensity of the color of a surface varies depending on how brightly it is illuminated. The ability to simulate the illumination of a surface, -or shading, is the next feature we’ll add to X-Sharp. +or shading, is the next feature we'll add to X-Sharp. The overall shading of an object is the sum of several types of shading components. *Ambient shading* is illumination by what you might think of -as background light, light that’s coming from all directions; all +as background light, light that's coming from all directions; all surfaces are equally illuminated by ambient light, regardless of their orientation. *Directed lighting*, producing diffuse shading, is illumination from one or more specific light sources. Directed light has @@ -24,7 +24,7 @@ determines how brightly it lights that surface. *Specular reflection* is the tendency of a surface to reflect light in a mirrorlike fashion. There are other sorts of shading components, including transparency and atmospheric effects, but the ambient and diffuse-shading components are -all we’re going to deal with in X-Sharp. +all we're going to deal with in X-Sharp. #### Ambient Shading {#Heading5 align="center"} @@ -43,40 +43,40 @@ Given an ambient-light red intensity of IA~red~ and a surface red reflectance R~red~, the displayed red ambient shading for that surface, as a fraction of the maximum red intensity, is simply min(IA~red~x R~red~, 1). The green and blue color components are handled similarly. -That’s really all there is to ambient shading, although of course we +That's really all there is to ambient shading, although of course we must design some way to map displayed color components into the -available palette of colors; I’ll do that in the next chapter. Ambient -shading isn’t the whole shading picture, though. In fact, scenes tend to +available palette of colors; I'll do that in the next chapter. Ambient +shading isn't the whole shading picture, though. In fact, scenes tend to look pretty bland without diffuse shading. #### Diffuse Shading {#Heading6 align="center"} Diffuse shading is more complicated than ambient shading, because the effective intensity of directed light falling on a surface depends on -the angle at which it strikes the surface. According to Lambert’s law, +the angle at which it strikes the surface. According to Lambert's law, the light energy from a directed light source striking a surface is proportional to the cosine of the angle at which it strikes the surface, with the angle measured relative to a vector perpendicular to the polygon (a polygon normal), as shown in Figure 54.1. If the red intensity of directed light is ID~red~, the red reflectance of the surface is R~red~, and the angle between the incoming directed light and -the surface’s normal is theta, then the displayed red diffuse shading +the surface's normal is theta, then the displayed red diffuse shading for that surface, as a fraction of the largest possible red intensity, is min (ID~red~xR~red~xcos(θ), 1). -That’s easy enough to calculate—but seemingly slow. Determining the -cosine of an angle can be sped up with a table lookup, but there’s also -the task of figuring out the angle, and, all in all, it doesn’t seem +That's easy enough to calculate—but seemingly slow. Determining the +cosine of an angle can be sped up with a table lookup, but there's also +the task of figuring out the angle, and, all in all, it doesn't seem that diffuse shading is going to be speedy enough for our purposes. Consider this, however: According to the properties of the dot product -(denoted by the operator “•”, as shown in Figure 54.2), cos(q)=(v•w)/ +(denoted by the operator "•", as shown in Figure 54.2), cos(q)=(v•w)/ |v| x |w| ), where v and w are vectors, q is the angle between v and w, and |v| is the length of v. Suppose, now, that v and w are unit vectors; that is, vectors exactly one unit long. Then the above equation reduces to cos(q)=v•w. In other words, we can calculate the cosine between N, the unit-normal vector (one-unit-long perpendicular vector) of a polygon, and L', the reverse of a unit vector describing the direction -of a light source, with just three multiplies and two adds. (I’ll +of a light source, with just three multiplies and two adds. (I'll explain why the light-direction vector must be reversed later.) Once we have that, we can easily calculate the red diffuse shading from a directed light source as min(ID~red~xR~red~x(L'• N), 1) and likewise for diff --git a/54-05.md b/54-05.md index c6f0fd9..eec7ba6 100644 --- a/54-05.md +++ b/54-05.md @@ -5,21 +5,21 @@ #### Shading: Implementation Details {#Heading7} In order to calculate the cosine of the angle between an incoming light -source and a polygon’s unit normal, we must first have the polygon’s +source and a polygon's unit normal, we must first have the polygon's unit normal. This could be calculated by generating a cross-product on -two polygon edges to generate a normal, then calculating the normal’s +two polygon edges to generate a normal, then calculating the normal's length and scaling to produce a unit normal. Unfortunately, that would -require taking a square root, so it’s not a desirable course of action. -Instead, I’ve made a change to X-Sharp’s polygon format. Now, the first -vertex in a shaded polygon’s vertex list is the end-point of a unit -normal that starts at the second point in the polygon’s vertex list, as -shown in Figure 54.3. The first point isn’t one of the polygon’s +require taking a square root, so it's not a desirable course of action. +Instead, I've made a change to X-Sharp's polygon format. Now, the first +vertex in a shaded polygon's vertex list is the end-point of a unit +normal that starts at the second point in the polygon's vertex list, as +shown in Figure 54.3. The first point isn't one of the polygon's vertices, but is used only to generate a unit normal. The second point, however, is a polygon vertex. Calculating the difference vector between -the first and second points yields the polygon’s unit normal. Adding a -unit-normal endpoint to each polygon isn’t free; each of those +the first and second points yields the polygon's unit normal. Adding a +unit-normal endpoint to each polygon isn't free; each of those end-points has to be transformed, along with the rest of the vertices, -and that takes time. Still, it’s faster than calculating a unit normal +and that takes time. Still, it's faster than calculating a unit normal for each polygon from scratch. ![](images/54-03.jpg)\ @@ -29,27 +29,27 @@ for each polygon from scratch. **Figure 54.4**  *The reversed light source vector.* We also need a unit vector for each directed light source. The directed -light sources I’ve implemented in X-Sharp are spotlights; that is, -they’re considered to be point light sources that are infinitely far +light sources I've implemented in X-Sharp are spotlights; that is, +they're considered to be point light sources that are infinitely far away. This allows the simplifying assumption that all light rays from a spotlight are parallel and of equal intensity throughout the displayed universe, so each spotlight can be represented with a single unit vector and a single intensity. The only trick is that in order to calculate the -desired cos(theta) between the polygon unit normal and a spotlight’s -unit vector, the direction of the spotlight’s unit vector must be +desired cos(theta) between the polygon unit normal and a spotlight's +unit vector, the direction of the spotlight's unit vector must be reversed, as shown in Figure 54.4. This is necessary because the dot product implicitly places vectors with their start points at the same -location when it’s used to calculate the cosine of the angle between two +location when it's used to calculate the cosine of the angle between two vectors. The light vector is incoming to the polygon surface, and the unit normal is outbound, so only by reversing one vector or the other will we get the cosine of the desired angle. -Given the two unit vectors, it’s a piece of cake to calculate +Given the two unit vectors, it's a piece of cake to calculate intensities, as shown in Listing 54.2. The sample program DEMO1, in the X-Sharp archive on the listings disk (built by running K1.BAT), puts the shading code to work displaying a rotating ball with ambient lighting and three spot lighting sources that the user can turn on and off. What -you’ll see when you run DEMO1 is that the shading is very good—face +you'll see when you run DEMO1 is that the shading is very good—face colors change very smoothly indeed—so long as only green lighting sources are on. However, if you combine spotlight two, which is blue, with any other light source, polygon colors will start to shift abruptly @@ -60,7 +60,7 @@ this case) for each color component when two or more primary colors are mixed. While this situation can be improved, it is fundamentally a result of the restricted capabilities of the 256-color palette, and there is only so much that can be done without a larger color set. In -the next chapter, I’ll talk about some ways to improve the quality of +the next chapter, I'll talk about some ways to improve the quality of 256-color shading. ------------------------ --------------------------------- -------------------- diff --git a/55-01.md b/55-01.md index 2e13687..dc79d2d 100644 --- a/55-01.md +++ b/55-01.md @@ -6,27 +6,27 @@ Chapter 55\ Color Modeling in 256-Color Mode {#Heading1} --------------------------------- -### Pondering X-Sharp’s Color Model in an RGB State of Mind {#Heading2} +### Pondering X-Sharp's Color Model in an RGB State of Mind {#Heading2} Once she turned six, my daughter wanted some fairly sophisticated books read to her*. Wind in the Willows.* *Little House on the Prairie.* Pretty heady stuff for one so young, and sometimes I wondered how much of it she really understood. As an experiment, during one reading I stopped whenever I came to a word I thought she might not know, and -asked her what it meant. One such word was “mulling.” +asked her what it meant. One such word was "mulling." -“Do you know what ‘mulling’ means?” I asked. +"Do you know what ‘mulling' means?" I asked. -She thought about it for a while, then said, “Pondering.” +She thought about it for a while, then said, "Pondering." -“Very good!” I said, more than a little surprised. +"Very good!" I said, more than a little surprised. -She smiled and said, “But, Dad, how do you know that I know what -‘pondering’ means?” +She smiled and said, "But, Dad, how do you know that I know what +‘pondering' means?" -“Okay,” I said, “What does ‘pondering’ mean?” +"Okay," I said, "What does ‘pondering' mean?" -“Mulling,” she said. +"Mulling," she said. What does this anecdote tell us about the universe in which we live? Well, it certainly indicates that this universe is inhabited by at least @@ -34,12 +34,12 @@ one comedian and one good straight man. Beyond that, though, it can be construed as a parable about the difficulty of defining things properly; for example, consider the complications inherent in the definition of color on a 256-color display adapter such as the VGA. Coincidentally, -VGA color modeling just happens to be this chapter’s topic, and the +VGA color modeling just happens to be this chapter's topic, and the place to start is with color modeling in general. #### A Color Model {#Heading3} -We’ve been developing X-Sharp for several chapters now. In the previous +We've been developing X-Sharp for several chapters now. In the previous chapter, we added illumination sources and shading; that addition makes it necessary for us to have a general-purpose color model, so that we can display the gradations of color intensity necessary to render @@ -50,14 +50,14 @@ shallower angle, we need to be able to display progressively dimmer shades of green. The first thing to do is to select a color model in which to perform our -shading calculations. I’ll use the dot product-based stuff I discussed -in the previous chapter. The approach we’ll take is to select an ideal +shading calculations. I'll use the dot product-based stuff I discussed +in the previous chapter. The approach we'll take is to select an ideal representation of the full color space and do our calculations there, as if we really could display every possible color; only as a final step will we map each desired color into the limited 256-color set of the VGA, or the color range of whatever adapter we happen to be working with. There are a number of color models that we might choose to work -with, but I’m going to go with the one that’s both most familiar and, in +with, but I'm going to go with the one that's both most familiar and, in my opinion, simplest: RGB (red, green, blue). In the RGB model, a given color is modeled as the mix of specific @@ -73,7 +73,7 @@ inside or on the cube. RGB is good for modeling colors generated by light sources, because red, green, and blue are the additive primaries; that is, all other colors -can be generated by mixing red, green, and blue light sources. They’re +can be generated by mixing red, green, and blue light sources. They're also the primaries for color computer displays, and the RGB model maps beautifully onto the display capabilities of 15- and 24-bpp display adapters, which tend to represent pixels as RGB combinations in display @@ -98,32 +98,32 @@ value 0, and full intensity is represented by the value 255. This gives us 256 levels of each primary color component, and a total of 16,772,216 possible colors. -Holy cow! Isn’t 16,000,000-plus colors a bit of overkill? +Holy cow! Isn't 16,000,000-plus colors a bit of overkill? -Actually, no, it isn’t. At the eighth Annual Computer Graphics Show in +Actually, no, it isn't. At the eighth Annual Computer Graphics Show in New York, Sheldon Linker, of Linker Systems, related an interesting tale about color perception research at the Jet Propulsion Lab back in the -’70s. The JPL color research folks had the capability to print more than +'70s. The JPL color research folks had the capability to print more than 50,000,000 distinct and very precise colors on paper. As a test, they tried printing out words in various colors, with each word printed on a -background that differed by only one color index from the word’s color. +background that differed by only one color index from the word's color. No one expected the human eye to be able to differentiate between two colors, out of 50,000,000-plus, that were so similar. It turned out, though, that everyone could read the words with no trouble at all; the human eye is surprisingly sensitive to color gradations, and also happens to be wonderful at detecting edges. -When the JPL team went to test the eye’s sensitivity to color on the +When the JPL team went to test the eye's sensitivity to color on the screen, they found that only about 16,000,000 colors could be distinguished, because the color-sensing mechanism of the human eye is more compatible with reflective sources such as paper and ink than with emissive sources such as CRTs. Still, the human eye can distinguish -about 16,000,000 colors on the screen. That’s not so hard to believe, if +about 16,000,000 colors on the screen. That's not so hard to believe, if you think about it; the eye senses each primary color separately, so -we’re really only talking about detecting 256 levels of intensity per -primary here. It’s the brain that does the amazing part; the +we're really only talking about detecting 256 levels of intensity per +primary here. It's the brain that does the amazing part; the 16,000,000-plus color capability actually comes not from extraordinary -sensitivity in the eye, but rather from the brain’s ability to +sensitivity in the eye, but rather from the brain's ability to distinguish between all the mixes of 256 levels of each of three primaries. diff --git a/55-02.md b/55-02.md index 623a4eb..6e19964 100644 --- a/55-02.md +++ b/55-02.md @@ -2,36 +2,36 @@ [Previous](55-01.html) [Table of Contents](index.html) [Next](55-03.html) ------------------------ --------------------------------- -------------------- -So it’s perfectly reasonable to maintain 24 bits of color resolution, +So it's perfectly reasonable to maintain 24 bits of color resolution, and X-Sharp represents colors internally as ideal, device-independent 24-bit RGB triplets. All shading calculations are performed on these -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 +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 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 +mapping is adapter-dependent. On a 24-bpp device, it's pretty obvious how the internal RGB color format maps to displayed pixel colors: directly. On VGAs with 15-bpp Sierra Hicolor DACS, the mapping is equally simple, with the five upper bits of each color component mapping straight to display pixels. But how on earth do we map those 16,000,000-plus RGB colors into the 256-color space of a standard VGA? -This is the “color definition” problem I mentioned at the start of this +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 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 -colors is a pretty puny selection. It’s easy to set up the palette to +colors is a pretty puny selection. It's easy to set up the palette to give yourself a good selection of just blue intensities, or of just -greens; but for general color modeling there’s simply not enough palette +greens; but for general color modeling there's simply not enough palette to go around. 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 +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()** that set up and perform the color mapping for DEMO1. diff --git a/55-03.md b/55-03.md index b6fe59f..b7b45c8 100644 --- a/55-03.md +++ b/55-03.md @@ -37,7 +37,7 @@ The downside is that this excellent quality is available for only three colors: red, green, and blue. What about all the other colors that are mixes of the primaries, like cyan or yellow, to say nothing of gray? In the DEMO1 color model, any RGB color that is not a pure primary is -mapped into a 2-2-2 RGB space that the remaining quarter of the VGA’s +mapped into a 2-2-2 RGB space that the remaining quarter of the VGA's palette is set up to display; that is, there are exactly two bits of precision for each color component, or 64 general RGB colors in all. This is genuinely lousy color resolution, being only 1/64th of the @@ -46,10 +46,10 @@ staggering 262,144 colors from the 24-bit RGB cube map to *each* color in the 2-2-2 VGA palette. The results are not impressive; the colors of mixed-primary surfaces jump abruptly, badly damaging the illusion of real illumination. To see how poor a 2-2-2 RGB selection can look, run -DEMO1, and press the ‘2’ key to turn on spotlight 2, the blue spotlight. +DEMO1, and press the ‘2' key to turn on spotlight 2, the blue spotlight. Because the ambient lighting is green, turning on the blue spotlight causes mixed-primary colors to be displayed—and the result looks -terrible, because there just isn’t enough color resolution. +terrible, because there just isn't enough color resolution. Unfortunately, 2-2-2 RGB is close to the best general color resolution the VGA can display; 3-3-2 is as good as it gets. @@ -62,55 +62,55 @@ those objects, and reserving the rest of the palette for the fixed colors of the other, nonshaded objects. Jim Kent, author of Autodesk Animator, suggests dynamically adjusting the palette to the needs of each frame, for example by allocating the colors for each frame on a -first-come, first-served basis. That wouldn’t be trivial to do in real +first-come, first-served basis. That wouldn't be trivial to do in real time, but it would make for extremely efficient use of the palette. Another widely used solution is to set up a 2-2-2, 3-3-2, or 2.6-2.6-2.6 (6 levels per primary) palette, and dither colors. Dithering is an excellent solution, but outside the scope of this book. Take a look at -Chapter 13 of Foley and Van Dam (cited in “Further Readings”) for an +Chapter 13 of Foley and Van Dam (cited in "Further Readings") for an introduction to color perception and approximation. -The sad truth is that the VGA’s 256-color palette is an inadequate +The sad truth is that the VGA's 256-color palette is an inadequate resource for general RGB shading. The good news is that clever 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. +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 -you’ve set up. It’s that simple, and the results can be striking indeed. +you've set up. It's that simple, and the results can be striking indeed. #### A Bonus from the BitMan {#Heading4} Finally, a note on fast VGA text, which came in from a correspondent who asked to be referred to simply as the BitMan. The BitMan passed along a -nifty application of the VGA’s under-appreciated write mode 3 that is, +nifty application of the VGA's under-appreciated write mode 3 that is, under the proper circumstances, the fastest possible way to draw text in any 16-color VGA mode. -The task at hand is illustrated by Figure 55.2. We want to draw what’s +The task at hand is illustrated by Figure 55.2. We want to draw what's known as solid text, in which the effect is the same as if the cell around each character was drawn in the background color, and then each character was drawn on top of the background box. (This is in contrast to transparent text, where each character is drawn in the foreground color without disturbing the background.) Assume that each character fits in an eight-wide cell (as is the case with the standard VGA fonts), -and that we’re drawing text at byte-aligned locations in display memory. +and that we're drawing text at byte-aligned locations in display memory. Solid text is useful for drawing menus, text areas, and the like; basically, it can be used whenever you want to display text on a solid-color background. The obvious way to implement solid text is to fill the rectangle representing the background box, then draw transparent text on top of the background box. However, there are two -problems with doing solid text this way. First, there’s some flicker, -because for a little while the box is there but the text hasn’t yet +problems with doing solid text this way. First, there's some flicker, +because for a little while the box is there but the text hasn't yet arrived. More important is that the background-followed-by-foreground approach accesses display memory three times for each byte of font data: once to draw the background box, once to read display memory to load the latches, and once to actually draw the font pattern. Display memory is -incredibly slow, so we’d like to reduce the number of accesses as much -as possible. With the BitMan’s approach, we can reduce the number of +incredibly slow, so we'd like to reduce the number of accesses as much +as possible. With the BitMan's approach, we can reduce the number of accesses to just one per font byte, and eliminate flicker, too. ![](images/55-02.jpg)\ @@ -118,7 +118,7 @@ accesses to just one per font byte, and eliminate flicker, too. The keys to fast solid text are the latches and write mode 3. The latches, as you may recall from earlier discussions in this book, are -four internal VGA registers that hold the last bytes read from the VGA’s +four internal VGA registers that hold the last bytes read from the VGA's four planes; every read from VGA memory loads the latches with the values stored at that display memory address across the four planes. Whenever a write is performed to VGA memory, the latches can provide diff --git a/55-04.md b/55-04.md index 9e3b101..d24ba3b 100644 --- a/55-04.md +++ b/55-04.md @@ -5,8 +5,8 @@ This is where it gets a little complicated. In write mode 3 (which incidentally is not available on the EGA), each byte value that the CPU writes to the VGA does not get written to display memory. Instead, it -turns into the bit mask. (Actually, it’s ANDed with the Bit Mask -register, and the result becomes the bit mask, but we’ll leave the Bit +turns into the bit mask. (Actually, it's ANDed with the Bit Mask +register, and the result becomes the bit mask, but we'll leave the Bit Mask register set to 0xFF, so the CPU value will become the bit mask.) The bit mask selects, on a bit-by-bit basis, between the data in the latches for each plane (the previously loaded background color, in this @@ -26,24 +26,24 @@ byte to punch holes in the background color provided by the latches, holes through which the foreground color from the Set/Reset register can flow. The result is that a single write draws exactly the combination of foreground and background pixels described by each font byte written by -the CPU. It may help to look at Listing 55.4, which shows The BitMan’s +the CPU. It may help to look at Listing 55.4, which shows The BitMan's technique in action. And yes, this technique is absolutely worth the -trouble; it’s about three times faster than the fill-then-draw approach +trouble; it's about three times faster than the fill-then-draw approach described above, and about twice as fast as transparent text. So far as I know, there is no faster way to draw text on a VGA. -It’s important to note that the BitMan’s technique only works on full -bytes of display memory. There’s no way to clip to finer precision; the +It's important to note that the BitMan's technique only works on full +bytes of display memory. There's no way to clip to finer precision; the background color will inevitably flood all of the eight destination -pixels that aren’t selected as foreground pixels. This makes The -BitMan’s technique most suitable for monospaced fonts with characters +pixels that aren't selected as foreground pixels. This makes The +BitMan's technique most suitable for monospaced fonts with characters that are multiples of eight pixels in width, and for drawing to byte-aligned addresses; the technique can be used in other situations, but is considerably more difficult to apply. **LISTING 55.4 L55-4.ASM** - ; Demonstrates drawing solid text on the VGA, using the BitMan’s write mode + ; Demonstrates drawing solid text on the VGA, using the BitMan's write mode ; 3-based, one-pass technique. CHAR_HEIGHT equ 8 ;# of scan lines per character (must be <256) @@ -65,9 +65,9 @@ but is considerably more difficult to apply. LineWidthBytes dw ? ;offset from one scan line to the next FontPtr dd ? ;pointer to font with which to draw SampleString label byte - db ‘ABCDEFGHIJKLMNOPQRSTUVWXYZ’ - db ‘abcdefghijklmnopqrstuvwxyz’ - db ‘0123456789!@#$%^&*(),<.>/?;:’,0 + db ‘ABCDEFGHIJKLMNOPQRSTUVWXYZ' + db ‘abcdefghijklmnopqrstuvwxyz' + db ‘0123456789!@#$%^&*(),<.>/?;:',0 .code start: @@ -130,7 +130,7 @@ but is considerably more difficult to apply. ; CharHeight must be set to the height of each character ; FontPtr must be set to the font with which to draw ; LineWidthBytes must be set to the scan line width in bytes - ; Don’t count on any registers other than DS, SS, and SP being preserved. + ; Don't count on any registers other than DS, SS, and SP being preserved. ; The X coordinate is truncated to a multiple of 8. Characters are ; assumed to be 8 pixels wide. align 2 @@ -144,14 +144,14 @@ but is considerably more difficult to apply. mul bx ; start offset of initial scan line add di,ax ;start offset of initial byte mov ax,SCREEN_SEGMENT - mov es,ax ;ES:DI = offset of initial character’s + mov es,ax ;ES:DI = offset of initial character's ; first scan line - ;set up the VGA’s hardware so that we can + ;set up the VGA's hardware so that we can ; fill the latches with the background color mov dx,GC_INDEX mov ax,(0ffh SHL 8) + BIT_MASK - out dx,ax ;set Bit Mask register to 0xFF (that’s the - ; default, but I’m doing this just to make sure + out dx,ax ;set Bit Mask register to 0xFF (that's the + ; default, but I'm doing this just to make sure ; you understand that Bit Mask register and ; CPU data are ANDed in write mode 3) mov ax,(003h SHL 8) + G_MODE @@ -165,17 +165,17 @@ but is considerably more difficult to apply. mov cl,es:[0ffffh] ;read the background color back into the ; latches; the latches are now filled with ; the background color. The value in CL - ; doesn’t matter, we just needed a target + ; doesn't matter, we just needed a target ; for the read, so we could load the latches mov ah,ch ;foreground color out dx,ax ;set the Set/Reset (drawing) color to the ; foreground color - ;we’re ready to draw! + ;we're ready to draw! DrawTextLoop: lodsb ;next character to draw and al,al ;end of string? jz DrawTextDone ;yes - push ds ;remember string’s segment + push ds ;remember string's segment push si ;remember offset of next character in string push di ;remember drawing offset ;load these variables before we wipe out DS @@ -200,7 +200,7 @@ but is considerably more difficult to apply. pop di ;retrieve initial drawing offset inc di ;drawing offset for next char pop si ;retrieve offset of next character in string - pop ds ;retrieve string’s segment + pop ds ;retrieve string's segment jmp DrawTextLoop ;draw next character, if any align2 diff --git a/56-01.md b/56-01.md index 52290e5..acc8843 100644 --- a/56-01.md +++ b/56-01.md @@ -8,18 +8,18 @@ Chapter 56\ ### Using Fast Texture Mapping to Place Pooh on a Polygon {#Heading2} -So, here’s where Winnie the Pooh lives: in a space station orbiting +So, here's where Winnie the Pooh lives: in a space station orbiting Saturn. No, really; I have it straight from my daughter, and an -eight-year-old wouldn’t make up something that important, would she? One -day she wondered aloud, “Where is the Hundred Acre Wood, exactly?” and +eight-year-old wouldn't make up something that important, would she? One +day she wondered aloud, "Where is the Hundred Acre Wood, exactly?" and before I could give one of those boring parental responses about how it was imaginary—but A.A. Milne probably imagined it to be somewhere near London—my daughter announced that the Hundred Acre Wood was in a space station orbiting Saturn, and there you have it. -As it turns out, that’s a very good location for the Hundred Acre Wood, +As it turns out, that's a very good location for the Hundred Acre Wood, leading to many exciting adventures for Pooh and Piglet. Consider the -time they went down to the Jupiter gravity level (we’re talking +time they went down to the Jupiter gravity level (we're talking centrifugal force here; the station is spinning, of course) and nearly turned into pancakes of the Pooh and Piglet varieties, respectively. Or the time they drifted out into the free-fall area at the core and had to @@ -27,11 +27,11 @@ be rescued by humans with wings strapped on (a tip of the hat to Robert Heinlein here). Or the time they were caught up by the current in the river through the Wood and drifted for weeks around the circumference of the station, meeting many cultures and finding many adventures along the -way. (Yes, Farmer’s Riverworld; no one said the stories you tell your +way. (Yes, Farmer's Riverworld; no one said the stories you tell your children need to be purely original, just interesting.) (If you think Pooh and Piglet in a space station is a tad peculiar, then -I won’t even mention Karla, the woman who invented agriculture, +I won't even mention Karla, the woman who invented agriculture, medicine, sanitation, reading and writing, peace, and just about everything else while travelling the length of the Americas with her mountain lion during the last Ice Age; or the Mars Cats and their trip @@ -42,8 +42,8 @@ enough to eat inhabited universes. But I digress.) Anyway, I bring up Pooh and the space station because the time has come to discuss fast texture mapping. *Texture mapping* is the process of mapping an image (in our case, a bitmap) onto the surface of a polygon -that’s been transformed in the process of 3-D drawing. Up to this point, -each polygon we’ve drawn in X-Sharp has been a single, solid color. Over +that's been transformed in the process of 3-D drawing. Up to this point, +each polygon we've drawn in X-Sharp has been a single, solid color. Over the last couple of chapters we added the ability to shade polygons according to lighting, but each polygon was still a single color. Thus, in order to produce any sort of intricate design, a great many tiny @@ -51,10 +51,10 @@ polygons would have to be drawn. That would be very slow, so we need another approach. One such approach is texture mapping; that is, mapping the bitmap containing the desired image onto the pixels contained within the transformed polygon. Done properly, this should make it possible to -change X-Sharp’s output from a bland collection of monocolor facets to a +change X-Sharp's output from a bland collection of monocolor facets to a lively, detailed, and much more realistic scene. -“What sort of scene?” you may well ask. This is where Pooh and the space +"What sort of scene?" you may well ask. This is where Pooh and the space station came in. When I sat down to think of a sample texture-mapping application, it occurred to me that the shaded ball demo we added to X-Sharp recently looked at least a bit like a spinning, spherical space @@ -73,13 +73,13 @@ destination pixel in the transformed polygon back to the image bitmap, via a reverse transformation, and seeing what color resides at the corresponding location in the image bitmap, as shown in Figure 56.1. It might seem more intuitive to map pixels the other way, from the image -bitmap to the transformed polygon, but in fact it’s crucial that the +bitmap to the transformed polygon, but in fact it's crucial that the mapping proceed backward from the destination to avoid gaps in the final image. With the approach of finding the right value for each destination -pixel in turn, via a backward mapping, there’s no way we can miss any +pixel in turn, via a backward mapping, there's no way we can miss any destination pixels. On the other hand, with the forward-mapping method, some destination pixels may be skipped or double-drawn, because this is -not necessarily a one-to-one or one-to-many mapping. Although we’re not +not necessarily a one-to-one or one-to-many mapping. Although we're not going to take advantage of it now, mapping back to the source makes it possible to average several neighboring image pixels together to calculate the value for each destination pixel; that is, to antialias @@ -92,12 +92,12 @@ color.* #### Mapping Textures Made Easy {#Heading4} -To understand how we’re going to map textures, consider Figure 56.2, +To understand how we're going to map textures, consider Figure 56.2, which maps a bitmapped image directly onto an untransformed polygon. -Here, we simply map the origin of the polygon’s untransformed coordinate +Here, we simply map the origin of the polygon's untransformed coordinate system somewhere within the image, then map the vertices to the -corresponding image pixels. (For simplicity, I’ll assume in this -discussion that the polygon’s coordinate system is in units of pixels, +corresponding image pixels. (For simplicity, I'll assume in this +discussion that the polygon's coordinate system is in units of pixels, but scaling images to polygons is eminently doable. This will become clearer when we look at mapping images onto transformed polygons, next.) Mapping the image to the polygon is then a simple matter of stepping one @@ -119,13 +119,13 @@ draw across each destination scan line, but the proper source pixels for each destination scan line may now track across the source bitmap at an angle, as shown in Figure 56.3. What can we do? -The solution is remarkably simple. We’ll just map each transformed +The solution is remarkably simple. We'll just map each transformed vertex to the corresponding vertex in the bitmap; this is easy, because the vertices are at the same indices in the original and transformed vertex lists. Each time we select a new edge to scan for the destination -polygon, we’ll select the corresponding edge in the source bitmap, as +polygon, we'll select the corresponding edge in the source bitmap, as well. Then—and this is crucial—each time we step a destination edge one -scan line, we’ll step the corresponding source image edge an equivalent +scan line, we'll step the corresponding source image edge an equivalent amount. ------------------------ --------------------------------- -------------------- diff --git a/56-02.md b/56-02.md index 29bac8a..7eef069 100644 --- a/56-02.md +++ b/56-02.md @@ -2,10 +2,10 @@ [Previous](56-01.html) [Table of Contents](index.html) [Next](56-03.html) ------------------------ --------------------------------- -------------------- -Ah, but what is an “equivalent amount”? Think of it this way. If a +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 -the source edge by 100, and add those amounts to the source edge’s +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** @@ -16,7 +16,7 @@ destination is doing. ![](images/56-03.jpg)\ **Figure 56.3**  *Mapping a texture onto a 2-D rotated polygon.* -Now we’re able to track the coordinates of the polygon edges through the +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 @@ -24,19 +24,19 @@ each scan line of the polygon, once for each pixel on the scan line. (**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 -the source image, as determined by the source edge points we’ve been +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**, -are used for descriptive purposes, and don’t necessarily correspond to +are used for descriptive purposes, and don't necessarily correspond to the actual variable names used in Listing 56.2.) -That’s a workable approach for 2-D rotated polygons—but what about 3-D +That's a workable approach for 2-D rotated polygons—but what about 3-D rotated polygons, where the visible dimensions of the polygon can vary -with 3-D rotation and perspective projection? First, I’d like to make it +with 3-D rotation and perspective projection? First, I'd like to make it clear that texture mapping takes place from the source image to the destination polygon after the destination polygon is projected to the screen. That is, the image will be mapped after the destination polygon @@ -49,8 +49,8 @@ result of 3-D rotation and perspective projection, we just end up taking bigger steps through the source image and skipping a lot of source image pixels, as shown in Figure 56.5. The upshot is that the above approach handles all transformations and projections effortlessly. It could also -be used to scale source images up to fit in larger polygons; all that’s -needed is a list of where the polygon’s vertices map into the source +be used to scale source images up to fit in larger polygons; all that's +needed is a list of where the polygon's vertices map into the source image, and everything else happens automatically. In fact, mapping from any polygonal area of a bitmap to any destination polygon will work, given only that the two polygons have the same number of vertices. @@ -66,15 +66,15 @@ the source image.* \ {#Heading5} -That’s all there is to quick-and-dirty texture mapping. This technique +That's all there is to quick-and-dirty texture mapping. This technique basically uses a two-stage digital differential analyzer (DDA) approach to step through the appropriate part of the source image in tandem with -the normal scan-line stepping through the destination polygon, so I’ll -call it “DDA texture mapping.” It’s worth noting that there is no need +the normal scan-line stepping through the destination polygon, so I'll +call it "DDA texture mapping." It's worth noting that there is no need for any trigonometric functions at all, and only two divides are required per scan line. -This isn’t a perfect approach, of course. For one thing, it isn’t +This isn't a perfect approach, of course. For one thing, it isn't anywhere near as fast as drawing solid polygons; the speed is more comparable to drawing each polygon as a series of lines. Also, the DDA approach results in far from perfect image quality, since source pixels @@ -85,39 +85,39 @@ pixels as we did for simple, unweighted antialiasing in Chapters F, G,Chapter K on the companion CD-ROM. Or, we could take a Wu antialiasing approach (see Chapter 57) and average the two bracketing pixels along each axis according to proximity. If we had cycles to waste (which, -given that this is real-time animation on a PC, we don’t), we could +given that this is real-time animation on a PC, we don't), we could improve image quality by putting the source pixels through a low-pass filter sized in X and Y according to the ratio of the source and destination dimensions (that is, how much the destination is scaled up or down from the source). -Even more important is that the sort of texture mapping I’ll do in -X-Sharp doesn’t correct for perspective. That doesn’t much matter for +Even more important is that the sort of texture mapping I'll do in +X-Sharp doesn't correct for perspective. That doesn't much matter for small polygons or polygons that are nearly parallel to the screen in 3-space, but it can produce very noticeable bowing of textures on large polygons at an angle to the screen. Perspective texture mapping is a -complex subject that’s outside the scope of this book, but you should be +complex subject that's outside the scope of this book, but you should be aware of its existence, because perspective texture mapping is a key element of many games these days. -Finally, I’d like to point out that this sort of DDA texture mapping is +Finally, I'd like to point out that this sort of DDA texture mapping is display-hardware dependent, because the bitmap for each image must be -compatible with the number of bits per pixel in the destination. That’s -actually a fairly serious issue. One of the nice things about X-Sharp’s +compatible with the number of bits per pixel in the destination. That's +actually a fairly serious issue. One of the nice things about X-Sharp's polygon orientation is that, until now, the only display dependent part of X-Sharp has been the transformation from RGB color space to the -adapter’s color space. Compensation for aspect ratio, resolution, and +adapter's color space. Compensation for aspect ratio, resolution, and the like all happens automatically in the course of projection. Still, -we need the ability to display detailed surfaces, and it’s hard to -conceive of a fast way to do so that’s totally hardware independent. (If +we need the ability to display detailed surfaces, and it's hard to +conceive of a fast way to do so that's totally hardware independent. (If you know of one, let me know care of the publisher.) For now, all we need is fast texture mapping of adequate quality, which -the straightforward, non-antialiased DDA approach supplies. I’m sure -there are many other fast approaches, and, as I’ve said, there are more +the straightforward, non-antialiased DDA approach supplies. I'm sure +there are many other fast approaches, and, as I've said, there are more accurate approaches, but DDA texture mapping works well, given the -constraints of the PC’s horsepower. Next, we’ll look at code that -performs DDA texture mapping. First, though, I’d like to take a moment +constraints of the PC's horsepower. Next, we'll look at code that +performs DDA texture mapping. First, though, I'd like to take a moment to thank Jim Kent, author of Autodesk Animator and a frequent correspondent, for getting me started with the DDA approach. diff --git a/56-03.md b/56-03.md index d0ef988..a60c7a4 100644 --- a/56-03.md +++ b/56-03.md @@ -4,8 +4,8 @@ ### Fast Texture Mapping: An Implementation {#Heading6} -As you might expect, I’ve implemented DDA texture mapping in X-Sharp, -and the changes are reflected in the X-Sharp archive in this chapter’s +As you might expect, I've implemented DDA texture mapping in X-Sharp, +and the changes are reflected in the X-Sharp archive in this chapter's subdirectory on the listings disk. Listing 56.1 shows the new header file entries, and Listing 56.2 shows the actual texture-mapped polygon drawer. The set-pixel routine that Listing 56.2 calls is a slight @@ -18,7 +18,7 @@ Listings 56.1 and 56.2 are the actual texture mapping code in its entirety. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Here’s a major tip: DDA texture mapping looks best on fast-moving surfaces, where the eye doesn’t have time to pick nits with the shearing and aliasing that’s an inevi table by-product of such a crude approach. Compile DEMO1 from the X-Sharp archive in this chapter’s subdirectory of the listings disk, and run it. The initial display looks okay, but certainly not great, because the rotational speed is so slow. Now press the S key a few times to speed up the rotation and flip between different rotation axes. I think you’ll be amazed at how much better DDA texture mapping looks at high speed. This technique would be great for mapping textures onto hurtling asteroids or jets, but would come up short for slow, finely detailed movements.* + ![](images/i.jpg) *Here's a major tip: DDA texture mapping looks best on fast-moving surfaces, where the eye doesn't have time to pick nits with the shearing and aliasing that's an inevi table by-product of such a crude approach. Compile DEMO1 from the X-Sharp archive in this chapter's subdirectory of the listings disk, and run it. The initial display looks okay, but certainly not great, because the rotational speed is so slow. Now press the S key a few times to speed up the rotation and flip between different rotation axes. I think you'll be amazed at how much better DDA texture mapping looks at high speed. This technique would be great for mapping textures onto hurtling asteroids or jets, but would come up short for slow, finely detailed movements.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- **LISTING 56.1 L56-1.C** @@ -49,19 +49,19 @@ entirety. /* Structure describing one face of an object (one polygon) */ typedef struct { - int * VertNums; /* pointer to list of indexes of this polygon’s vertices - in the object’s vertex list. The first two indexes + int * VertNums; /* pointer to list of indexes of this polygon's vertices + in the object's vertex list. The first two indexes must select end and start points, respectively, of this - polygon’s unit normal vector. Second point should also + polygon's unit normal vector. Second point should also be an active polygon vertex */ int NumVerts; /* # of verts in face, not including the initial vertex, which must be the end of a unit normal vector that starts at the second index in VertNums */ int ColorIndex; /* direct palette index; used only for non-shaded faces */ - ModelColor FullColor; /* polygon’s color */ + ModelColor FullColor; /* polygon's color */ int ShadingType; /* none, ambient, diffuse, texture mapped, etc. */ TextureMap * TexMap; /* pointer to bitmap for texture mapping, if any */ - Point * TexVerts; /* pointer to list of this polygon’s vertices, in + Point * TexVerts; /* pointer to list of this polygon's vertices, in TextureMap coordinates. Index n must map to index n + 1 in VertNums, (the + 1 is to skip over the unit normal endpoint in VertNums) */ @@ -71,14 +71,14 @@ entirety. **LISTING 56.2 L56-2.C** /* Draws a bitmap, mapped to a convex polygon (draws a texture-mapped polygon). - “Convex” means that every horizontal line drawn through the polygon at any + "Convex" means that every horizontal line drawn through the polygon at any point would cross exactly two active edges (neither horizontal lines nor zero-length edges count as active edges; both are acceptable anywhere in the polygon), and that the right & left edges never cross. Nonconvex - polygons won’t be drawn properly. Can’t fail. */ + polygons won't be drawn properly. Can't fail. */ #include #include - #include “polygon.h” + #include "polygon.h" /* Describes the current location and stepping, in both the source and the destination, of an edge */ typedef struct { @@ -91,7 +91,7 @@ entirety. Fixedpoint SourceY; /* current Y location in source for this edge */ Fixedpoint SourceStepX;/* X step in source for Y step in dest of 1 */ Fixedpoint SourceStepY;/* Y step in source for Y step in dest of 1 */ - /* variables used for all-integer Bresenham’s-type + /* variables used for all-integer Bresenham's-type X stepping through the dest, needed for precise pixel placement to avoid gaps */ int DestX; /* current X location in dest for this edge */ @@ -114,7 +114,7 @@ entirety. static int TexMapWidth; /* Draws a texture-mapped polygon, given a list of destination polygon vertices, a list of corresponding source texture polygon vertices, and a - pointer to the source texture’s descriptor. */ + pointer to the source texture's descriptor. */ void DrawTexturedPolygon(PointListHeader * Polygon, Point * TexVerts, TextureMap * TexMap) { @@ -131,7 +131,7 @@ entirety. } /* Scan through the destination polygon vertices and find the top of the left and right edges, taking advantage of our knowledge that vertices run - in a clockwise direction (else this polygon wouldn’t be visible due to + in a clockwise direction (else this polygon wouldn't be visible due to backface removal) */ MinY = 32767; MaxY = -32768; @@ -177,7 +177,7 @@ entirety. /* Draw the scan line between the two current edges */ ScanOutLine(&LeftEdge, &RightEdge); } - /* Advance the source and destination polygon edges, ending if we’ve + /* Advance the source and destination polygon edges, ending if we've scanned all the way to the bottom of the polygon */ if (!StepEdge(&LeftEdge)) { break; @@ -324,21 +324,21 @@ entirety. } No matter how you slice it, DDA texture mapping beats boring, -single-color polygons nine ways to Sunday. The big downside is that it’s +single-color polygons nine ways to Sunday. The big downside is that it's much slower than a normal polygon fill; move the ball close to the screen in DEMO1, and watch things slow down when one of those big -texture maps comes around. Of course, that’s partly because the code is +texture maps comes around. Of course, that's partly because the code is all in C; some well-chosen optimizations would work wonders. In the next -chapter we’ll discuss texture mapping further, crank up the speed of our +chapter we'll discuss texture mapping further, crank up the speed of our texture mapper, and attend to some rough spots that remain in the DDA texture mapping implementation, most notably in the area of exactly which texture pixels map to which destination pixels as a polygon rotates. -And, in case you’re curious, yes, there is a bear in DEMO1. I wouldn’t -say he looks much like a Pooh-type bear, but he’s a bear nonetheless. He +And, in case you're curious, yes, there is a bear in DEMO1. I wouldn't +say he looks much like a Pooh-type bear, but he's a bear nonetheless. He does tend to look a little startled when you flip the ball around so -that he’s zipping by on his head, but, heck, you would too in the same +that he's zipping by on his head, but, heck, you would too in the same situation. And remember, when you buy the next VGA megahit, *Bears in Space*, you saw it here first. diff --git a/57-01.md b/57-01.md index a5a7908..e101037 100644 --- a/57-01.md +++ b/57-01.md @@ -11,38 +11,38 @@ Chapter 57\ I recently spent an hour or so learning how to shear a sheep. Among other things, I learned—in great detail—about the importance of selecting the proper comb for your shears, heard about the man who holds -the world’s record for sheep sheared in a day (more than 600, if memory +the world's record for sheep sheared in a day (more than 600, if memory serves), and discovered, Lord help me, the many and varied ways in which the New Zealand Sheep Shearing Board improves the approved sheep-shearing method every year. The fellow giving the presentation did -his best, but let’s face it, sheep just aren’t very interesting. If you -have children, you’ll know why I was there; if you don’t, there’s no use +his best, but let's face it, sheep just aren't very interesting. If you +have children, you'll know why I was there; if you don't, there's no use explaining. The chap doing the shearing did say one thing that stuck with me, although it may not sound particularly profound. (Actually, it sounds -pretty silly, but bear with me.) He said, “You don’t get really good at -sheep shearing for 10 years, or 10,000 sheep.” I’ll buy that. In fact, +pretty silly, but bear with me.) He said, "You don't get really good at +sheep shearing for 10 years, or 10,000 sheep." I'll buy that. In fact, to extend that morsel of wisdom to the greater, non-ovine-centric universe, it actually takes a good chunk of experience before you get good at anything worthwhile—especially graphics, for a couple of reasons. First, performance matters a lot in graphics, and performance -programming is largely a matter of experience. You can’t speed up PC +programming is largely a matter of experience. You can't speed up PC graphics simply by looking in a book for a better algorithm; you have to understand the code C compilers generate, assembly language optimization, VGA hardware, and the performance implications of various graphics-programming approaches and algorithms. Second, computer graphics is a matter of illusion, of convincing the eye to see what you -want it to see, and that’s very much a black art based on experience. +want it to see, and that's very much a black art based on experience. #### Visual Quality: A Black Hole ... Er, Art {#Heading3} Pleasing the eye with realtime computer animation is something less than -a science, at least at the PC level, where there’s a limited color +a science, at least at the PC level, where there's a limited color palette and no time for antialiasing; in fact, sometimes it can be more than a little frustrating. As you may recall, in the previous chapter I implemented texture mapping in X-Sharp. There was plenty of experience -involved there, some of which I didn’t mention. My first implementation +involved there, some of which I didn't mention. My first implementation was disappointing; the texture maps shimmied and sheared badly, like a loosely affiliated flock of pixels, each marching to its own drummer. Then, I added a control key to speed up the rotation; what a difference! @@ -53,25 +53,25 @@ threshold into being accepted by the eye as a viewed object, rather than simply a collection of pixels. The obvious lesson here is that adequate speed is important to -convincing animation. There’s another, less obvious side to this lesson, -though. I’d been running the texture-mapping demo on a 20 MHz 386 with a +convincing animation. There's another, less obvious side to this lesson, +though. I'd been running the texture-mapping demo on a 20 MHz 386 with a slow VGA when I discovered the beneficial effects of greater animation speed. When, some time later, I ran the demo on a 33 MHz 486 with a fast VGA, I found that the faster rotation was too fast! The ball spun so -rapidly that the eye couldn’t blend successive images together into +rapidly that the eye couldn't blend successive images together into continuous motion, much like watching a badly flickering movie. ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *So the second lesson is that either too little or too much speed can destroy the illusion. Unless you’re antialiasing, you need to tune the shifting of your images so that they’re in the “sweet spot” of apparent motion, in which the eye is willing to ignore the jumping and aliasing, and blend the images together into continuous motion. Only experience can give you a feel for that sweet spot.* + ![](images/i.jpg) *So the second lesson is that either too little or too much speed can destroy the illusion. Unless you're antialiasing, you need to tune the shifting of your images so that they're in the "sweet spot" of apparent motion, in which the eye is willing to ignore the jumping and aliasing, and blend the images together into continuous motion. Only experience can give you a feel for that sweet spot.* ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- #### Fixed-Point Arithmetic, Redux {#Heading4} In the previous chapter I added texture mapping to X-Sharp, but lacked -space to explain some of its finer points. I’ll pick up the thread now +space to explain some of its finer points. I'll pick up the thread now and cover some of those points here, and discuss the visual and -performance enhancements that previous chapter’s code needed—and which -are now present in the version of X-Sharp in this chapter’s subdirectory +performance enhancements that previous chapter's code needed—and which +are now present in the version of X-Sharp in this chapter's subdirectory on the CD-ROM. Back in Chapter 38, I spent a good bit of time explaining exactly which @@ -87,7 +87,7 @@ for speed and ease of use, would be unacceptable because round-off error would result in imprecise pixel placement. More than a year then passed between the time I wrote that statement and -the time I implemented X-Sharp’s texture mapper, during which time my +the time I implemented X-Sharp's texture mapper, during which time my long-term memory apparently suffered at least partial failure. When I went to implement texture mapping for the previous chapter, I decided that since transformed destination vertices can fall at fractional pixel @@ -99,8 +99,8 @@ one small problem: gaps between polygons. Yes, folks, I had ignored the voice of experience (my own voice, at that) at my own peril. You can be assured I will not forget this -particular lesson again: Fixed-point arithmetic is not precise. That’s -not to say that it’s impossible to use fixed-point for drawing polygons; +particular lesson again: Fixed-point arithmetic is not precise. That's +not to say that it's impossible to use fixed-point for drawing polygons; if all adjacent edges share common start and end vertices and common edges are always stepped in the same direction, all polygons should share the same fixed-point imprecision, and edges should fit properly @@ -120,7 +120,7 @@ appear to have shifted by one pixel at the corresponding destination pixel; given all the aliasing and shearing already going on in the texture-mapping process, a one-pixel mapping error is insignificant. -Experience again: It’s the difference between knowing which flaws (like +Experience again: It's the difference between knowing which flaws (like small texture shifts) can reasonably be ignored, and which (like those that produce gaps between polygons) must be avoided at all costs. diff --git a/57-02.md b/57-02.md index ddc21df..bf5fad1 100644 --- a/57-02.md +++ b/57-02.md @@ -6,20 +6,20 @@ The double-DDA texture-mapping code presented in the previous chapter worked adequately, but there were two things about it that left me less -than satisfied. One flaw was performance; I’ll address that shortly. The +than satisfied. One flaw was performance; I'll address that shortly. The other flaw was the way textures shifted noticeably as the orientations of the polygons onto which they were mapped changed. -The previous chapter’s code followed the standard polygon inside/outside +The previous chapter's code followed the standard polygon inside/outside rule for determining which pixels in the source texture map were to be mapped: Pixels that mapped exactly to the left and top destination edges were considered to be inside, and pixels that mapped exactly to the -right and bottom destination edges were considered to be outside. That’s +right and bottom destination edges were considered to be outside. That's fine for filling polygons, but when copying texture maps, it causes different edges of the texture map to be omitted, depending on the destination orientation, because different edges of the texture map correspond to the right and bottom destination edges, depending on the -current rotation. Also, the previous chapter’s code truncated to get +current rotation. Also, the previous chapter's code truncated to get integer source coordinates. This, together with the orientation problem, meant that when a texture turned upside down, it slowed one new row and one new column of pixels from the next row and column of the texture @@ -33,7 +33,7 @@ math.* Listing 57.1 is one solution to these problems. This code, which replaces the equivalently named function presented in the previous chapter (and, of course, is present in the X-Sharp archive in this -chapter’s subdirectory of the listings disk), makes no attempt to follow +chapter's subdirectory of the listings disk), makes no attempt to follow the standard polygon inside/outside rules when mapping the source. Instead, it advances a half-step into the texture map before drawing the first pixel, so pixels along all edges are half included. Rounding @@ -84,9 +84,9 @@ level of orientation-dependent asymmetry. SourceStepY = FixedDiv(RightEdge->SourceY - SourceY, DestWidth); /* Advance 1/2 step in the stepping direction, to space scanned pixels - evenly between the left and right edges. (There’s a slight inaccuracy + evenly between the left and right edges. (There's a slight inaccuracy in dividing negative numbers by 2 by shifting rather than dividing, - but the inaccuracy is in the least significant bit, and we’ll just + but the inaccuracy is in the least significant bit, and we'll just live with it.) */ SourceX += SourceStepX >> 1; SourceY += SourceStepY >> 1; @@ -132,10 +132,10 @@ the rest. #### Fast Texture Mapping {#Heading7 align="center"} -Of course, there’s a problem with mapping a texture across many +Of course, there's a problem with mapping a texture across many polygons: Texture mapping is slow. If you run DEMO1 and move the ball up -close to the screen, you’ll see that the ball slows considerably -whenever a texture swings around into view. To some extent that can’t be +close to the screen, you'll see that the ball slows considerably +whenever a texture swings around into view. To some extent that can't be helped, because each pixel of a texture-mapped polygon has to be calculated and drawn independently. Nonetheless, we can certainly improve the performance of texture mapping a good deal over what I diff --git a/57-03.md b/57-03.md index a9363b2..f5924da 100644 --- a/57-03.md +++ b/57-03.md @@ -39,7 +39,7 @@ SourceY dd ? ;Y location in source for this edge SourceStepX dd ? ;X step in source for Y step in dest of 1 SourceStepY dd ? ;Y step in source for Y step in dest of 1 - ;variables used for all-integer Bresenham’s-type + ;variables used for all-integer Bresenham's-type ; X stepping through the dest, needed for precise ; pixel placement to avoid gaps DestX dw ? ;current X location in dest for this edge @@ -79,10 +79,10 @@ public -ScanOutLine align 2 -ScanOutLine proc near - push bp ;preserve caller’s stack frame + push bp ;preserve caller's stack frame mov bp,sp ;point to our stack frame sub sp,LOCAL-SIZE ;allocate space for local variables - push si ;preserve caller’s register variables + push si ;preserve caller's register variables push di ; Nothing to do if destination is fully X clipped. mov di,[bp].RightEdge @@ -127,7 +127,7 @@ cmp ax,0 ;is the whole step exactly an integer? jz SourceXNonNeg ;yes inc dx ;no, truncate to integer in the direction of - ; 0, because otherwise we’ll end up with a + ; 0, because otherwise we'll end up with a ; whole step of 1-too-large magnitude SourceXNonNeg: mov [bp].lXAdvanceByOne,cx ;amount to add to source pointer to @@ -155,7 +155,7 @@ cmp ax,0 ;is the whole step exactly an integer? jz SourceYNonNeg ;yes inc dx ;no, truncate to integer in the direction of - ; 0, because otherwise we’ll end up with a + ; 0, because otherwise we'll end up with a ; whole step of 1-too-large magnitude SourceYNonNeg: mov [bp].lYAdvanceByOne,cx ;amount to add to source pointer to @@ -164,9 +164,9 @@ imul dx ; image bitmap when Y steps (ignoring mov [bp].lYBaseAdvance,ax ; carry from the fractional part) ; Advance 1/2 step in the stepping direction, to space scanned pixels evenly - ; between the left and right edges. (There’s a slight inaccuracy in dividing + ; between the left and right edges. (There's a slight inaccuracy in dividing ; negative numbers by 2 by shifting rather than dividing, but the inaccuracy - ; is in the least significant bit, and we’ll just live with it.) + ; is in the least significant bit, and we'll just live with it.) mov ax,word ptr [bp].lSourceStepX mov dx,word ptr [bp].lSourceStepX+2 sar dx,1 @@ -242,13 +242,13 @@ add di,[-CurrentPageBase] ;offset of pixel in display memory ;ES:DI now points to the first destination pixel - and cl,011b ;CL = pixel’s plane + and cl,011b ;CL = pixel's plane mov al,MAP-MASK mov dx,SC-INDEX out dx,al ;point the SC Index register to the Map Mask - mov al,11h ;one plane bit in each nibble, so we’ll get carry + mov al,11h ;one plane bit in each nibble, so we'll get carry ; automatically when going from plane 3 to plane 0 - shl al,cl ;set the bit for the first pixel’s plane to 1 + shl al,cl ;set the bit for the first pixel's plane to 1 ; If source X step is negative, change over to working with non-negative ; values. cmp word ptr [bp].lXAdvanceByOne,0 @@ -264,13 +264,13 @@ not word ptr [bp].lSourceY SYStepSet: ; At this point: - ; AL = initial pixel’s plane mask + ; AL = initial pixel's plane mask ; BX = pointer to initial image pixel ; SI = # of pixels to fill ; DI = pointer to initial destination pixel mov dx,SC-INDEX+1 ;point to SC Data; Index points to Map Mask TexScanLoop: - ; Set the Map Mask for this pixel’s plane, then draw the pixel. + ; Set the Map Mask for this pixel's plane, then draw the pixel. out dx,al mov ah,[bx] ;get image pixel mov es:[di],ah ;set image pixel @@ -278,13 +278,13 @@ add bx,[bp].lXBaseAdvance ;advance the minimum # of pixels in X mov cx,word ptr [bp].lSourceStepX add word ptr [bp].lSourceX,cx;step the source X fractional part - jnc NoExtraXAdvance ;didn’t turn over; no extra advance + jnc NoExtraXAdvance ;didn't turn over; no extra advance add bx,[bp].lXAdvanceByOne ;did turn over; advance X one extra NoExtraXAdvance: add bx,[bp].lYBaseAdvance;advance the minimum # of pixels in Y mov cx,word ptr [bp].lSourceStepY add word ptr [bp].lSourceY,cx;step the source Y fractional part - jnc NoExtraYAdvance;didn’t turn over; no extra advance + jnc NoExtraYAdvance;didn't turn over; no extra advance add bx,[bp].lYAdvanceByOne;did turn over; advance Y one extra NoExtraYAdvance: ; Point to the next destination pixel, by cycling to the next plane, and @@ -295,10 +295,10 @@ dec si jnz TexScanLoop ScanDone: - pop di ;restore caller’s register variables + pop di ;restore caller's register variables pop si mov sp,bp ;deallocate local variables - pop bp ;restore caller’s stack frame + pop bp ;restore caller's stack frame ret -ScanOutLine endp end diff --git a/57-04.md b/57-04.md index cf07843..f6f68c8 100644 --- a/57-04.md +++ b/57-04.md @@ -7,13 +7,13 @@ draws across each destination scan line, near the end of the listing. One optimization is elimination of the call to the set-pixel routine used to draw each pixel in Listing 57.1. Function calls are expensive operations, to be avoided when performance matters. Also, although Mode -X (the undocumented 320x240 256-color VGA mode X-Sharp runs in) doesn’t +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 +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 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 +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 use a clever optimization to both step the destination and reduce the overhead of maintaining the mask. Two copies of the current plane mask @@ -42,7 +42,7 @@ efficient in Listing 57.1, consisting of only two adds and the macro take four instructions apiece, and the macro hides not only conversion from fixed-point to integer, but also a time-consuming multiplication. Incremental approaches are excellent at avoiding multiplication, because -cumulative additions can often replace multiplication. That’s the case +cumulative additions can often replace multiplication. That's the case with stepping through the source texture in Listing 57.2; ten instructions, with a maximum of two branches, replace all the texture calculations of Listing 57.1. Listing 57.2 simply detects when the @@ -55,14 +55,14 @@ worth the trouble? Indeed it is. Listing 57.2 is more than twice as fast as Listing 57.1, and the difference is very noticeable when large, texture-mapped areas are animated. Whether more than doubling performance is significant is a matter of opinion, I suppose, but -imagine that you’re in William Gibson’s *Neuromancer*, trying to crack a +imagine that you're in William Gibson's *Neuromancer*, trying to crack a corporate database. Which texture-mapping routine would you rather have interfacing you to Cyberspace? -I’m always interested in getting your feedback on and hearing about +I'm always interested in getting your feedback on and hearing about potential improvements to X-Sharp. Contact me through the publisher. There is no truth to the rumor that I can be reached under the alias -“sheep-shearer,” at least not for another 9,999 sheep. +"sheep-shearer," at least not for another 9,999 sheep. ------------------------ --------------------------------- -------------------- [Previous](57-03.html) [Table of Contents](index.html) [Next](58-01.html) diff --git a/58-01.md b/58-01.md index cc7ae7d..157d779 100644 --- a/58-01.md +++ b/58-01.md @@ -3,26 +3,26 @@ ------------------------ --------------------------------- -------------------- Chapter 58\ - Heinlein’s Crystal Ball, Spock’s Brain, and the 9-Cycle Dare {#Heading1} + Heinlein's Crystal Ball, Spock's Brain, and the 9-Cycle Dare {#Heading1} ------------------------------------------------------------- ### Using the Whole-Brain Approach to Accelerate Texture Mapping {#Heading2} -I’ve had the pleasure recently of rereading several of the works of -Robert A. Heinlein, and I’m as impressed as I was as a teenager—but in a +I've had the pleasure recently of rereading several of the works of +Robert A. Heinlein, and I'm as impressed as I was as a teenager—but in a different way. The first time around, I was wowed by the sheer romance -of technology married to powerful stories; this time, I’m struck most of -all by The Master’s remarkable prescience. “Blowups Happen” is about the +of technology married to powerful stories; this time, I'm struck most of +all by The Master's remarkable prescience. "Blowups Happen" is about the risks of nuclear power, and their effects on human psychology—written -before a chain reaction had ever happened on this planet. “Solution -Unsatisfactory” is about the unsolvable dilemma—ultimate offense, no +before a chain reaction had ever happened on this planet. "Solution +Unsatisfactory" is about the unsolvable dilemma—ultimate offense, no defense—posed by atomic weapons; this in 1941. And in *Between Planets* (1951), consider this minor bit of action: -> The doctor’s phone regretted politely that Dr. Jefferson was not at +> The doctor's phone regretted politely that Dr. Jefferson was not at > home and requested him to leave a message. He was dictating it when a -> warm voice interrupted: ‘I’m at home to you, Donald. Where are you, -> lad?’ +> warm voice interrupted: ‘I'm at home to you, Donald. Where are you, +> lad?' Predicting the widespread use of answering machines is perhaps not so remarkable, but foreseeing that they would be used for call screening @@ -31,15 +31,15 @@ is; technology is much easier to extrapolate than are social patterns. Even so, Heinlein was no prophet; his crystal ball was just a little less fuzzy than ours. The aforementioned call in *Between Planets* was placed on a viewphone; while that technology has indeed come to pass, -its widespread use has not. The ultimate weapon in “Solution -Unsatisfactory” was radioactive dust, not nuclear bombs, and we have +its widespread use has not. The ultimate weapon in "Solution +Unsatisfactory" was radioactive dust, not nuclear bombs, and we have somehow survived nearly 50 years of nuclear weapons without either acquiring a world dictator or destroying ourselves. Slide rules are all -over the place in Heinlein’s works, and in one story (the name now lost +over the place in Heinlein's works, and in one story (the name now lost to memory), an astronaut straps himself into a massive integral calculator; computers are nowhere to be found. -Most telling, I think, is that in “Blowups Happen,” the engineers +Most telling, I think, is that in "Blowups Happen," the engineers running the nuclear power plant—at considerable risk to both body and sanity—are the best of the best, highly skilled in math and required to ride the nuclear reaction on a second-to-second basis, with the risk of @@ -47,7 +47,7 @@ an explosion that might end life on Earth, and would surely kill them, if they slip. Contrast that with our present-day reality of nuclear plants run by generally competent technicians, with the occasional report of shoddy maintenance and bored power-plant employees using -drugs, playing games, and falling asleep while on duty. Heinlein’s +drugs, playing games, and falling asleep while on duty. Heinlein's universe makes for a better story, of course, but, more than that, it shows the filters and biases through which he viewed the world. At least in print, Heinlein was an unwavering believer in science, technology, @@ -65,7 +65,7 @@ special-casing the code so that it does just what it has to, and no more. The greatest impediment to this process is seeing the problem in terms of what the code currently does, or what you already know, thereby ignoring many possible solutions. Put another way, how you look at an -optimization problem determines how you’ll solve it; your assumptions +optimization problem determines how you'll solve it; your assumptions may speed and simplify the process, but they are also your limitations. Consider, for example, how a seemingly intractable problem becomes eminently tractable the instant you learn that someone else has solved @@ -76,7 +76,7 @@ mapper in X-Sharp. ### Texture Mapping Redux {#Heading3} -We’ve spent the previous several chapters exploring the X Sharp graphics +We've spent the previous several chapters exploring the X Sharp graphics library, something I built over time as a serious exercise in 3-D graphics. When X-Sharp reached the point at which we left it at the end of the previous chapter, I was rather pleased with it—with one @@ -92,11 +92,11 @@ assembly—pretty good code, by most standards!—and I felt comfortable with my implementation; but then I got a letter from John Miles, who was at the time getting seriously into 3-D and is now the author of a 3-D game library. (Yes, you can license it from his company, Non-Linear -Arts, if you’d like; John can be reached at 70322.2457@compuserve.com.) -John wrote me as follows: “Hmm, so *that’s* how texture-mapping works. -But 3 jumps *per pixel?* Hmph!” +Arts, if you'd like; John can be reached at 70322.2457@compuserve.com.) +John wrote me as follows: "Hmm, so *that's* how texture-mapping works. +But 3 jumps *per pixel?* Hmph!" -It was the “Hmph” that really got to me. +It was the "Hmph" that really got to me. #### Left-Brain Optimization {#Heading4} @@ -112,13 +112,13 @@ The inner loop of my original texture-mapping code is shown in Listing shown in Figure 58.1; an outer loop runs through all the scanlines in whatever polygon is being drawn. I immediately saw that I could eliminate nearly 10 percent of the cycles by unrolling the loop; -obviously, John had done that, else there’s no way he could branch only +obviously, John had done that, else there's no way he could branch only once per scanline. (By the way, branching only once per scanline via a fully unrolled loop is not generally recommended. A branch every few pixels costs relatively little, and the cache effects of fully unrolled code are *not* good.) I quickly came up with several other ways to speed up the code, but soon realized that all the clever coding in the world -wasn’t going to get me within 100 percent of John’s performance so long +wasn't going to get me within 100 percent of John's performance so long as I had to cycle from one plane to the next for every pixel. ![](images/58-01.jpg)\ diff --git a/58-02.md b/58-02.md index 6b59a35..8d3a8dd 100644 --- a/58-02.md +++ b/58-02.md @@ -5,20 +5,20 @@ **LISTING 58.1 L58-1.ASM** ; Inner loop to draw a single texture-mapped horizontal scanline in - ; Mode X, the VGA’s page-flipped 256-color mode. Because adjacent + ; Mode X, the VGA's page-flipped 256-color mode. Because adjacent ; pixels lie in different planes in Mode X, an OUT must be performed ; to select the proper plane before drawing each pixel. ; ; At this point: - ; AL = initial pixel’s plane mask + ; AL = initial pixel's plane mask ; DS:BX = initial source texture pointer - ; DX = pointer to VGA’s Sequencer Data register + ; DX = pointer to VGA's Sequencer Data register ; SI = # of pixels to fill ; ES:DI = pointer to initial destination pixel TexScanLoop: - ; Set the Map Mask for this pixel’s plane, then draw the pixel. + ; Set the Map Mask for this pixel's plane, then draw the pixel. out dx,al mov ah,[bx] ;get texture pixel @@ -29,14 +29,14 @@ add bx,[bp].lXBaseAdvance ;advance the minimum # of pixels in X mov cx,word ptr [bp].lSourceStepX add word ptr [bp].lSourceX,cx ;step the source X fractional part - jnc NoExtraXAdvance ;didn’t turn over; no extra advance + jnc NoExtraXAdvance ;didn't turn over; no extra advance add bx,[bp].lXAdvanceByOne ;did turn over; advance X one extra NoExtraXAdvance: add bx,[bp].lYBaseAdvance ;advance the minimum # of pixels in Y mov cx,word ptr [bp].lSourceStepY add word ptr [bp].lSourceY,cx ;step the source Y fractional part - jnc NoExtraYAdvance ;didn’t turn over; no extra advance + jnc NoExtraYAdvance ;didn't turn over; no extra advance add bx,[bp].lYAdvanceByOne ;did turn over; advance Y one extra NoExtraYAdvance: @@ -54,11 +54,11 @@ 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 -VGA’s hardware is needed to select the plane being drawn to. (See +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* 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 -of 18 cycles, double John’s 9 cycles, just to handle plane management. +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. @@ -68,7 +68,7 @@ necessary. I must confess, with some embarrassment, that at this point I threw myself into designing a solution that involved executing the texture mapping code up to four times per scanline, once for the pixels in each -plane. It’s hard to overstate the complexity of this approach, which +plane. It's hard to overstate the complexity of this approach, which involves quadrupling the normal pixel-to-pixel increments, adjusting the start value for each of the passes, and dealing with some nasty boundary cases. Make no mistake, the code was perfectly doable, and would in fact @@ -76,29 +76,29 @@ have gotten plane control out of the inner loop, but would have been very difficult to get exactly right, and would have suffered from substantial overhead. -Fortunately, in the last sentence I was able to say “would have,” not -“was,” because my friend Chris Hecker (checker@bix.com) came along to +Fortunately, in the last sentence I was able to say "would have," not +"was," because my friend Chris Hecker (checker@bix.com) came along to toss a figurative bucket of cold water on my right brain, which was evidently asleep. (Or possibly stolen by scantily-clad, attractive -aliens; remember “Spock’s Brain”?) Chris is the author of the WinG +aliens; remember "Spock's Brain"?) Chris is the author of the WinG Windows game graphics package, available from Microsoft via FTP, CompuServe, or MSDN Level 2; if, like me, you were at the Game Developers Conference in April 1994, you, along with everyone else, were -stunned to see Id’s megahit DOOM running at full speed in a window, -thanks to WinG. If you write games for a living, run, don’t walk, to +stunned to see Id's megahit DOOM running at full speed in a window, +thanks to WinG. If you write games for a living, run, don't walk, to check WinG out! Chris listened to my proposed design for all of maybe 30 seconds, -growing visibly more horrified by the moment, before he said, “But why -don’t you just draw vertical rather than horizontal scanlines?” +growing visibly more horrified by the moment, before he said, "But why +don't you just draw vertical rather than horizontal scanlines?" Why indeed? #### A 90-Degree Shift in Perspective {#Heading5} As I said earlier, how you look at an optimization problem defines how -you’ll be able to solve it. In order to boost performance, sometimes -it’s necessary to look at things from a different angle—and for texture +you'll be able to solve it. In order to boost performance, sometimes +it's necessary to look at things from a different angle—and for texture mapping this was literally as well as figuratively true. Chris suggested nothing more nor less than scanning out polygons at a 90-degree angle to normal, starting, say, at the left edge of the polygon, and @@ -108,10 +108,10 @@ would be in the same plane, and I would need to change planes only 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 +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 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.) @@ -132,7 +132,7 @@ of *four* times faster than Listing 58.1.) REPTLOOP_UNROLL - ; Set the Map Mask for this pixel’s plane, then draw the pixel. + ; Set the Map Mask for this pixel's plane, then draw the pixel. mov ah,[bx] ;get texture pixel mov es:[di],ah ;set screen pixel @@ -142,14 +142,14 @@ of *four* times faster than Listing 58.1.) add bx,[bp].lXBaseAdvance ;advance the minimum # of pixels in X mov cx,word ptr [bp].lSourceStepX add word ptr [bp].lSourceX,cx ;step the source X fractional part - jnc NoExtraXAdvance ;didn’t turn over; no extra advance + jnc NoExtraXAdvance ;didn't turn over; no extra advance add bx,[bp].lXAdvanceByOne ;did turn over; advance X one extra NoExtraXAdvance: add bx,[bp].lYBaseAdvance ;advance the minimum # of pixels in Y mov cx,word ptr [bp].lSourceStepY add word ptr [bp].lSourceY,cx ;step the source Y fractional part - jnc NoExtraYAdvance ;didn’t turn over; no extra advance + jnc NoExtraYAdvance ;didn't turn over; no extra advance add bx,[bp].lYAdvanceByOne ;did turn over; advance Y one extra NoExtraYAdvance: diff --git a/58-03.md b/58-03.md index 584fcb0..1b8ec74 100644 --- a/58-03.md +++ b/58-03.md @@ -2,25 +2,25 @@ [Previous](58-02.html) [Table of Contents](index.html) [Next](58-04.html) ------------------------ --------------------------------- -------------------- -I’d like to emphasize that algorithmically and conceptually, there is +I'd like to emphasize that algorithmically and conceptually, there is *no* difference between scanning out a polygon top to bottom and scanning it out left to right; it is only in conjunction with the hardware organization of Mode X that the scanning direction matters in the least. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *That’s what Zen programming is all about, though; tying together two pieces of seemingly unrelated information to good effect—and that’s what I had failed to do. Like Robert Heinlein—like all of us—I had viewed the world through a filter composed of my ingrained assumptions, and one of those assumptions, based on all my past experience, was that pixel processing proceeds left to right. Eventually, I might have come up with Chris’s approach; but I would only have come up with it when and if I relaxed and stepped back a little, and allowed myself—almost dared myself—to think of it. When you’re optimizing, be sure to leave quiet, nondirected time in which to conjure up those less obvious solutions, and periodically try to figure out what assumptions you’re making—and then question them!* + ![](images/i.jpg) *That's what Zen programming is all about, though; tying together two pieces of seemingly unrelated information to good effect—and that's what I had failed to do. Like Robert Heinlein—like all of us—I had viewed the world through a filter composed of my ingrained assumptions, and one of those assumptions, based on all my past experience, was that pixel processing proceeds left to right. Eventually, I might have come up with Chris's approach; but I would only have come up with it when and if I relaxed and stepped back a little, and allowed myself—almost dared myself—to think of it. When you're optimizing, be sure to leave quiet, nondirected time in which to conjure up those less obvious solutions, and periodically try to figure out what assumptions you're making—and then question them!* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ![](images/58-03.jpg)\ **Figure 58.3**  *Texture mapping a single vertical column.* -There are a few complications with Chris’s approach, not least that -X-Sharp’s polygon-filling convention (top and left edges included, +There are a few complications with Chris's approach, not least that +X-Sharp's polygon-filling convention (top and left edges included, bottom and right edges excluded) is hard to reproduce for column-oriented texture mapping. I solved this in X-Sharp version 22 by tweaking the edge-scanning code to allow column-oriented texture mapping -to match the current convention. (You’ll find X-Sharp 22 on the listings +to match the current convention. (You'll find X-Sharp 22 on the listings diskette in the directory for this chapter.) Chris also illustrated another important principle of optimization: A @@ -29,15 +29,15 @@ and get caught up in particular implementations; if you bounce your ideas off someone, you may well find them coming back with an unexpected—and welcome—spin. -### That’s Nice—But it Sure as Heck Ain’t 9 Cycles {#Heading6} +### That's Nice—But it Sure as Heck Ain't 9 Cycles {#Heading6} -Excellent as Chris’s suggestion was, I still had work to do: Listing -58.2 is still more than twice as slow as John Miles’s code. +Excellent as Chris's suggestion was, I still had work to do: Listing +58.2 is still more than twice as slow as John Miles's code. Traditionally, I start the optimization process with algorithmic optimization, then try to tie the algorithm and the hardware together for maximum efficiency, and finish up with instruction-by-instruction, -take-no-prisoners optimization. We’ve already done the first two steps, -so it’s time to get down to the bare metal. +take-no-prisoners optimization. We've already done the first two steps, +so it's time to get down to the bare metal. Listing 58.2 contains three functional parts: Drawing the pixel, advancing the destination pointer, and advancing the source texture @@ -56,8 +56,8 @@ execute as follows on the Pentium: 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 -Pentium’s second pipe, the V-pipe, lies idle for a cycle. We can reclaim +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. @@ -81,7 +81,7 @@ source pointer, is used to reflect both the X and Y coordinates. In Listing 58.2, the integer and fractional parts are added into the current coordinates with four separate 16-bit operations, and carries from fractional to integer parts are detected via conditional jumps, as -shown in Figure 58.4. There’s quite a lot we can do to improve this. +shown in Figure 58.4. There's quite a lot we can do to improve this. ![](images/58-04.jpg)\ **Figure 58.4**  *Original method for advancing the source texture @@ -96,23 +96,23 @@ 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 instructions to help with the 32-bit fixed-point arithmetic; true, the -size override prefix (because we’re in a 16-bit segment) will cost a -cycle per 32-bit instruction, but that’s better than the 3 cycles it -takes to do 32-bit arithmetic with 16-bit instructions. It isn’t -obvious, but there’s a nifty trick we can use here, again courtesy of +size override prefix (because we're in a 16-bit segment) will cost a +cycle per 32-bit instruction, but that's better than the 3 cycles it +takes to do 32-bit arithmetic with 16-bit instructions. It isn't +obvious, but there's a nifty trick we can use here, again courtesy of Chris Hecker (who, as you can tell, has done a fair amount of thinking about the complexities of texture mapping). We can store the current fractional parts of both the X *and* Y source coordinates in a single 32-bit register, EDX, as shown in Figure 58.6. -It’s important to note that the Y fraction is actually only 15 bits, +It's important to note that the Y fraction is actually only 15 bits, 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 -the source pointer in X. That’s a mere 3 cycles, and all that remains is +the source pointer in X. That's a mere 3 cycles, and all that remains is to finish advancing the source pointer in Y. ![](images/58-05.jpg)\ @@ -124,13 +124,13 @@ pointer.* register.* Actually, we also advanced the source pointer by the Y integer amount -back when we added BP to SI; all that’s left is to detect whether our -addition to the Y fractional current coordinate produced a carry. That’s -easily done by testing bit 15 of EDX; if it’s zero, there was no carry -and we’re done; otherwise, Y carried, so we have to reset bit 15 and +back when we added BP to SI; all that's left is to detect whether our +addition to the Y fractional current coordinate produced a carry. That's +easily done by testing bit 15 of EDX; if it's zero, there was no carry +and we're done; otherwise, Y carried, so we have to reset bit 15 and advance the source pointer by one scanline. The resulting program flow is shown in Figure 58.7. Note that unlike the X fractional addition, we -can’t get away with just adding in the carry from the Y fractional +can't get away with just adding in the carry from the Y fractional addition, because when the Y fraction carries, it indicates a move not from one pixel to the next on a scanline (a single byte), but rather from one scanline to the next (a full scanline width). diff --git a/58-04.md b/58-04.md index 489ff22..d68a482 100644 --- a/58-04.md +++ b/58-04.md @@ -7,7 +7,7 @@ to John Miles, but not there yet. We have one more trick up our sleeve, though: Suppose we point SS to the segment containing our textures, and point DS to the screen? (This requires either setting up a stack in the texture segment or ensuring that interrupts and other stack activity -can’t happen while SS points to that segment.) Then, we could swap the +can't happen while SS points to that segment.) Then, we could swap the functions of SI and BP; that would let us use BP, which accesses SS by default, to get at the textures, and DI to access the screen—all with no segment prefixes at all. By gosh, that would get us exactly one more @@ -60,36 +60,36 @@ Or can we? ENDM -#### Don’t Stop Thinking about Those Cycles {#Heading7} +#### Don't Stop Thinking about Those Cycles {#Heading7} Remember what I said at the outset, that knowing something has been done makes it much easier to do? A corollary is that pushing past that point, -once attained, is very difficult. It’s only natural to want to relax in +once attained, is very difficult. It's only natural to want to relax in the satisfaction of a job well done; then, too, the very nature of the -work changes. Getting from 44 cycles down to John’s 9 cycles was a huge +work changes. Getting from 44 cycles down to John's 9 cycles was a huge leap, but we knew it could be done—therefore the nature of the problem -was to figure out *how* it was done; in cases like this, if we’re sharp -enough (and of course we are!), we’re guaranteed eventual gratification. -Now that we’ve reached John’s level of performance, the problem becomes -*whether* the code can be made faster yet, and that’s a different kettle +was to figure out *how* it was done; in cases like this, if we're sharp +enough (and of course we are!), we're guaranteed eventual gratification. +Now that we've reached John's level of performance, the problem becomes +*whether* the code can be made faster yet, and that's a different kettle of fish altogether, for it may well be that after thinking about it for -a while, we’ll conclude that it can’t. Not only will we have wasted -time, but we’ll also never be sure we were right; we’ll know only that -*we* couldn’t find a solution. That way lies madness. +a while, we'll conclude that it can't. Not only will we have wasted +time, but we'll also never be sure we were right; we'll know only that +*we* couldn't find a solution. That way lies madness. 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. +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 -cycle really *is* wasted, for there’s a way to make the size prefix +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 +into a 32-bit segment and make *all* the instructions 32-bit. That's what Listing 58.4 does; this code is similar to Listing 58.3, but runs in 8 cycles per pixel, a 12.5 percent speedup over Listing 58.3. Whether Listing 58.4 actually draws more pixels per second than Listing 58.3 depends on whether display memory is fast enough to handle pixels as rapidly as Listing 58.4 can deliver them. That speed, one pixel every -122 nanoseconds on a 486/66, is one that ISA adapters can’t hope to +122 nanoseconds on a 486/66, is one that ISA adapters can't hope to match, but fast VLB and PCI adapters can handle with ease. Be aware, too, that cache misses when reading the source texture will generally reduce performance below the calculated 8-cycles-per-pixel level, diff --git a/58-05.md b/58-05.md index 7bdfed7..52155fa 100644 --- a/58-05.md +++ b/58-05.md @@ -48,7 +48,7 @@ language texture mapper. All it took was some help from my friends, a good, stiff jolt of right-brain thinking, and some solid left-brain polishing—plus the knowledge that such a speedup was possible. Treat every optimization task as if John Miles has just written to inform you -that he’s made it faster than your wildest dreams, and you’ll be amazed +that he's made it faster than your wildest dreams, and you'll be amazed at what you can do! ### Texture Mapping Notes {#Heading8} @@ -68,13 +68,13 @@ as follows: MOV BL,[ESI] ;cycle 3 U-pipe ADD EDX,EBP ;cycle 3 V-pipe -However, I don’t see any way to eliminate this last AGI, which happens +However, I don't see any way to eliminate this last AGI, which happens about half the time; even with it, the Pentium execution time for -Listing 58.4 is 5.5 cycles. That’s 61 nanoseconds—a highly respectable +Listing 58.4 is 5.5 cycles. That's 61 nanoseconds—a highly respectable 16 million texture-mapped pixels per second—on a 90 MHz Pentium. The type of texture mapping discussed in both this and earlier chapters -doesn’t do perspective correction when mapping textures. Why that is and +doesn't do perspective correction when mapping textures. Why that is and how to handle perspective correction is a topic for a whole separate book, but be aware that the textures on some large polygons (not the polygon edges themselves) drawn with the code in this chapter will @@ -82,22 +82,22 @@ appear to be unnaturally bowed, although small polygons should look fine. Finally, we never did get rid of the last jump in the texture mapper, -yet John Miles claimed no jumps at all. How did he do it? I’m not sure, -but I’d guess that he used a two-entry look-up table, based on the Y +yet John Miles claimed no jumps at all. How did he do it? I'm not sure, +but I'd guess that he used a two-entry look-up table, based on the Y carry, to decide how much to advance the source pointer in Y. However, I -couldn’t come up with any implementation of this approach that didn’t +couldn't come up with any implementation of this approach that didn't take 0.5 to 1 cycle more than the test-and-jump approach, so either I -didn’t come up with an adequately efficient implementation of the table, +didn't come up with an adequately efficient implementation of the table, John saved a cycle somewhere else, or perhaps John implemented his code in a 32-bit segment, but used the less-efficient table in his fervor to get rid of the final jump. The knowledge that I apparently came up with a different solution than John highlights that the technical aspects of -John’s implementation were, in truth, totally irrelevant to my -optimization efforts; the only actual effect John’s code had on me was +John's implementation were, in truth, totally irrelevant to my +optimization efforts; the only actual effect John's code had on me was to make me *believe* a texture mapper could run that fast. -Believe it! And while you’re at it, give both halves of your brain equal -time—and watch out for aliens in short skirts, 60’s bouffant hairdos, +Believe it! And while you're at it, give both halves of your brain equal +time—and watch out for aliens in short skirts, 60's bouffant hairdos, and an undue interest in either half. ------------------------ --------------------------------- -------------------- diff --git a/59-01.md b/59-01.md index 651daea..08ccf51 100644 --- a/59-01.md +++ b/59-01.md @@ -10,14 +10,14 @@ Chapter 59\ The answer is: Wendy Tucker. -The question that goes with that answer isn’t particularly interesting +The question that goes with that answer isn't particularly interesting to anyone but me—but the manner in which I came up with the answer is. I spent many of my childhood summers at Camp Chingacook, on Lake George in New York. It was a great place to have fun and do some growing up, with swimming and sailing and hiking and lots more. -When I was 14, Camp Chingacook had a mixer with a nearby girls’ camp. As +When I was 14, Camp Chingacook had a mixer with a nearby girls' camp. As best I can recall, I had never had any interest in girls before, but after the older kids had paired up, I noticed a pretty girl looking at me and, with considerable trepidation, I crossed the room to talk to @@ -30,12 +30,12 @@ That was the only time I ever saw her, although I would occasionally remember that warm glow and call up an image of her smiling face. That happened less frequently as the years passed and I had real girlfriends, and by the time I got married, that particular memory was stashed in -some back storeroom of my mind. I didn’t think of her again for more +some back storeroom of my mind. I didn't think of her again for more than a decade. A few days ago, for some reason, that mixer popped into my mind as I was trying to fall asleep. And I wondered, for the first time in 20 years, -what that girl’s name was. The name was there in my mind, somewhere; I +what that girl's name was. The name was there in my mind, somewhere; I could feel the shape of it, in that same back storeroom, if only I could figure out how to retrieve it. @@ -56,35 +56,35 @@ name popped, unbidden, into my mind. Wendy Tucker. There are many problems that are amenable to the straight-ahead, purely -conscious sort of approach that I first tried to use to retrieve Wendy’s -name. Writing code (once it’s designed) is often like that, as are some +conscious sort of approach that I first tried to use to retrieve Wendy's +name. Writing code (once it's designed) is often like that, as are some sorts of debugging, technical writing, and balancing your checkbook. I personally find these left-brain activities to be very appealing because -they’re finite and controllable; when I start one, I know I’ll be able +they're finite and controllable; when I start one, I know I'll be able to deal with whatever comes up and make good progress, just by plowing along. Inspiration and intuitive leaps are sometimes useful, but not required. The problem is, though, that neither you nor I will ever do anything great without inspiration and intuitive leaps, and especially not -without stepping away from what’s known and venturing into territories +without stepping away from what's known and venturing into territories beyond. The way to do that is not by trying harder but, paradoxically, by trying less hard, stepping back, and giving your right brain room to work, then listening for and nurturing whatever comes of that. On a -small scale, that’s how I remembered Wendy’s name, and on a larger -scale, that’s how programmers come up with products that are more than +small scale, that's how I remembered Wendy's name, and on a larger +scale, that's how programmers come up with products that are more than me-too, checklist-oriented software. -Which, for a couple of reasons, brings us neatly to this chapter’s +Which, for a couple of reasons, brings us neatly to this chapter's topic, Binary Space Partitioning (BSP) trees. First, games are probably the sort of software in which the right-brain element is most important—blockbuster games are almost always breakthroughs in one way or another—and some very successful games use BSP trees, most notably id -Software’s megahit DOOM. Second, BSP trees aren’t intuitively easy to +Software's megahit DOOM. Second, BSP trees aren't intuitively easy to grasp, and considerable ingenuity and inventiveness is required to get the most from them. -Before we begin, I’d like to thank John Carmack, the technical wizard +Before we begin, I'd like to thank John Carmack, the technical wizard behind DOOM, for generously sharing his knowledge of BSP trees with me. ### BSP Trees {#Heading3} @@ -92,18 +92,18 @@ behind DOOM, for generously sharing his knowledge of BSP trees with me. A BSP tree is, at heart, nothing more than a tree that subdivides space in order to isolate features of interest. Each node of a BSP tree splits an area or a volume (in 2-D or 3-D, respectively) into two parts along a -line or a plane; thus the name “Binary Space Partitioning.” The +line or a plane; thus the name "Binary Space Partitioning." The subdivision is hierarchical; the root node splits the world into two -subspaces, then each of the root’s two children splits one of those two +subspaces, then each of the root's two children splits one of those two subspaces into two more parts. This continues with each subspace being further subdivided, until each component of interest (each line segment or polygon, for example) has been assigned its own unique subspace. This is, admittedly, a pretty abstract description, but the workings of BSP trees will become clearer shortly; it may help to glance ahead to this -chapter’s figures. +chapter's figures. -Building a tree that subdivides space doesn’t sound particularly -profound, but there’s a lot that can be done with such a structure. BSP +Building a tree that subdivides space doesn't sound particularly +profound, but there's a lot that can be done with such a structure. BSP trees can be used to represent shapes, and operating on those shapes is a simple matter of combining trees as needed; this makes BSP trees a powerful way to implement Constructive Solid Geometry (CSG). BSP trees @@ -112,14 +112,14 @@ collision detection. #### Visibility Determination {#Heading4} -For the time being, I’m going to discuss only one of the many uses of +For the time being, I'm going to discuss only one of the many uses of BSP trees: The ability of a BSP tree to allow you to traverse a set of line segments or polygons in back-to-front or front-to-back order as seen from any arbitrary viewpoint. This sort of traversal can be very helpful in determining which parts of each line segment or polygon are visible and which are occluded from the current viewpoint in a 3-D scene. Thus, a BSP tree makes possible an efficient implementation of -the painter’s algorithm, whereby polygons are drawn in back-to-front +the painter's algorithm, whereby polygons are drawn in back-to-front order, with closer polygons overwriting more distant ones that overlap, as shown in Figure 59.1. (The line segments in Figure 1(a) and in other figures in this chapter, represent vertical walls, viewed from directly diff --git a/59-02.md b/59-02.md index c64f6d7..2157537 100644 --- a/59-02.md +++ b/59-02.md @@ -2,15 +2,15 @@ [Previous](59-01.html) [Table of Contents](index.html) [Next](59-03.html) ------------------------ --------------------------------- -------------------- -Back-to-front or front-to-back traversal in itself wouldn’t be so +Back-to-front or front-to-back traversal in itself wouldn't be so impressive—there are many ways to do that—were it not for one additional -detail: The traversal can always be performed in linear time, as we’ll +detail: The traversal can always be performed in linear time, as we'll see later on. For instance, you can traverse, a polygon list back-to-front from any viewpoint simply by walking through the corresponding BSP tree once, visiting each node one and only one time, and performing only one relatively inexpensive test at each node. -It’s hard to get cheaper sorting than linear time, and BSP-based +It's hard to get cheaper sorting than linear time, and BSP-based rendering stacks up well against alternatives such as z-buffering, octrees, z-scan sorting, and polygon sorting. Better yet, a scene database represented as a BSP tree can be clipped to the view pyramid @@ -22,12 +22,12 @@ for reasons that will become clear as we delve into the workings of BSP trees. ![](images/59-01.jpg)\ - **Figure 59.1**  *The painter’s algorithm.* + **Figure 59.1**  *The painter's algorithm.* #### Limitations of BSP Trees {#Heading5} -Powerful as they are, BSP trees aren’t perfect. By far the greatest -limitation of BSP trees is that they’re time-consuming to build, enough +Powerful as they are, BSP trees aren't perfect. By far the greatest +limitation of BSP trees is that they're time-consuming to build, enough so that, for all practical purposes, BSP trees must be precalculated, and cannot be built dynamically at runtime. In fact, a BSP-tree compiler that attempts to perform some optimization (limiting the number of @@ -49,7 +49,7 @@ quicker than building them, because the BSP trees being merged are already spatially sorted. Another possibility would be to generate a per-pixel z-buffer for each -frame as it’s rendered, to allow dynamically changing objects to be +frame as it's rendered, to allow dynamically changing objects to be drawn into the BSP-based world. In this scheme, the BSP tree would allow fast traversal and clipping of the complex, static world, and the z-buffer would handle the relatively localized visibility determination @@ -69,7 +69,7 @@ required to store the tree, which amounts to a few pointers per node, and the relative complexity of debugging BSP-tree compilation and usage; debugging a large data set being processed by recursive code (which BSP code tends to be) can be quite a challenge. Tools like the BSP compiler -I’ll present in the next chapter, which visually depicts the process of +I'll present in the next chapter, which visually depicts the process of spatial subdivision as a BSP tree is constructed, help a great deal with BSP debugging. @@ -77,23 +77,23 @@ BSP debugging. Now that we know a good bit about what a BSP tree is, how it helps in visible surface determination, and what its strengths and weaknesses -are, let’s take a look at how a BSP tree actually works to provide -front-to-back or back-to-front ordering. This chapter’s discussion will +are, let's take a look at how a BSP tree actually works to provide +front-to-back or back-to-front ordering. This chapter's discussion will be at a conceptual level, with plenty of figures; in the next chapter -we’ll get into mechanisms and implementation details. +we'll get into mechanisms and implementation details. -I’m going to discuss only 2-D BSP trees from here on out, because -they’re much easier to draw and to grasp than their 3-D counterparts. -Don’t worry, though; the principles of 2-D BSP trees using line segments +I'm going to discuss only 2-D BSP trees from here on out, because +they're much easier to draw and to grasp than their 3-D counterparts. +Don't worry, though; the principles of 2-D BSP trees using line segments generalize directly to 3-D BSP trees using polygons. Also, 2-D BSP trees are quite powerful in their own right, as evidenced by DOOM, which is built around 2-D BSP trees. -First, let’s construct a simple BSP tree. Figure 59.2 shows a set of -four lines that will constitute our sample world. I’ll refer to these as -walls, because that’s one easily-visualized context in which a 2-D BSP +First, let's construct a simple BSP tree. Figure 59.2 shows a set of +four lines that will constitute our sample world. I'll refer to these as +walls, because that's one easily-visualized context in which a 2-D BSP tree would be useful in a game. Think of Figure 59.2 as depicting -vertical walls viewed from directly above, so they’re lines for the +vertical walls viewed from directly above, so they're lines for the purpose of the BSP tree. Note that each wall has a front side, denoted by a normal (perpendicular) vector, and a back side. To make a BSP tree for this sample set, we need to split the world in two, then each part @@ -111,8 +111,8 @@ sense of producing the smallest tree, but it has the virtue of generating the splitting lines without expensive analysis. It also saves on data storage, because the data for the walls can do double duty in describing the splitting lines as well. (Putting one wall on each -splitting line doesn’t actually create a unique subspace for each wall, -but it does create a unique subspace *boundary* for each wall; as we’ll +splitting line doesn't actually create a unique subspace for each wall, +but it does create a unique subspace *boundary* for each wall; as we'll see, that spatial organization provides for the same unambiguous visibility ordering as a unique subspace would.) diff --git a/59-03.md b/59-03.md index bcf60d3..b2b554e 100644 --- a/59-03.md +++ b/59-03.md @@ -2,11 +2,11 @@ [Previous](59-02.html) [Table of Contents](index.html) [Next](59-04.html) ------------------------ --------------------------------- -------------------- -Creating a BSP tree is a recursive process, so we’ll perform the first +Creating a BSP tree is a recursive process, so we'll perform the first split and go from there. Figure 59.3 shows the world carved along the line of wall C into two parts: walls that are in front of wall C, and walls that are behind. (Any of the walls would have been an equally -valid choice for the initial split; we’ll return to the issue of +valid choice for the initial split; we'll return to the issue of choosing splitting walls in the next chapter.) This splitting into front and back is the essential dualism of BSP trees. @@ -14,23 +14,23 @@ and back is the essential dualism of BSP trees. **Figure 59.3**  *Initial split along the line of wall C.* Next, in Figure 59.4, the front subspace of wall C is split by wall D. -This is the only wall in that subspace, so we’re done with wall C’s +This is the only wall in that subspace, so we're done with wall C's front subspace. Figure 59.5 shows the back subspace of wall C being split by wall B. -There’s a difference here, though: Wall A straddles the splitting line +There's a difference here, though: Wall A straddles the splitting line generated from wall B. Does wall A belong in the front or back subspace of wall B? ![](images/59-04.jpg)\ - **Figure 59.4**  *Split of wall C’s front subspace along the line of + **Figure 59.4**  *Split of wall C's front subspace along the line of wall D.* ![](images/59-05.jpg)\ - **Figure 59.5**  *Split of wall C’s back subspace along the line of + **Figure 59.5**  *Split of wall C's back subspace along the line of wall B.* -Both, actually. Wall A gets split into two pieces, which I’ll call wall +Both, actually. Wall A gets split into two pieces, which I'll call wall A and wall E; each piece is assigned to the appropriate subspace and treated as a separate wall. As shown in Figure 59.6, each of the split pieces then has a subspace to itself, and each becomes a leaf of the @@ -38,7 +38,7 @@ tree. The BSP tree is now complete. #### Visibility Ordering {#Heading7} -Now that we’ve successfully built a BSP tree, you might justifiably be a +Now that we've successfully built a BSP tree, you might justifiably be a little puzzled as to how any of this helps with visibility ordering. The answer is that each BSP node can definitively determine which of its child trees is nearer and which is farther from any and all viewpoints; @@ -50,21 +50,21 @@ Consider the world of Figure 59.2 viewed from an arbitrary angle, as shown in Figure 59.7. The viewpoint is in front of wall C; this tells us that all walls belonging to the front tree that descends from wall C are nearer along every ray from the viewpoint than wall C is (that is, they -can’t be occluded by wall C). All the walls in wall C’s back tree are +can't be occluded by wall C). All the walls in wall C's back tree are likewise farther away than wall C along any ray. Thus, for this -viewpoint, we know for sure that if we’re using the painter’s algorithm, +viewpoint, we know for sure that if we're using the painter's algorithm, we want to draw all the walls in the back tree first, then wall C, and then the walls in the front tree. If the viewpoint had been on the back side of wall C, this order would have been reversed. Of course, we need more ordering information than wall C alone can give us, but we get that by traversing the tree recursively, making the same -far-near decision at each node. Figure 59.8 shows the painter’s +far-near decision at each node. Figure 59.8 shows the painter's algorithm (back-to-front) traversal order of the tree for the viewpoint -of Figure 59.7. At each node, we decide whether we’re seeing the front -or back side of that node’s wall, then visit whichever of the wall’s +of Figure 59.7. At each node, we decide whether we're seeing the front +or back side of that node's wall, then visit whichever of the wall's children is on the far side from the viewpoint, draw the wall, and then -visit the node’s nearer child, in that order. Visiting a child is +visit the node's nearer child, in that order. Visiting a child is recursive, involving the same far-near visiting order. ![](images/59-06.jpg)\ @@ -86,16 +86,16 @@ Figure 59.7.* The type of tree walk used to produce front-to-back or back-to-front BSP traversal is known as an *inorder* walk. More on this very shortly; -you’re also likely to find a discussion of inorder walking in any good +you're also likely to find a discussion of inorder walking in any good data structures book. The only special aspect of BSP walks is that a -decision has to be made at each node about which way the node’s wall is +decision has to be made at each node about which way the node's wall is facing relative to the viewpoint, so we know which child tree is nearer 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 +decision whether a node's wall is facing forward, made by **WallFacingForward()** in Listing 59.1, can, in general, be made by -generating a normal to the node’s wall in screenspace +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 checking the sign of the dot product of a viewspace (non-perspective @@ -129,7 +129,7 @@ of the left and right vertices indicates which way the wall is facing. } ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Be aware that BSP trees can often be made smaller and more efficient by detecting collinear surfaces (like aligned wall segments) and generating only one BSP node for each collinear set, with the collinear surfaces stored in, say, a linked list attached to that node. Collinear surfaces partition space identically and can’t occlude one another, so it suffices to generate one splitting node for each collinear set.* + ![](images/i.jpg) *Be aware that BSP trees can often be made smaller and more efficient by detecting collinear surfaces (like aligned wall segments) and generating only one BSP node for each collinear set, with the collinear surfaces stored in, say, a linked list attached to that node. Collinear surfaces partition space identically and can't occlude one another, so it suffices to generate one splitting node for each collinear set.* ------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------ --------------------------------- -------------------- diff --git a/59-04.md b/59-04.md index 7b6d26d..a6105d7 100644 --- a/59-04.md +++ b/59-04.md @@ -11,16 +11,16 @@ sequence recursively to each node and its children until the entire tree has been visited, as shown in Figure 59.9. Walking a BSP tree is basically an inorder tree walk; the only difference is that with a BSP tree a decision is made before each descent as to which subtree to visit -first, rather than simply visiting whatever’s pointed to by the -left-subtree pointer. Conceptually, however, an inorder walk is what’s -used to traverse a BSP tree; from now on I’ll discuss normal inorder +first, rather than simply visiting whatever's pointed to by the +left-subtree pointer. Conceptually, however, an inorder walk is what's +used to traverse a BSP tree; from now on I'll discuss normal inorder walking, with the understanding that the same principles apply to BSP trees. -As I’ve said again and again in my printed works over the years, you +As I've said again and again in my printed works over the years, you have to dig deep below the surface to *really* understand something if you want to get it right, and inorder walking turns out to be an -excellent example of this. In fact, it’s such a good example that I +excellent example of this. In fact, it's such a good example that I routinely use it as an interview question for programmer candidates, 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 @@ -39,11 +39,11 @@ unhesitatingly writes something like the perfectly good code in Listings // Function to inorder walk a tree, using code recursion. // Tested with 32-bit Visual C++ 1.10. #include - #include “tree.h” + #include "tree.h" extern void Visit(NODE *pNode); void WalkTree(NODE *pNode) { - // Make sure the tree isn’t empty + // Make sure the tree isn't empty if (pNode != NULL) { // Traverse the left subtree, if there is one @@ -70,7 +70,7 @@ unhesitatingly writes something like the perfectly good code in Listings } NODE; Then I ask if they have any idea how to make the code faster; some -don’t, but most point out that function calls are pretty expensive. +don't, but most point out that function calls are pretty expensive. Either way, I then ask them to rewrite the function without code recursion. @@ -80,21 +80,21 @@ I have never had *anyone* write a functional data-recursion inorder walk function in less time than that, and several people have simply never gotten the code to work at all. Even the best of them have fumbled their way through the code, sticking in a push here or a pop there, then -working through sample scenarios in their head to see what’s broken, +working through sample scenarios in their head to see what's broken, programming by trial and error until the errors seem to be gone. No one -is ever sure they have it right; instead, when they can’t find any more -bugs, they look at me hopefully to see if it’s thumbs-up or thumbs-down. +is ever sure they have it right; instead, when they can't find any more +bugs, they look at me hopefully to see if it's thumbs-up or thumbs-down. And yet, a data-recursive inorder walk implementation has exactly the same flowchart and *exactly* the same functionality as the -code-recursive version they’ve already written. They already have a +code-recursive version they've already written. They already have a fully functional model to follow, with all the problems solved, but they -can’t make the connection between that model and the code they’re trying +can't make the connection between that model and the code they're trying to implement. Why is this? #### Know It *Cold* {#Heading9} -The problem is that these people don’t understand inorder walking +The problem is that these people don't understand inorder walking 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 @@ -110,17 +110,17 @@ Listing 59.4, working with the code-recursive version as a model. // No stack overflow testing is performed. // Tested with 32-bit Visual C++ 1.10. #include - #include “tree.h” + #include "tree.h" #define MAX_PUSHED_NODES 100 extern void Visit(NODE *pNode); void WalkTree(NODE *pNode) { NODE *NodeStack[MAX_PUSHED_NODES]; NODE **pNodeStack; - // Make sure the tree isn’t empty + // Make sure the tree isn't empty if (pNode != NULL) { - NodeStack[0] = NULL; // push “stack empty” value + NodeStack[0] = NULL; // push "stack empty" value pNodeStack = NodeStack + 1; for (;;) { @@ -128,14 +128,14 @@ Listing 59.4, working with the code-recursive version as a model. // the current node and descend to the left // child to start traversing the left subtree. // Keep doing this until we come to a node - // with no left child; that’s the next node to + // with no left child; that's the next node to // visit in inorder sequence while (pNode->pLeftChild != NULL) { *pNodeStack++ = pNode; pNode = pNode->pLeftChild; } - // We’re at a node that has no left child, so + // We're at a node that has no left child, so // visit the node, then visit the right // subtree if there is one, or the last- // pushed node otherwise; repeat for each @@ -143,7 +143,7 @@ Listing 59.4, working with the code-recursive version as a model. // subtree is found or we run out of pushed // nodes (note that the left subtrees of // pushed nodes have already been visited, so - // they’re equivalent at this point to nodes + // they're equivalent at this point to nodes // with no left children) for (;;) { @@ -168,7 +168,7 @@ Listing 59.4, working with the code-recursive version as a model. if ((pNode = *—pNodeStack) == NULL) { // Stack is empty and the current node - // has no right child; we’re done + // has no right child; we're done return; } } diff --git a/59-05.md b/59-05.md index 0f2d95c..28f1020 100644 --- a/59-05.md +++ b/59-05.md @@ -6,18 +6,18 @@ Take a few minutes to look over Listing 59.4 and relate it to Listing 59.2. The structure is different, but upon examination it becomes clear that both listings reflect the same underlying model: For each node, visit the left subtree, visit the node, visit the right subtree. And -although Listing 59.4 is longer, that’s mostly because I commented it +although Listing 59.4 is longer, that's mostly because I commented it heavily to make sure its workings are understood; there are only 13 lines that actually do anything in Listing 59.4. -Let’s look at it another way. All the code in Listing 59.2 does is say: -“Here I am at a node. First I’ll visit the left subtree if there is one, -then I’ll visit this node, then I’ll visit the right subtree if there is -one. While I’m visiting the left subtree, I’ll just push a marker on a +Let's look at it another way. All the code in Listing 59.2 does is say: +"Here I am at a node. First I'll visit the left subtree if there is one, +then I'll visit this node, then I'll visit the right subtree if there is +one. While I'm visiting the left subtree, I'll just push a marker on a 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 +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 data recursion. When the implementation model changes to one with which they are unfamiliar, they abandon the perfectly good model they used @@ -25,8 +25,8 @@ before and try to rederive it in the new context by the seat of their pants. ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Here’s a secret when you’re faced with a situation like this: Step back and get a clear picture of what your code has to do. Omit no steps. You should build a model that is so consistent and solid that you can instantly answer any question about how the code should behave in any situation. For example, my interviewees often decide, by trial and error, that there are two distinct types of right children: Right children visited after popping back to visit a node after the left subtree has been visited, and right children visited after descending to a node that has no left child. This makes the traversal code a mass of special cases, each of which has to be detected by the programmer by trying out scenarios. Worse, you can never be sure with this approach that you’ve caught all the special cases.* - *The alternative is to develop and apply a unifying model. There aren’t really two types of right children; the rule is that all right children are visited after their parents are visited, period. The presence or absence of a left child is irrelevant. The possibility that a right child may be reached via different code paths depending on the presence of a left child does not affect the overall model. While this distinction may seem trivial it is in fact crucial, because if you have the model down cold, you can always tell if the implementation is correct by comparing it with the model.* + ![](images/i.jpg) *Here's a secret when you're faced with a situation like this: Step back and get a clear picture of what your code has to do. Omit no steps. You should build a model that is so consistent and solid that you can instantly answer any question about how the code should behave in any situation. For example, my interviewees often decide, by trial and error, that there are two distinct types of right children: Right children visited after popping back to visit a node after the left subtree has been visited, and right children visited after descending to a node that has no left child. This makes the traversal code a mass of special cases, each of which has to be detected by the programmer by trying out scenarios. Worse, you can never be sure with this approach that you've caught all the special cases.* + *The alternative is to develop and apply a unifying model. There aren't really two types of right children; the rule is that all right children are visited after their parents are visited, period. The presence or absence of a left child is irrelevant. The possibility that a right child may be reached via different code paths depending on the presence of a left child does not affect the overall model. While this distinction may seem trivial it is in fact crucial, because if you have the model down cold, you can always tell if the implementation is correct by comparing it with the model.* ------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- #### Measure and Learn {#Heading10} @@ -41,7 +41,7 @@ especially when you consider that the speedup is diluted by calling the **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 +and do produce different performance results. Always know what you're measuring!) **Listing 59.5 L59\_5.C** @@ -53,7 +53,7 @@ measuring!) #include #include #include - #include “tree.h” + #include "tree.h" long VisitCount = 0; void main(void); void BuildTree(NODE *pNode, int RemainingDepth); @@ -71,7 +71,7 @@ measuring!) { WalkTree(&RootNode); } - printf(“Seconds elapsed: %ld\n”, + printf("Seconds elapsed: %ld\n", time(NULL) - StartTime); getch(); } @@ -91,13 +91,13 @@ measuring!) pNode->pLeftChild = malloc(sizeof(NODE)); if (pNode->pLeftChild == NULL) { - printf(“Out of memory\n”); + printf("Out of memory\n"); exit(1); } pNode->pRightChild = malloc(sizeof(NODE)); if (pNode->pRightChild == NULL) { - printf(“Out of memory\n”); + printf("Out of memory\n"); exit(1); } BuildTree(pNode->pLeftChild, RemainingDepth - 1); diff --git a/59-06.md b/59-06.md index 4293dda..77c9bd1 100644 --- a/59-06.md +++ b/59-06.md @@ -10,20 +10,20 @@ good job with Listing 59.2. Most impressively, when compiling Listing 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 -advantage over Listing 59.2; in fact, it’s at a disadvantage because the +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 Listing 59.2 is a marvel of code generation, at just 3 instructions. -What’s more, although left-subtree traversal is more efficient with data +What's more, although left-subtree traversal is more efficient with data 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 +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 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 +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 recursion in a more complex function, and that a good assembly language implementation could probably speed up Listing 59.4 enough to make it measurably faster than Listing 59.2, but not even close to being @@ -32,7 +32,7 @@ measurably faster than Listing 59.2, but not even close to being The moral of this story (apart from it being a good idea to enable compiler optimization) is: -**1.**  Understand what you’re doing, through and through. +**1.**  Understand what you're doing, through and through. **2.**  Build a complete and consistent model in your head. @@ -40,36 +40,36 @@ compiler optimization) is: **4.**  Implement the design. -**5.**  Measure to learn what you’ve wrought. +**5.**  Measure to learn what you've wrought. -**6.**  Go back to step 1 and apply what you’ve just learned. +**6.**  Go back to step 1 and apply what you've just learned. -With each iteration you’ll dig deeper, learn more, and improve your +With each iteration you'll dig deeper, learn more, and improve your ability to know where and how to focus your design and programming efforts. For example, with the C compilers I used five to 10 years ago, back when I learned about the relative strengths and weaknesses of code and data recursion, and with the processors then in use, Listing 59.4 -would have blown away Listing 59.2. While doing this chapter, I’ve +would have blown away Listing 59.2. While doing this chapter, I've learned that given current processors and compiler technology, data -recursion isn’t going to get me any big wins; and yes, that was news to -me. That’s *good*; this information saves me from wasted effort in the +recursion isn't going to get me any big wins; and yes, that was news to +me. That's *good*; this information saves me from wasted effort in the future and tells me what to concentrate on when I use recursion. Assume nothing, keep digging deeper, and never stop learning and -growing. The world won’t hold still for you, but fortunately you *can* +growing. The world won't hold still for you, but fortunately you *can* run fast enough to keep up if you just keep at it. Depths within depths indeed! ### Surfing Amidst the Trees {#Heading11} -In the next chapter, we’ll build a BSP-tree compiler, and after that, -we’ll put together a rendering system built around the BSP trees the +In the next chapter, we'll build a BSP-tree compiler, and after that, +we'll put together a rendering system built around the BSP trees the compiler generates. If the subject of BSP trees really grabs your fancy (as it should if you care at all about performance graphics) there is at this writing (February 1996) a World Wide Web page on BSP trees that you must investigate at -[http://www.qualia.com/bspfaq/](http://www.qualia.com/bspfaq/). It’s set +[http://www.qualia.com/bspfaq/](http://www.qualia.com/bspfaq/). It's set up in the familiar Internet Frequently Asked Questions (FAQ) style, and is very good stuff. @@ -79,15 +79,15 @@ Foley, J., A. van Dam, S. Feiner, and J. Hughes, *Computer Graphics: Principles and Practice (Second Edition)*, Addison Wesley, 1990, pp. 555-557, 675-680. -Fuchs, H., Z. Kedem, and B. Naylor, “On Visible Surface Generation by A -Priori Tree Structures,” *Computer Graphics* Vol. 17(3), June 1980, pp. +Fuchs, H., Z. Kedem, and B. Naylor, "On Visible Surface Generation by A +Priori Tree Structures," *Computer Graphics* Vol. 17(3), June 1980, pp. 124-133. -Gordon, D., and S. Chen, “Front-to-Back Display of BSP Trees,” *IEEE +Gordon, D., and S. Chen, "Front-to-Back Display of BSP Trees," *IEEE Computer Graphics and Applications,* September 1991, pp. 79-85. -Naylor, B., “Binary Space Partitioning Trees as an Alternative -Representation of Polytopes,” *Computer Aided Design*, Vol. 22(4), May +Naylor, B., "Binary Space Partitioning Trees as an Alternative +Representation of Polytopes," *Computer Aided Design*, Vol. 22(4), May 1990, pp. 250-253. ------------------------ --------------------------------- -------------------- diff --git a/60-01.md b/60-01.md index f877303..75e0190 100644 --- a/60-01.md +++ b/60-01.md @@ -9,88 +9,88 @@ Chapter 60\ ### Taking BSP Trees from Concept to Reality {#Heading2} As long-time readers of my columns know, I tend to move my family around -the country quite a bit. Change doesn’t come out of the blue, so there’s +the country quite a bit. Change doesn't come out of the blue, so there's some interesting history to every move, but the roots of the latest move go back even farther than usual. To wit: In 1986, just after we moved from Pennsylvania to California, I started -writing a column for *Programmer’s Journal*. I was paid peanuts for +writing a column for *Programmer's Journal*. I was paid peanuts for writing it, and I doubt if even 5,000 people saw some of the first issues the columns appeared in, but I had a lot of fun exploring fast graphics for the EGA and VGA. By 1991, we were in Vermont, and I was writing the *Graphics -Programming* column for *Dr. Dobb’s Journal* (and having a great time +Programming* column for *Dr. Dobb's Journal* (and having a great time doing it, even though it took all my spare nights and weekends to stay ahead of the deadlines). In those days I received a lot of unsolicited evaluation software, including a PC shareware game called Commander Keen, a side-scrolling game that was every bit as good as the hot Nintendo games of the day. I loved the way the game looked, and actually -drafted a column opening about how for years I’d been claiming that the +drafted a column opening about how for years I'd been claiming that the PC could be a great game machine in the hands of great programmers, and here, finally, was the proof, in the form of Commander Keen. In the end, though, I decided that would be too close to a product review, an area -that I’ve observed inflames passions in nonconstructive ways, so I went +that I've observed inflames passions in nonconstructive ways, so I went with a different opening. In 1992, I did a series of columns about my X-Sharp 3-D library, and -hung out on *DDJ*’s bulletin board. There was another guy who hung out +hung out on *DDJ*'s bulletin board. There was another guy who hung out there who knew a lot about 3-D, a fellow named John Carmack who was -surely the only game programmer I’d ever heard of who developed under -NEXTSTEP. When we moved to Redmond, I didn’t have time for BBSs anymore, +surely the only game programmer I'd ever heard of who developed under +NEXTSTEP. When we moved to Redmond, I didn't have time for BBSs anymore, though. In early 1993, I hired Chris Hecker. Later that year, Chris showed me an alpha copy of DOOM, and I nearly fell out of my chair. About a year later, Chris forwarded me a newsgroup post about NEXTSTEP, and said, -“Isn’t this the guy you used to know on the *DDJ* bulletin board?” -Indeed it was John Carmack; what’s more, it turned out that John was the +"Isn't this the guy you used to know on the *DDJ* bulletin board?" +Indeed it was John Carmack; what's more, it turned out that John was the guy who had written DOOM. I sent him a congratulatory piece of mail, and he sent back some thoughts about what he was working on, and somewhere in there I asked if he ever came up my way. It turned out he had family in Seattle, so he stopped in and visited, and we had a great time. Over the next year, we exchanged some fascinating mail, and I became -steadily more impressed with John’s company, id Software. Eventually, -John asked if I’d be interested in joining id, and after a good bit of -consideration I couldn’t think of anything else that would be as much +steadily more impressed with John's company, id Software. Eventually, +John asked if I'd be interested in joining id, and after a good bit of +consideration I couldn't think of anything else that would be as much fun or teach me as much. The upshot is that here we all are in Dallas, -our fourth move of 2,000 miles or more since I’ve starting writing in -the computer field, and now I’m writing some seriously cool 3-D +our fourth move of 2,000 miles or more since I've starting writing in +the computer field, and now I'm writing some seriously cool 3-D software. -Now that I’m here, it’s an eye-opener to look back and see how events +Now that I'm here, it's an eye-opener to look back and see how events fit together over the last decade. You see, when John started doing PC game programming he learned fast graphics programming from those early -*Programmer’s Journal* articles of mine. The copy of Commander Keen that +*Programmer's Journal* articles of mine. The copy of Commander Keen that validated my faith in the PC as a game machine was the fruit of those -articles, for that was an id game (although I didn’t know that then). +articles, for that was an id game (although I didn't know that then). When John was hanging out on the *DDJ* BBS, he had just done Castle Wolfenstein 3-D, the first great indoor 3-D game, and was thinking about -how to do DOOM. (If only I’d known that then!) And had I not hired +how to do DOOM. (If only I'd known that then!) And had I not hired Chris, or had he not somehow remembered me talking about that guy who -used NEXTSTEP, I’d never have gotten back in touch with John, and things -would surely be different. (At the very least, I wouldn’t be hearing -jokes about how my daughter’s going to grow up saying “y’all”.) +used NEXTSTEP, I'd never have gotten back in touch with John, and things +would surely be different. (At the very least, I wouldn't be hearing +jokes about how my daughter's going to grow up saying "y'all".) -I think there’s a worthwhile lesson to be learned from all this, a -lesson that I’ve seen hold true for many other people, as well. If you +I think there's a worthwhile lesson to be learned from all this, a +lesson that I've seen hold true for many other people, as well. If you do what you love, and do it as well as you can, good things will eventually come of it. Not necessarily quickly or easily, but if you stick with it, they will come. There are threads that run through our -lives, and by the time we’ve been adults for a while, practically +lives, and by the time we've been adults for a while, practically everything that happens has roots that run far back in time. The implication should be clear: If you want good things to happen in your future, stretch yourself and put in the extra effort now at whatever you care passionately about, so those roots will have plenty to work with down the road. -All this is surprisingly closely related to this chapter’s topic, BSP +All this is surprisingly closely related to this chapter's topic, BSP trees, because John is the fellow who brought BSP trees into the spotlight by building DOOM around them. He also got me started with BSP trees by explaining how DOOM worked and getting me interested enough to want to experiment; the BSP compiler in this article is the direct -result. Finally, John has been an invaluable help to me as I’ve learned +result. Finally, John has been an invaluable help to me as I've learned about BSP trees, as will become evident when we discuss BSP optimization. @@ -98,9 +98,9 @@ Onward to compiling BSP trees. ### Compiling BSP Trees {#Heading3} -As you’ll recall from the previous chapter, a BSP tree is nothing more +As you'll recall from the previous chapter, a BSP tree is nothing more than a series of binary subdivisions that partion space into -ever-smaller pieces. That’s a simple data structure, and a BSP compiler +ever-smaller pieces. That's a simple data structure, and a BSP compiler is a correspondingly simple tool. First, it groups all the surfaces (lines in 2-D, or polygons in 3-D) together into a single subspace that encompasses the entire world of the database. Then, it chooses one of @@ -111,11 +111,11 @@ subspaces is then processed in the same fashion, and so on, recursively, until the point is reached where all surfaces have been assigned to nodes, and each leaf surface subdivides a subspace that is empty except for that surface. Put another way, the root node carves space into two -parts, and the root’s children carve each of those parts into two more +parts, and the root's children carve each of those parts into two more parts, and so on, with each surface carving ever smaller subspaces, until all surfaces have been used. (Actually, there are many other lines or planes that a BSP tree can use to carve up space, but this is the -approach we’ll use in the current discussion.) +approach we'll use in the current discussion.) ------------------------ --------------------------------- -------------------- [Previous](59-06.html) [Table of Contents](index.html) [Next](60-02.html) diff --git a/60-02.md b/60-02.md index c2629fc..772ab8b 100644 --- a/60-02.md +++ b/60-02.md @@ -5,26 +5,26 @@ If you find any of the above confusing (and it would be understandable if that were the case; BSP trees are not easy to get the hang of), you might want to refer back to the previous chapter. It would also be a -good idea to get hold of the visual BSP compiler I’ll discuss shortly; -when it comes to understanding BSP trees, there’s nothing quite like +good idea to get hold of the visual BSP compiler I'll discuss shortly; +when it comes to understanding BSP trees, there's nothing quite like seeing one being built. So there are really only two interesting operations in building a BSP -tree: choosing a root node for the current subspace (a “splitter”) and +tree: choosing a root node for the current subspace (a "splitter") and assigning surfaces to one side or another of the current root node, -splitting any that straddle the splitter. We’ll get to the issue of -choosing splitters shortly, but first let’s look at the process of +splitting any that straddle the splitter. We'll get to the issue of +choosing splitters shortly, but first let's look at the process of splitting and assigning. To do that, we need to understand parametric lines. #### Parametric Lines {#Heading4} -We’re all familiar with lines described in slope-intercept form, with y +We're all familiar with lines described in slope-intercept form, with y as a function of x y = mx + b -but there’s another sort of line description that’s very useful for +but there's another sort of line description that's very useful for clipping (and for a variety of 3-D purposes, such as curved surfaces and texture mapping): *parametric lines*. In parametric lines, x and y are decoupled from one another, and are instead described as a function of @@ -45,9 +45,9 @@ that this description is valid not only for the line segment, but also for the entire infinite line; however, only points with t values between 0 and 1 are actually on the line segment. -In our 2-D BSP compiler (as you’ll recall from the previous chapter, -we’re working with 2-D trees for simplicity, but the principles -generalize to 3-D), we’ll represent our walls (all vertical) as line +In our 2-D BSP compiler (as you'll recall from the previous chapter, +we're working with 2-D trees for simplicity, but the principles +generalize to 3-D), we'll represent our walls (all vertical) as line segments viewed from above. The segments will be stored in parametric form, with the endpoints of the original line segment and two t values describing the endpoints of the current (possibly clipped) segment @@ -56,7 +56,7 @@ providing a complete specification for each segment, as shown in Figure What does that do for us? For one thing, it keeps clipping errors from creeping in, because clipped line segments are always based on the -original line segment, not derived from clipped versions. Also, it’s +original line segment, not derived from clipped versions. Also, it's potentially a more compact format, because we need to store the endpoints only for the original line segments; for clipped line segments, we can just store pairs of t values, along with a pointer to @@ -86,24 +86,24 @@ where N is the normal of the splitter, S~start~ is the start point of the splitting line segment in standard (x,y) form, and L~start~ and L~end~ are the endpoints of the line segment being split, again in (x,y) form. Figure 60.3 illustrates the intersection calculation. Due to lack -of space, I’m just going to present this equation and its implications -as fact, rather than deriving them; if you want to know more, there’s an +of space, I'm just going to present this equation and its implications +as fact, rather than deriving them; if you want to know more, there's an excellent explanation on page 117 of *Computer Graphics: Principles and Practice,* by Foley and van Dam (Addison Wesley, ISBN 0-201-12110-7), a book that you should certainly have in your library. If the denominator is zero, we know that the lines are parallel and -don’t intersect, so we don’t divide, but rather check the sign of the +don't intersect, so we don't divide, but rather check the sign of the numerator, which tells us which side of the splitter the line segment is on. Otherwise, we do the division, and the result is the t value for the intersection point, as shown in Figure 60.3. We then simply compare the t value to the t values of the endpoints of the line segment being -split. If it’s between them, that’s where we split the line segment, +split. If it's between them, that's where we split the line segment, otherwise, we can tell which side of the splitter the line segment is on -by which side of the line segment’s t range it’s on. Simple comparisons -do all the work, and there’s no need to do the work of generating actual +by which side of the line segment's t range it's on. Simple comparisons +do all the work, and there's no need to do the work of generating actual x and y values. If you look closely at Listing 60.1, the core of the BSP -compiler, you’ll see that the parametric clipping code itself is +compiler, you'll see that the parametric clipping code itself is exceedingly short and simple. ![](images/60-03.jpg)\ @@ -112,9 +112,9 @@ exceedingly short and simple. One interesting point about Listing 60.1 is that it generates normals to splitting surfaces simply by exchanging the x and y lengths of the splitting line segment and negating the resultant y value, thereby -rotating the line 90 degrees. In 3-D, it’s not that simple to come by a +rotating the line 90 degrees. In 3-D, it's not that simple to come by a normal; you could calculate the normal as the cross-product of two of -the polygon’s edges, or precalculate it when you build the world +the polygon's edges, or precalculate it when you build the world database. #### The BSP Compiler {#Heading6} @@ -122,12 +122,12 @@ database. Listing 60.1 shows the core of a BSP compiler—the code that actually builds the BSP tree. (Note that Listing 60.1 is excerpted from a C++ .CPP file, but in fact what I show here is very close to straight C. It -may even compile as a .C file, though I haven’t checked.) The compiler +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 +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 diff --git a/60-03.md b/60-03.md index f302457..9210efc 100644 --- a/60-03.md +++ b/60-03.md @@ -33,7 +33,7 @@ static LINESEG *pCompiledLinesegs; // Builds a BSP tree from the specified line list. List must contain // at least one entry. If pCurrentTree is NULL, then this is the root - // node, otherwise pCurrentTree is the tree that’s been build so far. + // node, otherwise pCurrentTree is the tree that's been build so far. // Returns NULL for errors. LINESEG * SelectBSPTree(LINESEG * plineseghead, LINESEG * pCurrentTree, LINESEG ** pParentsChildPointer) @@ -45,7 +45,7 @@ LINESEG *pcurrentline; double nx, ny, numer, denom, t; // Pick a line as the root, and remove it from the list of lines - // to be categorized. The line we’ll select is the one of those in + // to be categorized. The line we'll select is the one of those in // the list that splits the fewest of the other lines in the list minsplits = MAX_INT; prootline = plineseghead; @@ -58,7 +58,7 @@ pvertexlist[prootline->endvertex].y; ny = -(pvertexlist[prootline->startvertex].x - pvertexlist[prootline->endvertex].x); - // Calculate the dot products we’ll need for line + // Calculate the dot products we'll need for line // intersection and spatial relationship numer = (nx * (pvertexlist[pcurrentline->startvertex].x - pvertexlist[prootline->startvertex].x)) + @@ -102,7 +102,7 @@ // appropriate pminsplit->pfronttree = NULL; pminsplit->pbacktree = NULL; - // Point the parent’s child pointer to this node, so we can + // Point the parent's child pointer to this node, so we can // track the currently-build tree *pParentsChildPointer = pminsplit; return BuildBSPTree(plineseghead, pminsplit, pCurrentTree); @@ -119,9 +119,9 @@ LINESEG *psplitline; double nx, ny, numer, denom, t; int Done; - // Categorize all non-root lines as either in front of the root’s - // infinite line, behind the root’s infinite line, or split by the - // root’s infinite line, in which case we split it into two lines + // Categorize all non-root lines as either in front of the root's + // infinite line, behind the root's infinite line, or split by the + // root's infinite line, in which case we split it into two lines pfrontlines = NULL; pbacklines = NULL; pcurrentline = plineseghead; @@ -135,7 +135,7 @@ pvertexlist[prootline->endvertex].y; ny = -(pvertexlist[prootline->startvertex].x - pvertexlist[prootline->endvertex].x); - // Calculate the dot products we’ll need for line intersection + // Calculate the dot products we'll need for line intersection // and spatial relationship numer = (nx * (pvertexlist[pcurrentline->startvertex].x - pvertexlist[prootline->startvertex].x)) + @@ -174,8 +174,8 @@ // The line segment must be split; add one split // segment to each list if (NumCompiledLinesegs > (MAX_NUM_LINESEGS - 1)) { - DisplayMessageBox(“Out of space for line segs;” - “increase MAX_NUM_LINESEGS”); + DisplayMessageBox("Out of space for line segs;" + "increase MAX_NUM_LINESEGS"); return NULL; } // Make a new line entry for the split part of line diff --git a/60-04.md b/60-04.md index 3388d54..9bb85cc 100644 --- a/60-04.md +++ b/60-04.md @@ -2,41 +2,41 @@ [Previous](60-03.html) [Table of Contents](index.html) [Next](61-01.html) ------------------------ --------------------------------- -------------------- -Listing 60.1 isn’t very long or complex, but it’s somewhat more -complicated than it could be because it’s structured to allow visual -display of the ongoing compilation process. That’s because Listing 60.1 +Listing 60.1 isn't very long or complex, but it's somewhat more +complicated than it could be because it's structured to allow visual +display of the ongoing compilation process. That's because Listing 60.1 is actually just a part of a BSP compiler for Win32 that visually depicts the progressive subdivision of space as the BSP tree is built. (Note that Listing 60.1 might not compile as printed; I may have missed copying some global variables that it uses.) The complete code is too -large to print here in its entirety, but it’s on the CD-ROM in file +large to print here in its entirety, but it's on the CD-ROM in file DDJBSP.ZIP. ### Optimizing the BSP Tree {#Heading7} -In the previous chapter, I promised that I’d discuss how to go about +In the previous chapter, I promised that I'd discuss how to go about deciding which wall to use as the splitter at each node in constructing a BSP tree. That turns out to be a far more difficult problem than one -might think, but we can’t ignore it, because the choice of splitter can +might think, but we can't ignore it, because the choice of splitter can make a huge difference. Consider, for example, a BSP in which the line or plane of the splitter at the root node splits every single other surface in the world, doubling the total number of surfaces to be dealt with. Contrast that with a BSP built from the same surface set in which the initial splitter -doesn’t split anything. Both trees provide a valid ordering, but one +doesn't split anything. Both trees provide a valid ordering, but one tree is much larger than the other, with twice as many polygons after the selection of just one node. Apply the same difference again to each node, and the relative difference in size (and, correspondingly, in traversal and rendering time) soon balloons astronomically. So we need to do *something* to optimize the BSP tree—but what? Before we can try -to answer that, we need to know exactly what we’d like to optimize. +to answer that, we need to know exactly what we'd like to optimize. There are several possible optimization objectives in BSP compilation. We might choose to balance the tree as evenly as possible, thereby reducing the average depth to which the tree must be traversed. Alternatively, we might try to approximately balance the area or volume -on either side of each splitter. That way we don’t end up with huge +on either side of each splitter. That way we don't end up with huge chunks of space in some tree branches and tiny slivers in others, and the overall processing time will be more consistent. Or, we might choose to select planes aligned with the major axes, because such planes can @@ -46,36 +46,36 @@ The BSP metric that seems most useful to me, however, is the number of polygons that are split into two polygons in the course of building a BSP tree. Fewer splits is better; the tree is smaller with fewer polygons, and drawing will go faster with fewer polygons to draw, due to -per-polygon overhead. There’s a problem with the fewest-splits metric, -though: There’s no sure way to achieve it. +per-polygon overhead. There's a problem with the fewest-splits metric, +though: There's no sure way to achieve it. The obvious approach to minimizing polygon splits would be to try all possible trees to find the best one. Unfortunately, the order of that particular problem is N!, as I found to my dismay when I implemented brute-force optimization in the first version of my BSP compiler. Take a moment to calculate the number of operations for the 20-polygon set I -originally tried brute-force optimization on. I’ll give you a hint: +originally tried brute-force optimization on. I'll give you a hint: There are 19 digits in 20!, and if each operation takes only one -microsecond, that’s over 70,000 years (or, if you prefer, over 500,000 +microsecond, that's over 70,000 years (or, if you prefer, over 500,000 dog years). Now consider that a single game level might have 5,000 to -10,000 polygons; there aren’t anywhere near enough dog years in the -lifetime of the universe to handle that. We’re going to have to give up +10,000 polygons; there aren't anywhere near enough dog years in the +lifetime of the universe to handle that. We're going to have to give up on optimal compilation and come up with a decent heuristic approach, no matter what optimization objective we select. -In Listing 60.1, I’ve applied the popular heuristic of choosing as the +In Listing 60.1, I've applied the popular heuristic of choosing as the splitter at each node the surface that splits the fewest of the other surfaces that are being considered for that node. In other words, I -choose the wall that splits the fewest of the walls in the subspace it’s +choose the wall that splits the fewest of the walls in the subspace it's subdividing. ### BSP Optimization: an Undiscovered Country {#Heading8} -Although BSP trees have been around for at least 15 years now, they’re +Although BSP trees have been around for at least 15 years now, they're still only partially understood and are a ripe area for applied research and general ingenuity. You might want to try your hand at inventing new -BSP optimization approaches; it’s an interesting problem, and you might -strike paydirt. There are many things that BSP trees can’t do well, +BSP optimization approaches; it's an interesting problem, and you might +strike paydirt. There are many things that BSP trees can't do well, because it takes so long to build them—but what they do, they do exceedingly well, so a better compilation approach that allowed BSP trees to be used for more purposes would be valuable, indeed. diff --git a/61-01.md b/61-01.md index 311dfbc..f525c86 100644 --- a/61-01.md +++ b/61-01.md @@ -8,41 +8,41 @@ Chapter 61\ ### The Fundamentals of the Math behind 3-D Graphics {#Heading2} -Several years ago, I opened a column in *Dr. Dobb’s Journal* with a -story about singing my daughter to sleep with Beatles’ songs. Beatles’ +Several years ago, I opened a column in *Dr. Dobb's Journal* with a +story about singing my daughter to sleep with Beatles' songs. Beatles' songs, at least the earlier ones, tend to be bouncy and pleasant, which makes them suitable goodnight fodder—and there are a *lot* of them, a -useful hedge against terminal boredom. So for many good reasons, “Can’t -Buy Me Love” and “A Hard Day’s Night” and “Help!” and the rest were +useful hedge against terminal boredom. So for many good reasons, "Can't +Buy Me Love" and "A Hard Day's Night" and "Help!" and the rest were evening staples for years. No longer, though. You see, I got my wife some Beatles tapes for -Christmas, and we’ve all been listening to them in the car, and now that +Christmas, and we've all been listening to them in the car, and now that my daughter has heard the real thing, she can barely stand to be in the same room, much less fall asleep, when I sing those songs. -What’s noteworthy is that the only variable involved in this change was -my daughter’s frame of reference. My singing hasn’t gotten any worse -over the last four years. (I’m not sure it’s *possible* for my singing -to get worse.) All that changed was my daughter’s frame of reference for +What's noteworthy is that the only variable involved in this change was +my daughter's frame of reference. My singing hasn't gotten any worse +over the last four years. (I'm not sure it's *possible* for my singing +to get worse.) All that changed was my daughter's frame of reference for those songs. The rest of the universe stayed the same; the change was in her mind, lock, stock, and barrel. Often, the key to solving a problem, or to working on a problem efficiently, is having a proper frame of reference. The model you have -of a problem you’re tackling often determines how deeply you can -understand the problem, and how flexible and innovative you’ll be able +of a problem you're tackling often determines how deeply you can +understand the problem, and how flexible and innovative you'll be able to be in solving it. -An excellent example of this, and one that I’ll discuss toward the end +An excellent example of this, and one that I'll discuss toward the end of this chapter, is that of *3-D transformation*—the process of converting coordinates from one coordinate space to another, for example from worldspace to viewspace. The way this is traditionally explained is functional, but not particularly intuitive, and fairly hard to -visualize. Recently, I’ve come across another way of looking at +visualize. Recently, I've come across another way of looking at transforms that seems to me to be far easier to grasp. The two approaches are technically equivalent, so the difference is purely a -matter of how we choose to view things—but sometimes that’s the most +matter of how we choose to view things—but sometimes that's the most important sort of difference. Before we can talk about transforming between coordinate spaces, @@ -53,47 +53,47 @@ however, we need two building blocks: dot products and cross products. At this point in the book, I was originally going to present a BSP-based renderer, to complement the BSP compiler I presented in the previous chapter. What changed my plans was the considerable amount of mail about -3-D math that I’ve gotten in recent months. In every case, the writer +3-D math that I've gotten in recent months. In every case, the writer has bemoaned his/her lack of expertise with 3-D math, and has asked what -books about 3-D math I’d recommend, and how else he/she could learn +books about 3-D math I'd recommend, and how else he/she could learn more. -That’s a commendable attitude, but the truth is, there’s not all that +That's a commendable attitude, but the truth is, there's not all that much to 3-D math, at least not when it comes to the sort of -polygon-based, realtime 3-D that’s done on PCs. You really need only two +polygon-based, realtime 3-D that's done on PCs. You really need only two basic math tools beyond simple arithmetic: dot products and cross products, and really mostly just the former. My friend Chris Hecker points out that this is an oversimplification; he notes that lots more math-related stuff, like BSP trees, graphs, discrete math for edge stepping, and affine and perspective texture mappings, goes into a -production-quality game. While that’s surely true, dot and cross +production-quality game. While that's surely true, dot and cross products, together with matrix math and perspective projection, constitute the bulk of what most people are asking about when they -inquire about “3-D math,” and, as we’ll see, are key tools for a lot of +inquire about "3-D math," and, as we'll see, are key tools for a lot of useful 3-D operations. The other thing the mail made clear was that there are a lot of people -out there who don’t understand either type of product, at least insofar +out there who don't understand either type of product, at least insofar as they apply to 3-D. Since much or even most advanced 3-D graphics machinery relies to a greater or lesser extent on dot products and cross products (even the line intersection formula I discussed in the last -chapter is actually a quotient of dot products), I’m going to spend this +chapter is actually a quotient of dot products), I'm going to spend this chapter examining these basic tools and some of their 3-D applications. -If this is old hat to you, my apologies, and I’ll return to BSP-based +If this is old hat to you, my apologies, and I'll return to BSP-based rendering in the next chapter. #### Foundation Definitions {#Heading4} The dot and cross products themselves are straightforward and require -almost no context to understand, but I need to define some terms I’ll -use when describing applications of the products, so I’ll do that now, +almost no context to understand, but I need to define some terms I'll +use when describing applications of the products, so I'll do that now, and then get started with dot products. -I’m going to have to assume you have *some* math background, or we’ll -never get to the good stuff. So, I’m just going to quickly define a +I'm going to have to assume you have *some* math background, or we'll +never get to the good stuff. So, I'm just going to quickly define a *vector* as a direction and a magnitude, represented as a coordinate -pair (in 2-D) or triplet (in 3-D), relative to the origin. That’s a -pretty sloppy definition, but it’ll do for our purposes; if you want the +pair (in 2-D) or triplet (in 3-D), relative to the origin. That's a +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). diff --git a/61-02.md b/61-02.md index 256f01f..d5cd2a1 100644 --- a/61-02.md +++ b/61-02.md @@ -2,7 +2,7 @@ [Previous](61-01.html) [Table of Contents](index.html) [Next](61-03.html) ------------------------ --------------------------------- -------------------- -I’ll be working in a left-handed coordinate system, whereby if you wrap +I'll be working in a left-handed coordinate system, whereby if you wrap the fingers of your left hand around the z axis with your thumb pointing in the positive z direction, your fingers will curl from the positive x axis to the positive y axis. The positive x axis runs left to right @@ -15,7 +15,7 @@ coordinates onto a viewplane, scaling coordinates according to their z distance from the viewpoint in order to provide proper perspective. *Objectspace* is the coordinate space in which an object is defined, independent of other objects and the world itself. *Worldspace* is the -absolute frame of reference for a 3-D world; all objects’ locations and +absolute frame of reference for a 3-D world; all objects' locations and orientations are with respect to worldspace, and this is the frame of reference around which the viewpoint and view direction move. *Viewspace* is worldspace as seen from the viewpoint, looking in the @@ -23,16 +23,16 @@ view direction. *Screenspace* is viewspace after perspective projection and scaling to the screen. Finally, *transformation* is the process of converting points from one -coordinate space into another; in our case, that’ll mean rotating and +coordinate space into another; in our case, that'll mean rotating and translating (moving) points from objectspace or worldspace to viewspace. For additional information, you might want to check out Foley & van -Dam’s *Computer Graphics* (ISBN 0-201-12110-7), or the chapters in this +Dam's *Computer Graphics* (ISBN 0-201-12110-7), or the chapters in this 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** = +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: @@ -44,7 +44,7 @@ As you can see, the result is a scalar value (a single real-valued number), *not* another vector. Now that we know how to calculate a dot product, what does that get us? -Not much. The dot product isn’t of much use for graphics until you start +Not much. The dot product isn't of much use for graphics until you start thinking of it this way ![](images/61-03d.jpg) @@ -52,7 +52,7 @@ thinking of it this way (eq. 3) where q is the angle between the two vectors, and the other two terms -are the lengths of the vectors, as shown in Figure 61.1. Although it’s +are the lengths of the vectors, as shown in Figure 61.1. Although it's not immediately obvious, equation 3 has a wide variety of applications in 3-D graphics. @@ -73,7 +73,7 @@ One obvious use of this is to find angles between unit vectors, in conjunction with an inverse cosine function or lookup table. A more useful application in 3-D graphics lies in lighting surfaces, where the cosine of the angle between incident light and the normal (perpendicular -vector) of a surface determines the fraction of the light’s full +vector) of a surface determines the fraction of the light's full intensity at which the surface is illuminated, as in ![](images/61-05d.jpg) @@ -101,7 +101,7 @@ direction vector, as shown in Figure 61.2. One question equation 6 begs is where the surface unit normal comes from. One approach is to store the end of a surface normal as an extra -data point with each polygon (with the start being some point that’s +data point with each polygon (with the start being some point that's already in the polygon), and transform it along with the rest of the points. This has the advantage that if the normal starts out as a unit normal, it will end up that way too, if only rotations and translations diff --git a/61-03.md b/61-03.md index 58077ec..b30df27 100644 --- a/61-03.md +++ b/61-03.md @@ -3,21 +3,21 @@ ------------------------ --------------------------------- -------------------- Why does this matter? It matters because, on average, half the polygons -in any scene are facing away from the viewer, and hence shouldn’t be -drawn. One way to identify such polygons is to see whether they’re +in any scene are facing away from the viewer, and hence shouldn't be +drawn. One way to identify such polygons is to see whether they're facing toward or away from the viewer; that is, whether their normals -have negative z values (so they’re visible) or positive z values (so -they should be culled). However, we’re talking about screenspace normals +have negative z values (so they're visible) or positive z values (so +they should be culled). However, we're talking about screenspace normals here, because the perspective projection can shift a polygon relative to the viewpoint so that although its viewspace normal has a negative z, its screenspace normal has a positive z, and vice-versa, as shown in -Figure 61.3. So we need screenspace normals, but those can’t readily be +Figure 61.3. So we need screenspace normals, but those can't readily be generated by transformation from worldspace. ![](images/61-03.jpg)\ **Figure 61.3**  *A problem with determining front/back visibility.* -The solution is to use the cross product of two of the polygon’s edges +The solution is to use the cross product of two of the polygon's edges to generate a normal. The formula for the cross product is: ![](images/61-07d.jpg) @@ -34,29 +34,29 @@ screenspace normals we need by taking the cross product of two adjacent polygon edges, as shown in Figure 61.4. ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *In fact, we can cull with only one-third the work needed to generate a full cross product; because we’re interested only in the sign of the z component of the normal, we can skip entirely calculating the x and y components. The only caveat is to be careful that neither edge you choose is zero-length and that the edges aren’t collinear, because the dot product can’t produce a normal in those cases.* + ![](images/i.jpg) *In fact, we can cull with only one-third the work needed to generate a full cross product; because we're interested only in the sign of the z component of the normal, we can skip entirely calculating the x and y components. The only caveat is to be careful that neither edge you choose is zero-length and that the edges aren't collinear, because the dot product can't produce a normal in those cases.* ------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ![](images/61-04.jpg)\ **Figure 61.4**  *How the cross product of polygon edge vectors generates a polygon normal.* -Perhaps the most often asked question about cross products is “Which way -do normals generated by cross products go?” In a left-handed coordinate +Perhaps the most often asked question about cross products is "Which way +do normals generated by cross products go?" In a left-handed coordinate system, curl the fingers of your left hand so the fingers curl through an angle of less than 180 degrees from the first vector in the cross product to the second vector. Your thumb now points in the direction of the normal. If you take the cross product of two orthogonal (right-angle) unit -vectors, the result will be a unit vector that’s orthogonal to both of -them. This means that if you’re generating a new coordinate space—such +vectors, the result will be a unit vector that's orthogonal to both of +them. This means that if you're generating a new coordinate space—such as a new viewing frame of reference—you only need to come up with unit vectors for two of the axes for the new coordinate space, and can then use their cross product to generate the unit vector for the third axis. -If you need unit normals, and the two vectors being crossed aren’t -orthogonal unit vectors, you’ll have to normalize the resulting vector; -that is, divide each of the vector’s components by the length of the +If you need unit normals, and the two vectors being crossed aren't +orthogonal unit vectors, you'll have to normalize the resulting vector; +that is, divide each of the vector's components by the length of the vector, to make it a unit long. ### Using the Sign of the Dot Product {#Heading8} @@ -65,10 +65,10 @@ The dot product is the cosine of the angle between two vectors, scaled by the magnitudes of the vectors. Magnitudes are always positive, so the sign of the cosine determines the sign of the result. The dot product is positive if the angle between the vectors is less than 90 degrees, -negative if it’s greater than 90 degrees, and zero if the angle is +negative if it's greater than 90 degrees, and zero if the angle is exactly 90 degrees. This means that just the sign of the dot product suffices for tests involving comparisons of angles to 90 degrees, and -there are more of those than you’d think. +there are more of those than you'd think. Consider, for example, the process of backface culling, which we discussed above in the context of using screenspace normals to determine @@ -81,12 +81,12 @@ in objectspace, if we transform the viewpoint into that frame of reference), given only a vertex and a normal for each polygon and a location for the viewer. -Here’s the trick: Calculate the vector from the viewpoint to any vertex -in the polygon and take its dot product with the polygon’s normal, as +Here's the trick: Calculate the vector from the viewpoint to any vertex +in the polygon and take its dot product with the polygon's normal, as shown in Figure 61.5. If the polygon is facing the viewpoint, the result is negative, because the angle between the two vectors is greater than 90 degrees. If the polygon is facing away, the result is positive, and -if the polygon is edge-on, the result is 0. That’s all there is to +if the polygon is edge-on, the result is 0. That's all there is to it—and this sort of backface culling happens before any transformation or projection at all is performed, saving a great deal of work for the half of all polygons, on average, that are culled. @@ -95,7 +95,7 @@ Backface culling with the dot product is just a special case of determining which side of a plane any point (in this case, the viewpoint) is on. The same trick can be applied whenever you want to determine whether a point is in front of or behind a plane, where a -plane is described by any point that’s on the plane (which I’ll call the +plane is described by any point that's on the plane (which I'll call the plane origin), plus a plane normal. One such application is in clipping a line (such as a polygon edge) to a plane. Just do a dot product between the plane normal and the vector from one line endpoint to the diff --git a/61-04.md b/61-04.md index be276ec..2caa62b 100644 --- a/61-04.md +++ b/61-04.md @@ -38,7 +38,7 @@ to the plane, as just described, and dot the whole line segment with the plane normal, to get the full length of the line along the plane normal. The ratio of the two dot products is then how far along the line from the endpoint the intersection point is; just move along the line segment -by that distance from the endpoint, and you’re at the intersection +by that distance from the endpoint, and you're at the intersection point, as shown in Listing 61.1. **LISTING 61.1 L61\_1.C** @@ -75,13 +75,13 @@ point, as shown in Listing 61.1. ### Rotation by Projection {#Heading10} -We can use the dot product’s projection capability to look at rotation +We can use the dot product's projection capability to look at rotation in an interesting way. Typically, rotations are represented by matrices. This is certainly a workable representation that encapsulates all aspects of transformation in a single object, and is ideal for concatenations of rotations and translations. One problem with matrices, though, is that many people, myself included, have a hard time looking -at a matrix of sines and cosines and visualizing what’s actually going +at a matrix of sines and cosines and visualizing what's actually going on. So when two 3-D experts, John Carmack and Billy Zelsnack, mentioned that they think of rotation differently, in a way that seemed more intuitive to me, I thought it was worth passing on. @@ -101,12 +101,12 @@ project the point onto each axis. Translation can be done separately from rotation by simple addition. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *Rotation by projection is exactly the same as rotation via matrix multiplication; in fact, the rows of a rotation matrix are the orthogonal unit vectors pointing along the new axes. Rotation by projection buys us no technical advantages, so that’s not what’s important here; the key is that the concept of rotation by projection, together with a separate translation step, gives us a new way to look at transformation that I, for one, find easier to visualize and experiment with. A new frame of reference for how we think about 3-D frames of reference, if you will.* + ![](images/i.jpg) *Rotation by projection is exactly the same as rotation via matrix multiplication; in fact, the rows of a rotation matrix are the orthogonal unit vectors pointing along the new axes. Rotation by projection buys us no technical advantages, so that's not what's important here; the key is that the concept of rotation by projection, together with a separate translation step, gives us a new way to look at transformation that I, for one, find easier to visualize and experiment with. A new frame of reference for how we think about 3-D frames of reference, if you will.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -Three things I’ve learned over the years are that it never hurts to +Three things I've learned over the years are that it never hurts to learn a new way of looking at things, that it helps to have a clearer, -more intuitive model in your head of whatever it is you’re working on, +more intuitive model in your head of whatever it is you're working on, and that new tools, or new ways to use old tools, are Good Things. My experience has been that rotation by projection, and dot product tricks in general, offer those sorts of benefits for 3-D. diff --git a/62-01.md b/62-01.md index fb2c2c2..f386ab5 100644 --- a/62-01.md +++ b/62-01.md @@ -8,7 +8,7 @@ Chapter 62\ ### Taking a Compiled BSP Tree from Logical to Visual Reality {#Heading2} -As I’ve noted before, I’m working on Quake, id Software’s follow-up to +As I've noted before, I'm working on Quake, id Software's follow-up to DOOM. A month or so back, we added page flipping to Quake, and made the startling discovery that the program ran nearly twice as fast with page flipping as it did with the alternative method of drawing the whole @@ -19,11 +19,11 @@ poor main memory performance, and cache misses when copying the frame from system memory to video memory. Although each of these can indeed affect performance, none seemed to account for the magnitude of the speedup, so I assumed there was some hidden hardware interaction at -work. Anyway, “why” was secondary; what really mattered was that we had +work. Anyway, "why" was secondary; what really mattered was that we had a way to double performance, which meant I had a lot of work to do to support page flipping as widely as possible. -A few days ago, I was using the Pentium’s built-in performance counters +A few days ago, I was using the Pentium's built-in performance counters 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 @@ -43,40 +43,40 @@ routine, the non-page-flipped approach suddenly became slightly *faster* than page flipping. The first relevant rule is pretty obvious: *Assume nothing*. Measure -early and often. Know what’s really going on when your program runs, if +early and often. Know what's really going on when your program runs, if you catch my drift. To do otherwise is to risk looking mighty foolish. 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 +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 should have done experiments until I was sure I knew what was going on before drawing any conclusions and acting on them. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - ![](images/i.jpg) *In general, make it a point not to fall into a tightly focused rut; stay loose and think of alternative possibilities and new approaches, and 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()** problem—and by so doing I doubled the performance of the entire program in five minutes, a rare accomplishment indeed.* + ![](images/i.jpg) *In general, make it a point not to fall into a tightly focused rut; stay loose and think of alternative possibilities and new approaches, and 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()** problem—and by so doing I doubled the performance of the entire program in five minutes, a rare accomplishment indeed.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -By the way, I have found the Pentium’s performance counters to be very +By the way, I have found the Pentium's performance counters to be very useful in of information on the performance counters and other aspects -of the Pentium is Mike Schmit’s book, *Pentium Processor Optimization +of the Pentium is Mike Schmit's book, *Pentium Processor Optimization Tools*, AP Professional, ISBN 0-12-627230-1. Onward to rendering from a BSP tree. #### BSP-based Rendering {#Heading3} -For the last several chapters I’ve been discussing the nature of BSP +For the last several chapters I've been discussing the nature of BSP (Binary Space Partitioning) trees, and in Chapter 60 I presented a -compiler for 2-D BSP trees. Now we’re ready to use those compiled BSP +compiler for 2-D BSP trees. Now we're ready to use those compiled BSP trees to do realtime rendering. -As you’ll recall, the BSP compiler took a list of vertical walls and +As you'll recall, the BSP compiler took a list of vertical walls and built a 2-D BSP tree from the walls, as viewed from above. The result is shown in Figure 62.1. The world is split into two pieces by the line of the root wall, and each half of the world is then split again by the -root’s children, and so on, until the world is carved into subspaces +root's children, and so on, until the world is carved into subspaces along the lines of all the walls. ![](images/62-01.jpg)\ @@ -84,14 +84,14 @@ along the lines of all the walls. Our objective is to draw the world so that whenever walls overlap we see the nearer wall at each overlapped pixel. The simplest way to do that is -with the painter’s algorithm; that is, drawing the walls in +with the painter's algorithm; that is, drawing the walls in back-to-front order, assuming no polygons interpenetrate or form cycles. BSP trees guarantee that no polygons interpenetrate (such polygons are automatically split), and make it easy to walk the polygons in back-to-front (or front-to-back) order. Given a BSP tree, in order to render a view of that tree, all we have to -do is descend the tree, deciding at each node whether we’re seeing the +do is descend the tree, deciding at each node whether we're seeing the front or back of the wall at that node from the current viewpoint. We use that knowledge to first recursively descend and draw the farther subtree of that node, then draw that node, and finally draw the nearer diff --git a/62-02.md b/62-02.md index c7555c7..57de319 100644 --- a/62-02.md +++ b/62-02.md @@ -43,7 +43,7 @@ FIXEDPOINT screenytopstart, screenybottomstart; FIXEDPOINT screenytopend, screenybottomend; } NODE, *PNODE; - char * pDIB; // pointer to DIB section we’ll draw into + char * pDIB; // pointer to DIB section we'll draw into HBITMAP hDIBSection; // handle of DIB section HPALETTE hpalDIB; int iteration = 0, WorldIsRunning = 1; @@ -158,7 +158,7 @@ VERTEX *pextravertex = pextravertexlist; pwall = pnodelist; for (wall = 0; wall < numnodes; wall++) { - // Assume the wall won’t be visible + // Assume the wall won't be visible pwall->isVisible = 0; // Generate the wall endpoints, accounting for t values and // clipping @@ -299,7 +299,7 @@ // Outside view triangle, trivially clipped goto NextWall; } - // Partially clipped in Y; we’ll do Y clipping at + // Partially clipped in Y; we'll do Y clipping at // drawing time } // The wall is visible; mark it as such and project it. @@ -349,15 +349,15 @@ // remembering the nodes we pass through on the way. // Figure whether this wall is facing frontward or // backward; do in viewspace because non-visible walls - // aren’t projected into screenspace, and we need to + // aren't projected into screenspace, and we need to // traverse all walls in the BSP tree, visible or not, // in order to find all the visible walls if (WallFacingViewer(pwall)) { - // We’re on the forward side of this wall, do the back + // We're on the forward side of this wall, do the back // children first pFarChildren = pwall->backtree; } else { - // We’re on the back side of this wall, do the front + // We're on the back side of this wall, do the front // children first pFarChildren = pwall->fronttree; } @@ -384,21 +384,21 @@ FillConvexPolygon(apoint, pwall->color); } } - // If there’s a near tree from this node, draw it; + // If there's a near tree from this node, draw it; // otherwise, work back up to the last-pushed parent - // node of the branch we just finished; we’re done if + // node of the branch we just finished; we're done if // there are no pending parent nodes. // Figure whether this wall is facing frontward or // backward; do in viewspace because non-visible walls - // aren’t projected into screenspace, and we need to + // aren't projected into screenspace, and we need to // traverse all walls in the BSP tree, visible or not, // in order to find all the visible walls if (WallFacingViewer(pwall)) { - // We’re on the forward side of this wall, do the + // We're on the forward side of this wall, do the // front children now pNearChildren = pwall->fronttree; } else { - // We’re on the back side of this wall, do the back + // We're on the back side of this wall, do the back // children now pNearChildren = pwall->backtree; } @@ -429,7 +429,7 @@ TransformVertices(); ClipWalls(); DrawWallsBackToFront(); - // We’ve drawn the frame; copy it to the screen + // We've drawn the frame; copy it to the screen hdcScreen = GetDC(hwndOutput); holdpal = SelectPalette(hdcScreen, hpalDIB, FALSE); RealizePalette(hdcScreen); diff --git a/62-03.md b/62-03.md index e0d5738..12dcf51 100644 --- a/62-03.md +++ b/62-03.md @@ -15,9 +15,9 @@ as coordinated by **UpdateWorld()**, is this: - Project wall vertices to screen coordinates. - Walk the walls back to front, and for each wall that lies at least partially in the view pyramid, perform backface culling (skip walls - facing away from the viewer), and draw the wall if it’s not culled. + facing away from the viewer), and draw the wall if it's not culled. -Next, we’ll look at each part of the pipeline more closely. The pipeline +Next, we'll look at each part of the pipeline more closely. The pipeline is too complex for me to be able to discuss each part in complete detail. Some sources for further reading are *Computer Graphics*, by Foley and van Dam (ISBN 0-201-12110-7), and the *DDJ Essential Books on @@ -27,7 +27,7 @@ Graphics Programming* CD. 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 +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. @@ -36,7 +36,7 @@ around the but only viewing horizontally. Although the BSP tree is only 2-D, it is quite possible to support looking up and down to at least some extent, particularly if the world dataset is restricted so that, for example, there are never two rooms stacked on top of each other, or -any tilted walls. For simplicity’s sake, I have chosen not to implement +any tilted walls. For simplicity's sake, I have chosen not to implement this in Listing 62.1, but you may find it educational to add it to the program yourself. @@ -53,9 +53,9 @@ 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 +the axes of the new coordinate system, and that's what **TransformVertices()** does, after first translating (moving) the -coordinate system to have its origin at the viewpoint. (It’s necessary +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 matrix math operation, and that this is in fact the more common way to @@ -82,8 +82,8 @@ 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 coordinates.) If both ends are on the near side of the front clip plane, -then the polygon is fully clipped, and we’re done with it. If both ends -are on the far side, then the polygon isn’t z-clipped, and we leave it +then the polygon is fully clipped, and we're done with it. If both ends +are on the far side, then the polygon isn't z-clipped, and we leave it unchanged. If the polygon straddles the near clip plane, then the wall is trimmed to stop at the near clip plane by adjusting the t value of the nearest endpoint appropriately; this calculation is a simple matter @@ -99,7 +99,7 @@ the screen. Like z-clipping, x-clipping can be done as a 2-D clip, because the walls and the left and right sides of the frustum are all vertical. We compare both the start and endpoint of each wall to the left and right sides of the frustum, and reject, accept, or clip each -wall’s t values accordingly. The test for x clipping is very simple, +wall's t values accordingly. The test for x clipping is very simple, because the edges of the frustum are defined as the planes where x==z and -x==z. @@ -114,15 +114,15 @@ 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 near clip plane guarantees that no remaining polygon point can have -z\<=0, ensuring that when we project we’ll always pass valid, +z\<=0, ensuring that when we project we'll always pass valid, y-clippable screenspace vertices to the polygon filler. ### Projection to Screenspace {#Heading8} -At this point, we have viewspace vertices for each wall that’s at least +At this point, we have viewspace vertices for each wall that's at least 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. +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()**. diff --git a/62-04.md b/62-04.md index 0c47167..5187d69 100644 --- a/62-04.md +++ b/62-04.md @@ -6,7 +6,7 @@ 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 @@ -22,14 +22,14 @@ 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 presented in Chapters 38 and 39. -It’s worth noting how backface culling and front/back wall orientation +It's worth noting how backface culling and front/back wall orientation testing are performed. (Note that walls are always one-sided, visible only from the front.) I discussed backface culling in general in the previous chapter, and mentioned two possible approaches: generating a screenspace normal (perpendicular vector) to the polygon and seeing which way that points, or taking the world or screenspace dot product between the vector from the viewpoint to any polygon point and the -polygon’s normal and checking the sign. Listing 62.1 does both, but +polygon's normal and checking the sign. Listing 62.1 does both, but because our BSP tree is 2-D and the viewer is always upright, we can save some work. @@ -39,17 +39,17 @@ end vertex. There are only two possible ways that a wall can be positioned in screenspace, then: viewed from the front, in which case the start vertex is to the left of the end vertex, or viewed from the back, in which case the start vertex is to the right of the end vertex, -as shown in Figure 62.4. So we can tell which side of a wall we’re +as shown in Figure 62.4. So we can tell which side of a wall we're seeing, and thus backface cull, simply by comparing the screenspace x 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 -viewspace sign of the dot product of the wall’s normal with a vector +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 -and altering signs. We can’t use the quicker screenspace x test here +and altering signs. We can't use the quicker screenspace x test here that we used for backface culling, because not all walls can be projected into screenspace; for example, trying to project a wall at z==0 would result in division by zero. @@ -68,7 +68,7 @@ a tiny BSP rendering demo, because 3D rendering, even when based on a 2-D BSP tree, requires a substantial amount of code and complexity. Listing 62.1 is reasonably close to a minimum rendering engine, and is specifically intended to illuminate basic BSP principles, given the -space limitations of one chapter in a book that’s already larger than it +space limitations of one chapter in a book that's already larger than it should be. Think of Listing 62.1 as a learning tool and a starting point. @@ -78,9 +78,9 @@ necessary to go to 3-D BSP trees to get a normal-looking world? No. Although 3-D BSP trees offer many advantages in that they allow arbitrary datasets with viewing in any arbitrary direction and, in -truth, aren’t much more complicated than 2-D BSP trees for back-to-front +truth, aren't much more complicated than 2-D BSP trees for back-to-front drawing, they do tend to be larger and more difficult to debug, and they -aren’t necessary for floors and ceilings. One way to get floors and +aren't necessary for floors and ceilings. One way to get floors and ceilings out of a 2-D BSP tree is to change the nature of the BSP tree so that polygons are no longer stored in the splitting nodes. Instead, each leaf of the tree—that is, each subspace carved out by the diff --git a/63-01.md b/63-01.md index 7bccd91..367d0c1 100644 --- a/63-01.md +++ b/63-01.md @@ -8,27 +8,27 @@ Chapter 63\ ### Knowing When to Hurl Conventional Math Wisdom Out the Window {#Heading2} -In a crisis, sometimes it’s best to go with the first solution that +In a crisis, sometimes it's best to go with the first solution that comes into your head—but not very often. When I turned 16, my mother had an aging, three-cylinder Saab—not one of -the sporty Saabs that appeared in the late ’70s, but a blunt-nosed, +the sporty Saabs that appeared in the late '70s, but a blunt-nosed, ungainly little wagon that seated up to seven people in sardine-like comfort, with two of them perched on the gas tank. That was the car I learned to drive on, and the one I took whenever I wanted to go -somewhere and my mother didn’t need it. +somewhere and my mother didn't need it. -My father’s car, on the other hand, was a Volvo sedan, only a couple of +My father's car, on the other hand, was a Volvo sedan, only a couple of years old and easily the classiest car my family had ever owned. To the -best of my recollection, as of New Year’s of my senior year, I had never -driven that car. However, I was going to a New Year’s party—in fact, I +best of my recollection, as of New Year's of my senior year, I had never +driven that car. However, I was going to a New Year's party—in fact, I was going to chauffeur four other people—and for reasons lost in the mists of time, I was allowed to take the Volvo. So, one crystal clear, stunningly cold night, I picked up my passengers, who included Robin -Viola, Kathy Smith, Jude Hawron...and Alan, whose last name I’ll omit in +Viola, Kathy Smith, Jude Hawron...and Alan, whose last name I'll omit in case he wants to run for president someday. -The party was at Craig Alexander’s house, way out in the middle of +The party was at Craig Alexander's house, way out in the middle of nowhere, and it was a good one. I heard Al Green for the first time, much beer was consumed (none by me, though), and around 2 a.m., we decided it was time to head home. So we piled into the Volvo, cranked @@ -36,13 +36,13 @@ the heat up to the max, and set off. We had gone about five miles when I sensed Alan was trying to tell me something. As I turned toward him, he said, quite expressively, -“BLEARGH!” and deposited a considerable volume of what had until +"BLEARGH!" and deposited a considerable volume of what had until recently been beer and chips into his lap. -Mind you, this wasn’t just any car Alan was tossing his cookies in—it -was my father’s prized Volvo. My reactions were up to the task; without -a moment’s hesitation, I shouted, “Do it out the window! Open the -window!” Alan obligingly rolled the window down and, with flawless aim, +Mind you, this wasn't just any car Alan was tossing his cookies in—it +was my father's prized Volvo. My reactions were up to the task; without +a moment's hesitation, I shouted, "Do it out the window! Open the +window!" Alan obligingly rolled the window down and, with flawless aim, sent some more erstwhile beer and chips on its way. And it was here that I learned that fast decisions are not necessarily @@ -52,13 +52,13 @@ slipstream and splattered along the length of the car. At that point, I did what I should have done in the first place; I stopped the car so Alan could get out and finish being sick in peace, while I assessed the full dimensions of the disaster. Not only was the rear half of the car -on the passenger side—including Robin’s window, accounting for the +on the passenger side—including Robin's window, accounting for the yelp—covered, but the noxious substance had frozen solid. It looked like someone had melted an enormous candle, or possibly put cake frosting on the car. The next morning, my father was remarkably good-natured about the whole -thing, considering, although I don’t remember ever actually driving the +thing, considering, although I don't remember ever actually driving the Volvo again. My penance consisted of cleaning the car, no small punishment considering that I had to take a hair dryer out to our unheated garage and melt and clean the gunk one small piece at a time. @@ -68,26 +68,26 @@ if anyone shows signed of being ill, a bit of wisdom that has proven useful a suprising number of times over the years. More important, though, is the lesson that it almost always pays to take at least a few seconds to size up a crisis situation and choose an effective response, -and that’s served me well more times than I can count. +and that's served me well more times than I can count. -There’s a surprisingly close analog to this in programming. Often, when -faced with a problem in his or her code, a programmer’s response is to +There's a surprisingly close analog to this in programming. Often, when +faced with a problem in his or her code, a programmer's response is to come up with a solution as quickly as possible and immediately hack it in. For all but the simplest problems, though, there are side effects and design issues involved that should be thought through before any coding is done. I try to think of bugs and other problem situations as opportunities to reexamine how my code works, as well as chances to -detect and correct structural defects I hadn’t previously suspected; in -fact, I’m often able to simplify code as I fix a bug, thanks to the +detect and correct structural defects I hadn't previously suspected; in +fact, I'm often able to simplify code as I fix a bug, thanks to the understanding I gain in the process. -Taking that a step farther, it’s useful to reexamine assumptions +Taking that a step farther, it's useful to reexamine assumptions periodically even if no bugs are involved. You might be surprised at how quickly assumptions that once were completely valid can deteriorate. For example, consider floating-point math. -### Not Your Father’s Floating-Point {#Heading3} +### Not Your Father's Floating-Point {#Heading3} Until last year, I had never done any serious floating-point (FP) optimization, for the perfectly good reason that FP math had never been @@ -106,7 +106,7 @@ subtraction are nearly as fast as integer operations, and FP multiplication and division have the potential to be much faster—all with the range and precision advantages of FP. Better yet, the FPU has its own set of eight registers, so the use of floating-point can help -relieve pressure on the x86’s integer registers, as well. +relieve pressure on the x86's integer registers, as well. One effect of all this is that with the Pentium, floating-point on the x86 has gone from being irrelevant to real-time 3-D to being a key @@ -121,24 +121,24 @@ more vertices to transform and project, and far more motion physics and line-of-sight calculations and the like as well. By way of getting you started with floating-point for real-time 3-D, in -this chapter I’ll examine the basics of Pentium FP optimization, then +this chapter I'll examine the basics of Pentium FP optimization, then look at how some key mathematical techniques for 3-D—dot product, cross product, transformation, and projection—can be accelerated. ### Pentium Floating-Point Optimization {#Heading4} -I’m going to assume you’re already familiar with x86 FP code in general; -for additional information, check out Intel’s *Pentium Processor User’s +I'm going to assume you're already familiar with x86 FP code in general; +for additional information, check out Intel's *Pentium Processor User's Manual* (order \#241430-001; 1-800-548-4725), a book that you should -have if you’re doing Pentium programming of any sort. I’d also recommend +have if you're doing Pentium programming of any sort. I'd also recommend taking a look around [http://www.intel.com](http://www.intel.com). -I’m going to focus on six core instructions in this section: FLD, FST, -FADD, FSUB, FMUL, and FDIV. First, let’s look at cycle times for these +I'm going to focus on six core instructions in this section: FLD, FST, +FADD, FSUB, FMUL, and FDIV. First, let's look at cycle times for these instructions. FLD takes 1 cycle; the value is pushed onto the FP stack and ready for use on the next cycle. FST takes 2 cycles, although when -storing to memory, there’s a potential extra cycle that can be lost, as -I’ll describe shortly. +storing to memory, there's a potential extra cycle that can be lost, as +I'll describe shortly. ------------------------ --------------------------------- -------------------- [Previous](62-04.html) [Table of Contents](index.html) [Next](63-02.html) diff --git a/63-02.md b/63-02.md index cfd7061..36c6825 100644 --- a/63-02.md +++ b/63-02.md @@ -4,44 +4,44 @@ FDIV is a painfully slow instruction, taking 39 cycles at full precision and 33 cycles at double precision, which is the default precision for -Visual C++ 2.0. While FDIV executes, the FPU is occupied, and can’t +Visual C++ 2.0. While FDIV executes, the FPU is occupied, and can't process subsequent FP instructions until FDIV finishes. However, during the cycles while FDIV is executing (with the exception of the one cycle during which FDIV starts), the integer unit can simultaneously execute instructions other than IMUL. (IMUL uses the FPU, and can only overlap with FDIV for a few cycles.) Since the integer unit can execute two -instructions per cycle, this means it’s possible to have three +instructions per cycle, this means it's possible to have three instructions, an FDIV and two integer instructions, executing at the -same time. That’s exactly what happens, for example, during the second +same time. That's exactly what happens, for example, during the second cycle of this code: FDIV ST(0),ST(1) ADD EAX,ECX INC EDX -There’s an important limitation, though; if the instruction stream +There's an important limitation, though; if the instruction stream following the FDIV reaches a FP instruction (or an IMUL), then that instruction and all subsequent instructions, both integer and FP, must wait to execute until FDIV has finished. When a FADD, FSUB, or FMUL instruction is executed, it is 3 cycles -before the result can be used by another instruction. (There’s an +before the result can be used by another instruction. (There's an exception: If the instruction that attempts to use the result is an FST -to memory, there’s an extra cycle lost, so it’s 4 cycles from the start +to memory, there's an extra cycle lost, so it's 4 cycles from the start of an arithmetic instruction until an FST of that value can begin, so FMUL ST(0),ST(1) FST [temp] -takes 6 cycles in all.) Again, it’s possible to execute integer-unit +takes 6 cycles in all.) Again, it's possible to execute integer-unit instructions during the 2 (or 3, for FST) cycles after one of these FP -instructions starts. There’s a more exciting possibility here, though: +instructions starts. There's a more exciting possibility here, though: Given properly structured code, the FPU is capable of averaging 1 cycle per FADD, FSUB, or FMUL. The secret is pipelining. #### Pipelining, Latency, and Throughput {#Heading5} -The Pentium’s FPU is the first pipelined x86 FPU. *Pipelining* means +The Pentium's FPU is the first pipelined x86 FPU. *Pipelining* means that the FPU is capable of starting an instruction every cycle, and can simultaneously handle several instructions in various stages of completion. Only certain x86 FP instructions allow another instruction @@ -57,11 +57,11 @@ is not an issue.) Thus, in the code sequence FADD~1~ can start on cycle N, FSUB can start on cycle N+1, FADD~2~ can start on cycle N+2, and FMUL can start on cycle N+3. At the start of cycle N+3, the result of FADD~1~ is available in the destination -operand, because it’s been 3 cycles since the instruction started; FSUB +operand, because it's been 3 cycles since the instruction started; FSUB is starting the final cycle of calculation; FADD~2~ is starting its second cycle, with one cycle yet to go after this; and FMUL is about to be issued. Each of the instructions takes 3 cycles to produce a result -from the time it starts, but because they’re simultaneously processed at +from the time it starts, but because they're simultaneously processed at different pipeline stages, one instruction is issued and one instruction completes every cycle. Thus, the latency of these instructions—that is, the time until the result is available—is 3 cycles, but the @@ -72,14 +72,14 @@ every 2 cycles, so between these two instructions FMUL ST(1),ST(0) FMUL ST(2),ST(0) -there’s a 1-cycle stall, and the following three instructions execute +there's a 1-cycle stall, and the following three instructions execute just as fast as the above pair: FMUL ST(1),ST(0) FLD ST(4) FMUL ST(0),ST(1) -There’s a caveat here, though: A FP instruction can’t be issued until +There's a caveat here, though: A FP instruction can't be issued until its operands are available. The FPU can reach a throughput of 1 cycle per instruction on this code @@ -93,7 +93,7 @@ Consider, however FADD ST(0),ST(2) FSUB ST(0),ST(1) -where the ST(0) operand to FSUB is calculated by FADD. Here, FSUB can’t +where the ST(0) operand to FSUB is calculated by FADD. Here, FSUB can't start until FADD has completed, so there are 2 stall cycles between the two instructions. When dependencies like this occur, the FPU runs at latency rather than throughput speeds, and performance can drop by as @@ -110,12 +110,12 @@ ST(0), and FST can only store from ST(0). How, then, can we keep three independent instructions going? The easy answer would be for Intel to change the FP registers from a -stack to a set of independent registers. Since they couldn’t do that, +stack to a set of independent registers. Since they couldn't do that, thanks to compatibility issues, they did the next best thing: They made the FXCH instruction, which swaps ST(0) and any other FP register, virtually free. In general, if FXCH is both preceded and followed by FP instructions, then it takes *no* cycles to execute. (Application Note -500, “Optimizations for Intel’s 32-bit Processors,” February 1994, +500, "Optimizations for Intel's 32-bit Processors," February 1994, available from [http://www.intel.com](http://www.intel.com), describes all .the conditions under which FXCH is free.) This allows you to move the target of a pending operation from ST(0) to another register, at the @@ -138,7 +138,7 @@ multiplications, without incurring any stalls, as shown in Listing 63.1. ### The Dot Product {#Heading7} -Now we’re ready to look at fast FP for common 3-D operations; we’ll +Now we're ready to look at fast FP for common 3-D operations; we'll start by looking at how to speed up the dot product. As discussed in Chapter 30, the dot product is heavily used in 3-D to calculate cosines and to project points along vectors. The dot product is calculated as d @@ -152,9 +152,9 @@ by doing all three FMULs first, then using FXCH to set the third FXCH aside to complete while the results of the first two FMULs, which have completed, are added. Listing 43.3 still loses 50 percent to stalls, but unless some other code is available to be interleaved with the dot -product code, that’s all we can do to speed things up. Fortunately, dot -products are often used in contexts where there’s plenty of interleaving -potential, as we’ll see when we discuss transformation. +product code, that's all we can do to speed things up. Fortunately, dot +products are often used in contexts where there's plenty of interleaving +potential, as we'll see when we discuss transformation. ------------------------ --------------------------------- -------------------- [Previous](63-01.html) [Table of Contents](index.html) [Next](63-03.html) diff --git a/63-03.md b/63-03.md index 2f8ffef..3594596 100644 --- a/63-03.md +++ b/63-03.md @@ -38,8 +38,8 @@ ### The Cross Product {#Heading8} -When last we looked at the cross product, we found that it’s handy for -generating a vector that’s normal to two other vectors. The cross +When last we looked at the cross product, we found that it's handy for +generating a vector that's normal to two other vectors. The cross product is calculated as [u~2~v~3~-u~3~v~2~ u~3~v~1~-u~1~v~3~ u~1~v~2~-u~2~v~1~]. The theoretical minimum cycle count for the cross product is 21 cycles. Listing 63.4 shows a straightforward @@ -78,7 +78,7 @@ losing 15 cycles to stalls. fstp [vec2+8] ;starts on cycle 34, ; ends on cycle 35 -We couldn’t get rid of many of the stalls in the dot product code +We couldn't get rid of many of the stalls in the dot product code because with six inputs and one output, it was impossible to interleave all the operations. However, the cross product, with three outputs, is much more amenable to optimization. In fact, three is the magic number; @@ -89,7 +89,7 @@ only one cycle to a stall, the cycle before the first FST; the relevant FSUB has just finished on the preceding cycle, so we run into the extra cycle of latency associated with FST. Listing 63.5 is more than 60 percent faster than Listing 63.4, a striking illustration of the power -of properly managing the Pentium’s FP pipeline. +of properly managing the Pentium's FP pipeline. **Listing 63.5 L63-5.ASM** @@ -124,7 +124,7 @@ of properly managing the Pentium’s FP pipeline. Transforming a point, for example from worldspace to viewspace, is one of the most heavily used FP operations in realtime 3-D. Conceptually, transformation is nothing more than three dot products and three -additions, as I will discuss in Chapter 61. (Note that I’m talking about +additions, as I will discuss in Chapter 61. (Note that I'm talking about a subset of a general 4x4 transformation matrix, where the fourth row is always implicitly [0 0 0 1]. This limited form suffices for common transformations, and does 25 percent less work than a full 4x4 diff --git a/63-04.md b/63-04.md index 4a7c344..9535219 100644 --- a/63-04.md +++ b/63-04.md @@ -12,11 +12,11 @@ if done one after the other using the faster dot-product code of Listing When fully interleaved, however, only a single cycle is lost (again to the extra cycle of FST latency), and the cycle count drops to 34, as -shown in Listing 63.6. This means that on a 100 MHz Pentium, it’s +shown in Listing 63.6. This means that on a 100 MHz Pentium, it's theoretically possible to do nearly 3,000,000 transforms per second, -although that’s a purely hypothetical number, due to cache effects and +although that's a purely hypothetical number, due to cache effects and set-up costs. Still, more than 1,000,000 transforms per second is -certainly feasible; at a frame rate of 30 Hz, that’s an impressive +certainly feasible; at a frame rate of 30 Hz, that's an impressive 30,000 transforms per frame. **Listing 63.6 L63-6.ASM** @@ -64,28 +64,28 @@ certainly feasible; at a frame rate of 30 Hz, that’s an impressive ### Projection {#Heading10} -The final optimization we’ll look at is projection to screenspace. +The final optimization we'll look at is projection to screenspace. Projection itself is basically nothing more than a divide (to get 1/z), -followed by two multiplies (to get x/z and y/z), so there wouldn’t seem +followed by two multiplies (to get x/z and y/z), so there wouldn't seem to be much in the way of FP optimization possibilities there. However, remember that although FDIV has a latency of up to 39 cycles, it can overlap with integer instructions for all but one of those cycles. That means that if we can find enough independent integer work to do before we need the 1/z result, we can effectively reduce the cost of the FDIV -to one cycle. Projection by itself doesn’t offer much with which to +to one cycle. Projection by itself doesn't offer much with which to overlap, but other work such as clamping, window-relative adjustments, or 2-D clipping could be interleaved with the FDIV for the next point. Another dramatic speed-up is possible by setting the precision of the FPU down to single precision via FLDCW, thereby cutting the time FDIV -takes to a mere 19 cycles. I don’t have the space to discuss reduced +takes to a mere 19 cycles. I don't have the space to discuss reduced precision in detail in this book, but be aware that along with potentially greater performance, it carries certain risks, as well. The reduced precision, which affects FADD, FSUB, FMUL, FDIV, and FSQRT, can -cause subtle differences from the results you’d get using compiler +cause subtle differences from the results you'd get using compiler defaults. If you use reduced precision, you should be on the alert for precision-related problems, such as clipped values that vary more than -you’d expect from the precise clip point, or the need for using larger +you'd expect from the precise clip point, or the need for using larger epsilons in comparisons for point-on-plane tests. ### Rounding Control {#Heading11} @@ -107,20 +107,20 @@ different from compiler default behavior while chop, ceil, or floor mode is in effect. A final note: There are some speed-ups to be had by manipulating FP -variables with integer instructions. Check out Chris Hecker’s column in +variables with integer instructions. Check out Chris Hecker's column in the February/March 1996 issue of *Game Developer* for details. ### A Farewell to 3-D Fixed-Point {#Heading12} As with most optimizations, there are both benefits and hazards to floating-point acceleration, especially pedal-to-the-metal optimizations -such as the last few I’ve mentioned. Nonetheless, I’ve found +such as the last few I've mentioned. Nonetheless, I've found floating-point to be generally both more robust and easier to use than fixed-point even with those maximum optimizations. Now that -floating-point is fast enough for real time, I don’t expect to be doing +floating-point is fast enough for real time, I don't expect to be doing a whole lot of fixed-point 3-D math from here on out. -And I won’t miss it a bit. +And I won't miss it a bit. ------------------------ --------------------------------- -------------------- [Previous](63-03.html) [Table of Contents](index.html) [Next](64-01.html) diff --git a/64-01.md b/64-01.md index 38f5afb..39e604a 100644 --- a/64-01.md +++ b/64-01.md @@ -3,14 +3,14 @@ ------------------------ --------------------------------- -------------------- Chapter 64\ - Quake’s Visible-Surface Determination {#Heading1} + Quake's Visible-Surface Determination {#Heading1} -------------------------------------- ### The Challenge of Separating All Things Seen from All Things Unseen {#Heading2} Years ago, I was working at Video Seven, a now-vanished video adapter manufacturer, helping to develop a VGA clone. The fellow who was -designing Video Seven’s VGA chip, Tom Wilson, had worked around the +designing Video Seven's VGA chip, Tom Wilson, had worked around the clock for months to make his VGA run as fast as possible, and was confident he had pretty much maxed out its performance. As Tom was putting the finishing touches on his chip design, however, news came @@ -27,65 +27,65 @@ the system bus and the VGA, so that when the CPU wrote to video memory, the write immediately went into the FIFO, allowing the CPU to keep on processing instead of stalling each time it wrote to display memory. -Tom couldn’t spare the gates or the time to do a full FIFO, but he could +Tom couldn't spare the gates or the time to do a full FIFO, but he could implement a one-deep FIFO, allowing the CPU to get one write ahead of -the VGA. He wasn’t sure how well it would work, but it was all he could +the VGA. He wasn't sure how well it would work, but it was all he could do, so he put it in and taped out the chip. The one-deep FIFO turned out to work astonishingly well; for a time, -Video Seven’s VGAs were the fastest around, a testament to Tom’s +Video Seven's VGAs were the fastest around, a testament to Tom's ingenuity and creativity under pressure. However, the truly remarkable -part of this story is that Paradise’s FIFO design turned out to bear not -the slightest resemblance to Tom’s, and *didn’t work as well.* Paradise +part of this story is that Paradise's FIFO design turned out to bear not +the slightest resemblance to Tom's, and *didn't work as well.* Paradise had stuck a *read* FIFO between display memory and the video output stage of the VGA, allowing the video output to read ahead, so that when the CPU wanted to access display memory, pixels could come from the FIFO while the CPU was serviced immediately. That did indeed help -performance—but not as much as Tom’s write FIFO. +performance—but not as much as Tom's write FIFO. ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *What we have here is as neat a parable about the nature of creative design as one could hope to find. The scrap of news about Paradise’s chip contained almost no actual information, but it forced Tom to push past the limits he had unconsciously set in coming up with his original design. And, in the end, I think that the single most important element of great design, whether it be hardware, software, or any creative endeavor, is precisely what the Paradise news triggered in Tom: the ability to detect the limits you have built into the way you think about your design, and then transcend those limits.* + ![](images/i.jpg) *What we have here is as neat a parable about the nature of creative design as one could hope to find. The scrap of news about Paradise's chip contained almost no actual information, but it forced Tom to push past the limits he had unconsciously set in coming up with his original design. And, in the end, I think that the single most important element of great design, whether it be hardware, software, or any creative endeavor, is precisely what the Paradise news triggered in Tom: the ability to detect the limits you have built into the way you think about your design, and then transcend those limits.* ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -The problem, of course, is how to go about transcending limits you don’t -even know you’ve imposed. There’s no formula for success, but two +The problem, of course, is how to go about transcending limits you don't +even know you've imposed. There's no formula for success, but two principles can stand you in good stead: simplify and keep on trying new things. -Generally, if you find your code getting more complex, you’re -fine-tuning a frozen design, and it’s likely you can get more of a +Generally, if you find your code getting more complex, you're +fine-tuning a frozen design, and it's likely you can get more of a speed-up, with less code, by rethinking the design. A really good design should bring with it a moment of immense satisfaction in which -everything falls into place, and you’re amazed at how little code is +everything falls into place, and you're amazed at how little code is needed and how all the boundary cases just work properly. As for how to rethink the design, do it by pursuing whatever ideas occur to you, no matter how off-the-wall they seem. Many of the truly -brilliant design ideas I’ve heard of over the years sounded like -nonsense at first, because they didn’t fit my preconceived view of the +brilliant design ideas I've heard of over the years sounded like +nonsense at first, because they didn't fit my preconceived view of the world. Often, such ideas are in fact off-the-wall, but just as the news -about Paradise’s chip sparked Tom’s imagination, aggressively pursuing +about Paradise's chip sparked Tom's imagination, aggressively pursuing seemingly outlandish ideas can open up new design possibilities for you. -Case in point: The evolution of Quake’s 3-D graphics engine. +Case in point: The evolution of Quake's 3-D graphics engine. ### VSD: The Toughest 3-D Challenge of All {#Heading3} -I’ve spent most of my waking hours for the last several months working -on Quake, id Software’s successor to DOOM, and I suspect I have a few -more months to go. The very best things don’t happen easily, nor +I've spent most of my waking hours for the last several months working +on Quake, id Software's successor to DOOM, and I suspect I have a few +more months to go. The very best things don't happen easily, nor quickly—but when they happen, all the sweat becomes worthwhile. In terms of graphics, Quake is to DOOM as DOOM was to its predecessor, Wolfenstein 3-D. Quake adds true, arbitrary 3-D (you can look up and down, lean, and even fall on your side), detailed lighting and shadows, -and 3-D monsters and players in place of DOOM’s sprites. Someday I hope +and 3-D monsters and players in place of DOOM's sprites. Someday I hope to talk about how all that works, but for the here and now I want to talk about what is, in my opinion, the toughest 3-D problem of all: visible surface determination (drawing the proper surface at each pixel), and its close relative, culling (discarding non-visible polygons as quickly as possible, a way of accelerating visible surface -determination). In the interests of brevity, I’ll use the abbreviation +determination). In the interests of brevity, I'll use the abbreviation VSD to mean both visible surface determination and culling from now on. Why do I think VSD is the toughest 3-D challenge? Although rasterization diff --git a/64-02.md b/64-02.md index ca46ed8..ad76e20 100644 --- a/64-02.md +++ b/64-02.md @@ -7,7 +7,7 @@ Before diving into VSD, let me note that each Quake level is stored as a single huge 3-D BSP tree. This BSP tree, like any BSP, subdivides space, in this case along the planes of the polygons. However, unlike the BSP -tree I presented in Chapter 62, Quake’s BSP tree does not store polygons +tree I presented in Chapter 62, Quake's BSP tree does not store polygons in the tree nodes, as part of the splitting planes, but rather in the empty (non-solid) leaves, as shown in overhead view in Figure 64.1. @@ -33,56 +33,56 @@ and the processing time would be the same for all possible viewpoints, giving the game a smooth visual flow. ![](images/64-01.jpg)\ - **Figure 64.1**  *Quake’s polygons are stored as empty leaves.* + **Figure 64.1**  *Quake's polygons are stored as empty leaves.* ![](images/64-02.jpg)\ **Figure 64.2**  *Pixels visible from the current viewpoint.* As it happens, it is easy to determine which polygons are outside the -frustum or partially clipped, and it’s quite possible to figure out +frustum or partially clipped, and it's quite possible to figure out precisely which pixels need to be drawn. Alas, the world is far from perfect, and those tests are far from free, so the real trick is how to accelerate or skip various tests and still produce the desired result. -As I discussed at length in Chapter 62, given a BSP, it’s easy and +As I discussed at length in Chapter 62, given a BSP, it's easy and inexpensive to walk the world in front-to-back or back-to-front order. The simplest VSD solution, which I in fact demonstrated earlier, is to simply walk the tree back-to-front, clip each polygon to the frustum, -and draw it if it’s facing forward and not entirely clipped (the -painter’s algorithm). Is that an adequate solution? +and draw it if it's facing forward and not entirely clipped (the +painter's algorithm). Is that an adequate solution? -For relatively simple worlds, it is perfectly acceptable. It doesn’t +For relatively simple worlds, it is perfectly acceptable. It doesn't scale very well, though. One problem is that as you add more polygons in the world, more transformations and tests have to be performed to cull -polygons that aren’t visible; at some point, that will bog considerably +polygons that aren't visible; at some point, that will bog considerably performance down. #### Nodes Inside and Outside the View Frustum {#Heading6} -Happily, there’s a good workaround for this particular problem. As +Happily, there's a good workaround for this particular problem. As discussed earlier, each leaf of a BSP tree represents a convex subspace, with the nodes that bound the leaf delimiting the space. Perhaps less obvious is that each node in a BSP tree also describes a subspace—the -subspace composed of all the node’s children, as shown in Figure 64.3. +subspace composed of all the node's children, as shown in Figure 64.3. Another way of thinking of this is that each node splits the subspace into two pieces created by the nodes above it in the tree, and the -node’s children then further carve that subspace into all the leaves +node's children then further carve that subspace into all the leaves that descend from the node. ![](images/64-03.jpg)\ **Figure 64.3**  *The substance described by node E.* -Since a node’s subspace is bounded and convex, it is possible to test +Since a node's subspace is bounded and convex, it is possible to test whether it is entirely outside the frustum. If it is, *all* of the -node’s children are certain to be fully clipped and can be rejected +node's children are certain to be fully clipped and can be rejected without any additional processing. Since most of the world is typically outside the frustum, many of the polygons in the world can be culled -almost for free, in huge, node-subspace chunks. It’s relatively +almost for free, in huge, node-subspace chunks. It's relatively expensive to perform a perfect test for subspace clipping, so instead bounding spheres or boxes are often maintained for each node, specifically for culling tests. -So culling to the frustum isn’t a problem, and the BSP can be used to +So culling to the frustum isn't a problem, and the BSP can be used to draw back-to- front. What, then, *is* the problem? ### Overdraw {#Heading7} @@ -91,13 +91,13 @@ The problem John Carmack, the driving technical force behind DOOM and Quake, faced when he designed Quake was that in a complex world, many scenes have an awful lot of polygons in the frustum. Most of those polygons are partially or entirely obscured by other polygons, but the -painter’s algorithm described earlier requires that every pixel of every +painter's algorithm described earlier requires that every pixel of every polygon in the frustum be drawn, often only to be overdrawn. In a 10,000-polygon Quake level, it would be easy to get a worst-case overdraw level of 10 times or more; that is, in some frames each pixel could be drawn 10 times or more, on average. No rasterizer is fast enough to compensate for an order of such magnitude and more work than -is actually necessary to show a scene; worse still, the painter’s +is actually necessary to show a scene; worse still, the painter's algorithm will cause a vast difference between best-case and worst-case performance, so the frame rate can vary wildly as the viewer moves around. @@ -114,18 +114,18 @@ than the overdraw that would otherwise result. When I arrived at id at the beginning of March 1995, John already had an engine prototyped and a plan in mind, and I assumed that our work was a simple matter of finishing and optimizing that engine. If I had been -aware of id’s history, however, I would have known better. John had done +aware of id's history, however, I would have known better. John had done not only DOOM, but also the engines for Wolfenstein 3-D and several earlier games, and had actually done several different versions of each engine in the course of development (once doing four engines in four weeks), for a total of perhaps 20 distinct engines over a four-year -period. John’s tireless pursuit of new and better designs for Quake’s +period. John's tireless pursuit of new and better designs for Quake's engine, from every angle he could think of, would end only when we shipped the product. By three months after I arrived, only one element of the original VSD -design was anywhere in sight, and John had taken the dictum of “try new -things” farther than I’d ever seen it taken. +design was anywhere in sight, and John had taken the dictum of "try new +things" farther than I'd ever seen it taken. ------------------------ --------------------------------- -------------------- [Previous](64-01.html) [Table of Contents](index.html) [Next](64-03.html) diff --git a/64-03.md b/64-03.md index 2970484..88051ae 100644 --- a/64-03.md +++ b/64-03.md @@ -4,7 +4,7 @@ ### The Beam Tree {#Heading8} -John’s original Quake design was to draw front-to-back, using a second +John's original Quake design was to draw front-to-back, using a second BSP tree to keep track of what parts of the screen were already drawn and which were still empty and therefore drawable by the remaining polygons. Logically, you can think of this BSP tree as being a 2-D @@ -14,7 +14,7 @@ tree.* A beam tree is a collection of 3-D wedges (beams), bounded by planes, projecting out from some center point, in this case the viewpoint, as shown in Figure 64.5. -In John’s design, the beam tree started out consisting of a single beam +In John's design, the beam tree started out consisting of a single beam describing the frustum; everything outside that beam was marked solid (so nothing would draw there), and the inside of the beam was marked empty. As each new polygon was reached while walking the world BSP tree @@ -45,16 +45,16 @@ aforementioned bulk culling to the frustum. The early-out feature of the beam tree—stopping when the beam tree becomes solid—seems appealing, because it appears to cap worst-case -performance. Unfortunately, there are still scenes where it’s possible +performance. Unfortunately, there are still scenes where it's possible to see all the way to the sky or the back wall of the world, so in the worst case, all polygons in the frustum will still have to be tested against the beam tree. Similar problems can arise from tiny cracks due to numeric precision limitations. Beam-tree clipping is fairly time-consuming, and in scenes with long view distances, such as views across the top of a level, the total cost of beam processing slowed -Quake’s frame rate to a crawl. So, in the end, the beam-tree approach -proved to suffer from much the same malady as the painter’s algorithm: -The worst case was much worse than the average case, and it didn’t scale +Quake's frame rate to a crawl. So, in the end, the beam-tree approach +proved to suffer from much the same malady as the painter's algorithm: +The worst case was much worse than the average case, and it didn't scale well with increasing level complexity. ### 3-D Engine du Jour {#Heading9} @@ -62,8 +62,8 @@ well with increasing level complexity. Once the beam tree was working, John relentlessly worked at speeding up the 3-D engine, always trying to improve the design, rather than tweaking the implementation. At least once a week, and often every day, -he would walk into my office and say “Last night I couldn’t get to -sleep, so I was thinking...” and I’d know that I was about to get my +he would walk into my office and say "Last night I couldn't get to +sleep, so I was thinking..." and I'd know that I was about to get my mind stretched yet again. John tried many ways to improve the beam tree, with some success, but more interesting was the profusion of wildly different approaches that he generated, some of which were merely @@ -79,12 +79,12 @@ sparked. Rays are cast in an 8x8 screen-pixel grid; this is a highly efficient operation because the first intersection with a surface can be found by simply clipping the ray into the BSP tree, starting at the viewpoint, -until a solid leaf is reached. If adjacent rays don’t hit the same +until a solid leaf is reached. If adjacent rays don't hit the same surface, then a ray is cast halfway between, and so on until all adjacent rays either hit the same surface or are on adjacent pixels; then the block around each ray is drawn from the polygon that was hit. This scales very well, being limited by the number of pixels, with no -overdraw. The problem is dropouts; it’s quite possible for small +overdraw. The problem is dropouts; it's quite possible for small polygons to fall between rays and vanish. #### Vertex-Free Surfaces {#Heading11} @@ -92,7 +92,7 @@ polygons to fall between rays and vanish. The world is represented by a set of surface planes. The polygons are implicit in the plane intersections, and are extracted from the planes as a final step before drawing. This makes for fast clipping and a very -small data set (planes are far more compact than polygons), but it’s +small data set (planes are far more compact than polygons), but it's time-consuming to extract polygons from planes. #### The Draw-Buffer {#Heading12} @@ -112,14 +112,14 @@ parallel while 8 pixels are processed. Polygons are rasterized into spans, which are added to a global span list and clipped against that list so that only the nearest span at each pixel remains. Little sorting is needed with front-to-back walking, -because if there’s any overlap, the span already in the list is nearer. +because if there's any overlap, the span already in the list is nearer. This eliminates overdraw, but at the cost of a lot of span arithmetic; also, every polygon still has to be turned into spans. #### Portals {#Heading14} The holes where polygons are missing on surfaces are tracked, because -it’s only through such portals that line-of-sight can extend. Drawing +it's only through such portals that line-of-sight can extend. Drawing goes front-to-back, and when a portal is encountered, polygons and portals behind it are clipped to its limits, until no polygons or portals remain visible. Applied recursively, this allows drawing only @@ -138,7 +138,7 @@ whereby a single, linear walk of a DOOM BSP tree produces zero-overdraw complex problem, though, and by the end of the week John was frustrated by the increasing complexity and persistent glitches in the visibility code. Although the direct-BSP approach was getting closer to working, it -was taking more and more tweaking, and a simple, clean design didn’t +was taking more and more tweaking, and a simple, clean design didn't seem to be falling out. When I left work one Friday, John was preparing to try to get the direct-BSP approach working properly over the weekend. diff --git a/64-04.md b/64-04.md index 4054725..8017ecb 100644 --- a/64-04.md +++ b/64-04.md @@ -3,7 +3,7 @@ ------------------------ --------------------------------- -------------------- When I came in on Monday, John had the look of a man who had broken -through to the other side—and also the look of a man who hadn’t had much +through to the other side—and also the look of a man who hadn't had much sleep. He had worked all weekend on the direct-BSP approach, and had gotten it working reasonably well, with insights into how to finish it off. At 3:30 Monday morning, as he lay in bed, thinking about portals, @@ -29,7 +29,7 @@ for a leaf includes all leaves visible from anywhere in the leaf, so some overdraw, typically on the order of 50 percent but ranging up to 150 percent, generally occurs). Better yet, precalculating the PVS results in a leveling of performance; worst case is no longer much worse -than best case, because there’s no longer extra VSD processing—just more +than best case, because there's no longer extra VSD processing—just more polygons and perhaps some extra overdraw—associated with complex scenes. The first time John showed me his working prototype, I went to the most complex scene I knew of, a place where the frame rate used to grind down @@ -38,10 +38,10 @@ slowdown. John says precalculating the PVS was a logical evolution of the approaches he had been considering, that there was no moment when he -said “Eureka!” Nonetheless, it was clearly a breakthrough to a +said "Eureka!" Nonetheless, it was clearly a breakthrough to a brand-new, superior design, a design that, together with a still-in-development sorted-edge rasterizer that completely eliminates -overdraw, comes remarkably close to meeting the “perfect-world” +overdraw, comes remarkably close to meeting the "perfect-world" specifications we laid out at the start. ### Simplify, and Keep on Trying New Things {#Heading16} @@ -49,31 +49,31 @@ specifications we laid out at the start. What does it all mean? Exactly what I said up front: Simplify, and keep trying new things. The precalculated PVS is simpler than any of the other schemes that had been considered (although precalculating the PVS -is an interesting task that I’ll discuss another time). In fact, at +is an interesting task that I'll discuss another time). In fact, at runtime the precalculated PVS is just a constrained version of the -painter’s algorithm. Does that mean it’s not particularly profound? +painter's algorithm. Does that mean it's not particularly profound? Not at all. All really great designs seem simple and even obvious—once -they’ve been designed. But the process of getting there requires +they've been designed. But the process of getting there requires incredible persistence and a willingness to try lots of different ideas until the right one falls into place, as happened here. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *My friend Chris Hecker has a theory that all approaches work out to the same thing in the end, since they all reflect the same underlying state and functionality. In terms of underlying theory, I’ve found that to be true; whether you do perspective texture mapping with a divide or with incremental hyperbolic calculations, the numbers do exactly the same thing. When it comes to implementation, however, my experience is that simply time-shifting an approach, or matching hardware capabilities better, or caching can make an astonishing difference.* + ![](images/i.jpg) *My friend Chris Hecker has a theory that all approaches work out to the same thing in the end, since they all reflect the same underlying state and functionality. In terms of underlying theory, I've found that to be true; whether you do perspective texture mapping with a divide or with incremental hyperbolic calculations, the numbers do exactly the same thing. When it comes to implementation, however, my experience is that simply time-shifting an approach, or matching hardware capabilities better, or caching can make an astonishing difference.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -My friend Terje Mathisen likes to say that “almost all programming can -be viewed as an exercise in caching,” and that’s exactly what John did. +My friend Terje Mathisen likes to say that "almost all programming can +be viewed as an exercise in caching," and that's exactly what John did. No matter how fast he made his VSD calculations, they could never be as fast as precalculating and looking up the visibility, and his most -inspired move was to yank himself out of the “faster code” mindset and +inspired move was to yank himself out of the "faster code" mindset and realize that it was in fact possible to precalculate (in effect, cache) and look up the PVS. The hardest thing in the world is to step outside a familiar, pretty good solution to a difficult problem and look for a different, better solution. The best ways I know to do that are to keep trying new, wacky -things, and always, always, always try to simplify. One of John’s goals +things, and always, always, always try to simplify. One of John's goals is to have fewer lines of code in each 3-D game than in the previous game, on the assumption that as he learns more, he should be able to do things better with less code. @@ -82,28 +82,28 @@ So far, it seems to have worked out pretty well for him. ### Learn Now, Pay Forward {#Heading17} -There’s one other thing I’d like to mention before I close this chapter. -Much of what I’ve learned, and a great deal of what I’ve written, has -been in the pages of *Dr. Dobb’s Journal.* As far back as I can +There's one other thing I'd like to mention before I close this chapter. +Much of what I've learned, and a great deal of what I've written, has +been in the pages of *Dr. Dobb's Journal.* As far back as I can remember, *DDJ* has epitomized the attitude that sharing programming information is A Good Thing. I know a lot of programmers who were able -to leap ahead in their development because of Hendrix’s Tiny C, or -Stevens’ D-Flat, or simply by browsing through *DDJ*’s annual +to leap ahead in their development because of Hendrix's Tiny C, or +Stevens' D-Flat, or simply by browsing through *DDJ*'s annual collections. (Me, for one.) Understandably, most companies understandably view sharing information in a very different way, as -potential profit lost—but that’s what makes *DDJ* so valuable to the +potential profit lost—but that's what makes *DDJ* so valuable to the programming community. It is in that spirit that id Software is allowing me to describe in these pages (which also appeared in one of the *DDJ* special issues) how -Quake works, even before Quake has shipped. That’s also why id has +Quake works, even before Quake has shipped. That's also why id has placed the full source code for Wolfenstein 3-D on [ftp.idsoftware.com/idstuff/source](ftp://ftp.idsoftware.com/idstuff/source); -and although you can’t just recompile the code and sell it, you can +and although you can't just recompile the code and sell it, you can learn how a full-blown, successful game works. Check wolfsrc.txt in the above-mentioned directory for details on how the code may be used. -So remember, when it’s legally possible, sharing information benefits us +So remember, when it's legally possible, sharing information benefits us all in the long run. You can pay forward the debt for the information you gain here and elsewhere by sharing what you know whenever you can, by writing an article or book or posting on the Net. None of us learns diff --git a/65-01.md b/65-01.md index fe245ba..245ce95 100644 --- a/65-01.md +++ b/65-01.md @@ -6,52 +6,52 @@ Chapter 65\ 3-D Clipping and Other Thoughts {#Heading1} -------------------------------- -### Determining What’s Inside Your Field of View {#Heading2} +### Determining What's Inside Your Field of View {#Heading2} -Our part of the world is changing, and I’m concerned. By way of +Our part of the world is changing, and I'm concerned. By way of explanation, three anecdotes. Anecdote the first: In the introduction to one of his books, Frank Herbert, author of *Dune*, told how he had once been approached by a friend who claimed he (the friend) had a killer idea for an SF story, and offered to tell it to Herbert. In return, Herbert had to agree that -if he used the idea in a story, he’d split the money from the story with -this fellow. Herbert’s response was that ideas were a dime a dozen; he +if he used the idea in a story, he'd split the money from the story with +this fellow. Herbert's response was that ideas were a dime a dozen; he had more story ideas than he could ever write in a lifetime. The hard part was the writing, not the ideas. -Anecdote the second: I’ve been programming micros for 15 years, and +Anecdote the second: I've been programming micros for 15 years, and writing about them for more than a decade and, until about a year ago, I had never—not once!—had anyone offer to sell me a technical idea. In the -last year, it’s happened multiple times, generally via unsolicited email -along the lines of Herbert’s tale. +last year, it's happened multiple times, generally via unsolicited email +along the lines of Herbert's tale. -This trend toward selling ideas is one symptom of an attitude that I’ve +This trend toward selling ideas is one symptom of an attitude that I've noticed more and more among programmers over the past few years—an attitude of which software patents are the most obvious manifestation—a desire to think something up without breaking a sweat, then let someone -else’s hard work make you money. It’s an attitude that says, “I’m so -smart that my ideas alone set me apart.” Sorry, it doesn’t work that way +else's hard work make you money. It's an attitude that says, "I'm so +smart that my ideas alone set me apart." Sorry, it doesn't work that way in the real world. Ideas are a dime a dozen in programming, too; I have -a lifetime’s worth of article and software ideas written neatly in a +a lifetime's worth of article and software ideas written neatly in a notebook, and I know several truly original thinkers who have far more -yet. Folks, it’s not the ideas; it’s design, implementation, and +yet. Folks, it's not the ideas; it's design, implementation, and especially hard work that make the difference. -Virtually every idea I’ve encountered in 3-D graphics was invented +Virtually every idea I've encountered in 3-D graphics was invented decades ago. You think you have a clever graphics idea? Sutherland, Sproull, Schumacker, Catmull, Smith, Blinn, Glassner, Kajiya, Heckbert, -or Teller probably thought of your idea years ago. (I’m serious—spend a -few weeks reading through the literature on 3-D graphics, and you’ll be -amazed at what’s already been invented and published.) If they thought +or Teller probably thought of your idea years ago. (I'm serious—spend a +few weeks reading through the literature on 3-D graphics, and you'll be +amazed at what's already been invented and published.) If they thought it was important enough, they wrote a paper about it, or tried to -commercialize it, but what they didn’t do was try to charge people for +commercialize it, but what they didn't do was try to charge people for the idea itself. A closely related point is the astonishing lack of gratitude some programmers show for the hard work and sense of community that went into building the knowledge base with which they work. How about this? Anyone -who thinks they have a unique idea that they want to “own” and milk for +who thinks they have a unique idea that they want to "own" and milk for money can do so—but first they have to track down and appropriately compensate all the people who made possible the compilers, algorithms, programming courses, books, hardware, and so forth that put them in a @@ -66,31 +66,31 @@ path, I guarantee that it will be a poorer profession for all of us—except the patent attorneys, I guess. Anecdote the third: A while back, I had the good fortune to have lunch -down by Seattle’s waterfront with Neal Stephenson, the author of *Snow -Crash* and *The Diamond Age* (one of the best SF books I’ve come across +down by Seattle's waterfront with Neal Stephenson, the author of *Snow +Crash* and *The Diamond Age* (one of the best SF books I've come across in a long time). As he talked about the nature of networked technology and what he hoped to see emerge, he mentioned that a couple of blocks down the street was the pawn shop where Jimi Hendrix bought his first -guitar. His point was that if a cheap guitar hadn’t been available, -Hendrix’s unique talent would never have emerged. Similarly, he views +guitar. His point was that if a cheap guitar hadn't been available, +Hendrix's unique talent would never have emerged. Similarly, he views the networking of society as a way to get affordable creative tools to many people, so as much talent as possible can be unearthed and developed. Extend that to programming. The way it should work is that a steady flow -of information circulates, so that everyone can do the best work they’re -capable of. The idea is that I don’t gain by intellectually +of information circulates, so that everyone can do the best work they're +capable of. The idea is that I don't gain by intellectually impoverishing you, and vice-versa; as we both compete and (intentionally or otherwise) share ideas, both our products become better, so the market grows larger and everyone benefits. -That’s the way things have worked with programming for a long time. So +That's the way things have worked with programming for a long time. So far as I can see it has worked remarkably well, and the recent signs of change make me concerned about the future of our profession. -Things aren’t changing *everywhere*, though; over the past year, I’ve +Things aren't changing *everywhere*, though; over the past year, I've circulated a good bit of info about 3-D graphics, and plan to keep on -doing it as long as I can. Next, we’re going to take a look at 3-D +doing it as long as I can. Next, we're going to take a look at 3-D clipping. ### 3-D Clipping Basics {#Heading3} @@ -100,7 +100,7 @@ was, so I was pleasantly surprised when I actually got around to doing it and found that it was quite straightforward, after all. At heart, 3-D clipping is nothing more than evaluating whether and where a line intersects a plane; in this context, the plane is considered to have an -“inside” (a side on which points are to be kept) and an “outside” (a +"inside" (a side on which points are to be kept) and an "outside" (a side on which points are to be removed or clipped). We can easily extend this single operation to polygon clipping, working with the line segments that form the edges of a polygon. @@ -108,26 +108,26 @@ segments that form the edges of a polygon. The most common application of 3-D clipping is as part of the process of hidden surface removal. In this application, the four planes that make up the view volume, or view frustum, are used to clip away parts of -polygons that aren’t visible. Sometimes this process includes clipping +polygons that aren't visible. Sometimes this process includes clipping to near and far plane, to restrict the depth of the scene. Other applications include clipping to splitting planes while building BSP trees, and clipping moving objects to convex sectors such as BSP leaves. -The clipping principles I’ll cover apply to any sort of 3-D clipping -task, but clipping to the frustum is the specific context in which I’ll +The clipping principles I'll cover apply to any sort of 3-D clipping +task, but clipping to the frustum is the specific context in which I'll discuss clipping below. -In a commercial application, you wouldn’t want to clip every single +In a commercial application, you wouldn't want to clip every single polygon in the scene database individually. As I mentioned in the last chapter, the use of bounding volumes to cull chunks of the scene database that fall entirely outside the frustum, without having to consider each polygon separately, is an important performance aspect of -scene rendering. Once that’s done, however, you’re still left with a set +scene rendering. Once that's done, however, you're still left with a set of polygons that may be entirely inside, or partially or completely -outside, the frustum. In this chapter, I’m going to talk about how to -clip those remaining polygons. I’ll focus on the basics of 3-D clipping, -the stuff I wish I’d known when I started doing 3-D. There are plenty of +outside, the frustum. In this chapter, I'm going to talk about how to +clip those remaining polygons. I'll focus on the basics of 3-D clipping, +the stuff I wish I'd known when I started doing 3-D. There are plenty of ways to speed up clipping under various circumstances, some of which -I’ll mention, but the material covered below will give you the tools you +I'll mention, but the material covered below will give you the tools you need to implement functional 3-D clipping. ------------------------ --------------------------------- -------------------- diff --git a/65-02.md b/65-02.md index b694c2c..e07dc95 100644 --- a/65-02.md +++ b/65-02.md @@ -10,13 +10,13 @@ clipped by (intersects) the plane at all and, if it is clipped, calculating the point of intersection. Before we can intersect a line segment with a plane, we must first -define how we’ll represent the line segment and the plane. The segment +define how we'll represent the line segment and the plane. The segment will be represented in the obvious way by the (x,y,z) coordinates of its two endpoints; this extends well to polygons, where each vertex is an (x,y,z) point. Planes can be described in many ways, among them are three points on the plane, a point on the plane and a unit normal, or a -unit normal and a distance from the origin along the normal; we’ll use -the latter definition. Further, we’ll define the normal to point to the +unit normal and a distance from the origin along the normal; we'll use +the latter definition. Further, we'll define the normal to point to the inside (unclipped side) of the plane. The structures for points, polygons, and planes are shown in Listing 65.1. @@ -58,8 +58,8 @@ polygons, and planes are shown in Listing 65.1. Given a line segment, and a plane to which to clip the segment, the first question is whether the segment is entirely on the inside or the outside of the plane, or intersects the plane. If the segment is on the -inside, then the segment is not clipped by the plane, and we’re done. If -it’s on the outside, then it’s entirely clipped, and we’re likewise +inside, then the segment is not clipped by the plane, and we're done. If +it's on the outside, then it's entirely clipped, and we're likewise done. If it intersects the plane, then we have to remove the clipped portion of the line by replacing the endpoint on the outside of the plane with the point of intersection between the line and the plane. @@ -68,7 +68,7 @@ The way to answer this question is to find out which side of the plane each endpoint is on, and the dot product is the right tool for the job. As you may recall from Chapter 61, dotting any vector with a unit normal returns the length of the projection of that vector onto the normal. -Therefore, if we take any point and dot it with the plane normal we’ll +Therefore, if we take any point and dot it with the plane normal we'll find out how far from the origin the point is, as measured along the plane normal. Another way to think of this is to say that the dot product of a point and the plane normal returns how far from the origin @@ -84,24 +84,24 @@ of the point and the normal; a simple comparison of the two values suffices to tell us which side of the plane the point is on. If the dot product of the point and the plane normal is greater than the plane distance, then the point is in front of the plane (inside the volume -being clipped to); if it’s less, then the point is outside the volume +being clipped to); if it's less, then the point is outside the volume and should be clipped. After we do this twice, once for each line endpoint, we know everything necessary to categorize our line segment. If both endpoints are on the -same side of the plane, there’s nothing more to do, because the line is -either completely inside or completely outside; otherwise, it’s on to +same side of the plane, there's nothing more to do, because the line is +either completely inside or completely outside; otherwise, it's on to the next step, clipping the line to the plane by replacing the outside vertex with the point of intersection of the line and the plane. Happily, it turns out that we already have all of the information we need to do this. From our earlier tests, we already know the length from the plane, -measured along the normal, to the inside endpoint; that’s just the +measured along the normal, to the inside endpoint; that's just the distance, along the normal, of the inside endpoint from the origin (the dot product of the endpoint with the normal), minus the plane distance, as shown in Figure 65.1. We also know the length of the line segment, -again measured as projected onto the normal; that’s the difference +again measured as projected onto the normal; that's the difference between the distances along the normal of the inside and outside endpoints from the origin. The ratio of these two lengths is the fraction of the segment that remains after clipping. If we scale the x, @@ -114,18 +114,18 @@ point of intersection. Line clipping is fine for wireframe rendering, but what we really want to do is polygon rendering of solid models, which requires polygon clipping. As with line segments, the clipping process with polygons is -to determine if they’re inside, outside, or partially inside the clip +to determine if they're inside, outside, or partially inside the clip volume, lopping off any vertices that are outside the clip volume and substituting vertices at the intersection between the polygon and the clip plane, as shown in Figure 65.2. An easy way to clip a polygon is to decompose it into a set of edges, -and clip each edge separately as a line segment. Let’s define a polygon +and clip each edge separately as a line segment. Let's define a polygon as a set of vertices that wind clockwise around the outside of the polygonal area, as viewed from the front side of the polygon; the edges are implicitly defined by the order of the vertices. Thus, an edge is the line segment described by the two adjacent vertices that form its -endpoints. We’ll clip a polygon by clipping each edge individually, +endpoints. We'll clip a polygon by clipping each edge individually, emitting vertices for the resulting polygon as appropriate, depending on the clipping state of the edge. If the start point of the edge is inside, that point is added to the output polygon. Then, if the start diff --git a/65-03.md b/65-03.md index 9a35d8e..7b226ce 100644 --- a/65-03.md +++ b/65-03.md @@ -20,7 +20,7 @@ { nextvert = (i + 1) % pin->numverts; - // Keep the current vertex if it’s inside the plane + // Keep the current vertex if it's inside the plane if (curin) *poutvert++ = *pinvert; @@ -56,22 +56,22 @@ } Believe it or not, this technique, applied in turn to each edge, is all -that’s needed to clip a polygon to a plane. Better yet, a polygon can be +that's needed to clip a polygon to a plane. Better yet, a polygon can be clipped to multiple planes by repeating the above process once for each clip plane, with each interation trimming away any part of the polygon -that’s clipped by that particular plane. +that's clipped by that particular plane. -One particularly useful aspect of 3-D clipping is that if you’re drawing +One particularly useful aspect of 3-D clipping is that if you're drawing texture mapped polygons, texture coordinates can be clipped in exactly the same way as (x,y,z) coordinates. In fact, the very same fraction -that’s used to advance x, y, and z from the inside point to the point of +that's used to advance x, y, and z from the inside point to the point of intersection with the clip plane can be used to advance the texture coordinates as well, so only one extra multiply and one extra add are required for each texture coordinate. #### Clipping to the Frustum {#Heading6} -Given a polygon-clipping function, it’s easy to clip to the frustum: set +Given a polygon-clipping function, it's easy to clip to the frustum: set up the four planes for the sides of the frustum, with another one or two planes for near and far clipping, if desired; next, clip each potentially visible polygon to each plane in turn; then draw whatever @@ -378,7 +378,7 @@ is available on the CD-ROM in the file DDJCLIP.ZIP. } pobject = pobject->pnext; } - // We’ve drawn the frame; copy it to the screen + // We've drawn the frame; copy it to the screen hdcScreen = GetDC(hwndOutput); holdpal = SelectPalette(hdcScreen, hpalDIB, FALSE); RealizePalette(hdcScreen); diff --git a/65-04.md b/65-04.md index 277554b..817456c 100644 --- a/65-04.md +++ b/65-04.md @@ -11,13 +11,13 @@ regarding range and precision. Floating-point is much easier—and, with the Pentium generation of processors, is generally comparable in speed. In fact, for some operations, such as multiplication in general and division when the floating-point unit is in single-precision mode, -floating-point is much faster. Check out Chris Hecker’s column in the +floating-point is much faster. Check out Chris Hecker's column in the February 1996 *Game Developer* for an interesting discussion along these lines. Second, the planes that form the frustum are shifted ever so slightly inward from their proper positions at the edge of the field of view. -This guarantees that it’s never possible to generate a visible vertex +This guarantees that it's never possible to generate a visible vertex exactly at the eyepoint, averting the divide-by-zero error that such a vertex would cause when projected and at no performance cost. @@ -40,12 +40,12 @@ objects began to warp visibly after many rotations. Fourth, Listing 65.3 processes each input polygon into a clipped polygon, one line segment at a time. It would be more efficient to -process all the vertices, categorizing whether and how they’re clipped, +process all the vertices, categorizing whether and how they're clipped, and then perform a test such as the Cohen-Sutherland outcode test to detect trivial acceptance (the polygon is entirely inside) and sometimes trivial rejection (the polygon is fully outside) without ever dealing with the edges, and to identify which planes actually need to be clipped -against, as discussed in “Line-Segment Clipping Revisited,” *Dr. Dobb’s +against, as discussed in "Line-Segment Clipping Revisited," *Dr. Dobb's Journal*, January 1996. Some clipping approaches also minimize the number of intersection calculations when a segment is clipped by multiple planes. Further, Listing 65.3 clips a polygon against each @@ -88,7 +88,7 @@ For a game, 90 degrees is a pretty good field of view, but can we get the same sort of efficient clipping if we need some other field of view? Sure. All we have to do is scale the x and y results of the world-to-view transformation to account for the field of view, so that -the coordinates lie in a viewspace that’s normalized such that the +the coordinates lie in a viewspace that's normalized such that the frustum planes extend along lines of x==z and y==z. The resulting visible projected points span the range -1 to 1 (before scaling up to get pixel coordinates), just as with a 90-degree field of view, so the @@ -96,27 +96,27 @@ rest of the drawing pipeline remains unchanged. Better yet, there is no cost in performance because the adjustment can be added to the transformation matrix. -I didn’t implement normalized clipping in Listing 65.3 because I wanted +I didn't implement normalized clipping in Listing 65.3 because I wanted to illustrate the general 3-D clipping mechanism without additional complications, and because for many applications the dot product (which, after all, takes only 10-20 cycles on a Pentium) is sufficient. However, -the more frustum clipping you’re doing, especially if most of the +the more frustum clipping you're doing, especially if most of the polygons are trivially visible, the more attractive the performance advantages of normalized clipping become. ### Further Reading {#Heading9} You now have the basics of 3-D clipping, but because fast clipping is -central to high-performance 3-D, there’s a lot more to be learned. One +central to high-performance 3-D, there's a lot more to be learned. One good place for further reading is Foley and van Dam; another is *Procedural Elements of Computer Graphics*, by David F. Rogers. Read and -understand either of these books, and you’ll know everything you need +understand either of these books, and you'll know everything you need for world-class clipping. And, as you read, you might take a moment to consider how wonderful it -is that anyone who’s interested can tap into so much expert knowledge +is that anyone who's interested can tap into so much expert knowledge for the price of a book—or, on the Internet, for free—with no strings -attached. Our part of the world is a pretty good place right now, isn’t +attached. Our part of the world is a pretty good place right now, isn't it? ------------------------ --------------------------------- -------------------- diff --git a/66-01.md b/66-01.md index 97e81c7..0f30ff7 100644 --- a/66-01.md +++ b/66-01.md @@ -3,47 +3,47 @@ ------------------------ --------------------------------- -------------------- Chapter 66\ - Quake’s Hidden-Surface Removal {#Heading1} + Quake's Hidden-Surface Removal {#Heading1} ------------------------------- ### Struggling with Z-Order Solutions to the Hidden Surface Problem {#Heading2} -Okay, I admit it: I’m sick and tired of classic rock. Admittedly, it’s +Okay, I admit it: I'm sick and tired of classic rock. Admittedly, it's been a while, about 20 years, since I was last excited to hear anything by the Cars or Boston, and I was never particularly excited in the first place about Bob Seger or Queen, to say nothing of Elvis, so some things -haven’t changed. But I knew something was up when I found myself +haven't changed. But I knew something was up when I found myself changing the station on the Allman Brothers and Steely Dan and Pink -Floyd and, God help me, the Beatles (just stuff like “Hello Goodbye” and -“I’ll Cry Instead,” though, not “Ticket to Ride” or “A Day in the Life”; -I’m not *that* far gone). It didn’t take long to figure out what the -problem was; I’d been hearing the same songs for a quarter-century, and +Floyd and, God help me, the Beatles (just stuff like "Hello Goodbye" and +"I'll Cry Instead," though, not "Ticket to Ride" or "A Day in the Life"; +I'm not *that* far gone). It didn't take long to figure out what the +problem was; I'd been hearing the same songs for a quarter-century, and I was bored. I tell you this by way of explaining why it was that when my daughter and I drove back from dinner the other night, the radio in my car was -tuned, for the first time ever, to a station whose slogan is “There is -no alternative.” +tuned, for the first time ever, to a station whose slogan is "There is +no alternative." -Now, we’re talking here about a 10-year-old who worships the Beatles and +Now, we're talking here about a 10-year-old who worships the Beatles and has been raised on a steady diet of oldies. She loves melodies, catchy -songs, and good singers, none of which you’re likely to find on an -alternative rock station. So it’s no surprise that when I turned on the -radio, the first word out of her mouth was “Yuck!” +songs, and good singers, none of which you're likely to find on an +alternative rock station. So it's no surprise that when I turned on the +radio, the first word out of her mouth was "Yuck!" What did surprise me was that after listening for a while, she said, -“You know, Dad, it’s actually kind of interesting.” +"You know, Dad, it's actually kind of interesting." Apart from giving me a clue as to what sort of music I can expect to -hear blasting through our house when she’s a teenager, her quick uptake +hear blasting through our house when she's a teenager, her quick uptake on alternative rock (versus my decades-long devotion to the music of my -youth) reminded me of something that it’s easy to forget as we become -older and more set in our ways. It reminded me that it’s essential to +youth) reminded me of something that it's easy to forget as we become +older and more set in our ways. It reminded me that it's essential to keep an open mind, and to be willing, better yet, eager, to try new things. Programmers tend to become attached to familiar approaches, and are inclined to stick with whatever is currently doing the job adequately well, but in programming there are always alternatives, and -I’ve found that they’re often worth considering. +I've found that they're often worth considering. Not that I should have needed any reminding, considering the ever-evolving nature of Quake. @@ -51,8 +51,8 @@ ever-evolving nature of Quake. ### Creative Flux and Hidden Surfaces {#Heading3} Back in Chapter 64, I described the creative flux that led to John -Carmack’s decision to use a precalculated potentially visible set (PVS) -of polygons for each possible viewpoint in Quake, the game we’re +Carmack's decision to use a precalculated potentially visible set (PVS) +of polygons for each possible viewpoint in Quake, the game we're developing here at id Software. The precalculated PVS meant that instead of having to spend a lot of time searching through the world database to find out which polygons were visible from the current viewpoint, we @@ -60,23 +60,23 @@ could simply draw all the polygons in the PVS from back-to-front (getting the ordering courtesy of the world BSP tree) and get the correct scene drawn with no searching at all; letting the back-to-front drawing perform the final stage of hidden-surface removal (HSR). This -was a terrific idea, but it was far from the end of the road for Quake’s +was a terrific idea, but it was far from the end of the road for Quake's design. #### Drawing Moving Objects {#Heading4} For one thing, there was still the question of how to sort and draw moving objects properly; in fact, this is the single technical question -I’ve been asked most often in recent months, so I’ll take a moment to +I've been asked most often in recent months, so I'll take a moment to address it here. The primary problem is that a moving model can span multiple BSP leaves, with the leaves that are touched varying as the model moves; that, together with the possibility of multiple models in -one leaf, means there’s no easy way to use BSP order to draw the models +one leaf, means there's no easy way to use BSP order to draw the models in correctly sorted order. When I wrote Chapter 64, we were drawing sprites (such as explosions), moveable BSP models (such as doors), and polygon models (such as monsters) by clipping each into all the leaves it touched, then drawing the appropriate parts as each BSP leaf was -reached in back-to-front traversal. However, this didn’t solve the issue +reached in back-to-front traversal. However, this didn't solve the issue of sorting multiple moving models in a single leaf against each other, and also left some ugly sorting problems with complex polygon models. @@ -86,7 +86,7 @@ each pixel, we compare its distance, or z, value with the z value of the pixel currently on the screen, drawing only if the new pixel is nearer than the current one.) First, we draw the basic world, walls, ceilings, and the like. No z-buffer *testing* is involved at this point (the world -visible surface determination is done in a different way, as we’ll see +visible surface determination is done in a different way, as we'll see soon); however, we do *fill* the z-buffer with the z values (actually, 1/z values, as discussed below) for all the world pixels. Z-filling is a much faster process than z-buffering the entire world would be, because @@ -97,8 +97,8 @@ around. #### Performance Impact {#Heading5} -Whenever a z-buffer is involved, the questions inevitably are: What’s -the memory footprint and what’s the performance impact? Well, the memory +Whenever a z-buffer is involved, the questions inevitably are: What's +the memory footprint and what's the performance impact? Well, the memory footprint at 320x200 is 128K, not trivial but not a big deal for a game that requires 8 MB to run. The performance impact is about 10 percent for z-filling the world, and roughly 20 percent (with lots of variation) @@ -114,9 +114,9 @@ performance cost. As I said above, in the Quake architecture, the world itself is drawn first, without z-buffer reads or compares, but filling the z-buffer with -the world polygons’ z values, and then the moving objects are drawn atop -the world, using full z-buffering. Thus far, I’ve discussed how to draw -moving objects. For the rest of this chapter, I’m going to talk about +the world polygons' z values, and then the moving objects are drawn atop +the world, using full z-buffering. Thus far, I've discussed how to draw +moving objects. For the rest of this chapter, I'm going to talk about the other part of the drawing equation; that is, how to draw the world itself, where the entire world is stored as a single BSP tree and never moves. diff --git a/66-02.md b/66-02.md index e9a05b0..694ef78 100644 --- a/66-02.md +++ b/66-02.md @@ -2,7 +2,7 @@ [Previous](66-01.html) [Table of Contents](index.html) [Next](66-03.html) ------------------------ --------------------------------- -------------------- -As you may recall from Chapter 64, we’re concerned with both raw +As you may recall from Chapter 64, we're concerned with both raw performance and level performance. That is, we want the drawing code to run as fast as possible, but we also want the difference in drawing speed between the average scene and the slowest-drawing scene to be as @@ -24,7 +24,7 @@ overdrawn by nearer polygons. Raw performance in general was also reduced by the typical 50% overdraw resulting from drawing everything in the PVS. So, although drawing the PVS back-to-front as the final HSR stage worked and was an improvement over previous designs, it was not -ideal. Surely, John thought, there’s a better way to leverage the PVS +ideal. Surely, John thought, there's a better way to leverage the PVS than back-to-front drawing. And indeed there is. @@ -49,7 +49,7 @@ this approach consists of turning each polygon into a set of spans, as shown in Figure 66.1, and then sorting and clipping the spans against each other until only the visible portions of visible spans are left to be drawn, as shown in Figure 66.2. This may sound a lot like z-buffering -(which is simply too slow for use in drawing the world, although it’s +(which is simply too slow for use in drawing the world, although it's fine for smaller moving objects, as described earlier), but there are crucial differences. @@ -63,8 +63,8 @@ becomes a per-span operation with sorted spans, and because of the coherence implicit in a span list, each edge is sorted only against some of the spans on the same line and is clipped only to the few spans that it overlaps horizontally. Although complex scenes still take longer to -process than simple scenes, the worst case isn’t as bad as with the beam -tree or back-to-front approaches, because there’s no overdraw or +process than simple scenes, the worst case isn't as bad as with the beam +tree or back-to-front approaches, because there's no overdraw or scanning of hidden pixels, because complexity is limited to pixel resolution and because span coherence tends to limit the worst-case sorting in any one area of the screen. As a bonus, the output of sorted @@ -72,24 +72,24 @@ spans is in precisely the form that a low-level rasterizer needs, a set of span descriptors, each consisting of a start coordinate and a length. In short, the sorted spans approach meets our original criteria pretty -well; although it isn’t zero-cost, it’s not horribly expensive, it +well; although it isn't zero-cost, it's not horribly expensive, it completely eliminates both overdraw and pixel scanning of obscured portions of polygons and it tends to level worst-case performance. We -wouldn’t want to rely on sorted spans alone as our hidden-surface +wouldn't want to rely on sorted spans alone as our hidden-surface mechanism, but the precalculated PVS reduces the number of polygons to a level that sorted spans can handle quite nicely. -So we’ve found the approach we need; now it’s just a matter of writing -some code and we’re on our way, right? Well, yes and no. Conceptually, -the sorted-spans approach is simple, but it’s surprisingly difficult to +So we've found the approach we need; now it's just a matter of writing +some code and we're on our way, right? Well, yes and no. Conceptually, +the sorted-spans approach is simple, but it's surprisingly difficult to implement, with a couple of major design choices to be made, a subtle -mathematical element, and some tricky gotchas that I’ll have to defer -until Chapter 67. Let’s look at the design choices first. +mathematical element, and some tricky gotchas that I'll have to defer +until Chapter 67. Let's look at the design choices first. ### Edges versus Spans {#Heading8} The first design choice is whether to sort spans or edges (both of which -fall into the general category of “sorted spans”). Although the results +fall into the general category of "sorted spans"). Although the results are the same both ways, a list of spans to be drawn, with no overdraw, the implementations and performance implications are quite different, because the sorting and clipping are performed using very different data diff --git a/66-03.md b/66-03.md index ec0d3f9..ac77778 100644 --- a/66-03.md +++ b/66-03.md @@ -16,7 +16,7 @@ current edge, and the current x coordinate is recorded in the polygon that is now the nearest. This saved coordinate later serves as the start of the span emitted when the new nearest polygon ceases to be in front. -Don’t worry if you didn’t follow all of that; the above is just a quick +Don't worry if you didn't follow all of that; the above is just a quick overview of edge-sorting to help make the rest of this chapter a little clearer. My thorough discussion of the topic will be in Chapter 67. @@ -34,12 +34,12 @@ the states of the spans are explicit at all times, and all work is done directly with spans. Both span-sorting and edge-sorting work well, and both have been -employed successfully in commercial projects. We’ve chosen to use +employed successfully in commercial projects. We've chosen to use edge-sorting in Quake partly because it seems inherently more efficient, with excellent horizontal coherence that makes for minimal time spent sorting, in contrast with the potentially costly sorting into linked lists that span-sorting can involve. A more important reason, though, is -that with edge-sorting we’re able to share edges between adjacent +that with edge-sorting we're able to share edges between adjacent polygons, and that cuts the work involved in sorting, clipping, and rasterizing edges nearly in half, while also shrinking the world database quite a bit due to the sharing. @@ -49,11 +49,11 @@ database quite a bit due to the sharing. encountered in the AEL.* One final advantage of edge-sorting is that it makes no distinction -between convex and concave polygons. That’s not an important +between convex and concave polygons. That's not an important consideration for most graphics engines, but in Quake, edge clipping, transformation, projection, and sorting have become a major bottleneck, -so we’re doing everything we can to get the polygon and edge counts -down, and concave polygons help a lot in that regard. While it’s +so we're doing everything we can to get the polygon and edge counts +down, and concave polygons help a lot in that regard. While it's possible to handle concave polygons with span-sorting, that can involve significant performance penalties. @@ -61,24 +61,24 @@ significant performance penalties. **Figure 66.4**  *Deactivating a polygon when a trailing edge is encountered in the AEL.* -Nonetheless, there’s no cut-and-dried answer as to which approach is +Nonetheless, there's no cut-and-dried answer as to which approach is better. In the end, span-sorting and edge-sorting amount to the same functionality, and the choice between them is a matter of whatever you -feel most comfortable with. In Chapter 67, I’ll go into considerable -detail about edge-sorting, complete with a full implementation. I’m +feel most comfortable with. In Chapter 67, I'll go into considerable +detail about edge-sorting, complete with a full implementation. I'm going the spend the rest of this chapter laying the foundation for Chapter 67 by discussing sorting keys and 1/z calculation. In the -process, I’m going to have to make a few forward references to aspects -of edge-sorting that I haven’t yet covered in detail; my apologies, but -it’s unavoidable, and all should become clear by the end of Chapter 67. +process, I'm going to have to make a few forward references to aspects +of edge-sorting that I haven't yet covered in detail; my apologies, but +it's unavoidable, and all should become clear by the end of Chapter 67. ### Edge-Sorting Keys {#Heading9} -Now that we know we’re going to sort edges, using them to emit spans for +Now that we know we're going to sort edges, using them to emit spans for the polygons nearest the viewer, the question becomes: How can we tell -which polygons are nearest? Ideally, we’d just store a sorting key in -each polygon, and whenever a new edge came along, we’d compare its -surface’s key to the keys of other currently active polygons, and could +which polygons are nearest? Ideally, we'd just store a sorting key in +each polygon, and whenever a new edge came along, we'd compare its +surface's key to the keys of other currently active polygons, and could easily tell which polygon was nearest. That sounds too good to be true, but it is possible. If, for example, @@ -88,37 +88,37 @@ for example, if you walk the BSP back-to-front, assigning each polygon an incrementally higher key as you reach it, polygons with higher keys are guaranteed to be in front of polygons with lower keys. This is the approach Quake used for a while, although a different approach is now -being used, for reasons I’ll explain shortly. +being used, for reasons I'll explain shortly. -If you don’t happen to have a BSP or similar data structure handy, or if -you have lots of moving polygons (BSPs don’t handle moving polygons very +If you don't happen to have a BSP or similar data structure handy, or if +you have lots of moving polygons (BSPs don't handle moving polygons very efficiently), another way to accomplish your objectives would be to sort all the polygons against one another before drawing the scene, assigning appropriate keys based on their spatial relationships in viewspace. Unfortunately, this is generally an extremely slow task, because every polygon must be compared to every other polygon. There are techniques to -improve the performance of polygon sorts, but I don’t know of anyone -who’s doing general polygon sorts of complex scenes in realtime on a PC. +improve the performance of polygon sorts, but I don't know of anyone +who's doing general polygon sorts of complex scenes in realtime on a PC. An alternative is to sort by z distance from the viewer in screenspace, an approach that dovetails nicely with the excellent spatial coherence of edge-sorting. As each new edge is encountered on a scan line, the -corresponding polygon’s z distance can be calculated and compared to the -other polygons’ distances, and the polygon can be sorted into the APL +corresponding polygon's z distance can be calculated and compared to the +other polygons' distances, and the polygon can be sorted into the APL accordingly. Getting z distances can be tricky, however. Remember that we need to be able to calculate z at any arbitrary point on a polygon, because an edge may occur and cause its polygon to be sorted into the APL at any point on the screen. We could calculate z directly from the screen x and y -coordinates and the polygon’s plane equation, but unfortunately this -can’t be done very quickly, because the z for a plane doesn’t vary -linearly in screenspace; however, 1/z *does* vary linearly, so we’ll use -that instead. (See Chris Hecker’s 1995 series of columns on texture +coordinates and the polygon's plane equation, but unfortunately this +can't be done very quickly, because the z for a plane doesn't vary +linearly in screenspace; however, 1/z *does* vary linearly, so we'll use +that instead. (See Chris Hecker's 1995 series of columns on texture mapping in *Game Developer* magazine for a discussion of screenspace linearity and gradients for 1/z.) Another advantage of using 1/z is that its resolution increases with decreasing distance, meaning that by using -1/z, we’ll have better depth resolution for nearby features, where it +1/z, we'll have better depth resolution for nearby features, where it matters most. ------------------------ --------------------------------- -------------------- diff --git a/66-04.md b/66-04.md index 578be89..99ec40a 100644 --- a/66-04.md +++ b/66-04.md @@ -12,10 +12,10 @@ per pixel across each span. A better solution is to calculate 1/z directly from the plane equation and the screen x and y of the pixel of interest. The equation is -1/z = (a/d)x’ - (b/d)y’ + c/d +1/z = (a/d)x' - (b/d)y' + c/d where z is the viewspace z coordinate of the point on the plane that -projects to screen coordinate (x’,y’) (the origin for this calculation +projects to screen coordinate (x',y') (the origin for this calculation is the center of projection, the point on the screen straight ahead of the viewpoint), [a b c] is the plane normal in viewspace, and d is the distance from the viewspace origin to the plane along the normal. @@ -24,30 +24,30 @@ per-plane constants. The full 1/z calculation requires two multiplies and two adds, all of which should be floating-point to avoid range errors. That much -floating-point math sounds expensive but really isn’t, especially on a -Pentium, where a plane’s 1/z value at any point can be calculated in as +floating-point math sounds expensive but really isn't, especially on a +Pentium, where a plane's 1/z value at any point can be calculated in as little as six cycles in assembly language. #### Where That 1/Z Equation Comes From {#Heading10} -For those who are interested, here’s a quick derivation of the 1/z +For those who are interested, here's a quick derivation of the 1/z equation. The plane equation for a plane is ax + by + cz - d = 0 where x and y are viewspace coordinates, and a, b, c, d, and z are -defined above. If we substitute x=x’z and y=-y’z (from the definition of +defined above. If we substitute x=x'z and y=-y'z (from the definition of the perspective projection, with y inverted because y increases upward in viewspace but downward in screenspace), and do some rearrangement, we get: -z = d / (ax’ - by’ + c) +z = d / (ax' - by' + c) Inverting and distributing yields: -= ax’/d - by’/d + c/d += ax'/d - by'/d + c/d -We’ll see 1/z sorting in action in Chapter 67. +We'll see 1/z sorting in action in Chapter 67. #### Quake and Z-Sorting {#Heading11} @@ -59,21 +59,21 @@ a BSP-ordered key, so why have we switched Quake to 1/z? The primary reason is to reduce the number of polygons. Drawing in BSP order means following certain rules, including the rule that polygons must be split if they cross BSP planes. This splitting increases the -numbers of polygons and edges considerably. By sorting on 1/z, we’re +numbers of polygons and edges considerably. By sorting on 1/z, we're able to leave polygons unsplit but still get correct drawing order, so we have far fewer edges to process and faster drawing overall, despite the added cost of 1/z sorting. Another advantage of 1/z sorting is that it solves the sorting issues I mentioned at the start involving moving models that are themselves small -BSP trees. Sorting in world BSP order wouldn’t work here, because these -models are separate BSPs, and there’s no easy way to work them into the -world BSP’s sequence order. We don’t want to use z-buffering for these -models because they’re often large objects such as doors, and we don’t +BSP trees. Sorting in world BSP order wouldn't work here, because these +models are separate BSPs, and there's no easy way to work them into the +world BSP's sequence order. We don't want to use z-buffering for these +models because they're often large objects such as doors, and we don't want to lose the overdraw-reduction benefits that closed doors provide when drawn through the edge list. With sorted spans, the edges of moving BSP models are simply placed in the edge list (first clipping polygons -so they don’t cross any solid world surfaces, to avoid complications +so they don't cross any solid world surfaces, to avoid complications associated with interpenetration), along with all the world edges, and 1/z sorting takes care of the rest. @@ -84,15 +84,15 @@ pages, and it may not all connect together yet in your mind. The code and accompanying explanation in the next chapter should help; if you want to peek ahead, the code is available on the CD-ROM as DDJZSORT.ZIP in the directory for Chapter 67. You may also want to take a look at -Foley and van Dam’s *Computer Graphics* or Rogers’ *Procedural Elements +Foley and van Dam's *Computer Graphics* or Rogers' *Procedural Elements for Computer Graphics*. -As I write this, it’s unclear whether Quake will end up sorting edges by -BSP order or 1/z. Actually, there’s no guarantee that sorted spans in +As I write this, it's unclear whether Quake will end up sorting edges by +BSP order or 1/z. Actually, there's no guarantee that sorted spans in any form will be the final design. Sometimes it seems like we change graphics engines as often as they play Elvis on the ‘50s oldies stations (but, one would hope, with more aesthetically pleasing results!) and no -doubt we’ll be considering the alternatives right up until the day we +doubt we'll be considering the alternatives right up until the day we ship. ------------------------ --------------------------------- -------------------- diff --git a/67-01.md b/67-01.md index 6e5adc1..862f290 100644 --- a/67-01.md +++ b/67-01.md @@ -11,40 +11,40 @@ Chapter 67\ In Chapter 66, we dove headlong into the intricacies of hidden surface removal by way of z-sorted (actually, 1/z-sorted) spans. At the end of that chapter, I noted that we were currently using 1/z-sorted spans in -Quake, but it was unclear whether we’d switch back to BSP order. Well, -some time after that writing, it’s become clear: We’re back to sorting +Quake, but it was unclear whether we'd switch back to BSP order. Well, +some time after that writing, it's become clear: We're back to sorting spans by BSP order. -In Robert A. Heinlein’s wonderful story “The Man Who Sold the Moon,” the +In Robert A. Heinlein's wonderful story "The Man Who Sold the Moon," the chief engineer of the Moon rocket project tries to figure out how to get a payload of three astronauts to the Moon and back. He starts out with a -four-stage rocket design, but finds that it won’t do the job, so he adds -a fifth stage. The fifth stage helps, but not quite enough, “Because,” -he explains, “I’ve had to add in too much dead weight, that’s why.” (The +four-stage rocket design, but finds that it won't do the job, so he adds +a fifth stage. The fifth stage helps, but not quite enough, "Because," +he explains, "I've had to add in too much dead weight, that's why." (The dead weight is the control and safety equipment that goes with the fifth stage.) He then tries adding yet another stage, only to find that the sixth stage actually results in a net slowdown. In the end, he has to give up on the three-person design and build a one-person spacecraft instead. -1/z-sorted spans in Quake turned out pretty much the same way, as we’ll -see in a moment. First, though, I’d like to note up front that this +1/z-sorted spans in Quake turned out pretty much the same way, as we'll +see in a moment. First, though, I'd like to note up front that this chapter is very technical and builds heavily on material I covered -earlier in this section of the book; if you haven’t already read +earlier in this section of the book; if you haven't already read Chapters 59 through 66, you really should. Make no mistake about it, this is commercial-quality stuff; in fact, the code in this chapter uses the same sorting technique as the test version of Quake, QTEST1.ZIP, that id Software placed on the Internet in early March 1996. This material is the Real McCoy, true reports from the leading edge, and I -trust that you’ll be patient if careful rereading and some occasional +trust that you'll be patient if careful rereading and some occasional catch-up reading of earlier chapters are required to absorb everything contained herein. Besides, the ultimate reference for any design is -working code, which you’ll find, in part, in Listing 67.1, and in its +working code, which you'll find, in part, in Listing 67.1, and in its entirety in the file DDJZSORT.ZIP on the CD-ROM. ### Quake and Sorted Spans {#Heading3} -As you’ll recall from Chapter 66, Quake uses sorted spans to get zero +As you'll recall from Chapter 66, Quake uses sorted spans to get zero overdraw while rendering the world, thereby both improving overall performance and leveling frame rates by speeding up scenes that would otherwise experience heavy overdraw. Our original design used spans @@ -52,7 +52,7 @@ sorted by BSP order; because we traverse the world BSP tree from front-to-back relative to the viewpoint, the order in which BSP nodes are visited is a guaranteed front-to-back sorting order. We simply gave each node an increasing BSP sequence number as it was visited, set each -polygon’s sort key to the BSP sequence number of the node (BSP splitting +polygon's sort key to the BSP sequence number of the node (BSP splitting plane) it lay on, and used those sort keys when generating spans. (In a change from earlier designs, polygons now are stored on nodes, @@ -64,8 +64,8 @@ of its node has been drawn. This results in less BSP splitting of polygons, which is A Good Thing, as explained below.) This worked flawlessly for the world, but had a couple of downsides. -First, it didn’t address the issue of sorting small, moving BSP models -such as doors; those models could be clipped into the world BSP tree’s +First, it didn't address the issue of sorting small, moving BSP models +such as doors; those models could be clipped into the world BSP tree's leaves and assigned sort keys corresponding to the leaves into which they fell, but there was still the question of how to sort multiple BSP models in the same world leaf against each other. Second, strict BSP @@ -74,7 +74,7 @@ entirely within a single leaf. This can be stretched by putting polygons on nodes, allowing for larger polygons on average, but even then, polygons still need to be split so that every polygon falls within the bounding volume for the node on which it lies. The end result, in either -case, is more and smaller polygons than if BSP order weren’t used—and +case, is more and smaller polygons than if BSP order weren't used—and that, in turn, means lower performance, because more polygons must be clipped, transformed, and projected, more sorting must be done, and more spans must be drawn. @@ -84,23 +84,23 @@ a lot faster. Accordingly, we switched from sorting on BSP order to sorting on 1/z, and left our polygons unsplit. Things did get faster at first, but not as much as we had expected, for two reasons. -First, as the world BSP tree is descended, we clip each node’s bounding -box in turn to see if it’s inside or outside each plane of the view +First, as the world BSP tree is descended, we clip each node's bounding +box in turn to see if it's inside or outside each plane of the view frustum. The clipping results can be remembered, and often allow the -avoidance of some or all clipping for the node’s polygons. For example, +avoidance of some or all clipping for the node's polygons. For example, all polygons in a node that has a trivially accepted bounding box are likewise guaranteed to be unclipped and in the frustum, since they all -lie within the node’s volume and need no further clipping. This +lie within the node's volume and need no further clipping. This efficient clipping mechanism vanished as soon as we stepped out of BSP order, because a polygon was no longer necessarily confined to its -node’s volume. +node's volume. -Second, sorting on 1/z isn’t as cheap as sorting on BSP order, because +Second, sorting on 1/z isn't as cheap as sorting on BSP order, because floating-point calculations and comparisons are involved, rather than -integer compares. So Quake got faster but, like Heinlein’s fifth rocket +integer compares. So Quake got faster but, like Heinlein's fifth rocket stage, there was clear evidence of diminishing returns. -That wasn’t the bad part; after all, even a small speed increase is A +That wasn't the bad part; after all, even a small speed increase is A Good Thing. The real problem was that our initial 1/z sorting proved to be unreliable. We first ran into problems when two forward-facing polygons started at a common edge, because it was hard to tell which one diff --git a/67-02.md b/67-02.md index 2a65b81..db195d9 100644 --- a/67-02.md +++ b/67-02.md @@ -4,33 +4,33 @@ And then yet another crop of sorting errors popped up. -We could have fixed those errors too; we’ll take a quick look at how to +We could have fixed those errors too; we'll take a quick look at how to deal with such cases shortly. However, like the sixth rocket stage, the fixes would have made Quake *slower* than it had been with BSP sorting. So we gave up and went back to BSP order, and now the code is simpler -and sorting works reliably. It’s too bad our experiment didn’t work out, -but it wasn’t wasted time because in trying what we did we learned quite +and sorting works reliably. It's too bad our experiment didn't work out, +but it wasn't wasted time because in trying what we did we learned quite a bit. In particular, we learned that the information provided by a simple, reliable world ordering mechanism, such as a BSP tree, can do more good than is immediately apparent, in terms of both performance and solid code. Nonetheless, sorting on 1/z can be a valuable tool, used in the right -context; drawing a Quake world just doesn’t happen to be such a case. In -fact, sorting on 1/z is how we’re now handling the sorting of multiple +context; drawing a Quake world just doesn't happen to be such a case. In +fact, sorting on 1/z is how we're now handling the sorting of multiple BSP models that lie within the same world leaf in Quake. In this case, -we don’t have the option of using BSP order (because we’re drawing -multiple independent trees), so we’ve set restrictions on the BSP models +we don't have the option of using BSP order (because we're drawing +multiple independent trees), so we've set restrictions on the BSP models to avoid running into the types of 1/z sorting errors we encountered -drawing the Quake world. Next, we’ll look at another application in +drawing the Quake world. Next, we'll look at another application in which sorting on 1/z is quite useful, one where objects move freely -through space. As is so often the case in 3-D, there is no one “right” +through space. As is so often the case in 3-D, there is no one "right" technique, but rather a great many different techniques, each one handy in the right situations. Often, a combination of techniques is beneficial; for example, the combination in Quake of BSP sorting for the world and 1/z sorting for BSP models in the same world leaf. -For the remainder of this chapter, I’m going to look at the three main +For the remainder of this chapter, I'm going to look at the three main types of 1/z span sorting, then discuss a sample 3-D app built around 1/z span sorting. @@ -41,13 +41,13 @@ are treated as sets of screenspace pixel spans, and 1/z (where z is distance from the viewpoint in viewspace, as measured along the viewplane normal) is used to sort the spans so that the nearest span overlapping each pixel is drawn. As I discussed in Chapter 66, in the -sample program we’re actually going to do all our sorting with polygon +sample program we're actually going to do all our sorting with polygon edges, which represent spans in an implicit form. There are three types of 1/z span sorting, each requiring a different implementation. In order of increasing speed and decreasing complexity, they are: intersecting, abutting, and independent. (These are names of -my own devising; I haven’t come across any standard nomenclature in the +my own devising; I haven't come across any standard nomenclature in the literature.) #### Intersecting Span Sorting {#Heading5} @@ -64,12 +64,12 @@ Intersecting is the slowest and most complicated type of span sorting, because it is necessary to compare 1/z values at two points in order to detect interpenetration, and additional work must be done to split the spans as necessary. Thus, although intersecting span sorting certainly -works, it’s not the first choice for performance. +works, it's not the first choice for performance. #### Abutting Span Sorting {#Heading6} Abutting span sorting occurs when polygons that are not part of a -continuous surface can butt up against one another, but don’t +continuous surface can butt up against one another, but don't interpenetrate, as shown in Figure 67.2. This is the sorting used in Quake, where objects like doors often abut walls and floors, and turns out to be more complicated than you might think. The problem is that @@ -77,7 +77,7 @@ when an abutting polygon starts on a given scan line, as with polygon B in Figure 67.2, it starts at exactly the same 1/z value as the polygon it abuts, in this case, polygon A, so additional sorting is needed when these ties happen. Of course, the two-point sorting used for -intersecting polygons would work, but we’d like to find something +intersecting polygons would work, but we'd like to find something faster. As it turns out, the additional sorting for abutting polygons is @@ -106,7 +106,7 @@ between adjacent polygons in Quake, so that the world is a large polygon mesh. When a polygon ends and is followed by an adjacent polygon that shares the edge that just ended, we simply assume that the adjacent polygon sorts relative to other active polygons in the same place as the -one that ended (because the mesh is continuous and there’s no +one that ended (because the mesh is continuous and there's no interpenetration), rather than doing a 1/z sort from scratch. This speeds things up by saving a lot of sorting, but it means that if there is a sorting error, a whole string of adjacent polygons can be sorted diff --git a/67-03.md b/67-03.md index b9d80f2..3dc576a 100644 --- a/67-03.md +++ b/67-03.md @@ -15,7 +15,7 @@ worry about. Independent span sorting is ideal for scenes with lots of moving objects that never actually touch each other, such as a space battle. Next, -we’ll look at an implementation of independent 1/z span sorting. +we'll look at an implementation of independent 1/z span sorting. ### 1/z Span Sorting in Action {#Heading8} @@ -89,7 +89,7 @@ for real-world applications than the 3-D clipping demo from Chapter 65. for (i=0 ; i<3 ; i++) viewvec.v[i] = ppoly->verts[0].v[i] - currentpos.v[i]; - // Use an epsilon here so we don’t get polygons tilted so + // Use an epsilon here so we don't get polygons tilted so // sharply that the gradients are unusable or invalid if (DotProduct (&viewvec, &pplane->normal) < -0.01) return 1; @@ -97,7 +97,7 @@ for real-world applications than the 3-D clipping demo from Chapter 65. } - // Add the polygon’s edges to the global edge table. + // Add the polygon's edges to the global edge table. void AddPolygonEdges (plane_t *plane, polygon2D_t *screenpoly) { double distinv, deltax, deltay, slope; @@ -106,7 +106,7 @@ for real-world applications than the 3-D clipping demo from Chapter 65. numverts = screenpoly->numverts; - // Clamp the polygon’s vertices just in case some very near + // Clamp the polygon's vertices just in case some very near // points have wandered out of range due to floating-point // imprecision for (i=0 ; iverts[nextvert].y); height = bottomy - topy; if (height == 0) - continue; // doesn’t cross any scan lines + continue; // doesn't cross any scan lines if (height < 0) { // Leading edge temp = topy; @@ -172,16 +172,16 @@ for real-world applications than the 3-D clipping demo from Chapter 65. pavailedge->pnextremove = removeedges[bottomy - 1]; removeedges[bottomy - 1] = pavailedge; - // Associate the edge with the surface we’ll create for + // Associate the edge with the surface we'll create for // this polygon pavailedge->psurf = pavailsurf; - // Make sure we don’t overflow the edge array + // Make sure we don't overflow the edge array if (pavailedge < &edges[MAX_EDGES]) pavailedge++; } - // Create the surface, so we’ll know how to sort and draw from + // Create the surface, so we'll know how to sort and draw from // the edges pavailsurf->state = 0; pavailsurf->color = currentcolor; @@ -198,7 +198,7 @@ for real-world applications than the 3-D clipping demo from Chapter 65. xcenter * pavailsurf->zinvstepx - ycenter * pavailsurf->zinvstepy; - // Make sure we don’t overflow the surface array + // Make sure we don't overflow the surface array if (pavailsurf < &surfs[MAX_SURFS]) pavailsurf++; } @@ -261,14 +261,14 @@ for real-world applications than the 3-D clipping demo from Chapter 65. for (pedge=edgehead.pnext ; pedge ; pedge=pedge->pnext) { psurf = pedge->psurf; if (pedge->leading) { - // It’s a leading edge. Figure out where it is + // It's a leading edge. Figure out where it is // relative to the current surfaces and insert in - // the surface stack; if it’s on top, emit the span + // the surface stack; if it's on top, emit the span // for the current top. - // First, make sure the edges don’t cross + // First, make sure the edges don't cross if (++psurf->state == 1) { fx = (double)pedge->x * (1.0 / (double)0x10000); - // Calculate the surface’s 1/z value at this pixel + // Calculate the surface's 1/z value at this pixel zinv = psurf->zinv00 + psurf->zinvstepx * fx + psurf->zinvstepy * fy; // See if that makes it a new top surface @@ -276,7 +276,7 @@ for real-world applications than the 3-D clipping demo from Chapter 65. zinv2 = psurf2->zinv00 + psurf2->zinvstepx * fx + psurf2->zinvstepy * fy; if (zinv >= zinv2) { - // It’s a new top surface + // It's a new top surface // emit the span for the current top x = (pedge->x + 0xFFFF) >> 16; pspan->count = x - psurf2->visxstart; @@ -284,7 +284,7 @@ for real-world applications than the 3-D clipping demo from Chapter 65. pspan->y = y; pspan->x = psurf2->visxstart; pspan->color = psurf2->color; - // Make sure we don’t overflow + // Make sure we don't overflow // the span array if (pspan < &spans[MAX_SPANS]) pspan++; @@ -313,19 +313,19 @@ for real-world applications than the 3-D clipping demo from Chapter 65. } } } else { - // It’s a trailing edge; if this was the top surface, + // It's a trailing edge; if this was the top surface, // emit the span and remove it. - // First, make sure the edges didn’t cross + // First, make sure the edges didn't cross if (—psurf->state == 0) { if (surfstack.pnext == psurf) { - // It’s on top, emit the span + // It's on top, emit the span x = ((pedge->x + 0xFFFF) >> 16); pspan->count = x - psurf->visxstart; if (pspan->count > 0) { pspan->y = y; pspan->x = psurf->visxstart; pspan->color = psurf->color; - // Make sure we don’t overflow + // Make sure we don't overflow // the span array if (pspan < &spans[MAX_SPANS]) pspan++; @@ -427,7 +427,7 @@ for real-world applications than the 3-D clipping demo from Chapter 65. TransformPolygon (&tpoly1, &tpoly2); ProjectPolygon (&tpoly2, &screenpoly); - // Move the polygon’s plane into viewspace + // Move the polygon's plane into viewspace // First move it into worldspace (object relative) tnormal = ppoly[i].plane.normal; plane.distance = ppoly[i].plane.distance + @@ -454,7 +454,7 @@ for real-world applications than the 3-D clipping demo from Chapter 65. ScanEdges (); DrawSpans (); - // We’ve drawn the frame; copy it to the screen + // We've drawn the frame; copy it to the screen hdcScreen = GetDC(hwndOutput); holdpal = SelectPalette(hdcScreen, hpalDIB, FALSE); RealizePalette(hdcScreen); diff --git a/67-04.md b/67-04.md index 3f0001b..97b011a 100644 --- a/67-04.md +++ b/67-04.md @@ -3,7 +3,7 @@ ------------------------ --------------------------------- -------------------- By the same token, Listing 67.1 is quite a bit more complicated than the -earlier code. The earlier code’s HSR consisted of a z-sort of objects, +earlier code. The earlier code's HSR consisted of a z-sort of objects, followed by the drawing of the objects in back-to-front order, one polygon at a time. Apart from the simple object sorter, all that was needed was backface culling and a polygon rasterizer. @@ -13,16 +13,16 @@ 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 -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 +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. -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 +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 +surfaces in no particular order. (A surface is a drawing object that's 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 @@ -81,7 +81,7 @@ leading edge causes its surface to be 1/z-sorted into the surface stack, with a span emitted if necessary. Each trailing edge causes its surface to be removed from the surface stack, again with a span emitted if necessary. As you can see from Listing 67.1, it takes a fair bit of code -to implement this, but all that’s really going on is a surface stack +to implement this, but all that's really going on is a surface stack driven by edge events. ------------------------ --------------------------------- -------------------- diff --git a/67-05.md b/67-05.md index 5b43601..f3a5404 100644 --- a/67-05.md +++ b/67-05.md @@ -4,11 +4,11 @@ #### Implementation Notes {#Heading9} -Finally, a few notes on Listing 67.1. First, you’ll notice that although +Finally, a few notes on Listing 67.1. First, you'll notice that although we clip all polygons to the view frustum in worldspace, we nonetheless later clamp them to valid screen coordinates before adding them to the edge list. This catches any cases where arithmetic imprecision results -in clipped polygon vertices that are a bit outside the frustum. I’ve +in clipped polygon vertices that are a bit outside the frustum. I've only found such imprecision to be significant at very small z distances, so clamping would probably be unnecessary if there were a near clip plane, and might not even be needed in Listing 67.1, because of the @@ -20,7 +20,7 @@ percent of the time leads to sporadic and hard-to-debug errors. There is no separate routine to clear the background in Listing 67.1. Instead, a special background surface at an effectively infinite distance is added, so whenever no polygons are active the background -color is drawn. If desired, it’s a simple matter to flag the background +color is drawn. If desired, it's a simple matter to flag the background surface and draw the background specially. For example, the background could be drawn as a starfield or a cloudy sky. @@ -31,7 +31,7 @@ for the latter case: Storage for the maximum number of vertices per polygon must be allocated in the polygon structures. In a fully polished implementation, vertices would be linked together or pointed to, and would be dynamically allocated from a vertex pool, so each polygon -wouldn’t have to contain enough space for the maximum possible number of +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 @@ -44,9 +44,9 @@ coordinates from floating point to fixed point. Due to this conversion, it is possible, although rare, for a polygon that is viewed nearly edge-on to have a trailing edge that occurs slightly *before* the corresponding leading edge, and the span-generation code will behave -badly if it tries to emit a span for a surface that hasn’t yet started. +badly if it tries to emit a span for a surface that hasn't yet started. It would help performance if this sort of fix-up could be eliminated by -careful arithmetic, but I haven’t yet found a way to do so for +careful arithmetic, but I haven't yet found a way to do so for 1/z-sorted spans. Lastly, as discussed in Chapter 66, Listing 67.1 uses the gradients for diff --git a/68-01.md b/68-01.md index 2284f6e..297c904 100644 --- a/68-01.md +++ b/68-01.md @@ -3,7 +3,7 @@ ------------------------ --------------------------------- -------------------- Chapter 68\ - Quake’s Lighting Model {#Heading1} + Quake's Lighting Model {#Heading1} ----------------------- ### A Radically Different Approach to Lighting Polygons {#Heading2} @@ -25,50 +25,50 @@ detract from the sheer thrill of being in a computer-simulated universe. Then the college got a PDP-11 with four CRT terminals, and suddenly Star Trek could redraw in a second instead of a minute. Better yet, I found the source code for the Star Trek program in the recesses of the new -system, the first time I’d ever seen any real-world code other than my +system, the first time I'd ever seen any real-world code other than my own, and excitedly dove into it. One evening, as I was looking through the code, a really cute girl at the next terminal asked me for help getting a program to run. After I had helped her, eager to get to know -her better, I said, “Want to see something? This is the actual source -for the Star Trek game!” and proceeded to page through the code, +her better, I said, "Want to see something? This is the actual source +for the Star Trek game!" and proceeded to page through the code, describing each subroutine. We got to talking, and eventually I worked up the nerve to ask her out. She said sure, and we ended up having a good time, although things soon fell apart because of her two or three other boyfriends (I never did get an exact count). The interesting thing, though, was her response when I finally got around to asking her -out. She said, “It’s about time!” When I asked what she meant, she said, -“I’ve been trying to get you to ask me out all evening—but it took you -forever! You didn’t actually think I was interested in that Star Trek -program, did you?” +out. She said, "It's about time!" When I asked what she meant, she said, +"I've been trying to get you to ask me out all evening—but it took you +forever! You didn't actually think I was interested in that Star Trek +program, did you?" Actually, yes, I had thought that, because *I* was interested in it. One thing I learned from that experience, and have had reinforced countless times since, is that we—you, me, anyone who programs because they love -it, who would do it for free if necessary—are a breed apart. We’re +it, who would do it for free if necessary—are a breed apart. We're different, and luckily so; while everyone else is worrying about -downsizing, we’re in one of the hottest industries in the world. And, so -far as I can see, the biggest reason we’re in such a good situation -isn’t intelligence, or hard work, or education, although those help; -it’s that we actually *like* this stuff. +downsizing, we're in one of the hottest industries in the world. And, so +far as I can see, the biggest reason we're in such a good situation +isn't intelligence, or hard work, or education, although those help; +it's that we actually *like* this stuff. -It’s important to keep it that way. I’ve seen far too many people start +It's important to keep it that way. I've seen far too many people start to treat programming like a job, forgetting the joy of doing it, and -burn out. So keep an eye on how you feel about the programming you’re -doing, and if it’s getting stale, it’s time to learn something new; -there’s plenty of interesting programming of all sorts to be done. -Follow your interests—and don’t forget to have fun! +burn out. So keep an eye on how you feel about the programming you're +doing, and if it's getting stale, it's time to learn something new; +there's plenty of interesting programming of all sorts to be done. +Follow your interests—and don't forget to have fun! ### The Lighting Conundrum {#Heading3} -I spent about two years working with John Carmack on Quake’s 3-D +I spent about two years working with John Carmack on Quake's 3-D graphics engine. John faced several fundamental design issues while -architecting Quake. I’ve written in earlier chapters about some of those +architecting Quake. I've written in earlier chapters about some of those issues, including eliminating non-visible polygons quickly via a precalculated potentially visible set (PVS), and improving performance by inserting potentially visible polygons into a global edge list and scanning out only the nearest polygon at each pixel. -In this chapter, I’m going to talk about another, equally crucial design +In this chapter, I'm going to talk about another, equally crucial design issue: how we developed our lighting approach for the part of the Quake engine that draws the world itself, the static walls and floors and ceilings. Monsters and players are drawn using completely different @@ -77,7 +77,7 @@ world, on the other hand, was to be as precise as possible, getting everything right so that polygons, textures, and sophisticated lighting would be pegged in place, with no visible shifting or distortion under all viewing conditions, for maximum player immersion—all with good -performance, of course. As I’ll discuss, the twin goals of performance +performance, of course. As I'll discuss, the twin goals of performance and rock-solid, complex lighting proved to be difficult to achieve with traditional lighting approaches; ultimately, a dramatically different approach was required. @@ -91,10 +91,10 @@ all relevant world lighting, linearly interpolating between lighting values down the edges of the polygon, and then linearly interpolating between the edges of the polygon across each span. If texture mapping is desired (and all polygons are texture mapped in Quake), then at each -pixel in each span, the pixel’s corresponding texture map location +pixel in each span, the pixel's corresponding texture map location (texel) is determined, and the interpolated lighting is applied to the texel to generate a final, lit pixel. Texels are generally taken from a -32x32 or 64x64 texture that’s tiled repeatedly across the polygon, for +32x32 or 64x64 texture that's tiled repeatedly across the polygon, for several reasons: performance (a 64x64 texture sits nicely in the 486 or Pentium cache), database size, and less artwork. @@ -107,7 +107,7 @@ is lit by looking up the pixel color in a table, using the texel color and the lighting intensity as the look-up indices. Gouraud shading allows for decent lighting effects with a relatively -small amount of calculation and a compact data set that’s a simple +small amount of calculation and a compact data set that's a simple extension of the basic polygon model. However, there are several important drawbacks to Gouraud shading, as well. diff --git a/68-02.md b/68-02.md index a9ee8a1..72dec77 100644 --- a/68-02.md +++ b/68-02.md @@ -4,7 +4,7 @@ A primary problem with Gouraud shading is that it requires the vertices used for world geometry to serve as lighting sample points as well, even -though there isn’t necessarily a close relationship between lighting and +though there isn't necessarily a close relationship between lighting and geometry. This artificial coupling often forces the subdivision of a single polygon into several polygons purely for lighting reasons, as with the spotlights mentioned above; these extra polygons increase the @@ -29,7 +29,7 @@ that increases the rasterization load. #### Perspective Correctness {#Heading6} -Another problem is that Gouraud shading isn’t perspective-correct. With +Another problem is that Gouraud shading isn't perspective-correct. With Gouraud shading, lighting varies linearly across the face of a polygon, in equal increments per pixel—but unless the polygon is parallel to the screen, the same sort of perspective correction is needed to step @@ -44,10 +44,10 @@ First, the extent of the mismatch between Gouraud shading and perspective lighting varies with the angle and orientation of the polygon being lit. As a polygon turns to become more on-edge, for example, the lighting warps more and therefore shifts relative to the -perspective-texture mapped texels it’s shading, an effect I’ll call +perspective-texture mapped texels it's shading, an effect I'll call *viewing variance*. Lighting can similarly shift as a result of clipping, for example if one or more polygon edges are completely -clipped; I’ll refer to this as *clipping variance*. +clipped; I'll refer to this as *clipping variance*. These are fairly subtle effects; more pronounced is the *rotational variance* that occurs when Gouraud shading any polygon with more than @@ -61,8 +61,8 @@ shift can be quite drastic, depending on how different the colors at the vertices are. It was rotational variance that finally brought the lighting issue to a -head for Quake. We’d look at the floors, which were Gouraud-shaded -quads; then we’d pivot, and the lighting would shimmy and shift, +head for Quake. We'd look at the floors, which were Gouraud-shaded +quads; then we'd pivot, and the lighting would shimmy and shift, especially where there were spotlights and shadows. Given the goal of rendering the world as accurately and convincingly as possible, this was unacceptable. @@ -71,11 +71,11 @@ The obvious solution to rotational variance is to use only triangles, but that brings with it a new set of problems. It takes twice as many triangles as quads to describe the same scene, increasing the size of the world database and requiring extra rasterization, at a performance -cost. Triangles still don’t provide perspective lighting; their lighting -is rotationally invariant, but it’s still wrong—just wrong in a more +cost. Triangles still don't provide perspective lighting; their lighting +is rotationally invariant, but it's still wrong—just wrong in a more consistant way. Gouraud-shaded triangles still result in odd lighting patterns, and require lots of triangles to support shadowing and other -lighting detail. Finally, triangles don’t solve clipping or viewing +lighting detail. Finally, triangles don't solve clipping or viewing variance. ![](images/68-02.jpg)\ @@ -83,7 +83,7 @@ variance. orientation.* Yet another problem is that while it may work well to add extra geometry -so that spotlights and shadows show up well, that’s feasible only for +so that spotlights and shadows show up well, that's feasible only for static lighting. Dynamic lighting—light cast by sources that move—has to work with whatever geometry the world has to offer, because its needs are constantly changing. @@ -91,7 +91,7 @@ are constantly changing. These issues led us to conclude that if we were going to use Gouraud shading, we would have to build Quake levels from many small triangles, with sufficiently finely detailed geometry so that complex lighting -could be supported and the inaccuracies of Gouraud shading wouldn’t be +could be supported and the inaccuracies of Gouraud shading wouldn't be too noticeable. Unfortunately, that line of thinking brought us back to the problem of a much larger world database and a much heavier rasterization load (all the worse because Gouraud shading requires an @@ -102,36 +102,36 @@ slow to support the complex worlds we had hoped for in Quake. ### The Quest for Alternative Lighting {#Heading7} -None of which is to say that Gouraud shading isn’t useful in general. +None of which is to say that Gouraud shading isn't useful in general. Descent uses it to excellent effect, and in fact Quake uses Gouraud shading for moving entities, because these consist of small triangles and are always in motion, which helps hide the relatively small lighting -errors. However, Gouraud shading didn’t seem capable of meeting our +errors. However, Gouraud shading didn't seem capable of meeting our design goals for rendering quality and speed for drawing the world as a whole, so it was time to look for alternatives. There are many alternative lighting approaches, most of them higher-quality than Gouraud, starting with Phong shading, in which the -surface normal is interpolated across the polygon’s surface, and going +surface normal is interpolated across the polygon's surface, and going all the way up to ray-tracing lighting techniques in which full illumination calculations are performed for all direct and reflected paths from each light source for each pixel. What all these approaches -have in common is that they’re slower than Gouraud shading, too slow for +have in common is that they're slower than Gouraud shading, too slow for our purposes in Quake. For weeks, we kicked around and rejected various possibilities and continued working with Gouraud shading for lack of a -better alternative—until the day John came into work and said, “You -know, I have an idea....” +better alternative—until the day John came into work and said, "You +know, I have an idea...." #### Decoupling Lighting from Rasterization {#Heading8} -John’s idea came to him while was looking at a wall that had been carved +John's idea came to him while was looking at a wall that had been carved into several pieces because of a spotlight, with an ugly lighting glitch due to a t-junction. He thought to himself that if only there were some way to treat it as one surface, it would look better and draw faster—and then he realized that there was a way to do that. The insight was to split lighting and rasterization into two separate -steps. In a normal Gouraud-based rasterizer, there’s first an off-line +steps. In a normal Gouraud-based rasterizer, there's first an off-line preprocessing step when the world database is built, during which polygons are added to support additional lighting detail as needed, and lighting values are calculated at the vertices of all polygons. At diff --git a/68-03.md b/68-03.md index 56a9d5f..589f08f 100644 --- a/68-03.md +++ b/68-03.md @@ -2,21 +2,21 @@ [Previous](68-02.html) [Table of Contents](index.html) [Next](68-04.html) ------------------------ --------------------------------- -------------------- -Quake’s approach, which I’ll call surface-based lighting, preprocesses +Quake's approach, which I'll call surface-based lighting, preprocesses differently, and adds an extra rendering step. During off-line preprocessing, a grid, called a light map, is calculated for each polygon in the world, with a lighting value every 16 texels horizontally and vertically. This lighting is done by casting light from all the nearby lights in the world to each of the grid points on the polygon, and summing the results for each grid point. The Quake preprocessor -filters the values, so shadow edges don’t have a stair-step appearance +filters the values, so shadow edges don't have a stair-step appearance (a technique suggested by Billy Zelsnack); additional preprocessing could be done, for example Phong shading to make surfaces appear -smoothly curved. Then, at runtime, the polygon’s texture is tiled into a +smoothly curved. Then, at runtime, the polygon's texture is tiled into a buffer, with each texel lit according to the weighted average intensities of the four nearest light map points, as shown in Figure 68.3. If dynamic lighting is needed, the light map is modified -accordingly before the buffer, which I’ll call a surface, is built. Then +accordingly before the buffer, which I'll call a surface, is built. Then the polygon is drawn with perspective texture mapping, with the surface serving as the input texture, and with no lighting performed during the texture mapping. @@ -35,7 +35,7 @@ the filtering performed when the light map is built, is sufficient to support complex shadows with smoothly fading edges. Additionally, surface-based lighting eliminates lighting glitches at t-junctions, because lighting is unrelated to vertices. In short, surface-based -lighting meets all of Quake’s visual quality goals, which leaves only +lighting meets all of Quake's visual quality goals, which leaves only one question: How does it perform? #### Size and Speed {#Heading9} @@ -54,38 +54,38 @@ overlaps an FDIV for floating-point perspective correction with integer pixel drawing in 16-pixel bursts, has been squeezed down to 7.5 cycles per pixel on a Pentium, so the combined inner loop times for building and drawing a surface is roughly in the neighborhood of 10 cycles per -pixel. It’s certainly possible to write a Gouraud-shaded -perspective-correct texture mapper that’s somewhat faster than 10 +pixel. It's certainly possible to write a Gouraud-shaded +perspective-correct texture mapper that's somewhat faster than 10 cycles, but 10 cycles/pixel is fast enough to do 40 frames/second at 640x400 on a Pentium/100, so the cycle counts of surface-based lighting -are acceptable. It’s worth noting that it’s possible to write a one-pass +are acceptable. It's worth noting that it's possible to write a one-pass texture mapper that does approximately perspective-correct lighting. -However, I have yet to hear of or devise such an inner loop that isn’t +However, I have yet to hear of or devise such an inner loop that isn't complicated and full of special cases, which makes it hard to optimize; -worse, this approach doesn’t work well with the procedural and -post-processing techniques I’ll discuss shortly. +worse, this approach doesn't work well with the procedural and +post-processing techniques I'll discuss shortly. ![](images/68-03.jpg)\ **Figure 68.3**  *Tiling the texture and lighting the texels from the light map.* Moreover, surface-based lighting tends to spend more of its time in -inner loops, because polygons can have any number of sides and don’t +inner loops, because polygons can have any number of sides and don't need to be split into multiple smaller polygons for lighting purposes; this reduces the amount of transformation and projection that are required, and makes polygon spans longer. So the performance of surface-based lighting stacks up very well indeed—except for caching. I mentioned earlier that a 64x64 texture tile fits nicely in the -processor cache. A typical surface doesn’t. Every texel in every surface +processor cache. A typical surface doesn't. Every texel in every surface is unique, so even at 320x200 resolution, something on the rough order of 64,000 texels must be read in order to draw a single scene. (The number actually varies quite a bit, as discussed below, but 64,000 is in -the ballpark.) This means that on a Pentium, we’re guaranteed to miss +the ballpark.) This means that on a Pentium, we're guaranteed to miss the cache once every 32 texels, and the number can be considerably worse -than that if the texture access patterns are such that we don’t use +than that if the texture access patterns are such that we don't use every texel in a given cache line before that data gets thrown out of -the cache. Then, too, when a surface is built, the surface buffer won’t +the cache. Then, too, when a surface is built, the surface buffer won't be in the cache, so the writes will be uncached writes that have to go to main memory, then get read back from main memory at texture mapping time, potentially slowing things further still. All this together makes @@ -104,9 +104,9 @@ surface were visible in the next frame, it could be reused without having to be rebuilt. With surface rebuilding needed only rarely, thanks to surface caching, -Quake’s rasterization speed is generally the speed of the unlit, +Quake's rasterization speed is generally the speed of the unlit, perspective-correct texture-mapping inner loop, which suffers from more -cache misses than Gouraud-shaded, tiled texture mapping, but doesn’t +cache misses than Gouraud-shaded, tiled texture mapping, but doesn't have the overhead of Gouraud shading, and allows the use of larger polygons. In the worst case, where everything in a frame is a new surface, the speed of the surface-caching approach is somewhat slower diff --git a/68-04.md b/68-04.md index cf400ca..a7e1ac4 100644 --- a/68-04.md +++ b/68-04.md @@ -10,7 +10,7 @@ due to perspective foreshortening, distant polygons have only a few pixels relative to the surface size in texels. Surfaces associated with partly hidden polygons must be fully built, even though only part of the polygon is visible, and if polygons are drawn back to front with -overdraw, some polygons won’t even be visible, but will still require +overdraw, some polygons won't even be visible, but will still require surface building and caching. What all this meant was that the surface cache initially looked to be very large, on the order of several megabytes, even at 320x200—too much for a game intended to run on an 8 @@ -25,7 +25,7 @@ Second, surfaces are built at four mipmap levels, depending on distance, with each mipmap level having one-quarter as many texels as the preceding level, as shown in Figure 68.4. -For those whose heads haven’t been basted in 3-D technology for the past +For those whose heads haven't been basted in 3-D technology for the past several years, *mipmapping* is 3-D graphics jargon for a process that normalizes the number of texels in a surface to be approximately equal to the number of pixels, reducing calculation time for distant surfaces @@ -66,15 +66,15 @@ has to be rebuilt. In the worst case, where the lighting changes on every visible surface, the surface cache provides no benefit, and rendering runs at the combined speed of surface building and texture mapping. This worst-case slowdown is tolerable but certainly noticeable, -so it’s best to design games that use surface caching so only some of +so it's best to design games that use surface caching so only some of the surfaces change lighting at any one time. If necessary, you could alternate surface relighting so that half of the surfaces change on even frames, and half on odd frames, but large-scale, constant relighting is -not surface caching’s strongest suit. +not surface caching's strongest suit. -Finally, Quake barely begins to tap surface caching’s potential. All +Finally, Quake barely begins to tap surface caching's potential. All sorts of procedural texturing and post-processing effects are possible. -If a wall is shot, a sprite of pockmarks could be attached to the wall’s +If a wall is shot, a sprite of pockmarks could be attached to the wall's data structure, and the sprite could be drawn into the surface each time the surface is rebuilt. The same could be done for splatters, or graffiti, with translucency easily supported. These effects would then diff --git a/69-01.md b/69-01.md index 95f20ff..0bcd6f6 100644 --- a/69-01.md +++ b/69-01.md @@ -3,12 +3,12 @@ ------------------------ --------------------------------- -------------------- Chapter 69\ - Surface Caching and Quake’s Triangle Models {#Heading1} + Surface Caching and Quake's Triangle Models {#Heading1} -------------------------------------------- ### Probing Hardware-Assisted Surfaces and Fast Model Animation Without Sprites {#Heading2} -In the late ’70s, I spent a summer doing contract programming at a +In the late '70s, I spent a summer doing contract programming at a government-funded installation called the Northeast Solar Energy Center (NESEC). Those were heady times for solar energy, what with the oil shortages, and there was lots of money being thrown at places like @@ -17,8 +17,8 @@ NESEC, which was growing fast. NESEC was across the street from MIT, which made for good access to resources. Unfortunately, it also meant that NESEC was in a severely parking-impaired part of the world, what with the student population and -Boston’s chronic parking shortage. The NESEC building did have its own -parking lot, but it wasn’t nearly big enough, because students parked in +Boston's chronic parking shortage. The NESEC building did have its own +parking lot, but it wasn't nearly big enough, because students parked in it at every opportunity. The lot was posted, and cars periodically got towed, but King Canute stood a better chance against the tide than NESEC did against the student hordes, and late arrivals to work often had to @@ -33,60 +33,60 @@ topped it off, left the empty oil can next to the car so I would see it and remember to pick it up to throw out on my way back, and headed toward the computer center. -I’d gone only a few hundred feet when I heard footsteps and shouting +I'd gone only a few hundred feet when I heard footsteps and shouting behind me, and a wild-eyed man in a business suit came running up to me, -screaming. “It’s bad enough you park in our lot, but now you’re leaving -your garbage lying around!” he yelled. “Don’t you people have any sense -of decency?” I told him I worked at NESEC and was going to pick up the -can on my way back, and he shouted, “Don’t give me that!” I repeated my +screaming. "It's bad enough you park in our lot, but now you're leaving +your garbage lying around!" he yelled. "Don't you people have any sense +of decency?" I told him I worked at NESEC and was going to pick up the +can on my way back, and he shouted, "Don't give me that!" I repeated my statements, calmly, and told him who I worked for and where my office -was, and he said, “Don’t give me that” again, but with a little less +was, and he said, "Don't give me that" again, but with a little less certainty. I kept adding detail until it was obvious that I was telling -the truth, and he suddenly said, “Oh, my God,” turned red, and started +the truth, and he suddenly said, "Oh, my God," turned red, and started to apologize profusely. A few days later, we passed in the hallway, and -he didn’t look me in the eye. +he didn't look me in the eye. The interesting point is that there was really no useful outcome that could have resulted from his outburst. Suppose I had been a student—what would he have accomplished by yelling at me? He let his emotions overrule his common sense, and as a result, did something he later -wished he hadn’t. I’ve seen many programmers do the same thing, -especially when they’re working long hours and not feeling adequately +wished he hadn't. I've seen many programmers do the same thing, +especially when they're working long hours and not feeling adequately appreciated. For example, some time back I got mail from a programmer -who complained bitterly that although he was critical to his company’s -success, management didn’t appreciate his hard work and talent, and +who complained bitterly that although he was critical to his company's +success, management didn't appreciate his hard work and talent, and asked if I could help him find a better job. I suggested several ways that he might look for another job, but also asked if he had tried working his problems out with his employers; if he really was that -valuable, what did he have to lose? He admitted he hadn’t, and recently +valuable, what did he have to lose? He admitted he hadn't, and recently he wrote back and said that he had talked to his boss, and now he was getting paid a lot more money, was getting credit for his work, and was just flat-out happy. We programmers think of ourselves as rational creatures, but most of us get angry at times, and when we do, like everyone else, we tend to be -driven by our emotions instead of our minds. It’s my experience that +driven by our emotions instead of our minds. It's my experience that thinking rationally under those circumstances can be difficult, but produces better long-term results every time—so if you find yourself in that situation, stay cool and think your way through it, and odds are -you’ll be happier down the road. +you'll be happier down the road. Of course, most of the time programmers really *are* rational creatures, -and the more information we have, the better. In that spirit, let’s look -at more of the stuff that makes Quake tick, starting with what I’ve +and the more information we have, the better. In that spirit, let's look +at more of the stuff that makes Quake tick, starting with what I've recently learned about surface caching. ### Surface Caching with Hardware Assistance {#Heading3} In Chapter 68, I discussed in detail the surface caching technique that Quake uses to do detailed, high-quality lighting without lots of -polygons. Since writing that chapter, I’ve gone further, and spent a -considerable amount of time working on the port of Quake to Rendition’s +polygons. Since writing that chapter, I've gone further, and spent a +considerable amount of time working on the port of Quake to Rendition's Verite 3-D accelerator chip. So let me start off this chapter by -discussing what I’ve learned about using surface caching in conjunction +discussing what I've learned about using surface caching in conjunction with hardware. -As you’ll recall, the key to surface caching is that lighting +As you'll recall, the key to surface caching is that lighting information and polygon detail are stored separately, with lighting not tied to polygon vertices, then combined on demand into what I call *surfaces*: lit, textured rectangles that are used as the input to the @@ -99,21 +99,21 @@ with some significant quality advantages. The surface-caching architecture of the Verite version of Quake (which we call VQuake) is essentially the same as in the software-only version of Quake: The CPU builds surfaces on demand, which are then downloaded -to the accelerator’s memory and cached there. There are a couple of key +to the accelerator's memory and cached there. There are a couple of key differences, however: the need to download surfaces, and the requirement that the surfaces be in 16-bit-per-pixel (bpp) format. Downloading surfaces to the accelerator is a performance hit that -doesn’t exist in the software-only version. Although Verite uses DMA to +doesn't exist in the software-only version. Although Verite uses DMA to download surfaces, DMA does in fact steal performance from the CPU. This cost is increased by the requirement for 16-bpp surfaces, because twice as much data must be downloaded. Worse still, it takes about twice as long to build 16-bpp surfaces as 8-bpp surfaces, so the cost of missing the surface cache is well over twice as expensive in VQuake as in Quake. -Fortunately, there’s 4 MB of memory on Verite-based adapters, so the -surface cache doesn’t miss very often and VQuake runs fine (and looks +Fortunately, there's 4 MB of memory on Verite-based adapters, so the +surface cache doesn't miss very often and VQuake runs fine (and looks very good, thanks to bilinear texture filtering, which by itself is -pretty much worth the cost of 3-D hardware), but it’s nonetheless true +pretty much worth the cost of 3-D hardware), but it's nonetheless true that a completely straightforward port of the surface-caching model is not as appealing for hardware as for software. This is especially true at high resolutions, where the needs of the surface cache increase due @@ -121,7 +121,7 @@ to more detailed surfaces but available memory decreases due to frame buffer size. Does my recent experience indicate that as the PC market moves to -hardware, there’s no choice but to move to Gouraud shading, despite the +hardware, there's no choice but to move to Gouraud shading, despite the quality issues? Not at all. First of all, surface caching does still work well, just not as relatively well compared to Gouraud shading as is the case in software. Second, there are at least two alternatives that diff --git a/69-02.md b/69-02.md index 9f1a5c5..a1fba8a 100644 --- a/69-02.md +++ b/69-02.md @@ -18,7 +18,7 @@ used allow drawing into a texture, which is not universally true. Neither do all APIs or accelerators allow applications enough control over the texture heap so that an efficient surface cache can be implemented, a point that favors non-caching approaches. (A similar -option that wasn’t open to us due to time limitations is downloading +option that wasn't open to us due to time limitations is downloading 8-bpp surfaces and having the accelerator expand them to 16-bpp surfaces as it stores them in texture memory. Better yet, some accelerators support 8-bpp palettized hardware textures that are expanded to 16-bpp @@ -31,50 +31,50 @@ one pass, then lighting from the light map as a second pass, using the light map as an alpha texture. In other words, the textured polygon is drawn first, with no lighting, then the light map is textured on top of the polygon, with the light map intensity used as an alpha value to -determine how brightly to light each texel. The hardware’s +determine how brightly to light each texel. The hardware's texture-mapping circuitry is used for both passes, so the lighting comes out perspective-correct and consistent under all viewing conditions, -just as with the surface cache. The lighting polygons don’t even have to +just as with the surface cache. The lighting polygons don't even have to match the texture polygons, so they can represent dynamically changing lighting. Two-pass lighting not only looks good, but has no memory footprint other than texture and light map storage, and provides level performance, -because it’s not dependent on surface cache hit rate. The primary +because it's not dependent on surface cache hit rate. The primary downside to two-pass lighting is that it requires at least twice as much performance from the accelerator as single-pass drawing. The current crop of 3-D accelerators is not particularly fast, and few of them are up to the task of doing two passes at high resolution, although that will change soon. Another potential problem is that some accelerators -don’t implement true alpha blending. Nonetheless, as accelerators get +don't implement true alpha blending. Nonetheless, as accelerators get better, I expect two-pass drawing (or three-or-more-pass, for adding splatters and the like by overlaying sprite polygons) to be widely used. -I also expect Gouraud shading to be widely used; it’s easy to use and +I also expect Gouraud shading to be widely used; it's easy to use and fast. Also, speedier CPUs and accelerators will enable much more detailed geometry to be used, and the smaller that polygons become, the better Gouraud shading looks compared to surface caching and two-pass lighting. -The next graphics engine you’ll see from id Software will be oriented -heavily toward hardware accelerators, and at this point it’s a tossup +The next graphics engine you'll see from id Software will be oriented +heavily toward hardware accelerators, and at this point it's a tossup whether the engine will use surface caching, Gouraud shading, or two-pass lighting. ### Drawing Triangle Models {#Heading6} Most of the last group of chapters in this book discuss how Quake works. -If you look closely, though, you’ll see that almost all of the +If you look closely, though, you'll see that almost all of the information is about drawing the world—the static walls, floors, ceilings, and such. There are several reasons for this, in particular -that it’s hard to get a world renderer working well, and that the world +that it's hard to get a world renderer working well, and that the world is the base on which everything else is drawn. However, moving entities, such as monsters, are essential to a useful game engine. Traditionally, these have been done with sprites, but when we set out to build Quake, we knew that it was time to move on to polygon-based models. (In the -case of Quake, the models are composed of triangles.) We didn’t know +case of Quake, the models are composed of triangles.) We didn't know exactly how we were going to make the drawing of these models fast enough, though, and went through quite a bit of experimentation and -learning in the process of doing so. For the rest of this chapter I’ll +learning in the process of doing so. For the rest of this chapter I'll discuss some interesting aspects of our triangle-model architecture, and present code for one useful approach for the rapid drawing of triangle models. @@ -84,8 +84,8 @@ models. We would have liked one rendering model, and hence one graphics pipeline, for all drawing in Quake; this would have simplified the code and tools, and would have made it much easier to focus our optimization -efforts. However, when we tried adding polygon models to Quake’s global -edge table, edge processing slowed down unacceptably. This isn’t that +efforts. However, when we tried adding polygon models to Quake's global +edge table, edge processing slowed down unacceptably. This isn't that surprising, because the edge table was designed to handle 200 to 300 large polygons, not the 2,000 to 3,000 tiny triangles that a dozen triangle models in a scene can add. Restructuring the edge list to use @@ -93,7 +93,7 @@ trees rather than linked lists would have helped with the larger data sets, but the basic problem is that the edge table requires a considerable amount of overhead per edge per scan line, and triangle models have too few pixels per edge to justify that overhead. Also, the -much larger edge table generated by adding triangle models doesn’t fit +much larger edge table generated by adding triangle models doesn't fit well in the CPU cache. Consequently, we implemented a separate drawing pipeline for triangle diff --git a/69-03.md b/69-03.md index e534e12..b11d517 100644 --- a/69-03.md +++ b/69-03.md @@ -12,15 +12,15 @@ vertex calculations are performed. Early on, we decided to allow lower drawing quality for triangle models than for the world, in the interests of speed. For example, the triangles in the models are small, and usually distant—and generally -part of a quickly moving monster that’s trying its best to do you in—so +part of a quickly moving monster that's trying its best to do you in—so the quality benefits of perspective texture mapping would add little value. Consequently, we chose to draw the triangles with affine texture mapping, avoiding the work required for perspective. Mind you, the -models are perspective-correct at the vertices; it’s just the pixels +models are perspective-correct at the vertices; it's just the pixels between the vertices that suffer slight warping. ![](images/69-01.jpg)\ - **Figure 69.1**  *Quake’s triangle-model drawing pipeline.* + **Figure 69.1**  *Quake's triangle-model drawing pipeline.* #### Trading Subpixel Precision for Speed {#Heading8} @@ -45,10 +45,10 @@ because the integer execution pipes can process independently while FDIV executes. Finally, we decided to Gouraud-shade the triangle models, because this -makes them look considerably more 3-D. However, we can’t afford to +makes them look considerably more 3-D. However, we can't afford to calculate where all the relevant light sources for each model are in each frame, or even which is the primary light source. Instead, we -select each model’s lighting level based on how brightly the floor point +select each model's lighting level based on how brightly the floor point it was standing on is lit, and use that lighting level for both ambient lighting (so all parts of the model have some illumination) and Gouraud shading—but the lighting vector for Gouraud shading is a fixed vector, @@ -56,22 +56,22 @@ so the model is always lit from the same direction. Somewhat surprisingly, in practice this looks considerably better than pure ambient lighting. -#### An Idea that Didn’t Work {#Heading9} +#### An Idea that Didn't Work {#Heading9} -As we implemented triangle models, we tried several ideas that didn’t -work out. One that’s notable because it seems so appealing is caching a -model’s image from one frame and reusing it in the next frame as a +As we implemented triangle models, we tried several ideas that didn't +work out. One that's notable because it seems so appealing is caching a +model's image from one frame and reusing it in the next frame as a sprite. Our thinking was that clipping, transforming, projecting, and drawing a several-hundred-triangle model was going to be a lot more expensive than drawing a sprite, too expensive to allow very many models to be visible at once. We wanted to be able to display at least a dozen simultaneous models, so the idea was that for all but the closest -models, we’d draw into a sprite, then reuse that sprite at the model’s +models, we'd draw into a sprite, then reuse that sprite at the model's new locations for the next two or three frames, amortizing the 3-D drawing cost over several frames and boosting overall model-drawing -performance. The rendering wouldn’t be exactly right when the sprite was +performance. The rendering wouldn't be exactly right when the sprite was reused, because the view of the model would change from frame to frame -as the viewer and model moved, but it didn’t seem likely that that +as the viewer and model moved, but it didn't seem likely that that slight inaccuracy would be noticeable for any but the nearest and largest models. @@ -81,13 +81,13 @@ fact they looked a lot like the sprites used in DOOM—precisely the effect we were trying to avoid. This was especially true if we reused them more than once—and if we reused them only once, then we had to do one full 3-D rendering plus two sprite renderings every two frames, -which wasn’t much faster than simply doing two 3-D renderings. +which wasn't much faster than simply doing two 3-D renderings. The sprite architecture also introduced considerable code complexity, increased memory footprint because of the need to cache the sprites, and made it difficult to get hidden surfaces exactly right because sprites are unavoidably 2-D. The performance of drawing the sprites dropped -sharply as models got closer, and that’s also where the sprites looked +sharply as models got closer, and that's also where the sprites looked worse when they were reused, limiting sprites to use at a considerable distance. All these problems could have been worked out reasonably well if necessary, but the sprite architecture just had the feeling of being @@ -103,7 +103,7 @@ suffering heavily from effects such as texture aliasing and pixel quantization, he reasoned, so it should work just as well if we could come up with another way of drawing blobs of approximately equal quality. The trick was to come up with such an alternative approach. We -tossed around half-formed ideas like flood-filling the model’s image +tossed around half-formed ideas like flood-filling the model's image within its silhouette, or encoding the model as a set of deltas, picking a visible seed point, and working around the visible side of the model according to the deltas. The first approach that seemed practical enough @@ -114,7 +114,7 @@ triangles were large, and the quality was very erratic. However, it did point the way to something that in the end did the trick. One morning I came in to the office to find that overnight (and well -into the morning), John had designed and implemented a technique I’ll +into the morning), John had designed and implemented a technique I'll call *subdivision rasterization*. This technique scans out approximately the right pixels for each triangle, with almost no overhead, as follows. First, all vertices in the model are drawn. Ideally, only the vertices diff --git a/69-04.md b/69-04.md index c5d1697..8d0f55d 100644 --- a/69-04.md +++ b/69-04.md @@ -4,15 +4,15 @@ Once the vertices are drawn, the triangles are processed one at a time. Each triangle that makes it through backface culling is then drawn with -recursive subdivision. If any of the triangle’s sides is more than one +recursive subdivision. If any of the triangle's sides is more than one pixel long in either x or y—that is, if the triangle contains any pixels -that aren’t at vertices—then that side is split in half as nearly as +that aren't at vertices—then that side is split in half as nearly as possible at given integer coordinates, and a new vertex is created at the split, with texture and screen coordinates that are halfway between those of the vertices at the endpoints. (The same splitting could be done for lighting, but we found that for small triangles—the sort that subdivision works well on—it was adequate to flat-shade each triangle at -the light level of the first vertex, so we didn’t bother with Gouraud +the light level of the first vertex, so we didn't bother with Gouraud shading.) The halfway values can be calculated very quickly with shifts. This vertex is drawn, and then each of the two resulting triangles is then processed recursively in the same way, as shown in Figure 69.2. @@ -26,7 +26,7 @@ very simple and easily optimized, especially by comparison with a generalized triangle rasterizer. Subdivision rasterization introduces considerably more error than affine -texture mapping, and doesn’t draw exactly the right triangle shape, but +texture mapping, and doesn't draw exactly the right triangle shape, but the difference is very hard to detect for triangles that contain only a few pixels. We found that the point at which the difference between the two rasterizers becomes noticeable was surprisingly close: 30 or 40 feet @@ -48,7 +48,7 @@ clear success. **LISTING 69.1 L69-1.C** - // Quake’s recursive subdivision triangle rasterizer; draws all + // Quake's recursive subdivision triangle rasterizer; draws all // pixels in a triangle other than the vertices by splitting an // edge to form a new vertex, drawing the vertex, and recursively // processing each of the two new triangles formed by using the @@ -69,7 +69,7 @@ clear success. int z; short *zbuf; - // try to find an edge that’s more than one pixel long in x or y + // try to find an edge that's more than one pixel long in x or y d = lp2[0] - lp1[0]; if (d < -1 || d > 1) goto split; @@ -129,20 +129,20 @@ clear success. z = new[5]>>16; - // point to the pixel’s z-buffer entry, looking up the scanline start + // point to the pixel's z-buffer entry, looking up the scanline start // address based on screen y and adding in the screen x coordinate zbuf = zspantable[new[1]] + new[0]; - // draw the split vertex if it’s not obscured by something nearer, as + // draw the split vertex if it's not obscured by something nearer, as // indicated by the z-buffer if (z >= *zbuf) { int pix; - // set the z-buffer to the new pixel’s distance + // set the z-buffer to the new pixel's distance *zbuf = z; - // get the texel from the model’s skin bitmap, according to + // get the texel from the model's skin bitmap, according to // the s and t texture coordinates, and translate it through // the lighting look-up table set according to the first // vertex for the original (top-level) triangle. Both s and @@ -167,27 +167,27 @@ clear success. #### More Ideas that Might Work {#Heading11} Useful as subdivision rasterization proved to be, we by no means think -that we’ve maxed out triangle-model drawing, if only because we spent +that we've maxed out triangle-model drawing, if only because we spent far less design and development time on subdivision than on the affine -rasterizer, so it’s likely that there’s quite a bit more performance to +rasterizer, so it's likely that there's quite a bit more performance to be found for drawing small triangles. For example, it could be faster to precalculate drawing masks or even precompile drawing code for all possible small triangles (say, up to 4x4 or 5x5), and the memory -footprint looks reasonable. (It’s worth noting that both precalculated +footprint looks reasonable. (It's worth noting that both precalculated drawing and subdivision rasterization are only possible because we snap to integer coordinates; none of this stuff works with fixed-point vertices.) More interesting still is the stack-based rendering described in the -article “Time/Space Tradeoffs for Polygon Mesh Rendering,” by Bar-Yehuda +article "Time/Space Tradeoffs for Polygon Mesh Rendering," by Bar-Yehuda and Gotsman, in the April, 1996 *ACM Transactions on Graphics*. Unfortunately, the article is highly abstract and slow going, but the -bottom line is that it’s possible to represent a triangle mesh as a +bottom line is that it's possible to represent a triangle mesh as a stream of commands that place vertices in a stack, remove them from the stack, and draw triangles using the vertices in the stack. This results in excellent CPU cache coherency, because rather than indirecting all over a vertex pool to retrieve vertex data, all vertices reside in a -tiny stack that’s guaranteed to be in the cache. Local variables used +tiny stack that's guaranteed to be in the cache. Local variables used while drawing can be stored in a small block next to the stack, and the stream of commands representing the model is accessed sequentially from start to finish, so cache utilization should be very high. As processors @@ -196,10 +196,10 @@ optimizations of this sort will become steadily more important in improving drawing performance. As with so many aspects of 3-D, there is no one best approach to drawing -triangle models, and no such thing as the fastest code. In a way, that’s -frustrating, but the truth is, it’s these nearly infinite possibilities +triangle models, and no such thing as the fastest code. In a way, that's +frustrating, but the truth is, it's these nearly infinite possibilities that make 3-D so interesting; not only is it an endless, varied -challenge, but there’s almost always a better solution waiting to be +challenge, but there's almost always a better solution waiting to be found. ------------------------ --------------------------------- -------------------- diff --git a/70-01.md b/70-01.md index 192ea50..3f09274 100644 --- a/70-01.md +++ b/70-01.md @@ -12,76 +12,76 @@ were given to use for materials and, they are like all of us, they wanted to use everything. But they did not need everything. They could do better with less, in a different way.* -—*Frederik Pohl*, The Gold at the Starbow’s End +—*Frederik Pohl*, The Gold at the Starbow's End Eleven years ago, I started the first serious graphics article I ever wrote with the above quote. The point I was making at the time was that programming assumptions based on high-level languages or other processors had to be discarded in the quest for maximum x86 performance. -While that’s certainly still true, time and the microcomputer world have -moved on, and today there’s a more important lesson 3-D game programmers -can draw from Frederik Pohl’s words. Nowadays, CPUs, 3-D hardware, 3-D +While that's certainly still true, time and the microcomputer world have +moved on, and today there's a more important lesson 3-D game programmers +can draw from Frederik Pohl's words. Nowadays, CPUs, 3-D hardware, 3-D algorithms, and 3-D data structures are evolving so rapidly that the enemy is now often the assumptions and techniques from the last product—and sometimes the assumptions and techniques in the *current* -product. We all feel most comfortable with techniques we’ve already +product. We all feel most comfortable with techniques we've already mastered, but leading-edge 3-D game technology is such a delicate balancing act between performance, features (particularly with game -designers always wanting to add more), and workflow (as we’ll see, +designers always wanting to add more), and workflow (as we'll see, preprocessing that improves performance often hurts designer -productivity) that it’s never safe to stop looking for a better approach +productivity) that it's never safe to stop looking for a better approach until the game has actually shipped. Change is the rule, and we must -always be looking to “do better with less, in a different way.” +always be looking to "do better with less, in a different way." -I’ve talked about Quake’s technology elsewhere in this book, However, +I've talked about Quake's technology elsewhere in this book, However, those chapters focused on specific areas, not overall structure. Moreover, Quake changed in significant ways between the writing of those chapters and the final shipping. Then, after shipping, Quake was ported to 3-D hardware. And the post-Quake engine, code-named Trinity, is already in development at this writing (Spring 1997), with some -promising results. So in wrapping up this book, I’ll recap Quake’s +promising results. So in wrapping up this book, I'll recap Quake's overall structure relatively quickly, then bring you up to date on the -latest developments. And in the spirit of Frederik Pohl’s quote, I’ll +latest developments. And in the spirit of Frederik Pohl's quote, I'll point out that we implemented and discarded at least half a dozen 3-D -engines in the course of developing Quake (and all of Quake’s code was +engines in the course of developing Quake (and all of Quake's code was written from scratch, rather than using Doom code), and almost switched -to another one in the final month, as I’ll describe later. And even at +to another one in the final month, as I'll describe later. And even at this early stage, Trinity uses almost no Quake technology. -In fact, I’ll take this opportunity to coin Carmack’s Law, as follows: +In fact, I'll take this opportunity to coin Carmack's Law, as follows: *Fight code entropy*. If you have a new fundamental assumption, throw away your old code and rewrite it from scratch. Incremental patching and modifying seems easier at first, and is the normal course of things in software development, but ends up being much harder and producing -bulkier, markedly inferior code in the long run, as we’ll see when we +bulkier, markedly inferior code in the long run, as we'll see when we discuss the net code for QuakeWorld. It may seem safer to modify working code, but the nastiest bugs arise from unexpected side effects and incorrect assumptions, which almost always arise in patched-over code, not in code designed from the ground up. Do the hard work up front to -make your code simple, elegant, great—and just plain *right*—and it’ll +make your code simple, elegant, great—and just plain *right*—and it'll pay off many times over in the long run. -Before I begin, I’d like to remind you that all of the Doom and Quake -material I’m presenting in this book is presented in the spirit of +Before I begin, I'd like to remind you that all of the Doom and Quake +material I'm presenting in this book is presented in the spirit of sharing information to make our corner of the world a better place for -everyone. I’d like to thank John Carmack, Quake’s architect and lead +everyone. I'd like to thank John Carmack, Quake's architect and lead programmer, and id Software for allowing me to share this technology with you, and I encourage you to share your own insights by posting on the Internet and writing books and articles whenever you have the opportunity and the right to do so. (Of course, check with your employer -first!) We’ve all benefited greatly from the shared wisdom of people +first!) We've all benefited greatly from the shared wisdom of people like Knuth, Foley and van Dam, Jim Blinn, Jim Kajiya, and hundreds of others—are you ready to take a shot at making your own contribution to the future? ### Preprocessing the World {#Heading2} -For the most part, I’ll discuss Quake’s 3-D engine in this chapter, -although I’ll touch on other areas of interest. For 3-D rendering +For the most part, I'll discuss Quake's 3-D engine in this chapter, +although I'll touch on other areas of interest. For 3-D rendering purposes, Quake consists of two basic sorts of objects: the world, which is stored as a single BSP model and never changes shape or position; and potentially moving objects, called *entities*, which are drawn in -several different ways. I’ll discuss each separately. +several different ways. I'll discuss each separately. The world is constructed from a set of brushes, which are n-sided convex polyhedra placed in a level by a designer using a map editor, with a @@ -97,15 +97,15 @@ nodes carve space. The top node carves the world into two subspaces, and divides the remaining polygons into two sets, splitting any polygon that spans the node into two pieces. Each subspace is then similarly split by one node each, and so on until all polygons have been used to create -nodes. A node’s subspace is the total space occupied by all its +nodes. A node's subspace is the total space occupied by all its children: the subspace that the node splits into two parts, and that its -children continue to subdivide. When the only polygon in a node’s +children continue to subdivide. When the only polygon in a node's subspace is the polygon that splits the subspace—the polygon whose plane defines the node—then the two child subspaces are called leaves, and are not divided any further. The BSP tree is built using the polygon that splits the fewest of the -polygons in the current node’s subspace as the heuristic for choosing +polygons in the current node's subspace as the heuristic for choosing splitters, which is not an optimal solution—but an optimal solution is NP-complete, and our heuristic adds only 10% to 15% more polygons to the level as a result of BSP splits. Polygons are not split all the way into @@ -116,9 +116,9 @@ culling as well), thereby reducing splitting considerably, because polygons are split only by parent nodes, not by child nodes (as would be necessary if polygons were split into leaves). Eliminating polygon splits, thus reducing the total number of polygons per level, not only -shrinks Quake’s memory footprint, but also reduces the number of +shrinks Quake's memory footprint, but also reduces the number of polygons that need to be processed by the 3-D pipeline, producing a -speedup of about 10% in Quake’s overall performance. +speedup of about 10% in Quake's overall performance. ------------------------ --------------------------------- -------------------- [Previous](69-04.html) [Table of Contents](index.html) [Next](70-02.html) diff --git a/70-02.md b/70-02.md index 975c868..95c1068 100644 --- a/70-02.md +++ b/70-02.md @@ -5,7 +5,7 @@ Getting proper front-to-back drawing order is a little more complicated with polygons on nodes. As we walk the BSP tree front-to-back, in each leaf we mark the polygons that are at least partially in that leaf, and -then after we’ve recursed and processed everything in front of a node, +then after we've recursed and processed everything in front of a node, we then process all the marked polygons on that node, after which we recurse to process the polygons behind the node. So putting the polygons on the nodes saves memory and improves performance significantly, but @@ -35,7 +35,7 @@ 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.) +lighting, which I'll discuss later.) Some good news, though, is that in the nearly two years since we got the Alpha, Pentium Pros have become as fast as that generation of Alphas, so @@ -45,14 +45,14 @@ John has always been a big advocate of moving code out of the runtime program into utilities, and of preprocessing for performance and runtime simplicity, but even he thinks that in Quake, we may have pushed that to the point where it interfered too much with workflow. The real problem, -of course, is that even a huge amount of money can’t buy orders of +of course, is that even a huge amount of money can't buy orders of magnitude more performance than commodity computers; we are getting an -eight-R10000 SGI compute server, but that’s only about twice as fast as +eight-R10000 SGI compute server, but that's only about twice as fast as an off-the-shelf four-processor Pentium Pro. The size of the PVS for each leaf is manageable because it is stored as a bit vector, with a 1-bit for the position in the overall leaf array of -each leaf that’s visible from the current leaf. Most leaves are +each leaf that's visible from the current leaf. Most leaves are invisible from any one leaf, so the PVS for each leaf consists mostly of zeros, and compacts nicely with run-length encoding. @@ -66,7 +66,7 @@ specific viewpoint; this can cause two or three times as many polygons as are actually visible to be considered. John has been researching the possibility of an EVS—an *exactly visible set*—and has concluded that a 6-D BSP with hyperbolic separating planes could do the job; the problem -now is that he doesn’t know how to get the math to work, at least at any +now is that he doesn't know how to get the math to work, at least at any reasonable speed. An interesting extension of the PVS is what John calls the *potentially @@ -77,18 +77,18 @@ exactly the hearable space, because sounds could echo or carry further than that, but it does serve quite nicely as a potentially *relevant* space—the set of leaves that have any interest to the player. In Quake, all sounds that happen anywhere in the world are sent to the client, and -are heard, even through walls, if they’re close enough; an explosion +are heard, even through walls, if they're close enough; an explosion around the corner could be well within hearing and very important to -hear, so the PVS can’t be used to reject that sound, but unfortunately +hear, so the PVS can't be used to reject that sound, but unfortunately an explosion on the other side of a solid wall will sound exactly the same. Not only is it confusing hearing sounds through walls, but in a modem game, the bandwidth required to send all the sounds in a level can slow things down considerably. In a recent version of QuakeWorld, a -specifically multiplayer variant of Quake I’ll discuss later, John uses +specifically multiplayer variant of Quake I'll discuss later, John uses the PHS to determine which sounds to bother sending, and the resulting bandwidth improvement has made it possible to bump the maximum number of players from 16 to 32. Better yet, a sound on the other side of a solid -wall won’t be heard unless there’s an opening that permits the sound to +wall won't be heard unless there's an opening that permits the sound to come through. (In the future, John will use the PVS to determine fully audible sounds, and the PHS to determine muted sounds.) Also, the PHS can be used for events like explosions that might not have their center @@ -101,7 +101,7 @@ traced out into the world to see what polygons it strikes, and the cumulative effect of all lights on each surface is stored as a light map, a sampling of light values on a 16-texel grid. In Quake 2, radiosity lighting—a considerably more expensive process, but one that -produces highly realistic lighting—is performed, but I’ll save that for +produces highly realistic lighting—is performed, but I'll save that for later. ------------------------ --------------------------------- -------------------- diff --git a/70-03.md b/70-03.md index 5a1b6a5..2327f00 100644 --- a/70-03.md +++ b/70-03.md @@ -2,20 +2,20 @@ [Previous](70-02.html) [Table of Contents](index.html) [Next](70-04.html) ------------------------ --------------------------------- -------------------- -### Passages: The Last-Minute Change that Didn’t Happen {#Heading4} +### Passages: The Last-Minute Change that Didn't Happen {#Heading4} Earlier, I mentioned that we almost changed 3-D engines again in the -last month of Quake’s development. Here’s what happened: One of the +last month of Quake's development. Here's what happened: One of the alternatives to the PVS is the use of *portals*, where the focus is on -the places where polygons don’t exist along leaf faces, rather than the -more usual focus on the polygons themselves. These “empty” places are +the places where polygons don't exist along leaf faces, rather than the +more usual focus on the polygons themselves. These "empty" places are themselves polygons, called portals, that describe all the places that visibility can pass from one leaf to another. Portals are used by the PVS generator to determine visibility, and are used in other 3-D engines as the primary mechanism for determining leaf or sector visibility. For example, portals can be projected to screenspace, then used as a 2-D clipping region to restrict drawing of more distant polygons to only -those that are visible through the portal. Or, as in Quake’s +those that are visible through the portal. Or, as in Quake's preprocessor, visibility boundary planes can be constructed from one portal to the next, and 3-D clipping to those planes can be used to determine visible polygons or leaves. Used either way, portals can @@ -25,35 +25,35 @@ the portals themselves can easily be changed on the fly. The problem with portal-based visibility is that it tends to perform at its worst in complex scenes, which can have many, many portals. Since those are the most expensive scenes to draw, as well, portals tend to -worsen the worst case. However, late in Quake’s development, John +worsen the worst case. However, late in Quake's development, John realized that the approach of storing portals themselves in the world -database could readily be improved upon. (To be clear, Quake wasn’t -using portals at that point, and didn’t end up using them.) Since the +database could readily be improved upon. (To be clear, Quake wasn't +using portals at that point, and didn't end up using them.) Since the aforementioned sets of 3-D visibility clipping planes *between* portals—which he named *passages*—were what actually got used for visibility, if he stored those, instead of generating them dynamically from the portals, he would be able to do visibility much faster than with standard portals. This would give a significantly tighter polygon set than the PVS, because it would be based on visibility through the -passages from the viewpoint, rather than the PVS’s approach of +passages from the viewpoint, rather than the PVS's approach of visibility from anywhere in the leaf, and that would be a considerable help, because the level designers were running right up against -performance limits, partly because of the PVS’s relatively loose polygon +performance limits, partly because of the PVS's relatively loose polygon set. John immediately decided that passages-based visibility was a sufficiently superior approach that if it worked out, he would switch Quake to it, even at that late stage, and within a weekend, he had implemented it and had it working—only to find that, like portals, it -improved best cases but worsened worst cases, and overall wasn’t a win +improved best cases but worsened worst cases, and overall wasn't a win for Quake. In truth, given how close we were to shipping, John was as -much thankful as disappointed that passages didn’t work out, but the +much thankful as disappointed that passages didn't work out, but the possibilities were too great for us not to have taken a shot at it. So why even bother mentioning this? Partly to show that not every interesting idea pans out; I tend to discuss those that *did* pan out, -and it’s instructive to point out that many ideas don’t. That doesn’t -mean you shouldn’t try promising ideas, though. First, some do pan out, -and you’ll never know which unless you try. Second, an idea that doesn’t -work out in one case can still be filed away for another case. It’s +and it's instructive to point out that many ideas don't. That doesn't +mean you shouldn't try promising ideas, though. First, some do pan out, +and you'll never know which unless you try. Second, an idea that doesn't +work out in one case can still be filed away for another case. It's quite likely that passages will be useful in a different context in a future engine. @@ -65,14 +65,14 @@ your understanding will be when you tackle your next project. Everything described so far is a preprocessing step. When Quake is actually running, the world is drawn as follows: First, the PVS for the view leaf is decompressed, and each leaf flagged as visible is marked as -being in the current frame’s PVS. (The marking is done by storing the -current frame’s number in the leaf; this avoids having to clear the PVS +being in the current frame's PVS. (The marking is done by storing the +current frame's number in the leaf; this avoids having to clear the PVS marking each frame.) All the parent nodes of each leaf in the PVS are also marked; this information could have been stored as additional PVS flags, but to save space is bubbled up the BSP from each visible leaf. After the PVS is marked, the BSP is walked front-to-back. At each node, -the bounding box of the node’s subspace is clipped against the view +the bounding box of the node's subspace is clipped against the view frustum; if the bounding box is fully clipped, then that node and all its children are ignored. Likewise, if the node is not in the PVS for the current viewpoint leaf, the node and all its children are ignored. @@ -97,23 +97,23 @@ being the same as a polygon). Taken together, these spans cover every pixel on the screen once and only once, resulting in zero overdraw; surfaces that are completely hidden by nearer surfaces generate no spans at all. The spans are then drawn; all the spans for one surface are -drawn, and then all the spans for the next, so that there’s texture +drawn, and then all the spans for the next, so that there's texture coherency between spans, which is very helpful for processor cache coherency, and also to reduce setup overhead. -The primary purpose of the edge list is to make Quake’s performance as +The primary purpose of the edge list is to make Quake's performance as level—that is, as consistent—as possible. Compared to simply drawing all potentially drawable polygons front-to-back, the edge list certainly -slows down the best case, that is, when there’s no overdraw. However, by +slows down the best case, that is, when there's no overdraw. However, by eliminating overdraw, the worst case is helped considerably; in Quake, -there’s a ratio of perhaps 4:1 between worst and best case drawing time, +there's a ratio of perhaps 4:1 between worst and best case drawing time, versus the 10:1 or more that can happen with straight polygon drawing. Leveling is very important, because cases where a game slows down to the point of being unplayable dictate game and level design, and the fewer constraints placed on design, the better. ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *A corollary is that best case performance can be seductively misleading; it’s a great feeling to see a scene running at 30 or even 60 frames per second, but if the bulk of the game runs at 15 fps, those best cases are just going to make the rest of the game look worse.* + ![](images/i.jpg) *A corollary is that best case performance can be seductively misleading; it's a great feeling to see a scene running at 30 or even 60 frames per second, but if the bulk of the game runs at 15 fps, those best cases are just going to make the rest of the game look worse.* ------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------ --------------------------------- -------------------- diff --git a/70-04.md b/70-04.md index 47863fe..756d5ee 100644 --- a/70-04.md +++ b/70-04.md @@ -2,8 +2,8 @@ [Previous](70-03.html) [Table of Contents](index.html) [Next](70-05.html) ------------------------ --------------------------------- -------------------- -The edge list is an atypical technology for John; it’s an extra stage in -the engine, it’s complex, and it doesn’t scale well. A Quake level might +The edge list is an atypical technology for John; it's an extra stage in +the engine, it's complex, and it doesn't scale well. A Quake level might have a maximum of 500 potentially drawable polygons that get placed into the edge list, and that runs fine, but if you were to try to put 5,000 polygons into the edge list, it would quickly bog down due to edge @@ -15,8 +15,8 @@ degree of complexity possible in a Pentium-based game (and even then, only with the reduction in polygons made possible by the PVS), but will probably be poorly suited to more complex scenes. It served well in the Quake engine, but remains an inelegant solution, and, in the end, it -feels like there’s something better we didn’t hit on. However, as John -says, “I’m pragmatic above all else”—and the edge list did the job. +feels like there's something better we didn't hit on. However, as John +says, "I'm pragmatic above all else"—and the edge list did the job. ### Rasterization {#Heading6} @@ -37,7 +37,7 @@ then is only a small ripple, well worth the performance improvement gained by doing the perspective-correct math only once every 16 pixels. To speed things up a little more, the FDIV to calculate the reciprocal of 1/z is overlapped with drawing 16 pixels, taking advantage of the -Pentium’s ability to perform floating-point in parallel with integer +Pentium's ability to perform floating-point in parallel with integer instructions, so the FDIV effectively takes only one cycle. #### Lighting {#Heading7} @@ -46,14 +46,14 @@ Lighting is less simple to explain. The traditional way of doing polygon lighting is to calculate the correct light at the vertices and linearly interpolate between those points (Gouraud shading), but this has several disadvantages; in particular, it makes it hard to get detailed lighting -without creating a lot of extra polygons, the lighting isn’t perspective +without creating a lot of extra polygons, the lighting isn't perspective correct, and the lighting varies with viewing angle for polygons other than triangles. To address these problems, Quake uses surface-based -lighting instead. In this approach, when it’s time to draw a surface (a -world polygon), that polygon’s texture is tiled into a memory buffer. At -the same time, the texture is lit according to the surface’s light map, +lighting instead. In this approach, when it's time to draw a surface (a +world polygon), that polygon's texture is tiled into a memory buffer. At +the same time, the texture is lit according to the surface's light map, as calculated during preprocessing. Lighting values are linearly -interpolated between the light map’s 16-texel grid points, so the +interpolated between the light map's 16-texel grid points, so the lighting effects are smooth, but slightly blurry. Then, the polygon is drawn to the screen using the perspective-correct texture mapping described above, with the prelit surface buffer being the source @@ -66,8 +66,8 @@ from it than it does to do lighting and texture mapping in a single pass. However, surface buffers are cached for reuse, so only the texture mapping stage is usually needed. Quake surfaces tend to be big, so texture mapping is slowed by cache misses; however, the Quake approach -doesn’t need to interpolate lighting on a pixel-by-pixel basis, which -helps speed things up, and it doesn’t require additional polygons to +doesn't need to interpolate lighting on a pixel-by-pixel basis, which +helps speed things up, and it doesn't require additional polygons to provide sophisticated lighting. On balance, the performance of surface-based drawing is roughly comparable to tiled, Gouraud-shaded texture mapping—and it looks much better, being perspective correct, @@ -81,10 +81,10 @@ would draw the splatter as well. #### Dynamic Lighting {#Heading8} -Here we come to a feature added to Quake after last year’s Computer Game -Developer’s Conference (CGDC). At that time, Quake did not support -dynamic lighting; that is, explosions and such didn’t produce temporary -lighting effects. We hadn’t thought dynamic lighting would add enough to +Here we come to a feature added to Quake after last year's Computer Game +Developer's Conference (CGDC). At that time, Quake did not support +dynamic lighting; that is, explosions and such didn't produce temporary +lighting effects. We hadn't thought dynamic lighting would add enough to the game to be worth the trouble; however, at CGDC Billy Zelsnack showed us a demo of his latest 3-D engine, which was far from finished at the time, but did have impressive dynamic lighting effects. This caused us @@ -106,25 +106,25 @@ light sources and project their sphere of illumination into the world, and which would then add the dynamic contributions into the appropriate light maps and rebuild the affected surfaces. I said I would as soon as I finished up the stuff I was working on, but it might be a day or two. -A little while later, he said, “I bet I can get dynamic lighting working -in less than an hour,” and dove into the code. One hour and nine minutes -later, we had dynamic lighting, and it’s now hard to imagine Quake +A little while later, he said, "I bet I can get dynamic lighting working +in less than an hour," and dove into the code. One hour and nine minutes +later, we had dynamic lighting, and it's now hard to imagine Quake without it. (It sure is easier to imagine the impact of features and -implement them once you’ve seen them done by someone else!) +implement them once you've seen them done by someone else!) -One interesting point about Quake’s dynamic lighting is how inaccurate +One interesting point about Quake's dynamic lighting is how inaccurate it is. It is basically a linear projection, accounting properly for -neither surface angle nor lighting falloff with distance—and yet that’s +neither surface angle nor lighting falloff with distance—and yet that's almost impossible to notice unless you specifically look for it, and has no negative impact on gameplay whatsoever. Motion and fast action can surely cover for a multitude of graphics sins. -It’s well worth pointing out that because Quake’s lighting is +It's well worth pointing out that because Quake's lighting is perspective correct and independent of vertices, and because the rasterizer is both subpixel and subtexel correct, Quake worlds are visually very solid and stable. This was an important design goal from the start, both as a point of technical pride and because it greatly -improves the player’s sense of immersion. +improves the player's sense of immersion. ------------------------ --------------------------------- -------------------- [Previous](70-03.html) [Table of Contents](index.html) [Next](70-05.html) diff --git a/70-05.md b/70-05.md index 9d9d1e9..07af191 100644 --- a/70-05.md +++ b/70-05.md @@ -4,8 +4,8 @@ ### Entities {#Heading9} -So far, all we’ve drawn is the static, unchanging (apart from dynamic -lighting) world. That’s an important foundation, but it’s certainly not +So far, all we've drawn is the static, unchanging (apart from dynamic +lighting) world. That's an important foundation, but it's certainly not a game; now we need to add moving objects. These objects fall into four very different categories: BSP models, polygon models, sprites, and particles. @@ -23,7 +23,7 @@ given the BSP sorting order of the leaf in which it resides, allowing it to sort properly versus the world polygons. If two or more polygons from different BSP models are in the same leaf, however, BSP ordering is no longer useful, so we then sort those polygons by 1/z, calculated from -the polygons’ plane equations. +the polygons' plane equations. Interesting note: We originally tried to sort all world polygons on 1/z as well, the reason being that we could then avoid splitting polygons @@ -35,18 +35,18 @@ objects made it difficult to get global 1/z sorting to work completely reliably, and the code that we had to add to work around these problems slowed things up to the point where we were getting no extra performance for all the extra code complexity. This is not to say that 1/z sorting -can’t work (especially in something like a flight sim, where objects +can't work (especially in something like a flight sim, where objects never abut), but BSP sorting order can be a wonderful thing, partly -because it always works perfectly, and partly because it’s simpler and +because it always works perfectly, and partly because it's simpler and faster to sort on integer node and leaf orders than on floating-point 1/z values. BSP models take some extra time because of the cost of clipping them into the world BSP tree, but render just as fast as the rest of the world, again with no overdraw, so closed doors, for example, block -drawing of whatever’s on the other side (although it’s still necessary +drawing of whatever's on the other side (although it's still necessary to transform, project, and add to the edge list the polygons the door -occludes, because they’re still in the PVS—they’re potentially visible +occludes, because they're still in the PVS—they're potentially visible if the door opens). This makes BSP models most suitable for fairly simple structures, such as boxes, which have relatively few polygons to clip, and cause relatively few edges to be added to the edge list. @@ -57,13 +57,13 @@ Polygon models, such as monsters, weapons, and projectiles, consist of a triangle mesh with front and back skins stretched over the model. For speed, the triangles are drawn with affine texture mapping; the triangles are small enough, and the models are generally distant enough, -that affine distortion isn’t visible. (However, it is visible on the -player’s weapon; this caused a lot of extra work for the artists, and we +that affine distortion isn't visible. (However, it is visible on the +player's weapon; this caused a lot of extra work for the artists, and we will probably implement a perspective-correct polygon-model rasterizer in Quake 2 for this specific purpose.) The triangles are also Gouraud shaded; interestingly, the light vector used to shade the models is always from the same direction, and has no relation to any actual lights -in the world (although it does vary in intensity, along with the model’s +in the world (although it does vary in intensity, along with the model's ambient lighting, to match the brightness of the spot the player is standing above in the world). Even this highly inaccurate lighting works well, though; the Gouraud shading makes models look much more @@ -73,22 +73,22 @@ flashes. One issue with polygon models was how to handle occlusion issues; that is, what parts of models were visible, and what surfaces they were in -front of. We couldn’t add models to the edge list, because the hundreds +front of. We couldn't add models to the edge list, because the hundreds of polygons per model would overwhelm the edge list. Our initial occlusion solution was to sort polygon-model polygons into the world BSP, drawing the portions in each leaf at the right points as we drew the world in BSP order. That worked reasonably well with respect to the world (not perfectly, though, because it would have been too expensive to clip all the polygon-model polygons into the world, so there was some -occlusion error), but didn’t handle the case of sorting polygon models -in the same leaf against each other, and also didn’t help the polygons +occlusion error), but didn't handle the case of sorting polygon models +in the same leaf against each other, and also didn't help the polygons in a given polygon model sort properly against each other. The solution to this turned out to be z-buffering. After all the spans in the world are drawn, the z-buffer is filled in for those spans. This is a write-only operation, and involves no comparisons or overdraw (remember, the spans cover every pixel on the screen exactly once), so -it’s not that expensive—the performance cost is about 10%. Then polygon +it's not that expensive—the performance cost is about 10%. Then polygon models are drawn with z-buffering; this involves a z-compare at each polygon-model pixel, but no complicated clipping or sorting—and occlusion is exactly right in all respects. Polygon models tend to @@ -97,17 +97,17 @@ that high, anyway. Opinions vary as to the desirability of z-buffers; some people who favor more analytical approaches to hidden surface removal claim that John has -been seduced by the z-buffer. Maybe so, but there’s a lot there to be +been seduced by the z-buffer. Maybe so, but there's a lot there to be seduced by, and that will be all the more true as hardware rendering becomes the norm. The addition of particles—thousands of tiny colored rectangles—to Quake illustrated just how seductive the z-buffer can be; it would have been very difficult to get all those rectangles to draw properly using any other occlusion technique. Certainly z-buffering by -itself can’t perform well enough to serve for all hidden surface -removal; that’s why we have the PVS and the edge list (although for +itself can't perform well enough to serve for all hidden surface +removal; that's why we have the PVS and the edge list (although for hardware rendering the PVS would suffice), but z-buffering pretty much means that if you can figure out how to draw an effect, you can readily -insert it into the world with proper occlusion, and that’s a powerful +insert it into the world with proper occlusion, and that's a powerful capability indeed. Supporting scenes with a dozen or more models of 300 to 500 polygons @@ -119,7 +119,7 @@ hundred or more models without requiring a lot of work to eliminate most of those that were occluded. (Note that this is not unique to the PVS; whatever high-level culling scheme we had ended up using for world polygons would have provided the same benefit for polygon models.) Also, -model bounding boxes were used to trivially clip those that weren’t in +model bounding boxes were used to trivially clip those that weren't in the view pyramid, and to identify those that were unclipped, so they could be sent through a special fast path. The biggest breakthrough, though, was a very different sort of rasterizer that John came up with diff --git a/70-06.md b/70-06.md index 96b8887..d1c60be 100644 --- a/70-06.md +++ b/70-06.md @@ -6,14 +6,14 @@ This rasterizer, which we call the *subdivision rasterizer*, first draws all the vertices in the model. Then it takes each front-facing triangle, -and determines if it has a side that’s at least two pixels long. If it +and determines if it has a side that's at least two pixels long. If it does, we split that side into two pieces at the pixel nearest to the middle (using adds and shifts to average the endpoints of that side), draw the vertex at the split point, and process each of the two split triangles recursively, until we get down to triangles that have only one-pixel sides and hence have nothing left to draw. This approach is hideously slow and quite ugly (due to inaccuracies from integer -quantization) for 100-pixel triangles—but it’s very fast for, say, +quantization) for 100-pixel triangles—but it's very fast for, say, five-pixel triangles, and is indistinguishable from more accurate rasterization when a model is 25 or 50 feet away. Better yet, the subdivider is ridiculously simple—a few dozen lines of code, far simpler @@ -24,7 +24,7 @@ rasterizer got fairly close to the same performance with further optimization—in the range of 10% to 50% slower—but that took weeks of difficult programming. -We switch between the two rasterizers based on the model’s distance and +We switch between the two rasterizers based on the model's distance and average triangle size, and in almost any scene, most models are far enough away so subdivision rasterization is used. There are undoubtedly faster ways yet to rasterize distant models adequately well, but the @@ -37,11 +37,11 @@ We had hoped to be able to eliminate sprites completely, making Quake 100% 3-D, but sprites—although sometimes very visibly 2-D—were used for a few purposes, most noticeably the cores of explosions. As of CGDC last year, explosions consisted of an exploding spray of particles (discussed -below), but there just wasn’t enough visual punch with that +below), but there just wasn't enough visual punch with that representation; adding a series of sprites animating an explosion did the trick. (In hindsight, we probably should have made the explosions polygon models rather than sprites; it would have looked about as good, -and the few sprites we used didn’t justify the considerable amount of +and the few sprites we used didn't justify the considerable amount of code and programming time required to support them.) Drawing a sprite is similar to drawing a normal polygon, complete with perspective correction, although of course the inner loop must detect and skip over @@ -62,10 +62,10 @@ explosion core. ### How We Spent Our Summer Vacation: After Shipping Quake {#Heading15} -Since shipping Quake in the summer of 1996, we’ve extended it in several -ways: We’ve worked with Rendition to port it to the Verite accelerator -chip, we’ve ported it to OpenGL, we’ve ported it to Win32, we’ve done -QuakeWorld, and we’ve added features for Quake 2. I’ll discuss each of +Since shipping Quake in the summer of 1996, we've extended it in several +ways: We've worked with Rendition to port it to the Verite accelerator +chip, we've ported it to OpenGL, we've ported it to Win32, we've done +QuakeWorld, and we've added features for Quake 2. I'll discuss each of these briefly. #### Verite Quake {#Heading16} @@ -74,12 +74,12 @@ Verite Quake (VQuake) was the first hardware-accelerated version of Quake. It looks extremely good, due to bilinear texture filtering, which eliminates most pixel aliasing, and because it provides good performance at higher resolutions such as 512x384 and 640x480. Implementing VQuake -proved to be an interesting task, for two reasons: The Verite chip’s -fill rate was marginal for Quake’s needs, and Verite contains a +proved to be an interesting task, for two reasons: The Verite chip's +fill rate was marginal for Quake's needs, and Verite contains a programmable RISC chip, enabling more sophisticated processing than most 3-D accelerators. The need to squeeze as much performance as possible out of Verite ruled out the use of a standard API such as Direct 3D or -OpenGL; instead, VQuake uses Rendition’s proprietary API, Speedy3D, with +OpenGL; instead, VQuake uses Rendition's proprietary API, Speedy3D, with the addition of some special calls and custom Verite code. Interestingly, VQuake is very similar to software Quake; in order to @@ -87,7 +87,7 @@ allow Verite to handle the high pixel processing loads of high-res, VQuake uses an edge list and builds span lists on the CPU, just as in software Quake, then Verite DMAs the span descriptors to onboard memory and draws them. (This was only possible because Verite is fully -programmable; most accelerators wouldn’t be able to support this +programmable; most accelerators wouldn't be able to support this architecture.) Similarly, the CPU builds lit, tiled surfaces in system RAM, then Verite DMAs them to an onboard surface cache, from which they are texture-mapped. In short, VQuake is very much like normal Quake, @@ -95,11 +95,11 @@ except that the drawing of the spans is done by a specialized processor. This approach works well, but some of the drawbacks of a surface cache become more noticeable when hardware is involved. First, the DMAing is -an extra step that’s not necessary in software, slowing things down. +an extra step that's not necessary in software, slowing things down. Second, onboard memory is a relatively limited resource (4 MB total), and textures must be 16-bpp (because hardware can only do filtering in RGB modes), thus eating up twice as much memory as the software -version’s 8-bpp textures—and memory becomes progressively scarcer at +version's 8-bpp textures—and memory becomes progressively scarcer at higher resolutions, especially given the need for a z-buffer and two 16-bpp pages. (Note that using the edge list helps here, because it filters out spans from polygons that are in the PVS but fully occluded, @@ -115,8 +115,8 @@ alpha lighting). This approach produces exactly the same results as the surface cache, without requiring downloading and caching of large surfaces, and has the advantage of very level performance. However, this approach requires at least twice the fill rate of the surface cache -approach, and Verite didn’t have enough fill rate for that at higher -resolutions. It’s also worth noting that two-pass alpha lighting doesn’t +approach, and Verite didn't have enough fill rate for that at higher +resolutions. It's also worth noting that two-pass alpha lighting doesn't have the same potential for procedural texturing that surface caching does. In fact, given MMX and ever-faster CPUs, and the ability of the CPU and the accelerator to process in parallel, it will become @@ -124,7 +124,7 @@ increasingly tempting to use the CPU to build surfaces with procedural texturing such as bump mapping, shimmers, and warps; this sort of procedural texturing has the potential to give accelerated games highly distinctive visuals. So the choice between surface caching and two-pass -alpha lighting for hardware accelerators depends on a game’s needs, and +alpha lighting for hardware accelerators depends on a game's needs, and it seems most likely that the two approaches will be mixed together, with surface caching used for special surfaces, and two-pass alpha lighting used for most drawing. diff --git a/70-07.md b/70-07.md index 8e5a358..ea48bd2 100644 --- a/70-07.md +++ b/70-07.md @@ -11,11 +11,11 @@ currently raging; the observation I want to make here is that GLQuake uses two-pass alpha lighting, and runs very well on fast chips such as the 3Dfx, but rather slowly on most of the current group of accelerators. The accelerators coming out this year should all run -GLQuake fine, however. It’s also worth noting that we’ll be using +GLQuake fine, however. It's also worth noting that we'll be using two-pass alpha lighting in the N64 port of Quake; in fact, it looks like -the N64’s hardware is capable of performing both texture-tiling and +the N64's hardware is capable of performing both texture-tiling and alpha-lighting in a single pass, which is pretty much an ideal -hardware-acceleration architecture: It’s as good looking and generally +hardware-acceleration architecture: It's as good looking and generally faster than surface caching, without the need to build, download, and cache surfaces, and much better looking and about as fast as Gouraud shading. We hope to see similar capabilities implemented in PC @@ -43,8 +43,8 @@ also avoids having to do z-buffer clearing by splitting the z range into two parts, and alternating between the two parts from frame to frame; at the same time, the z-compare polarity is switched (from greater-than-or-equal to less-than-or-equal), so that the previous -frame’s z values are always considered more distant than the current -frame’s. +frame's z values are always considered more distant than the current +frame's. GLQuake was very easy to develop, taking only a weekend to get up and running, and that leads to another important point: OpenGL is also an @@ -53,20 +53,20 @@ levels, is written for OpenGL running on Win32, and when John needed a 3-D texture editing tool for modifying model skins, he was able to write it in one night by building it on OpenGL. After we finished Quake, we realized that about half our code and half our time was spent on tools, -rather than on the game engine itself, and the artists’ and level -designers’ productivity is heavily dependent on the tools they have to -use; considering all that, we’d be foolish not to use OpenGL, which is +rather than on the game engine itself, and the artists' and level +designers' productivity is heavily dependent on the tools they have to +use; considering all that, we'd be foolish not to use OpenGL, which is very well suited to such tasks. One good illustration of how much easier a good 3-D API can make development is how quickly John was able to add two eye-candy features to GLQuake: dynamic shadows and reflections. Dynamic shadows were -implemented by projecting a model’s silhouette onto the ground plane, -then alpha-blending that silhouette into the world. This doesn’t always +implemented by projecting a model's silhouette onto the ground plane, +then alpha-blending that silhouette into the world. This doesn't always work properly—for example, if the player is standing at the edge of a cliff, the shadow sticks out in the air—but it was added in a few hours, and most of the time looks terrific. Implementing it properly will take -only a day or two more and should run adequately fast; it’s a simple +only a day or two more and should run adequately fast; it's a simple matter of projecting the silhouette into the world, and onto the surfaces it encounters. @@ -88,33 +88,33 @@ into the mirror surface, to make the surface itself less than perfectly reflective and visible enough to seem real. Both alpha-blending and z-buffering are relatively new to PC games, but -are standard equipment on accelerators, and it’s a lot of fun seeing +are standard equipment on accelerators, and it's a lot of fun seeing what sorts of previously very difficult effects can now be up and working in a matter of hours. #### WinQuake {#Heading18} -I’m not going to spend much time on the Win32 port of Quake; most of +I'm not going to spend much time on the Win32 port of Quake; most of what I learned doing this consists of tedious details that are doubtless -well covered elsewhere, and frankly it wasn’t a particularly interesting -task and was harder than I expected, and I’m pretty much tired of the +well covered elsewhere, and frankly it wasn't a particularly interesting +task and was harder than I expected, and I'm pretty much tired of the whole thing. However, I will say that Win32 is clearly the future, especially now that NT is coming on strong, and like it or not, you had best learn to write games for Win32. Also, Internet gaming is becoming -ever more important, and Win32’s built-in TCP/IP support is a big +ever more important, and Win32's built-in TCP/IP support is a big advantage over DOS; that alone was enough to convince us we had to port -Quake. As a last comment, I’d say that it is nice to have Windows take +Quake. As a last comment, I'd say that it is nice to have Windows take care of device configuration and interfacing—now if only we could get manufacturers to write drivers for those devices that actually worked reliably! This will come as no surprise to veteran Windows programmers, who have suffered through years of buggy 2-D Windows drivers, but if -you’re new to Windows programming, be prepared to run into and learn to +you're new to Windows programming, be prepared to run into and learn to work around—or at least document in your readme files—driver bugs on a regular basis. Still, when you get down to it, the future of gaming is a networked -Win32 world, and that’s that, so if you haven’t already moved to Win32, -I’d say it’s time. +Win32 world, and that's that, so if you haven't already moved to Win32, +I'd say it's time. #### QuakeWorld {#Heading19} @@ -123,7 +123,7 @@ done as a learning experience; it is not a commercial product, but is freely distributed on the Internet. The idea behind it was to try to improve the multiplayer experience, especially for people linked by modem, by reducing actual and perceived latency. Before I discuss -QuakeWorld, however, I should discuss the evolution of Quake’s +QuakeWorld, however, I should discuss the evolution of Quake's multiplayer code. ------------------------ --------------------------------- -------------------- diff --git a/70-08.md b/70-08.md index b10f3fb..e2649d0 100644 --- a/70-08.md +++ b/70-08.md @@ -11,11 +11,11 @@ more flexible and robust than peer-to-peer, and it is much easier to have players come and go at will with client-server. Quake is client-server from the ground up, and even in single-player mode, messages are passed through buffers between the client code and the -server code; it’s quite likely that the client and server would have +server code; it's quite likely that the client and server would have been two processes, in fact, were it not for the need to support DOS. -Client-server turned out to be the right decision, because Quake’s +Client-server turned out to be the right decision, because Quake's ability to support persistent, come-and-go-as-you-please Internet -servers with up to 16 people has been instrumental in the game’s high +servers with up to 16 people has been instrumental in the game's high visibility in the press, and its lasting popularity. However, client-server is not without a cost, because, in its pure form, @@ -31,10 +31,10 @@ respects client-server is very attractive. So the big task with client-server is to reduce latency. As of the release of QTest1, the first and last prerelease of Quake, -John had smoothed net play considerably by actually keeping the client’s +John had smoothed net play considerably by actually keeping the client's virtual time a bit earlier than the time of the last server packet, and -interpolating events between the last two packets to the client’s -virtual time. This meant that events didn’t snap to whatever packet had +interpolating events between the last two packets to the client's +virtual time. This meant that events didn't snap to whatever packet had arrived last, and got rid of considerable jerking and stuttering. Unfortunately, it actually increased latency, because of the retarding of time needed to make the interpolation possible. This illustrates a @@ -42,27 +42,27 @@ common tradeoff, which is that reduced latency often makes for rougher play. ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ![](images/i.jpg) *Reduced latency also often makes for more frustrating play. It’s actually not hard to reduce the latency perceived by the player, but many of the approaches that reduce latency introduce the potential for paradoxes that can be quite distracting and annoying. For example, a player may see a rocket go by, and think they’ve dodged it, only to find themselves exploding a second later as the difference of opinion between his simulation and the other simulation is resolved to his detriment.* + ![](images/i.jpg) *Reduced latency also often makes for more frustrating play. It's actually not hard to reduce the latency perceived by the player, but many of the approaches that reduce latency introduce the potential for paradoxes that can be quite distracting and annoying. For example, a player may see a rocket go by, and think they've dodged it, only to find themselves exploding a second later as the difference of opinion between his simulation and the other simulation is resolved to his detriment.* ------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Worse, QTest1 was prone to frequent hitching over all but the best connections, because it was built around reliable packet delivery (TCP) -provided by the operating system. Whenever a packet didn’t arrive, there +provided by the operating system. Whenever a packet didn't arrive, there was a long pause waiting for the retransmission. After QTest1, John realized that this was a fundamentally wrong assumption, and changed the code to use unreliable packet delivery (UDP), sending the relevant portion of the full state every time (possible only because the PVS can be used to cull most events in a level), and letting the game logic -itself deal with packets that didn’t arrive. A reliable sideband was +itself deal with packets that didn't arrive. A reliable sideband was used as well, but only for events like scores, not for gameplay state. -However, this was a good example of Carmack’s Law: John did not rewrite +However, this was a good example of Carmack's Law: John did not rewrite the net code to reflect this new fundamental assumption, and wound up with 8,000 lines of messy code that took right up until Quake shipped to debug. For QuakeWorld, John did rewrite the net code from scratch around the assumption of unreliable packet delivery, and it wound up as just 1,500 lines of clean, bug-free code. -In the long run, it’s cheaper to rewrite than to patch and modify! +In the long run, it's cheaper to rewrite than to patch and modify! So as of shipping Quake, multiplayer performance was quite smooth, but latency was still a major issue, often in the 250 to 400 ms range for @@ -72,7 +72,7 @@ runs 10 or 20 times a second, batching up inputs in between ticks, and sending out results after the tick. By contrast, QuakeWorld servers run immediately whenever a client sends input, knocking up to 50 or 100 ms off response time, although at the cost of a greater server processing -load. (A similar anti-latency idea that wasn’t implemented in QuakeWorld +load. (A similar anti-latency idea that wasn't implemented in QuakeWorld is having a separate thread that can send input off to the server as soon as it happens, instead of incurring up to a frame of latency.) @@ -82,38 +82,38 @@ server packet (after all, the client has all the information needed to move the player, unless an outside force intervenes), giving very responsive control. The rest of the world is drawn as of the latest server packet; this is jerkier than Quake, again showing that smoothness -is often a tradeoff for latency. The player’s prediction may, of course, +is often a tradeoff for latency. The player's prediction may, of course, result in a minor paradox; for example, if an explosion turns out to -have knocked the player sideways, the player’s location may suddenly +have knocked the player sideways, the player's location may suddenly jump without warning as the server packet arrives with the correct location. In the latest version of QuakeWorld, the other players are predicted as well, with consequently more frequent paradoxes, but smoother, more convincing motion. Platforms and doors are still not predicted, and consequently are still pretty jerky. It is, of course, -possible to predict more and more objects into the future; it’s a +possible to predict more and more objects into the future; it's a tradeoff of smoothness and perceived low latency for the frustration of -paradoxes—and that’s the way it’s going to stay until most people are +paradoxes—and that's the way it's going to stay until most people are connected to the Internet by something better than modems. #### Quake 2 {#Heading20} -I can’t talk in detail about Quake 2 as a game, but I can describe some -interesting technology features. The Quake 2 rendering engine isn’t +I can't talk in detail about Quake 2 as a game, but I can describe some +interesting technology features. The Quake 2 rendering engine isn't going to change that much from Quake; the improvements are largely in areas such as physics, gameplay, artwork, and overall design. The most interesting graphics change is in the preprocessing, where John has added support for radiosity lighting; that is, the ability to put a light source into the world and have the light bounced around the world realistically. This is sometimes terrific—it makes for great glowing -light around lava and hanging light panels—but in other cases it’s less +light around lava and hanging light panels—but in other cases it's less spectacular than the effects that designers can get by placing lots of direct-illumination light sources in a room, so the two methods can be used as needed. Also, radiosity is *very* computationally expensive, -approximately as expensive as BSPing. Most of the radiosity demos I’ve +approximately as expensive as BSPing. Most of the radiosity demos I've seen have been in one or two rooms, and the order of the problem goes up -tremendously on whole Quake levels. Here’s another case where the PVS is +tremendously on whole Quake levels. Here's another case where the PVS is essential; without it, radiosity processing time would be -O(polygons^2^), but with the PVS it’s +O(polygons^2^), but with the PVS it's O(polygons\*average\_potentially\_visible\_polygons), which is over an order of magnitude less (and increases approximately linearly, rather than as a squared function, with greater-level complexity). diff --git a/70-09.md b/70-09.md index ea0589f..b20f43a 100644 --- a/70-09.md +++ b/70-09.md @@ -9,7 +9,7 @@ Doom, a welcome change from the claustrophobic feel of Quake. Another likely change in Quake 2 is a shift from interpreted Quake-C code for game logic to compiled DLLs. Part of the incentive here is -performance—interpretation isn’t cheap—and part is debugging, because +performance—interpretation isn't cheap—and part is debugging, because the standard debugger can be used with DLLs. The drawback, of course, is portability; Quake-C program files are completely portable to any platform Quake runs on, with no modification or recompilation, but DLLs @@ -17,10 +17,10 @@ compiled for Win32 require a real porting effort to run anywhere else. Our thinking here is that there are almost no non-console platforms other than the PC that matter that much anymore, and for those few that do (notably the Mac and Linux), the DLLs can be ported along with the -core engine code. It just doesn’t make sense for easy portability to +core engine code. It just doesn't make sense for easy portability to tiny markets to impose a significant development and performance cost on the one huge market. Consoles will always require serious porting effort -anyway, so going to Win32-specific DLLs for the PC version won’t make +anyway, so going to Win32-specific DLLs for the PC version won't make much difference in the ease of doing console ports. Finally, Internet support will improve in Quake 2. Some of the @@ -32,7 +32,7 @@ letting the browser take care of most of the UI makes it possible to eliminate menuing and such from the Quake 2 engine. Think of being able to browse hundreds of Quake servers from a single Web page (much as you can today with QSpy, but with the advantage of a standard, familiar -interface and easy extensibility), and I think you’ll see why John +interface and easy extensibility), and I think you'll see why John considers this the game interface of the future. By the way, Quake 2 is currently being developed as a native Win32 app @@ -40,8 +40,8 @@ only; no DOS version is planned. ### Looking Forward {#Heading21} -In my address to the Computer Game Developer’s Conference in 1996, I -said that it wasn’t a bad time to start up a game company aimed at +In my address to the Computer Game Developer's Conference in 1996, I +said that it wasn't a bad time to start up a game company aimed at hardware-only rasterization, and trying to make a game that leapfrogged the competition. It looks like I was probably a year early, because hardware took longer to ship than I expected, although there was a good @@ -49,37 +49,37 @@ living to be made writing games that hardware vendors could bundle with their boards. Now, though, it clearly is time. By Christmas 1997, there will be several million fast accelerators out there, and by Christmas 1998, there will be tens of millions. At the same time, vastly more -people are getting access to the Internet, and it’s from the convergence +people are getting access to the Internet, and it's from the convergence of these two trends that I think the technology for the next generation of breakthrough real-time games will emerge. -John is already working on id’s next graphics engine, code-named Trinity +John is already working on id's next graphics engine, code-named Trinity and targeted around Christmas of 1998. Trinity is not only a hardware-only engine, its baseline system is a Pentium Pro 200-plus with MMX, 32 MB, and an accelerator capable of at least 50 megapixels and 300 K triangles per second with alpha blending and z-buffering. The goals of -Trinity are quite different from those of Quake. Quake’s primary +Trinity are quite different from those of Quake. Quake's primary technical goals were to do high-quality, well-lit, complex indoor scenes with 6 degrees of freedom, and to support client-server Internet play. -That was a good start, but only that. Trinity’s goals are to have much +That was a good start, but only that. Trinity's goals are to have much less-constrained, better-connected worlds than Quake. Imagine seeing through open landscape from one server to the next, and seeing the -action on adjacent servers in detail, in real time, and you’ll have an +action on adjacent servers in detail, in real time, and you'll have an idea of where things are heading in the near future. A huge graphics challenge for the next generation of games is level of -detail (LOD) management. If we’re to have larger, more open worlds, +detail (LOD) management. If we're to have larger, more open worlds, there will inevitably be more geometry visible at one time. At the same -time, the push for greater detail that’s been in progress for the past +time, the push for greater detail that's been in progress for the past four years or so will continue; people will start expecting to see real cracks and bumps when they get close to a wall, not just a picture of cracks and bumps painted on a flat wall. Without LOD, these two trends -are in direct opposition; there’s no way you can make the world larger +are in direct opposition; there's no way you can make the world larger and make all its surfaces more detailed at the same time, without bringing the renderer to its knees. The solution is to draw nearer surfaces with more detail than farther -surfaces. In itself, that’s not so hard, but doing it without popping +surfaces. In itself, that's not so hard, but doing it without popping and snapping being visible as you move about is quite a challenge. John has implemented fractal landscapes with constantly adjustable level of detail, and has made it so new vertices appear as needed and gradually @@ -97,7 +97,7 @@ more detailed when viewed up close, so LOD for moving polygon models will definitely be needed. One interesting side effect of morphing vertices as part of LOD is that -Gouraud shading doesn’t work very well with this approach. The problem +Gouraud shading doesn't work very well with this approach. The problem is that adding a new vertex causes a major shift in Gouraud shading, which is, after all, based on lighting at vertices. Consequently, two-pass alpha lighting and surface caching seem to be much better @@ -107,12 +107,12 @@ Some people worry that the widespread use of hardware acceleration will mean that 3-D programs will all look the same, and that there will no longer be much challenge in 3-D programming. I hope that this brief discussion of the tightly interconnected, highly detailed worlds toward -which we’re rapidly heading will help you realize that both the +which we're rapidly heading will help you realize that both the challenge and the potential of 3-D programming are in fact greater than -they’ve ever been. The trick is that rather than getting stuck in the -rut of established techniques, you must constantly strive to “do better -with less, in a different way”; keep learning and changing and trying -new approaches—and working your rear end off—and odds are you’ll be part +they've ever been. The trick is that rather than getting stuck in the +rut of established techniques, you must constantly strive to "do better +with less, in a different way"; keep learning and changing and trying +new approaches—and working your rear end off—and odds are you'll be part of the wave of the future. ------------------------ --------------------------------- ------------------------- diff --git a/about.md b/about.md index b8d3791..620e4b6 100644 --- a/about.md +++ b/about.md @@ -23,7 +23,7 @@ even advertisements held information for me to assimilate. John Romero clued me in early to the articles by Michael Abrash. The good stuff. Graphics hardware. Code optimization. Knowledge and wisdom for the aspiring developer. They were even fun to read. For a long time, -my personal quest was to find a copy of Michael’s first book, *Zen of +my personal quest was to find a copy of Michael's first book, *Zen of Assembly Language.* I looked in every bookstore I visited, but I never did find it. I made do with the articles I could dig up. @@ -34,12 +34,12 @@ Software. A year or two later, after Wolfenstein-3D, I bumped into Michael (in a virtual sense) for the first time. I was looking around on M&T Online, a -BBS run by the Dr. Dobb’s publishers before the Internet explosion, when +BBS run by the Dr. Dobb's publishers before the Internet explosion, when I saw some posts from the man himself. We traded email, and for a couple -months we played tag-team gurus on the graphics forum before Doom’s +months we played tag-team gurus on the graphics forum before Doom's development took over my life. -A friend of Michael’s at his new job put us back in touch with each +A friend of Michael's at his new job put us back in touch with each other after Doom began to make its impact, and I finally got a chance to meet up with him in person. @@ -48,15 +48,15 @@ to Michael and an interested group of his coworkers. Every few days afterwards, I would get an email from Michael asking for an elaboration on one of my points, or discussing an aspect of the future of graphics. -Eventually, I popped the question—I offered him a job at id. “Just +Eventually, I popped the question—I offered him a job at id. "Just think: no reporting to anyone, an opportunity to code all day, starting with a clean sheet of paper. A chance to do *the right thing* as a -programmer.” It didn’t work. I kept at it though, and about a year later +programmer." It didn't work. I kept at it though, and about a year later I finally convinced him to come down and take a look at id. I was working on Quake. Going from Doom to Quake was a tremendous step. I knew where I wanted to -end up, but I wasn’t at all clear what the steps were to get there. I +end up, but I wasn't at all clear what the steps were to get there. I was trying a huge number of approaches, and even the failures were teaching me a lot. My enthusiasm must have been contagious, because he took the job. @@ -65,7 +65,7 @@ Much heroic programming ensued. Several hundred thousand lines of code were written. And rewritten. And rewritten. And rewritten. In hindsight, I have plenty of regrets about various aspects of Quake, -but it is a rare person that doesn’t freely acknowledge the technical +but it is a rare person that doesn't freely acknowledge the technical triumph of it. We nailed it. Sure, a year from now I will have probably found a new perspective that will make me cringe at the clunkiness of some part of Quake, but at the moment it still looks pretty damn good to @@ -75,15 +75,15 @@ I was very happy to have Michael describe much of the Quake technology in his ongoing magazine articles. We learned a lot, and I hope we managed to teach a bit. -When a non-programmer hears about Michael’s articles or the source code -I have released, I usually get a stunned “WTF would you do that for???” +When a non-programmer hears about Michael's articles or the source code +I have released, I usually get a stunned "WTF would you do that for???" look. -They don’t get it. +They don't get it. Programming is not a zero-sum game. Teaching something to a fellow -programmer doesn’t take it away from you. I’m happy to share what I can, -because I’m in it for the love of programming. The Ferraris are just +programmer doesn't take it away from you. I'm happy to share what I can, +because I'm in it for the love of programming. The Ferraris are just gravy, honest! This book contains many of the original articles that helped launch my diff --git a/about_author.md b/about_author.md index 5474313..faa0fe8 100644 --- a/about_author.md +++ b/about_author.md @@ -17,14 +17,14 @@ for showing me how much fun it could be in his even longer-vanished but genuinely terrific column in *Creative Computing* (the most enjoyable single column I have ever read in a computer magazine; I used to haunt the mailbox around the beginning of the month just to see what Will had -to say). Thanks to Robert Keller, Erin O’Connor, Liz Oakley, Steve -Baker, and the rest of the cast of thousands that made *Programmer’s +to say). Thanks to Robert Keller, Erin O'Connor, Liz Oakley, Steve +Baker, and the rest of the cast of thousands that made *Programmer's Journal* a uniquely fun magazine—especially Erin, who did more than anyone to teach me the proper use of the English language. (To this day, -Erin will still patiently explain to me when one should use “that” and -when one should use “which,” even though eight years of instruction on +Erin will still patiently explain to me when one should use "that" and +when one should use "which," even though eight years of instruction on this and related topics have left no discernible imprint on my brain.) -Thanks to Tami Zemel, Monica Berg, and the rest of the *Dr. Dobb’s +Thanks to Tami Zemel, Monica Berg, and the rest of the *Dr. Dobb's Journal* crew for excellent, professional editing, and for just being great people. Thanks to the Coriolis gang for their tireless hard work: Jeff Duntemann, Kim Eoff, Jody Kent, Robert Clarfield, and Anthony @@ -37,7 +37,7 @@ Hardenbergh, Dave Miller, Steve Levy, Jack Davis, Duane Strong, Daev Rohr, Bill Weber, Dan Gochnauer, Patrick Milligan, Tom Wilson, Peter Klerings, Dave Methvin, Mick Brown, the people in the ibm.pc/fast.code topic on Bix, and all the rest of you who have been so generous with -your ideas and suggestions. I’ve done my best to acknowledge +your ideas and suggestions. I've done my best to acknowledge contributors by name in this book, but if your name is omitted, my apologies, and consider yourself thanked; this book could not have happened without you. And, of course, thanks to Shay and Emily for their diff --git a/appendix-a.md b/appendix-a.md index 46978fe..411fda3 100644 --- a/appendix-a.md +++ b/appendix-a.md @@ -5,15 +5,15 @@ Afterword {#Heading1} --------- -If you’ve followed me this far, you might agree that we’ve come through -some rough country. Still, I’m of the opinion that hard-won knowledge is +If you've followed me this far, you might agree that we've come through +some rough country. Still, I'm of the opinion that hard-won knowledge is the best knowledge, not only because it sticks to you better, but also because winning a hard race makes it easier to win the next one. This is an unusual book in that sense: In addition to being a compilation of much of what I know about fast computer graphics, it is a journal recording some of the process by which I discovered and refined -that knowledge. I didn’t just sit down one day to write this book—I +that knowledge. I didn't just sit down one day to write this book—I wrote it over a period of years and published its component parts in many places. It is a journal of my successes and frustrations, with side glances of my life as it happened along the way. @@ -30,7 +30,7 @@ is much the richer for it. Here and there, I learned things that had nothing at all to do with fast graphics. -For example: I’m not a doomsayer who thinks American education lags +For example: I'm not a doomsayer who thinks American education lags hopelessly behind the rest of the Western world, but now and then something happens that makes me wonder. Some time back, I received a letter from one Melvyn J. Lafitte requesting that I spend some time in @@ -44,7 +44,7 @@ His technique involved defining polygon vertices in clockwise order, as viewed from the visible side. Then, he explained, one can use the cross-product equations found in any math book to determine which way the perpendicular to the polygon is pointing. Better yet, he pointed -out, it’s necessary to calculate only the Z component of the +out, it's necessary to calculate only the Z component of the perpendicular, and only the sign of the Z component need actually be tested. @@ -58,28 +58,28 @@ fundamental techniques of 3-D drawing. Melvyn lives in Moens, France. At the time he wrote me, Melvyn was 17 years old. Try to imagine any American 17-year-old of your acquaintance inventing backface removal. Try to imagine any teenager you know even -using the phrase “the cross-product equations found in any math book.” +using the phrase "the cross-product equations found in any math book." Not to mention that Melvyn was able to write a highly technical letter -in English; and if Melvyn’s English was something less than flawless, it +in English; and if Melvyn's English was something less than flawless, it was perfectly understandable, and, in my experience, vastly better than -an average, or even well-educated, American’s French. Please understand, +an average, or even well-educated, American's French. Please understand, I believe we Americans excel in a wide variety of ways, but I worry that when it comes to math and foreign languages, we are becoming a nation of *têtes de pomme de terre*. -Maybe I worry too much. If the glass is half empty, well, it’s also half +Maybe I worry too much. If the glass is half empty, well, it's also half full. Plainly, something I wrote inspired Melvyn to do something that is wonderful, whether he realizes it or not. And it has been tremendously gratifying to sense in the letters I have received the same feeling of remarkably smart people going out there and doing amazing things just for the sheer unadulterated fun of it. -I don’t think I’m exaggerating too much (well, maybe a little) when I -say that this sort of fun is what I live for. I’m glad to see that so +I don't think I'm exaggerating too much (well, maybe a little) when I +say that this sort of fun is what I live for. I'm glad to see that so many of you share that same passion. Good luck. Thank you for your input, your code, and all your kind words. -Don’t be afraid to attempt the impossible. Simply knowing what is +Don't be afraid to attempt the impossible. Simply knowing what is impossible is useful knowledge—and you may well find, in the wake of some unexpected success, that not half of the things we call impossible have any right at all to wear the label. diff --git a/book-index.md b/book-index.md index 0ef03b7..4b7724d 100644 --- a/book-index.md +++ b/book-index.md @@ -143,7 +143,7 @@ viewing variance, 1249 moving models in 3-D drawings, 1212-1222 -painter’s algorithm, 1099, 1104-1105 +painter's algorithm, 1099, 1104-1105 perspective correctness problem, 1248-1250 @@ -840,7 +840,7 @@ internal animation, 872 masked images, 871-872 -Antialiasing, Wu’s algorithm, 776-779, **780-791,** 791-792 +Antialiasing, Wu's algorithm, 776-779, **780-791,** 791-792 Apparent motion, in animation, 1064 @@ -880,7 +880,7 @@ objectives, 28 optimizing instructions, 23-24 -programmer’s responsibilities, 27-29 +programmer's responsibilities, 27-29 rearranging instructions, 418-419 @@ -1004,7 +1004,7 @@ principles, 796-798 shearing, 813 -“Black box” approach, and future of programming, 725-726 +"Black box" approach, and future of programming, 725-726 Blocks. *See* Restartable blocks. @@ -1054,7 +1054,7 @@ pairing in U-pipe, 405 x86 family CPUs, performance, 140 -Bresenham’s line-drawing algorithm +Bresenham's line-drawing algorithm basic algorithm @@ -1080,7 +1080,7 @@ integer-based implementation, 685-687 potential optimizations, 705 -Bresenham’s run-length slice algorithm. *See* Run-length slice +Bresenham's run-length slice algorithm. *See* Run-length slice algorithm. Bridges, John @@ -1189,7 +1189,7 @@ culling to frustum, 1181-1184 overdraw problem, 1184-1185 -painter’s algorithm, 1099-1106 +painter's algorithm, 1099-1106 polygon culling, 1181-1184 @@ -1545,7 +1545,7 @@ Code recursion vs. data recursion, 1108-1110 -Euclid’s algorithm, 198-199 +Euclid's algorithm, 198-199 Collision detection demo program, **531-534** @@ -1577,9 +1577,9 @@ using subset of DAC, 649 Color cycling demo program, 643, **644-648,** 648-649 -Color Don’t Care register, 534 +Color Don't Care register, 534 -Color Don’t Care register demo program, **535-537,** 535 +Color Don't Care register demo program, **535-537,** 535 Color mapping demo program, EGA, **551-555** @@ -1850,7 +1850,7 @@ Data recursion vs. code recursion, 1108 -Euclid’s algorithm, 200 +Euclid's algorithm, 200 inorder tree traversal, 1108, **1109-1110,** 1110 @@ -2039,7 +2039,7 @@ Division, 32-bit, 181-184, 1008 DMA (direct memory access), and DRAM refresh, 95 -“Don’t care” planes, 535 +"Don't care" planes, 535 DOS function calls @@ -2142,7 +2142,7 @@ pixel drawing optimization, 1074, 1086 -painter’s algorithm and overdraw problem, 1184 +painter's algorithm and overdraw problem, 1184 single-color drawing with write mode 3, 831-832 @@ -2406,7 +2406,7 @@ prefetch queue cycle-eater, 86 wait states, 101 -Euclid’s algorithm +Euclid's algorithm algorithm, 197 @@ -2420,7 +2420,7 @@ recursive implementations, **198, 200** **EVGALine** function -Bresenham’s algorithm +Bresenham's algorithm assembly implementation, 671, **675-677** @@ -2773,7 +2773,7 @@ Set/Reset register, 666 brute-force approach, 195 -Euclid’s algorithm +Euclid's algorithm code recursive approach, 198 @@ -2785,7 +2785,7 @@ GCD (Greatest Common Denominator) problem brute-force approach, 193-196 -Euclid’s algorithm, 197-200 +Euclid's algorithm, 197-200 subtraction approach, 196-197 @@ -3154,7 +3154,7 @@ Latches and bit mask, 470 -and Color Don’t Care register, **535-537,** 535 +and Color Don't Care register, **535-537,** 535 and CPU reads, 530 @@ -3264,7 +3264,7 @@ Line-drawing algorithms accumulated pixels approach (Jim Mackraz), 678 -Bresenham’s algorithms +Bresenham's algorithms basic line-drawing algorithm, 655-661, **661-665,** 665-671, **671-677** @@ -3394,9 +3394,9 @@ vs. rotating or shifting, 145-146 word count program -author’s implementation, 303, 304 +author's implementation, 303, 304 -David Stafford’s implementation, **309-311,** 317-319 +David Stafford's implementation, **309-311,** 317-319 WC50 (Terje Mathisen), 307 @@ -3688,7 +3688,7 @@ Multi-word arithmetic, 147-148 **NEG** EAX instruction, 222 -Negation, two’s complement, 171 +Negation, two's complement, 171 **Next1** function, **353** @@ -3724,13 +3724,13 @@ Object-oriented programming, 725-726 360x480 256-color mode line drawing demo program, **615** -Bresenham’s line-drawing algorithm, **662,** 668-669 +Bresenham's line-drawing algorithm, **662,** 668-669 **Octant1** function 360x480 256-color mode line drawing demo program, **616** -Bresenham’s line-drawing algorithm, **663,** 668-669 +Bresenham's line-drawing algorithm, **663,** 668-669 Octants, and line orientations, 666-667 @@ -3994,7 +3994,7 @@ Overdraw problem, VSD and beam trees, 1185-1186 -painter’s algorithm, 1184-1185 +painter's algorithm, 1184-1185 sorted spans, 1215 @@ -4056,7 +4056,7 @@ split screen and page flipping, **820-825,** **825-830,** 836-837 320x400 256-color mode, **600-605** -Painter’s algorithm +Painter's algorithm *See also* 3-D animation; 3-D drawing. @@ -4346,9 +4346,9 @@ Pixel drawing optimization, 1074, 1086 -painter’s algorithm and overdraw problem, 1184 +painter's algorithm and overdraw problem, 1184 -Pixel intensity calculations, Wu’s antialiasing algorithm, 778-779 +Pixel intensity calculations, Wu's antialiasing algorithm, 778-779 Pixel values, mapping to colors, 548-551, **551-555** @@ -4388,7 +4388,7 @@ and bit mask, 465 capturing and restoring screens, 541-542, **543-547,** 547-548 -and Color Don’t Care register, 534-535, **535-537** +and Color Don't Care register, 534-535, **535-537** fonts, in text modes, 516 @@ -4688,7 +4688,7 @@ Profiling, and 80x87 emulator, Borland C++, 999 Program size vs. clock cycles, 28 -*Programmer’s Guide to PC Video Systems* (book), 651 +*Programmer's Guide to PC Video Systems* (book), 651 Projection @@ -4832,7 +4832,7 @@ Read mode, 0, 521 Read mode 1 -Color Don’t Care register, 534 +Color Don't Care register, 534 overview, 525-526 @@ -4878,7 +4878,7 @@ code recursion vs. data recursion, 1108-1110 -Euclid’s algorithm, 198-199 +Euclid's algorithm, 198-199 compiler-based optimization, 1112-1113 @@ -4888,7 +4888,7 @@ vs. code recursion, 1108-1110 compiler-based optimization, 1112-1113 -Euclid’s algorithm, 200 +Euclid's algorithm, 200 inorder tree traversal, 1108-1110 @@ -4904,7 +4904,7 @@ Reference materials bitmapped text, drawing, 471 -Bresenham’s line-drawing algorithm, 660 +Bresenham's line-drawing algorithm, 660 BSP trees, 1114, 1157 @@ -5162,7 +5162,7 @@ vertical, in texture mapping, 1084-1086 **ScanBuffer** assembly routine -author’s implementation, **301-302**, **303-304** +author's implementation, **301-302**, **303-304** hand-optimized implementation(Willem Clements), **313-315** @@ -5816,7 +5816,7 @@ viewing variance, 1249 moving models in 3-D drawings, 1212-1222 -painter’s algorithm, 1099, 1104-1105 +painter's algorithm, 1099, 1104-1105 perspective correctness problem, 1248-1250 @@ -6192,7 +6192,7 @@ resolution, 360x480 256-color mode, 619-620 Two-pass lighting, 1262 -Two’s complement negation, 171 +Two's complement negation, 171 **U** @@ -6236,7 +6236,7 @@ unit vectors, dot product, 1136-1137 **VectorsUp** function -Bresenham’s line-drawing algorithm, **664-665** +Bresenham's line-drawing algorithm, **664-665** 360x480 256-color mode line drawing program, **617-618** @@ -6298,7 +6298,7 @@ VGA compatibility, 446-447, 610-611 VGA memory -Color Don’t Care register, **535-537**, 535 +Color Don't Care register, **535-537**, 535 CPU reads, 520, 526 @@ -6350,7 +6350,7 @@ and page flipping, 444-445 read mode 1 -Color Don’t Care register, 534 +Color Don't Care register, 534 overview, 525-526, 531 @@ -6418,7 +6418,7 @@ vs. write mode 3, 832, 844 Color Compare register, in read mode 1, 531 -Color Don’t Care register, in read mode 1, 534 +Color Don't Care register, in read mode 1, 534 Color Select register, color paging, 628-629 @@ -6701,11 +6701,11 @@ lookup table, **303**, 304, 317-319 **ScanBuffer** assembly routine -author’s implementation, **301-302** +author's implementation, **301-302** -Stafford, David’s, **309-311**, 317-319 +Stafford, David's, **309-311**, 317-319 -Willem Clements’ implementation, **313-315** +Willem Clements' implementation, **313-315** as state machine, 315 diff --git a/index.md b/index.md index 81a76c6..9bdc02e 100644 --- a/index.md +++ b/index.md @@ -12,18 +12,18 @@ Michael Abrash's Graphics Programming Black Book Special Edition - [The Human Element of Code Optimization](01-01.html#Heading2) - [Understanding High Performance](01-01.html#Heading3) - - [When Fast Isn’t Fast](01-01.html#Heading4) + - [When Fast Isn't Fast](01-01.html#Heading4) - [Rules for Building High-Performance Code](01-02.html#Heading5) - - [Know Where You’re Going](01-02.html#Heading6) + - [Know Where You're Going](01-02.html#Heading6) - [Make a Big Map](01-02.html#Heading7) - [Make Lots of Little Maps](01-02.html#Heading8) - [Know the Territory](01-03.html#Heading9) - [Know When It Matters](01-04.html#Heading10) - [Always Consider the Alternatives](01-04.html#Heading11) - [Know How to Turn On the Juice](01-05.html#Heading12) - - [Where We’ve Been, What We’ve Seen](01-06.html#Heading13) - - [Where We’re Going](01-06.html#Heading14) + - [Where We've Been, What We've Seen](01-06.html#Heading13) + - [Where We're Going](01-06.html#Heading14) 2. [Chapter 2—A World Apart](02-01.html#Heading1) - [The Unique Nature of Assembly Language @@ -65,7 +65,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition Performance](04-01.html#Heading2) - [Cycle-Eaters](04-01.html#Heading3) - [The Nature of Cycle-Eaters](04-01.html#Heading4) - - [The 8088’s Ancestral Cycle-Eaters](04-01.html#Heading5) + - [The 8088's Ancestral Cycle-Eaters](04-01.html#Heading5) - [The 8-Bit Bus Cycle-Eater](04-01.html#Heading6) - [The Impact of the 8-Bit Bus Cycle-Eater](04-02.html#Heading7) @@ -133,10 +133,10 @@ Michael Abrash's Graphics Programming Black Book Special Edition 8. [Chapter 8—Speeding Up C with Assembly Language](08-01.html#Heading1) - - [Jumping Languages When You Know It’ll + - [Jumping Languages When You Know It'll Help](08-01.html#Heading2) - - [Billy, Don’t Be a Compiler](08-01.html#Heading3) - - [Don’t Call Your Functions on Me, Baby](08-01.html#Heading4) + - [Billy, Don't Be a Compiler](08-01.html#Heading3) + - [Don't Call Your Functions on Me, Baby](08-01.html#Heading4) - [Stack Frames Slow So Much](08-02.html#Heading5) - [Torn Between Two Segments](08-02.html#Heading6) - [Why Speeding Up Is Hard to Do](08-02.html#Heading7) @@ -195,7 +195,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition - [POPF and the 286](11-07.html#Heading17) 12. [Chapter 12—Pushing the 486](12-01.html#Heading1) - - [It’s Not Just a Bigger 386](12-01.html#Heading2) + - [It's Not Just a Bigger 386](12-01.html#Heading2) - [Enter the 486](12-01.html#Heading3) - [Rules to Optimize By](12-01.html#Heading4) - [The Hazards of Indexed Addressing](12-01.html#Heading5) @@ -237,7 +237,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition - [Circular Lists](15-03.html#Heading5) - [Hi/Lo in 24 Bytes](15-04.html#Heading6) - 16. [Chapter 16—There Ain’t No Such Thing as the Fastest + 16. [Chapter 16—There Ain't No Such Thing as the Fastest Code](16-01.html#Heading1) - [Lessons Learned in the Pursuit of the Ultimate Word Counter](16-01.html#Heading2) @@ -259,7 +259,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition 17. [Chapter 17—The Game of Life](17-01.html#Heading1) - [The Triumph of Algorithmic Optimization in a Cellular Automata Game](17-01.html#Heading2) - - [Conway’s Game](17-01.html#Heading3) + - [Conway's Game](17-01.html#Heading3) - [The Rules of the Game](17-01.html#Heading4) - [Where Does the Time Go?](17-03.html#Heading5) - [The Hazards and Advantages of @@ -270,13 +270,13 @@ Michael Abrash's Graphics Programming Black Book Special Edition - [Acting on What We Know](17-06.html#Heading10) - [The Challenge That Ate My Life](17-08.html#Heading11) - 18. [Chapter 18—It’s a plain Wonderful Life](18-01.html#Heading1) + 18. [Chapter 18—It's a plain Wonderful Life](18-01.html#Heading1) - [Optimization beyond the Pale](18-01.html#Heading2) - [Breaking the Rules](18-01.html#Heading3) - [Table-Driven Magic](18-02.html#Heading4) - [Keeping Track of Change with a Change List](18-04.html#Heading5) - - [A Layperson’s Overview of QLIFE](18-05.html#Heading6) + - [A Layperson's Overview of QLIFE](18-05.html#Heading6) 19. [Chapter 19—Pentium: Not the Same Old Song](19-01.html#Heading1) - [Learning a Whole Different Set of Optimization @@ -292,7 +292,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition - [Going Superscalar](19-04.html#Heading11) 20. [Chapter 20—Pentium Rules](20-01.html#Heading1) - - [How Your Carbon-Based Optimizer Can Put the “Super” in + - [How Your Carbon-Based Optimizer Can Put the "Super" in Superscalar](20-01.html#Heading2) - [An Instruction in Every Pipe](20-01.html#Heading3) - [V-Pipe-Capable Instructions](20-02.html#Heading4) @@ -300,19 +300,19 @@ Michael Abrash's Graphics Programming Black Book Special Edition - [Superscalar Notes](20-04.html#Heading6) - [Register Starvation](20-04.html#Heading7) - 21. [Chapter 21—Unleashing the Pentium’s + 21. [Chapter 21—Unleashing the Pentium's V-Pipe](21-01.html#Heading1) - [Focusing on Keeping Both Pentium Pipes Full](21-01.html#Heading2) - [Address Generation Interlocks](21-01.html#Heading3) - [Register Contention](21-02.html#Heading4) - [Exceptions to Register Contention](21-02.html#Heading5) - - [Who’s in First?](21-02.html#Heading6) + - [Who's in First?](21-02.html#Heading6) - [Pentium Optimization in Action](21-03.html#Heading7) - [A Quick Note on the 386 and 486](21-05.html#Heading8) 22. [Chapter 22—Zenning and the Flexible Mind](22-01.html#Heading1) - - [Taking a Spin through What You’ve + - [Taking a Spin through What You've Learned](22-01.html#Heading2) - [Zenning](22-01.html#Heading3) @@ -343,7 +343,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition Mechanisms](25-01.html#Heading2) - [VGA Data Rotation](25-01.html#Heading3) - [The Bit Mask](25-01.html#Heading4) - - [The VGA’s Set/Reset Circuitry](25-03.html#Heading5) + - [The VGA's Set/Reset Circuitry](25-03.html#Heading5) - [Setting All Planes to a Single Color](25-04.html#Heading6) - [Manipulating Planes Individually](25-05.html#Heading7) @@ -359,7 +359,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition - [Write Mode 2, Chunky Bitmaps,and Text-Graphics Coexistence](27-01.html#Heading2) - [Write Mode 2 and Set/Reset](27-01.html#Heading3) - - [A Byte’s Progress in Write Mode 2](27-01.html#Heading4) + - [A Byte's Progress in Write Mode 2](27-01.html#Heading4) - [Copying Chunky Bitmaps to VGA Memory Using Write Mode 2](27-02.html#Heading5) - [Drawing Color-Patterned Lines Using Write Mode @@ -371,11 +371,11 @@ Michael Abrash's Graphics Programming Black Book Special Edition Back](27-04.html#Heading9) 28. [Chapter 28—Reading VGA Memory](28-01.html#Heading1) - - [Read Modes 0 and 1, and the Color Don’t Care + - [Read Modes 0 and 1, and the Color Don't Care Register](28-01.html#Heading2) - [Read Mode 0](28-01.html#Heading3) - [Read Mode 1](28-03.html#Heading4) - - [When all Planes “Don’t Care”](28-04.html#Heading5) + - [When all Planes "Don't Care"](28-04.html#Heading5) 29. [Chapter 29—Saving Screens and Other VGA Mysteries](29-01.html#Heading1) @@ -392,7 +392,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition EGA and VGA](30-01.html#Heading2) - [How the Split Screen Works](30-01.html#Heading3) - [The Split Screen in Action](30-01.html#Heading4) - - [VGA and EGA Split-Screen Operation Don’t + - [VGA and EGA Split-Screen Operation Don't Mix](30-03.html#Heading5) - [Setting the Split-Screen-Related Registers](30-03.html#Heading6) @@ -419,7 +419,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition 32. [Chapter 32—Be It Resolved: 360x480](32-01.html#Heading1) - [Taking 256-Color Modes About as Far as the Standard VGA Can Take Them](32-01.html#Heading2) - - [Extended 256-Color Modes: What’s Not to + - [Extended 256-Color Modes: What's Not to Like?](32-01.html#Heading3) - [360x480 256-Color Mode](32-01.html#Heading4) - [How 360x480 256-Color Mode Works](32-04.html#Heading5) @@ -441,7 +441,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition - [256-Color Mode](33-02.html#Heading7) - [Setting the Palette RAM](33-02.html#Heading8) - [Setting the DAC](33-03.html#Heading9) - - [If You Can’t Call the BIOS, Who Ya Gonna + - [If You Can't Call the BIOS, Who Ya Gonna Call?](33-03.html#Heading10) - [An Example of Setting the DAC](33-03.html#Heading11) @@ -462,17 +462,17 @@ Michael Abrash's Graphics Programming Black Book Special Edition 35. [Chapter 35—Bresenham Is Fast, and Fast Is Good](35-01.html#Heading1) - - [Implementing and Optimizing Bresenham’s Line-Drawing + - [Implementing and Optimizing Bresenham's Line-Drawing Algorithm](35-01.html#Heading2) - [The Task at Hand](35-01.html#Heading3) - - [Bresenham’s Line-Drawing Algorithm](35-01.html#Heading4) + - [Bresenham's Line-Drawing Algorithm](35-01.html#Heading4) - [Strengths and Weaknesses](35-02.html#Heading5) - [An Implementation in C](35-03.html#Heading6) - [Looking at EVGALine](35-04.html#Heading7) - [Drawing Each Line](35-05.html#Heading8) - [Drawing Each Pixel](35-05.html#Heading9) - [Comments on the C Implementation](35-06.html#Heading10) - - [Bresenham’s Algorithm in Assembly](35-06.html#Heading11) + - [Bresenham's Algorithm in Assembly](35-06.html#Heading11) 36. [Chapter 36—The Good, the Bad, and the Run-Sliced](36-01.html#Heading1) @@ -528,9 +528,9 @@ Michael Abrash's Graphics Programming Black Book Special Edition Structure](41-01.html#Heading2) - [Nomenclature in Action](41-01.html#Heading3) - 42. [Chapter 42—Wu’ed in Haste; Fried, Stewed at + 42. [Chapter 42—Wu'ed in Haste; Fried, Stewed at Leisure](42-01.html#Heading1) - - [Fast Antialiased Lines Using Wu’s + - [Fast Antialiased Lines Using Wu's Algorithm](42-01.html#Heading2) - [Wu Antialiasing](42-01.html#Heading3) - [Tracing and Intensity in One](42-02.html#Heading4) @@ -582,7 +582,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition - [Drawing Order and Visual Quality](46-03.html#Heading7) 47. [Chapter 47—Mode X: 256-Color VGA Magic](47-01.html#Heading1) - - [Introducing the VGA’s Undocumented “Animation-Optimal” + - [Introducing the VGA's Undocumented "Animation-Optimal" Mode](47-01.html#Heading2) - [What Makes Mode X Special?](47-01.html#Heading3) - [Selecting 320x240 256-Color Mode](47-02.html#Heading4) @@ -591,7 +591,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition Quarter](47-06.html#Heading6) 48. [Chapter 48—Mode X Marks the Latch](48-01.html#Heading1) - - [The Internals of Animation’s Best Video Display + - [The Internals of Animation's Best Video Display Mode](48-01.html#Heading2) - [Allocating Memory in Mode X](48-03.html#Heading3) - [Copying Pixel Blocks within Display @@ -634,7 +634,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition X-Sharp](52-01.html#Heading1) - [The First Iteration of a Generalized 3-D Animation Package](52-01.html#Heading2) - - [This Chapter’s Demo Program](52-01.html#Heading3) + - [This Chapter's Demo Program](52-01.html#Heading3) - [A New Animation Framework: X-Sharp](52-07.html#Heading4) - [Three Keys to Realtime Animation Performance](52-07.html#Heading5) @@ -660,7 +660,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition 55. [Chapter 55—Color Modeling in 256-Color Mode](55-01.html#Heading1) - - [Pondering X-Sharp’s Color Model in an RGB State of + - [Pondering X-Sharp's Color Model in an RGB State of Mind](55-01.html#Heading2) - [A Color Model](55-01.html#Heading3) - [A Bonus from the BitMan](55-03.html#Heading4) @@ -687,16 +687,16 @@ Michael Abrash's Graphics Programming Black Book Special Edition Polygons](57-02.html#Heading6) - [Fast Texture Mapping](57-02.html#Heading7) - 58. [Chapter 58—Heinlein’s Crystal Ball, Spock’s Brain, and the + 58. [Chapter 58—Heinlein's Crystal Ball, Spock's Brain, and the 9-Cycle Dare](58-01.html#Heading1) - [Using the Whole-Brain Approach to Accelerate Texture Mapping](58-01.html#Heading2) - [Texture Mapping Redux](58-01.html#Heading3) - [Left-Brain Optimization](58-01.html#Heading4) - [A 90-Degree Shift in Perspective](58-02.html#Heading5) - - [That’s Nice—But it Sure as Heck Ain’t 9 + - [That's Nice—But it Sure as Heck Ain't 9 Cycles](58-03.html#Heading6) - - [Don’t Stop Thinking about Those + - [Don't Stop Thinking about Those Cycles](58-04.html#Heading7) - [Texture Mapping Notes](58-05.html#Heading8) @@ -756,7 +756,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition 3-D](63-01.html#Heading1) - [Knowing When to Hurl Conventional Math Wisdom Out the Window](63-01.html#Heading2) - - [Not Your Father’s Floating-Point](63-01.html#Heading3) + - [Not Your Father's Floating-Point](63-01.html#Heading3) - [Pentium Floating-Point Optimization](63-01.html#Heading4) - [Pipelining, Latency, and Throughput](63-02.html#Heading5) @@ -768,7 +768,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition - [Rounding Control](63-04.html#Heading11) - [A Farewell to 3-D Fixed-Point](63-04.html#Heading12) - 64. [Chapter 64—Quake’s Visible-Surface + 64. [Chapter 64—Quake's Visible-Surface Determination](64-01.html#Heading1) - [The Challenge of Separating All Things Seen from All Things Unseen](64-01.html#Heading2) @@ -795,7 +795,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition 65. [Chapter 65—3-D Clipping and Other Thoughts](65-01.html#Heading1) - - [Determining What’s Inside Your Field of + - [Determining What's Inside Your Field of View](65-01.html#Heading2) - [3-D Clipping Basics](65-01.html#Heading3) - [Intersecting a Line Segment with a @@ -806,7 +806,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition - [Advantages of Viewspace Clipping](65-04.html#Heading8) - [Further Reading](65-04.html#Heading9) - 66. [Chapter 66—Quake’s Hidden-Surface Removal](66-01.html#Heading1) + 66. [Chapter 66—Quake's Hidden-Surface Removal](66-01.html#Heading1) - [Struggling with Z-Order Solutions to the Hidden Surface Problem](66-01.html#Heading2) - [Creative Flux and Hidden Surfaces](66-01.html#Heading3) @@ -833,7 +833,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition - [1/z Span Sorting in Action](67-03.html#Heading8) - [Implementation Notes](67-05.html#Heading9) - 68. [Chapter 68—Quake’s Lighting Model](68-01.html#Heading1) + 68. [Chapter 68—Quake's Lighting Model](68-01.html#Heading1) - [A Radically Different Approach to Lighting Polygons](68-01.html#Heading2) - [The Lighting Conundrum](68-01.html#Heading3) @@ -849,7 +849,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition - [Two Final Notes on Surface Caching](68-04.html#Heading12) - 69. [Chapter 69—Surface Caching and Quake’s Triangle + 69. [Chapter 69—Surface Caching and Quake's Triangle Models](69-01.html#Heading1) - [Probing Hardware-Assisted Surfaces and Fast Model Animation Without Sprites](69-01.html#Heading2) @@ -862,7 +862,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition - [Drawing Triangle Models Fast](69-02.html#Heading7) - [Trading Subpixel Precision for Speed](69-03.html#Heading8) - - [An Idea that Didn’t Work](69-03.html#Heading9) + - [An Idea that Didn't Work](69-03.html#Heading9) - [An Idea that Did Work](69-03.html#Heading10) - [More Ideas that Might Work](69-04.html#Heading11) @@ -870,7 +870,7 @@ Michael Abrash's Graphics Programming Black Book Special Edition Future](70-01.html#Heading1) - [Preprocessing the World](70-01.html#Heading2) - [The Potentially Visible Set (PVS)](70-02.html#Heading3) - - [Passages: The Last-Minute Change that Didn’t + - [Passages: The Last-Minute Change that Didn't Happen](70-03.html#Heading4) - [Drawing the World](70-03.html#Heading5) - [Rasterization](70-04.html#Heading6) diff --git a/intro.md b/intro.md index b7abe68..9fc5871 100644 --- a/intro.md +++ b/intro.md @@ -10,69 +10,69 @@ onto a rocket during takeoff—in the middle of a hurricane. It seemed like the whole world was watching, waiting to see if id Software could top Doom; every casual e-mail tidbit or conversation with a visitor ended up posted on the Internet within hours. And meanwhile, we were -pouring everything we had into Quake’s technology; I’d often come in in +pouring everything we had into Quake's technology; I'd often come in in the morning to find John still there, working on a new idea so -intriguing that he couldn’t bear to sleep until he had tried it out. +intriguing that he couldn't bear to sleep until he had tried it out. Toward the end, when I spent most of my time speeding things up, I would spend the day in a trance writing optimized assembly code, stagger out of the Town East Tower into the blazing Texas heat, and somehow drive home on LBJ Freeway without smacking into any of the speeding pickups -whizzing past me on both sides. At home, I’d fall into a fitful sleep, +whizzing past me on both sides. At home, I'd fall into a fitful sleep, then come back the next day in a daze and do it again. Everything happened so fast, and under so much pressure, that sometimes I wonder how any of us made it through that without completely burning out. -At the same time, of course, it was tremendously exciting. John’s ideas +At the same time, of course, it was tremendously exciting. John's ideas were endless and brilliant, and Quake ended up establishing a new standard for Internet and first-person 3-D game technology. Happily, id has an enlightened attitude about sharing information, and was willing to let me write about the Quake technology—both how it worked and how it evolved. Over the two years I worked at id, I wrote a number of columns -about Quake in *Dr. Dobb’s Sourcebook*, as well as a detailed overview +about Quake in *Dr. Dobb's Sourcebook*, as well as a detailed overview for the 1997 Computer Game Developers Conference. You can find these in the latter part of this book; they represent a rare look into the development and inner workings of leading-edge software development, and I hope you enjoy reading them as much as I enjoyed developing the technology and writing about it. -The rest of this book is pretty much everything I’ve written over the -past decade about graphics and performance programming that’s still +The rest of this book is pretty much everything I've written over the +past decade about graphics and performance programming that's still relevant to programming today, and that covers a lot of ground. Most of *Zen of Graphics Programming, 2nd Edition* is in there (and the rest is on the CD); all of *Zen of Code Optimization* is there too, and even my 1989 book *Zen of Assembly Language*, with its long-dated 8088 cycle counts but a lot of useful perspectives, is on the CD. Add to that the most recent 20,000 words of Quake material, and you have most of what -I’ve learned over the past decade in one neat package. +I've learned over the past decade in one neat package. -I’m delighted to have all this material in print in a single place, -because over the past ten years I’ve run into a lot of people who have +I'm delighted to have all this material in print in a single place, +because over the past ten years I've run into a lot of people who have found my writings useful—and a lot more who would like to read them, but -couldn’t find them. It’s hard to keep programming material (especially +couldn't find them. It's hard to keep programming material (especially stuff that started out as columns) in print for very long, and I would like to thank The Coriolis Group, and particularly my good friend Jeff Duntemann (without whom not only this volume but pretty much my entire -writing career wouldn’t exist), for helping me keep this material +writing career wouldn't exist), for helping me keep this material available. -I’d also like to thank Jon Erickson, editor of *Dr. Dobb’s*, both for +I'd also like to thank Jon Erickson, editor of *Dr. Dobb's*, both for encouragement and general good cheer and for giving me a place to write whatever I wanted about realtime 3-D. It still amazes me that I was able -to find time to write a column every two months during Quake’s -development, and if Jon hadn’t made it so easy and enjoyable, it could +to find time to write a column every two months during Quake's +development, and if Jon hadn't made it so easy and enjoyable, it could never have happened. -I’d also like to thank Chris Hecker and Jennifer Pahlka of the Computer +I'd also like to thank Chris Hecker and Jennifer Pahlka of the Computer Game Developers Conference, without whose encouragement, nudging, and occasional well-deserved nagging there is no chance I would ever have written a paper for the CGDC—a paper that ended up being the most -comprehensive overview of the Quake technology that’s ever likely to be +comprehensive overview of the Quake technology that's ever likely to be written, and which appears in these pages. -I don’t have much else to say that hasn’t already been said elsewhere in +I don't have much else to say that hasn't already been said elsewhere in this book, in one of the introductions to the previous volumes or in one -of the astonishingly large number of chapters. As you’ll see as you -read, it’s been quite a decade for microcomputer programmers, and I have +of the astonishingly large number of chapters. As you'll see as you +read, it's been quite a decade for microcomputer programmers, and I have been extremely fortunate to not only be a part of it, but to be able to chronicle part of it as well.